def range_call_semantics(self, stmt: Call, state: State) -> State: result = set() if len(stmt.arguments) == 1: start = Literal(IntegerLyraType(), "0") stops = self.semantics(stmt.arguments[0], state).result step = Literal(IntegerLyraType(), "1") for stop in stops: range = Range(stmt.typ, start, stop, step) result.add(range) state.result = result return state elif len(stmt.arguments) == 2: starts = self.semantics(stmt.arguments[0], state).result stops = self.semantics(stmt.arguments[1], state).result step = Literal(IntegerLyraType(), "1") for start in starts: for stop in stops: range = Range(stmt.typ, start, stop, step) result.add(range) state.result = result return state elif len(stmt.arguments) == 3: starts = self.semantics(stmt.arguments[0], state).result stops = self.semantics(stmt.arguments[1], state).result steps = self.semantics(stmt.arguments[2], state).result for start in starts: for stop in stops: for step in steps: range = Range(stmt.typ, start, stop, step) result.add(range) state.result = result return state error = f"Call to {stmt.name} with unexpected number of arguments!" raise ValueError(error)
def list_call_semantics(self, stmt: Call, state: State) -> State: """Semantics of a call to 'list'. :param stmt: call to 'list' to be executed :param state: state before executing the call statement :return: state modified by the call statement """ def do(variable): if isinstance(variable.typ, StringLyraType): typ = ListLyraType(variable.typ) state.result = {VariableIdentifier(typ, variable.name)} return state elif isinstance(variable.typ, ListLyraType): state.result = {variable} return state elif isinstance(variable.typ, TupleLyraType): raise NotImplementedError(f"Conversion to list of {variable.typ} is not yet implemented!") elif isinstance(variable.typ, SetLyraType): typ = ListLyraType(variable.typ.typ) state.result = {VariableIdentifier(typ, variable.name)} return state elif isinstance(variable.typ, DictLyraType): typ = ListLyraType(variable.typ.key_typ) state.result = {VariableIdentifier(typ, variable.name)} return state raise TypeError(f"Unexpected type {variable.typ} for list conversion!") if not stmt.arguments: state.result = {ListDisplay(stmt.typ, list())} return state assert len(stmt.arguments) == 1 # exactly one argument is expected argument = stmt.arguments[0] if isinstance(argument, VariableAccess): return do(argument.variable) # elif isinstance(argument, SubscriptionAccess): # target = argument.target # if isinstance(target, VariableAccess): # return do(target.variable) elif isinstance(argument, Call): if isinstance(argument.typ, StringLyraType): typ = ListLyraType(argument.typ) call = Call(argument.pp, argument.name, argument.arguments, typ) state.result = self.semantics(call, state).result return state elif isinstance(argument.typ, ListLyraType): state.result = self.semantics(argument, state).result return state elif isinstance(argument.typ, SetLyraType): typ = ListLyraType(argument.typ.typ) call = Call(argument.pp, argument.name, argument.arguments, typ) state.result = self.semantics(call, state).result return state elif isinstance(argument.typ, DictLyraType): typ = ListLyraType(argument.typ.key_typ) call = Call(argument.pp, argument.name, argument.arguments, typ) state.result = self.semantics(call, state).result return state raise NotImplementedError(f"Semantics for {stmt} is not yet implemented!")
def tuple_call_semantics(self, stmt: Call, state: State, interpreter: Interpreter) -> State: """Semantics of a call to 'tuple'. :param stmt: call to 'tuple' to be executed :param state: state before executing the call statement :return: state modified by the call statement """ if not stmt.arguments: state.result = {TupleDisplay(stmt.typ, list())} return state assert len(stmt.arguments) == 1 # exactly one argument is expected argument = self.semantics(stmt.arguments[0], state, interpreter).result result = set() for expression in argument: if isinstance(expression.typ, StringLyraType): if isinstance(expression, Literal): typs = [ deepcopy(expression.typ) for _ in range(len(expression.val)) ] typ = TupleLyraType(typs) result.add(CastOperation(typ, expression)) else: error = f"Cast to tuple of {expression} is not yet implemented!" raise NotImplementedError(error) elif isinstance(expression.typ, (ListLyraType, SetLyraType)): if isinstance(expression, ListDisplay): typs = [ deepcopy(expression.typ.typ) for _ in range(len(expression.items)) ] typ = TupleLyraType(typs) result.add(CastOperation(typ, expression)) else: error = f"Cast to tuple of {expression} is not yet implemented!" raise NotImplementedError(error) elif isinstance(expression.typ, TupleLyraType): result.add(expression) elif isinstance(expression.typ, DictLyraType): if isinstance(expression, DictDisplay): typs = [ deepcopy(expression.typ.key_typ) for _ in range(len(expression.keys)) ] typ = TupleLyraType(typs) result.add(CastOperation(typ, expression)) else: error = f"Cast to tuple of {expression} is not yet implemented!" raise NotImplementedError(error) else: error = f"Cast to list of expression {expression} with unexpected type!" raise ValueError(error) state.result = result return state
def split_call_semantics(self, stmt: Call, state: State) -> State: if len(stmt.arguments) != 1: error = f"Semantics for multiple arguments of {stmt.name} is not yet implemented!" raise NotImplementedError(error) argument = self.semantics(stmt.arguments[0], state).result result = set() for arg in argument: assert isinstance(arg, Expression) if not isinstance(arg.typ, StringLyraType): error = f"Call to {stmt.name} of argument with unexpected type!" raise ValueError(error) typ = ListLyraType(StringLyraType()) if isinstance(arg, Literal): # "a b c".split() -> ["a", "b", "c"] items = [ Literal(StringLyraType(), val) for val in arg.val.split() ] result.add(ListDisplay(typ, items)) continue elif isinstance(arg, VariableIdentifier): # x.split() result.add(VariableIdentifier(typ, arg.name)) continue elif isinstance(arg, Input): # input().split() result.add(Input(typ)) continue error = f"Call to {stmt.name} of unexpected argument!" raise ValueError(error) state.result = result return state
def subscription_access_semantics(self, stmt: SubscriptionAccess, state: State) -> State: """Semantics of a subscription access. :param stmt: subscription access statement to be executed :param state: state before executing the subscription access :return: state modified by the subscription access """ target = self.semantics(stmt.target, state).result key = self.semantics(stmt.key, state).result result = set() for primary, index in itertools.product(target, key): if isinstance(primary.typ, StringLyraType): subscription = Subscription(primary.typ, primary, index) result.add(subscription) elif isinstance(primary.typ, (ListLyraType, SetLyraType)): subscription = Subscription(primary.typ.typ, primary, index) result.add(subscription) elif isinstance(primary.typ, DictLyraType): subscription = Subscription(primary.typ.key_typ, primary, index) result.add(subscription) else: error = f"Semantics for subscription of {primary} is not yet implemented!" raise NotImplementedError(error) state.result = result return state
def strip_call_semantics(self, stmt: Call, state: State, interpreter: Interpreter) -> State: if len(stmt.arguments) != 1: error = f"Semantics for multiple arguments of {stmt.name} is not yet implemented!" raise NotImplementedError(error) argument = self.semantics(stmt.arguments[0], state, interpreter).result result = set() for arg in argument: assert isinstance(arg, Expression) if not isinstance(arg.typ, StringLyraType): error = f"Call to {stmt.name} of argument with unexpected type!" raise ValueError(error) typ = StringLyraType() if isinstance(arg, Input): # input().strip() result.add(Input(typ)) continue elif isinstance(arg, VariableIdentifier): # x.strip() result.add(VariableIdentifier(typ, arg.name)) continue elif isinstance(arg, Subscription): # x[i].strip() result.add(Subscription(typ, arg.target, arg.key)) continue error = f"Call to {stmt.name} of unexpected argument {arg}!" raise ValueError(error) state.result = result return state
def _cast_call_semantics(self, stmt: Call, state: State, typ: LyraType) -> State: """Semantics of a call to 'int' or 'bool'. :param stmt: call to 'int' or 'bool' to be executed :param state: state before executing the call statement :return: state modified by the call statement """ if len(stmt.arguments) != 1: error = f"Semantics for multiple arguments of {stmt.name} is not yet implemented!" raise NotImplementedError(error) argument = self.semantics(stmt.arguments[0], state).result result = set() for expression in argument: if isinstance(expression, Input): result.add(Input(typ)) elif isinstance(expression, Literal): result.add(Literal(typ, expression.val)) elif isinstance(expression, VariableIdentifier): result.add(VariableIdentifier(typ, expression.name)) elif isinstance(expression, Subscription): pass # TODO else: error = f"Argument of type {expression.typ} of {stmt.name} is not yet supported!" raise NotImplementedError(error) state.result = result return state
def _unary_operation(self, stmt: Call, operator: UnaryOperation.Operator, state: State): """Semantics of a call to a unary operation. :param stmt: call to unary operation to be executed :param operator: unary operator :param state: state before executing the call statements :return: state modified by the call statement """ assert len( stmt.arguments) == 1 # unary operations have exactly one argument argument = self.semantics(stmt.arguments[0], state).result result = set() if isinstance(operator, UnaryArithmeticOperation.Operator): for expression in argument: operation = UnaryArithmeticOperation(stmt.typ, operator, expression) result.add(operation) elif isinstance(operator, UnaryBooleanOperation.Operator): for expression in argument: operation = UnaryBooleanOperation(stmt.typ, operator, expression) result.add(operation) else: error = f"Semantics for unary operation {operator} is not yet implemented!" raise NotImplementedError(error) state.result = result return state
def dict_display_access_semantics(self, stmt: DictDisplayAccess, state: State) -> State: """Semantics of a list display access. :param stmt: dictionary display access statement to be executed :param state: state before executing the dictionary display access :return: state modified by the dictionary display access """ k_exprs = [self.semantics(k, state).result for k in stmt.keys] # List[Set[Expression]] v_exprs = [self.semantics(v, state).result for v in stmt.values] result = set() if k_exprs: # not empty # One "Set" of Tuples of possible key-value pairs per actual k-v-pair k_v_tuples = map(itertools.product, k_exprs, v_exprs) k_typ = next(iter(k_exprs[0]) ).typ # Is there a better way to retrieve the types? v_typ = next(iter(v_exprs[0])).typ for combination in itertools.product(*k_v_tuples): unzip = list( zip(*combination )) # to create two separate lists for keys and values display = DictDisplay(DictLyraType(k_typ, v_typ), list(unzip[0]), list(unzip[1])) result.add(display) else: # empty dict of generic type TODO: way to get specific type? result.add(DictDisplay(DictLyraType(LyraType(), LyraType()))) state.result = result return state
def _binary_operation(self, stmt: Call, operator: BinaryOperation.Operator, state: State): """Semantics of a call to a binary operation. :param stmt: call to binary operation to be executed :param operator: binary operator :param state: state before executing the call statements :return: state modified by the call statement """ assert len(stmt.arguments ) == 2 # binary operations have exactly two arguments argument1 = self.semantics(stmt.arguments[0], state).result argument2 = self.semantics(stmt.arguments[1], state).result result = set() if isinstance(operator, BinaryArithmeticOperation.Operator): for left, right in itertools.product(argument1, argument2): operation = BinaryArithmeticOperation(stmt.typ, left, operator, right) result.add(operation) elif isinstance(operator, BinaryComparisonOperation.Operator): for left, right in itertools.product(argument1, argument2): operation = BinaryComparisonOperation(stmt.typ, left, operator, right) result.add(operation) elif isinstance(operator, BinaryBooleanOperation.Operator): for left, right in itertools.product(argument1, argument2): operation = BinaryBooleanOperation(stmt.typ, left, operator, right) result.add(operation) else: error = f"Semantics for binary operator {operator} is not yet implemented!" raise NotImplementedError(error) state.result = result return state
def variable_access_semantics(self, stmt: VariableAccess, state: State) -> State: """Semantics of a variable access. :param stmt: variable access statement to be executed :param state: state before executing the variable access :return: state modified by the variable access """ state.result = {stmt.variable} return state
def literal_evaluation_semantics(self, stmt: LiteralEvaluation, state: State) -> State: """Semantics of a literal evaluation. :param stmt: literal evaluation statement to be executed :param state: state before executing the literal evaluation :return: stated modified by the literal evaluation """ state.result = {stmt.literal} return state
def input_call_semantics(self, stmt: Call, state: State) -> State: """Semantics of a calls to 'input'. :param stmt: call to 'input' to be executed :param state: state before executing the call statement :return: state modified by the call statement """ state.result = {Input(stmt.typ)} return state
def dict_call_semantics(self, stmt: Call, state: State) -> State: """Semantics of a call to 'dict'. :param stmt: call to 'dict' to be executed :param state: state before executing the call statement :return: state modified by the call statement """ if not stmt.arguments: state.result = {SetDisplay(stmt.typ, list())} return state raise NotImplementedError(f"Semantics for {stmt} is not yet implemented!")
def user_defined_call_semantics(self, stmt: Call, state: State, interpreter: Interpreter): """Forward semantics of a user-defined function/method call. :param stmt: call statement to be executed :param state: state before executing the call statement :return: state modified by the call statement """ fname, fcfg = stmt.name, interpreter.cfgs[stmt.name] # add formal function parameters to the state and assign their actual values formal_args = interpreter.fargs[fname] for formal, actual in zip(formal_args, stmt.arguments): rhs = self.semantics(actual, state, interpreter).result state = state.add_variable(formal).forget_variable(formal) state = state.assign({formal}, rhs) if isinstance(actual, Call) and actual.name in interpreter.cfgs: _ret = VariableIdentifier(formal.typ, '{}#return'.format(actual.name)) state = state.remove_variable(_ret) # add local function variables to the state _formal_args = set() for formal in formal_args: _formal_args.add(formal) # if isinstance(formal.typ, (SequenceLyraType, ContainerLyraType)): # _formal_args.add(LengthIdentifier(formal)) # if isinstance(formal.typ, DictLyraType): # _formal_args.add(KeysIdentifier(formal)) # _formal_args.add(ValuesIdentifier(formal)) local_vars = set(fcfg.variables).difference(_formal_args) for local in local_vars: state = state.add_variable(local).forget_variable(local) fresult = interpreter.analyze(fcfg, state) # analyze the function fstate = fresult.get_node_result(fcfg.out_node)[state][-1] state = state.bottom().join(deepcopy(fstate)) # assign return variable if state.result: ret = VariableIdentifier(stmt.typ, '{}#return'.format(fname)) state = state.add_variable(ret).forget_variable(ret) state = state.assign({ret}, state.result) state.result = {ret} # remove local function variables and formal function parameters for local in local_vars: if not local.special: state = state.remove_variable(local) for formal in formal_args: state = state.remove_variable(formal) return state
def set_call_semantics(self, stmt: Call, state: State, interpreter: Interpreter) -> State: """Semantics of a call to 'set'. :param stmt: call to 'set' to be executed :param state: state before executing the call statement :return: state modified by the call statement """ if not stmt.arguments: state.result = {SetDisplay(stmt.typ, list())} return state assert len(stmt.arguments) == 1 # exactly one argument is expected argument = self.semantics(stmt.arguments[0], state, interpreter).result result = set() for expression in argument: if isinstance(expression.typ, StringLyraType): typ = SetLyraType(expression.typ) result.add(CastOperation(typ, expression)) elif isinstance(expression.typ, ListLyraType): typ = SetLyraType(expression.typ.typ) result.add(CastOperation(typ, expression)) elif isinstance(expression.typ, TupleLyraType): if all(typ == expression.typ.typs[0] for typ in expression.typ.typs): typ = SetLyraType(expression.typ.typs[0]) result.add(CastOperation(typ, expression)) else: error = f"Cast to list of {expression} is not yet implemented!" raise NotImplementedError(error) elif isinstance(expression.typ, SetLyraType): result.add(expression) elif isinstance(expression.typ, DictLyraType): typ = SetLyraType(expression.typ.key_typ) result.add(CastOperation(typ, expression)) else: error = f"Cast to list of expression {expression} with unexpected type!" raise ValueError(error) state.result = result return state
def values_call_semantics(self, stmt: Call, state: State) -> State: """Semantics of calls to 'values'. :param stmt: call to 'values' to be executed :param state: state before executing the call statement :return: state modified by the call statement """ if isinstance(stmt.arguments[0], VariableAccess): # target state.result = {Values(stmt.typ, stmt.arguments[0].variable)} else: error = f"Semantics for values() call on non-variable {stmt.arguments[0]} is not yet "\ f"implemented!" raise NotImplementedError(error) return state
def list_display_access_semantics(self, stmt: ListDisplayAccess, state: State) -> State: """Semantics of a list display access. :param stmt: list display access statement to be executed :param state: state before executing the list display access :return: state modified by the list display access """ items = [self.semantics(item, state).result for item in stmt.items] result = set() for combination in itertools.product(*items): display = ListDisplay(stmt.typ, list(combination)) result.add(display) state.result = result return state
def bitxor_call_semantics(self, stmt: Call, state: State, interpreter: Interpreter) -> State: """Semantics of calls to '^' (bitwise xor). :param stmt: call to '^' (bitwise xor) to be executed :param state: state before executing the call statement :return: state modified by the call statement """ argument = self.semantics(stmt.arguments[0], state, interpreter).result result = set() for arg in argument: result.add(VariableIdentifier(stmt.typ, arg.name)) state.result = result return state
def _binary_operation(self, stmt: Call, operator: BinaryOperation.Operator, state: State): """Semantics of a call to a binary operation. :param stmt: call to binary operation to be executed :param operator: binary operator :param state: state before executing the call statements :return: state modified by the call statement """ arguments = list() updated = state for i in range(len(stmt.arguments)): updated = self.semantics(stmt.arguments[i], updated) arguments.append(updated.result) assert len(arguments) >= 2 # binary operations have at least two arguments result = set() if isinstance(operator, BinaryArithmeticOperation.Operator): for product in itertools.product(*arguments): operation = product[0] for i in range(1, len(arguments)): right = product[i] operation = BinaryArithmeticOperation(stmt.typ, operation, operator, right) result.add(operation) elif isinstance(operator, BinarySequenceOperation.Operator): for product in itertools.product(*arguments): operation = product[0] for i in range(1, len(arguments)): right = product[i] operation = BinarySequenceOperation(stmt.typ, operation, operator, right) result.add(operation) elif isinstance(operator, BinaryComparisonOperation.Operator): for product in itertools.product(*arguments): operation = product[0] for i in range(1, len(arguments)): right = product[i] BCO = BinaryComparisonOperation operation = BCO(stmt.typ, operation, operator, right, forloop=stmt.forloop) result.add(operation) elif isinstance(operator, BinaryBooleanOperation.Operator): for product in itertools.product(*arguments): operation = product[0] for i in range(1, len(arguments)): right = product[i] operation = BinaryBooleanOperation(stmt.typ, operation, operator, right) result.add(operation) else: error = f"Semantics for binary operator {operator} is not yet implemented!" raise NotImplementedError(error) state.result = result return state
def len_call_semantics(self, stmt: Call, state: State) -> State: """Semantics of a call to 'len'. :param stmt: call to 'len' to be executed :param state: state before executing the call statement :return: state modified by the call statement """ assert len(stmt.arguments) == 1 # unary operations have exactly one argument argument = stmt.arguments[0] if isinstance(argument, VariableAccess): variable = argument.variable state.result = {LengthIdentifier(variable)} return state error = f"Semantics for length of {argument} is not yet implemented!" raise NotImplementedError(error)
def subscription_access_semantics(self, stmt: SubscriptionAccess, state: State) -> State: """Semantics of a subscription access. :param stmt: subscription access statement to be executed :param state: state before executing the subscription access :return: state modified by the subscription access """ target = self.semantics(stmt.target, state).result key = self.semantics(stmt.key, state).result result = set() for primary, index in itertools.product(target, key): subscription = Subscription(primary.typ, primary, index) result.add(subscription) state.result = result return state
def slicing_access_semantics(self, stmt: SlicingAccess, state: State) -> State: """Semantics of a slicing access. :param stmt: slicing access statement to be executed :param state: state before executing the slicing access :return: state modified by the slicing access """ target = self.semantics(stmt.target, state).result lower = self.semantics(stmt.lower, state).result upper = self.semantics(stmt.upper, state).result if stmt.upper else {None} stride = self.semantics(stmt.stride, state).result if stmt.stride else {None} result = set() for primary, start, stop, step in itertools.product(target, lower, upper, stride): slicing = Slicing(primary.typ, primary, start, stop, step) result.add(slicing) state.result = result return state
def range_call_semantics(self, stmt: Call, state: State) -> State: arguments = [ self.semantics(arg, state).result.pop() for arg in stmt.arguments ] start = Literal(IntegerLyraType(), "0") step = Literal(IntegerLyraType(), "1") if len(arguments) == 1: end = arguments[0] elif len(arguments) in [2, 3]: start = arguments[0] end = arguments[1] if len(arguments) == 3: step = arguments[2] else: error = f"Semantics for range call with {len(arguments)} arguments is not implemented!" raise NotImplementedError(error) state.result = {Range(stmt.typ, start, end, step)} return state
def dict_display_access_semantics(self, stmt: DictDisplayAccess, state: State) -> State: """Semantics of a list display access. :param stmt: dictionary display access statement to be executed :param state: state before executing the dictionary display access :return: state modified by the dictionary display access """ keys = [self.semantics(k, state).result for k in stmt.keys] # List[Set[Expression]] values = [self.semantics(v, state).result for v in stmt.values] result = set() if keys: # not empty for combination in itertools.product(*map(itertools.product, keys, values)): unzip = list(zip(*combination)) # to create two separate lists for keys and values display = DictDisplay(stmt.typ, list(unzip[0]), list(unzip[1])) result.add(display) else: result.add(DictDisplay(stmt.typ, list(), list())) state.result = result return state
def tuple_display_access_semantics(self, stmt: TupleDisplayAccess, state: State) -> State: """Semantics of a tuple display access. :param stmt: tuple display access statement to be executed :param state: state before executing the tuple display access :return: state modified by the tuple display access """ items = [self.semantics(item, state).result for item in stmt.items] result = set() if items: for combination in itertools.product(*items): types = [elem.typ for elem in combination] display = TupleDisplay(TupleLyraType(types), list(combination)) result.add(display) else: error = f"Semantics for empty tuples not yet implemented!" # TODO: Handle empty tuple raise NotImplementedError(error) state.result = result return state
def list_display_access_semantics(self, stmt: ListDisplayAccess, state: State) -> State: """Semantics of a list display access. :param stmt: list display access statement to be executed :param state: state before executing the list display access :return: state modified by the list display access """ items = [self.semantics(item, state).result for item in stmt.items] result = set() if items: # not empty for combination in itertools.product(*items): display = ListDisplay(ListLyraType(combination[0].typ), list(combination)) result.add(display) else: result.add(ListDisplay(ListLyraType( LyraType()))) # empty list of generic type # TODO: way to get specific type? -> add type to display statements? state.result = result return state
def user_defined_call_semantics(self, stmt: Call, state: State, interpreter: Interpreter): """Backward semantics of a user-defined function/method call. :param stmt: call statement to be executed :param state: state before executing the call statement :return: state modified by the call statement """ fname, fcfg, _ = stmt.name, interpreter.cfgs[stmt.name], deepcopy(state) # analyze the function fresult = interpreter.analyze(fcfg, state) fstate = fresult.get_node_result(fcfg.in_node)[state][-1] state = state.bottom().join(deepcopy(fstate)) # substitute function actual to formal parameters for formal, actual in zip(interpreter.fargs[fname], stmt.arguments): if isinstance(actual, Call) and actual.name in interpreter.cfgs: # TODO: right might not be a Call but just contain a Call state.result = {formal} state = self.semantics(actual, state, interpreter) else: rhs = self.semantics(actual, state, interpreter).result state = state.substitute({formal}, rhs) return state
def len_call_semantics(self, stmt: Call, state: State, interpreter: Interpreter) -> State: """Semantics of a call to 'len'. :param stmt: call to 'len' to be executed :param state: state before executing the call statement :return: state modified by the call statement """ assert len( stmt.arguments) == 1 # unary operations have exactly one argument argument = stmt.arguments[0] if isinstance(argument, VariableAccess): variable = argument.variable state.result = {LengthIdentifier(variable)} return state elif isinstance(argument, ListDisplayAccess): items = [ self.semantics(item, state, interpreter).result for item in argument.items ] result = set() for combination in itertools.product(*items): display = ListDisplay(argument.typ, list(combination)) result.add(LengthIdentifier(display)) state.result = result return state elif isinstance(argument, TupleDisplayAccess): items = [ self.semantics(item, state, interpreter).result for item in argument.items ] result = set() for combination in itertools.product(*items): display = TupleDisplay(argument.typ, list(combination)) result.add(LengthIdentifier(display)) state.result = result return state elif isinstance(argument, SetDisplayAccess): items = [ self.semantics(item, state, interpreter).result for item in argument.items ] result = set() for combination in itertools.product(*items): display = SetDisplay(argument.typ, list(combination)) result.add(LengthIdentifier(display)) state.result = result return state elif isinstance(argument, DictDisplayAccess): keys = [ self.semantics(k, state, interpreter).result for k in argument.keys ] values = [ self.semantics(v, state, interpreter).result for v in argument.values ] result = set() if keys: # not empty List[Set[Expression]] for combination in itertools.product( *map(itertools.product, keys, values)): unzip = list(zip(*combination)) display = DictDisplay(argument.typ, list(unzip[0]), list(unzip[1])) result.add(LengthIdentifier(display)) else: result.add( LengthIdentifier(DictDisplay(argument.typ, list(), list()))) state.result = result return state error = f"Semantics for length of {argument} is not yet implemented!" raise NotImplementedError(error)