diff options
Diffstat (limited to 'media/libaom/cmakeparser.py')
-rw-r--r-- | media/libaom/cmakeparser.py | 293 |
1 files changed, 293 insertions, 0 deletions
diff --git a/media/libaom/cmakeparser.py b/media/libaom/cmakeparser.py new file mode 100644 index 0000000000..b93a313bce --- /dev/null +++ b/media/libaom/cmakeparser.py @@ -0,0 +1,293 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +from pyparsing import (CharsNotIn, Group, Forward, Literal, Suppress, Word, + QuotedString, ZeroOrMore, alphas, alphanums) +from string import Template +import re + +# Grammar for CMake +comment = Literal('#') + ZeroOrMore(CharsNotIn('\n')) +quoted_argument = QuotedString('\"', '\\', multiline=True) +unquoted_argument = CharsNotIn('\n ()#\"\\') +argument = quoted_argument | unquoted_argument | Suppress(comment) +arguments = Forward() +arguments << (argument | (Literal('(') + ZeroOrMore(arguments) + Literal(')'))) +identifier = Word(alphas, alphanums+'_') +command = Group(identifier + Literal('(') + ZeroOrMore(arguments) + Literal(')')) +file_elements = command | Suppress(comment) +cmake = ZeroOrMore(file_elements) + + +def extract_arguments(parsed): + """Extract the command arguments skipping the parentheses""" + return parsed[2:len(parsed) - 1] + + +def match_block(command, parsed, start): + """Find the end of block starting with the command""" + depth = 0 + end = start + 1 + endcommand = 'end' + command + while parsed[end][0] != endcommand or depth > 0: + if parsed[end][0] == command: + depth += 1 + elif parsed[end][0] == endcommand: + depth -= 1 + end = end + 1 + if end == len(parsed): + print('error: eof when trying to match block statement: %s' + % parsed[start]) + return end + + +def parse_if(parsed, start): + """Parse if/elseif/else/endif into a list of conditions and commands""" + depth = 0 + conditions = [] + condition = [extract_arguments(parsed[start])] + start = start + 1 + end = start + + while parsed[end][0] != 'endif' or depth > 0: + command = parsed[end][0] + if command == 'if': + depth += 1 + elif command == 'else' and depth == 0: + condition.append(parsed[start:end]) + conditions.append(condition) + start = end + 1 + condition = [['TRUE']] + elif command == 'elseif' and depth == 0: + condition.append(parsed[start:end]) + conditions.append(condition) + condition = [extract_arguments(parsed[end])] + start = end + 1 + elif command == 'endif': + depth -= 1 + end = end + 1 + if end == len(parsed): + print('error: eof when trying to match if statement: %s' + % parsed[start]) + condition.append(parsed[start:end]) + conditions.append(condition) + return end, conditions + + +def substs(variables, values): + """Substitute variables into values""" + new_values = [] + for value in values: + t = Template(value) + new_value = t.safe_substitute(variables) + + # Safe substitute leaves unrecognized variables in place. + # We replace them with the empty string. + new_values.append(re.sub('\$\{\w+\}', '', new_value)) + return new_values + + +def evaluate(variables, cache_variables, parsed): + """Evaluate a list of parsed commands, returning sources to build""" + i = 0 + sources = [] + while i < len(parsed): + command = parsed[i][0] + arguments = substs(variables, extract_arguments(parsed[i])) + + if command == 'foreach': + end = match_block(command, parsed, i) + for argument in arguments[1:]: + # ; is also a valid divider, why have one when you can have two? + argument = argument.replace(';', ' ') + for value in argument.split(): + variables[arguments[0]] = value + cont_eval, new_sources = evaluate(variables, cache_variables, + parsed[i+1:end]) + sources.extend(new_sources) + if not cont_eval: + return cont_eval, sources + elif command == 'function': + # for now we just execute functions inline at point of declaration + # as this is sufficient to build libaom + pass + elif command == 'if': + i, conditions = parse_if(parsed, i) + for condition in conditions: + if evaluate_boolean(variables, condition[0]): + cont_eval, new_sources = evaluate(variables, + cache_variables, + condition[1]) + sources.extend(new_sources) + if not cont_eval: + return cont_eval, sources + break + elif command == 'include': + if arguments: + try: + print('including: %s' % arguments[0]) + sources.extend(parse(variables, cache_variables, arguments[0])) + except IOError: + print('warning: could not include: %s' % arguments[0]) + elif command == 'list': + try: + action = arguments[0] + variable = arguments[1] + values = arguments[2:] + if action == 'APPEND': + if variable not in variables: + variables[variable] = ' '.join(values) + else: + variables[variable] += ' ' + ' '.join(values) + except (IndexError, KeyError): + pass + elif command == 'option': + variable = arguments[0] + value = arguments[2] + # Allow options to be override without changing CMake files + if variable not in variables: + variables[variable] = value + elif command == 'return': + return False, sources + elif command == 'set': + variable = arguments[0] + values = arguments[1:] + # CACHE variables are not set if already present + try: + cache = values.index('CACHE') + values = values[0:cache] + if variable not in variables: + variables[variable] = ' '.join(values) + cache_variables.append(variable) + except ValueError: + variables[variable] = ' '.join(values) + # we need to emulate the behavior of these function calls + # because we don't support interpreting them directly + # see bug 1492292 + elif command in ['set_aom_config_var', 'set_aom_detect_var']: + variable = arguments[0] + value = arguments[1] + if variable not in variables: + variables[variable] = value + cache_variables.append(variable) + elif command == 'set_aom_option_var': + # option vars cannot go into cache_variables + variable = arguments[0] + value = arguments[2] + if variable not in variables: + variables[variable] = value + elif command == 'add_asm_library': + try: + sources.extend(variables[arguments[1]].split(' ')) + except (IndexError, KeyError): + pass + elif command == 'add_intrinsics_object_library': + try: + sources.extend(variables[arguments[3]].split(' ')) + except (IndexError, KeyError): + pass + elif command == 'add_library': + for source in arguments[1:]: + sources.extend(source.split(' ')) + elif command == 'target_sources': + for source in arguments[1:]: + sources.extend(source.split(' ')) + elif command == 'MOZDEBUG': + print('>>>> MOZDEBUG: %s' % ' '.join(arguments)) + i += 1 + return True, sources + + +def evaluate_boolean(variables, arguments): + """Evaluate a boolean expression""" + if not arguments: + return False + + argument = arguments[0] + + if argument == 'NOT': + return not evaluate_boolean(variables, arguments[1:]) + + if argument == '(': + i = 0 + depth = 1 + while depth > 0 and i < len(arguments): + i += 1 + if arguments[i] == '(': + depth += 1 + if arguments[i] == ')': + depth -= 1 + return evaluate_boolean(variables, arguments[1:i]) + + def evaluate_constant(argument): + try: + as_int = int(argument) + if as_int != 0: + return True + else: + return False + except ValueError: + upper = argument.upper() + if upper in ['ON', 'YES', 'TRUE', 'Y']: + return True + elif upper in ['OFF', 'NO', 'FALSE', 'N', 'IGNORE', '', 'NOTFOUND']: + return False + elif upper.endswith('-NOTFOUND'): + return False + return None + + def lookup_variable(argument): + # If statements can have old-style variables which are not demarcated + # like ${VARIABLE}. Attempt to look up the variable both ways. + try: + if re.search('\$\{\w+\}', argument): + try: + t = Template(argument) + value = t.substitute(variables) + try: + # Attempt an old-style variable lookup with the + # substituted value. + return variables[value] + except KeyError: + return value + except ValueError: + # TODO: CMake supports nesting, e.g. ${${foo}} + return None + else: + return variables[argument] + except KeyError: + return None + + lhs = lookup_variable(argument) + if lhs is None: + # variable resolution failed, treat as string + lhs = argument + + if len(arguments) > 1: + op = arguments[1] + if op == 'AND': + return evaluate_constant(lhs) and evaluate_boolean(variables, arguments[2:]) + elif op == 'MATCHES': + rhs = lookup_variable(arguments[2]) + if not rhs: + rhs = arguments[2] + return not re.match(rhs, lhs) is None + elif op == 'OR': + return evaluate_constant(lhs) or evaluate_boolean(variables, arguments[2:]) + elif op == 'STREQUAL': + rhs = lookup_variable(arguments[2]) + if not rhs: + rhs = arguments[2] + return lhs == rhs + else: + lhs = evaluate_constant(lhs) + if lhs is None: + lhs = lookup_variable(argument) + + return lhs + + +def parse(variables, cache_variables, filename): + parsed = cmake.parseFile(filename) + cont_eval, sources = evaluate(variables, cache_variables, parsed) + return sources |