def analyze(self, input_string): # Parse statement into its component statements, then recursively analyze each one # and finally return a lambda function that evaluates all parameters and executes commands exprs = self.parse(input_string) exprs_processed = [] for expr in exprs: if self.is_number(expr): # Numbers are our most basic primitive; we return a lambda function that replaces the string # with the corresponding number exprs_processed.append(lambda: self.get_number_value(expr)) elif self.is_symbol(expr): # For strings ("symbols"), the returned function simply keeps them as is. Their value (if defined) # will be evaluated later as part of the execution exprs_processed.append(lambda: expr) elif self.is_command(expr): # For commands we first recursively analyze the arguments, then verify correct syntax, # and finally return a lambda function that evaluates all arguments and executes the command # (note that the arguments themselves may be commands, which get executed as part of the evaluation # process as well) cmd = self.get_cmd(expr) args_unevaluated = self.get_args(expr) args = [] for arg in args_unevaluated: args.append(self.analyze(arg)) if cmd == "if": cmd = "if_else" args.append(lambda: 0) # Special case handling for "if" statement, which transforms it into an if_else statement with a # blank else clause. This saves us from having to implement both functions, as well as avoids problems # with the "if" command shadowing python's if cmd == "and": cmd = "i_and" if cmd == "or": cmd = "i_or" # Avoid shadowing with and/or commands CommandsInspector.verify_commands(self.commands, cmd, args) # self.commands.verify_command(cmd, args) exprs_processed.append( (lambda xcmd, xargs: lambda: self.eval_and_exec( xcmd, xargs))(cmd, args)) # Important: This double-lambda construct is here because python evaluates variables on execution # and not on definition. Without this, other processed statements which return a lambda function will # refer to the same cmd and arg, which will only be evaluated in the end. In other words, instead of # analyzing all statements, only the last one will be analyzed (multiple times). To work our way around # this, we construct a lambda that constructs the lambda we actually want, and immediately call it. # Another way around this would be to define default arguments cmd=cmd, args=args since default # arguments are evaluated on definition. else: raise Exception("Syntax error in expression " + str(expr)) return lambda: self.execute_multiple(exprs_processed)
def eval_and_exec_if_else(self, cmd, args): # If statements are a special case since we only want to execute the lambdas of the arguments based on the # predicate. Therefore only the first argument (the predicate) is evaluated at this stage, and the relevant # result is evaluated within the function itself args_eval = args.copy() # Avoid modifying the original arguments args_eval[0] = args_eval[0]() if self.is_symbol(args_eval[0]): args_eval[0] = self.get_symbol_value(args_eval[0]) return CommandsInspector.execute_command(self.commands, cmd, args_eval)
def eval_and_exec_general(self, cmd, args): # First execute all lambda functions for all arguments, reducing them all to either numbers or symbols # Then resolve all symbols by replacing them with their defined values args_eval = args.copy() # Avoid modifying the original arguments for idx, arg in enumerate(args_eval): args_eval[idx] = arg() if self.is_symbol(args_eval[idx]): args_eval[idx] = self.get_symbol_value(args_eval[idx]) return CommandsInspector.execute_command(self.commands, cmd, args_eval)
def eval_and_exec_define(self, cmd, args): # Define is a special case since it expects the first argument to be an undefined symbol, so we must not # attempt to fully resolve it args_eval = args.copy() # Avoid modifying the original arguments for idx, arg in enumerate(args_eval): args_eval[idx] = arg() if self.is_symbol(args_eval[1]): args_eval[1] = self.get_symbol_value(args_eval[1]) return CommandsInspector.execute_command(self.commands, cmd, args_eval)