expression-system-design.md 38 KB

Expression System Design

Overview

This document outlines the architecture for a string expression system in the Invercargill library that supports evaluation, translation, and serves as a foundation for future ORM-like functionality.

Goals

  • String expressions for various operations in Invercargill
  • Foundation for ORM-type library (similar to LINQ but with strings instead of expression trees)
  • Support for evaluation AND translation
  • Return value types, objects, and enumerables
  • Boolean expressions: ==, !=, >=, <=, <, >, &&, ||
  • Arithmetic expressions: +, -, *, /, %
  • Unary operators: - (negate), ! (not)
  • Ternary conditional: condition ? true_value : false_value
  • Nested expressions with brackets
  • Property navigation via PropertyAccessor
  • Function accessors (initially for Enumerable)
  • Lambda expressions with closure support
  • Root names provided via Properties object

Example Expressions

a == b
a.prop
a.values.where(s => s.rank > a.min_rank)
a.values.where(s => s.rank > a.min_rank).first().is_important

Architecture

Core Components Overview

classDiagram
    class Expression {
        <<interface>>
        +evaluate(EvaluationContext): Element
        +accept(ExpressionVisitor): void
        +get_type(): ExpressionType
    }
    
    class EvaluationContext {
        +Properties root_values
        +EvaluationContext? parent
        +Element? get_variable(string name)
        +EvaluationContext create_child_scope(string param_name, Element value)
    }
    
    class ExpressionVisitor {
        <<interface>>
        +visit_literal(LiteralExpression)
        +visit_variable(VariableExpression)
        +visit_binary(BinaryExpression)
        +visit_unary(UnaryExpression)
        +visit_ternary(TernaryExpression)
        +visit_property(PropertyExpression)
        +visit_function_call(FunctionCallExpression)
        +visit_lambda(LambdaExpression)
    }
    
    Expression <-- LiteralExpression
    Expression <-- VariableExpression
    Expression <-- BinaryExpression
    Expression <-- UnaryExpression
    Expression <-- TernaryExpression
    Expression <-- PropertyExpression
    Expression <-- FunctionCallExpression
    Expression <-- LambdaExpression
    Expression <-- BracketedExpression
    
    ExpressionVisitor --> Expression: visits
    EvaluationContext --> Expression: used by

Core Interfaces

Expression Interface

The base interface for all expression types:

namespace Invercargill.Expressions {

    public enum ExpressionType {
        LITERAL,
        VARIABLE,
        BINARY,
        UNARY,
        TERNARY,
        PROPERTY,
        FUNCTION_CALL,
        LAMBDA,
        BRACKETED
    }

    public interface Expression : Object {
        public abstract ExpressionType expression_type { get; }
        
        // Evaluate the expression with the given context
        public abstract Element evaluate(EvaluationContext context) throws ExpressionError;
        
        // Visitor pattern for translation/traversal
        public abstract void accept(ExpressionVisitor visitor);
        
        // Optional: Get the expected return type (for optimization/validation)
        public virtual Type? get_return_type() { return null; }
        
        // Convenience method for string representation (useful for debugging)
        public virtual string to_expression_string() { return expression_type.to_string(); }
    }

}

EvaluationContext

Manages variable scopes and closures:

namespace Invercargill.Expressions {

    public class EvaluationContext : Object {
        
        // Root values available in the expression
        public Properties root_values { get; construct set; }
        
        // Parent context for closure support
        public EvaluationContext? parent { get; construct set; }
        
        // Lambda parameter name and value (only in child scopes)
        private string? _parameter_name;
        private Element? _parameter_value;
        
        public EvaluationContext(Properties root_values) {
            Object(root_values: root_values);
        }
        
        private EvaluationContext.with_parent(EvaluationContext parent, string param_name, Element param_value) {
            Object(root_values: parent.root_values, parent: parent);
            _parameter_name = param_name;
            _parameter_value = param_value;
        }
        
        // Get a variable by name - searches current scope then parent
        public Element? get_variable(string name) throws ExpressionError {
            // Check if it's the lambda parameter
            if (_parameter_name != null && _parameter_name == name) {
                return _parameter_value;
            }
            
            // Check parent scope (for closures)
            if (parent != null) {
                return parent.get_variable(name);
            }
            
            // Check root values
            Element element;
            if (root_values.try_get(name, out element)) {
                return element;
            }
            
            throw new ExpressionError.NON_EXISTANT_PROPERTY(
                @"Variable '$name' not found in current scope"
            );
        }
        
        // Create a child scope for lambda execution
        public EvaluationContext create_child_scope(string param_name, Element param_value) {
            return new EvaluationContext.with_parent(this, param_name, param_value);
        }
    }

}

ExpressionVisitor Interface

For translation and traversal:

namespace Invercargill.Expressions {

    public interface ExpressionVisitor : Object {
        public virtual void visit_literal(LiteralExpression expr) {}
        public virtual void visit_variable(VariableExpression expr) {}
        public virtual void visit_binary(BinaryExpression expr) {}
        public virtual void visit_unary(UnaryExpression expr) {}
        public virtual void visit_ternary(TernaryExpression expr) {}
        public virtual void visit_property(PropertyExpression expr) {}
        public virtual void visit_function_call(FunctionCallExpression expr) {}
        public virtual void visit_lambda(LambdaExpression expr) {}
        public virtual void visit_bracketed(BracketedExpression expr) {}
    }

}

Expression Types

1. LiteralExpression

Wraps constant values:

public class LiteralExpression : Object, Expression {
    public ExpressionType expression_type { get { return ExpressionType.LITERAL; } }
    
    public Element value { get; construct set; }
    
    public LiteralExpression(Element value) {
        Object(value: value);
    }
    
    // Convenience constructors
    public LiteralExpression.for_string(string val) {
        this(new ValueElement(val));
    }
    
    public LiteralExpression.for_int(int val) {
        this(new ValueElement(val));
    }
    
    public LiteralExpression.for_bool(bool val) {
        this(new ValueElement(val));
    }
    
    public LiteralExpression.for_null() {
        this(new NullElement());
    }
    
    public Element evaluate(EvaluationContext context) throws ExpressionError {
        return value;
    }
    
    public void accept(ExpressionVisitor visitor) {
        visitor.visit_literal(this);
    }
}

2. VariableExpression

References a named variable from the context:

public class VariableExpression : Object, Expression {
    public ExpressionType expression_type { get { return ExpressionType.VARIABLE; } }
    
    public string variable_name { get; construct set; }
    
    public VariableExpression(string name) {
        Object(variable_name: name);
    }
    
    public Element evaluate(EvaluationContext context) throws ExpressionError {
        return context.get_variable(variable_name);
    }
    
    public void accept(ExpressionVisitor visitor) {
        visitor.visit_variable(this);
    }
}

3. BinaryExpression

Handles binary operators:

public enum BinaryOperator {
    // Comparison
    EQUAL,           // ==
    NOT_EQUAL,       // !=
    GREATER_THAN,    // >
    LESS_THAN,       // <
    GREATER_EQUAL,   // >=
    LESS_EQUAL,      // <=
    
    // Logical
    AND,             // &&
    OR,              // ||
    
    // Arithmetic
    ADD,             // +
    SUBTRACT,        // -
    MULTIPLY,        // *
    DIVIDE,          // /
    MODULO;          // %
    
    public string to_string() {
        switch (this) {
            case EQUAL: return "==";
            case NOT_EQUAL: return "!=";
            case GREATER_THAN: return ">";
            case LESS_THAN: return "<";
            case GREATER_EQUAL: return ">=";
            case LESS_EQUAL: return "<=";
            case AND: return "&&";
            case OR: return "||";
            case ADD: return "+";
            case SUBTRACT: return "-";
            case MULTIPLY: return "*";
            case DIVIDE: return "/";
            case MODULO: return "%";
            default: return "";
        }
    }
    
    public bool is_comparison() {
        return this in { EQUAL, NOT_EQUAL, GREATER_THAN, LESS_THAN, GREATER_EQUAL, LESS_EQUAL };
    }
    
    public bool is_logical() {
        return this in { AND, OR };
    }
    
    public bool is_arithmetic() {
        return this in { ADD, SUBTRACT, MULTIPLY, DIVIDE, MODULO };
    }
}

public class BinaryExpression : Object, Expression {
    public ExpressionType expression_type { get { return ExpressionType.BINARY; } }
    
    public Expression left { get; construct set; }
    public Expression right { get; construct set; }
    public BinaryOperator operator { get; construct set; }
    
    public BinaryExpression(Expression left, BinaryOperator op, Expression right) {
        Object(left: left, right: right, operator: op);
    }
    
    public Element evaluate(EvaluationContext context) throws ExpressionError {
        var left_val = left.evaluate(context);
        var right_val = right.evaluate(context);
        
        switch (operator) {
            case BinaryOperator.AND:
                return new ValueElement(evaluate_bool(left_val) && evaluate_bool(right_val));
            case BinaryOperator.OR:
                return new ValueElement(evaluate_bool(left_val) || evaluate_bool(right_val));
            case BinaryOperator.EQUAL:
                return new ValueElement(evaluate_equality(left_val, right_val));
            case BinaryOperator.NOT_EQUAL:
                return new ValueElement(!evaluate_equality(left_val, right_val));
            case BinaryOperator.GREATER_THAN:
            case BinaryOperator.LESS_THAN:
            case BinaryOperator.GREATER_EQUAL:
            case BinaryOperator.LESS_EQUAL:
                return new ValueElement(evaluate_comparison(left_val, right_val, operator));
            case BinaryOperator.ADD:
                return evaluate_arithmetic(left_val, right_val, (a, b) => a + b, (a, b) => a + b);
            case BinaryOperator.SUBTRACT:
                return evaluate_arithmetic(left_val, right_val, (a, b) => a - b, (a, b) => a - b);
            case BinaryOperator.MULTIPLY:
                return evaluate_arithmetic(left_val, right_val, (a, b) => a * b, (a, b) => a * b);
            case BinaryOperator.DIVIDE:
                return evaluate_arithmetic(left_val, right_val, (a, b) => a / b, (a, b) => a / b);
            case BinaryOperator.MODULO:
                return evaluate_arithmetic_int(left_val, right_val, (a, b) => a % b);
        }
    }
    
    private Element evaluate_arithmetic(
        Element left,
        Element right,
        owned ComputeIntDelegate int_op,
        owned ComputeDoubleDelegate double_op
    ) throws ExpressionError {
        // Try double first for mixed arithmetic
        double? left_d, right_d;
        if (left.try_get_as(out left_d) && right.try_get_as(out right_d)) {
            return new ValueElement(double_op(left_d, right_d));
        }
        
        // Try int
        int? left_i, right_i;
        if (left.try_get_as(out left_i) && right.try_get_as(out right_i)) {
            return new ValueElement(int_op(left_i, right_i));
        }
        
        // String concatenation for + operator
        if (operator == BinaryOperator.ADD) {
            string? left_s, right_s;
            if (left.try_get_as(out left_s) && right.try_get_as(out right_s)) {
                return new ValueElement(left_s + right_s);
            }
        }
        
        throw new ExpressionError.INVALID_TYPE(
            @"Cannot perform arithmetic on $(left.type_name()) and $(right.type_name())"
        );
    }
    
    private Element evaluate_arithmetic_int(
        Element left,
        Element right,
        owned ComputeIntDelegate op
    ) throws ExpressionError {
        int? left_i, right_i;
        if (left.try_get_as(out left_i) && right.try_get_as(out right_i)) {
            return new ValueElement(op(left_i, right_i));
        }
        throw new ExpressionError.INVALID_TYPE("Modulo operator requires integer operands");
    }
    
    private delegate int ComputeIntDelegate(int a, int b);
    private delegate double ComputeDoubleDelegate(double a, double b);
    
    private bool evaluate_bool(Element element) throws ExpressionError {
        if (element.is<bool>()) {
            return element.as<bool>();
        }
        throw new ExpressionError.INVALID_TYPE("Expected boolean value");
    }
    
    private bool evaluate_equality(Element left, Element right) {
        // Use existing equality operators from Invercargill.Operators
        // Handle null cases
        if (left.is_null() && right.is_null()) return true;
        if (left.is_null() || right.is_null()) return false;
        
        // Try to compare using Element's try_get_as
        // ... implementation details
    }
    
    private bool evaluate_comparison(Element left, Element right, BinaryOperator op) throws ExpressionError {
        // Use existing comparison operators from Invercargill.Operators
        // ... implementation details
    }
    
    public void accept(ExpressionVisitor visitor) {
        visitor.visit_binary(this);
        left.accept(visitor);
        right.accept(visitor);
    }
    
    public override string to_expression_string() {
        return @"($(_left.to_expression_string()) $(operator.to_string()) $(_right.to_expression_string()))";
    }
}

4. UnaryExpression

Handles unary operators (negation, etc.):

public enum UnaryOperator {
    NEGATE,    // - (arithmetic negation)
    NOT,       // ! (logical negation)
    INCREMENT, // ++ (prefix/postfix increment)
    DECREMENT; // -- (prefix/postfix decrement)
    
    public string to_string() {
        switch (this) {
            case NEGATE: return "-";
            case NOT: return "!";
            case INCREMENT: return "++";
            case DECREMENT: return "--";
            default: return "";
        }
    }
}

public class UnaryExpression : Object, Expression {
    public ExpressionType expression_type { get { return ExpressionType.UNARY; } }
    
    public Expression operand { get; construct set; }
    public UnaryOperator operator { get; construct set; }
    public bool is_prefix { get; construct set; } // true for ++x, false for x++
    
    public UnaryExpression(UnaryOperator op, Expression operand, bool is_prefix = true) {
        Object(operand: operand, operator: op, is_prefix: is_prefix);
    }
    
    public Element evaluate(EvaluationContext context) throws ExpressionError {
        var operand_val = operand.evaluate(context);
        
        switch (operator) {
            case UnaryOperator.NEGATE:
                return evaluate_negate(operand_val);
            case UnaryOperator.NOT:
                return evaluate_not(operand_val);
            case UnaryOperator.INCREMENT:
            case UnaryOperator.DECREMENT:
                throw new ExpressionError.INVALID_SYNTAX(
                    "Increment/decrement operators not supported in expression evaluation"
                );
        }
    }
    
    private Element evaluate_negate(Element element) throws ExpressionError {
        // Try numeric types
        int? int_val;
        if (element.try_get_as(out int_val)) {
            return new ValueElement(-int_val);
        }
        
        double? double_val;
        if (element.try_get_as(out double_val)) {
            return new ValueElement(-double_val);
        }
        
        int64? int64_val;
        if (element.try_get_as(out int64_val)) {
            return new ValueElement(-int64_val);
        }
        
        throw new ExpressionError.INVALID_TYPE("Cannot negate non-numeric value");
    }
    
    private Element evaluate_not(Element element) throws ExpressionError {
        if (element.is<bool>()) {
            return new ValueElement(!element.as<bool>());
        }
        throw new ExpressionError.INVALID_TYPE("Logical NOT requires boolean operand");
    }
    
    public void accept(ExpressionVisitor visitor) {
        visitor.visit_unary(this);
        operand.accept(visitor);
    }
    
    public override string to_expression_string() {
        if (is_prefix) {
            return @"$(operator.to_string())$(operand.to_expression_string())";
        }
        return @"$(operand.to_expression_string())$(operator.to_string())";
    }
}

5. TernaryExpression

Handles conditional expressions (ternary operator):

public class TernaryExpression : Object, Expression {
    public ExpressionType expression_type { get { return ExpressionType.TERNARY; } }
    
    public Expression condition { get; construct set; }
    public Expression true_expression { get; construct set; }
    public Expression false_expression { get; construct set; }
    
    public TernaryExpression(Expression condition, Expression true_expr, Expression false_expr) {
        Object(condition: condition, true_expression: true_expr, false_expression: false_expr);
    }
    
    public Element evaluate(EvaluationContext context) throws ExpressionError {
        var cond_val = condition.evaluate(context);
        
        bool cond_bool;
        if (cond_val.try_get_as(out cond_bool)) {
            if (cond_bool) {
                return true_expression.evaluate(context);
            } else {
                return false_expression.evaluate(context);
            }
        }
        
        throw new ExpressionError.INVALID_TYPE("Ternary condition must evaluate to boolean");
    }
    
    public void accept(ExpressionVisitor visitor) {
        visitor.visit_ternary(this);
        condition.accept(visitor);
        true_expression.accept(visitor);
        false_expression.accept(visitor);
    }
    
    public override string to_expression_string() {
        return @"$(condition.to_expression_string()) ? $(true_expression.to_expression_string()) : $(false_expression.to_expression_string())";
    }
}

6. PropertyExpression

Navigates properties using PropertyAccessor:

public class PropertyExpression : Object, Expression {
    public ExpressionType expression_type { get { return ExpressionType.PROPERTY; } }
    
    public Expression target { get; construct set; }
    public string property_name { get; construct set; }
    
    public PropertyExpression(Expression target, string property_name) {
        Object(target: target, property_name: property_name);
    }
    
    public Element evaluate(EvaluationContext context) throws ExpressionError {
        var target_value = target.evaluate(context);
        
        // Get PropertyAccessor from the element
        PropertyAccessor? accessor = null;
        
        // If the element itself is a PropertyAccessor
        if (target_value.try_get_as(out accessor)) {
            return accessor.read_property(property_name);
        }
        
        // If it's an Object, wrap it
        Object? obj;
        if (target_value.try_get_as(out obj)) {
            accessor = new ObjectPropertyAccessor(obj);
            return accessor.read_property(property_name);
        }
        
        // If it's a Properties instance
        Properties? props;
        if (target_value.try_get_as(out props)) {
            accessor = new PropertiesPropertyAccessor(props);
            return accessor.read_property(property_name);
        }
        
        throw new ExpressionError.INVALID_TYPE(
            @"Cannot access property '$property_name' on type $(target_value.type_name())"
        );
    }
    
    public void accept(ExpressionVisitor visitor) {
        visitor.visit_property(this);
        target.accept(visitor);
    }
}

7. FunctionCallExpression

Calls functions via FunctionAccessor:

public class FunctionCallExpression : Object, Expression {
    public ExpressionType expression_type { get { return ExpressionType.FUNCTION_CALL; } }
    
    public Expression target { get; construct set; }
    public string function_name { get; construct set; }
    public Gee.List<Expression> arguments { get; construct set; }
    
    public FunctionCallExpression(Expression target, string function_name, Gee.List<Expression>? arguments = null) {
        Object(target: target, function_name: function_name, arguments: arguments ?? new Gee.ArrayList<Expression>());
    }
    
    public Element evaluate(EvaluationContext context) throws ExpressionError {
        var target_value = target.evaluate(context);
        
        // Get FunctionAccessor from the element
        FunctionAccessor? accessor = get_function_accessor(target_value);
        
        if (accessor == null) {
            throw new ExpressionError.INVALID_TYPE(
                @"Cannot call function '$function_name' on type $(target_value.type_name())"
            );
        }
        
        // Evaluate arguments
        var evaluated_args = new Gee.ArrayList<Element>();
        foreach (var arg in arguments) {
            evaluated_args.add(arg.evaluate(context));
        }
        
        return accessor.call_function(function_name, evaluated_args, context);
    }
    
    private FunctionAccessor? get_function_accessor(Element element) {
        // Check if element directly implements FunctionAccessor
        FunctionAccessor? accessor;
        if (element.try_get_as(out accessor)) {
            return accessor;
        }
        
        // Check for Elements (Enumerable wrapper)
        Elements? elements;
        if (element.try_get_as(out elements)) {
            return new EnumerableFunctionAccessor(elements);
        }
        
        return null;
    }
    
    public void accept(ExpressionVisitor visitor) {
        visitor.visit_function_call(this);
        target.accept(visitor);
        foreach (var arg in arguments) {
            arg.accept(visitor);
        }
    }
}

8. LambdaExpression

Represents lambda functions with closure support:

public class LambdaExpression : Object, Expression {
    public ExpressionType expression_type { get { return ExpressionType.LAMBDA; } }
    
    public string parameter_name { get; construct set; }
    public Expression body { get; construct set; }
    
    // Capture the context at creation time for closures
    private EvaluationContext? _captured_context;
    
    public LambdaExpression(string parameter_name, Expression body) {
        Object(parameter_name: parameter_name, body: body);
    }
    
    // Called when the lambda is created in an expression
    public void capture_context(EvaluationContext context) {
        _captured_context = context;
    }
    
    // Evaluate the lambda with a specific argument
    public Element evaluate_with_argument(Element argument) throws ExpressionError {
        if (_captured_context == null) {
            throw new ExpressionError.INVALID_TYPE("Lambda has no captured context");
        }
        
        // Create child scope with the parameter
        var lambda_context = _captured_context.create_child_scope(parameter_name, argument);
        
        return body.evaluate(lambda_context);
    }
    
    // Standard evaluate returns the lambda itself as a callable
    public Element evaluate(EvaluationContext context) throws ExpressionError {
        capture_context(context);
        return new LambdaElement(this);
    }
    
    public void accept(ExpressionVisitor visitor) {
        visitor.visit_lambda(this);
        body.accept(visitor);
    }
}

9. BracketedExpression

Handles parenthesized expressions for precedence:

public class BracketedExpression : Object, Expression {
    public ExpressionType expression_type { get { return ExpressionType.BRACKETED; } }
    
    public Expression inner { get; construct set; }
    
    public BracketedExpression(Expression inner) {
        Object(inner: inner);
    }
    
    public Element evaluate(EvaluationContext context) throws ExpressionError {
        return inner.evaluate(context);
    }
    
    public void accept(ExpressionVisitor visitor) {
        visitor.visit_bracketed(this);
        inner.accept(visitor);
    }
}

Supporting Types

LambdaElement

Wraps a lambda for passing around:

public class LambdaElement : Object, Element {
    private LambdaExpression _lambda;
    
    public LambdaElement(LambdaExpression lambda) {
        _lambda = lambda;
    }
    
    public LambdaExpression get_lambda() {
        return _lambda;
    }
    
    // Element interface implementation
    public bool assignable_to_type(Type type) {
        return type == typeof(LambdaElement) || type == typeof(LambdaExpression);
    }
    
    public Type? type() {
        return typeof(LambdaExpression);
    }
    
    public bool is_null() {
        return false;
    }
    
    public bool try_get_as<T>(out T result) {
        if (typeof(T) == typeof(LambdaElement)) {
            result = this;
            return true;
        }
        if (typeof(T) == typeof(LambdaExpression)) {
            result = _lambda;
            return true;
        }
        result = null;
        return false;
    }
}

FunctionAccessor Interface

namespace Invercargill.Expressions {

    public interface FunctionAccessor : Object {
        
        // Check if a function is available
        public abstract bool has_function(string function_name);
        
        // Get available function names
        public abstract Enumerable<string> get_function_names();
        
        // Call a function with evaluated arguments
        // The context is provided for lambdas that need to capture scope
        public abstract Element call_function(
            string function_name,
            Gee.List<Element> arguments,
            EvaluationContext context
        ) throws ExpressionError;
    }

}

EnumerableFunctionAccessor

Implementation for Enumerable/Elements:

public class EnumerableFunctionAccessor : Object, FunctionAccessor {
    
    private Elements _target;
    
    public EnumerableFunctionAccessor(Elements target) {
        _target = target;
    }
    
    public bool has_function(string function_name) {
        return get_supported_functions().contains(function_name);
    }
    
    public Enumerable<string> get_function_names() {
        return get_supported_functions();
    }
    
    private Enumerable<string> get_supported_functions() {
        return Iterate.from_array(new string[] {
            "where", "select", "first", "last", "single",
            "take", "skip", "count", "any", "all",
            "order_by", "order_by_descending", "distinct",
            "element_at", "concat", "reverse"
        });
    }
    
    public Element call_function(
        string function_name,
        Gee.List<Element> arguments,
        EvaluationContext context
    ) throws ExpressionError {
        
        switch (function_name.to_lower()) {
            case "where":
                return call_where(arguments, context);
            case "first":
                return call_first(arguments);
            case "last":
                return call_last(arguments);
            case "count":
                return call_count(arguments);
            case "any":
                return call_any(arguments, context);
            // ... other methods
            
            default:
                throw new ExpressionError.INVALID_TYPE(
                    @"Unknown function '$function_name' for Enumerable"
                );
        }
    }
    
    private Element call_where(Gee.List<Element> arguments, EvaluationContext context) throws ExpressionError {
        if (arguments.size != 1) {
            throw new ExpressionError.INVALID_SYNTAX("where() requires exactly 1 argument");
        }
        
        var lambda_element = arguments[0];
        LambdaExpression? lambda;
        
        if (!lambda_element.try_get_as(out lambda)) {
            throw new ExpressionError.INVALID_TYPE("where() requires a lambda argument");
        }
        
        // Capture the current context for closure support
        lambda.capture_context(context);
        
        // Apply the where filter
        var filtered = _target.where(item => {
            try {
                var result = lambda.evaluate_with_argument(new NativeElement<Object>(item));
                return result.as<bool>();
            } catch (Error e) {
                return false;
            }
        });
        
        return new NativeElement<Elements>(filtered.to_elements());
    }
    
    private Element call_first(Gee.List<Element> arguments) throws ExpressionError {
        if (arguments.size > 1) {
            throw new ExpressionError.INVALID_SYNTAX("first() takes at most 1 argument");
        }
        
        try {
            if (arguments.size == 0) {
                return new NativeElement<Object?>(_target.first());
            }
            
            // With predicate lambda
            var lambda_element = arguments[0];
            LambdaExpression? lambda;
            if (!lambda_element.try_get_as(out lambda)) {
                throw new ExpressionError.INVALID_TYPE("first() predicate must be a lambda");
            }
            
            var result = _target.first(item => {
                try {
                    var eval_result = lambda.evaluate_with_argument(new NativeElement<Object>(item));
                    return eval_result.as<bool>();
                } catch (Error e) {
                    return false;
                }
            });
            
            return new NativeElement<Object?>(result);
        } catch (SequenceError e) {
            return new NullElement();
        }
    }
    
    // ... other method implementations
}

Expression Evaluator

A facade for evaluating expressions:

public class ExpressionEvaluator : Object {
    
    public Element evaluate(Expression expression, Properties root_values) throws ExpressionError {
        var context = new EvaluationContext(root_values);
        return expression.evaluate(context);
    }
    
    public T evaluate_as<T>(Expression expression, Properties root_values) throws Error {
        var result = evaluate(expression, root_values);
        return result.as<T>();
    }
    
    // Convenience method for creating and evaluating a simple property chain
    public Element evaluate_property_chain(string[] properties, Properties root_values) throws ExpressionError {
        if (properties.length == 0) {
            throw new ExpressionError.INVALID_SYNTAX("Property chain cannot be empty");
        }
        
        Expression current = new VariableExpression(properties[0]);
        
        for (int i = 1; i < properties.length; i++) {
            current = new PropertyExpression(current, properties[i]);
        }
        
        return evaluate(current, root_values);
    }
}

Translation Pattern

For ORM-like translation, implement ExpressionVisitor:

public class SqlExpressionTranslator : Object, ExpressionVisitor {
    
    private StringBuilder _builder = new StringBuilder();
    private int _parameter_index = 0;
    private Gee.ArrayList<Value> _parameters = new Gee.ArrayList<Value>();
    
    public string translate(Expression expression) {
        _builder.truncate(0);
        _parameters.clear();
        _parameter_index = 0;
        
        expression.accept(this);
        
        return _builder.str;
    }
    
    public Gee.List<Value> get_parameters() {
        return _parameters.read_only_view;
    }
    
    public override void visit_literal(LiteralExpression expr) {
        // Add parameter placeholder
        _parameters.add(extract_value(expr.value));
        _builder.append(@"@p$(_parameter_index)");
        _parameter_index++;
    }
    
    public override void visit_variable(VariableExpression expr) {
        // Translate variable to column reference
        _builder.append(expr.variable_name);
    }
    
    public override void visit_binary(BinaryExpression expr) {
        _builder.append("(");
        expr.left.accept(this);
        _builder.append(" ");
        _builder.append(expr.operator.to_string());
        _builder.append(" ");
        expr.right.accept(this);
        _builder.append(")");
    }
    
    public override void visit_unary(UnaryExpression expr) {
        if (expr.is_prefix) {
            _builder.append(expr.operator.to_string());
            expr.operand.accept(this);
        } else {
            expr.operand.accept(this);
            _builder.append(expr.operator.to_string());
        }
    }
    
    public override void visit_ternary(TernaryExpression expr) {
        expr.condition.accept(this);
        _builder.append(" CASE WHEN ");
        // For SQL, we translate ternary to CASE WHEN ... THEN ... ELSE ... END
        expr.true_expression.accept(this);
        _builder.append(" ELSE ");
        expr.false_expression.accept(this);
        _builder.append(" END");
    }
    
    public override void visit_property(PropertyExpression expr) {
        expr.target.accept(this);
        _builder.append(".");
        _builder.append(expr.property_name);
    }
    
    public override void visit_function_call(FunctionCallExpression expr) {
        // Handle specific functions differently
        // e.g., where() becomes part of WHERE clause
        // ... implementation
    }
    
    public override void visit_lambda(LambdaExpression expr) {
        // Lambda body becomes part of the SQL expression
        expr.body.accept(this);
    }
}

File Structure

src/lib/Expressions/
├── Expression.vala              # Main Expression interface
├── ExpressionError.vala         # Error domain (existing)
├── ExpressionType.vala          # ExpressionType enum
├── EvaluationContext.vala       # Context for evaluation
├── ExpressionVisitor.vala       # Visitor interface
├── ExpressionEvaluator.vala     # Facade for evaluation
│
├── PropertyAccessor.vala        # Interface (existing)
├── ObjectPropertyAccessor.vala  # Implementation (existing)
├── PropertiesPropertyAccessor.vala # Implementation (existing)
│
├── FunctionAccessor.vala        # Interface for function calls
├── EnumerableFunctionAccessor.vala # Implementation for Enumerable
│
├── Expressions/
│   ├── LiteralExpression.vala
│   ├── VariableExpression.vala
│   ├── BinaryExpression.vala
│   ├── BinaryOperator.vala
│   ├── UnaryExpression.vala
│   ├── UnaryOperator.vala
│   ├── TernaryExpression.vala
│   ├── PropertyExpression.vala
│   ├── FunctionCallExpression.vala
│   ├── LambdaExpression.vala
│   └── BracketedExpression.vala
│
├── Elements/
│   └── LambdaElement.vala       # Element wrapper for lambdas
│
└── Translators/
    ├── SqlExpressionTranslator.vala
    └── StructuredExpressionTranslator.vala

Usage Examples

Basic Variable and Property Access

var props = new PropertiesDictionary();
props.set_native("a", some_object);
props.set_native("b", 42);

var expr = new BinaryExpression(
    new PropertyExpression(new VariableExpression("a"), "value"),
    BinaryOperator.GREATER_THAN,
    new VariableExpression("b")
);

var evaluator = new ExpressionEvaluator();
var result = evaluator.evaluate_as<bool>(expr, props);

Lambda with Closure

// Expression: a.values.where(s => s.rank > a.min_rank)
var props = new PropertiesDictionary();
props.set_native("a", container_with_values_and_min_rank);

// Build: a.values
var a_values = new PropertyExpression(
    new VariableExpression("a"),
    "values"
);

// Build: s.rank > a.min_rank
var lambda_body = new BinaryExpression(
    new PropertyExpression(new VariableExpression("s"), "rank"),
    BinaryOperator.GREATER_THAN,
    new PropertyExpression(new VariableExpression("a"), "min_rank")
);

// Build: s => s.rank > a.min_rank
var lambda = new LambdaExpression("s", lambda_body);

// Build: a.values.where(s => s.rank > a.min_rank)
var where_call = new FunctionCallExpression(a_values, "where", 
    new Gee.ArrayList<Expression>.wrap({ lambda })
);

var evaluator = new ExpressionEvaluator();
var result = evaluator.evaluate(where_call, props);

Chained Function Calls

// Expression: a.values.where(s => s.rank > a.min_rank).first().is_important
// ... previous where_call ...

var first_call = new FunctionCallExpression(where_call, "first");
var is_important = new PropertyExpression(first_call, "is_important");

var evaluator = new ExpressionEvaluator();
var result = evaluator.evaluate_as<bool>(is_important, props);

Design Decisions Summary

  1. Dynamically Typed: Uses Element system for flexible type handling
  2. Closure Support: EvaluationContext maintains parent chain for scope resolution
  3. Visitor Pattern: Enables translation without modifying expression classes
  4. Interface-Based Accessors: Both PropertyAccessor and FunctionAccessor are interfaces for extensibility
  5. Separation of Concerns:
    • Expressions define structure
    • Evaluators handle execution
    • Visitors handle translation
  6. ORM Ready: The visitor pattern allows for SQL or other query language generation

Next Steps

  1. Implement core interfaces (Expression, EvaluationContext, ExpressionVisitor)
  2. Implement expression types (start with LiteralExpression, VariableExpression, BinaryExpression)
  3. Implement FunctionAccessor interface and EnumerableFunctionAccessor
  4. Add LambdaExpression with closure capture
  5. Add ExpressionEvaluator facade
  6. Write unit tests
  7. Add parser (future task)
  8. Add SQL translator (future task)