# 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 ```mermaid classDiagram class Expression { <> +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 { <> +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: ```vala 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: ```vala 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: ```vala 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: ```vala 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: ```vala 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: ```vala 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()) { return element.as(); } 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.): ```vala 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()) { return new ValueElement(!element.as()); } 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): ```vala 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`: ```vala 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`: ```vala 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 arguments { get; construct set; } public FunctionCallExpression(Expression target, string function_name, Gee.List? arguments = null) { Object(target: target, function_name: function_name, arguments: arguments ?? new Gee.ArrayList()); } 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(); 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: ```vala 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: ```vala 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: ```vala 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(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 ```vala 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 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 arguments, EvaluationContext context ) throws ExpressionError; } } ``` ### EnumerableFunctionAccessor Implementation for Enumerable/Elements: ```vala 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 get_function_names() { return get_supported_functions(); } private Enumerable 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 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 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(item)); return result.as(); } catch (Error e) { return false; } }); return new NativeElement(filtered.to_elements()); } private Element call_first(Gee.List 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(_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(item)); return eval_result.as(); } catch (Error e) { return false; } }); return new NativeElement(result); } catch (SequenceError e) { return new NullElement(); } } // ... other method implementations } ``` --- ## Expression Evaluator A facade for evaluating expressions: ```vala 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(Expression expression, Properties root_values) throws Error { var result = evaluate(expression, root_values); return result.as(); } // 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`: ```vala public class SqlExpressionTranslator : Object, ExpressionVisitor { private StringBuilder _builder = new StringBuilder(); private int _parameter_index = 0; private Gee.ArrayList _parameters = new Gee.ArrayList(); public string translate(Expression expression) { _builder.truncate(0); _parameters.clear(); _parameter_index = 0; expression.accept(this); return _builder.str; } public Gee.List 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 ```vala 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(expr, props); ``` ### Lambda with Closure ```vala // 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.wrap({ lambda }) ); var evaluator = new ExpressionEvaluator(); var result = evaluator.evaluate(where_call, props); ``` ### Chained Function Calls ```vala // 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(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)