diff options
Diffstat (limited to 'mesonbuild/ast')
-rw-r--r-- | mesonbuild/ast/__init__.py | 32 | ||||
-rw-r--r-- | mesonbuild/ast/interpreter.py | 209 | ||||
-rw-r--r-- | mesonbuild/ast/introspection.py | 241 | ||||
-rw-r--r-- | mesonbuild/ast/postprocess.py | 86 | ||||
-rw-r--r-- | mesonbuild/ast/printer.py | 203 | ||||
-rw-r--r-- | mesonbuild/ast/visitor.py | 140 |
6 files changed, 911 insertions, 0 deletions
diff --git a/mesonbuild/ast/__init__.py b/mesonbuild/ast/__init__.py new file mode 100644 index 0000000..a9370dc --- /dev/null +++ b/mesonbuild/ast/__init__.py @@ -0,0 +1,32 @@ +# 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', + 'AstIDGenerator', + 'AstIndentationGenerator', + 'AstVisitor', + 'AstPrinter', + 'IntrospectionInterpreter', + 'build_target_functions', +] + +from .interpreter import AstInterpreter +from .introspection import IntrospectionInterpreter, build_target_functions +from .visitor import AstVisitor +from .postprocess import AstIDGenerator, AstIndentationGenerator +from .printer import AstPrinter diff --git a/mesonbuild/ast/interpreter.py b/mesonbuild/ast/interpreter.py new file mode 100644 index 0000000..2071432 --- /dev/null +++ b/mesonbuild/ast/interpreter.py @@ -0,0 +1,209 @@ +# 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 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.assignments = {} + 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): + assert(isinstance(node, mparser.PlusAssignmentNode)) + if node.var_name not in self.assignments: + self.assignments[node.var_name] = [] + self.assignments[node.var_name] += [node.value] # Save a reference to the value node + self.evaluate_statement(node.value) # Evaluate the value just in case + + 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): + assert(isinstance(node, mparser.AssignmentNode)) + self.assignments[node.var_name] = [node.value] # Save a reference to the value node + 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 + flattend_args = [] + temp_args = [] + if isinstance(args, mparser.ArrayNode): + args = [x for x in args.args.arguments] + elif isinstance(args, mparser.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] + else: + temp_args += [i] + for i in temp_args: + if isinstance(i, mparser.ElementaryNode) and not isinstance(i, mparser.IdNode): + flattend_args += [i.value] + elif isinstance(i, (str, bool, int, float)) or 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): + flattend_kwargs[key] = val.value + elif isinstance(val, (mparser.ArrayNode, mparser.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 + return flattend_kwargs diff --git a/mesonbuild/ast/introspection.py b/mesonbuild/ast/introspection.py new file mode 100644 index 0000000..5d0ec5a --- /dev/null +++ b/mesonbuild/ast/introspection.py @@ -0,0 +1,241 @@ +# 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 AstInterpreter +from .. import compilers, environment, mesonlib, mparser, optinterpreter +from .. import coredata as cdata +from ..interpreterbase import InvalidArguments +from ..build import Executable, Jar, SharedLibrary, SharedModule, StaticLibrary +import os + +build_target_functions = ['executable', 'jar', 'library', 'shared_library', 'shared_module', 'static_library', 'both_libraries'] + +class IntrospectionHelper: + # mimic an argparse namespace + def __init__(self, cross_file): + self.cross_file = cross_file + self.native_file = None + self.cmd_line_options = {} + +class IntrospectionInterpreter(AstInterpreter): + # Interpreter to detect the options without a build directory + # Most of the code is stolen from interperter.Interpreter + def __init__(self, source_root, subdir, backend, cross_file=None, subproject='', subproject_dir='subprojects', env=None): + super().__init__(source_root, subdir) + + options = IntrospectionHelper(cross_file) + self.cross_file = cross_file + if env is None: + self.environment = environment.Environment(source_root, None, options) + else: + self.environment = env + self.subproject = subproject + self.subproject_dir = subproject_dir + self.coredata = self.environment.get_coredata() + self.option_file = os.path.join(self.source_root, self.subdir, 'meson_options.txt') + self.backend = backend + self.default_options = {'backend': self.backend} + self.project_data = {} + self.targets = [] + + self.funcs.update({ + 'add_languages': self.func_add_languages, + 'executable': self.func_executable, + 'jar': self.func_jar, + 'library': self.func_library, + 'project': self.func_project, + 'shared_library': self.func_shared_lib, + 'shared_module': self.func_shared_module, + 'static_library': self.func_static_lib, + 'both_libraries': self.func_both_lib, + }) + + def func_project(self, node, args, kwargs): + if len(args) < 1: + raise InvalidArguments('Not enough arguments to project(). Needs at least the project name.') + + proj_name = args[0] + proj_vers = kwargs.get('version', 'undefined') + proj_langs = self.flatten_args(args[1:]) + if isinstance(proj_vers, mparser.ElementaryNode): + proj_vers = proj_vers.value + if not isinstance(proj_vers, str): + proj_vers = 'undefined' + self.project_data = {'descriptive_name': proj_name, 'version': proj_vers} + + if os.path.exists(self.option_file): + oi = optinterpreter.OptionInterpreter(self.subproject) + oi.process(self.option_file) + self.coredata.merge_user_options(oi.options) + + def_opts = self.flatten_args(kwargs.get('default_options', [])) + self.project_default_options = mesonlib.stringlistify(def_opts) + self.project_default_options = cdata.create_options_dict(self.project_default_options) + self.default_options.update(self.project_default_options) + self.coredata.set_default_options(self.default_options, self.subproject, self.environment.cmd_line_options) + + if not self.is_subproject() and 'subproject_dir' in kwargs: + spdirname = kwargs['subproject_dir'] + if isinstance(spdirname, str): + self.subproject_dir = spdirname + if not self.is_subproject(): + self.project_data['subprojects'] = [] + subprojects_dir = os.path.join(self.source_root, self.subproject_dir) + if os.path.isdir(subprojects_dir): + for i in os.listdir(subprojects_dir): + if os.path.isdir(os.path.join(subprojects_dir, i)): + self.do_subproject(i) + + self.coredata.init_backend_options(self.backend) + options = {k: v for k, v in self.environment.cmd_line_options.items() if k.startswith('backend_')} + + self.coredata.set_options(options) + self.func_add_languages(None, proj_langs, None) + + def do_subproject(self, dirname): + subproject_dir_abs = os.path.join(self.environment.get_source_dir(), self.subproject_dir) + subpr = os.path.join(subproject_dir_abs, dirname) + try: + subi = IntrospectionInterpreter(subpr, '', self.backend, cross_file=self.cross_file, subproject=dirname, subproject_dir=self.subproject_dir, env=self.environment) + subi.analyze() + subi.project_data['name'] = dirname + self.project_data['subprojects'] += [subi.project_data] + except: + return + + def func_add_languages(self, node, args, kwargs): + args = self.flatten_args(args) + need_cross_compiler = self.environment.is_cross_build() + for lang in sorted(args, key=compilers.sort_clink): + lang = lang.lower() + if lang not in self.coredata.compilers: + self.environment.detect_compilers(lang, need_cross_compiler) + + def build_target(self, node, args, kwargs, targetclass): + if not args: + return + kwargs = self.flatten_kwargs(kwargs, True) + name = self.flatten_args(args)[0] + srcqueue = [node] + if 'sources' in kwargs: + srcqueue += kwargs['sources'] + + source_nodes = [] + while srcqueue: + curr = srcqueue.pop(0) + arg_node = None + if isinstance(curr, mparser.FunctionNode): + arg_node = curr.args + elif isinstance(curr, mparser.ArrayNode): + arg_node = curr.args + elif isinstance(curr, mparser.IdNode): + # Try to resolve the ID and append the node to the queue + id = curr.value + if id in self.assignments and self.assignments[id]: + node = self.assignments[id][0] + if isinstance(node, (mparser.ArrayNode, mparser.IdNode, mparser.FunctionNode)): + srcqueue += [node] + if arg_node is None: + continue + elemetary_nodes = list(filter(lambda x: isinstance(x, (str, mparser.StringNode)), arg_node.arguments)) + srcqueue += list(filter(lambda x: isinstance(x, (mparser.FunctionNode, mparser.ArrayNode, mparser.IdNode)), arg_node.arguments)) + # Pop the first element if the function is a build target function + if isinstance(curr, mparser.FunctionNode) and curr.func_name in build_target_functions: + elemetary_nodes.pop(0) + if elemetary_nodes: + source_nodes += [curr] + + # Filter out kwargs from other target types. For example 'soversion' + # passed to library() when default_library == 'static'. + kwargs = {k: v for k, v in kwargs.items() if k in targetclass.known_kwargs} + + is_cross = False + objects = [] + empty_sources = [] # Passing the unresolved sources list causes errors + target = targetclass(name, self.subdir, self.subproject, is_cross, empty_sources, objects, self.environment, kwargs) + + self.targets += [{ + 'name': target.get_basename(), + 'id': target.get_id(), + 'type': target.get_typename(), + 'defined_in': os.path.normpath(os.path.join(self.source_root, self.subdir, environment.build_filename)), + 'subdir': self.subdir, + 'build_by_default': target.build_by_default, + 'sources': source_nodes, + 'kwargs': kwargs, + 'node': node, + }] + + return + + def build_library(self, node, args, kwargs): + default_library = self.coredata.get_builtin_option('default_library') + if default_library == 'shared': + return self.build_target(node, args, kwargs, SharedLibrary) + elif default_library == 'static': + return self.build_target(node, args, kwargs, StaticLibrary) + elif default_library == 'both': + return self.build_target(node, args, kwargs, SharedLibrary) + + def func_executable(self, node, args, kwargs): + return self.build_target(node, args, kwargs, Executable) + + def func_static_lib(self, node, args, kwargs): + return self.build_target(node, args, kwargs, StaticLibrary) + + def func_shared_lib(self, node, args, kwargs): + return self.build_target(node, args, kwargs, SharedLibrary) + + def func_both_lib(self, node, args, kwargs): + return self.build_target(node, args, kwargs, SharedLibrary) + + def func_shared_module(self, node, args, kwargs): + return self.build_target(node, args, kwargs, SharedModule) + + def func_library(self, node, args, kwargs): + return self.build_library(node, args, kwargs) + + def func_jar(self, node, args, kwargs): + return self.build_target(node, args, kwargs, Jar) + + def func_build_target(self, node, args, kwargs): + if 'target_type' not in kwargs: + return + target_type = kwargs.pop('target_type') + if isinstance(target_type, mparser.ElementaryNode): + target_type = target_type.value + if target_type == 'executable': + return self.build_target(node, args, kwargs, Executable) + elif target_type == 'shared_library': + return self.build_target(node, args, kwargs, SharedLibrary) + elif target_type == 'static_library': + return self.build_target(node, args, kwargs, StaticLibrary) + elif target_type == 'both_libraries': + return self.build_target(node, args, kwargs, SharedLibrary) + elif target_type == 'library': + return self.build_library(node, args, kwargs) + elif target_type == 'jar': + return self.build_target(node, args, kwargs, Jar) + + def is_subproject(self): + return self.subproject != '' + + def analyze(self): + self.load_root_meson_file() + self.sanity_check_ast() + self.parse_project() + self.run() diff --git a/mesonbuild/ast/postprocess.py b/mesonbuild/ast/postprocess.py new file mode 100644 index 0000000..e913b4f --- /dev/null +++ b/mesonbuild/ast/postprocess.py @@ -0,0 +1,86 @@ +# 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 + +from . import AstVisitor +from .. import mparser + +class AstIndentationGenerator(AstVisitor): + def __init__(self): + self.level = 0 + + def visit_default_func(self, node: mparser.BaseNode): + # Store the current level in the node + node.level = self.level + + def visit_ArrayNode(self, node: mparser.ArrayNode): + self.visit_default_func(node) + self.level += 1 + node.args.accept(self) + self.level -= 1 + + def visit_DictNode(self, node: mparser.DictNode): + self.visit_default_func(node) + self.level += 1 + node.args.accept(self) + self.level -= 1 + + def visit_MethodNode(self, node: mparser.MethodNode): + self.visit_default_func(node) + node.source_object.accept(self) + self.level += 1 + node.args.accept(self) + self.level -= 1 + + def visit_FunctionNode(self, node: mparser.FunctionNode): + self.visit_default_func(node) + self.level += 1 + node.args.accept(self) + self.level -= 1 + + def visit_ForeachClauseNode(self, node: mparser.ForeachClauseNode): + self.visit_default_func(node) + self.level += 1 + node.items.accept(self) + node.block.accept(self) + self.level -= 1 + + def visit_IfClauseNode(self, node: mparser.IfClauseNode): + self.visit_default_func(node) + for i in node.ifs: + i.accept(self) + if node.elseblock: + self.level += 1 + node.elseblock.accept(self) + self.level -= 1 + + def visit_IfNode(self, node: mparser.IfNode): + self.visit_default_func(node) + self.level += 1 + node.condition.accept(self) + node.block.accept(self) + self.level -= 1 + +class AstIDGenerator(AstVisitor): + def __init__(self): + self.counter = {} + + def visit_default_func(self, node: mparser.BaseNode): + name = type(node).__name__ + if name not in self.counter: + self.counter[name] = 0 + node.ast_id = name + '#' + str(self.counter[name]) + self.counter[name] += 1 diff --git a/mesonbuild/ast/printer.py b/mesonbuild/ast/printer.py new file mode 100644 index 0000000..60e0b0d --- /dev/null +++ b/mesonbuild/ast/printer.py @@ -0,0 +1,203 @@ +# 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 + +from .. import mparser +from . import AstVisitor +import re + +arithmic_map = { + 'add': '+', + 'sub': '-', + 'mod': '%', + 'mul': '*', + 'div': '/' +} + +class AstPrinter(AstVisitor): + def __init__(self, indent: int = 2, arg_newline_cutoff: int = 5): + self.result = '' + self.indent = indent + self.arg_newline_cutoff = arg_newline_cutoff + self.ci = '' + self.is_newline = True + self.last_level = 0 + + def post_process(self): + self.result = re.sub(r'\s+\n', '\n', self.result) + + def append(self, data: str, node: mparser.BaseNode): + level = 0 + if node and hasattr(node, 'level'): + level = node.level + else: + level = self.last_level + self.last_level = level + if self.is_newline: + self.result += ' ' * (level * self.indent) + self.result += data + self.is_newline = False + + def append_padded(self, data: str, node: mparser.BaseNode): + if self.result[-1] not in [' ', '\n']: + data = ' ' + data + self.append(data + ' ', node) + + def newline(self): + self.result += '\n' + self.is_newline = True + + def visit_BooleanNode(self, node: mparser.BooleanNode): + self.append('true' if node.value else 'false', node) + + def visit_IdNode(self, node: mparser.IdNode): + self.append(node.value, node) + + def visit_NumberNode(self, node: mparser.NumberNode): + self.append(str(node.value), node) + + def visit_StringNode(self, node: mparser.StringNode): + self.append("'" + node.value + "'", node) + + def visit_ContinueNode(self, node: mparser.ContinueNode): + self.append('continue', node) + + def visit_BreakNode(self, node: mparser.BreakNode): + self.append('break', node) + + def visit_ArrayNode(self, node: mparser.ArrayNode): + self.append('[', node) + node.args.accept(self) + self.append(']', node) + + def visit_DictNode(self, node: mparser.DictNode): + self.append('{', node) + node.args.accept(self) + self.append('}', node) + + def visit_OrNode(self, node: mparser.OrNode): + node.left.accept(self) + self.append_padded('or', node) + node.right.accept(self) + + def visit_AndNode(self, node: mparser.AndNode): + node.left.accept(self) + self.append_padded('and', node) + node.right.accept(self) + + def visit_ComparisonNode(self, node: mparser.ComparisonNode): + node.left.accept(self) + self.append_padded(mparser.comparison_map[node.ctype], node) + node.right.accept(self) + + def visit_ArithmeticNode(self, node: mparser.ArithmeticNode): + node.left.accept(self) + self.append_padded(arithmic_map[node.operation], node) + node.right.accept(self) + + def visit_NotNode(self, node: mparser.NotNode): + self.append_padded('not', node) + node.value.accept(self) + + def visit_CodeBlockNode(self, node: mparser.CodeBlockNode): + for i in node.lines: + i.accept(self) + self.newline() + + def visit_IndexNode(self, node: mparser.IndexNode): + self.append('[', node) + node.index.accept(self) + self.append(']', node) + + def visit_MethodNode(self, node: mparser.MethodNode): + node.source_object.accept(self) + self.append('.' + node.name + '(', node) + node.args.accept(self) + self.append(')', node) + + def visit_FunctionNode(self, node: mparser.FunctionNode): + self.append(node.func_name + '(', node) + node.args.accept(self) + self.append(')', node) + + def visit_AssignmentNode(self, node: mparser.AssignmentNode): + self.append(node.var_name + ' = ', node) + node.value.accept(self) + + def visit_PlusAssignmentNode(self, node: mparser.PlusAssignmentNode): + self.append(node.var_name + ' += ', node) + node.value.accept(self) + + def visit_ForeachClauseNode(self, node: mparser.ForeachClauseNode): + varnames = [x.value for x in node.varnames] + self.append_padded('foreach', node) + self.append_padded(', '.join(varnames), node) + self.append_padded(':', node) + node.items.accept(self) + self.newline() + node.block.accept(self) + self.append('endforeach', node) + + def visit_IfClauseNode(self, node: mparser.IfClauseNode): + prefix = '' + for i in node.ifs: + self.append_padded(prefix + 'if', node) + prefix = 'el' + i.accept(self) + if node.elseblock: + self.append('else', node) + node.elseblock.accept(self) + self.append('endif', node) + + def visit_UMinusNode(self, node: mparser.UMinusNode): + self.append_padded('-', node) + node.value.accept(self) + + def visit_IfNode(self, node: mparser.IfNode): + node.condition.accept(self) + self.newline() + node.block.accept(self) + + def visit_TernaryNode(self, node: mparser.TernaryNode): + node.condition.accept(self) + self.append_padded('?', node) + node.trueblock.accept(self) + self.append_padded(':', node) + node.falseblock.accept(self) + + def visit_ArgumentNode(self, node: mparser.ArgumentNode): + break_args = True if (len(node.arguments) + len(node.kwargs)) > self.arg_newline_cutoff else False + for i in node.arguments + list(node.kwargs.values()): + if not isinstance(i, mparser.ElementaryNode): + break_args = True + if break_args: + self.newline() + for i in node.arguments: + i.accept(self) + self.append(', ', node) + if break_args: + self.newline() + for key, val in node.kwargs.items(): + self.append(key, node) + self.append_padded(':', node) + val.accept(self) + self.append(', ', node) + if break_args: + self.newline() + if break_args: + self.result = re.sub(r', \n$', '\n', self.result) + else: + self.result = re.sub(r', $', '', self.result) diff --git a/mesonbuild/ast/visitor.py b/mesonbuild/ast/visitor.py new file mode 100644 index 0000000..c8769d4 --- /dev/null +++ b/mesonbuild/ast/visitor.py @@ -0,0 +1,140 @@ +# 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 + +from .. import mparser + +class AstVisitor: + def __init__(self): + pass + + def visit_default_func(self, node: mparser.BaseNode): + pass + + def visit_BooleanNode(self, node: mparser.BooleanNode): + self.visit_default_func(node) + + def visit_IdNode(self, node: mparser.IdNode): + self.visit_default_func(node) + + def visit_NumberNode(self, node: mparser.NumberNode): + self.visit_default_func(node) + + def visit_StringNode(self, node: mparser.StringNode): + self.visit_default_func(node) + + def visit_ContinueNode(self, node: mparser.ContinueNode): + self.visit_default_func(node) + + def visit_BreakNode(self, node: mparser.BreakNode): + self.visit_default_func(node) + + def visit_ArrayNode(self, node: mparser.ArrayNode): + self.visit_default_func(node) + node.args.accept(self) + + def visit_DictNode(self, node: mparser.DictNode): + self.visit_default_func(node) + node.args.accept(self) + + def visit_EmptyNode(self, node: mparser.EmptyNode): + self.visit_default_func(node) + + def visit_OrNode(self, node: mparser.OrNode): + self.visit_default_func(node) + node.left.accept(self) + node.right.accept(self) + + def visit_AndNode(self, node: mparser.AndNode): + self.visit_default_func(node) + node.left.accept(self) + node.right.accept(self) + + def visit_ComparisonNode(self, node: mparser.ComparisonNode): + self.visit_default_func(node) + node.left.accept(self) + node.right.accept(self) + + def visit_ArithmeticNode(self, node: mparser.ArithmeticNode): + self.visit_default_func(node) + node.left.accept(self) + node.right.accept(self) + + def visit_NotNode(self, node: mparser.NotNode): + self.visit_default_func(node) + node.value.accept(self) + + def visit_CodeBlockNode(self, node: mparser.CodeBlockNode): + self.visit_default_func(node) + for i in node.lines: + i.accept(self) + + def visit_IndexNode(self, node: mparser.IndexNode): + self.visit_default_func(node) + node.index.accept(self) + + def visit_MethodNode(self, node: mparser.MethodNode): + self.visit_default_func(node) + node.source_object.accept(self) + node.args.accept(self) + + def visit_FunctionNode(self, node: mparser.FunctionNode): + self.visit_default_func(node) + node.args.accept(self) + + def visit_AssignmentNode(self, node: mparser.AssignmentNode): + self.visit_default_func(node) + node.value.accept(self) + + def visit_PlusAssignmentNode(self, node: mparser.PlusAssignmentNode): + self.visit_default_func(node) + node.value.accept(self) + + def visit_ForeachClauseNode(self, node: mparser.ForeachClauseNode): + self.visit_default_func(node) + node.items.accept(self) + node.block.accept(self) + + def visit_IfClauseNode(self, node: mparser.IfClauseNode): + self.visit_default_func(node) + for i in node.ifs: + i.accept(self) + if node.elseblock: + node.elseblock.accept(self) + + def visit_UMinusNode(self, node: mparser.UMinusNode): + self.visit_default_func(node) + node.value.accept(self) + + def visit_IfNode(self, node: mparser.IfNode): + self.visit_default_func(node) + node.condition.accept(self) + node.block.accept(self) + + def visit_TernaryNode(self, node: mparser.TernaryNode): + self.visit_default_func(node) + node.condition.accept(self) + node.trueblock.accept(self) + node.falseblock.accept(self) + + def visit_ArgumentNode(self, node: mparser.ArgumentNode): + self.visit_default_func(node) + for i in node.arguments: + i.accept(self) + for i in node.commas: + pass + for val in node.kwargs.values(): + val.accept(self) |