diff options
author | Daniel Mensinger <daniel@mensinger-ka.de> | 2021-09-01 23:18:28 +0200 |
---|---|---|
committer | Daniel Mensinger <daniel@mensinger-ka.de> | 2021-09-25 12:44:11 +0200 |
commit | d93d01b6c5c98a77e057b914e8d66b01d2a10771 (patch) | |
tree | ca759dff3424ad09519fada202bbbbe4ad9eaf14 | |
parent | 5fcb0e6525e2044e0f82bda488a51350e0f7f29f (diff) | |
download | meson-d93d01b6c5c98a77e057b914e8d66b01d2a10771.zip meson-d93d01b6c5c98a77e057b914e8d66b01d2a10771.tar.gz meson-d93d01b6c5c98a77e057b914e8d66b01d2a10771.tar.bz2 |
interpreter: Introduce StringHolder
Another commit in my quest to rid InterpreterBase from all higher
level object processing logic.
Additionally, there is a a logic change here, since `str.join` now
uses varargs and can now accept more than one argument (and supports
list flattening).
-rw-r--r-- | docs/markdown/Reference-manual.md | 1 | ||||
-rw-r--r-- | docs/markdown/snippets/str_join.md | 5 | ||||
-rw-r--r-- | mesonbuild/ast/interpreter.py | 3 | ||||
-rw-r--r-- | mesonbuild/interpreter/__init__.py | 2 | ||||
-rw-r--r-- | mesonbuild/interpreter/interpreter.py | 4 | ||||
-rw-r--r-- | mesonbuild/interpreter/mesonmain.py | 5 | ||||
-rw-r--r-- | mesonbuild/interpreter/primitives/__init__.py | 4 | ||||
-rw-r--r-- | mesonbuild/interpreter/primitives/string.py | 174 | ||||
-rw-r--r-- | mesonbuild/interpreterbase/__init__.py | 3 | ||||
-rw-r--r-- | mesonbuild/interpreterbase/_unholder.py | 4 | ||||
-rw-r--r-- | mesonbuild/interpreterbase/baseobjects.py | 4 | ||||
-rw-r--r-- | mesonbuild/interpreterbase/interpreterbase.py | 135 | ||||
-rw-r--r-- | test cases/common/35 string operations/meson.build | 1 | ||||
-rw-r--r-- | test cases/failing/11 object arithmetic/test.json | 2 | ||||
-rw-r--r-- | test cases/failing/12 string arithmetic/test.json | 3 |
15 files changed, 216 insertions, 134 deletions
diff --git a/docs/markdown/Reference-manual.md b/docs/markdown/Reference-manual.md index 8ef36de..31ed77e 100644 --- a/docs/markdown/Reference-manual.md +++ b/docs/markdown/Reference-manual.md @@ -2268,6 +2268,7 @@ are immutable, all operations return their results as a new string. - `join(list_of_strings)`: the opposite of split, for example `'.'.join(['a', 'b', 'c']` yields `'a.b.c'`. + *(Since 0.60.0)* more than one argument is supported and lists will be flattened. - `replace('old_substr', 'new_str')` *(since 0.58.0)*: replaces instances of `old_substr` in the string with `new_str` and returns a new string diff --git a/docs/markdown/snippets/str_join.md b/docs/markdown/snippets/str_join.md new file mode 100644 index 0000000..b430d66 --- /dev/null +++ b/docs/markdown/snippets/str_join.md @@ -0,0 +1,5 @@ +## Relax restrictions of `str.join()` + +Since 0.60.0, the [[str.join]] method can take an arbitrary number of arguments +instead of just one list. Additionally, all lists past to [[str.join]] will now +be flattened. diff --git a/mesonbuild/ast/interpreter.py b/mesonbuild/ast/interpreter.py index 4fd1378..5998e5b 100644 --- a/mesonbuild/ast/interpreter.py +++ b/mesonbuild/ast/interpreter.py @@ -364,7 +364,8 @@ class AstInterpreter(InterpreterBase): mkwargs = {} # type: T.Dict[str, TYPE_nvar] try: if isinstance(src, str): - result = self.string_method_call(src, node.name, margs, mkwargs) + from ..interpreter import Interpreter, StringHolder + result = StringHolder(src, T.cast(Interpreter, self)).method_call(node.name, margs, mkwargs) elif isinstance(src, bool): from ..interpreter import Interpreter, BooleanHolder result = BooleanHolder(src, T.cast(Interpreter, self)).method_call(node.name, margs, mkwargs) diff --git a/mesonbuild/interpreter/__init__.py b/mesonbuild/interpreter/__init__.py index 90d7faf..c93dbc9 100644 --- a/mesonbuild/interpreter/__init__.py +++ b/mesonbuild/interpreter/__init__.py @@ -37,6 +37,7 @@ __all__ = [ 'BooleanHolder', 'IntegerHolder', + 'StringHolder', ] from .interpreter import Interpreter, permitted_dependency_kwargs @@ -50,4 +51,5 @@ from .interpreterobjects import (ExecutableHolder, BuildTargetHolder, CustomTarg from .primitives import ( BooleanHolder, IntegerHolder, + StringHolder, ) diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py index f94ed2d..7a935da 100644 --- a/mesonbuild/interpreter/interpreter.py +++ b/mesonbuild/interpreter/interpreter.py @@ -376,6 +376,8 @@ class Interpreter(InterpreterBase, HoldableObject): # Primitives int: P_OBJ.IntegerHolder, bool: P_OBJ.BooleanHolder, + str: P_OBJ.StringHolder, + P_OBJ.MesonVersionString: P_OBJ.MesonVersionStringHolder, # Meson types mesonlib.File: OBJ.FileHolder, @@ -2399,7 +2401,7 @@ This will become a hard error in the future.''' % kwargs['input'], location=self @typed_pos_args('join_paths', varargs=str, min_varargs=1) @noKwargs def func_join_paths(self, node: mparser.BaseNode, args: T.Tuple[T.List[str]], kwargs: 'TYPE_kwargs') -> str: - return self.join_path_strings(args[0]) + return os.path.join(*args[0]).replace('\\', '/') def run(self) -> None: super().run() diff --git a/mesonbuild/interpreter/mesonmain.py b/mesonbuild/interpreter/mesonmain.py index 637ca72..15c1082 100644 --- a/mesonbuild/interpreter/mesonmain.py +++ b/mesonbuild/interpreter/mesonmain.py @@ -14,8 +14,9 @@ from ..mesonlib import MachineChoice, OptionKey from ..programs import OverrideProgram, ExternalProgram from ..interpreter.type_checking import ENV_KW from ..interpreterbase import (MesonInterpreterObject, FeatureNew, FeatureDeprecated, - typed_pos_args, noArgsFlattening, noPosargs, noKwargs, - typed_kwargs, KwargInfo, MesonVersionString, InterpreterException) + typed_pos_args, noArgsFlattening, noPosargs, noKwargs, + typed_kwargs, KwargInfo, InterpreterException) +from .primitives import MesonVersionString from .type_checking import NATIVE_KW, NoneType if T.TYPE_CHECKING: diff --git a/mesonbuild/interpreter/primitives/__init__.py b/mesonbuild/interpreter/primitives/__init__.py index 5d16744..d6c0795 100644 --- a/mesonbuild/interpreter/primitives/__init__.py +++ b/mesonbuild/interpreter/primitives/__init__.py @@ -4,7 +4,11 @@ __all__ = [ 'BooleanHolder', 'IntegerHolder', + 'StringHolder', + 'MesonVersionString', + 'MesonVersionStringHolder', ] from .boolean import BooleanHolder from .integer import IntegerHolder +from .string import StringHolder, MesonVersionString, MesonVersionStringHolder diff --git a/mesonbuild/interpreter/primitives/string.py b/mesonbuild/interpreter/primitives/string.py new file mode 100644 index 0000000..6b7155a --- /dev/null +++ b/mesonbuild/interpreter/primitives/string.py @@ -0,0 +1,174 @@ +# Copyright 2021 The Meson development team +# SPDX-license-identifier: Apache-2.0 + +import re +from pathlib import PurePath + +import typing as T + +from ...mesonlib import version_compare +from ...interpreterbase import ( + ObjectHolder, + MesonOperator, + FeatureNew, + typed_operator, + noKwargs, + noPosargs, + typed_pos_args, + + TYPE_var, + TYPE_kwargs, + + InvalidArguments, +) + + +if T.TYPE_CHECKING: + # Object holders need the actual interpreter + from ...interpreter import Interpreter + +class StringHolder(ObjectHolder[str]): + def __init__(self, obj: str, interpreter: 'Interpreter') -> None: + super().__init__(obj, interpreter) + self.methods.update({ + 'contains': self.contains_method, + 'startswith': self.startswith_method, + 'endswith': self.endswith_method, + 'format': self.format_method, + 'join': self.join_method, + 'replace': self.replace_method, + 'split': self.split_method, + 'strip': self.strip_method, + 'substring': self.substring_method, + 'to_int': self.to_int_method, + 'to_lower': self.to_lower_method, + 'to_upper': self.to_upper_method, + 'underscorify': self.underscorify_method, + 'version_compare': self.version_compare_method, + }) + + + self.trivial_operators.update({ + # Arithmetic + MesonOperator.PLUS: (str, lambda x: self.held_object + x), + + # Comparison + MesonOperator.EQUALS: (str, lambda x: self.held_object == x), + MesonOperator.NOT_EQUALS: (str, lambda x: self.held_object != x), + MesonOperator.GREATER: (str, lambda x: self.held_object > x), + MesonOperator.LESS: (str, lambda x: self.held_object < x), + MesonOperator.GREATER_EQUALS: (str, lambda x: self.held_object >= x), + MesonOperator.LESS_EQUALS: (str, lambda x: self.held_object <= x), + }) + + # Use actual methods for functions that require additional checks + self.operators.update({ + MesonOperator.DIV: self.op_div, + }) + + def display_name(self) -> str: + return 'str' + + @noKwargs + @typed_pos_args('str.contains', str) + def contains_method(self, args: T.Tuple[str], kwargs: TYPE_kwargs) -> bool: + return self.held_object.find(args[0]) >= 0 + + @noKwargs + @typed_pos_args('str.startswith', str) + def startswith_method(self, args: T.Tuple[str], kwargs: TYPE_kwargs) -> bool: + return self.held_object.startswith(args[0]) + + @noKwargs + @typed_pos_args('str.endswith', str) + def endswith_method(self, args: T.Tuple[str], kwargs: TYPE_kwargs) -> bool: + return self.held_object.endswith(args[0]) + + @noKwargs + @typed_pos_args('str.format', varargs=object) + def format_method(self, args: T.Tuple[T.List[object]], kwargs: TYPE_kwargs) -> str: + arg_strings: T.List[str] = [] + for arg in args[0]: + if isinstance(arg, bool): # Python boolean is upper case. + arg = str(arg).lower() + arg_strings.append(str(arg)) + + def arg_replace(match: T.Match[str]) -> str: + idx = int(match.group(1)) + if idx >= len(arg_strings): + raise InvalidArguments(f'Format placeholder @{idx}@ out of range.') + return arg_strings[idx] + + return re.sub(r'@(\d+)@', arg_replace, self.held_object) + + @noKwargs + @typed_pos_args('str.join', varargs=str) + def join_method(self, args: T.Tuple[T.List[str]], kwargs: TYPE_kwargs) -> str: + return self.held_object.join(args[0]) + + @noKwargs + @typed_pos_args('str.replace', str, str) + def replace_method(self, args: T.Tuple[str, str], kwargs: TYPE_kwargs) -> str: + return self.held_object.replace(args[0], args[1]) + + @noKwargs + @typed_pos_args('str.split', optargs=[str]) + def split_method(self, args: T.Tuple[T.Optional[str]], kwargs: TYPE_kwargs) -> T.List[str]: + return self.held_object.split(args[0]) + + @noKwargs + @typed_pos_args('str.strip', optargs=[str]) + def strip_method(self, args: T.Tuple[T.Optional[str]], kwargs: TYPE_kwargs) -> str: + return self.held_object.strip(args[0]) + + @noKwargs + @typed_pos_args('str.substring', optargs=[int, int]) + def substring_method(self, args: T.Tuple[T.Optional[int], T.Optional[int]], kwargs: TYPE_kwargs) -> str: + start = args[0] if args[0] is not None else 0 + end = args[1] if args[1] is not None else len(self.held_object) + return self.held_object[start:end] + + @noKwargs + @noPosargs + def to_int_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> int: + try: + return int(self.held_object) + except ValueError: + raise InvalidArguments(f'String {self.held_object!r} cannot be converted to int') + + @noKwargs + @noPosargs + def to_lower_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: + return self.held_object.lower() + + @noKwargs + @noPosargs + def to_upper_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: + return self.held_object.upper() + + @noKwargs + @noPosargs + def underscorify_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: + return re.sub(r'[^a-zA-Z0-9]', '_', self.held_object) + + @noKwargs + @typed_pos_args('str.version_compare', str) + def version_compare_method(self, args: T.Tuple[str], kwargs: TYPE_kwargs) -> bool: + return version_compare(self.held_object, args[0]) + + + @FeatureNew('/ with string arguments', '0.49.0') + @typed_operator(MesonOperator.DIV, str) + def op_div(self, other: str) -> str: + return (PurePath(self.held_object) / other).as_posix() + + +class MesonVersionString(str): + pass + +class MesonVersionStringHolder(StringHolder): + @noKwargs + @typed_pos_args('str.version_compare', str) + def version_compare_method(self, args: T.Tuple[str], kwargs: TYPE_kwargs) -> bool: + self.interpreter.tmp_meson_version = args[0] + return version_compare(self.held_object, args[0]) diff --git a/mesonbuild/interpreterbase/__init__.py b/mesonbuild/interpreterbase/__init__.py index f237c2f..d4c4a36 100644 --- a/mesonbuild/interpreterbase/__init__.py +++ b/mesonbuild/interpreterbase/__init__.py @@ -17,7 +17,6 @@ __all__ = [ 'MesonInterpreterObject', 'ObjectHolder', 'RangeHolder', - 'MesonVersionString', 'MutableInterpreterObject', 'MesonOperator', @@ -129,5 +128,5 @@ from .exceptions import ( from .disabler import Disabler, is_disabled from .helpers import check_stringlist, default_resolve_key, flatten, resolve_second_level_holders -from .interpreterbase import MesonVersionString, InterpreterBase +from .interpreterbase import InterpreterBase from .operator import MesonOperator diff --git a/mesonbuild/interpreterbase/_unholder.py b/mesonbuild/interpreterbase/_unholder.py index 202f53b..221c52c 100644 --- a/mesonbuild/interpreterbase/_unholder.py +++ b/mesonbuild/interpreterbase/_unholder.py @@ -19,9 +19,7 @@ from ..mesonlib import HoldableObject, MesonBugException import typing as T def _unholder(obj: T.Union[TYPE_var, InterpreterObject]) -> TYPE_var: - if isinstance(obj, str): - return obj - elif isinstance(obj, list): + if isinstance(obj, list): return [_unholder(x) for x in obj] elif isinstance(obj, dict): return {k: _unholder(v) for k, v in obj.items()} diff --git a/mesonbuild/interpreterbase/baseobjects.py b/mesonbuild/interpreterbase/baseobjects.py index 80cf0b5..c979f7d 100644 --- a/mesonbuild/interpreterbase/baseobjects.py +++ b/mesonbuild/interpreterbase/baseobjects.py @@ -129,8 +129,8 @@ class MesonInterpreterObject(InterpreterObject): class MutableInterpreterObject: ''' Dummy class to mark the object type as mutable ''' -HoldableTypes = (HoldableObject, int, bool) -TYPE_HoldableTypes = T.Union[HoldableObject, int, bool] +HoldableTypes = (HoldableObject, int, bool, str) +TYPE_HoldableTypes = T.Union[HoldableObject, int, bool, str] InterpreterObjectTypeVar = T.TypeVar('InterpreterObjectTypeVar', bound=TYPE_HoldableTypes) class ObjectHolder(InterpreterObject, T.Generic[InterpreterObjectTypeVar]): diff --git a/mesonbuild/interpreterbase/interpreterbase.py b/mesonbuild/interpreterbase/interpreterbase.py index 6e942b7..9ae50c8 100644 --- a/mesonbuild/interpreterbase/interpreterbase.py +++ b/mesonbuild/interpreterbase/interpreterbase.py @@ -62,6 +62,8 @@ HolderMapType = T.Dict[ T.Union[ T.Type[mesonlib.HoldableObject], T.Type[int], + T.Type[bool], + T.Type[str], ], # For some reason, this has to be a callable and can't just be ObjectHolder[InterpreterObjectTypeVar] T.Callable[[InterpreterObjectTypeVar, 'Interpreter'], ObjectHolder[InterpreterObjectTypeVar]] @@ -84,11 +86,8 @@ def _holderify_result(types: T.Union[None, T.Type, T.Tuple[T.Type, ...]] = None) return T.cast(__FN, wrapper) return inner -class MesonVersionString(str): - pass - class InterpreterBase: - elementary_types = (str, list) + elementary_types = (list, ) def __init__(self, source_root: str, subdir: str, subproject: str): self.source_root = source_root @@ -128,9 +127,6 @@ class InterpreterBase: me.file = mesonfile raise me - def join_path_strings(self, args: T.Sequence[str]) -> str: - return os.path.join(*args).replace('\\', '/') - def parse_project(self) -> None: """ Parses project() and initializes languages, compilers etc. Do this @@ -203,7 +199,7 @@ class InterpreterBase: elif isinstance(cur, mparser.MethodNode): return self.method_call(cur) elif isinstance(cur, mparser.StringNode): - return cur.value + return self._holderify(cur.value) elif isinstance(cur, mparser.BooleanNode): return self._holderify(cur.value) elif isinstance(cur, mparser.IfClauseNode): @@ -259,7 +255,7 @@ class InterpreterBase: def resolve_key(key: mparser.BaseNode) -> str: if not isinstance(key, mparser.StringNode): FeatureNew.single_use('Dictionary entry using non literal key', '0.53.0', self.subproject) - str_key = self.evaluate_statement(key) + str_key = _unholder(self.evaluate_statement(key)) if not isinstance(str_key, str): raise InvalidArguments('Key must be a string') return str_key @@ -382,13 +378,13 @@ class InterpreterBase: # Use type: ignore because mypy will complain that we are comparing two Unions, # but we actually guarantee earlier that both types are the same elif node.ctype == '<': - return val1 < val2 # type: ignore + return val1 < val2 elif node.ctype == '<=': - return val1 <= val2 # type: ignore + return val1 <= val2 elif node.ctype == '>': - return val1 > val2 # type: ignore + return val1 > val2 elif node.ctype == '>=': - return val1 >= val2 # type: ignore + return val1 >= val2 else: raise InvalidCode('You broke my compare eval.') @@ -436,14 +432,6 @@ class InterpreterBase: raise InterpreterException(f'Argument to negation ({v}) is not an InterpreterObject but {type(v).__name__}.') return v.operator_call(MesonOperator.UMINUS, None) - @FeatureNew('/ with string arguments', '0.49.0') - def evaluate_path_join(self, l: str, r: str) -> str: - if not isinstance(l, str): - raise InvalidCode('The division operator can only append to a string.') - if not isinstance(r, str): - raise InvalidCode('The division operator can only append a string.') - return self.join_path_strings((l, r)) - def evaluate_arithmeticstatement(self, cur: mparser.ArithmeticNode) -> T.Union[TYPE_var, InterpreterObject]: l = self.evaluate_statement(cur.left) if isinstance(l, Disabler): @@ -484,9 +472,7 @@ class InterpreterBase: raise InvalidCode('Multiplication works only with integers.') raise mesonlib.MesonBugException('The integer was not held by an ObjectHolder!') elif cur.operation == 'div': - if isinstance(l, str) and isinstance(r, str): - return self.evaluate_path_join(l, r) - raise InvalidCode('Division works only with strings or integers.') + raise mesonlib.MesonBugException('The integer or string was not held by an ObjectHolder!') elif cur.operation == 'mod': if not isinstance(l, int) or not isinstance(r, int): raise InvalidCode('Modulo works only with integers.') @@ -508,7 +494,8 @@ class InterpreterBase: return self.evaluate_statement(node.falseblock) @FeatureNew('format strings', '0.58.0') - def evaluate_fstring(self, node: mparser.FormatStringNode) -> TYPE_var: + @_holderify_result(str) + def evaluate_fstring(self, node: mparser.FormatStringNode) -> str: assert isinstance(node, mparser.FormatStringNode) def replace(match: T.Match[str]) -> str: @@ -545,8 +532,8 @@ class InterpreterBase: if len(node.varnames) != 2: raise InvalidArguments('Foreach on dict unpacks key and value') for key, value in sorted(items.items()): - self.set_variable(node.varnames[0], key) - self.set_variable(node.varnames[1], value) + self.set_variable(node.varnames[0], self._holderify(key)) + self.set_variable(node.varnames[1], self._holderify(value, permissive=True)) try: self.evaluate_codeblock(node.block) except ContinueRequest: @@ -656,7 +643,7 @@ class InterpreterBase: if is_disabled(args, kwargs): return Disabler() if isinstance(obj, str): - return self._holderify(self.string_method_call(obj, method_name, args, kwargs)) + raise mesonlib.MesonBugException('Strings are now wrapped in object holders!') if isinstance(obj, bool): raise mesonlib.MesonBugException('Booleans are now wrapped in object holders!') if isinstance(obj, int): @@ -680,8 +667,6 @@ class InterpreterBase: # TODO: remove `permissive` once all primitives are ObjectHolders if res is None: return None - if isinstance(res, str): - return res elif isinstance(res, list): return [self._holderify(x, permissive=permissive) for x in res] elif isinstance(res, dict): @@ -722,96 +707,6 @@ class InterpreterBase: return s return None - @noKwargs - def string_method_call(self, obj: str, method_name: str, posargs: T.List[TYPE_var], kwargs: TYPE_kwargs) -> T.Union[str, int, bool, T.List[str]]: - if method_name == 'strip': - s1 = self._get_one_string_posarg(posargs, 'strip') - if s1 is not None: - return obj.strip(s1) - return obj.strip() - elif method_name == 'format': - return self.format_string(obj, posargs) - elif method_name == 'to_upper': - return obj.upper() - elif method_name == 'to_lower': - return obj.lower() - elif method_name == 'underscorify': - return re.sub(r'[^a-zA-Z0-9]', '_', obj) - elif method_name == 'split': - s2 = self._get_one_string_posarg(posargs, 'split') - if s2 is not None: - return obj.split(s2) - return obj.split() - elif method_name == 'startswith' or method_name == 'contains' or method_name == 'endswith': - s3 = posargs[0] - if not isinstance(s3, str): - raise InterpreterException('Argument must be a string.') - if method_name == 'startswith': - return obj.startswith(s3) - elif method_name == 'contains': - return obj.find(s3) >= 0 - return obj.endswith(s3) - elif method_name == 'to_int': - try: - return int(obj) - except Exception: - raise InterpreterException(f'String {obj!r} cannot be converted to int') - elif method_name == 'join': - if len(posargs) != 1: - raise InterpreterException('Join() takes exactly one argument.') - strlist = posargs[0] - check_stringlist(strlist) - assert isinstance(strlist, list) # Required for mypy - return obj.join(strlist) - elif method_name == 'version_compare': - if len(posargs) != 1: - raise InterpreterException('Version_compare() takes exactly one argument.') - cmpr = posargs[0] - if not isinstance(cmpr, str): - raise InterpreterException('Version_compare() argument must be a string.') - if isinstance(obj, MesonVersionString): - self.tmp_meson_version = cmpr - return mesonlib.version_compare(obj, cmpr) - elif method_name == 'substring': - if len(posargs) > 2: - raise InterpreterException('substring() takes maximum two arguments.') - start = 0 - end = len(obj) - if len (posargs) > 0: - if not isinstance(posargs[0], int): - raise InterpreterException('substring() argument must be an int') - start = posargs[0] - if len (posargs) > 1: - if not isinstance(posargs[1], int): - raise InterpreterException('substring() argument must be an int') - end = posargs[1] - return obj[start:end] - elif method_name == 'replace': - FeatureNew.single_use('str.replace', '0.58.0', self.subproject) - if len(posargs) != 2: - raise InterpreterException('replace() takes exactly two arguments.') - if not isinstance(posargs[0], str) or not isinstance(posargs[1], str): - raise InterpreterException('replace() requires that both arguments be strings') - return obj.replace(posargs[0], posargs[1]) - raise InterpreterException('Unknown method "%s" for a string.' % method_name) - - def format_string(self, templ: str, args: T.List[TYPE_var]) -> str: - arg_strings = [] - for arg in args: - if isinstance(arg, mparser.BaseNode): - arg = self.evaluate_statement(arg) - if isinstance(arg, bool): # Python boolean is upper case. - arg = str(arg).lower() - arg_strings.append(str(arg)) - - def arg_replace(match: T.Match[str]) -> str: - idx = int(match.group(1)) - if idx >= len(arg_strings): - raise InterpreterException(f'Format placeholder @{idx}@ out of range.') - return arg_strings[idx] - - return re.sub(r'@(\d+)@', arg_replace, templ) - def unknown_function_called(self, func_name: str) -> None: raise InvalidCode('Unknown function "%s".' % func_name) diff --git a/test cases/common/35 string operations/meson.build b/test cases/common/35 string operations/meson.build index ca0342d..3405e95 100644 --- a/test cases/common/35 string operations/meson.build +++ b/test cases/common/35 string operations/meson.build @@ -61,6 +61,7 @@ assert('@0@'.format(true) == 'true', 'bool string formatting failed') assert(' '.join(['a', 'b', 'c']) == 'a b c', 'join() array broken') assert(''.join(['a', 'b', 'c']) == 'abc', 'empty join() broken') assert(' '.join(['a']) == 'a', 'single join broken') +assert(' '.join(['a'], ['b', ['c']], 'd') == 'a b c d', 'varargs join broken') version_number = '1.2.8' diff --git a/test cases/failing/11 object arithmetic/test.json b/test cases/failing/11 object arithmetic/test.json index 5339fac..84f5c46 100644 --- a/test cases/failing/11 object arithmetic/test.json +++ b/test cases/failing/11 object arithmetic/test.json @@ -2,7 +2,7 @@ "stdout": [ { "match": "re", - "line": "test cases/failing/11 object arithmetic/meson\\.build:3:0: ERROR: Invalid use of addition: .*" + "line": "test cases/failing/11 object arithmetic/meson\\.build:3:0: ERROR: The `\\+` of str does not accept objects of type MesonMain .*" } ] } diff --git a/test cases/failing/12 string arithmetic/test.json b/test cases/failing/12 string arithmetic/test.json index 476f9bb..c762229 100644 --- a/test cases/failing/12 string arithmetic/test.json +++ b/test cases/failing/12 string arithmetic/test.json @@ -1,8 +1,7 @@ { "stdout": [ { - "match": "re", - "line": "test cases/failing/12 string arithmetic/meson\\.build:3:0: ERROR: Invalid use of addition: .*" + "line": "test cases/failing/12 string arithmetic/meson.build:3:0: ERROR: The `+` of str does not accept objects of type int (3)" } ] } |