Merge pull request #5292 from mensinda/introInterpFix

ast: Add basic string operation support for AstInterpreter (fixes #5286)
pull/4799/merge
Jussi Pakkanen 6 years ago committed by GitHub
commit 8018ef6ac6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 111
      mesonbuild/ast/interpreter.py
  2. 7
      mesonbuild/ast/introspection.py
  3. 18
      mesonbuild/interpreter.py
  4. 18
      mesonbuild/interpreterbase.py
  5. 16
      test cases/unit/55 introspection/meson.build

@ -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

@ -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]

@ -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):

@ -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)

@ -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)

Loading…
Cancel
Save