diff options
-rw-r--r-- | mesonbuild/interpreterbase.py | 14 | ||||
-rw-r--r-- | mesonbuild/mparser.py | 15 | ||||
-rw-r--r-- | test cases/common/238 fstrings/meson.build | 9 |
3 files changed, 36 insertions, 2 deletions
diff --git a/mesonbuild/interpreterbase.py b/mesonbuild/interpreterbase.py index 026eaf2..d486b89 100644 --- a/mesonbuild/interpreterbase.py +++ b/mesonbuild/interpreterbase.py @@ -708,6 +708,8 @@ class InterpreterBase: return self.evaluate_indexing(cur) elif isinstance(cur, mparser.TernaryNode): return self.evaluate_ternary(cur) + elif isinstance(cur, mparser.FormatStringNode): + return self.evaluate_fstring(cur) elif isinstance(cur, mparser.ContinueNode): raise ContinueRequest() elif isinstance(cur, mparser.BreakNode): @@ -926,6 +928,18 @@ The result of this is undefined and will become a hard error in a future Meson r else: return self.evaluate_statement(node.falseblock) + def evaluate_fstring(self, node: mparser.FormatStringNode) -> TYPE_var: + assert(isinstance(node, mparser.FormatStringNode)) + + def replace(match: T.Match[str]) -> str: + var = str(match.group(1)) + try: + return str(self.variables[var]) + except KeyError: + raise mesonlib.MesonException(f'Identifier "{var}" does not name a variable.') + + return re.sub(r'{([_a-zA-Z][_0-9a-zA-Z]*)}', replace, node.value) + def evaluate_foreach(self, node: mparser.ForeachClauseNode) -> None: assert(isinstance(node, mparser.ForeachClauseNode)) items = self.evaluate_statement(node.items) diff --git a/mesonbuild/mparser.py b/mesonbuild/mparser.py index 79f461e..1079682 100644 --- a/mesonbuild/mparser.py +++ b/mesonbuild/mparser.py @@ -114,6 +114,7 @@ class Lexer: self.token_specification = [ # Need to be sorted longest to shortest. ('ignore', re.compile(r'[ \t]')), + ('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*')), ('eol_cont', re.compile(r'\\\n')), @@ -189,7 +190,7 @@ class Lexer: curl_count -= 1 elif tid == 'dblquote': raise ParseException('Double quotes are not supported. Use single quotes.', self.getline(line_start), lineno, col) - elif tid == 'string': + elif tid in {'string', 'fstring'}: # Handle here and not on the regexp to give a better error message. if match_text.find("\n") != -1: mlog.warning(textwrap.dedent("""\ @@ -200,7 +201,7 @@ class Lexer: str(lineno), str(col) ) - value = match_text[1:-1] + value = match_text[2 if tid == 'fstring' else 1:-1] try: value = ESCAPE_SEQUENCE_SINGLE_RE.sub(decode_match, value) except MesonUnicodeDecodeError as err: @@ -288,6 +289,14 @@ class StringNode(ElementaryNode[str]): def __str__(self) -> str: return "String node: '%s' (%d, %d)." % (self.value, self.lineno, self.colno) +class FormatStringNode(ElementaryNode[str]): + def __init__(self, token: Token[str]): + super().__init__(token) + assert isinstance(self.value, str) + + def __str__(self) -> str: + return "Format string node: '{self.value}' ({self.lineno}, {self.colno})." + class ContinueNode(ElementaryNode): pass @@ -671,6 +680,8 @@ class Parser: return NumberNode(t) if self.accept('string'): return StringNode(t) + if self.accept('fstring'): + return FormatStringNode(t) return EmptyNode(self.current.lineno, self.current.colno, self.current.filename) def key_values(self) -> ArgumentNode: diff --git a/test cases/common/238 fstrings/meson.build b/test cases/common/238 fstrings/meson.build new file mode 100644 index 0000000..ffee053 --- /dev/null +++ b/test cases/common/238 fstrings/meson.build @@ -0,0 +1,9 @@ +project('meson-test', 'c') + +n = 10 +m = 'bar' +s = f'test {n} string ({n}): {m}' + +if s != 'test 10 string (10): bar' + error('Incorrect string formatting') +endif |