diff options
author | Jussi Pakkanen <jpakkane@gmail.com> | 2019-04-24 22:50:23 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-04-24 22:50:23 +0300 |
commit | 8018ef6ac6dead04f9a9f37bed3b47237b90ebad (patch) | |
tree | 0cedc11158920ec074edc8b08a6fa118f27d30f9 | |
parent | 26e205293a590aa700711198fc4ec16727e567b2 (diff) | |
parent | 8e587dfeba48f4f659d702ccecf890eea471627a (diff) | |
download | meson-8018ef6ac6dead04f9a9f37bed3b47237b90ebad.zip meson-8018ef6ac6dead04f9a9f37bed3b47237b90ebad.tar.gz meson-8018ef6ac6dead04f9a9f37bed3b47237b90ebad.tar.bz2 |
Merge pull request #5292 from mensinda/introInterpFix
ast: Add basic string operation support for AstInterpreter (fixes #5286)
-rw-r--r-- | mesonbuild/ast/interpreter.py | 111 | ||||
-rw-r--r-- | mesonbuild/ast/introspection.py | 7 | ||||
-rw-r--r-- | mesonbuild/interpreter.py | 18 | ||||
-rw-r--r-- | mesonbuild/interpreterbase.py | 18 | ||||
-rw-r--r-- | test cases/unit/55 introspection/meson.build | 16 |
5 files changed, 123 insertions, 47 deletions
diff --git a/mesonbuild/ast/interpreter.py b/mesonbuild/ast/interpreter.py index a75c0b7..eb9cb9f 100644 --- a/mesonbuild/ast/interpreter.py +++ b/mesonbuild/ast/interpreter.py @@ -20,9 +20,22 @@ from .. import interpreterbase, mparser, mesonlib from .. import environment from ..interpreterbase import InvalidArguments, BreakRequest, ContinueRequest +from ..mparser import ( + ArgumentNode, + ArithmeticNode, + ArrayNode, + AssignmentNode, + BaseNode, + ElementaryNode, + EmptyNode, + IdNode, + MethodNode, + PlusAssignmentNode, + TernaryNode, +) import os, sys -from typing import List, Optional +from typing import List, Any, Optional class DontCareObject(interpreterbase.InterpreterObject): pass @@ -162,13 +175,13 @@ class AstInterpreter(interpreterbase.InterpreterBase): return 0 def evaluate_ternary(self, node): - assert(isinstance(node, mparser.TernaryNode)) + assert(isinstance(node, TernaryNode)) self.evaluate_statement(node.condition) self.evaluate_statement(node.trueblock) self.evaluate_statement(node.falseblock) def evaluate_plusassign(self, node): - assert(isinstance(node, mparser.PlusAssignmentNode)) + assert(isinstance(node, PlusAssignmentNode)) if node.var_name not in self.assignments: self.assignments[node.var_name] = [] self.assign_vals[node.var_name] = [] @@ -184,10 +197,12 @@ class AstInterpreter(interpreterbase.InterpreterBase): pass def reduce_arguments(self, args): - assert(isinstance(args, mparser.ArgumentNode)) - if args.incorrect_order(): - raise InvalidArguments('All keyword arguments must be after positional arguments.') - return args.arguments, args.kwargs + if isinstance(args, ArgumentNode): + if args.incorrect_order(): + raise InvalidArguments('All keyword arguments must be after positional arguments.') + return self.flatten_args(args.arguments), args.kwargs + else: + return self.flatten_args(args), {} def evaluate_comparison(self, node): self.evaluate_statement(node.left) @@ -215,45 +230,95 @@ class AstInterpreter(interpreterbase.InterpreterBase): def evaluate_if(self, node): for i in node.ifs: self.evaluate_codeblock(i.block) - if not isinstance(node.elseblock, mparser.EmptyNode): + if not isinstance(node.elseblock, EmptyNode): self.evaluate_codeblock(node.elseblock) def get_variable(self, varname): return 0 def assignment(self, node): - assert(isinstance(node, mparser.AssignmentNode)) + assert(isinstance(node, AssignmentNode)) self.assignments[node.var_name] = [node.value] # Save a reference to the value node if hasattr(node.value, 'ast_id'): self.reverse_assignment[node.value.ast_id] = node self.assign_vals[node.var_name] = [self.evaluate_statement(node.value)] # Evaluate the value just in case - def flatten_args(self, args, include_unknown_args: bool = False): - # Resolve mparser.ArrayNode if needed + def flatten_args(self, args: Any, include_unknown_args: bool = False, id_loop_detect: Optional[List[str]] = None) -> List[str]: + def quick_resolve(n: BaseNode, loop_detect: Optional[List[str]] = None) -> Any: + if loop_detect is None: + loop_detect = [] + if isinstance(n, IdNode): + if n.value in loop_detect or n.value not in self.assignments: + return [] + return quick_resolve(self.assignments[n.value][0], loop_detect = loop_detect + [n.value]) + elif isinstance(n, ElementaryNode): + return n.value + else: + return n + + if id_loop_detect is None: + id_loop_detect = [] flattend_args = [] - temp_args = [] - if isinstance(args, mparser.ArrayNode): + + if isinstance(args, ArrayNode): args = [x for x in args.args.arguments] - elif isinstance(args, mparser.ArgumentNode): + + elif isinstance(args, ArgumentNode): args = [x for x in args.arguments] - for i in args: - if isinstance(i, mparser.ArrayNode): - temp_args += [x for x in i.args.arguments] + + elif isinstance(args, ArithmeticNode): + if args.operation != 'add': + return [] # Only handle string and array concats + l = quick_resolve(args.left) + r = quick_resolve(args.right) + if isinstance(l, str) and isinstance(r, str): + args = [l + r] # String concatination detected else: - temp_args += [i] - for i in temp_args: - if isinstance(i, mparser.ElementaryNode) and not isinstance(i, mparser.IdNode): + args = self.flatten_args(l, include_unknown_args, id_loop_detect) + self.flatten_args(r, include_unknown_args, id_loop_detect) + + elif isinstance(args, MethodNode): + src = quick_resolve(args.source_object) + margs = self.flatten_args(args.args, include_unknown_args, id_loop_detect) + try: + if isinstance(src, str): + args = [self.string_method_call(src, args.name, margs)] + elif isinstance(src, bool): + args = [self.bool_method_call(src, args.name, margs)] + elif isinstance(src, int): + args = [self.int_method_call(src, args.name, margs)] + elif isinstance(src, list): + args = [self.array_method_call(src, args.name, margs)] + elif isinstance(src, dict): + args = [self.dict_method_call(src, args.name, margs)] + else: + return [] + except mesonlib.MesonException: + return [] + + # Make sure we are always dealing with lists + if not isinstance(args, list): + args = [args] + + # Resolve the contents of args + for i in args: + if isinstance(i, IdNode) and i.value not in id_loop_detect: + flattend_args += self.flatten_args(quick_resolve(i), include_unknown_args, id_loop_detect + [i.value]) + elif isinstance(i, (ArrayNode, ArgumentNode, ArithmeticNode, MethodNode)): + flattend_args += self.flatten_args(i, include_unknown_args, id_loop_detect) + elif isinstance(i, mparser.ElementaryNode): flattend_args += [i.value] - elif isinstance(i, (str, bool, int, float)) or include_unknown_args: + elif isinstance(i, (str, bool, int, float)): + flattend_args += [i] + elif include_unknown_args: flattend_args += [i] return flattend_args def flatten_kwargs(self, kwargs: object, include_unknown_args: bool = False): flattend_kwargs = {} for key, val in kwargs.items(): - if isinstance(val, mparser.ElementaryNode): + if isinstance(val, ElementaryNode): flattend_kwargs[key] = val.value - elif isinstance(val, (mparser.ArrayNode, mparser.ArgumentNode)): + elif isinstance(val, (ArrayNode, ArgumentNode)): flattend_kwargs[key] = self.flatten_args(val, include_unknown_args) elif isinstance(val, (str, bool, int, float)) or include_unknown_args: flattend_kwargs[key] = val diff --git a/mesonbuild/ast/introspection.py b/mesonbuild/ast/introspection.py index f8fb2e8..49d531f 100644 --- a/mesonbuild/ast/introspection.py +++ b/mesonbuild/ast/introspection.py @@ -182,11 +182,12 @@ class IntrospectionInterpreter(AstInterpreter): srcqueue += [curr.left, curr.right] if arg_node is None: continue - elemetary_nodes = list(filter(lambda x: isinstance(x, (str, StringNode)), arg_node.arguments)) - srcqueue += list(filter(lambda x: isinstance(x, (FunctionNode, ArrayNode, IdNode, ArithmeticNode)), arg_node.arguments)) + arg_nodes = arg_node.arguments.copy() # Pop the first element if the function is a build target function if isinstance(curr, FunctionNode) and curr.func_name in build_target_functions: - elemetary_nodes.pop(0) + arg_nodes.pop(0) + elemetary_nodes = [x for x in arg_nodes if isinstance(x, (str, StringNode))] + srcqueue += [x for x in arg_nodes if isinstance(x, (FunctionNode, ArrayNode, IdNode, ArithmeticNode))] if elemetary_nodes: source_nodes += [curr] diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index b107a25..8ca7758 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -4076,24 +4076,6 @@ This will become a hard error in the future.''', location=self.current_node) if not os.path.isfile(fname): raise InterpreterException('Tried to add non-existing source file %s.' % s) - def format_string(self, templ, args): - if isinstance(args, mparser.ArgumentNode): - args = args.arguments - arg_strings = [] - for arg in args: - 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): - idx = int(match.group(1)) - if idx >= len(arg_strings): - raise InterpreterException('Format placeholder @{}@ out of range.'.format(idx)) - return arg_strings[idx] - - return re.sub(r'@(\d+)@', arg_replace, templ) - # Only permit object extraction from the same subproject def validate_extraction(self, buildtarget): if not self.subdir.startswith(self.subproject_dir): diff --git a/mesonbuild/interpreterbase.py b/mesonbuild/interpreterbase.py index 650d1e0..c148cbd 100644 --- a/mesonbuild/interpreterbase.py +++ b/mesonbuild/interpreterbase.py @@ -920,6 +920,24 @@ The result of this is undefined and will become a hard error in a future Meson r return mesonlib.version_compare(obj, cmpr) raise InterpreterException('Unknown method "%s" for a string.' % method_name) + def format_string(self, templ, args): + if isinstance(args, mparser.ArgumentNode): + args = args.arguments + arg_strings = [] + for arg in args: + 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): + idx = int(match.group(1)) + if idx >= len(arg_strings): + raise InterpreterException('Format placeholder @{}@ out of range.'.format(idx)) + return arg_strings[idx] + + return re.sub(r'@(\d+)@', arg_replace, templ) + def unknown_function_called(self, func_name): raise InvalidCode('Unknown function "%s".' % func_name) diff --git a/test cases/unit/55 introspection/meson.build b/test cases/unit/55 introspection/meson.build index 95a7c5f..f11d64d 100644 --- a/test cases/unit/55 introspection/meson.build +++ b/test cases/unit/55 introspection/meson.build @@ -20,9 +20,19 @@ endif subdir('sharedlib') subdir('staticlib') -t1 = executable('test1', 't1.cpp', link_with: [sharedlib], install: true, build_by_default: get_option('test_opt2')) -t2 = executable('test2', 't2.cpp', link_with: [staticlib]) -t3 = executable('test3', 't3.cpp', link_with: [sharedlib, staticlib], dependencies: [dep1]) +var1 = '1' +var2 = 2.to_string() +var3 = 'test3' + +t1 = executable('test' + var1, ['t1.cpp'], link_with: [sharedlib], install: true, build_by_default: get_option('test_opt2')) +t2 = executable('test@0@'.format('@0@'.format(var2)), 't2.cpp', link_with: [staticlib]) +t3 = executable(var3, 't3.cpp', link_with: [sharedlib, staticlib], dependencies: [dep1]) + +### BEGIN: Test inspired by taisei: https://github.com/taisei-project/taisei/blob/master/meson.build#L293 +systype = '@0@'.format(host_machine.system()) +systype = '@0@, @1@, @2@'.format(systype, host_machine.cpu_family(), host_machine.cpu()) +message(systype) +### END: Test inspired by taisei test('test case 1', t1) test('test case 2', t2) |