Add support for multiline f-strings

+ Extend the parser to recognize the multiline f-strings, which the
documentation already implies will work.

The syntax is like:
```
x = 'hello'
y = 'world'

msg = f'''This is a multiline string.

Sending a message: '@x@ @y@'
'''
```

which produces:
```
This is a multiline string.

Sending a message: 'hello world'

```

+ Added some f-string tests cases to "62 string arithmetic" to exercise
the new behavior.
pull/10103/head
Peter Lesslie 3 years ago committed by Eli Schwartz
parent 78a6f3bd5c
commit d771fc7d0b
  1. 6
      docs/markdown/Syntax.md
  2. 22
      docs/markdown/snippets/support-multiline-fstring.md
  3. 7
      mesonbuild/interpreterbase/interpreterbase.py
  4. 21
      mesonbuild/mparser.py
  5. 43
      test cases/common/62 string arithmetic/meson.build
  6. 7
      test cases/common/62 string arithmetic/test.json

@ -178,7 +178,7 @@ These are raw strings that do not support the escape sequences listed
above. These strings can also be combined with the string formatting
functionality via `.format()` described below.
Note that multiline f-strings are not supported.
Note that multiline f-string support was added in version 0.63.
### String index
@ -211,8 +211,8 @@ As can be seen, the formatting works by replacing placeholders of type
*(Added 0.58)*
Format strings can be used as a non-positional alternative to the
string formatting functionality described above. Note that multiline f-strings
are not supported.
string formatting functionality described above. Note that multiline f-string
support was added in version 0.63.
```meson
n = 10

@ -0,0 +1,22 @@
## Added support for multiline fstrings
Added support for multiline f-strings which use the same syntax as f-strings
for string substition.
```meson
x = 'hello'
y = 'world'
msg = f'''Sending a message...
"@x@ @y@"
'''
```
which produces:
```
Sending a message....
"hello world"
```

@ -217,6 +217,9 @@ class InterpreterBase:
elif isinstance(cur, mparser.TernaryNode):
return self.evaluate_ternary(cur)
elif isinstance(cur, mparser.FormatStringNode):
if isinstance(cur, mparser.MultilineFormatStringNode):
return self.evaluate_multiline_fstring(cur)
else:
return self.evaluate_fstring(cur)
elif isinstance(cur, mparser.ContinueNode):
raise ContinueRequest()
@ -367,6 +370,10 @@ class InterpreterBase:
else:
return self.evaluate_statement(node.falseblock)
@FeatureNew('multiline format strings', '0.63.0')
def evaluate_multiline_fstring(self, node: mparser.MultilineFormatStringNode) -> InterpreterObject:
return self.evaluate_fstring(node)
@FeatureNew('format strings', '0.58.0')
def evaluate_fstring(self, node: mparser.FormatStringNode) -> InterpreterObject:
assert isinstance(node, mparser.FormatStringNode)

@ -114,6 +114,7 @@ class Lexer:
self.token_specification = [
# Need to be sorted longest to shortest.
('ignore', re.compile(r'[ \t]')),
('multiline_fstring', re.compile(r"f'''(.|\n)*?'''", re.M)),
('fstring', re.compile(r"f'([^'\\]|(\\.))*'")),
('id', re.compile('[_a-zA-Z][_0-9a-zA-Z]*')),
('number', re.compile(r'0[bB][01]+|0[oO][0-7]+|0[xX][0-9a-fA-F]+|0|[1-9]\d*')),
@ -203,9 +204,17 @@ class Lexer:
value = ESCAPE_SEQUENCE_SINGLE_RE.sub(decode_match, value)
except MesonUnicodeDecodeError as err:
raise MesonException(f"Failed to parse escape sequence: '{err.match}' in string:\n {match_text}")
elif tid == 'multiline_string':
tid = 'string'
elif tid in {'multiline_string', 'multiline_fstring'}:
# For multiline strings, parse out the value and pass
# through the normal string logic.
# For multiline format strings, we have to emit a
# different AST node so we can add a feature check,
# but otherwise, it follows the normal fstring logic.
if tid == 'multiline_string':
value = match_text[3:-3]
tid = 'string'
else:
value = match_text[4:-3]
lines = match_text.split('\n')
if len(lines) > 1:
lineno += len(lines) - 1
@ -298,7 +307,11 @@ class FormatStringNode(ElementaryNode[str]):
assert isinstance(self.value, str)
def __str__(self) -> str:
return "Format string node: '{self.value}' ({self.lineno}, {self.colno})."
return f"Format string node: '{self.value}' ({self.lineno}, {self.colno})."
class MultilineFormatStringNode(FormatStringNode):
def __str__(self) -> str:
return f"Multiline Format string node: '{self.value}' ({self.lineno}, {self.colno})."
class ContinueNode(ElementaryNode):
pass
@ -685,6 +698,8 @@ class Parser:
return StringNode(t)
if self.accept('fstring'):
return FormatStringNode(t)
if self.accept('multiline_fstring'):
return MultilineFormatStringNode(t)
return EmptyNode(self.current.lineno, self.current.colno, self.current.filename)
def key_values(self) -> ArgumentNode:

@ -1,4 +1,4 @@
project('string arithmetic', 'c')
project('string arithmetic', 'c', meson_version: '>=0.62.0')
assert('foo' + 'bar' == 'foobar')
assert('foo' + 'bar' + 'baz' == 'foobarbaz')
@ -6,3 +6,44 @@ assert('foo' + 'bar' + 'baz' == 'foobarbaz')
a = 'a'
b = 'b'
assert(a + b + 'c' == 'abc')
# ------------------------------------------------------------------------------
# format strings:
# ------------------------------------------------------------------------------
sub1 = 'the'
sub2 = ' quick\n'
sub3 = ' brown'
sub4 = '\nfox'
x = f'@sub1@@sub2@@sub3@@sub4@'
assert(x == sub1 + sub2 + sub3 + sub4)
assert(x == 'the quick\n brown\nfox')
# ------------------------------------------------------------------------------
# multi-line format strings
# ------------------------------------------------------------------------------
y_actual = f'''This is a multi-line comment with string substition:
"@sub1@@sub2@@sub3@@sub4@"
And I can even substitute the entry multiple times!
@sub1@
@sub2@
@sub3@
'''
y_expect = '''This is a multi-line comment with string substition:
"the quick
brown
fox"
And I can even substitute the entry multiple times!
the
quick
brown
'''
message('actual=' + y_actual)
message('expect=' + y_expect)
assert(y_actual == y_expect)

@ -0,0 +1,7 @@
{
"stdout": [
{
"line": "test cases/common/62 string arithmetic/meson.build:25: WARNING: Project targeting '>=0.62.0' but tried to use feature introduced in '0.63.0': multiline format strings."
}
]
}
Loading…
Cancel
Save