← Execution Model | Sandbox → |
---|---|
Dana Interpreter¶
Module: opendxa.dana.sandbox.interpreter
Given the program AST after transformation (and optional type checking), we are ready to execute the program. See Type System and Casting for more details.
This document describes the architecture, responsibilities, and flow of the Dana Interpreter, which is responsible for executing Dana programs by traversing the AST and managing sandbox context. Refer to the Execution Model for an overview of how the interpreter fits into the broader execution flow.
Overview¶
The Dana Interpreter has been significantly refactored into a modular, unified execution architecture. It executes Dana programs by processing the Abstract Syntax Tree (AST) through specialized executor components, treating all nodes as expressions that produce values while handling their statement-like side effects.
Architecture¶
The interpreter uses a unified execution model where every AST node is treated as an expression that produces a value. This provides consistency and simplifies the execution logic while maintaining support for statements that have side effects.
Key Design Principles¶
- Unified Execution: All nodes go through a single
execute()
method - Modular Executors: Specialized executors handle different node types
- Value-First: Every node evaluation produces a value
- Dispatcher Pattern: Node types are mapped to specialized handlers
Main Components¶
Core Interpreter¶
- DanaInterpreter: Main entry point that initializes the execution environment, manages the function registry, and coordinates with the unified executor
- DanaExecutor: Central execution engine that dispatches to specialized executors based on node type
Specialized Executors¶
- ExpressionExecutor: Handles expressions (arithmetic, logical, identifiers, literals, function calls)
- StatementExecutor: Executes statements (assignments, conditionals, loops)
- ControlFlowExecutor: Manages control flow (if/else, while, for, return, break, continue)
- CollectionExecutor: Handles collections and f-string expressions
- FunctionExecutor: Manages function definitions and calls (see also Functions and Polymorphism)
- ProgramExecutor: Executes complete programs and statement blocks
Supporting Infrastructure¶
- BaseExecutor: Base class providing common functionality for all executors
- FunctionRegistry: Unified registry for Dana and Python functions with namespacing support.
- SandboxContext: Provides execution context, variable scope management (see State and Scopes and Sandbox), and access to LLM resources.
- Hooks: Extensible hook system for monitoring and extending execution
Execution Flow¶
graph TB
AST[[AST Node]] --> DI[DanaInterpreter]
DI --> DE[DanaExecutor]
DE --> Dispatch{Node Type}
subgraph SEG [Specialized Executors]
direction TB
SC[SandboxContext]
FR[FunctionRegistry]
EE[ExpressionExecutor]
EE --> ER[[Expression Result]]
CE[CollectionExecutor]
CE --> CoR[[Collection/String]]
FE[FunctionExecutor]
FE --> FuR[[Function Result]]
PE[ProgramExecutor]
PE --> Hooks[Hook System]
PE --> PR[[Program Result]]
SE[StatementExecutor]
SE --> SR[[Statement Result]]
CFE[ControlFlowExecutor]
CFE --> CR[[Control Flow Result]]
end
Dispatch --> SEG
style AST fill:#e1f5fe
style DE fill:#f3e5f5
style ER fill:#e8f5e8
style SR fill:#e8f5e8
style CR fill:#e8f5e8
style CoR fill:#e8f5e8
style FuR fill:#e8f5e8
style PR fill:#e8f5e8
Execution Steps¶
- AST Node: Any AST node from the parser (statement, expression, program)
- DanaInterpreter: Entry point that manages context and delegates to DanaExecutor
- DanaExecutor: Central dispatcher that routes nodes to appropriate specialized executors
- Specialized Executors: Handle specific node types using their domain knowledge
- Supporting Services: Function registry, context management, hooks provide infrastructure
- Results: Each executor produces appropriate results (expressions return values, statements may return None but have side effects)
Key Features¶
Unified Execution Model¶
- Single Entry Point: All nodes execute through
DanaExecutor.execute()
- Consistent Interface: Every node produces a value, simplifying chaining and composition
- Type Dispatch: Automatic routing to appropriate specialized executors
Function System Integration¶
- Unified Function Registry: Supports both Dana and Python functions
- Namespacing: Functions can be organized into namespaces (e.g.,
math.sin
) - Context Injection: Automatic context passing to functions that need it
- Cross-Language Calls: Seamless calling between Dana and Python
Modular Architecture¶
- Specialized Executors: Each executor handles a specific domain (expressions, control flow, etc.)
- Inheritance Hierarchy: All executors inherit from
BaseExecutor
for consistency - Handler Registration: Dynamic registration of node type handlers
Error Handling and Diagnostics¶
- Improved Error Messages: User-friendly error formatting with context
- Execution Path Tracking: Debugging support with execution path information
- Exception Handling: Proper handling of control flow exceptions (return, break, continue). See also Error Handling.
Example Usage¶
Basic Program Execution¶
from opendxa.dana.sandbox.parser.dana_parser import DanaParser # Assuming parser details
from opendxa.dana.sandbox.interpreter.dana_interpreter import DanaInterpreter
from opendxa.dana.sandbox.sandbox_context import SandboxContext
# Parse Dana code (refer to Parser documentation)
# parser = DanaParser()
# result = parser.parse("private:x = 10\nif private:x > 5:\n log('Value is greater than 5')") # using log instead of print
#
# if result.is_valid:
# # Create context and interpreter
# context = SandboxContext()
# interpreter = DanaInterpreter(context)
#
# # Execute the program
# output = interpreter.execute_program(result.program)
#
# # Get any logged output (assuming a mechanism for this, e.g., from context or a log handler)
# # logged_output = context.get_logs() # Example
# print("Execution result:", output)
# # print("Program output:", logged_output)
# else:
# print("Parse errors:", result.errors)
print
is not a standard Dana function; log
or similar should be used.
Single Statement Execution¶
# # Execute a single statement
# stmt_result = parser.parse("private:result = 42 * 2")
# if stmt_result.is_valid:
# value = interpreter.execute_statement(stmt_result.program, context) # context might be passed differently
# print("Statement result:", value)
# print("Variable value:", context.get("private:result"))
Expression Evaluation¶
# # Evaluate an expression
# expr_result = parser.parse("10 + 20 * 3")
# if expr_result.is_valid:
# value = interpreter.evaluate_expression(expr_result.program, context) # context might be passed differently
# print("Expression value:", value) # Output: 70
Advanced Features¶
Function Registration and Calling¶
# Register a Python function
# def my_function(a, b):
# return a + b
#
# interpreter.function_registry.register(
# "add", my_function, namespace="math", func_type="python"
# )
# Call from Dana code
# dana_code_calling_add = "local:sum = math:add(5, 3)"
# result = parser.parse(dana_code_calling_add)
# if result.is_valid:
# interpreter.execute_program(result.program, context)
# print("Sum from Dana:", context.get("local:sum"))
FunctionRegistry
is a key component. The specifics of registering and how namespaces are handled (e.g., math:add
vs math.add
) would be detailed in its own documentation or within the Functions and Polymorphism section.
Self-reflection: This document needs an accompanying ast.md
for full context. The examples involving parser.parse
and direct execution methods like execute_statement
or evaluate_expression
should be harmonized with the final API of the Dana Sandbox/Runtime. The print()
in Dana examples was replaced with log()
or comments about logging, as print
isn't a standard Dana function.