diff options
author | Jussi Pakkanen <jpakkane@gmail.com> | 2016-11-19 20:18:30 +0200 |
---|---|---|
committer | Jussi Pakkanen <jpakkane@gmail.com> | 2016-11-19 20:18:30 +0200 |
commit | 7ed7219d9d937103c9d6760dfdcd7c6d895c1745 (patch) | |
tree | 1750273eb7b69135d626632946020124aff69a98 /mesonbuild/interpreterbase.py | |
parent | 0a31afd672f10d1d996fca1915551b647b8489e2 (diff) | |
download | meson-7ed7219d9d937103c9d6760dfdcd7c6d895c1745.zip meson-7ed7219d9d937103c9d6760dfdcd7c6d895c1745.tar.gz meson-7ed7219d9d937103c9d6760dfdcd7c6d895c1745.tar.bz2 |
Moved functions to base enough to get the base sample project parsed.
Diffstat (limited to 'mesonbuild/interpreterbase.py')
-rw-r--r-- | mesonbuild/interpreterbase.py | 203 |
1 files changed, 199 insertions, 4 deletions
diff --git a/mesonbuild/interpreterbase.py b/mesonbuild/interpreterbase.py index 6c11ff7..7e732be 100644 --- a/mesonbuild/interpreterbase.py +++ b/mesonbuild/interpreterbase.py @@ -15,11 +15,10 @@ # This class contains the basic functionality needed to run any interpreter # or an interpreter-based tool. -from . import mparser -from . import mesonlib -from . import environment +from . import mparser, mesonlib, mlog +from . import environment, dependencies -import os +import os, copy, re class InterpreterException(mesonlib.MesonException): pass @@ -30,13 +29,27 @@ class InvalidCode(InterpreterException): class InvalidArguments(InterpreterException): pass +class InterpreterObject(): + def __init__(self): + self.methods = {} + + def method_call(self, method_name, args, kwargs): + if method_name in self.methods: + return self.methods[method_name](args, kwargs) + raise InvalidCode('Unknown method "%s" in object.' % method_name) + +class MutableInterpreterObject(InterpreterObject): + def __init__(self): + super().__init__() class InterpreterBase: def __init__(self, source_root, subdir): self.source_root = source_root self.funcs = {} + self.builtin = {} self.subdir = subdir + self.variables = {} def load_root_meson_file(self): mesonfile = os.path.join(self.source_root, self.subdir, environment.build_filename) @@ -52,3 +65,185 @@ class InterpreterBase: except mesonlib.MesonException as me: me.file = environment.build_filename raise me + + def parse_project(self): + """ + Parses project() and initializes languages, compilers etc. Do this + early because we need this before we parse the rest of the AST. + """ + self.evaluate_codeblock(self.ast, end=1) + + def sanity_check_ast(self): + if not isinstance(self.ast, mparser.CodeBlockNode): + raise InvalidCode('AST is of invalid type. Possibly a bug in the parser.') + if len(self.ast.lines) == 0: + raise InvalidCode('No statements in code.') + first = self.ast.lines[0] + if not isinstance(first, mparser.FunctionNode) or first.func_name != 'project': + raise InvalidCode('First statement must be a call to project') + + def run(self): + # Evaluate everything after the first line, which is project() because + # we already parsed that in self.parse_project() + self.evaluate_codeblock(self.ast, start=1) + + def evaluate_codeblock(self, node, start=0, end=None): + if node is None: + return + if not isinstance(node, mparser.CodeBlockNode): + e = InvalidCode('Tried to execute a non-codeblock. Possibly a bug in the parser.') + e.lineno = node.lineno + e.colno = node.colno + raise e + statements = node.lines[start:end] + i = 0 + while i < len(statements): + cur = statements[i] + try: + self.evaluate_statement(cur) + except Exception as e: + if not(hasattr(e, 'lineno')): + e.lineno = cur.lineno + e.colno = cur.colno + e.file = os.path.join(self.subdir, 'meson.build') + raise e + i += 1 # In THE FUTURE jump over blocks and stuff. + + def evaluate_statement(self, cur): + if isinstance(cur, mparser.FunctionNode): + return self.function_call(cur) + elif isinstance(cur, mparser.AssignmentNode): + return self.assignment(cur) + elif isinstance(cur, mparser.MethodNode): + return self.method_call(cur) + elif isinstance(cur, mparser.StringNode): + return cur.value + elif isinstance(cur, mparser.BooleanNode): + return cur.value + elif isinstance(cur, mparser.IfClauseNode): + return self.evaluate_if(cur) + elif isinstance(cur, mparser.IdNode): + return self.get_variable(cur.value) + elif isinstance(cur, mparser.ComparisonNode): + return self.evaluate_comparison(cur) + elif isinstance(cur, mparser.ArrayNode): + return self.evaluate_arraystatement(cur) + elif isinstance(cur, mparser.NumberNode): + return cur.value + elif isinstance(cur, mparser.AndNode): + return self.evaluate_andstatement(cur) + elif isinstance(cur, mparser.OrNode): + return self.evaluate_orstatement(cur) + elif isinstance(cur, mparser.NotNode): + return self.evaluate_notstatement(cur) + elif isinstance(cur, mparser.UMinusNode): + return self.evaluate_uminusstatement(cur) + elif isinstance(cur, mparser.ArithmeticNode): + return self.evaluate_arithmeticstatement(cur) + elif isinstance(cur, mparser.ForeachClauseNode): + return self.evaluate_foreach(cur) + elif isinstance(cur, mparser.PlusAssignmentNode): + return self.evaluate_plusassign(cur) + elif isinstance(cur, mparser.IndexNode): + return self.evaluate_indexing(cur) + elif isinstance(cur, mparser.TernaryNode): + return self.evaluate_ternary(cur) + elif self.is_elementary_type(cur): + return cur + else: + raise InvalidCode("Unknown statement.") + + def evaluate_arraystatement(self, cur): + (arguments, kwargs) = self.reduce_arguments(cur.args) + if len(kwargs) > 0: + raise InvalidCode('Keyword arguments are invalid in array construction.') + return arguments + + + def function_call(self, node): + func_name = node.func_name + (posargs, kwargs) = self.reduce_arguments(node.args) + if func_name in self.funcs: + return self.funcs[func_name](node, self.flatten(posargs), kwargs) + else: + self.unknown_function_called(func_name) + + def unknown_function_called(self, func_name): + raise InvalidCode('Unknown function "%s".' % func_name) + + def reduce_arguments(self, args): + assert(isinstance(args, mparser.ArgumentNode)) + if args.incorrect_order(): + raise InvalidArguments('All keyword arguments must be after positional arguments.') + reduced_pos = [self.evaluate_statement(arg) for arg in args.arguments] + reduced_kw = {} + for key in args.kwargs.keys(): + if not isinstance(key, str): + raise InvalidArguments('Keyword argument name is not a string.') + a = args.kwargs[key] + reduced_kw[key] = self.evaluate_statement(a) + if not isinstance(reduced_pos, list): + reduced_pos = [reduced_pos] + return (reduced_pos, reduced_kw) + + def flatten(self, args): + if isinstance(args, mparser.StringNode): + return args.value + if isinstance(args, (int, str, InterpreterObject)): + return args + result = [] + for a in args: + if isinstance(a, list): + rest = self.flatten(a) + result = result + rest + elif isinstance(a, mparser.StringNode): + result.append(a.value) + else: + result.append(a) + return result + + def assignment(self, node): + assert(isinstance(node, mparser.AssignmentNode)) + var_name = node.var_name + if not isinstance(var_name, str): + raise InvalidArguments('Tried to assign value to a non-variable.') + value = self.evaluate_statement(node.value) + value = self.to_native(value) + if not self.is_assignable(value): + raise InvalidCode('Tried to assign an invalid value to variable.') + # For mutable objects we need to make a copy on assignment + if isinstance(value, MutableInterpreterObject): + value = copy.deepcopy(value) + self.set_variable(var_name, value) + return value + + def set_variable(self, varname, variable): + if variable is None: + raise InvalidCode('Can not assign None to variable.') + if not isinstance(varname, str): + raise InvalidCode('First argument to set_variable must be a string.') + if not self.is_assignable(variable): + raise InvalidCode('Assigned value not of assignable type.') + if re.match('[_a-zA-Z][_0-9a-zA-Z]*$', varname) is None: + raise InvalidCode('Invalid variable name: ' + varname) + if varname in self.builtin: + raise InvalidCode('Tried to overwrite internal variable "%s"' % varname) + self.variables[varname] = variable + + def get_variable(self, varname): + if varname in self.builtin: + return self.builtin[varname] + if varname in self.variables: + return self.variables[varname] + raise InvalidCode('Unknown variable "%s".' % varname) + + def to_native(self, arg): + if isinstance(arg, (mparser.StringNode, mparser.NumberNode, + mparser.BooleanNode)): + return arg.value + return arg + + def is_assignable(self, value): + return isinstance(value, (InterpreterObject, dependencies.Dependency, + str, int, list, mesonlib.File)) + |