Skip to content

Evaluator API Reference

The core module (jsl.core) contains the main evaluation engine for JSL expressions.

Main Evaluator Class

Evaluator

The main evaluator class for JSL expressions.

jsl.core.Evaluator(host_dispatcher=None, resource_limits=None, host_gas_policy=None)

The core JSL evaluator - recursive evaluation engine.

This is a clean, elegant reference implementation that uses traditional recursive tree-walking to evaluate S-expressions. It serves as the specification for JSL's semantics.

Characteristics: - Simple and easy to understand - Direct mapping from S-expressions to evaluation - Perfect for learning and testing JSL semantics - Limited by Python's recursion depth for deep expressions

For production use with resumption and better performance, use the stack-based evaluator which compiles to JPN (JSL Postfix Notation).

Source code in jsl/core.py
def __init__(self, host_dispatcher: Optional[HostDispatcher] = None, 
             resource_limits: Optional[ResourceLimits] = None,
             host_gas_policy: Optional['HostGasPolicy'] = None):
    self.host = host_dispatcher or HostDispatcher()
    self.resources = ResourceBudget(resource_limits, host_gas_policy) if resource_limits else None

eval(expr, env)

Evaluate a JSL expression in the given environment.

This is a pure recursive evaluator without resumption support. For resumable evaluation, use the stack-based evaluator.

Source code in jsl/core.py
def eval(self, expr: JSLExpression, env: Env) -> JSLValue:
    """
    Evaluate a JSL expression in the given environment.

    This is a pure recursive evaluator without resumption support.
    For resumable evaluation, use the stack-based evaluator.
    """
    # Resource checking
    if self.resources:
        # Check time periodically
        self.resources.check_time()

        # Consume gas based on expression type
        if isinstance(expr, (int, float, bool)) or expr is None:
            self.resources.consume_gas(GasCost.LITERAL)
        elif isinstance(expr, str):
            if expr.startswith("@"):
                self.resources.consume_gas(GasCost.LITERAL)
            else:
                self.resources.consume_gas(GasCost.VARIABLE)
        elif isinstance(expr, dict):
            self.resources.consume_gas(GasCost.DICT_CREATE + 
                                      len(expr) * GasCost.DICT_PER_ITEM)

    # Literals: numbers, booleans, null, objects
    if isinstance(expr, (int, float, bool)) or expr is None:
        return expr

    # Objects: evaluate both keys and values, keys must be strings
    if isinstance(expr, dict):
        return self._eval_dict(expr, env)

    # Strings: variables or string literals
    if isinstance(expr, str):
        return self._eval_string(expr, env)

    # Arrays: function calls or special forms
    if isinstance(expr, list):
        return self._eval_list(expr, env)

    raise JSLTypeError(f"Cannot evaluate expression of type {type(expr)}")

Overview

The evaluator implements JSL's core evaluation semantics:

  • Expressions: Everything in JSL is an expression that evaluates to a value
  • Environments: Lexical scoping with nested environment chains
  • Host Commands: Bidirectional communication with the host system
  • Tail Call Optimization: Efficient recursion handling

Evaluation Rules

Literals

  • Numbers: 42, 3.14 evaluate to themselves
  • Strings: "@hello" evaluates to the literal string "hello"
  • Booleans: true, false evaluate to themselves
  • null: null evaluates to itself

Variables

Variable references are resolved through the environment chain:

["let", {"x": 42}, "x"]

Special Forms

  • let: Creates local bindings
  • def: Defines variables in the current environment
  • lambda: Creates function closures
  • if: Conditional evaluation
  • do: Sequential execution
  • quote: Prevents evaluation
  • host: Executes host commands

Function Calls

Regular function calls use list syntax:

["func", "arg1", "arg2"]

Where func evaluates to a callable (function or closure).

Objects

Objects are evaluated by evaluating all key-value pairs:

{"key": "value", "computed": ["add", 1, 2]}

Keys must evaluate to strings, values can be any JSL expression.

Error Handling

The evaluator provides detailed error information including:

  • Expression context
  • Environment state
  • Call stack trace
  • Host command failures

Security

The evaluator includes security measures:

  • Sandboxing: Host commands are controlled by the dispatcher
  • Resource Limits: Evaluation depth and memory usage controls
  • Safe Evaluation: No access to Python internals by default

Usage Examples

Basic Evaluation

from jsl.core import Evaluator, Env

evaluator = Evaluator()
env = Env()

# Evaluate a simple expression
result = evaluator.eval(["+", 1, 2], env)
print(result)  # 3

With Variables

# Define a variable
evaluator.eval(["def", "x", 42], env)

# Use the variable
result = evaluator.eval(["*", "x", 2], env)
print(result)  # 84

Function Definition and Call

# Define a function
evaluator.eval(["def", "square", ["lambda", ["x"], ["*", "x", "x"]]], env)

# Call the function
result = evaluator.eval(["square", 5], env)
print(result)  # 25

Host Commands

from jsl.core import HostDispatcher

# Create a dispatcher with custom commands
dispatcher = HostDispatcher()
dispatcher.register("print", lambda args: print(*args))

evaluator = Evaluator(host_dispatcher=dispatcher)

# Execute a host command
evaluator.eval(["host", "print", "@Hello, World!"], env)

Performance Considerations

Tail Call Optimization

The evaluator optimizes tail calls to prevent stack overflow in recursive functions.

Memory Management

  • Environments use reference counting
  • Closures are garbage collected when no longer referenced
  • Host commands can implement resource limits

Caching

  • Function closures cache their compiled form
  • Environment lookups are optimized for common patterns
  • Object evaluation caches key-value pairs when possible