|
@@ -0,0 +1,1226 @@
|
|
|
|
|
+# 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 {
|
|
|
|
|
+ <<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:
|
|
|
|
|
+
|
|
|
|
|
+```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<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.):
|
|
|
|
|
+
|
|
|
|
|
+```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<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):
|
|
|
|
|
+
|
|
|
|
|
+```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<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:
|
|
|
|
|
+
|
|
|
|
|
+```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<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
|
|
|
|
|
+
|
|
|
|
|
+```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<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:
|
|
|
|
|
+
|
|
|
|
|
+```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<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:
|
|
|
|
|
+
|
|
|
|
|
+```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<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`:
|
|
|
|
|
+
|
|
|
|
|
+```vala
|
|
|
|
|
+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
|
|
|
|
|
+
|
|
|
|
|
+```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<bool>(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<Expression>.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<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)
|