| """Runtime interpreter for the GAL AST. | |
| After lexer, parser, and semantic validation succeed, server.py calls | |
| Interpreter.interpret(ast). This file executes AST nodes and stores runtime | |
| state such as variables, functions, scopes, loop flags, and output. | |
| """ | |
| # AUTO: Imports names from another module. | |
| from shared.ast_nodes import * | |
| # AUTO: Imports a module used by this file. | |
| import threading | |
| # AUTO: Imports a module used by this file. | |
| import sys | |
| # AUTO: Calls `sys.setrecursionlimit`. | |
| sys.setrecursionlimit(10000) | |
| # AUTO: Starts protected code that can catch errors. | |
| try: | |
| # AUTO: Imports a module used by this file. | |
| import eventlet.event as _ev | |
| # AUTO: Sets `_USE_EVENTLET`. | |
| _USE_EVENTLET = True | |
| # AUTO: Handles the matching error case. | |
| except ImportError: | |
| # AUTO: Sets `_USE_EVENTLET`. | |
| _USE_EVENTLET = False | |
| # AUTO: Imports names from another module. | |
| from semantic.errors import SemanticError # noqa: F401 - some runtime checks raise it | |
| # AUTO: Imports names from another module. | |
| from interpreter.errors import ( # noqa: F401 - runtime-specific error classes | |
| # AUTO: Executes this statement. | |
| ReturnValue, | |
| # AUTO: Executes this statement. | |
| _CancelledError, | |
| # AUTO: Executes this statement. | |
| InterpreterError, | |
| # AUTO: Executes this statement. | |
| InterpreterInputRequest, | |
| # AUTO: Closes the current grouped code/data. | |
| ) | |
| # AUTO: Defines class `Interpreter`. | |
| class Interpreter: | |
| # AUTO: Defines function `__init__`. | |
| def __init__(self, socketio=None): | |
| # GUIDE: Output and Socket.IO are how plant() and water() communicate with UI. | |
| # LINE: Stores output text generated by plant(). | |
| self.output = [] | |
| # LINE: Holds the UI/emitter object used to send output/input events. | |
| self.socketio = socketio | |
| # GUIDE: Loop state is used by prune/skip and infinite-loop protection. | |
| # LINE: Tracks active loops so prune/skip know if they are allowed. | |
| self.loop_stack = [] | |
| # LINE: Becomes True when prune should stop a loop. | |
| self.break_flag = False | |
| # LINE: Becomes True when skip should jump to the next loop iteration. | |
| self.continue_flag = False | |
| # GUIDE: Input state lets water() pause until the UI sends a value. | |
| # LINE: True while water() is waiting for user input. | |
| self.input_required = False | |
| # LINE: Stores wait objects for interactive water() calls. | |
| self.input_events = {} | |
| # LINE: Stores input values received from the UI. | |
| self.input_values = {} | |
| # LINE: Optional current AST node pointer for runtime context. | |
| self.current_node = None | |
| # LINE: Optional parent AST node pointer for runtime context. | |
| self.current_parent = None | |
| # GUIDE: Runtime symbol storage. scopes[-1] is current active scope. | |
| # LINE: Older/global variable map kept for compatibility. | |
| self.variables = {} | |
| # LINE: Stores variables declared globally. | |
| self.global_variables = {} | |
| # LINE: Stores declared functions by name. | |
| self.functions = {} | |
| # LINE: Scope stack; the last dictionary is the current active scope. | |
| self.scopes = [{}] | |
| # LINE: Name of the function currently executing. | |
| self.current_func_name = None | |
| # LINE: Per-function variable tracking storage. | |
| self.function_variables = {} | |
| # LINE: Stores bundle/struct type definitions. | |
| self.bundle_types = {} | |
| # AUTO: Defines function `declare_variable`. | |
| def declare_variable(self, name, type_, value=None, is_list=False, is_fertile=False): | |
| # LINE: Use the current top scope for this declaration. | |
| scope = self.scopes[-1] | |
| # LINE: Remember current function name if needed for future tracking. | |
| current_func = self.current_func_name | |
| # LINE: If name is not in current scope, create a new runtime variable entry. | |
| if name not in self.scopes[-1]: | |
| # AUTO: Sets `scope[name]`. | |
| scope[name] = { | |
| # LINE: Store GAL type such as seed/tree/vine. | |
| "type": type_, | |
| # LINE: Store the current runtime value. | |
| "value": value, | |
| # LINE: Mark whether this variable is an array/list. | |
| "is_list": is_list, | |
| # LINE: Mark whether this variable is fertile/constant. | |
| "is_fertile": is_fertile | |
| # AUTO: Closes the current grouped code/data. | |
| } | |
| # AUTO: Runs when previous condition did not pass. | |
| else: | |
| # LINE: Duplicate global declaration is a semantic error. | |
| if name in self.global_variables: | |
| # AUTO: Returns this result to the caller. | |
| return f"Semantic Error: Variable '{name}' already declared." | |
| # LINE: Compatibility path for older global storage. | |
| self.variables[name] = { | |
| # AUTO: Executes this statement. | |
| "type": type_, | |
| # AUTO: Executes this statement. | |
| "value": value, | |
| # AUTO: Executes this statement. | |
| "is_list": is_list, | |
| # AUTO: Executes this statement. | |
| "is_fertile": is_fertile | |
| # AUTO: Closes the current grouped code/data. | |
| } | |
| # AUTO: Sets `self.global_variables[name]`. | |
| self.global_variables[name] = self.variables[name] | |
| # AUTO: Defines function `lookup_variable`. | |
| def lookup_variable(self, name): | |
| # LINE: Search from inner scope to outer scope so locals override globals. | |
| for i, scope in enumerate(reversed(self.scopes)): | |
| # AUTO: Checks this condition. | |
| if name in scope: | |
| # LINE: Return the variable info dictionary once found. | |
| return scope[name] | |
| # LINE: Fallback to older variable map if not found in scopes. | |
| if name in self.variables: | |
| # AUTO: Returns this result to the caller. | |
| return self.variables[name] | |
| # LINE: Returning a string means caller should raise an error. | |
| return f"Semantic Error: Variable '{name}' used before declaration." | |
| # AUTO: Defines function `set_variable`. | |
| def set_variable(self, name, value): | |
| # LINE: Search all scopes from inner to outer for assignment target. | |
| for i in reversed(range(len(self.scopes))): | |
| # AUTO: Sets `scope`. | |
| scope = self.scopes[i] | |
| # AUTO: Checks this condition. | |
| if name in scope: | |
| # LINE: Update only the stored value, not type/list/fertile metadata. | |
| scope[name]["value"] = value | |
| # AUTO: Returns this result to the caller. | |
| return | |
| # LINE: Assignment target was never declared. | |
| return f"Semantic Error: Variable '{name}' not declared in any scope." | |
| # AUTO: Defines function `declare_function`. | |
| def declare_function(self, name, return_type, params, node=None): | |
| # LINE: Function names must be unique. | |
| if name in self.functions: | |
| # AUTO: Returns this result to the caller. | |
| return f"Semantic Error: Function '{name}' already declared." | |
| # LINE: Save function metadata and body node for later calls. | |
| self.functions[name] = {"return_type": return_type, "params": params, "node": node} | |
| # AUTO: Defines function `lookup_function`. | |
| def lookup_function(self, name): | |
| # LINE: Return saved function metadata if it exists. | |
| if name in self.functions: | |
| # AUTO: Returns this result to the caller. | |
| return self.functions[name] | |
| # LINE: Return error string if function was never declared. | |
| return f"Semantic Error: Function '{name}' is not defined." | |
| # AUTO: Defines function `enter_scope`. | |
| def enter_scope(self): | |
| # LINE: Push a new local variable dictionary. | |
| self.scopes.append({}) | |
| # AUTO: Defines function `exit_scope`. | |
| def exit_scope(self): | |
| # LINE: Never remove the global scope. | |
| if len(self.scopes) > 1: | |
| # LINE: Pop local variables when leaving a block/function. | |
| self.scopes.pop() | |
| # AUTO: Checks this condition. | |
| if self.current_func_name: | |
| # AUTO: Sets `current_func`. | |
| current_func = self.current_func_name | |
| # AUTO: Checks this condition. | |
| if current_func in self.function_variables: | |
| # AUTO: Calls `self.function_variables[current_func].clear`. | |
| self.function_variables[current_func].clear() | |
| # AUTO: Defines function `interpret`. | |
| def interpret(self, node): | |
| # GUIDE: Central runtime dispatcher; each AST node class is sent to its | |
| # matching eval_* method. This is where execution branches by node type. | |
| # LINE: ProgramNode means start the whole program execution. | |
| if isinstance(node, ProgramNode): | |
| # AUTO: Returns this result to the caller. | |
| return self.eval_program(node) | |
| # LINE: BundleDefinitionNode registers a bundle/struct type. | |
| elif isinstance(node, BundleDefinitionNode): | |
| # AUTO: Returns this result to the caller. | |
| return self.eval_bundle_definition(node) | |
| # LINE: MemberAccessNode reads obj.member. | |
| elif isinstance(node, MemberAccessNode): | |
| # AUTO: Returns this result to the caller. | |
| return self.eval_member_access(node) | |
| # LINE: ArrayMemberAccessNode reads arr[i].member. | |
| elif isinstance(node, ArrayMemberAccessNode): | |
| # AUTO: Returns this result to the caller. | |
| return self.eval_array_member_access(node) | |
| # LINE: VariableDeclarationNode creates a variable at runtime. | |
| elif isinstance(node, VariableDeclarationNode): | |
| # AUTO: Returns this result to the caller. | |
| return self.eval_variable_declaration(node) | |
| # LINE: AssignmentNode updates a variable/list/member value. | |
| elif isinstance(node, AssignmentNode): | |
| # AUTO: Returns this result to the caller. | |
| return self.eval_assignment(node) | |
| # LINE: BinaryOpNode evaluates operators like +, -, *, /, ==, &&. | |
| elif isinstance(node, BinaryOpNode): | |
| # AUTO: Sets `value`. | |
| value = self.eval_binary_op(node) | |
| # LINE: Guard against numbers larger than GAL's numeric limit. | |
| if isinstance(value, (int, float)): | |
| # AUTO: Checks this condition. | |
| if value > 1000000000000000 or value < -9999999999999999: | |
| # AUTO: Stops this flow by raising an error. | |
| raise InterpreterError(f"Runtime Error: Evaluated number exceeds maximum number of 16 digits", node.line) | |
| # AUTO: Returns this result to the caller. | |
| return value | |
| # LINE: FunctionDeclarationNode is saved, not executed immediately. | |
| elif isinstance(node, FunctionDeclarationNode): | |
| # AUTO: Returns this result to the caller. | |
| return self.eval_function_declaration(node) | |
| # LINE: PrintNode executes plant(). | |
| elif isinstance(node, PrintNode): | |
| # AUTO: Returns this result to the caller. | |
| return self.eval_print(node) | |
| # LINE: ListNode builds an array/list value. | |
| elif isinstance(node, ListNode): | |
| # AUTO: Returns this result to the caller. | |
| return self.eval_list(node) | |
| # LINE: ListAccessNode reads arr[index]. | |
| elif isinstance(node, ListAccessNode): | |
| # AUTO: Returns this result to the caller. | |
| return self.eval_list_access(node) | |
| # LINE: ReturnNode executes reclaim. | |
| elif isinstance(node, ReturnNode): | |
| # AUTO: Returns this result to the caller. | |
| return self.eval_return(node) | |
| # LINE: FunctionCallNode executes root(), gcd(), or another function call. | |
| elif isinstance(node, FunctionCallNode): | |
| # AUTO: Returns this result to the caller. | |
| return self.eval_function_call(node) | |
| # AUTO: Checks the next alternate condition. | |
| elif isinstance(node, AppendNode): | |
| # AUTO: Returns this result to the caller. | |
| return self.eval_append(node) | |
| # AUTO: Checks the next alternate condition. | |
| elif isinstance(node, InsertNode): | |
| # AUTO: Returns this result to the caller. | |
| return self.eval_insert(node) | |
| # AUTO: Checks the next alternate condition. | |
| elif isinstance(node, RemoveNode): | |
| # AUTO: Returns this result to the caller. | |
| return self.eval_remove(node) | |
| # AUTO: Checks the next alternate condition. | |
| elif isinstance(node, UnaryOpNode): | |
| # AUTO: Returns this result to the caller. | |
| return self.eval_unaryop(node) | |
| # AUTO: Checks the next alternate condition. | |
| elif isinstance(node, FertileDeclarationNode): | |
| # AUTO: Returns this result to the caller. | |
| return self.eval_sturdy_declaration(node) | |
| # AUTO: Checks the next alternate condition. | |
| elif isinstance(node, CastNode): | |
| # AUTO: Returns this result to the caller. | |
| return self.eval_cast(node) | |
| # AUTO: Checks the next alternate condition. | |
| elif isinstance(node, SoilNode): | |
| # AUTO: Returns this result to the caller. | |
| return self.eval_soil(node) | |
| # AUTO: Checks the next alternate condition. | |
| elif isinstance(node, BloomNode): | |
| # AUTO: Returns this result to the caller. | |
| return self.eval_bloom(node) | |
| # AUTO: Checks the next alternate condition. | |
| elif isinstance(node, IfStatementNode): | |
| # AUTO: Returns this result to the caller. | |
| return self.eval_if_statement(node) | |
| # AUTO: Checks the next alternate condition. | |
| elif isinstance(node, ForLoopNode): | |
| # AUTO: Returns this result to the caller. | |
| return self.eval_for_loop(node) | |
| # AUTO: Checks the next alternate condition. | |
| elif isinstance(node, WhileLoopNode): | |
| # AUTO: Returns this result to the caller. | |
| return self.eval_while_loop(node) | |
| # AUTO: Checks the next alternate condition. | |
| elif isinstance(node, DoWhileLoopNode): | |
| # AUTO: Returns this result to the caller. | |
| return self.eval_do_while_loop(node) | |
| # AUTO: Checks the next alternate condition. | |
| elif isinstance(node, BreakNode): | |
| # AUTO: Returns this result to the caller. | |
| return self.eval_break(node) | |
| # AUTO: Checks the next alternate condition. | |
| elif isinstance(node, ContinueNode): | |
| # AUTO: Returns this result to the caller. | |
| return self.eval_continue(node) | |
| # AUTO: Checks the next alternate condition. | |
| elif isinstance(node, SwitchNode): | |
| # AUTO: Returns this result to the caller. | |
| return self.eval_switch(node) | |
| # AUTO: Checks the next alternate condition. | |
| elif node.node_type == "Input": | |
| # LINE: Input node executes water(). | |
| return self.eval_input(node) | |
| # AUTO: Checks the next alternate condition. | |
| elif node.node_type == "Value": | |
| # LINE: Value node converts a literal token into a Python value. | |
| value = self._parse_literal(node.value) | |
| # AUTO: Returns this result to the caller. | |
| return value | |
| # AUTO: Checks the next alternate condition. | |
| elif node.node_type == "Identifier": | |
| # LINE: Identifier reads the stored value of a variable. | |
| var_info = self.lookup_variable(node.value) | |
| # AUTO: Checks this condition. | |
| if isinstance(var_info, str): | |
| # AUTO: Stops this flow by raising an error. | |
| raise InterpreterError(var_info, node.line) | |
| # AUTO: Returns this result to the caller. | |
| return var_info["value"] | |
| # AUTO: Checks the next alternate condition. | |
| elif node.node_type == "FormattedString": | |
| # LINE: FormattedString removes quotes and decodes escapes. | |
| return self.eval_formatted_string(node) | |
| # AUTO: Checks the next alternate condition. | |
| elif node.node_type == "VariableDeclarationList": | |
| # LINE: Declare each variable inside a grouped declaration list. | |
| for child in node.children: | |
| # AUTO: Calls `self.eval_variable_declaration`. | |
| self.eval_variable_declaration(child) | |
| # AUTO: Checks the next alternate condition. | |
| elif node.node_type == "AssignmentList": | |
| # LINE: Execute each assignment/update inside a grouped assignment list. | |
| for child in node.children: | |
| # AUTO: Checks this condition. | |
| if isinstance(child, AssignmentNode): | |
| # AUTO: Calls `self.eval_assignment`. | |
| self.eval_assignment(child) | |
| # AUTO: Checks the next alternate condition. | |
| elif isinstance(child, UnaryOpNode): | |
| # AUTO: Calls `self.eval_unaryop`. | |
| self.eval_unaryop(child) | |
| # AUTO: Checks the next alternate condition. | |
| elif node.node_type == "List": | |
| # LINE: Evaluate every list child and return a Python list. | |
| return [self.interpret(child) for child in node.children] | |
| # AUTO: Checks the next alternate condition. | |
| elif node.node_type == "Block": | |
| # LINE: Execute a block of statements in order. | |
| self.eval_block(node) | |
| # AUTO: Runs when previous condition did not pass. | |
| else: | |
| # LINE: Unknown AST node means builder/interpreter are out of sync. | |
| raise Exception(f"Unknown AST node type: {node.node_type}") | |
| # AUTO: Defines function `eval_program`. | |
| def eval_program(self, node): | |
| # GUIDE: First register top-level declarations/functions, then call root(). | |
| # LINE: Visit each top-level child under ProgramNode. | |
| for child in node.children: | |
| # Top-level children are usually FunctionDeclarationNode, bundle | |
| # definitions, and global declarations. Function declarations are | |
| # stored, not executed yet. | |
| # LINE: This saves function declarations or executes global declarations. | |
| self.interpret(child) | |
| # After registration, create a fake function call node for root(). | |
| # This is how the interpreter starts the user's main program. | |
| # LINE: Create a runtime call equivalent to root(). | |
| main_call = FunctionCallNode("root", [], node.line) | |
| # LINE: Dispatch that root() call through interpret() like any other call. | |
| return self.interpret(main_call) | |
| # AUTO: Defines function `eval_variable_declaration`. | |
| def eval_variable_declaration(self, node): | |
| # GUIDE: Creates a runtime variable entry, using either an initializer value | |
| # or a default value based on the GAL data type. | |
| # LINE: First child stores the declared type, like seed/tree/vine. | |
| var_type = node.children[0].value | |
| # LINE: Second child stores the variable name. | |
| var_name = node.children[1].value | |
| # LINE: Third child is optional initializer, like = 10 or = water(seed). | |
| value_node = node.children[2] if len(node.children) > 2 else None | |
| # LINE: Starts false and becomes true for array/list initializers. | |
| is_list = False | |
| # LINE: Default runtime values when there is no initializer. | |
| default_values = { | |
| # If a variable has no initializer, these are the runtime defaults. | |
| # AUTO: Executes this statement. | |
| "seed": 0, | |
| # AUTO: Executes this statement. | |
| "tree": 0.0, | |
| # AUTO: Executes this statement. | |
| "leaf": '', | |
| # AUTO: Executes this statement. | |
| "vine": "", | |
| # AUTO: Executes this statement. | |
| "branch": False, | |
| # AUTO: Closes the current grouped code/data. | |
| } | |
| # LINE: If initializer exists, evaluate it before declaring the variable. | |
| if value_node: | |
| # There is an initializer, so evaluate the initializer AST node now. | |
| # LINE: List initializer means array/list value. | |
| if value_node.node_type == "List": | |
| # Array/list initializer: evaluate each element and store a | |
| # Python list as the runtime value. | |
| # AUTO: Checks this condition. | |
| if var_type in self.bundle_types: | |
| # AUTO: Sets `value`. | |
| value = [self._build_bundle_defaults(var_type) for _ in value_node.children] | |
| # AUTO: Runs when previous condition did not pass. | |
| else: | |
| # AUTO: Defines function `materialize`. | |
| def materialize(list_node): | |
| # LINE: Convert nested ListNode AST into Python list. | |
| result = [] | |
| # AUTO: Starts a loop over these values. | |
| for child in list_node.children: | |
| # LINE: Recursively handle nested array values. | |
| if isinstance(child, ListNode): | |
| # AUTO: Appends a value to a list. | |
| result.append(materialize(child)) | |
| # AUTO: Runs when previous condition did not pass. | |
| else: | |
| # LINE: Evaluate normal element expression/literal. | |
| item = self.interpret(child) | |
| # AUTO: Checks this condition. | |
| if var_type == "seed" and isinstance(item, float): | |
| # AUTO: Sets `item`. | |
| item = int(item) | |
| # AUTO: Checks the next alternate condition. | |
| elif var_type == "tree": | |
| # AUTO: Sets `item`. | |
| item = float(item) | |
| # AUTO: Appends a value to a list. | |
| result.append(item) | |
| # AUTO: Returns this result to the caller. | |
| return result | |
| # LINE: Store the materialized Python list as the variable value. | |
| value = materialize(value_node) | |
| # LINE: Mark this declaration as a list/array. | |
| is_list = True | |
| # AUTO: Runs when previous condition did not pass. | |
| else: | |
| # Normal initializer such as seed x = 10 or vine s = "hi". | |
| # LINE: Evaluate the initializer expression. | |
| value = self.interpret(value_node) | |
| # LINE: Convert tree-like float into seed integer if needed. | |
| if var_type == "seed" and isinstance(value, float): | |
| # AUTO: Sets `value`. | |
| value = int(value) | |
| # LINE: seed/tree only accept numeric values. | |
| if var_type in {"tree", "seed"}: | |
| # AUTO: Checks this condition. | |
| if not isinstance(value, (int, float)): | |
| # AUTO: Stops this flow by raising an error. | |
| raise InterpreterError(f"Semantic Error: Type Mismatch! Invalid value for {var_name}", node.line) | |
| # LINE: Prevent branch/boolean from being treated as a number. | |
| if isinstance(value, bool): | |
| # AUTO: Stops this flow by raising an error. | |
| raise InterpreterError(f"Semantic Error: Type Mismatch! Invalid value for {var_name}", node.line) | |
| # LINE: tree stores integer initializer as float. | |
| if var_type == "tree" and isinstance(value, int): | |
| # AUTO: Sets `value`. | |
| value = float(value) | |
| # LINE: leaf must receive a string-like character value. | |
| if var_type == "leaf": | |
| # AUTO: Checks this condition. | |
| if not isinstance(value, str): | |
| # AUTO: Stops this flow by raising an error. | |
| raise InterpreterError(f"Semantic Error: Type Mismatch! Invalid value for {var_name}", node.line) | |
| # LINE: vine must receive a string value. | |
| if var_type == "vine": | |
| # AUTO: Checks this condition. | |
| if not isinstance(value, str): | |
| # AUTO: Stops this flow by raising an error. | |
| raise InterpreterError(f"Semantic Error: Type Mismatch! Invalid value for {var_name}", node.line) | |
| # LINE: branch can convert 0/1 numeric-style values into bool. | |
| if var_type == "branch": | |
| # AUTO: Checks this condition. | |
| if isinstance(value, int) or isinstance(value, float): | |
| # AUTO: Checks this condition. | |
| if value == 0: | |
| # AUTO: Sets `value`. | |
| value = False | |
| # AUTO: Runs when previous condition did not pass. | |
| else: | |
| # AUTO: Sets `value`. | |
| value = True | |
| # AUTO: Runs when previous condition did not pass. | |
| else: | |
| # No initializer, so use a default value based on type. | |
| # LINE: Bundle variables get default member dictionaries. | |
| if var_type in self.bundle_types: | |
| # AUTO: Sets `value`. | |
| value = self._build_bundle_defaults(var_type) | |
| # AUTO: Runs when previous condition did not pass. | |
| else: | |
| # LINE: Built-in types get their default value from default_values. | |
| value = default_values.get(var_type, None) | |
| # LINE: Save the variable into the current runtime scope. | |
| self.declare_variable(var_name, var_type, value, is_list=is_list) | |
| # AUTO: Defines function `eval_bundle_definition`. | |
| def eval_bundle_definition(self, node): | |
| # LINE: Save bundle type name with its member definitions. | |
| self.bundle_types[node.bundle_name] = node.members | |
| # AUTO: Defines function `_build_bundle_defaults`. | |
| def _build_bundle_defaults(self, bundle_type_name): | |
| # LINE: Default values for each built-in GAL member type. | |
| _member_defaults = {"seed": 0, "tree": 0.0, "leaf": '', "vine": "", "branch": False} | |
| # LINE: Get member list for this bundle type. | |
| members = self.bundle_types[bundle_type_name] | |
| # LINE: Build a dictionary value for the bundle instance. | |
| result = {} | |
| # LINE: Visit every member name/type in the bundle. | |
| for name, typ in members.items(): | |
| # LINE: Nested bundle member gets its own default dictionary. | |
| if typ in self.bundle_types: | |
| # AUTO: Sets `result[name]`. | |
| result[name] = self._build_bundle_defaults(typ) | |
| # AUTO: Runs when previous condition did not pass. | |
| else: | |
| # LINE: Built-in member gets a simple default value. | |
| result[name] = _member_defaults.get(typ, None) | |
| # LINE: Return default bundle value. | |
| return result | |
| # AUTO: Defines function `eval_member_access`. | |
| def eval_member_access(self, node): | |
| # LINE: First child is object/previous access; second child is member name. | |
| obj_child = node.children[0] | |
| # LINE: Store member name being read. | |
| member_name = node.children[1].value | |
| # LINE: Nested member access, like a.b.c. | |
| if obj_child.node_type == "MemberAccess": | |
| # AUTO: Sets `bundle_value`. | |
| bundle_value = self.eval_member_access(obj_child) | |
| # LINE: Array member access, like students[i].age. | |
| elif obj_child.node_type == "ArrayMemberAccess": | |
| # AUTO: Sets `bundle_value`. | |
| bundle_value = self.eval_array_member_access(obj_child) | |
| # AUTO: Runs when previous condition did not pass. | |
| else: | |
| # LINE: Simple object name, like student.age. | |
| obj_name = obj_child.value | |
| # LINE: Look up the object variable. | |
| var_info = self.lookup_variable(obj_name) | |
| # AUTO: Checks this condition. | |
| if isinstance(var_info, str): | |
| # AUTO: Stops this flow by raising an error. | |
| raise InterpreterError(var_info, node.line) | |
| # LINE: Get the bundle dictionary value. | |
| bundle_value = var_info["value"] | |
| # LINE: Member access only works on bundle dictionaries. | |
| if not isinstance(bundle_value, dict): | |
| # AUTO: Stops this flow by raising an error. | |
| raise InterpreterError(f"Runtime Error: Value is not a bundle.", node.line) | |
| # LINE: Requested member must exist in the bundle. | |
| if member_name not in bundle_value: | |
| # AUTO: Stops this flow by raising an error. | |
| raise InterpreterError(f"Runtime Error: Bundle has no member '{member_name}'.", node.line) | |
| # LINE: Return the member's stored value. | |
| return bundle_value[member_name] | |
| # AUTO: Defines function `eval_array_member_access`. | |
| def eval_array_member_access(self, node): | |
| # LINE: First child is array access part, like students[i]. | |
| list_access_node = node.children[0] | |
| # LINE: Second child is member name, like age. | |
| member_name = node.children[1].value | |
| # LINE: Evaluate students[i] first. | |
| bundle_element = self.eval_list_access(list_access_node) | |
| # LINE: Array element must be a bundle dictionary. | |
| if not isinstance(bundle_element, dict): | |
| # AUTO: Stops this flow by raising an error. | |
| raise InterpreterError(f"Runtime Error: Array element is not a bundle.", node.line) | |
| # LINE: Requested bundle member must exist. | |
| if member_name not in bundle_element: | |
| # AUTO: Stops this flow by raising an error. | |
| raise InterpreterError(f"Runtime Error: Bundle has no member '{member_name}'.", node.line) | |
| # LINE: Return students[i].member value. | |
| return bundle_element[member_name] | |
| # AUTO: Defines function `eval_sturdy_declaration`. | |
| def eval_sturdy_declaration(self, node): | |
| # LINE: First child is fertile variable type. | |
| var_type = node.children[0].value | |
| # LINE: Second child is fertile variable name. | |
| var_name = node.children[1].value | |
| # LINE: Third child is required initializer. | |
| value_node = node.children[2] | |
| # LINE: Evaluate initializer. | |
| value = self.interpret(value_node) | |
| # LINE: Declare variable with is_fertile=True so reassignment is blocked. | |
| self.declare_variable(var_name, var_type, value, is_list=False, is_fertile=True) | |
| # AUTO: Defines function `eval_assignment`. | |
| def eval_assignment(self, node): | |
| # GUIDE: Assignments evaluate RHS first, then write into a variable, | |
| # array element, or bundle member target. | |
| # LINE: Left child is the assignment target. | |
| target_node = node.children[0] | |
| # LINE: Right child is the value/expression being assigned. | |
| value_node = node.children[1] | |
| # LINE: RHS list means assign an array/list value. | |
| if value_node.node_type == "List": | |
| # RHS is an array/list value. | |
| # AUTO: Sets `value`. | |
| value = [] | |
| # AUTO: Starts a loop over these values. | |
| for val in value_node.children: | |
| # LINE: Evaluate each list item before storing. | |
| item = self.interpret(val) | |
| # AUTO: Appends a value to a list. | |
| value.append(item) | |
| # AUTO: Runs when previous condition did not pass. | |
| else: | |
| # RHS is an expression, literal, function call, water(), etc. | |
| # LINE: Evaluate the right side first, such as a + b or water(seed). | |
| value = self.interpret(value_node) | |
| # AUTO: Checks this condition. | |
| if isinstance(value_node, AppendNode) or isinstance(value_node, InsertNode) or isinstance(value_node, RemoveNode): | |
| # LINE: append/insert/remove already changed the list themselves. | |
| return | |
| # LINE: If target is arr[index], write into a list element. | |
| if target_node.node_type == "ListAccess": | |
| # Assignment into an array/list element, e.g. arr[i] = value. | |
| # LINE: Collect indexes for arr[i] or nested arr[i][j]. | |
| indices = [] | |
| # LINE: Start from the ListAccess target node. | |
| current = target_node | |
| # LINE: Walk nested ListAccess nodes from outside to inside. | |
| while hasattr(current, 'node_type') and current.node_type == "ListAccess": | |
| # LINE: Evaluate the index expression inside brackets. | |
| idx = self.interpret(current.children[1].children[0]) | |
| # LINE: Index must be an integer. | |
| if not isinstance(idx, int): | |
| # AUTO: Stops this flow by raising an error. | |
| raise InterpreterError(f"Runtime Error: List index must be an integer. Got '{idx}'", node.line) | |
| # LINE: Store the index for later navigation. | |
| indices.append(idx) | |
| # LINE: Move toward the base list name. | |
| current = current.children[0].value | |
| # LINE: current now holds the base list variable name. | |
| list_name = current | |
| # LINE: Look up the list variable. | |
| list_entry = self.lookup_variable(list_name) | |
| # AUTO: Checks this condition. | |
| if isinstance(list_entry, str): | |
| # AUTO: Stops this flow by raising an error. | |
| raise InterpreterError(list_entry, node.line) | |
| # LINE: Get the actual list/string value. | |
| list_value = list_entry["value"] | |
| # LINE: Target must be a list or string. | |
| if not isinstance(list_value, (list, str)): | |
| # AUTO: Stops this flow by raising an error. | |
| raise InterpreterError(f"Runtime Error: Variable '{list_name}' is not a list.", node.line) | |
| # LINE: String index assignment path. | |
| if isinstance(list_value, str): | |
| # LINE: Strings only support one-dimensional indexing. | |
| if len(indices) != 1: | |
| # AUTO: Stops this flow by raising an error. | |
| raise InterpreterError(f"Runtime Error: Multi-dimensional indexing not supported for strings.", node.line) | |
| # LINE: Final string index. | |
| final_idx = indices[0] | |
| # LINE: Check string index bounds. | |
| if final_idx < 0 or final_idx >= len(list_value): | |
| # AUTO: Stops this flow by raising an error. | |
| raise InterpreterError(f"Runtime Error: Index '{final_idx}' out of bounds for '{list_name}'.", node.line) | |
| # LINE: String index assignment must receive one character. | |
| if not isinstance(value, str) or len(value) != 1: | |
| # AUTO: Stops this flow by raising an error. | |
| raise InterpreterError(f"Runtime Error: Can only assign a single character to a string index.", node.line) | |
| # LINE: Create updated string with one character replaced. | |
| list_value = list_value[:final_idx] + value + list_value[final_idx + 1:] | |
| # LINE: Save updated string back into variable entry. | |
| list_entry["value"] = list_value | |
| # AUTO: Runs when previous condition did not pass. | |
| else: | |
| # LINE: Reverse indexes so traversal starts from base list. | |
| indices.reverse() | |
| # LINE: Start navigating from the base list value. | |
| target = list_value | |
| # LINE: Walk all indexes except the final assignment index. | |
| for i, idx in enumerate(indices[:-1]): | |
| # LINE: Check each intermediate index is within bounds. | |
| if idx < 0 or idx >= len(target): | |
| # AUTO: Stops this flow by raising an error. | |
| raise InterpreterError(f"Runtime Error: Index '{idx}' out of bounds for list '{list_name}'.", node.line) | |
| # LINE: Move into the nested list. | |
| target = target[idx] | |
| # LINE: Intermediate target must still be a list. | |
| if not isinstance(target, list): | |
| # AUTO: Stops this flow by raising an error. | |
| raise InterpreterError(f"Runtime Error: Cannot index into a non-list value.", node.line) | |
| # LINE: Last index is where assignment happens. | |
| final_idx = indices[-1] | |
| # LINE: Check final index bounds. | |
| if final_idx < 0 or final_idx >= len(target): | |
| # AUTO: Stops this flow by raising an error. | |
| raise InterpreterError(f"Runtime Error: Index '{final_idx}' out of bounds for list '{list_name}'.", node.line) | |
| # LINE: Store RHS value into final list element. | |
| target[final_idx] = value | |
| # AUTO: Checks the next alternate condition. | |
| elif target_node.node_type == "MemberAccess": | |
| # Assignment into a bundle member, e.g. student.age = 20. | |
| # LINE: chain stores member path names, like ["info", "age"]. | |
| chain = [] | |
| # LINE: Start from full member access target. | |
| current = target_node | |
| # LINE: Walk member access nodes until reaching base object. | |
| while hasattr(current, 'node_type') and current.node_type == "MemberAccess": | |
| # LINE: Save current member name. | |
| chain.append(current.children[1].value) | |
| # LINE: Move left toward the base object. | |
| current = current.children[0] | |
| # LINE: Reverse to access members from base object outward. | |
| chain.reverse() | |
| # LINE: Base object might be array member access. | |
| if hasattr(current, 'node_type') and current.node_type == "ArrayMemberAccess": | |
| # AUTO: Sets `bundle_value`. | |
| bundle_value = self.interpret(current) | |
| # AUTO: Checks this condition. | |
| if not isinstance(bundle_value, dict): | |
| # AUTO: Stops this flow by raising an error. | |
| raise InterpreterError(f"Runtime Error: Value is not a bundle.", node.line) | |
| # AUTO: Runs when previous condition did not pass. | |
| else: | |
| # LINE: Base object is a normal variable. | |
| obj_name = current.value | |
| # LINE: Look up bundle variable. | |
| var_info = self.lookup_variable(obj_name) | |
| # AUTO: Checks this condition. | |
| if isinstance(var_info, str): | |
| # AUTO: Stops this flow by raising an error. | |
| raise InterpreterError(var_info, node.line) | |
| # LINE: Get bundle dictionary from variable. | |
| bundle_value = var_info["value"] | |
| # AUTO: Checks this condition. | |
| if not isinstance(bundle_value, dict): | |
| # AUTO: Stops this flow by raising an error. | |
| raise InterpreterError(f"Runtime Error: Variable '{obj_name}' is not a bundle.", node.line) | |
| # LINE: Navigate through nested bundle members before the final member. | |
| for member in chain[:-1]: | |
| # AUTO: Checks this condition. | |
| if member not in bundle_value: | |
| # AUTO: Stops this flow by raising an error. | |
| raise InterpreterError(f"Runtime Error: Bundle has no member '{member}'.", node.line) | |
| # AUTO: Sets `bundle_value`. | |
| bundle_value = bundle_value[member] | |
| # AUTO: Checks this condition. | |
| if not isinstance(bundle_value, dict): | |
| # AUTO: Stops this flow by raising an error. | |
| raise InterpreterError(f"Runtime Error: Member '{member}' is not a bundle.", node.line) | |
| # LINE: Final member is the field being assigned. | |
| final_member = chain[-1] | |
| # LINE: Final member must exist. | |
| if final_member not in bundle_value: | |
| # AUTO: Stops this flow by raising an error. | |
| raise InterpreterError(f"Runtime Error: Bundle has no member '{final_member}'.", node.line) | |
| # LINE: Find the declared type chain for the final member. | |
| type_chain_current = current | |
| # AUTO: Checks this condition. | |
| if hasattr(type_chain_current, 'node_type') and type_chain_current.node_type == "ArrayMemberAccess": | |
| # LINE: Get base array name from array member access. | |
| la_node = type_chain_current.children[0] | |
| # AUTO: Repeats while this condition is true. | |
| while hasattr(la_node, 'node_type') and la_node.node_type == "ListAccess": | |
| # AUTO: Sets `la_node`. | |
| la_node = la_node.children[0].value | |
| # LINE: Read bundle type from base array variable. | |
| var_type = self.lookup_variable(la_node)["type"] if not isinstance(self.lookup_variable(la_node), str) else None # type: ignore | |
| # AUTO: Runs when previous condition did not pass. | |
| else: | |
| # LINE: Base object type comes from the variable entry. | |
| obj_name = type_chain_current.value | |
| # AUTO: Sets `var_info`. | |
| var_info = self.lookup_variable(obj_name) | |
| # AUTO: Sets `var_type`. | |
| var_type = var_info["type"] if not isinstance(var_info, str) else None | |
| # LINE: Convert assigned value to match member type when needed. | |
| if var_type and var_type in self.bundle_types: | |
| # AUTO: Sets `cur_type`. | |
| cur_type = var_type | |
| # AUTO: Starts a loop over these values. | |
| for member in chain: | |
| # AUTO: Checks this condition. | |
| if cur_type in self.bundle_types: | |
| # AUTO: Sets `cur_type`. | |
| cur_type = self.bundle_types[cur_type].get(member, cur_type) | |
| # AUTO: Checks this condition. | |
| if cur_type == "seed" and isinstance(value, float): | |
| # AUTO: Sets `value`. | |
| value = int(value) | |
| # AUTO: Checks the next alternate condition. | |
| elif cur_type == "tree" and isinstance(value, int): | |
| # AUTO: Sets `value`. | |
| value = float(value) | |
| # AUTO: Checks the next alternate condition. | |
| elif cur_type == "branch" and isinstance(value, int): | |
| # AUTO: Executes this statement. | |
| value = True if value != 0 else False | |
| # LINE: Store value into the bundle member. | |
| bundle_value[final_member] = value | |
| # AUTO: Checks the next alternate condition. | |
| elif target_node.node_type == "ArrayMemberAccess": | |
| # Assignment into a bundle member inside an array element, | |
| # e.g. students[i].age = 20. | |
| # LINE: First child is list element access. | |
| list_access_node = target_node.children[0] | |
| # LINE: Second child is member name. | |
| member_name = target_node.children[1].value | |
| # LINE: Evaluate the array element first. | |
| bundle_element = self.eval_list_access(list_access_node) | |
| # LINE: Array element must be a bundle dictionary. | |
| if not isinstance(bundle_element, dict): | |
| # AUTO: Stops this flow by raising an error. | |
| raise InterpreterError(f"Runtime Error: Array element is not a bundle.", node.line) | |
| # LINE: Requested member must exist. | |
| if member_name not in bundle_element: | |
| # AUTO: Stops this flow by raising an error. | |
| raise InterpreterError(f"Runtime Error: Bundle has no member '{member_name}'.", node.line) | |
| # LINE: Find the base array variable name. | |
| current = list_access_node | |
| # AUTO: Repeats while this condition is true. | |
| while hasattr(current, 'node_type') and current.node_type == "ListAccess": | |
| # AUTO: Sets `current`. | |
| current = current.children[0].value | |
| # LINE: current now stores the base variable name. | |
| var_name = current | |
| # LINE: Look up base array variable. | |
| var_info = self.lookup_variable(var_name) | |
| # LINE: Convert value to member type if the base array is a bundle type. | |
| if not isinstance(var_info, str) and var_info["type"] in self.bundle_types: | |
| # AUTO: Sets `member_type`. | |
| member_type = self.bundle_types[var_info["type"]].get(member_name) | |
| # AUTO: Checks this condition. | |
| if member_type == "seed" and isinstance(value, float): | |
| # AUTO: Sets `value`. | |
| value = int(value) | |
| # AUTO: Checks the next alternate condition. | |
| elif member_type == "tree" and isinstance(value, int): | |
| # AUTO: Sets `value`. | |
| value = float(value) | |
| # AUTO: Checks the next alternate condition. | |
| elif member_type == "branch" and isinstance(value, int): | |
| # AUTO: Executes this statement. | |
| value = True if value != 0 else False | |
| # LINE: Store value into the array element's member. | |
| bundle_element[member_name] = value | |
| # AUTO: Runs when previous condition did not pass. | |
| else: | |
| # Simple variable assignment, e.g. total = x + y. | |
| # LINE: Target node value is the variable name. | |
| var_name = target_node.value | |
| # LINE: Look up variable metadata and current value. | |
| var_info = self.lookup_variable(var_name) | |
| # AUTO: Checks this condition. | |
| if isinstance(var_info, str): | |
| # AUTO: Stops this flow by raising an error. | |
| raise InterpreterError(var_info, node.line) | |
| # LINE: Read declared type for conversion. | |
| var_type = var_info["type"] | |
| # LINE: Assigning float to seed truncates to int. | |
| if var_type == "seed" and isinstance(value, float): | |
| # AUTO: Sets `value`. | |
| value = int(value) | |
| # LINE: Assigning int to tree converts to float. | |
| if var_type == "tree" and isinstance(value, int): | |
| # AUTO: Sets `value`. | |
| value = float(value) | |
| # LINE: Assigning int to branch converts 0 to False and nonzero to True. | |
| if var_type == "branch" and isinstance(value, int): | |
| # AUTO: Executes this statement. | |
| value = True if value != 0 else False | |
| # LINE: Save converted value into the variable. | |
| self.set_variable(var_name, value) | |
| # LINE: Return assigned value so assignment expression can be reused. | |
| return value | |
| # AUTO: Defines function `eval_binary_op`. | |
| def eval_binary_op(self, node): | |
| # GUIDE: Binary operations evaluate left/right child expressions before | |
| # applying arithmetic, comparison, logical, or concat behavior. | |
| # Example AST for x + y: | |
| # left child = Identifier(x), right child = Identifier(y), value = "+" | |
| # LINE: Evaluate the left operand first. | |
| left = self.interpret(node.children[0]) | |
| # LINE: Evaluate the right operand second. | |
| right = self.interpret(node.children[1]) | |
| # LINE: node.value stores the actual operator symbol. | |
| operator = node.value | |
| # LINE: Backtick is GAL string concatenation. | |
| if operator == '`': | |
| # GAL string concat operator. | |
| # AUTO: Sets `result`. | |
| result = str(left) + str(right) | |
| # AUTO: Returns this result to the caller. | |
| return result | |
| # Convert token/literal strings like "10", "~5", "sunshine" into Python | |
| # values like 10, -5, True before applying the operator. | |
| # LINE: Convert left literal text into Python int/float/bool/string if needed. | |
| left = self._parse_literal(left) | |
| # LINE: Convert right literal text into Python int/float/bool/string if needed. | |
| right = self._parse_literal(right) | |
| # LINE: Plus with any string becomes concatenation. | |
| if operator == '+' and (isinstance(left, str) or isinstance(right, str)): | |
| # AUTO: Sets `result`. | |
| result = str(left) + str(right) | |
| # AUTO: Returns this result to the caller. | |
| return result | |
| # AUTO: Starts protected code that can catch errors. | |
| try: | |
| # LINE: Choose operation based on the operator stored in the AST node. | |
| if operator == '+': | |
| # Numeric addition. If both operands are non-numeric, convert | |
| # truthy/empty values into numbers first. | |
| # AUTO: Checks this condition. | |
| if not isinstance(left, (int, float)) and not isinstance(right, (int, float)): | |
| # AUTO: Checks this condition. | |
| if isinstance(left, bool): | |
| # AUTO: Executes this statement. | |
| left = 1 if left == True else 0 | |
| # AUTO: Checks the next alternate condition. | |
| elif isinstance(left, str): | |
| # AUTO: Executes this statement. | |
| left = 1 if left != "" else 0 | |
| # AUTO: Checks this condition. | |
| if isinstance(right, bool): | |
| # AUTO: Executes this statement. | |
| right = 1 if right == True else 0 | |
| # AUTO: Checks the next alternate condition. | |
| elif isinstance(right, str): | |
| # AUTO: Executes this statement. | |
| right = 1 if right != "" else 0 | |
| # AUTO: Returns this result to the caller. | |
| return left + right # type: ignore[operator] | |
| # AUTO: Checks the next alternate condition. | |
| elif operator == '-': | |
| # LINE: Subtraction path. | |
| if not isinstance(left, (int, float)) and not isinstance(right, (int, float)): | |
| # AUTO: Checks this condition. | |
| if isinstance(left, bool): | |
| # AUTO: Executes this statement. | |
| left = 1 if left == True else 0 | |
| # AUTO: Checks the next alternate condition. | |
| elif isinstance(left, str): | |
| # AUTO: Executes this statement. | |
| left = 1 if left != "" else 0 | |
| # AUTO: Checks this condition. | |
| if isinstance(right, bool): | |
| # AUTO: Executes this statement. | |
| right = 1 if right == True else 0 | |
| # AUTO: Checks the next alternate condition. | |
| elif isinstance(right, str): | |
| # AUTO: Executes this statement. | |
| right = 1 if right != "" else 0 | |
| # AUTO: Returns this result to the caller. | |
| return left - right # type: ignore[operator] | |
| # AUTO: Checks the next alternate condition. | |
| elif operator == '*': | |
| # LINE: Multiplication path. | |
| if not isinstance(left, (int, float)) and not isinstance(right, (int, float)): | |
| # AUTO: Checks this condition. | |
| if isinstance(left, bool): | |
| # AUTO: Executes this statement. | |
| left = 1 if left == True else 0 | |
| # AUTO: Checks the next alternate condition. | |
| elif isinstance(left, str): | |
| # AUTO: Executes this statement. | |
| left = 1 if left != "" else 0 | |
| # AUTO: Checks this condition. | |
| if isinstance(right, bool): | |
| # AUTO: Executes this statement. | |
| right = 1 if right == True else 0 | |
| # AUTO: Checks the next alternate condition. | |
| elif isinstance(right, str): | |
| # AUTO: Executes this statement. | |
| right = 1 if right != "" else 0 | |
| # AUTO: Returns this result to the caller. | |
| return left * right # type: ignore[operator] | |
| # AUTO: Checks the next alternate condition. | |
| elif operator == '**': | |
| # LINE: Exponent path. | |
| if not isinstance(left, (int, float)) and not isinstance(right, (int, float)): | |
| # AUTO: Checks this condition. | |
| if isinstance(left, bool): | |
| # AUTO: Executes this statement. | |
| left = 1 if left == True else 0 | |
| # AUTO: Checks the next alternate condition. | |
| elif isinstance(left, str): | |
| # AUTO: Executes this statement. | |
| left = 1 if left != "" else 0 | |
| # AUTO: Checks this condition. | |
| if isinstance(right, bool): | |
| # AUTO: Executes this statement. | |
| right = 1 if right == True else 0 | |
| # AUTO: Checks the next alternate condition. | |
| elif isinstance(right, str): | |
| # AUTO: Executes this statement. | |
| right = 1 if right != "" else 0 | |
| # AUTO: Returns this result to the caller. | |
| return left ** right # type: ignore[operator] | |
| # AUTO: Checks the next alternate condition. | |
| elif operator == '/': | |
| # Division includes runtime zero checking. | |
| # LINE: Division path. | |
| if not isinstance(left, (int, float)) and not isinstance(right, (int, float)): | |
| # AUTO: Checks this condition. | |
| if isinstance(left, bool): | |
| # AUTO: Executes this statement. | |
| left = 1 if left == True else 0 | |
| # AUTO: Checks the next alternate condition. | |
| elif isinstance(left, str): | |
| # AUTO: Executes this statement. | |
| left = 1 if left != "" else 0 | |
| # AUTO: Checks this condition. | |
| if isinstance(right, bool): | |
| # AUTO: Executes this statement. | |
| right = 1 if right == True else 0 | |
| # AUTO: Checks the next alternate condition. | |
| elif isinstance(right, str): | |
| # AUTO: Executes this statement. | |
| right = 1 if right != "" else 0 | |
| # AUTO: Checks this condition. | |
| if right == 0: | |
| # LINE: Stop execution when divisor is zero. | |
| raise InterpreterError("Runtime Error: Division by zero is undefined", node.line) | |
| # AUTO: Returns this result to the caller. | |
| return left / right # type: ignore[operator] | |
| # AUTO: Checks the next alternate condition. | |
| elif operator == '%': | |
| # LINE: Modulo/remainder path. | |
| if not isinstance(left, (int, float)) and not isinstance(right, (int, float)): | |
| # AUTO: Checks this condition. | |
| if isinstance(left, bool): | |
| # AUTO: Executes this statement. | |
| left = 1 if left == True else 0 | |
| # AUTO: Checks the next alternate condition. | |
| elif isinstance(left, str): | |
| # AUTO: Executes this statement. | |
| left = 1 if left != "" else 0 | |
| # AUTO: Checks this condition. | |
| if isinstance(right, bool): | |
| # AUTO: Executes this statement. | |
| right = 1 if right == True else 0 | |
| # AUTO: Checks the next alternate condition. | |
| elif isinstance(right, str): | |
| # AUTO: Executes this statement. | |
| right = 1 if right != "" else 0 | |
| # AUTO: Checks this condition. | |
| if right == 0: | |
| # LINE: Modulo by zero is also invalid. | |
| raise InterpreterError("Runtime Error: Division by zero is undefined", node.line) | |
| # AUTO: Returns this result to the caller. | |
| return left % right # type: ignore[operator] | |
| # AUTO: Checks the next alternate condition. | |
| elif operator == '==': | |
| # Comparison operators return branch/boolean results. | |
| # LINE: Equality comparison. | |
| return left == right | |
| # AUTO: Checks the next alternate condition. | |
| elif operator == '!=': | |
| # LINE: Not-equal comparison. | |
| return left != right | |
| # AUTO: Checks the next alternate condition. | |
| elif operator == '<': | |
| # LINE: Less-than comparison. | |
| if isinstance(left, str): | |
| # AUTO: Executes this statement. | |
| left = 0 if left == "" else 1 | |
| # AUTO: Checks this condition. | |
| if isinstance(right, str): | |
| # AUTO: Executes this statement. | |
| right = 0 if right == "" else 1 | |
| # AUTO: Returns this result to the caller. | |
| return left < right | |
| # AUTO: Checks the next alternate condition. | |
| elif operator == '<=': | |
| # LINE: Less-than-or-equal comparison. | |
| if isinstance(left, str): | |
| # AUTO: Executes this statement. | |
| left = 0 if left == "" else 1 | |
| # AUTO: Checks this condition. | |
| if isinstance(right, str): | |
| # AUTO: Executes this statement. | |
| right = 0 if right == "" else 1 | |
| # AUTO: Returns this result to the caller. | |
| return left <= right | |
| # AUTO: Checks the next alternate condition. | |
| elif operator == '>': | |
| # LINE: Greater-than comparison. | |
| if isinstance(left, str): | |
| # AUTO: Executes this statement. | |
| left = 0 if left == "" else 1 | |
| # AUTO: Checks this condition. | |
| if isinstance(right, str): | |
| # AUTO: Executes this statement. | |
| right = 0 if right == "" else 1 | |
| # AUTO: Returns this result to the caller. | |
| return left > right | |
| # AUTO: Checks the next alternate condition. | |
| elif operator == '>=': | |
| # LINE: Greater-than-or-equal comparison. | |
| if isinstance(left, str): | |
| # AUTO: Executes this statement. | |
| left = 0 if left == "" else 1 | |
| # AUTO: Checks this condition. | |
| if isinstance(right, str): | |
| # AUTO: Executes this statement. | |
| right = 0 if right == "" else 1 | |
| # AUTO: Returns this result to the caller. | |
| return left >= right | |
| # AUTO: Checks the next alternate condition. | |
| elif operator == '&&': | |
| # Logical operators convert numeric/string values into boolean | |
| # truthiness before applying AND/OR. | |
| # LINE: Logical AND path. | |
| if isinstance(left, int) or isinstance(left, float): | |
| # AUTO: Checks this condition. | |
| if left == 0: | |
| # AUTO: Sets `left`. | |
| left = False | |
| # AUTO: Runs when previous condition did not pass. | |
| else: | |
| # AUTO: Sets `left`. | |
| left = True | |
| # AUTO: Checks the next alternate condition. | |
| elif isinstance(right, int) or isinstance(right, float): | |
| # AUTO: Checks this condition. | |
| if right == 0: | |
| # AUTO: Sets `right`. | |
| right = False | |
| # AUTO: Runs when previous condition did not pass. | |
| else: | |
| # AUTO: Sets `right`. | |
| right = True | |
| # AUTO: Checks the next alternate condition. | |
| elif isinstance(left, str): | |
| # AUTO: Executes this statement. | |
| left = False if left == "" else True | |
| # AUTO: Checks the next alternate condition. | |
| elif isinstance(right, str): | |
| # AUTO: Executes this statement. | |
| right = False if right == "" else True | |
| # AUTO: Checks the next alternate condition. | |
| elif isinstance(left, str) or isinstance(right, str): | |
| # AUTO: Sets `left`. | |
| left = bool(left) | |
| # AUTO: Checks the next alternate condition. | |
| elif isinstance(left, str) or isinstance(right, str): | |
| # AUTO: Sets `right`. | |
| right = bool(right) | |
| # AUTO: Returns this result to the caller. | |
| return bool(left) and bool(right) | |
| # AUTO: Checks the next alternate condition. | |
| elif operator == '||': | |
| # LINE: Logical OR path. | |
| if isinstance(left, int) or isinstance(left, float): | |
| # AUTO: Checks this condition. | |
| if left == 0: | |
| # AUTO: Sets `left`. | |
| left = False | |
| # AUTO: Runs when previous condition did not pass. | |
| else: | |
| # AUTO: Sets `left`. | |
| left = True | |
| # AUTO: Checks the next alternate condition. | |
| elif isinstance(right, int) or isinstance(right, float): | |
| # AUTO: Checks this condition. | |
| if right == 0: | |
| # AUTO: Sets `right`. | |
| right = False | |
| # AUTO: Runs when previous condition did not pass. | |
| else: | |
| # AUTO: Sets `right`. | |
| right = True | |
| # AUTO: Checks the next alternate condition. | |
| elif isinstance(left, str) or isinstance(right, str): | |
| # AUTO: Sets `left`. | |
| left = bool(left) | |
| # AUTO: Checks the next alternate condition. | |
| elif isinstance(left, str) or isinstance(right, str): | |
| # AUTO: Sets `right`. | |
| right = bool(right) | |
| # AUTO: Returns this result to the caller. | |
| return bool(left) or bool(right) | |
| # AUTO: Checks the next alternate condition. | |
| elif operator == '!': | |
| # AUTO: Returns this result to the caller. | |
| return not bool(left) | |
| # AUTO: Checks the next alternate condition. | |
| elif operator == 'neg': | |
| # AUTO: Returns this result to the caller. | |
| return -left # type: ignore | |
| # AUTO: Runs when previous condition did not pass. | |
| else: | |
| # AUTO: Stops this flow by raising an error. | |
| raise Exception(f"Unknown operator: {operator}") | |
| # AUTO: Handles the matching error case. | |
| except ZeroDivisionError: | |
| # AUTO: Stops this flow by raising an error. | |
| raise InterpreterError("Runtime Error: Division by zero", "") | |
| # AUTO: Defines function `_parse_literal`. | |
| def _parse_literal(self, value): | |
| # AUTO: Checks this condition. | |
| if isinstance(value, str): | |
| # AUTO: Sets `var_info`. | |
| var_info = self.lookup_variable(value) | |
| # AUTO: Checks this condition. | |
| if var_info is not None and not isinstance(var_info, str): | |
| # AUTO: Returns this result to the caller. | |
| return var_info["value"] | |
| # AUTO: Checks this condition. | |
| if isinstance(value, (int, float, bool)): | |
| # AUTO: Returns this result to the caller. | |
| return value | |
| # AUTO: Checks this condition. | |
| if not isinstance(value, str): | |
| # AUTO: Returns this result to the caller. | |
| return value | |
| # AUTO: Sets `value`. | |
| value = value.strip() | |
| # AUTO: Checks this condition. | |
| if value.startswith('"') and value.endswith('"'): | |
| # AUTO: Returns this result to the caller. | |
| return value[1:-1] | |
| # AUTO: Checks this condition. | |
| if value.startswith("'") and value.endswith("'"): | |
| # AUTO: Returns this result to the caller. | |
| return value[1:-1] | |
| # AUTO: Checks this condition. | |
| if value in ('true', 'sunshine'): | |
| # AUTO: Returns this result to the caller. | |
| return True | |
| # AUTO: Checks this condition. | |
| if value in ('false', 'frost'): | |
| # AUTO: Returns this result to the caller. | |
| return False | |
| # AUTO: Sets `parse_value`. | |
| parse_value = value | |
| # AUTO: Checks this condition. | |
| if parse_value.startswith('~'): | |
| # AUTO: Sets `parse_value`. | |
| parse_value = '-' + parse_value[1:] | |
| # AUTO: Starts protected code that can catch errors. | |
| try: | |
| # AUTO: Checks this condition. | |
| if '.' in parse_value: | |
| # AUTO: Returns this result to the caller. | |
| return float(parse_value) | |
| # AUTO: Returns this result to the caller. | |
| return int(parse_value) | |
| # AUTO: Handles the matching error case. | |
| except ValueError: | |
| # AUTO: Returns this result to the caller. | |
| return value | |
| # AUTO: Defines function `eval_function_declaration`. | |
| def eval_function_declaration(self, node): | |
| # GUIDE: Function declarations are registered, not executed immediately. | |
| # The saved body runs later when a FunctionCallNode is interpreted. | |
| # LINE: First child stores function return type. | |
| return_type = node.children[0].value | |
| # LINE: Second child stores the parameter list node. | |
| parameters_node = node.children[1] | |
| # LINE: node.value stores the function name. | |
| func_name = node.value | |
| # LINE: Collect parameters into simple dictionaries. | |
| params = [] | |
| # AUTO: Checks this condition. | |
| if parameters_node and len(parameters_node.children) > 0: | |
| # AUTO: Starts a loop over these values. | |
| for param in parameters_node.children: | |
| # LINE: Parameter nodes must have the expected AST shape. | |
| if not hasattr(param, 'node_type') or param.node_type != 'Parameter': | |
| # AUTO: Stops this flow by raising an error. | |
| raise Exception(f"Invalid parameter: {param.value}") | |
| # LINE: First parameter child is type. | |
| param_type = param.children[0].value | |
| # LINE: Second parameter child is name. | |
| param_name = param.children[1].value | |
| # LINE: Detect array/list parameter marker. | |
| is_list = any(child.node_type == "ArrayParam" for child in param.children) | |
| # LINE: Save this parameter metadata. | |
| params.append({"name": param_name, "type": param_type, "is_list": is_list}) | |
| # LINE: Register the function in self.functions for later calls. | |
| self.declare_function(func_name, return_type, params, node) | |
| # LINE: Declaration itself produces no runtime value. | |
| return None | |
| # AUTO: Defines function `eval_block`. | |
| def eval_block(self, block_node): | |
| # GUIDE: Execute statements in order. reclaim/prune/skip can interrupt this | |
| # normal sequence through ReturnValue or loop flags. | |
| # LINE: Run each statement inside the block from top to bottom. | |
| for statement in block_node.children: | |
| # LINE: Dispatch statement to the correct eval_* method. | |
| self.interpret(statement) | |
| # LINE: Stop this block if prune was triggered. | |
| if self.break_triggered(): | |
| # AUTO: Returns this result to the caller. | |
| return | |
| # LINE: Stop this block if skip was triggered. | |
| if self.continue_flag: | |
| # AUTO: Returns this result to the caller. | |
| return | |
| # AUTO: Defines function `plant`. | |
| def plant(self, value): | |
| # AUTO: Sends an event/message to the frontend. | |
| self.socketio.emit('output', {'output': str(value)}) | |
| # AUTO: Defines function `plant_out`. | |
| def plant_out(self, num): | |
| # AUTO: Sends an event/message to the frontend. | |
| self.socketio.emit('output', {'output': str(num)}) | |
| # AUTO: Appends a value to a list. | |
| self.output.append(str(num)) | |
| # AUTO: Defines function `eval_print`. | |
| def eval_print(self, node): | |
| # GUIDE: plant() evaluates args, applies optional {} formatting, and | |
| # emits the final text to the UI terminal. | |
| # LINE: plant() with no arguments prints nothing. | |
| if not node.children: | |
| # AUTO: Returns this result to the caller. | |
| return | |
| # LINE: First plant argument can be normal text or a format string. | |
| first = node.children[0] | |
| # LINE: Evaluate the first argument. | |
| evaluated_first = self.interpret(first) | |
| # AUTO: Checks this condition. | |
| if isinstance(evaluated_first, float): | |
| # LINE: Limit displayed float decimals to 5 digits. | |
| whole, dot, dec = str(evaluated_first).partition('.') | |
| # AUTO: Sets `dec`. | |
| dec = dec[:5] | |
| # AUTO: Sets `evaluated_first`. | |
| evaluated_first = float(f"{whole}.{dec}") | |
| # LINE: If first string has {}, use Python format with remaining args. | |
| if isinstance(evaluated_first, str) and '{}' in evaluated_first: | |
| # AUTO: Sets `values`. | |
| values = [] | |
| # AUTO: Starts a loop over these values. | |
| for arg in node.children[1:]: | |
| # LINE: Evaluate each format value. | |
| value = self.interpret(arg) | |
| # AUTO: Checks this condition. | |
| if isinstance(value, str) and not isinstance(self.lookup_variable(value), str): | |
| # AUTO: Sets `value`. | |
| value = self.lookup_variable(value)["value"] # type: ignore[index] | |
| # AUTO: Checks this condition. | |
| if isinstance(value, float): | |
| # AUTO: Sets `whole, dot, dec`. | |
| whole, dot, dec = str(value).partition('.') | |
| # AUTO: Sets `dec`. | |
| dec = dec[:5] | |
| # AUTO: Sets `value`. | |
| value = float(f"{whole}.{dec}") | |
| # AUTO: Appends a value to a list. | |
| values.append(value) | |
| # AUTO: Starts protected code that can catch errors. | |
| try: | |
| # LINE: Replace {} placeholders with evaluated values. | |
| output_str = evaluated_first.format(*values) | |
| # AUTO: Handles the matching error case. | |
| except Exception as e: | |
| # AUTO: Stops this flow by raising an error. | |
| raise Exception(f"Format error in plant(): '{evaluated_first}' with {values}: {e}") | |
| # LINE: Send formatted output to UI. | |
| self.plant(output_str) | |
| # AUTO: Returns this result to the caller. | |
| return | |
| # LINE: Multiple plant args without {} are joined with spaces. | |
| if len(node.children) > 1: | |
| # AUTO: Sets `parts`. | |
| parts = [str(evaluated_first)] | |
| # AUTO: Starts a loop over these values. | |
| for arg in node.children[1:]: | |
| # LINE: Evaluate each extra plant argument. | |
| value = self.interpret(arg) | |
| # AUTO: Checks this condition. | |
| if isinstance(value, float): | |
| # AUTO: Sets `whole, dot, dec`. | |
| whole, dot, dec = str(value).partition('.') | |
| # AUTO: Sets `dec`. | |
| dec = dec[:5] | |
| # AUTO: Sets `value`. | |
| value = float(f"{whole}.{dec}") | |
| # AUTO: Appends a value to a list. | |
| parts.append(str(value)) | |
| # LINE: Output the combined text. | |
| self.plant(" ".join(parts)) | |
| # AUTO: Returns this result to the caller. | |
| return | |
| # LINE: Single plant argument output path. | |
| self.plant(str(evaluated_first)) | |
| # AUTO: Defines function `eval_formatted_string`. | |
| def eval_formatted_string(self, node): | |
| # AUTO: Sets `value`. | |
| value = node.value | |
| # AUTO: Checks this condition. | |
| if value.startswith('"') and value.endswith('"'): | |
| # AUTO: Sets `value`. | |
| value = value[1:-1] | |
| # AUTO: Sets `value`. | |
| value = value.replace(r'\\', '\\') | |
| # AUTO: Sets `value`. | |
| value = value.replace(r'\n', '\n') | |
| # AUTO: Sets `value`. | |
| value = value.replace(r'\t', '\t') | |
| # AUTO: Sets `value`. | |
| value = value.replace(r'\"', '"') | |
| # AUTO: Sets `value`. | |
| value = value.replace(r'\{', '{') | |
| # AUTO: Sets `value`. | |
| value = value.replace(r'\}', '}') | |
| # AUTO: Sets `value`. | |
| value = value.replace(r'\/', '/') | |
| # AUTO: Returns this result to the caller. | |
| return value | |
| # AUTO: Defines function `eval_list`. | |
| def eval_list(self, node): | |
| # AUTO: Sets `result`. | |
| result = [] | |
| # AUTO: Starts a loop over these values. | |
| for child in node.children: | |
| # AUTO: Checks this condition. | |
| if isinstance(child, ListNode): | |
| # AUTO: Appends a value to a list. | |
| result.append(self.eval_list(child)) | |
| # AUTO: Runs when previous condition did not pass. | |
| else: | |
| # AUTO: Appends a value to a list. | |
| result.append(self.interpret(child)) | |
| # AUTO: Returns this result to the caller. | |
| return result | |
| # AUTO: Defines function `eval_list_access`. | |
| def eval_list_access(self, node): | |
| # AUTO: Sets `name_or_node`. | |
| name_or_node = node.children[0].value | |
| # AUTO: Checks this condition. | |
| if hasattr(name_or_node, 'node_type') and name_or_node.node_type == "ListAccess": | |
| # AUTO: Sets `list_value`. | |
| list_value = self.eval_list_access(name_or_node) | |
| # AUTO: Sets `display_name`. | |
| display_name = "nested list" | |
| # AUTO: Runs when previous condition did not pass. | |
| else: | |
| # AUTO: Sets `list_name`. | |
| list_name = name_or_node | |
| # AUTO: Sets `list_entry`. | |
| list_entry = self.lookup_variable(list_name) | |
| # AUTO: Sets `list_value`. | |
| list_value = list_entry["value"] # type: ignore | |
| # AUTO: Sets `display_name`. | |
| display_name = list_name | |
| # AUTO: Sets `index_node`. | |
| index_node = node.children[1] | |
| # AUTO: Sets `index`. | |
| index = self.interpret(index_node.children[0]) | |
| # AUTO: Checks this condition. | |
| if not isinstance(index, int): | |
| # AUTO: Stops this flow by raising an error. | |
| raise InterpreterError(f"Runtime Error: List index must be an integer. Got '{index}'", node.line) | |
| # AUTO: Checks this condition. | |
| if not isinstance(list_value, (list, str)): | |
| # AUTO: Stops this flow by raising an error. | |
| raise InterpreterError(f"Runtime Error: Cannot index into a non-list value.", node.line) | |
| # AUTO: Checks this condition. | |
| if index < 0 or index >= len(list_value): | |
| # AUTO: Stops this flow by raising an error. | |
| raise InterpreterError(f"Runtime Error: Index '{index}' out of bounds for '{display_name}'.", node.line) | |
| # AUTO: Returns this result to the caller. | |
| return list_value[index] | |
| # AUTO: Defines function `eval_return`. | |
| def eval_return(self, node): | |
| # GUIDE: reclaim jumps out of the current function by raising ReturnValue. | |
| # LINE: Evaluate reclaim value if present; root usually has none. | |
| value = self.interpret(node.children[0]) if node.children else None | |
| # LINE: Raise ReturnValue so nested blocks immediately exit the function. | |
| raise ReturnValue(value) | |
| # AUTO: Defines function `eval_function_call`. | |
| def eval_function_call(self, node): | |
| # GUIDE: Function call flow; evaluate args, enter scope, bind params, | |
| # run the saved body, then leave the scope. | |
| # LINE: node.value is the function name being called. | |
| function_name = node.value | |
| # Evaluate all actual arguments before entering the called function. | |
| # Example: gcd(a, b) becomes [value_of_a, value_of_b]. | |
| # LINE: Evaluate every argument expression before binding parameters. | |
| args = [self.interpret(arg.children[0]) for arg in node.children] | |
| # Look up the function saved earlier by eval_function_declaration(). | |
| # LINE: Fetch function metadata from self.functions. | |
| func_info = self.lookup_function(function_name) | |
| # AUTO: Checks this condition. | |
| if isinstance(func_info, str): | |
| # AUTO: Stops this flow by raising an error. | |
| raise InterpreterError(func_info, node.line) | |
| # LINE: Expected parameter list saved during declaration. | |
| expected_params = func_info["params"] | |
| # LINE: FunctionDeclarationNode containing the function body. | |
| function_node = func_info["node"] | |
| # LINE: Argument count must match parameter count. | |
| if len(expected_params) != len(args): | |
| # AUTO: Stops this flow by raising an error. | |
| raise InterpreterError( | |
| # AUTO: Executes this statement. | |
| f"Runtime Error: Function '{function_name}' expects {len(expected_params)} argument(s), got {len(args)}.", | |
| # AUTO: Executes this statement. | |
| node.line | |
| # AUTO: Closes the current grouped code/data. | |
| ) | |
| # LINE: Enter a new local function scope. | |
| self.enter_scope() | |
| # AUTO: Starts protected code that can catch errors. | |
| try: | |
| # LINE: Bind each argument value to its parameter variable. | |
| for i, param in enumerate(expected_params): | |
| # Bind each argument value to its parameter name in the new | |
| # function scope. Example: parameter "a" receives 48. | |
| # AUTO: Sets `param_name`. | |
| param_name = param["name"] | |
| # AUTO: Sets `param_type`. | |
| param_type = param["type"] | |
| # AUTO: Sets `arg_value`. | |
| arg_value = args[i] | |
| # AUTO: Sets `is_list`. | |
| is_list = param.get("is_list", False) | |
| # LINE: Parameters are stored like local variables. | |
| self.declare_variable(param_name, param_type, arg_value, is_list=is_list) | |
| # AUTO: Starts protected code that can catch errors. | |
| try: | |
| # Execute the function body block. If reclaim runs inside, | |
| # eval_return raises ReturnValue and jumps to the except below. | |
| # LINE: Run the saved function body. | |
| self.eval_block(function_node.children[2]) | |
| # AUTO: Handles the matching error case. | |
| except ReturnValue as ret: | |
| # The reclaim value becomes the function call result. | |
| # LINE: Return reclaim's value to the caller. | |
| return ret.value | |
| # LINE: If no reclaim value happened, function returns None. | |
| return None | |
| # AUTO: Runs cleanup code no matter what happened. | |
| finally: | |
| # LINE: Always leave the function scope even if an error/reclaim happens. | |
| self.exit_scope() | |
| # LINE: Clear active function marker. | |
| self.current_func_name = None | |
| # AUTO: Defines function `eval_append`. | |
| def eval_append(self, node): | |
| # AUTO: Sets `list_name`. | |
| list_name = node.parent.children[0].value | |
| # AUTO: Sets `list_info`. | |
| list_info = self.lookup_variable(list_name) | |
| # AUTO: Starts a loop over these values. | |
| for child in node.children: | |
| # AUTO: Sets `value`. | |
| value = self.interpret(child) | |
| # AUTO: Appends a value to a list. | |
| list_info["value"].append(value) # type: ignore | |
| # AUTO: Defines function `eval_insert`. | |
| def eval_insert(self, node): | |
| # AUTO: Sets `list_name`. | |
| list_name = node.parent.children[0].value | |
| # AUTO: Sets `list_info`. | |
| list_info = self.lookup_variable(list_name) | |
| # AUTO: Sets `index`. | |
| index = self.interpret(node.children[0].children[0]) | |
| # AUTO: Checks this condition. | |
| if not isinstance(index, int): | |
| # AUTO: Stops this flow by raising an error. | |
| raise InterpreterError("Runtime Error: Insert index must be an integer", node.line) | |
| # AUTO: Checks this condition. | |
| if index < 0 or index > len(list_info["value"]): # type: ignore | |
| # AUTO: Stops this flow by raising an error. | |
| raise InterpreterError(f"Runtime Error: Index {index} out of range for insert", node.line) | |
| # AUTO: Starts a loop over these values. | |
| for child in node.children[1:]: | |
| # AUTO: Sets `value`. | |
| value = self.interpret(child) | |
| # AUTO: Executes this statement. | |
| list_info["value"].insert(index, value) # type: ignore | |
| # AUTO: Adds into `index`. | |
| index += 1 | |
| # AUTO: Defines function `eval_remove`. | |
| def eval_remove(self, node): | |
| # AUTO: Sets `list_name`. | |
| list_name = node.children[0].value | |
| # AUTO: Sets `index_node`. | |
| index_node = node.children[1].children[0] | |
| # AUTO: Sets `list_info`. | |
| list_info = self.lookup_variable(list_name) | |
| # AUTO: Checks this condition. | |
| if isinstance(list_info, str): | |
| # AUTO: Stops this flow by raising an error. | |
| raise InterpreterError(list_info, node.line) | |
| # AUTO: Sets `index`. | |
| index = self.interpret(index_node) | |
| # AUTO: Checks this condition. | |
| if not isinstance(index, int): | |
| # AUTO: Stops this flow by raising an error. | |
| raise InterpreterError("Runtime Error: Remove index must be an integer", node.line) | |
| # AUTO: Checks this condition. | |
| if index < 0 or index >= len(list_info["value"]): | |
| # AUTO: Stops this flow by raising an error. | |
| raise InterpreterError(f"Runtime Error: Index {index} out of bounds for remove", node.line) | |
| # AUTO: Sets `removed`. | |
| removed = list_info["value"].pop(index) | |
| # AUTO: Defines function `eval_unaryop`. | |
| def eval_unaryop(self, node): | |
| # LINE: Member increment/decrement path, like student.age++. | |
| if isinstance(node.children[0], MemberAccessNode) and node.value in {"++", "--"}: | |
| # LINE: Target is the member access node. | |
| target = node.children[0] | |
| # LINE: chain stores member names from nested access. | |
| chain = [] | |
| # LINE: Start walking from the target access. | |
| current = target | |
| # LINE: Collect all member names until base object. | |
| while isinstance(current, MemberAccessNode): | |
| # AUTO: Appends a value to a list. | |
| chain.append(current.children[1].value) | |
| # AUTO: Sets `current`. | |
| current = current.children[0] | |
| # LINE: Reverse so access starts from base object outward. | |
| chain.reverse() | |
| # LINE: Base object variable name. | |
| obj_name = current.value | |
| # LINE: Look up base object variable. | |
| var_info = self.lookup_variable(obj_name) | |
| # AUTO: Checks this condition. | |
| if isinstance(var_info, str): | |
| # AUTO: Stops this flow by raising an error. | |
| raise InterpreterError(var_info, node.line) | |
| # LINE: Get bundle dictionary value. | |
| bundle_value = var_info["value"] | |
| # LINE: Member increment requires bundle object. | |
| if not isinstance(bundle_value, dict): | |
| # AUTO: Stops this flow by raising an error. | |
| raise InterpreterError(f"Runtime Error: Variable '{obj_name}' is not a bundle.", node.line) | |
| # LINE: Navigate nested bundle path before final member. | |
| for member in chain[:-1]: | |
| # AUTO: Checks this condition. | |
| if member not in bundle_value: | |
| # AUTO: Stops this flow by raising an error. | |
| raise InterpreterError(f"Runtime Error: Bundle has no member '{member}'.", node.line) | |
| # AUTO: Sets `bundle_value`. | |
| bundle_value = bundle_value[member] | |
| # AUTO: Checks this condition. | |
| if not isinstance(bundle_value, dict): | |
| # AUTO: Stops this flow by raising an error. | |
| raise InterpreterError(f"Runtime Error: Member '{member}' is not a bundle.", node.line) | |
| # LINE: Final member is incremented/decremented. | |
| final_member = chain[-1] | |
| # AUTO: Checks this condition. | |
| if final_member not in bundle_value: | |
| # AUTO: Stops this flow by raising an error. | |
| raise InterpreterError(f"Runtime Error: Bundle has no member '{final_member}'.", node.line) | |
| # LINE: Save old value for postfix result. | |
| original = bundle_value[final_member] | |
| # LINE: Compute new value depending on ++ or --. | |
| new_value = original + 1 if node.value == "++" else original - 1 | |
| # LINE: Store updated value in bundle member. | |
| bundle_value[final_member] = new_value | |
| # LINE: Postfix returns old value, prefix returns new value. | |
| return original if node.position == "post" else new_value | |
| # LINE: Simple variable unary path, not array/list access. | |
| if not isinstance(node.children[0], ListAccessNode): | |
| # LINE: Operand node stores the variable/literal being changed. | |
| operand_node = node.children[0] | |
| # LINE: For ++/-- this is the variable name. | |
| operand_name = operand_node.value | |
| # LINE: Look up variable info dictionary. | |
| var_info = self.lookup_variable(operand_name) | |
| # LINE: Increment variable path. | |
| if node.value == "++": | |
| # AUTO: Checks this condition. | |
| if isinstance(var_info, str): | |
| # AUTO: Stops this flow by raising an error. | |
| raise InterpreterError(var_info, node.line) | |
| # LINE: Prefix ++ updates first then returns new value. | |
| if node.position == "pre": | |
| # AUTO: Adds into `var_info["value"]`. | |
| var_info["value"] += 1 | |
| # AUTO: Returns this result to the caller. | |
| return var_info["value"] | |
| # AUTO: Runs when previous condition did not pass. | |
| else: | |
| # LINE: Postfix ++ returns old value then updates. | |
| original = var_info["value"] | |
| # AUTO: Adds into `var_info["value"]`. | |
| var_info["value"] += 1 | |
| # AUTO: Returns this result to the caller. | |
| return original | |
| # LINE: Decrement variable path. | |
| elif node.value == "--": | |
| # AUTO: Checks this condition. | |
| if isinstance(var_info, str): | |
| # AUTO: Stops this flow by raising an error. | |
| raise InterpreterError(var_info, node.line) | |
| # LINE: Prefix -- updates first then returns new value. | |
| if node.position == "pre": | |
| # AUTO: Subtracts from `var_info["value"]`. | |
| var_info["value"] -= 1 | |
| # AUTO: Returns this result to the caller. | |
| return var_info["value"] | |
| # AUTO: Runs when previous condition did not pass. | |
| else: | |
| # LINE: Postfix -- returns old value then updates. | |
| original = var_info["value"] | |
| # AUTO: Subtracts from `var_info["value"]`. | |
| var_info["value"] -= 1 | |
| # AUTO: Returns this result to the caller. | |
| return original | |
| # LINE: Minus operator path. | |
| elif node.value == "-": | |
| # LINE: Evaluate operand then negate it. | |
| value = self.interpret(operand_node) | |
| # AUTO: Returns this result to the caller. | |
| return -value | |
| # LINE: GAL negative operator path. | |
| elif node.value == "~": | |
| # LINE: Evaluate operand then negate it. | |
| value = self.interpret(operand_node) | |
| # AUTO: Returns this result to the caller. | |
| return -value | |
| # LINE: Logical not path. | |
| elif node.value == "!": | |
| # LINE: Evaluate operand then invert boolean truth. | |
| value = self.interpret(operand_node) | |
| # AUTO: Returns this result to the caller. | |
| return not value | |
| # AUTO: Runs when previous condition did not pass. | |
| else: | |
| # LINE: Array/list element ++/-- path, like arr[i]++. | |
| operand_node = node.children[0] | |
| # LINE: Base list variable name. | |
| list_name = operand_node.children[0].value | |
| # LINE: Index node inside brackets. | |
| index_node = operand_node.children[1] | |
| # LINE: Evaluate index expression. | |
| index = self.interpret(index_node.children[0]) | |
| # LINE: Look up list variable. | |
| list_entry = self.lookup_variable(list_name) | |
| # AUTO: Checks this condition. | |
| if isinstance(list_entry, str): | |
| # AUTO: Stops this flow by raising an error. | |
| raise InterpreterError(list_entry, node.line) | |
| # LINE: Get actual Python list value. | |
| list_value = list_entry["value"] | |
| # LINE: Index must be integer. | |
| if not isinstance(index, int): | |
| # AUTO: Stops this flow by raising an error. | |
| raise InterpreterError(f"Runtime Error: List index must be an integer. Got '{index}'", node.line) | |
| # LINE: Target variable must be a list. | |
| if not isinstance(list_value, list): | |
| # AUTO: Stops this flow by raising an error. | |
| raise InterpreterError(f"Runtime Error: Variable '{list_name}' is not a list.", node.line) | |
| # LINE: Check index bounds. | |
| if index < 0 or index >= len(list_value): | |
| # AUTO: Stops this flow by raising an error. | |
| raise InterpreterError(f"Runtime Error: Index '{index}' out of bounds for list '{list_name}'.", node.line) | |
| # LINE: Increment array element. | |
| if node.value == "++": | |
| # AUTO: Sets `original`. | |
| original = list_value[index] | |
| # AUTO: Adds into `list_value[index]`. | |
| list_value[index] += 1 | |
| # AUTO: Returns this result to the caller. | |
| return original if node.position == "post" else list_value[index] | |
| # LINE: Decrement array element. | |
| elif node.value == "--": | |
| # AUTO: Sets `original`. | |
| original = list_value[index] | |
| # AUTO: Subtracts from `list_value[index]`. | |
| list_value[index] -= 1 | |
| # AUTO: Returns this result to the caller. | |
| return original if node.position == "post" else list_value[index] | |
| # LINE: If no unary branch matched, this operator is unsupported. | |
| raise InterpreterError(f"Unknown unary operator {node.value}", node.line) | |
| # AUTO: Defines function `eval_cast`. | |
| def eval_cast(self, node): | |
| # LINE: Second child is the expression being converted. | |
| value = self.interpret(node.children[1]) | |
| # LINE: First child stores target cast type. | |
| cast_type = node.children[0].value | |
| # LINE: Convert value to seed/int. | |
| if cast_type == "seed": | |
| # AUTO: Returns this result to the caller. | |
| return int(value) | |
| # LINE: Convert value to tree/float. | |
| elif cast_type == "tree": | |
| # AUTO: Returns this result to the caller. | |
| return float(value) | |
| # LINE: Convert value to leaf/character. | |
| elif cast_type == "leaf": | |
| # LINE: Integer leaf cast uses character code. | |
| if isinstance(value, int): | |
| # AUTO: Returns this result to the caller. | |
| return chr(value) | |
| # LINE: String leaf cast takes first character or null char. | |
| return str(value)[0] if value else '\0' | |
| # LINE: Convert value to branch/bool. | |
| elif cast_type == "branch": | |
| # AUTO: Returns this result to the caller. | |
| return bool(value) | |
| # LINE: Convert value to vine/string. | |
| elif cast_type == "vine": | |
| # AUTO: Returns this result to the caller. | |
| return str(value) | |
| # AUTO: Runs when previous condition did not pass. | |
| else: | |
| # LINE: Unknown target type is runtime error. | |
| raise InterpreterError(f"Unknown cast type: {cast_type}", node.line) | |
| # AUTO: Defines function `eval_soil`. | |
| def eval_soil(self, node): | |
| # LINE: First child is the variable whose value will be lowercased. | |
| var_name = node.children[0].value | |
| # LINE: Look up the variable entry. | |
| var_info = self.lookup_variable(var_name) | |
| # LINE: Return lowercase version of the stored value. | |
| return var_info["value"].lower() # type: ignore | |
| # AUTO: Defines function `eval_bloom`. | |
| def eval_bloom(self, node): | |
| # LINE: First child is the variable whose value will be uppercased. | |
| var_name = node.children[0].value | |
| # LINE: Look up the variable entry. | |
| var_info = self.lookup_variable(var_name) | |
| # LINE: Return uppercase version of the stored value. | |
| return var_info["value"].upper() # type: ignore | |
| # AUTO: Defines function `eval_if_statement`. | |
| def eval_if_statement(self, node): | |
| # LINE: Evaluate spring condition from first child. | |
| condition_result = self.interpret(node.children[0].children[0]) | |
| # LINE: Create local scope for this if/else chain. | |
| self.enter_scope() | |
| # AUTO: Starts protected code that can catch errors. | |
| try: | |
| # LINE: If spring condition is True, run spring block. | |
| if condition_result: | |
| # AUTO: Calls `self.eval_block`. | |
| self.eval_block(node.children[1]) | |
| # AUTO: Runs when previous condition did not pass. | |
| else: | |
| # LINE: Start checking children after spring condition/block. | |
| current_node = 2 | |
| # LINE: Walk bud/wither nodes until one runs or list ends. | |
| while current_node < len(node.children): | |
| # LINE: Current child can be ElseIfStatement or ElseStatement. | |
| elif_node = node.children[current_node] | |
| # LINE: bud condition path. | |
| if elif_node.node_type == "ElseIfStatement": | |
| # LINE: Evaluate bud condition. | |
| elif_condition_result = self.interpret(elif_node.children[0].children[0]) | |
| # LINE: bud condition must be branch/bool. | |
| if not isinstance(elif_condition_result, bool): | |
| # AUTO: Stops this flow by raising an error. | |
| raise InterpreterError(f"Runtime Error: Condition must be a boolean. Got '{condition_result}'", node.line) | |
| # LINE: If bud is true, run its block and stop the chain. | |
| if elif_condition_result: | |
| # AUTO: Starts protected code that can catch errors. | |
| try: | |
| # LINE: bud block gets its own local scope. | |
| self.enter_scope() | |
| # LINE: Execute bud block. | |
| self.eval_block(elif_node.children[1]) | |
| # AUTO: Runs cleanup code no matter what happened. | |
| finally: | |
| # LINE: Leave bud local scope. | |
| self.exit_scope() | |
| # AUTO: Returns this result to the caller. | |
| return | |
| # LINE: wither/else path. | |
| elif elif_node.node_type == "ElseStatement": | |
| # AUTO: Starts protected code that can catch errors. | |
| try: | |
| # LINE: wither block gets its own local scope. | |
| self.enter_scope() | |
| # LINE: Execute wither block. | |
| self.eval_block(elif_node.children[0]) | |
| # AUTO: Runs cleanup code no matter what happened. | |
| finally: | |
| # LINE: Leave wither local scope. | |
| self.exit_scope() | |
| # AUTO: Returns this result to the caller. | |
| return | |
| # LINE: Move to next bud/wither child. | |
| current_node += 1 | |
| # AUTO: Runs cleanup code no matter what happened. | |
| finally: | |
| # LINE: Always leave spring chain scope. | |
| self.exit_scope() | |
| # LINE: If no block ran, return no value. | |
| return None | |
| # AUTO: Defines function `eval_for_loop`. | |
| def eval_for_loop(self, node): | |
| # GUIDE: cultivate flow; initialize once, check condition, run block, | |
| # apply update expressions, then repeat. | |
| # LINE: Mark that execution is inside a cultivate loop. | |
| self.enter_loop('for') | |
| # LINE: Create loop-local scope. | |
| self.enter_scope() | |
| # LINE: Safety limit to prevent infinite loops. | |
| MAX_LOOP_ITERATIONS = 10000 | |
| # LINE: Counts how many loop iterations already ran. | |
| LOOP_COUNTER = 0 | |
| # AUTO: Starts protected code that can catch errors. | |
| try: | |
| # LINE: First child is the initializer part of cultivate. | |
| instantiate_node = node.children[0] | |
| # AUTO: Checks this condition. | |
| if isinstance(instantiate_node, VariableDeclarationNode): | |
| # First part of cultivate: seed i = 0 | |
| # AUTO: Sets `var_type`. | |
| var_type = instantiate_node.children[0].value | |
| # AUTO: Sets `var_name`. | |
| var_name = instantiate_node.children[1].value | |
| # AUTO: Sets `initial_value_node`. | |
| initial_value_node = self.interpret(instantiate_node.children[2]) | |
| # AUTO: Calls `self.declare_variable`. | |
| self.declare_variable(var_name, var_type, initial_value_node) | |
| # AUTO: Checks the next alternate condition. | |
| elif isinstance(instantiate_node, AssignmentNode): | |
| # First part of cultivate: i = 0 | |
| # AUTO: Sets `var_name`. | |
| var_name = instantiate_node.children[0].value | |
| # AUTO: Sets `initial_value_node`. | |
| initial_value_node = self.interpret(instantiate_node.children[1]) | |
| # AUTO: Sets `self.lookup_variable(var_name)["value"]`. | |
| self.lookup_variable(var_name)["value"] = initial_value_node # type: ignore | |
| # LINE: Second child is the loop condition. | |
| condition_node = node.children[1].children[0] | |
| # Second part of cultivate: evaluate condition such as i <= n. | |
| # LINE: Evaluate condition before the first iteration. | |
| condition_result = self.interpret(condition_node) | |
| # LINE: Loop condition must evaluate to branch/bool. | |
| if not isinstance(condition_result, bool): | |
| # AUTO: Stops this flow by raising an error. | |
| raise InterpreterError(f"Runtime Error: Condition must be a boolean. Got '{condition_result}'", node.line) | |
| # LINE: Keep running while condition is sunshine/True. | |
| while condition_result: | |
| # AUTO: Adds into `LOOP_COUNTER`. | |
| LOOP_COUNTER += 1 | |
| # AUTO: Checks this condition. | |
| if LOOP_COUNTER > MAX_LOOP_ITERATIONS: | |
| # AUTO: Stops this flow by raising an error. | |
| raise InterpreterError("Runtime Error: Infinite loop detected!", node.line) | |
| # LINE: Execute the loop body block. | |
| self.eval_block(node.children[3]) | |
| # AUTO: Checks this condition. | |
| if self.continue_flag: | |
| # LINE: skip clears here before updates/next condition. | |
| self.continue_flag = False | |
| # AUTO: Checks this condition. | |
| if self.break_triggered(): | |
| # LINE: prune stops the loop immediately. | |
| break | |
| # LINE: Run update expressions after each iteration. | |
| for update_expr in node.children[2].children: | |
| # Third part of cultivate: apply update such as i++. | |
| # AUTO: Dispatches an AST node for execution. | |
| self.interpret(update_expr) | |
| # Re-check the loop condition for the next iteration. | |
| # LINE: Re-evaluate condition to decide if loop continues. | |
| condition_result = self.interpret(condition_node) | |
| # AUTO: Runs cleanup code no matter what happened. | |
| finally: | |
| # LINE: Always remove loop scope after loop ends/errors. | |
| self.exit_scope() | |
| # LINE: Always clear loop tracking after loop ends/errors. | |
| self.exit_loop() | |
| # AUTO: Defines function `eval_while_loop`. | |
| def eval_while_loop(self, node): | |
| # GUIDE: grow checks the branch condition before each block execution. | |
| # LINE: Mark that execution is inside a grow loop. | |
| self.enter_loop('while') | |
| # LINE: Create local loop scope. | |
| self.enter_scope() | |
| # LINE: Safety limit to avoid infinite grow loops. | |
| MAX_LOOP_ITERATIONS = 10000 | |
| # LINE: Count loop iterations. | |
| LOOP_COUNTER = 0 | |
| # LINE: First child stores grow condition expression. | |
| condition_node = node.children[0].children[0] | |
| # AUTO: Starts protected code that can catch errors. | |
| try: | |
| # Evaluate grow(condition) before the first iteration. | |
| # LINE: Evaluate condition before entering body. | |
| condition_result = self.interpret(condition_node) | |
| # LINE: grow condition must be branch/bool. | |
| if not isinstance(condition_result, bool): | |
| # AUTO: Stops this flow by raising an error. | |
| raise InterpreterError(f"Runtime Error: Condition must be a boolean. Got '{condition_result}'", node.line) | |
| # LINE: Continue loop while condition is sunshine/True. | |
| while condition_result: | |
| # AUTO: Adds into `LOOP_COUNTER`. | |
| LOOP_COUNTER += 1 | |
| # AUTO: Checks this condition. | |
| if LOOP_COUNTER > MAX_LOOP_ITERATIONS: | |
| # AUTO: Stops this flow by raising an error. | |
| raise InterpreterError("Runtime Error: Infinite loop detected!", node.line) | |
| # LINE: Second child is the grow body block. | |
| block_node = node.children[1] | |
| # LINE: Execute grow body once. | |
| self.eval_block(block_node) | |
| # AUTO: Checks this condition. | |
| if self.continue_flag: | |
| # LINE: skip resets before next condition check. | |
| self.continue_flag = False | |
| # AUTO: Checks this condition. | |
| if self.break_triggered(): | |
| # LINE: prune exits the grow loop. | |
| break | |
| # Re-evaluate the condition after the block. If false, loop stops. | |
| # LINE: Check condition again after the body. | |
| condition_result = self.interpret(condition_node) | |
| # AUTO: Runs cleanup code no matter what happened. | |
| finally: | |
| # LINE: Clear loop tracking. | |
| self.exit_loop() | |
| # LINE: Remove loop-local scope. | |
| self.exit_scope() | |
| # AUTO: Defines function `eval_do_while_loop`. | |
| def eval_do_while_loop(self, node): | |
| # AUTO: Calls `self.enter_loop`. | |
| self.enter_loop('do-while') | |
| # AUTO: Sets `MAX_LOOP_ITERATIONS`. | |
| MAX_LOOP_ITERATIONS = 10000 | |
| # AUTO: Sets `LOOP_COUNTER`. | |
| LOOP_COUNTER = 0 | |
| # AUTO: Sets `condition_node`. | |
| condition_node = node.children[1].children[0] | |
| # AUTO: Sets `block_node`. | |
| block_node = node.children[0] | |
| # AUTO: Starts protected code that can catch errors. | |
| try: | |
| # AUTO: Repeats while this condition is true. | |
| while True: | |
| # AUTO: Calls `self.eval_block`. | |
| self.eval_block(block_node) | |
| # AUTO: Adds into `LOOP_COUNTER`. | |
| LOOP_COUNTER += 1 | |
| # AUTO: Checks this condition. | |
| if LOOP_COUNTER > MAX_LOOP_ITERATIONS: | |
| # AUTO: Stops this flow by raising an error. | |
| raise InterpreterError("Runtime Error: Infinite loop detected!", node.line) | |
| # AUTO: Checks this condition. | |
| if self.continue_flag: | |
| # AUTO: Sets `self.continue_flag`. | |
| self.continue_flag = False | |
| # AUTO: Checks this condition. | |
| if self.break_triggered(): | |
| # AUTO: Stops the nearest loop. | |
| break | |
| # AUTO: Sets `condition_result`. | |
| condition_result = self.interpret(condition_node) | |
| # AUTO: Checks this condition. | |
| if not isinstance(condition_result, bool): | |
| # AUTO: Stops this flow by raising an error. | |
| raise InterpreterError(f"Runtime Error: Condition must be a boolean. Got '{condition_result}'", node.line) | |
| # AUTO: Checks this condition. | |
| if not condition_result: | |
| # AUTO: Stops the nearest loop. | |
| break | |
| # AUTO: Runs cleanup code no matter what happened. | |
| finally: | |
| # AUTO: Calls `self.exit_loop`. | |
| self.exit_loop() | |
| # AUTO: Defines function `eval_break`. | |
| def eval_break(self, node): | |
| # LINE: prune is legal only when loop_stack is not empty. | |
| if self.loop_stack: | |
| # LINE: Set break flag so the loop can stop. | |
| self.trigger_break() | |
| # AUTO: Runs when previous condition did not pass. | |
| else: | |
| # LINE: Using prune outside loop/switch is a runtime error. | |
| raise InterpreterError("Runtime Error: Break statement used outside of a loop", node.line) | |
| # AUTO: Defines function `trigger_break`. | |
| def trigger_break(self): | |
| # LINE: Mark that current loop should stop. | |
| self.break_flag = True | |
| # AUTO: Defines function `break_triggered`. | |
| def break_triggered(self): | |
| # LINE: Return whether prune was triggered. | |
| return self.break_flag | |
| # AUTO: Defines function `enter_loop`. | |
| def enter_loop(self, loop_type): | |
| # LINE: Push loop type so prune/skip know we are inside a loop/switch. | |
| self.loop_stack.append(loop_type) | |
| # LINE: Reset prune flag for new loop. | |
| self.break_flag = False | |
| # LINE: Reset skip flag for new loop. | |
| self.continue_flag = False | |
| # AUTO: Defines function `exit_loop`. | |
| def exit_loop(self): | |
| # LINE: Only pop when a loop/switch context exists. | |
| if self.loop_stack: | |
| # LINE: Remove current loop/switch context. | |
| self.loop_stack.pop() | |
| # LINE: Clear prune after leaving loop. | |
| self.break_flag = False | |
| # LINE: Clear skip after leaving loop. | |
| self.continue_flag = False | |
| # AUTO: Defines function `eval_continue`. | |
| def eval_continue(self, node): | |
| # LINE: skip is legal only inside a loop. | |
| if self.loop_stack: | |
| # LINE: Set skip flag. | |
| self.trigger_continue() | |
| # AUTO: Runs when previous condition did not pass. | |
| else: | |
| # LINE: skip outside loop is runtime error. | |
| raise InterpreterError("Runtime Error: Continue statement used outside of a loop", node.line) | |
| # AUTO: Defines function `continue_triggered`. | |
| def continue_triggered(self): | |
| # LINE: Return whether skip was triggered. | |
| return self.continue_flag | |
| # AUTO: Defines function `trigger_continue`. | |
| def trigger_continue(self): | |
| # LINE: Mark that current loop should skip to next iteration. | |
| self.continue_flag = True | |
| # AUTO: Defines function `eval_switch`. | |
| def eval_switch(self, node): | |
| # LINE: harvest behaves like a switch context for prune. | |
| self.enter_loop('switch') | |
| # LINE: Create local scope for switch execution. | |
| self.enter_scope() | |
| # LINE: First child is the switch expression. | |
| switch_expr_node = node.children[0] | |
| # LINE: Evaluate switch expression once. | |
| switch_value = self.interpret(switch_expr_node) | |
| # LINE: Tracks if a matching variety case has been found. | |
| matched_case = False | |
| # LINE: Tracks if prune stopped case execution. | |
| break_found = False | |
| # LINE: Stores soil/default block if present. | |
| default_case = None | |
| # AUTO: Starts protected code that can catch errors. | |
| try: | |
| # LINE: Visit every variety/soil child after switch expression. | |
| for case_node in node.children[1:]: | |
| # LINE: Case node type tells variety or soil/default. | |
| label_type = case_node.node_type | |
| # LINE: variety case path. | |
| if label_type == "Case": | |
| # LINE: First case child is case literal/expression. | |
| case_value_node = case_node.children[0] | |
| # LINE: Second case child is block to run. | |
| block_node = case_node.children[1] | |
| # LINE: Evaluate case value for comparison. | |
| case_value = self.interpret(case_value_node) | |
| # LINE: Run this block if value matches or fall-through already started. | |
| if switch_value == case_value or matched_case: | |
| # LINE: Mark that switch found a matching case. | |
| matched_case = True | |
| # AUTO: Starts protected code that can catch errors. | |
| try: | |
| # LINE: Each case block gets its own local scope. | |
| self.enter_scope() | |
| # LINE: Execute case statements. | |
| self.eval_block(block_node) | |
| # LINE: Stop switch if prune was triggered. | |
| if self.break_triggered(): | |
| # AUTO: Stops the nearest loop. | |
| break_found = True | |
| # AUTO: Stops the nearest loop. | |
| break | |
| # AUTO: Runs cleanup code no matter what happened. | |
| finally: | |
| # LINE: Leave case scope. | |
| self.exit_scope() | |
| # LINE: soil/default case path. | |
| elif label_type == "Default": | |
| # LINE: Save default block to run later if no case matched. | |
| default_case = case_node.children[0] | |
| # LINE: Run soil/default only if no variety matched and no prune happened. | |
| if not matched_case and not break_found and default_case: | |
| # AUTO: Starts protected code that can catch errors. | |
| try: | |
| # LINE: Default block gets its own local scope. | |
| self.enter_scope() | |
| # LINE: Execute soil/default statements. | |
| self.eval_block(default_case) | |
| # AUTO: Runs cleanup code no matter what happened. | |
| finally: | |
| # LINE: Leave default scope. | |
| self.exit_scope() | |
| # AUTO: Runs cleanup code no matter what happened. | |
| finally: | |
| # LINE: Leave switch/prune context. | |
| self.exit_loop() | |
| # LINE: Leave switch local scope. | |
| self.exit_scope() | |
| # AUTO: Defines function `emit_input_request`. | |
| def emit_input_request(self, var_name, prompt): | |
| # LINE: Tell frontend to show input prompt for water(). | |
| self.socketio.emit('input_required', {'prompt': prompt, 'variable': var_name}) | |
| # AUTO: Defines function `provide_input`. | |
| def provide_input(self, var_name, input_value): | |
| # LINE: Get waiting event for this water() variable. | |
| evt = self.input_events.get(var_name) | |
| # LINE: If interpreter is not waiting yet, store input for later. | |
| if evt is None: | |
| # AUTO: Sets `self.input_values[var_name]`. | |
| self.input_values[var_name] = input_value | |
| # AUTO: Returns this result to the caller. | |
| return | |
| # LINE: Eventlet mode resumes the waiting green thread. | |
| if _USE_EVENTLET: | |
| # AUTO: Calls `evt.send`. | |
| evt.send(input_value) | |
| # AUTO: Runs when previous condition did not pass. | |
| else: | |
| # LINE: Threading mode stores value and releases wait(). | |
| self.input_values[var_name] = input_value | |
| # AUTO: Calls `evt.set`. | |
| evt.set() | |
| # AUTO: Defines function `wait_for_input`. | |
| def wait_for_input(self, var_name): | |
| # LINE: If input arrived early, consume it immediately. | |
| if var_name in self.input_values: | |
| # AUTO: Returns this result to the caller. | |
| return self.input_values.pop(var_name) | |
| # LINE: Eventlet waiting path used by Socket.IO server. | |
| if _USE_EVENTLET: | |
| # LINE: Create event object that pauses execution. | |
| evt = _ev.Event() | |
| # LINE: Store event so provide_input can resume it. | |
| self.input_events[var_name] = evt | |
| # LINE: Pause here until frontend sends input. | |
| value = evt.wait() | |
| # LINE: Remove event after input arrives. | |
| self.input_events.pop(var_name, None) | |
| # LINE: Stop if execution was cancelled while waiting. | |
| if getattr(self, '_cancelled', False): | |
| # AUTO: Stops this flow by raising an error. | |
| raise _CancelledError() | |
| # LINE: Return received input value. | |
| return value | |
| # AUTO: Runs when previous condition did not pass. | |
| else: | |
| # LINE: Standard threading waiting path. | |
| event = threading.Event() | |
| # LINE: Store event so provide_input can set it. | |
| self.input_events[var_name] = event | |
| # LINE: Pause here until event.set(). | |
| event.wait() | |
| # LINE: Stop if execution was cancelled while waiting. | |
| if getattr(self, '_cancelled', False): | |
| # AUTO: Stops this flow by raising an error. | |
| raise _CancelledError() | |
| # LINE: Read input value sent by frontend. | |
| value = self.input_values.pop(var_name, None) | |
| # LINE: Remove finished event. | |
| self.input_events.pop(var_name, None) | |
| # LINE: Return received input value. | |
| return value | |
| # AUTO: Defines function `eval_input`. | |
| def eval_input(self, node): | |
| # GUIDE: water() finds target variable/type from parent node, asks the | |
| # UI for a value, then converts that value before assignment. | |
| # LINE: Parent tells whether water() is declaration, assignment, or expression. | |
| parent_node = node.parent | |
| # LINE: Case seed n = water(seed); | |
| if isinstance(parent_node, VariableDeclarationNode): | |
| # Case: seed n = water(seed); | |
| # AUTO: Sets `var_name`. | |
| var_name = parent_node.children[1].value | |
| # AUTO: Sets `var_type`. | |
| var_type = parent_node.children[0].value | |
| # LINE: Case water(n); or n = water(seed); | |
| elif isinstance(parent_node, AssignmentNode): | |
| # Case: water(n); or n = water(seed); | |
| # AUTO: Sets `target`. | |
| target = parent_node.children[0] | |
| # LINE: Array input target path like arr[i]. | |
| if isinstance(target, ListAccessNode): | |
| # AUTO: Sets `current`. | |
| current = target | |
| # AUTO: Repeats while this condition is true. | |
| while hasattr(current, 'node_type') and current.node_type == "ListAccess": | |
| # AUTO: Sets `current`. | |
| current = current.children[0].value | |
| # AUTO: Sets `var_name`. | |
| var_name = current if isinstance(current, str) else str(current) | |
| # AUTO: Sets `var_type`. | |
| var_type = self.lookup_variable(var_name)["type"] # type: ignore | |
| # AUTO: Runs when previous condition did not pass. | |
| else: | |
| # LINE: Simple variable input target path. | |
| var_name = target.value | |
| # AUTO: Sets `var_type`. | |
| var_type = self.lookup_variable(var_name)["type"] # type: ignore | |
| # AUTO: Runs when previous condition did not pass. | |
| else: | |
| # Case: water(seed) used directly as an expression. | |
| # LINE: Expression water() has no variable target, so use temporary name. | |
| var_name = "_input" | |
| # AUTO: Checks this condition. | |
| if node.value and "(" in node.value: | |
| # LINE: Extract requested input type from water(seed/tree/etc.). | |
| inner = node.value.split("(")[1].rstrip(")") | |
| # AUTO: Sets `var_type`. | |
| var_type = inner if inner in {"seed", "tree", "leaf", "branch", "vine"} else "vine" | |
| # AUTO: Runs when previous condition did not pass. | |
| else: | |
| # LINE: Plain water() defaults to vine/string input. | |
| var_type = "vine" | |
| # LINE: Prompt text sent to UI. | |
| prompt = f"Input for {var_name}: " | |
| # LINE: Mark interpreter as waiting for input. | |
| self.input_required = True | |
| # Ask the UI/browser for input and wait until capture_input sends it. | |
| # LINE: Send input_required event to frontend. | |
| self.emit_input_request(var_name, prompt) | |
| # LINE: Pause execution until frontend sends input. | |
| input_value = self.wait_for_input(var_name) | |
| # LINE: Mark input wait as finished. | |
| self.input_required = False | |
| # LINE: Convert user text into seed integer when needed. | |
| if var_type == "seed": | |
| # AUTO: Sets `original_input`. | |
| original_input = input_value | |
| # AUTO: Checks this condition. | |
| if isinstance(input_value, str) and input_value.startswith('-'): | |
| # AUTO: Stops this flow by raising an error. | |
| raise InterpreterError(f"Runtime Error: GAL uses '~' for negative numbers, not '-'. Got '{original_input}'; did you mean '~{original_input[1:]}'?", node.line) # type: ignore | |
| # AUTO: Checks this condition. | |
| if isinstance(input_value, str) and input_value.startswith('~'): | |
| # AUTO: Sets `input_value`. | |
| input_value = '-' + input_value[1:] | |
| # AUTO: Starts protected code that can catch errors. | |
| try: | |
| # AUTO: Checks this condition. | |
| if len(input_value.strip('-').lstrip('0')) > 16: | |
| # AUTO: Stops this flow by raising an error. | |
| raise InterpreterError(f"Runtime Error: Input value exceeds maximum number of 16 digits", node.line) | |
| # AUTO: Sets `input_value`. | |
| input_value = int(float(input_value)) # type: ignore | |
| # AUTO: Handles the matching error case. | |
| except ValueError: | |
| # AUTO: Stops this flow by raising an error. | |
| raise InterpreterError(f"Runtime Error: Expected integer value, got '{original_input}'", node.line) | |
| # AUTO: Checks the next alternate condition. | |
| elif var_type == "tree": | |
| # AUTO: Sets `original_input`. | |
| original_input = input_value | |
| # AUTO: Checks this condition. | |
| if isinstance(input_value, str) and input_value.startswith('-'): | |
| # AUTO: Stops this flow by raising an error. | |
| raise InterpreterError(f"Runtime Error: GAL uses '~' for negative numbers, not '-'. Got '{original_input}'; did you mean '~{original_input[1:]}'?", node.line) # type: ignore | |
| # AUTO: Checks this condition. | |
| if isinstance(input_value, str) and input_value.startswith('~'): | |
| # AUTO: Sets `input_value`. | |
| input_value = '-' + input_value[1:] | |
| # AUTO: Starts protected code that can catch errors. | |
| try: | |
| # AUTO: Checks this condition. | |
| if '.' in input_value: # type: ignore | |
| # AUTO: Sets `integer_part, decimal_part`. | |
| integer_part, decimal_part = str(input_value).split('.') | |
| # AUTO: Checks this condition. | |
| if len(integer_part.strip('-').lstrip('0')) > 16: | |
| # AUTO: Stops this flow by raising an error. | |
| raise InterpreterError(f"Runtime Error: Input value exceeds maximum number of 16 digits", node.line) | |
| # AUTO: Checks this condition. | |
| if len(decimal_part.rstrip('0')) > 5: | |
| # AUTO: Stops this flow by raising an error. | |
| raise InterpreterError(f"Runtime Error: Input value exceeds maximum number of 5 decimal numbers", node.line) | |
| # AUTO: Runs when previous condition did not pass. | |
| else: | |
| # AUTO: Checks this condition. | |
| if len(input_value.strip('-').lstrip('0')) > 16: | |
| # AUTO: Stops this flow by raising an error. | |
| raise InterpreterError(f"Runtime Error: Input value exceeds maximum number of 16 digits", node.line) | |
| # AUTO: Sets `input_value`. | |
| input_value = float(input_value) # type: ignore | |
| # AUTO: Handles the matching error case. | |
| except ValueError: | |
| # AUTO: Stops this flow by raising an error. | |
| raise InterpreterError(f"Runtime Error: Expected float value, got '{original_input}'", node.line) | |
| # AUTO: Checks the next alternate condition. | |
| elif var_type == "branch": | |
| # AUTO: Checks this condition. | |
| if input_value == "true" or input_value == "false": | |
| # AUTO: Executes this statement. | |
| suggestion = "sunshine" if input_value == "true" else "frost" | |
| # AUTO: Stops this flow by raising an error. | |
| raise InterpreterError(f"Runtime Error: GAL uses 'sunshine' and 'frost' for booleans, not 'true'/'false'. Got '{input_value}'; did you mean '{suggestion}'?", node.line) | |
| # AUTO: Checks this condition. | |
| if input_value == "sunshine": | |
| # AUTO: Sets `input_value`. | |
| input_value = True | |
| # AUTO: Checks the next alternate condition. | |
| elif input_value == "frost": | |
| # AUTO: Sets `input_value`. | |
| input_value = False | |
| # AUTO: Runs when previous condition did not pass. | |
| else: | |
| # AUTO: Stops this flow by raising an error. | |
| raise InterpreterError(f"Runtime Error: expected branch value (sunshine/frost), got '{input_value}'", node.line) | |
| # AUTO: Checks the next alternate condition. | |
| elif var_type == "leaf": | |
| # AUTO: Checks this condition. | |
| if len(input_value) != 1: # type: ignore | |
| # AUTO: Stops this flow by raising an error. | |
| raise InterpreterError(f"Runtime Error: Expected a single character for leaf, got '{input_value}'", node.line) | |
| # AUTO: Sets `input_value`. | |
| input_value = str(input_value) | |
| # AUTO: Checks the next alternate condition. | |
| elif var_type == "vine": | |
| # AUTO: Sets `input_value`. | |
| input_value = str(input_value) | |
| # AUTO: Returns this result to the caller. | |
| return input_value | |