diff options
author | Daniel Mensinger <daniel@mensinger-ka.de> | 2019-01-15 18:45:25 +0100 |
---|---|---|
committer | Daniel Mensinger <daniel@mensinger-ka.de> | 2019-01-22 16:09:34 +0100 |
commit | ccad493e85e46bf0e78cfac8b77b03b7be75a396 (patch) | |
tree | 255ebb2995f3db3a24bdc71bc327bb2f03f106f9 /mesonbuild/ast | |
parent | 72486afd08d66d6323c2113739dcfff74813058b (diff) | |
download | meson-ccad493e85e46bf0e78cfac8b77b03b7be75a396.zip meson-ccad493e85e46bf0e78cfac8b77b03b7be75a396.tar.gz meson-ccad493e85e46bf0e78cfac8b77b03b7be75a396.tar.bz2 |
Basic AST visitor pattern
Diffstat (limited to 'mesonbuild/ast')
-rw-r--r-- | mesonbuild/ast/__init__.py | 25 | ||||
-rw-r--r-- | mesonbuild/ast/interpreter.py | 332 | ||||
-rw-r--r-- | mesonbuild/ast/printer.py | 22 | ||||
-rw-r--r-- | mesonbuild/ast/visitor.py | 118 |
4 files changed, 497 insertions, 0 deletions
diff --git a/mesonbuild/ast/__init__.py b/mesonbuild/ast/__init__.py new file mode 100644 index 0000000..4b82d7e --- /dev/null +++ b/mesonbuild/ast/__init__.py @@ -0,0 +1,25 @@ +# Copyright 2019 The Meson development team + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This class contains the basic functionality needed to run any interpreter +# or an interpreter-based tool. + +__all__ = [ + 'AstInterpreter', + 'RewriterInterpreter', + 'AstVisitor' +] + +from .interpreter import (AstInterpreter, RewriterInterpreter) +from .visitor import AstVisitor diff --git a/mesonbuild/ast/interpreter.py b/mesonbuild/ast/interpreter.py new file mode 100644 index 0000000..c1068d4 --- /dev/null +++ b/mesonbuild/ast/interpreter.py @@ -0,0 +1,332 @@ +# Copyright 2016 The Meson development team + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This class contains the basic functionality needed to run any interpreter +# or an interpreter-based tool. + +from .. import interpreterbase, mparser, mesonlib +from .. import environment + +from ..interpreterbase import InterpreterException, InvalidArguments, BreakRequest, ContinueRequest + +import os, sys + +class DontCareObject(interpreterbase.InterpreterObject): + pass + +class MockExecutable(interpreterbase.InterpreterObject): + pass + +class MockStaticLibrary(interpreterbase.InterpreterObject): + pass + +class MockSharedLibrary(interpreterbase.InterpreterObject): + pass + +class MockCustomTarget(interpreterbase.InterpreterObject): + pass + +class MockRunTarget(interpreterbase.InterpreterObject): + pass + +ADD_SOURCE = 0 +REMOVE_SOURCE = 1 + +class AstInterpreter(interpreterbase.InterpreterBase): + def __init__(self, source_root, subdir): + super().__init__(source_root, subdir) + self.visited_subdirs = {} + self.funcs.update({'project': self.func_do_nothing, + 'test': self.func_do_nothing, + 'benchmark': self.func_do_nothing, + 'install_headers': self.func_do_nothing, + 'install_man': self.func_do_nothing, + 'install_data': self.func_do_nothing, + 'install_subdir': self.func_do_nothing, + 'configuration_data': self.func_do_nothing, + 'configure_file': self.func_do_nothing, + 'find_program': self.func_do_nothing, + 'include_directories': self.func_do_nothing, + 'add_global_arguments': self.func_do_nothing, + 'add_global_link_arguments': self.func_do_nothing, + 'add_project_arguments': self.func_do_nothing, + 'add_project_link_arguments': self.func_do_nothing, + 'message': self.func_do_nothing, + 'generator': self.func_do_nothing, + 'error': self.func_do_nothing, + 'run_command': self.func_do_nothing, + 'assert': self.func_do_nothing, + 'subproject': self.func_do_nothing, + 'dependency': self.func_do_nothing, + 'get_option': self.func_do_nothing, + 'join_paths': self.func_do_nothing, + 'environment': self.func_do_nothing, + 'import': self.func_do_nothing, + 'vcs_tag': self.func_do_nothing, + 'add_languages': self.func_do_nothing, + 'declare_dependency': self.func_do_nothing, + 'files': self.func_do_nothing, + 'executable': self.func_do_nothing, + 'static_library': self.func_do_nothing, + 'shared_library': self.func_do_nothing, + 'library': self.func_do_nothing, + 'build_target': self.func_do_nothing, + 'custom_target': self.func_do_nothing, + 'run_target': self.func_do_nothing, + 'subdir': self.func_subdir, + 'set_variable': self.func_do_nothing, + 'get_variable': self.func_do_nothing, + 'is_variable': self.func_do_nothing, + }) + + def func_do_nothing(self, node, args, kwargs): + return True + + def func_subdir(self, node, args, kwargs): + args = self.flatten_args(args) + if len(args) != 1 or not isinstance(args[0], str): + sys.stderr.write('Unable to evaluate subdir({}) in AstInterpreter --> Skipping\n'.format(args)) + return + + prev_subdir = self.subdir + subdir = os.path.join(prev_subdir, args[0]) + absdir = os.path.join(self.source_root, subdir) + buildfilename = os.path.join(self.subdir, environment.build_filename) + absname = os.path.join(self.source_root, buildfilename) + symlinkless_dir = os.path.realpath(absdir) + if symlinkless_dir in self.visited_subdirs: + sys.stderr.write('Trying to enter {} which has already been visited --> Skipping\n'.format(args[0])) + return + self.visited_subdirs[symlinkless_dir] = True + + if not os.path.isfile(absname): + sys.stderr.write('Unable to find build file {} --> Skipping\n'.format(buildfilename)) + return + with open(absname, encoding='utf8') as f: + code = f.read() + assert(isinstance(code, str)) + try: + codeblock = mparser.Parser(code, self.subdir).parse() + except mesonlib.MesonException as me: + me.file = buildfilename + raise me + + self.subdir = subdir + self.evaluate_codeblock(codeblock) + self.subdir = prev_subdir + + def method_call(self, node): + return True + + def evaluate_arithmeticstatement(self, cur): + return 0 + + def evaluate_plusassign(self, node): + return 0 + + def evaluate_indexing(self, node): + return 0 + + def unknown_function_called(self, func_name): + 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 + + def evaluate_comparison(self, node): + return False + + def evaluate_foreach(self, node): + try: + self.evaluate_codeblock(node.block) + except ContinueRequest: + pass + except BreakRequest: + pass + + def evaluate_if(self, node): + for i in node.ifs: + self.evaluate_codeblock(i.block) + if not isinstance(node.elseblock, mparser.EmptyNode): + self.evaluate_codeblock(node.elseblock) + + def get_variable(self, varname): + return 0 + + def assignment(self, node): + pass + + def flatten_args(self, args): + # Resolve mparser.ArrayNode if needed + flattend_args = [] + if isinstance(args, mparser.ArrayNode): + args = [x.value for x in args.args.arguments] + for i in args: + if isinstance(i, mparser.ArrayNode): + flattend_args += [x.value for x in i.args.arguments] + elif isinstance(i, str): + flattend_args += [i] + else: + pass + return flattend_args + +class RewriterInterpreter(AstInterpreter): + def __init__(self, source_root, subdir): + super().__init__(source_root, subdir) + self.asts = {} + self.funcs.update({'files': self.func_files, + 'executable': self.func_executable, + 'static_library': self.func_static_lib, + 'shared_library': self.func_shared_lib, + 'library': self.func_library, + 'build_target': self.func_build_target, + 'custom_target': self.func_custom_target, + 'run_target': self.func_run_target, + 'subdir': self.func_subdir, + 'set_variable': self.func_set_variable, + 'get_variable': self.func_get_variable, + 'is_variable': self.func_is_variable, + }) + + def func_executable(self, node, args, kwargs): + if args[0] == self.targetname: + if self.operation == ADD_SOURCE: + self.add_source_to_target(node, args, kwargs) + elif self.operation == REMOVE_SOURCE: + self.remove_source_from_target(node, args, kwargs) + else: + raise NotImplementedError('Bleep bloop') + return MockExecutable() + + def func_static_lib(self, node, args, kwargs): + return MockStaticLibrary() + + def func_shared_lib(self, node, args, kwargs): + return MockSharedLibrary() + + def func_library(self, node, args, kwargs): + return self.func_shared_lib(node, args, kwargs) + + def func_custom_target(self, node, args, kwargs): + return MockCustomTarget() + + def func_run_target(self, node, args, kwargs): + return MockRunTarget() + + def func_subdir(self, node, args, kwargs): + prev_subdir = self.subdir + subdir = os.path.join(prev_subdir, args[0]) + self.subdir = subdir + buildfilename = os.path.join(self.subdir, environment.build_filename) + absname = os.path.join(self.source_root, buildfilename) + if not os.path.isfile(absname): + self.subdir = prev_subdir + raise InterpreterException('Nonexistent build def file %s.' % buildfilename) + with open(absname, encoding='utf8') as f: + code = f.read() + assert(isinstance(code, str)) + try: + codeblock = mparser.Parser(code, self.subdir).parse() + self.asts[subdir] = codeblock + except mesonlib.MesonException as me: + me.file = buildfilename + raise me + try: + self.evaluate_codeblock(codeblock) + except SubdirDoneRequest: + pass + self.subdir = prev_subdir + + def func_files(self, node, args, kwargs): + if not isinstance(args, list): + return [args] + return args + + def transform(self): + self.load_root_meson_file() + self.asts[''] = self.ast + self.sanity_check_ast() + self.parse_project() + self.run() + + def add_source(self, targetname, filename): + self.operation = ADD_SOURCE + self.targetname = targetname + self.filename = filename + self.transform() + + def remove_source(self, targetname, filename): + self.operation = REMOVE_SOURCE + self.targetname = targetname + self.filename = filename + self.transform() + + def add_source_to_target(self, node, args, kwargs): + namespan = node.args.arguments[0].bytespan + buildfilename = os.path.join(self.source_root, self.subdir, environment.build_filename) + raw_data = open(buildfilename, 'r').read() + updated = raw_data[0:namespan[1]] + (", '%s'" % self.filename) + raw_data[namespan[1]:] + open(buildfilename, 'w').write(updated) + sys.exit(0) + + def remove_argument_item(self, args, i): + assert(isinstance(args, mparser.ArgumentNode)) + namespan = args.arguments[i].bytespan + # Usually remove the comma after this item but if it is + # the last argument, we need to remove the one before. + if i >= len(args.commas): + i -= 1 + if i < 0: + commaspan = (0, 0) # Removed every entry in the list. + else: + commaspan = args.commas[i].bytespan + if commaspan[0] < namespan[0]: + commaspan, namespan = namespan, commaspan + buildfilename = os.path.join(self.source_root, args.subdir, environment.build_filename) + raw_data = open(buildfilename, 'r').read() + intermediary = raw_data[0:commaspan[0]] + raw_data[commaspan[1]:] + updated = intermediary[0:namespan[0]] + intermediary[namespan[1]:] + open(buildfilename, 'w').write(updated) + sys.exit(0) + + def hacky_find_and_remove(self, node_to_remove): + for a in self.asts[node_to_remove.subdir].lines: + if a.lineno == node_to_remove.lineno: + if isinstance(a, mparser.AssignmentNode): + v = a.value + if not isinstance(v, mparser.ArrayNode): + raise NotImplementedError('Not supported yet, bro.') + args = v.args + for i in range(len(args.arguments)): + if isinstance(args.arguments[i], mparser.StringNode) and self.filename == args.arguments[i].value: + self.remove_argument_item(args, i) + raise NotImplementedError('Sukkess') + + def remove_source_from_target(self, node, args, kwargs): + for i in range(1, len(node.args)): + # Is file name directly in function call as a string. + if isinstance(node.args.arguments[i], mparser.StringNode) and self.filename == node.args.arguments[i].value: + self.remove_argument_item(node.args, i) + # Is file name in a variable that gets expanded here. + if isinstance(node.args.arguments[i], mparser.IdNode): + avar = self.get_variable(node.args.arguments[i].value) + if not isinstance(avar, list): + raise NotImplementedError('Non-arrays not supported yet, sorry.') + for entry in avar: + if isinstance(entry, mparser.StringNode) and entry.value == self.filename: + self.hacky_find_and_remove(entry) + sys.exit('Could not find source %s in target %s.' % (self.filename, args[0])) diff --git a/mesonbuild/ast/printer.py b/mesonbuild/ast/printer.py new file mode 100644 index 0000000..c1710be --- /dev/null +++ b/mesonbuild/ast/printer.py @@ -0,0 +1,22 @@ +# Copyright 2018 The Meson development team + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This class contains the basic functionality needed to run any interpreter +# or an interpreter-based tool + +from . import AstVisitor + +class AstPrinter(AstVisitor): + def __init__(self): + pass diff --git a/mesonbuild/ast/visitor.py b/mesonbuild/ast/visitor.py new file mode 100644 index 0000000..487cd5b --- /dev/null +++ b/mesonbuild/ast/visitor.py @@ -0,0 +1,118 @@ +# Copyright 2018 The Meson development team + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This class contains the basic functionality needed to run any interpreter +# or an interpreter-based tool + +from .. import mparser + +class AstVisitor: + def __init__(self): + pass + + def visit_BooleanNode(self, node: mparser.BooleanNode): + pass + + def visit_IdNode(self, node: mparser.IdNode): + pass + + def visit_NumberNode(self, node: mparser.NumberNode): + pass + + def visit_StringNode(self, node: mparser.StringNode): + pass + + def visit_ContinueNode(self, node: mparser.ContinueNode): + pass + + def visit_BreakNode(self, node: mparser.BreakNode): + pass + + def visit_ArrayNode(self, node: mparser.ArrayNode): + node.args.accept(self) + + def visit_DictNode(self, node: mparser.DictNode): + node.args.accept(self) + + def visit_EmptyNode(self, node: mparser.EmptyNode): + pass + + def visit_OrNode(self, node: mparser.OrNode): + node.left.accept(self) + node.right.accept(self) + + def visit_AndNode(self, node: mparser.AndNode): + node.left.accept(self) + node.right.accept(self) + + def visit_ComparisonNode(self, node: mparser.ComparisonNode): + node.left.accept(self) + node.right.accept(self) + + def visit_ArithmeticNode(self, node: mparser.ArithmeticNode): + node.left.accept(self) + node.right.accept(self) + + def visit_NotNode(self, node: mparser.NotNode): + node.value.accept(self) + + def visit_CodeBlockNode(self, node: mparser.CodeBlockNode): + for i in node.lines: + i.accept(self) + + def visit_IndexNode(self, node: mparser.IndexNode): + node.index.accept(self) + + def visit_MethodNode(self, node: mparser.MethodNode): + node.source_object.accept(self) + node.args.accept(self) + + def visit_FunctionNode(self, node: mparser.FunctionNode): + node.args.accept(self) + + def visit_AssignmentNode(self, node: mparser.AssignmentNode): + node.value.accept(self) + + def visit_PlusAssignmentNode(self, node: mparser.PlusAssignmentNode): + node.value.accept(self) + + def visit_ForeachClauseNode(self, node: mparser.ForeachClauseNode): + node.items.accept(self) + node.block.accept(self) + + def visit_IfClauseNode(self, node: mparser.IfClauseNode): + for i in node.ifs: + i.accept(self) + if node.elseblock: + node.elseblock.accept(self) + + def visit_UMinusNode(self, node: mparser.UMinusNode): + node.value.accept(self) + + def visit_IfNode(self, node: mparser.IfNode): + node.condition.accept(self) + node.block.accept(self) + + def visit_TernaryNode(self, node: mparser.TernaryNode): + node.condition.accept(self) + node.trueblock.accept(self) + node.falseblock.accept(self) + + def visit_ArgumentNode(self, node: mparser.ArgumentNode): + for i in node.arguments: + i.accept(self) + for i in node.commas: + pass + for val in node.kwargs.values(): + val.accept(self) |