#!/usr/bin/env python3 import sys import argparse import sexpdata # # Dummy options, these would first have defaults defined # in project.conf and then be overridden in the given # invocation of buildstream before resolving anything. # OPTIONS = { 'foo': 'FOO', 'bar': 'BAR', 'baz': '', 'frob': False, 'number': 6, } # Supported symbols: # # ifeq - resolves to true if both arguments are equal # and - resolves to true if all arguments are truthy # or - resolves to true if any argument is truthy # # Refer to an option by its name, unquoted, compare it # to another option, or treat it as a truthy value, or # compare it to a value, use quotes to express a value. # # Run some things: # # ./testsexp.py -s '(ifeq foo "FOO")' # OUTPUT: True # # ./testsexp.py -s '(ifeq foo "BAR")' # OUTPUT: False # # ./testsexp.py -s '(and frob (ifeq "FOO" foo))' # OUTPUT: False # # ~/testsexp.py -s '(or frob (ifeq "FOO" foo))' # OUTPUT: True # # ~/testsexp.py -s '(or frob baz)' # OUTPUT: False # # ~/testsexp.py -s '(or frob baz foo)' # OUTPUT: True # def get_option(name): try: return OPTIONS[name] except KeyError as e: raise SexpError("Reference to undefined option '{}'".format(name)) from e class SexpError(Exception): pass # Main entry point # def evaluate_list(token_list): token_list = list(token_list) symbol_token = token_list.pop(0) if not isinstance(symbol_token, sexpdata.Symbol): raise SexpError('First element of list must be a symbol') symbol_name = symbol_token.value() try: symbol_type = SYMBOLS[symbol_name] except KeyError as e: raise SexpError('Unrecognized symbol {}'.format(symbol_name)) from e symbol = symbol_type(token_list) return symbol.resolve() # Interface for implementing symbols in the sexp conditions # # Each symbol resolves to a value. class Symbol(): def __init__(self, args=None): self._resolved = None self._args = list(args) def get_value(self): if self._resolved is None: self._resolved = self.resolve() # args() # # A generator for iterating over each resolved # argument of the symbol, to be used in resolve() # def args(self): for arg in self._args: resolved_arg = arg if isinstance(arg, list): resolved_arg = evaluate_list(arg) elif isinstance(arg, sexpdata.Symbol): resolved_arg = get_option(arg.value()) yield resolved_arg # resolve() # # Returns: # (value): An abstract value for this resolved symbol # def resolve(self): pass ################################################################### # Symbol table: functions here define how symbols are implemented ################################################################### # # (ifeq "a" "b") # # Resolves to True or False # class symbol_ifeq(Symbol): def resolve(self): # Enter generator g = self.args() try: value1 = next(g) value2 = next(g) except StopIteration as e: raise SexpError('ifeq: Expected two arguments') from e ended = False try: value3 = next(g) except StopIteration as e: ended = True if not ended: raise SexpError('ifeq: Expected two arguments') from e return value1 == value2 # # (and "a" "b" "c" ...) # # Resolves to True if all arguments are also truthy # class symbol_and(Symbol): def resolve(self): # Use python truthyness is_true = True for arg in self.args(): if not arg: is_true = False break return is_true # # (or "a" "b" "c" ...) # # Resolves to True if any arguments are truthy # class symbol_or(Symbol): def resolve(self): # Use python truthyness is_true = False for arg in self.args(): if arg: is_true = True break return is_true #################################################### # SYMBOL TABLE #################################################### SYMBOLS = { 'ifeq': symbol_ifeq, 'and': symbol_and, 'or': symbol_or, } #################################################### # TEST MAIN #################################################### if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument('-s', '--sexp', required=True, help='S Expression to test') args = parser.parse_args() list_data = sexpdata.loads(args.sexp) try: result = evaluate_list(list_data) except SexpError as e: print("Error: {}".format(e)) sys.exit(-1) print("Result: {}".format(result))