commit
228a9035af
14 changed files with 1113 additions and 642 deletions
@ -0,0 +1,241 @@ |
||||
# 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, mlog, mparser, mesonlib |
||||
from . import environment |
||||
|
||||
from .interpreterbase import InterpreterException, InvalidArguments |
||||
|
||||
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.asts = {} |
||||
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_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_do_nothing(self, node, args, kwargs): |
||||
return True |
||||
|
||||
def method_call(self, node): |
||||
return True |
||||
|
||||
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('Nonexistant 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 |
||||
self.evaluate_codeblock(codeblock) |
||||
self.subdir = prev_subdir |
||||
|
||||
def func_files(self, node, args, kwargs): |
||||
if not isinstance(args, list): |
||||
return [args] |
||||
return args |
||||
|
||||
def evaluate_arithmeticstatement(self, cur): |
||||
return 0 |
||||
|
||||
def evaluate_plusassign(self, node): |
||||
return 0 |
||||
|
||||
def evaluate_indexing(self, node): |
||||
return 0 |
||||
|
||||
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 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 unknown_function_called(self, func_name): |
||||
mlog.warning('Unknown function called: ' + func_name) |
||||
|
||||
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])) |
@ -0,0 +1,636 @@ |
||||
# 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 mparser, mesonlib, mlog |
||||
from . import environment, dependencies |
||||
|
||||
import os, copy, re |
||||
from functools import wraps |
||||
|
||||
# Decorators for method calls. |
||||
|
||||
def check_stringlist(a, msg='Arguments must be strings.'): |
||||
if not isinstance(a, list): |
||||
mlog.debug('Not a list:', str(a)) |
||||
raise InvalidArguments('Argument not a list.') |
||||
if not all(isinstance(s, str) for s in a): |
||||
mlog.debug('Element not a string:', str(a)) |
||||
raise InvalidArguments(msg) |
||||
|
||||
def noPosargs(f): |
||||
@wraps(f) |
||||
def wrapped(self, node, args, kwargs): |
||||
if len(args) != 0: |
||||
raise InvalidArguments('Function does not take positional arguments.') |
||||
return f(self, node, args, kwargs) |
||||
return wrapped |
||||
|
||||
def noKwargs(f): |
||||
@wraps(f) |
||||
def wrapped(self, node, args, kwargs): |
||||
if len(kwargs) != 0: |
||||
raise InvalidArguments('Function does not take keyword arguments.') |
||||
return f(self, node, args, kwargs) |
||||
return wrapped |
||||
|
||||
def stringArgs(f): |
||||
@wraps(f) |
||||
def wrapped(self, node, args, kwargs): |
||||
assert(isinstance(args, list)) |
||||
check_stringlist(args) |
||||
return f(self, node, args, kwargs) |
||||
return wrapped |
||||
|
||||
|
||||
class InterpreterException(mesonlib.MesonException): |
||||
pass |
||||
|
||||
class InvalidCode(InterpreterException): |
||||
pass |
||||
|
||||
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) |
||||
if not os.path.isfile(mesonfile): |
||||
raise InvalidArguments('Missing Meson file in %s' % mesonfile) |
||||
with open(mesonfile, encoding='utf8') as mf: |
||||
code = mf.read() |
||||
if len(code.strip()) == 0: |
||||
raise InvalidCode('Builder file is empty.') |
||||
assert(isinstance(code, str)) |
||||
try: |
||||
self.ast = mparser.Parser(code, self.subdir).parse() |
||||
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 evaluate_notstatement(self, cur): |
||||
v = self.evaluate_statement(cur.value) |
||||
if isinstance(v, mparser.BooleanNode): |
||||
v = v.value |
||||
if not isinstance(v, bool): |
||||
raise InterpreterException('Argument to "not" is not a boolean.') |
||||
return not v |
||||
|
||||
def evaluate_if(self, node): |
||||
assert(isinstance(node, mparser.IfClauseNode)) |
||||
for i in node.ifs: |
||||
result = self.evaluate_statement(i.condition) |
||||
if not(isinstance(result, bool)): |
||||
raise InvalidCode('If clause {!r} does not evaluate to true or false.'.format(result)) |
||||
if result: |
||||
self.evaluate_codeblock(i.block) |
||||
return |
||||
if not isinstance(node.elseblock, mparser.EmptyNode): |
||||
self.evaluate_codeblock(node.elseblock) |
||||
|
||||
def evaluate_comparison(self, node): |
||||
v1 = self.evaluate_statement(node.left) |
||||
v2 = self.evaluate_statement(node.right) |
||||
if self.is_elementary_type(v1): |
||||
val1 = v1 |
||||
else: |
||||
val1 = v1.value |
||||
if self.is_elementary_type(v2): |
||||
val2 = v2 |
||||
else: |
||||
val2 = v2.value |
||||
if node.ctype == '==': |
||||
return val1 == val2 |
||||
elif node.ctype == '!=': |
||||
return val1 != val2 |
||||
elif node.ctype == '<': |
||||
return val1 < val2 |
||||
elif node.ctype == '<=': |
||||
return val1 <= val2 |
||||
elif node.ctype == '>': |
||||
return val1 > val2 |
||||
elif node.ctype == '>=': |
||||
return val1 >= val2 |
||||
else: |
||||
raise InvalidCode('You broke my compare eval.') |
||||
|
||||
def evaluate_andstatement(self, cur): |
||||
l = self.evaluate_statement(cur.left) |
||||
if isinstance(l, mparser.BooleanNode): |
||||
l = l.value |
||||
if not isinstance(l, bool): |
||||
raise InterpreterException('First argument to "and" is not a boolean.') |
||||
if not l: |
||||
return False |
||||
r = self.evaluate_statement(cur.right) |
||||
if isinstance(r, mparser.BooleanNode): |
||||
r = r.value |
||||
if not isinstance(r, bool): |
||||
raise InterpreterException('Second argument to "and" is not a boolean.') |
||||
return r |
||||
|
||||
def evaluate_orstatement(self, cur): |
||||
l = self.evaluate_statement(cur.left) |
||||
if isinstance(l, mparser.BooleanNode): |
||||
l = l.get_value() |
||||
if not isinstance(l, bool): |
||||
raise InterpreterException('First argument to "or" is not a boolean.') |
||||
if l: |
||||
return True |
||||
r = self.evaluate_statement(cur.right) |
||||
if isinstance(r, mparser.BooleanNode): |
||||
r = r.get_value() |
||||
if not isinstance(r, bool): |
||||
raise InterpreterException('Second argument to "or" is not a boolean.') |
||||
return r |
||||
|
||||
def evaluate_uminusstatement(self, cur): |
||||
v = self.evaluate_statement(cur.value) |
||||
if isinstance(v, mparser.NumberNode): |
||||
v = v.value |
||||
if not isinstance(v, int): |
||||
raise InterpreterException('Argument to negation is not an integer.') |
||||
return -v |
||||
|
||||
def evaluate_arithmeticstatement(self, cur): |
||||
l = self.to_native(self.evaluate_statement(cur.left)) |
||||
r = self.to_native(self.evaluate_statement(cur.right)) |
||||
|
||||
if cur.operation == 'add': |
||||
try: |
||||
return l + r |
||||
except Exception as e: |
||||
raise InvalidCode('Invalid use of addition: ' + str(e)) |
||||
elif cur.operation == 'sub': |
||||
if not isinstance(l, int) or not isinstance(r, int): |
||||
raise InvalidCode('Subtraction works only with integers.') |
||||
return l - r |
||||
elif cur.operation == 'mul': |
||||
if not isinstance(l, int) or not isinstance(r, int): |
||||
raise InvalidCode('Multiplication works only with integers.') |
||||
return l * r |
||||
elif cur.operation == 'div': |
||||
if not isinstance(l, int) or not isinstance(r, int): |
||||
raise InvalidCode('Division works only with integers.') |
||||
return l // r |
||||
elif cur.operation == 'mod': |
||||
if not isinstance(l, int) or not isinstance(r, int): |
||||
raise InvalidCode('Modulo works only with integers.') |
||||
return l % r |
||||
else: |
||||
raise InvalidCode('You broke me.') |
||||
|
||||
def evaluate_ternary(self, node): |
||||
assert(isinstance(node, mparser.TernaryNode)) |
||||
result = self.evaluate_statement(node.condition) |
||||
if not isinstance(result, bool): |
||||
raise InterpreterException('Ternary condition is not boolean.') |
||||
if result: |
||||
return self.evaluate_statement(node.trueblock) |
||||
else: |
||||
return self.evaluate_statement(node.falseblock) |
||||
|
||||
def evaluate_foreach(self, node): |
||||
assert(isinstance(node, mparser.ForeachClauseNode)) |
||||
varname = node.varname.value |
||||
items = self.evaluate_statement(node.items) |
||||
if not isinstance(items, list): |
||||
raise InvalidArguments('Items of foreach loop is not an array') |
||||
for item in items: |
||||
self.set_variable(varname, item) |
||||
self.evaluate_codeblock(node.block) |
||||
|
||||
def evaluate_plusassign(self, node): |
||||
assert(isinstance(node, mparser.PlusAssignmentNode)) |
||||
varname = node.var_name |
||||
addition = self.evaluate_statement(node.value) |
||||
# Remember that all variables are immutable. We must always create a |
||||
# full new variable and then assign it. |
||||
old_variable = self.get_variable(varname) |
||||
if isinstance(old_variable, str): |
||||
if not isinstance(addition, str): |
||||
raise InvalidArguments('The += operator requires a string on the right hand side if the variable on the left is a string') |
||||
new_value = old_variable + addition |
||||
elif isinstance(old_variable, int): |
||||
if not isinstance(addition, int): |
||||
raise InvalidArguments('The += operator requires an int on the right hand side if the variable on the left is an int') |
||||
new_value = old_variable + addition |
||||
elif not isinstance(old_variable, list): |
||||
raise InvalidArguments('The += operator currently only works with arrays, strings or ints ') |
||||
# Add other data types here. |
||||
else: |
||||
if isinstance(addition, list): |
||||
new_value = old_variable + addition |
||||
else: |
||||
new_value = old_variable + [addition] |
||||
self.set_variable(varname, new_value) |
||||
|
||||
def evaluate_indexing(self, node): |
||||
assert(isinstance(node, mparser.IndexNode)) |
||||
iobject = self.evaluate_statement(node.iobject) |
||||
if not isinstance(iobject, list): |
||||
raise InterpreterException('Tried to index a non-array object.') |
||||
index = self.evaluate_statement(node.index) |
||||
if not isinstance(index, int): |
||||
raise InterpreterException('Index value is not an integer.') |
||||
if index < -len(iobject) or index >= len(iobject): |
||||
raise InterpreterException('Index %d out of bounds of array of size %d.' % (index, len(iobject))) |
||||
return iobject[index] |
||||
|
||||
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 method_call(self, node): |
||||
invokable = node.source_object |
||||
if isinstance(invokable, mparser.IdNode): |
||||
object_name = invokable.value |
||||
obj = self.get_variable(object_name) |
||||
else: |
||||
obj = self.evaluate_statement(invokable) |
||||
method_name = node.name |
||||
args = node.args |
||||
if isinstance(obj, mparser.StringNode): |
||||
obj = obj.get_value() |
||||
if isinstance(obj, str): |
||||
return self.string_method_call(obj, method_name, args) |
||||
if isinstance(obj, bool): |
||||
return self.bool_method_call(obj, method_name, args) |
||||
if isinstance(obj, int): |
||||
return self.int_method_call(obj, method_name, args) |
||||
if isinstance(obj, list): |
||||
return self.array_method_call(obj, method_name, self.reduce_arguments(args)[0]) |
||||
if not isinstance(obj, InterpreterObject): |
||||
raise InvalidArguments('Variable "%s" is not callable.' % object_name) |
||||
(args, kwargs) = self.reduce_arguments(args) |
||||
if method_name == 'extract_objects': |
||||
self.validate_extraction(obj.held_object) |
||||
return obj.method_call(method_name, self.flatten(args), kwargs) |
||||
|
||||
def bool_method_call(self, obj, method_name, args): |
||||
obj = self.to_native(obj) |
||||
(posargs, _) = self.reduce_arguments(args) |
||||
if method_name == 'to_string': |
||||
if len(posargs) == 0: |
||||
if obj == True: |
||||
return 'true' |
||||
else: |
||||
return 'false' |
||||
elif len(posargs) == 2 and isinstance(posargs[0], str) and isinstance(posargs[1], str): |
||||
if obj == True: |
||||
return posargs[0] |
||||
else: |
||||
return posargs[1] |
||||
else: |
||||
raise InterpreterException('bool.to_string() must have either no arguments or exactly two string arguments that signify what values to return for true and false.') |
||||
elif method_name == 'to_int': |
||||
if obj == True: |
||||
return 1 |
||||
else: |
||||
return 0 |
||||
else: |
||||
raise InterpreterException('Unknown method "%s" for a boolean.' % method_name) |
||||
|
||||
def int_method_call(self, obj, method_name, args): |
||||
obj = self.to_native(obj) |
||||
(posargs, _) = self.reduce_arguments(args) |
||||
if method_name == 'is_even': |
||||
if len(posargs) == 0: |
||||
return obj % 2 == 0 |
||||
else: |
||||
raise InterpreterException('int.is_even() must have no arguments.') |
||||
elif method_name == 'is_odd': |
||||
if len(posargs) == 0: |
||||
return obj % 2 != 0 |
||||
else: |
||||
raise InterpreterException('int.is_odd() must have no arguments.') |
||||
else: |
||||
raise InterpreterException('Unknown method "%s" for an integer.' % method_name) |
||||
|
||||
def string_method_call(self, obj, method_name, args): |
||||
obj = self.to_native(obj) |
||||
(posargs, _) = self.reduce_arguments(args) |
||||
if method_name == 'strip': |
||||
return obj.strip() |
||||
elif method_name == 'format': |
||||
return self.format_string(obj, args) |
||||
elif method_name == 'to_upper': |
||||
return obj.upper() |
||||
elif method_name == 'to_lower': |
||||
return obj.lower() |
||||
elif method_name == 'underscorify': |
||||
return re.sub(r'[^a-zA-Z0-9]', '_', obj) |
||||
elif method_name == 'split': |
||||
if len(posargs) > 1: |
||||
raise InterpreterException('Split() must have at most one argument.') |
||||
elif len(posargs) == 1: |
||||
s = posargs[0] |
||||
if not isinstance(s, str): |
||||
raise InterpreterException('Split() argument must be a string') |
||||
return obj.split(s) |
||||
else: |
||||
return obj.split() |
||||
elif method_name == 'startswith' or method_name == 'contains' or method_name == 'endswith': |
||||
s = posargs[0] |
||||
if not isinstance(s, str): |
||||
raise InterpreterException('Argument must be a string.') |
||||
if method_name == 'startswith': |
||||
return obj.startswith(s) |
||||
elif method_name == 'contains': |
||||
return obj.find(s) >= 0 |
||||
return obj.endswith(s) |
||||
elif method_name == 'to_int': |
||||
try: |
||||
return int(obj) |
||||
except Exception: |
||||
raise InterpreterException('String {!r} cannot be converted to int'.format(obj)) |
||||
elif method_name == 'join': |
||||
if len(posargs) != 1: |
||||
raise InterpreterException('Join() takes exactly one argument.') |
||||
strlist = posargs[0] |
||||
check_stringlist(strlist) |
||||
return obj.join(strlist) |
||||
elif method_name == 'version_compare': |
||||
if len(posargs) != 1: |
||||
raise InterpreterException('Version_compare() takes exactly one argument.') |
||||
cmpr = posargs[0] |
||||
if not isinstance(cmpr, str): |
||||
raise InterpreterException('Version_compare() argument must be a string.') |
||||
return mesonlib.version_compare(obj, cmpr) |
||||
raise InterpreterException('Unknown method "%s" for a string.' % method_name) |
||||
|
||||
def unknown_function_called(self, func_name): |
||||
raise InvalidCode('Unknown function "%s".' % func_name) |
||||
|
||||
def array_method_call(self, obj, method_name, args): |
||||
if method_name == 'contains': |
||||
return self.check_contains(obj, args) |
||||
elif method_name == 'length': |
||||
return len(obj) |
||||
elif method_name == 'get': |
||||
index = args[0] |
||||
if not isinstance(index, int): |
||||
raise InvalidArguments('Array index must be a number.') |
||||
if index < -len(obj) or index >= len(obj): |
||||
raise InvalidArguments('Array index %s is out of bounds for array of size %d.' % (index, len(obj))) |
||||
return obj[index] |
||||
raise InterpreterException('Arrays do not have a method called "%s".' % method_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)) |
||||
|
||||
def func_build_target(self, node, args, kwargs): |
||||
if 'target_type' not in kwargs: |
||||
raise InterpreterException('Missing target_type keyword argument') |
||||
target_type = kwargs.pop('target_type') |
||||
if target_type == 'executable': |
||||
return self.func_executable(node, args, kwargs) |
||||
elif target_type == 'shared_library': |
||||
return self.func_shared_lib(node, args, kwargs) |
||||
elif target_type == 'static_library': |
||||
return self.func_static_lib(node, args, kwargs) |
||||
elif target_type == 'library': |
||||
return self.func_library(node, args, kwargs) |
||||
elif target_type == 'jar': |
||||
return self.func_jar(node, args, kwargs) |
||||
else: |
||||
raise InterpreterException('Unknown target_type.') |
||||
|
||||
def func_set_variable(self, node, args, kwargs): |
||||
if len(args) != 2: |
||||
raise InvalidCode('Set_variable takes two arguments.') |
||||
varname = args[0] |
||||
value = self.to_native(args[1]) |
||||
self.set_variable(varname, value) |
||||
|
||||
# @noKwargs |
||||
def func_get_variable(self, node, args, kwargs): |
||||
if len(args)<1 or len(args)>2: |
||||
raise InvalidCode('Get_variable takes one or two arguments.') |
||||
varname = args[0] |
||||
if not isinstance(varname, str): |
||||
raise InterpreterException('First argument must be a string.') |
||||
try: |
||||
return self.variables[varname] |
||||
except KeyError: |
||||
pass |
||||
if len(args) == 2: |
||||
return args[1] |
||||
raise InterpreterException('Tried to get unknown variable "%s".' % varname) |
||||
|
||||
@stringArgs |
||||
@noKwargs |
||||
def func_is_variable(self, node, args, kwargs): |
||||
if len(args) != 1: |
||||
raise InvalidCode('Is_variable takes two arguments.') |
||||
varname = args[0] |
||||
return varname in self.variables |
||||
|
||||
def is_elementary_type(self, v): |
||||
return isinstance(v, (int, float, str, bool, list)) |
||||
|
@ -0,0 +1,64 @@ |
||||
#!/usr/bin/env python3 |
||||
# 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. |
||||
|
||||
# This tool is used to manipulate an existing Meson build definition. |
||||
# |
||||
# - add a file to a target |
||||
# - remove files from a target |
||||
# - move targets |
||||
# - reindent? |
||||
|
||||
import mesonbuild.astinterpreter |
||||
from mesonbuild.mesonlib import MesonException |
||||
from mesonbuild import mlog |
||||
import sys, traceback |
||||
import argparse |
||||
|
||||
parser = argparse.ArgumentParser() |
||||
|
||||
parser.add_argument('--sourcedir', default='.', |
||||
help='Path to source directory.') |
||||
parser.add_argument('--target', default=None, |
||||
help='Name of target to edit.') |
||||
parser.add_argument('--filename', default=None, |
||||
help='Name of source file to add or remove to target.') |
||||
parser.add_argument('commands', nargs='+') |
||||
|
||||
if __name__ == '__main__': |
||||
options = parser.parse_args() |
||||
if options.target is None or options.filename is None: |
||||
sys.exit("Must specify both target and filename.") |
||||
print('This tool is highly experimental, use with care.') |
||||
rewriter = mesonbuild.astinterpreter.AstInterpreter(options.sourcedir, '') |
||||
try: |
||||
if options.commands[0] == 'add': |
||||
rewriter.add_source(options.target, options.filename) |
||||
elif options.commands[0] == 'remove': |
||||
rewriter.remove_source(options.target, options.filename) |
||||
else: |
||||
sys.exit('Unknown command: ' + options.commands[0]) |
||||
except Exception as e: |
||||
if isinstance(e, MesonException): |
||||
if hasattr(e, 'file') and hasattr(e, 'lineno') and hasattr(e, 'colno'): |
||||
mlog.log(mlog.red('\nMeson encountered an error in file %s, line %d, column %d:' % (e.file, e.lineno, e.colno))) |
||||
else: |
||||
mlog.log(mlog.red('\nMeson encountered an error:')) |
||||
mlog.log(e) |
||||
else: |
||||
traceback.print_exc() |
||||
sys.exit(1) |
@ -0,0 +1,5 @@ |
||||
project('rewritetest', 'c') |
||||
|
||||
sources = ['trivial.c'] |
||||
|
||||
exe = executable('trivialprog', 'notthere.c', sources) |
@ -0,0 +1,5 @@ |
||||
project('rewritetest', 'c') |
||||
|
||||
sources = ['trivial.c', 'notthere.c'] |
||||
|
||||
exe = executable('trivialprog', sources) |
@ -0,0 +1,5 @@ |
||||
project('rewritetest', 'c') |
||||
|
||||
sources = ['trivial.c'] |
||||
|
||||
exe = executable('trivialprog', sources) |
@ -0,0 +1,5 @@ |
||||
project('subdir rewrite', 'c') |
||||
|
||||
subdir('sub1') |
||||
subdir('sub2') |
||||
|
@ -0,0 +1 @@ |
||||
srcs = ['first.c'] |
@ -0,0 +1 @@ |
||||
srcs = ['first.c', 'second.c'] |
@ -0,0 +1,2 @@ |
||||
executable('something', srcs) |
||||
|
Loading…
Reference in new issue