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.
==, !=, >=, <=, <, >, &&, ||+, -, *, /, %- (negate), ! (not)condition ? true_value : false_valuePropertyAccessorEnumerable)Properties objecta == b
a.prop
a.values.where(s => s.rank > a.min_rank)
a.values.where(s => s.rank > a.min_rank).first().is_important
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
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(); }
}
}
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);
}
}
}
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) {}
}
}
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);
}
}
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);
}
}
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()))";
}
}
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())";
}
}
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())";
}
}
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);
}
}
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);
}
}
}
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);
}
}
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);
}
}
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;
}
}
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;
}
}
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
}
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);
}
}
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);
}
}
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
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);
// 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);
// 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);
Element system for flexible type handlingEvaluationContext maintains parent chain for scope resolutionPropertyAccessor and FunctionAccessor are interfaces for extensibilityExpression, EvaluationContext, ExpressionVisitor)LiteralExpression, VariableExpression, BinaryExpression)FunctionAccessor interface and EnumerableFunctionAccessorLambdaExpression with closure captureExpressionEvaluator facade