Ver Fonte

Expressive vibes

Billy Barrow há 1 semana atrás
pai
commit
24613fa490
35 ficheiros alterados com 4615 adições e 0 exclusões
  1. 2 0
      src/lib/Delegates.vala
  2. 59 0
      src/lib/Element.vala
  3. 5 0
      src/lib/Enumerable.vala
  4. 129 0
      src/lib/Expressions/BinaryOperator.vala
  5. 69 0
      src/lib/Expressions/Elements/LambdaElement.vala
  6. 602 0
      src/lib/Expressions/EnumerableFunctionAccessor.vala
  7. 140 0
      src/lib/Expressions/EvaluationContext.vala
  8. 53 0
      src/lib/Expressions/Expression.vala
  9. 17 0
      src/lib/Expressions/ExpressionError.vala
  10. 146 0
      src/lib/Expressions/ExpressionEvaluator.vala
  11. 435 0
      src/lib/Expressions/ExpressionParser.vala
  12. 388 0
      src/lib/Expressions/ExpressionTokenizer.vala
  13. 34 0
      src/lib/Expressions/ExpressionType.vala
  14. 65 0
      src/lib/Expressions/ExpressionVisitor.vala
  15. 355 0
      src/lib/Expressions/Expressions/BinaryExpression.vala
  16. 47 0
      src/lib/Expressions/Expressions/BracketedExpression.vala
  17. 143 0
      src/lib/Expressions/Expressions/FunctionCallExpression.vala
  18. 110 0
      src/lib/Expressions/Expressions/LambdaExpression.vala
  19. 114 0
      src/lib/Expressions/Expressions/LiteralExpression.vala
  20. 74 0
      src/lib/Expressions/Expressions/PropertyExpression.vala
  21. 76 0
      src/lib/Expressions/Expressions/TernaryExpression.vala
  22. 126 0
      src/lib/Expressions/Expressions/UnaryExpression.vala
  23. 42 0
      src/lib/Expressions/Expressions/VariableExpression.vala
  24. 49 0
      src/lib/Expressions/FunctionAccessor.vala
  25. 77 0
      src/lib/Expressions/ObjectPropertyAccessor.vala
  26. 30 0
      src/lib/Expressions/PropertiesPropertyAccessor.vala
  27. 20 0
      src/lib/Expressions/PropertyAccessor.vala
  28. 64 0
      src/lib/Expressions/UnaryOperator.vala
  29. 141 0
      src/lib/Operators/Contain.vala
  30. 18 0
      src/lib/Promotions/Elements.vala
  31. 2 0
      src/lib/Promotions/Registration.c
  32. 33 0
      src/lib/meson.build
  33. 948 0
      src/tests/Integration/Expressions.vala
  34. 1 0
      src/tests/TestRunner.vala
  35. 1 0
      src/tests/meson.build

+ 2 - 0
src/lib/Delegates.vala

@@ -28,4 +28,6 @@ namespace Invercargill {
 
     public delegate void DebugOutputDelegate(string output);
 
+    public delegate Value ContainDelegate<T>(T item);
+
 }

+ 59 - 0
src/lib/Element.vala

@@ -125,4 +125,63 @@ namespace Invercargill {
 
     }
 
+    public class ValueElement : Object, Element {
+
+        public Value value { get; private set; }
+
+        public ValueElement(Value value) {
+            this.value = value;
+        }
+        
+        public bool assignable_to_type(GLib.Type type) {
+            if(is_type(type)) {
+                return true;
+            }
+            if(value.type().is_a(typeof(Enumerable)) && type == typeof(Elements)) {
+                return true;
+            }
+            return false;
+        }
+        public GLib.Type? type() {
+            return value.type();
+        }
+        public bool is_null() {
+            var value_type = value.type();
+            if(!value_type.is_value_type()) {
+                return value.get_pointer() == null;
+            }
+            return false;
+        }
+        public bool try_get_as<T>(out T result) {
+            var value_type = value.type();
+            var requested_type = typeof(T);
+
+            // Handle Enumerable -> Elements conversion
+            if(value_type.is_a(typeof(Enumerable)) && requested_type == typeof(Elements)) {
+                result = (T)((Enumerable)value).to_elements();
+                return true;
+            }
+
+            // For reference types, try to get the object
+            if(!requested_type.is_value_type() && value_type.is_a(requested_type)) {
+                result = (T)value.get_object();
+                return true;
+            }
+
+            // Try value transformation for reference types
+            if(!requested_type.is_value_type()) {
+                var converted = Value(requested_type);
+                if(value.transform(ref converted)) {
+                    result = (T)converted.get_object();
+                    return true;
+                }
+            }
+
+            result = null;
+            return false;
+        }
+
+        
+    }
+
 }

+ 5 - 0
src/lib/Enumerable.vala

@@ -698,6 +698,11 @@ namespace Invercargill {
             return new Modifiers.Scanner<T, TOut>(this, initial_value, (owned)scanning_delegate);
         }
 
+        // Commented out due to Vala compiler issue with Value? as generic type parameter
+        // public virtual Enumerable<Value?> as_values() {
+        //     return select<Value?>(i => i);
+        // }
+
         public virtual void debug_dump(string additional_message = "", StringifyDelegate<T>? stringifier = null, DebugOutputDelegate? output_func = null, bool formatting = true) {
             DebugPrinter.print_enumerable_dump<T>(this, stringifier, additional_message, output_func, formatting);
         }

+ 129 - 0
src/lib/Expressions/BinaryOperator.vala

@@ -0,0 +1,129 @@
+
+namespace Invercargill.Expressions {
+
+    /**
+     * Enumeration of binary operators supported in expressions.
+     */
+    public enum BinaryOperator {
+        // Comparison operators
+        EQUAL,           // ==
+        NOT_EQUAL,       // !=
+        GREATER_THAN,    // >
+        LESS_THAN,       // <
+        GREATER_EQUAL,   // >=
+        LESS_EQUAL,      // <=
+
+        // Logical operators
+        AND,             // &&
+        OR,              // ||
+
+        // Arithmetic operators
+        ADD,             // +
+        SUBTRACT,        // -
+        MULTIPLY,        // *
+        DIVIDE,          // /
+        MODULO;          // %
+
+        /**
+         * Gets the string representation of this operator.
+         * 
+         * @return The operator symbol as a string
+         */
+        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 "";
+            }
+        }
+
+        /**
+         * Checks if this is a comparison operator.
+         * 
+         * @return true if this is a comparison operator
+         */
+        public bool is_comparison() {
+            switch (this) {
+                case EQUAL:
+                case NOT_EQUAL:
+                case GREATER_THAN:
+                case LESS_THAN:
+                case GREATER_EQUAL:
+                case LESS_EQUAL:
+                    return true;
+                default:
+                    return false;
+            }
+        }
+
+        /**
+         * Checks if this is a logical operator.
+         * 
+         * @return true if this is a logical operator
+         */
+        public bool is_logical() {
+            switch (this) {
+                case AND:
+                case OR:
+                    return true;
+                default:
+                    return false;
+            }
+        }
+
+        /**
+         * Checks if this is an arithmetic operator.
+         * 
+         * @return true if this is an arithmetic operator
+         */
+        public bool is_arithmetic() {
+            switch (this) {
+                case ADD:
+                case SUBTRACT:
+                case MULTIPLY:
+                case DIVIDE:
+                case MODULO:
+                    return true;
+                default:
+                    return false;
+            }
+        }
+
+        /**
+         * Parses a string to a BinaryOperator.
+         * 
+         * @param str The string to parse
+         * @return The parsed BinaryOperator, or null if invalid
+         */
+        public static BinaryOperator? from_string(string str) {
+            switch (str) {
+                case "==": return EQUAL;
+                case "!=": return NOT_EQUAL;
+                case ">": return GREATER_THAN;
+                case "<": return LESS_THAN;
+                case ">=": return GREATER_EQUAL;
+                case "<=": return LESS_EQUAL;
+                case "&&": return AND;
+                case "||": return OR;
+                case "+": return ADD;
+                case "-": return SUBTRACT;
+                case "*": return MULTIPLY;
+                case "/": return DIVIDE;
+                case "%": return MODULO;
+                default: return null;
+            }
+        }
+    }
+
+}

+ 69 - 0
src/lib/Expressions/Elements/LambdaElement.vala

@@ -0,0 +1,69 @@
+
+namespace Invercargill.Expressions {
+
+    /**
+     * Element wrapper for LambdaExpression.
+     * 
+     * Allows lambdas to be passed around as Element values, enabling
+     * them to be used as arguments to function calls.
+     */
+    public class LambdaElement : Object, Element {
+
+        private LambdaExpression _lambda;
+
+        /**
+         * Creates a new LambdaElement wrapping the given lambda.
+         * 
+         * @param lambda The lambda expression to wrap
+         */
+        public LambdaElement(LambdaExpression lambda) {
+            _lambda = lambda;
+        }
+
+        /**
+         * Gets the wrapped lambda expression.
+         * 
+         * @return The LambdaExpression
+         */
+        public LambdaExpression get_lambda() {
+            return _lambda;
+        }
+
+        public bool assignable_to_type(Type type) {
+            return type == typeof(LambdaElement) 
+                || type == typeof(LambdaExpression)
+                || type == typeof(Element);
+        }
+
+        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 = (T)this;
+                return true;
+            }
+            if (typeof(T) == typeof(LambdaExpression)) {
+                result = (T)_lambda;
+                return true;
+            }
+            if (typeof(T) == typeof(Element)) {
+                result = (T)(Element)this;
+                return true;
+            }
+            result = null;
+            return false;
+        }
+
+        public override string to_string() {
+            return @"Lambda[$(_lambda.parameter_name) => $(_lambda.body.to_expression_string())]";
+        }
+
+    }
+
+}

+ 602 - 0
src/lib/Expressions/EnumerableFunctionAccessor.vala

@@ -0,0 +1,602 @@
+using Invercargill.DataStructures;
+
+namespace Invercargill.Expressions {
+
+    /**
+     * FunctionAccessor implementation for Enumerable/Elements.
+     * 
+     * Provides access to common Enumerable methods like where, select,
+     * first, last, count, any, all, etc. for use within expressions.
+     */
+    public class EnumerableFunctionAccessor : Object, FunctionAccessor {
+
+        private Elements _target;
+
+        /**
+         * Creates a new EnumerableFunctionAccessor.
+         * 
+         * @param target The Elements to provide function access for
+         */
+        public EnumerableFunctionAccessor(Elements target) {
+            _target = target;
+        }
+
+        public bool has_function(string function_name) {
+            string lower_name = function_name.down();
+            foreach (var func in get_supported_functions()) {
+                if (func == lower_name) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        public Enumerable<string> get_function_names() {
+            return get_supported_functions();
+        }
+
+        private Enumerable<string> get_supported_functions() {
+            var functions = new string[] {
+                "where", "select", "first", "last", "single",
+                "first_or_default", "last_or_default", "single_or_default",
+                "take", "skip", "count", "any", "all",
+            };
+            return Wrap.array(functions);
+        }
+
+        public Element call_function(
+            string function_name,
+            Series<Element> arguments,
+            EvaluationContext context
+        ) throws ExpressionError {
+
+            var lower_name = function_name.down();
+            
+            switch (lower_name) {
+                case "where":
+                    return call_where(arguments, context);
+
+                case "select":
+                    return call_select(arguments, context);
+
+                case "first":
+                    return call_first(arguments);
+
+                case "last":
+                    return call_last(arguments);
+
+                case "single":
+                    return call_single(arguments);
+
+                case "first_or_default":
+                    return call_first_or_default(arguments);
+
+                case "last_or_default":
+                    return call_last_or_default(arguments);
+
+                case "single_or_default":
+                    return call_single_or_default(arguments);
+
+                case "take":
+                    return call_take(arguments);
+
+                case "skip":
+                    return call_skip(arguments);
+
+                case "count":
+                    return call_count(arguments);
+
+                case "any":
+                    return call_any(arguments, context);
+
+                case "all":
+                    return call_all(arguments, context);
+
+                case "order_by":
+                    return call_order_by(arguments, context);
+
+                case "order_by_descending":
+                    return call_order_by_descending(arguments, context);
+
+                case "distinct":
+                    return call_distinct();
+
+                case "element_at":
+                    return call_element_at(arguments);
+
+                case "element_at_or_default":
+                    return call_element_at_or_default(arguments);
+
+                case "concat":
+                    return call_concat(arguments);
+
+                case "reverse":
+                    return call_reverse();
+
+                case "to_array":
+                    return call_to_array();
+
+                default:
+                    throw new ExpressionError.NON_EXISTANT_PROPERTY(
+                        @"Unknown function '$function_name' for Enumerable"
+                    );
+            }
+        }
+
+        // Function implementations
+
+        private Element call_where(Series<Element> arguments, EvaluationContext context) throws ExpressionError {
+            if (arguments.length != 1) {
+                throw new ExpressionError.INVALID_SYNTAX("where() requires exactly 1 argument (a lambda)");
+            }
+
+            Element? first_arg_elem = arguments.first_or_default();
+            Element first_arg = first_arg_elem ?? new NullElement();
+            
+            var lambda = get_lambda_argument(first_arg, "where");
+
+            // 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(item);
+                    bool? bool_result = null;
+                    if (result.try_get_as(out bool_result) && bool_result != null) {
+                        return bool_result;
+                    }
+                    return false;
+                } catch (Error e) {
+                    return false;
+                }
+            });
+
+            return new NativeElement<Elements>(filtered.to_elements());
+        }
+
+        private Element call_select(Series<Element> arguments, EvaluationContext context) throws ExpressionError {
+            if (arguments.length != 1) {
+                throw new ExpressionError.INVALID_SYNTAX("select() requires exactly 1 argument (a lambda)");
+            }
+
+            Element? first_arg_elem = arguments.first_or_default();
+            Element first_arg = first_arg_elem ?? new NullElement();
+            
+            var lambda = get_lambda_argument(first_arg, "select");
+
+            // Capture the current context for closure support
+            lambda.capture_context(context);
+
+            // Apply the select transformation
+            var transformed = _target.select<Element>(item => {
+                try {
+                    return lambda.evaluate_with_argument(item);
+                } catch (Error e) {
+                    return new NullElement();
+                }
+            });
+
+            return new NativeElement<Elements>(transformed.to_elements());
+        }
+
+        private Element call_first(Series<Element> arguments) throws ExpressionError {
+            if (arguments.length > 1) {
+                throw new ExpressionError.INVALID_SYNTAX("first() takes at most 1 argument");
+            }
+
+            try {
+                return _target.first();
+            } catch (SequenceError e) {
+                return new NullElement();
+            }
+        }
+
+        private Element call_last(Series<Element> arguments) throws ExpressionError {
+            if (arguments.length > 1) {
+                throw new ExpressionError.INVALID_SYNTAX("last() takes at most 1 argument");
+            }
+
+            try {
+                return _target.last();
+            } catch (SequenceError e) {
+                return new NullElement();
+            }
+        }
+
+        private Element call_single(Series<Element> arguments) throws ExpressionError {
+            if (arguments.length > 1) {
+                throw new ExpressionError.INVALID_SYNTAX("single() takes at most 1 argument");
+            }
+
+            try {
+                return _target.single();
+            } catch (SequenceError e) {
+                return new NullElement();
+            }
+        }
+
+        private Element call_first_or_default(Series<Element> arguments) throws ExpressionError {
+            if (arguments.length > 1) {
+                throw new ExpressionError.INVALID_SYNTAX("first_or_default() takes at most 1 argument");
+            }
+
+            var result = _target.first_or_default();
+            if (result == null) {
+                return new NullElement();
+            }
+            return result;
+        }
+
+        private Element call_last_or_default(Series<Element> arguments) throws ExpressionError {
+            if (arguments.length > 1) {
+                throw new ExpressionError.INVALID_SYNTAX("last_or_default() takes at most 1 argument");
+            }
+
+            var result = _target.last_or_default();
+            if (result == null) {
+                return new NullElement();
+            }
+            return result;
+        }
+
+        private Element call_single_or_default(Series<Element> arguments) throws ExpressionError {
+            if (arguments.length > 1) {
+                throw new ExpressionError.INVALID_SYNTAX("single_or_default() takes at most 1 argument");
+            }
+
+            try {
+                var result = _target.single_or_default();
+                if (result == null) {
+                    return new NullElement();
+                }
+                return result;
+            } catch (SequenceError e) {
+                return new NullElement();
+            }
+        }
+
+        private Element call_take(Series<Element> arguments) throws ExpressionError {
+            if (arguments.length != 1) {
+                throw new ExpressionError.INVALID_SYNTAX("take() requires exactly 1 argument (count)");
+            }
+
+            int? count = null;
+            Element? countElem = arguments.first_or_default();
+            if (countElem != null) {
+                countElem.try_get_as(out count);
+            }
+            if (count == null) {
+                throw new ExpressionError.INVALID_TYPE("take() requires an integer argument");
+            }
+
+            var result = _target.take((uint)count);
+            return new NativeElement<Elements>(result.to_elements());
+        }
+
+        private Element call_skip(Series<Element> arguments) throws ExpressionError {
+            if (arguments.length != 1) {
+                throw new ExpressionError.INVALID_SYNTAX("skip() requires exactly 1 argument (count)");
+            }
+
+            int? count = null;
+            Element? countElem = arguments.first_or_default();
+            if (countElem != null) {
+                countElem.try_get_as(out count);
+            }
+            if (count == null) {
+                throw new ExpressionError.INVALID_TYPE("skip() requires an integer argument");
+            }
+
+            var result = _target.skip((uint)count);
+            return new NativeElement<Elements>(result.to_elements());
+        }
+
+        private Element call_count(Series<Element> arguments) throws ExpressionError {
+            if (arguments.length > 1) {
+                throw new ExpressionError.INVALID_SYNTAX("count() takes at most 1 argument");
+            }
+
+            // Simple count without predicate
+            var count = _target.count();
+            return new NativeElement<int?>((int)count);
+        }
+
+        private Element call_any(Series<Element> arguments, EvaluationContext context) throws ExpressionError {
+            if (arguments.length > 1) {
+                throw new ExpressionError.INVALID_SYNTAX("any() takes at most 1 argument");
+            }
+
+            if (arguments.length == 0) {
+                var result = _target.any();
+                return new NativeElement<bool?>(result);
+            }
+
+            // With predicate
+            Element? first_arg_elem = arguments.first_or_default();
+            Element first_arg = first_arg_elem ?? new NullElement();
+            
+            var lambda = get_lambda_argument(first_arg, "any");
+            lambda.capture_context(context);
+
+            var result = _target.any(item => {
+                try {
+                    var eval_result = lambda.evaluate_with_argument(item);
+                    bool? bool_result = null;
+                    if (eval_result.try_get_as(out bool_result) && bool_result != null) {
+                        return bool_result;
+                    }
+                    return false;
+                } catch (Error e) {
+                    return false;
+                }
+            });
+
+            return new NativeElement<bool?>(result);
+        }
+
+        private Element call_all(Series<Element> arguments, EvaluationContext context) throws ExpressionError {
+            if (arguments.length != 1) {
+                throw new ExpressionError.INVALID_SYNTAX("all() requires exactly 1 argument (a lambda)");
+            }
+
+            Element? first_arg_elem = arguments.first_or_default();
+            Element first_arg = first_arg_elem ?? new NullElement();
+            
+            var lambda = get_lambda_argument(first_arg, "all");
+            lambda.capture_context(context);
+
+            var result = _target.all(item => {
+                try {
+                    var eval_result = lambda.evaluate_with_argument(item);
+                    bool? bool_result = null;
+                    if (eval_result.try_get_as(out bool_result) && bool_result != null) {
+                        return bool_result;
+                    }
+                    return false;
+                } catch (Error e) {
+                    return false;
+                }
+            });
+
+            return new NativeElement<bool?>(result);
+        }
+
+        private Element call_order_by(Series<Element> arguments, EvaluationContext context) throws ExpressionError {
+            if (arguments.length != 1) {
+                throw new ExpressionError.INVALID_SYNTAX("order_by() requires exactly 1 argument (a lambda)");
+            }
+
+            Element? first_arg_elem = arguments.first_or_default();
+            Element first_arg = first_arg_elem ?? new NullElement();
+            
+            var lambda = get_lambda_argument(first_arg, "order_by");
+            lambda.capture_context(context);
+
+            var result = _target.order_by<Element>(item => {
+                try {
+                    return lambda.evaluate_with_argument(item);
+                } catch (Error e) {
+                    return new NullElement();
+                }
+            }, (a, b) => {
+                // Compare elements
+                if (a == null && b == null) return 0;
+                if (a == null) return 1;
+                if (b == null) return -1;
+
+                // Try numeric comparison
+                double? a_d = null;
+                double? b_d = null;
+                a.try_get_as(out a_d);
+                b.try_get_as(out b_d);
+                if (a_d != null && b_d != null) {
+                    if (a_d < b_d) return -1;
+                    if (a_d > b_d) return 1;
+                    return 0;
+                }
+
+                // Try string comparison
+                string? a_s = null;
+                string? b_s = null;
+                a.try_get_as(out a_s);
+                b.try_get_as(out b_s);
+                if (a_s != null && b_s != null) {
+                    return strcmp(a_s, b_s);
+                }
+
+                return 0;
+            });
+
+            return new NativeElement<Elements>(result.to_elements());
+        }
+
+        private Element call_order_by_descending(Series<Element> arguments, EvaluationContext context) throws ExpressionError {
+            if (arguments.length != 1) {
+                throw new ExpressionError.INVALID_SYNTAX("order_by_descending() requires exactly 1 argument (a lambda)");
+            }
+
+            Element? first_arg_elem = arguments.first_or_default();
+            Element first_arg = first_arg_elem ?? new NullElement();
+            
+            var lambda = get_lambda_argument(first_arg, "order_by_descending");
+            lambda.capture_context(context);
+
+            var result = _target.order_by_descending<Element>(item => {
+                try {
+                    return lambda.evaluate_with_argument(item);
+                } catch (Error e) {
+                    return new NullElement();
+                }
+            }, (a, b) => {
+                // Compare elements
+                if (a == null && b == null) return 0;
+                if (a == null) return 1;
+                if (b == null) return -1;
+
+                // Try numeric comparison
+                double? a_d = null;
+                double? b_d = null;
+                a.try_get_as(out a_d);
+                b.try_get_as(out b_d);
+                if (a_d != null && b_d != null) {
+                    if (a_d < b_d) return -1;
+                    if (a_d > b_d) return 1;
+                    return 0;
+                }
+
+                // Try string comparison
+                string? a_s = null;
+                string? b_s = null;
+                a.try_get_as(out a_s);
+                b.try_get_as(out b_s);
+                if (a_s != null && b_s != null) {
+                    return strcmp(a_s, b_s);
+                }
+
+                return 0;
+            });
+
+            return new NativeElement<Elements>(result.to_elements());
+        }
+
+        private Element call_distinct() {
+            var result = _target.distinct((a, b) => {
+                if (a == null && b == null) return true;
+                if (a == null || b == null) return false;
+
+                // Try equality comparison
+                string? a_s = null;
+                string? b_s = null;
+                a.try_get_as(out a_s);
+                b.try_get_as(out b_s);
+                if (a_s != null && b_s != null) {
+                    return a_s == b_s;
+                }
+
+                double? a_d = null;
+                double? b_d = null;
+                a.try_get_as(out a_d);
+                b.try_get_as(out b_d);
+                if (a_d != null && b_d != null) {
+                    return a_d == b_d;
+                }
+
+                int? a_i = null;
+                int? b_i = null;
+                a.try_get_as(out a_i);
+                b.try_get_as(out b_i);
+                if (a_i != null && b_i != null) {
+                    return a_i == b_i;
+                }
+
+                return a == b;
+            });
+
+            return new NativeElement<Elements>(result.to_elements());
+        }
+
+        private Element call_element_at(Series<Element> arguments) throws ExpressionError {
+            if (arguments.length != 1) {
+                throw new ExpressionError.INVALID_SYNTAX("element_at() requires exactly 1 argument (index)");
+            }
+
+            int? index = null;
+            Element? indexElem = arguments.first_or_default();
+            if (indexElem != null) {
+                indexElem.try_get_as(out index);
+            }
+            if (index == null) {
+                throw new ExpressionError.INVALID_TYPE("element_at() requires an integer argument");
+            }
+
+            try {
+                return _target.element_at((uint)index);
+            } catch (SequenceError e) {
+                return new NullElement();
+            }
+        }
+
+        private Element call_element_at_or_default(Series<Element> arguments) throws ExpressionError {
+            if (arguments.length != 1) {
+                throw new ExpressionError.INVALID_SYNTAX("element_at_or_default() requires exactly 1 argument (index)");
+            }
+
+            int? index = null;
+            Element? indexElem = arguments.first_or_default();
+            if (indexElem != null) {
+                indexElem.try_get_as(out index);
+            }
+            if (index == null) {
+                throw new ExpressionError.INVALID_TYPE("element_at_or_default() requires an integer argument");
+            }
+
+            var result = _target.element_at_or_default((uint)index);
+            if (result == null) {
+                return new NullElement();
+            }
+            return result;
+        }
+
+        private Element call_concat(Series<Element> arguments) throws ExpressionError {
+            if (arguments.length != 1) {
+                throw new ExpressionError.INVALID_SYNTAX("concat() requires exactly 1 argument (another enumerable)");
+            }
+
+            Element? first_arg_elem = arguments.first_or_default();
+            Elements? other = null;
+            if (first_arg_elem == null) {
+                throw new ExpressionError.INVALID_TYPE("concat() requires an Enumerable argument");
+            }
+            
+            first_arg_elem.try_get_as(out other);
+            if (other == null) {
+                // Try to convert from Enumerable
+                Enumerable<Element>? other_enum = null;
+                first_arg_elem.try_get_as(out other_enum);
+                if (other_enum != null) {
+                    other = other_enum.to_elements();
+                } else {
+                    throw new ExpressionError.INVALID_TYPE("concat() requires an Enumerable argument");
+                }
+            }
+
+            var result = _target.concat(other);
+            return new NativeElement<Elements>(result.to_elements());
+        }
+
+        private Element call_reverse() {
+            var result = _target.reverse();
+            return new NativeElement<Elements>(result.to_elements());
+        }
+
+        private Element call_to_array() {
+            // Return the elements as-is since arrays can't be generic type arguments in Vala
+            // and can't be wrapped in ValueElement directly
+            // The caller can iterate the elements directly
+            return new NativeElement<Elements>(_target);
+        }
+
+        // Helper methods
+
+        private LambdaExpression get_lambda_argument(Element arg, string function_name) throws ExpressionError {
+            LambdaExpression? lambda = null;
+
+            // Try to get as LambdaElement first
+            LambdaElement? lambda_elem = null;
+            if (arg.try_get_as(out lambda_elem) && lambda_elem != null) {
+                lambda = lambda_elem.get_lambda();
+            }
+            // Try to get as LambdaExpression directly
+            else if (!arg.try_get_as(out lambda) || lambda == null) {
+                throw new ExpressionError.INVALID_TYPE(
+                    @"$function_name() requires a lambda argument"
+                );
+            }
+
+            return lambda;
+        }
+
+    }
+
+}

+ 140 - 0
src/lib/Expressions/EvaluationContext.vala

@@ -0,0 +1,140 @@
+
+namespace Invercargill.Expressions {
+
+    /**
+     * Manages variable scopes and closures for expression evaluation.
+     * 
+     * The EvaluationContext maintains a chain of scopes via the parent reference,
+     * enabling closure support for lambda expressions. Variables are resolved
+     * by searching the current scope first, then parent scopes.
+     */
+    public class EvaluationContext : Object {
+
+        /**
+         * Root values available in the expression (from Properties).
+         */
+        public Properties root_values { get; construct set; }
+
+        /**
+         * Parent context for closure support.
+         */
+        public EvaluationContext? parent { get; construct set; }
+
+        /**
+         * Lambda parameter name (only in child scopes).
+         */
+        private string? _parameter_name;
+
+        /**
+         * Lambda parameter value (only in child scopes).
+         */
+        private Element? _parameter_value;
+
+        /**
+         * Creates a new root evaluation context.
+         * 
+         * @param root_values The Properties object containing root-level variables
+         */
+        public EvaluationContext(Properties root_values) {
+            Object(root_values: root_values);
+        }
+
+        /**
+         * Creates a child scope for lambda execution.
+         * 
+         * @param parent The parent context
+         * @param param_name The parameter name for the lambda
+         * @param param_value The value to bind to the parameter
+         */
+        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;
+        }
+
+        /**
+         * Gets a variable by name, searching current scope then parent scopes.
+         * 
+         * @param name The variable name to look up
+         * @return The Element value of the variable
+         * @throws ExpressionError if the variable is not found
+         */
+        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"
+            );
+        }
+
+        /**
+         * Checks if a variable exists in any accessible scope.
+         * 
+         * @param name The variable name to check
+         * @return true if the variable exists, false otherwise
+         */
+        public bool has_variable(string name) {
+            // Check if it's the lambda parameter
+            if (_parameter_name != null && _parameter_name == name) {
+                return true;
+            }
+
+            // Check parent scope
+            if (parent != null) {
+                return parent.has_variable(name);
+            }
+
+            // Check root values
+            return root_values.has(name);
+        }
+
+        /**
+         * Creates a child scope for lambda execution.
+         * 
+         * @param param_name The parameter name for the lambda
+         * @param param_value The value to bind to the parameter
+         * @return A new EvaluationContext with this context as parent
+         */
+        public EvaluationContext create_child_scope(string param_name, Element param_value) {
+            return new EvaluationContext.with_parent(this, param_name, param_value);
+        }
+
+        /**
+         * Gets the parameter name for this scope (if any).
+         * 
+         * @return The parameter name, or null if this is not a lambda scope
+         */
+        public string? get_parameter_name() {
+            return _parameter_name;
+        }
+
+        /**
+         * Gets the parameter value for this scope (if any).
+         * 
+         * @return The parameter value, or null if this is not a lambda scope
+         */
+        public Element? get_parameter_value() {
+            return _parameter_value;
+        }
+
+    }
+
+}

+ 53 - 0
src/lib/Expressions/Expression.vala

@@ -0,0 +1,53 @@
+
+namespace Invercargill.Expressions {
+
+    /**
+     * Base interface for all expression types.
+     * 
+     * Expressions can be evaluated to produce a value, or visited for
+     * translation/traversal purposes.
+     */
+    public interface Expression : Object {
+
+        /**
+         * The type of this expression.
+         */
+        public abstract ExpressionType expression_type { get; }
+
+        /**
+         * Evaluate the expression with the given context.
+         * 
+         * @param context The evaluation context containing variables and scope
+         * @return The result of evaluating this expression
+         * @throws ExpressionError if evaluation fails
+         */
+        public abstract Element evaluate(EvaluationContext context) throws ExpressionError;
+
+        /**
+         * Accept a visitor for traversal or translation.
+         * 
+         * @param visitor The visitor to accept
+         */
+        public abstract void accept(ExpressionVisitor visitor);
+
+        /**
+         * Get the expected return type of this expression.
+         * 
+         * @return The Type of the result, or null if unknown
+         */
+        public virtual Type? get_return_type() { 
+            return null; 
+        }
+
+        /**
+         * Get a string representation of this expression.
+         * 
+         * @return A string representation useful for debugging
+         */
+        public virtual string to_expression_string() { 
+            return expression_type.to_string(); 
+        }
+
+    }
+
+}

+ 17 - 0
src/lib/Expressions/ExpressionError.vala

@@ -0,0 +1,17 @@
+
+
+namespace Invercargill.Expressions {
+
+    public errordomain ExpressionError {
+        INVALID_TYPE,
+        NON_EXISTANT_PROPERTY,
+        INVALID_SYNTAX,
+        TYPE_MISMATCH,
+        INVALID_OPERATOR,
+        DIVISION_BY_ZERO,
+        UNDEFINED_VARIABLE,
+        INVALID_ARGUMENTS,
+        FUNCTION_NOT_FOUND;
+    }
+
+}

+ 146 - 0
src/lib/Expressions/ExpressionEvaluator.vala

@@ -0,0 +1,146 @@
+
+namespace Invercargill.Expressions {
+
+    /**
+     * Facade for evaluating expressions.
+     * 
+     * Provides a simple interface for evaluating expression trees with
+     * a Properties object containing root values.
+     */
+    public class ExpressionEvaluator : Object {
+
+        /**
+         * Evaluates an expression with the given root values.
+         * 
+         * @param expression The expression to evaluate
+         * @param root_values The Properties object containing root-level variables
+         * @return The result of evaluating the expression
+         * @throws ExpressionError if evaluation fails
+         */
+        public Element evaluate(Expression expression, Properties root_values) throws ExpressionError {
+            var context = new EvaluationContext(root_values);
+            return expression.evaluate(context);
+        }
+
+        /**
+         * Evaluates an expression and returns the result as a specific type.
+         * 
+         * @param expression The expression to evaluate
+         * @param root_values The Properties object containing root-level variables
+         * @return The result of evaluating the expression as type T
+         * @throws Error if evaluation fails or type conversion fails
+         */
+        public T evaluate_as<T>(Expression expression, Properties root_values) throws Error {
+            var result = evaluate(expression, root_values);
+            return result.as<T>();
+        }
+
+        /**
+         * Evaluates an expression and returns the result as a boolean.
+         * 
+         * @param expression The expression to evaluate
+         * @param root_values The Properties object containing root-level variables
+         * @return The boolean result
+         * @throws Error if evaluation fails or result is not a boolean
+         */
+        public bool evaluate_as_bool(Expression expression, Properties root_values) throws Error {
+            var result = evaluate(expression, root_values);
+            bool? bool_val;
+            if (result.try_get_as(out bool_val)) {
+                return bool_val;
+            }
+            throw new ExpressionError.INVALID_TYPE(
+                @"Expected boolean result, got $(result.type_name())"
+            );
+        }
+
+        /**
+         * Evaluates an expression and returns the result as an integer.
+         * 
+         * @param expression The expression to evaluate
+         * @param root_values The Properties object containing root-level variables
+         * @return The integer result
+         * @throws Error if evaluation fails or result is not an integer
+         */
+        public int evaluate_as_int(Expression expression, Properties root_values) throws Error {
+            var result = evaluate(expression, root_values);
+            int? int_val;
+            if (result.try_get_as(out int_val)) {
+                return int_val;
+            }
+            throw new ExpressionError.INVALID_TYPE(
+                @"Expected integer result, got $(result.type_name())"
+            );
+        }
+
+        /**
+         * Evaluates an expression and returns the result as a string.
+         * 
+         * @param expression The expression to evaluate
+         * @param root_values The Properties object containing root-level variables
+         * @return The string result
+         * @throws Error if evaluation fails or result is not a string
+         */
+        public string evaluate_as_string(Expression expression, Properties root_values) throws Error {
+            var result = evaluate(expression, root_values);
+            string? str_val;
+            if (result.try_get_as(out str_val)) {
+                return str_val;
+            }
+            throw new ExpressionError.INVALID_TYPE(
+                @"Expected string result, got $(result.type_name())"
+            );
+        }
+
+        /**
+         * Evaluates a property chain expression.
+         * 
+         * Convenience method for creating and evaluating a simple property chain
+         * like "a.b.c" from an array of property names.
+         * 
+         * @param properties The property names in order (e.g., ["a", "b", "c"])
+         * @param root_values The Properties object containing root-level variables
+         * @return The result of evaluating the property chain
+         * @throws ExpressionError if evaluation fails
+         */
+        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);
+        }
+
+        /**
+         * Creates an evaluation context for the given root values.
+         * 
+         * Useful when you need to evaluate multiple expressions with the same context.
+         * 
+         * @param root_values The Properties object containing root-level variables
+         * @return A new EvaluationContext
+         */
+        public EvaluationContext create_context(Properties root_values) {
+            return new EvaluationContext(root_values);
+        }
+
+        /**
+         * Evaluates an expression with an existing context.
+         * 
+         * @param expression The expression to evaluate
+         * @param context The evaluation context
+         * @return The result of evaluating the expression
+         * @throws ExpressionError if evaluation fails
+         */
+        public Element evaluate_with_context(Expression expression, EvaluationContext context) throws ExpressionError {
+            return expression.evaluate(context);
+        }
+
+    }
+
+}

+ 435 - 0
src/lib/Expressions/ExpressionParser.vala

@@ -0,0 +1,435 @@
+using Invercargill.DataStructures;
+
+namespace Invercargill.Expressions {
+
+    /**
+     * Parser for expression strings.
+     * 
+     * Converts expression strings into Expression trees using recursive descent parsing.
+     * Supports:
+     * - Literals: integers, floats, strings (single/double quotes), booleans, null
+     * - Variables: identifiers
+     * - Operators: +, -, *, /, %, ==, !=, <, >, <=, >=, &&, ||, !
+     * - Ternary: condition ? true_expr : false_expr
+     * - Property access: obj.property
+     * - Function calls: obj.method(args...)
+     * - Lambdas: x => expression
+     * - Parentheses: (expression)
+     * 
+     * Operator precedence (lowest to highest):
+     * 1. Ternary: ? :
+     * 2. Or: ||
+     * 3. And: &&
+     * 4. Equality: ==, !=
+     * 5. Comparison: <, >, <=, >=
+     * 6. Additive: +, -
+     * 7. Multiplicative: *, /, %
+     * 8. Unary: !, -
+     * 9. Postfix: .property, .method(args), (args)
+     * 10. Primary: literals, variables, parentheses
+     */
+    public class ExpressionParser : Object {
+
+        private Token[] _tokens;
+        private int _position;
+
+        /**
+         * Creates a new parser for the given token stream.
+         *
+         * @param tokens The tokens to parse
+         */
+        public ExpressionParser(Series<Token> tokens) {
+            _tokens = tokens.to_array();
+            _position = 0;
+        }
+
+        /**
+         * Parses an expression string and returns the expression tree.
+         * 
+         * @param input The expression string to parse
+         * @return The root of the expression tree
+         * @throws ExpressionError if parsing fails
+         */
+        public static Expression parse(string input) throws ExpressionError {
+            var tokenizer = new ExpressionTokenizer(input);
+            var tokens = tokenizer.tokenize_all();
+            var parser = new ExpressionParser(tokens);
+            return parser.parse_expression();
+        }
+
+        /**
+         * Parses the token stream and returns the expression tree.
+         * 
+         * @return The root of the expression tree
+         * @throws ExpressionError if parsing fails
+         */
+        public Expression parse_expression() throws ExpressionError {
+            var expr = parse_ternary();
+            
+            // Ensure we've consumed all tokens
+            if (!is_at_end()) {
+                var token = peek();
+                throw new ExpressionError.INVALID_SYNTAX(
+                    @"Unexpected token '$(token.value)' at position $(token.position)"
+                );
+            }
+            
+            return expr;
+        }
+
+        // ==================== Precedence Levels ====================
+
+        // Ternary: ? :
+        private Expression parse_ternary() throws ExpressionError {
+            var condition = parse_or();
+
+            if (match(TokenType.QUESTION)) {
+                var true_expr = parse_ternary();
+                expect(TokenType.COLON, "Expected ':' in ternary expression");
+                var false_expr = parse_ternary();
+                return new TernaryExpression(condition, true_expr, false_expr);
+            }
+
+            return condition;
+        }
+
+        // Or: ||
+        private Expression parse_or() throws ExpressionError {
+            var left = parse_and();
+
+            while (match(TokenType.OR)) {
+                var right = parse_and();
+                left = new BinaryExpression(left, right, BinaryOperator.OR);
+            }
+
+            return left;
+        }
+
+        // And: &&
+        private Expression parse_and() throws ExpressionError {
+            var left = parse_equality();
+
+            while (match(TokenType.AND)) {
+                var right = parse_equality();
+                left = new BinaryExpression(left, right, BinaryOperator.AND);
+            }
+
+            return left;
+        }
+
+        // Equality: ==, !=
+        private Expression parse_equality() throws ExpressionError {
+            var left = parse_comparison();
+
+            while (true) {
+                if (match(TokenType.EQUALS)) {
+                    var right = parse_comparison();
+                    left = new BinaryExpression(left, right, BinaryOperator.EQUAL);
+                } else if (match(TokenType.NOT_EQUALS)) {
+                    var right = parse_comparison();
+                    left = new BinaryExpression(left, right, BinaryOperator.NOT_EQUAL);
+                } else {
+                    break;
+                }
+            }
+
+            return left;
+        }
+
+        // Comparison: <, >, <=, >=
+        private Expression parse_comparison() throws ExpressionError {
+            var left = parse_additive();
+
+            while (true) {
+                if (match(TokenType.LESS_THAN)) {
+                    var right = parse_additive();
+                    left = new BinaryExpression(left, right, BinaryOperator.LESS_THAN);
+                } else if (match(TokenType.GREATER_THAN)) {
+                    var right = parse_additive();
+                    left = new BinaryExpression(left, right, BinaryOperator.GREATER_THAN);
+                } else if (match(TokenType.LESS_EQUALS)) {
+                    var right = parse_additive();
+                    left = new BinaryExpression(left, right, BinaryOperator.LESS_EQUAL);
+                } else if (match(TokenType.GREATER_EQUALS)) {
+                    var right = parse_additive();
+                    left = new BinaryExpression(left, right, BinaryOperator.GREATER_EQUAL);
+                } else {
+                    break;
+                }
+            }
+
+            return left;
+        }
+
+        // Additive: +, -
+        private Expression parse_additive() throws ExpressionError {
+            var left = parse_multiplicative();
+
+            while (true) {
+                if (match(TokenType.PLUS)) {
+                    var right = parse_multiplicative();
+                    left = new BinaryExpression(left, right, BinaryOperator.ADD);
+                } else if (match(TokenType.MINUS)) {
+                    var right = parse_multiplicative();
+                    left = new BinaryExpression(left, right, BinaryOperator.SUBTRACT);
+                } else {
+                    break;
+                }
+            }
+
+            return left;
+        }
+
+        // Multiplicative: *, /, %
+        private Expression parse_multiplicative() throws ExpressionError {
+            var left = parse_unary();
+
+            while (true) {
+                if (match(TokenType.STAR)) {
+                    var right = parse_unary();
+                    left = new BinaryExpression(left, right, BinaryOperator.MULTIPLY);
+                } else if (match(TokenType.SLASH)) {
+                    var right = parse_unary();
+                    left = new BinaryExpression(left, right, BinaryOperator.DIVIDE);
+                } else if (match(TokenType.PERCENT)) {
+                    var right = parse_unary();
+                    left = new BinaryExpression(left, right, BinaryOperator.MODULO);
+                } else {
+                    break;
+                }
+            }
+
+            return left;
+        }
+
+        // Unary: !, -
+        private Expression parse_unary() throws ExpressionError {
+            if (match(TokenType.NOT)) {
+                var operand = parse_unary();
+                return new UnaryExpression(UnaryOperator.NOT, operand);
+            }
+            if (match(TokenType.MINUS)) {
+                var operand = parse_unary();
+                return new UnaryExpression(UnaryOperator.NEGATE, operand);
+            }
+
+            return parse_postfix();
+        }
+
+        // Postfix: .property, .method(args)
+        private Expression parse_postfix() throws ExpressionError {
+            var expr = parse_primary();
+
+            while (true) {
+                if (match(TokenType.DOT)) {
+                    var name_token = expect(TokenType.IDENTIFIER, "Expected property or method name after '.'");
+                    
+                    if (match(TokenType.LPAREN)) {
+                        // Function call
+                        var args = parse_arguments();
+                        expect(TokenType.RPAREN, "Expected ')' after function arguments");
+                        expr = new FunctionCallExpression(expr, name_token.value, args);
+                    } else {
+                        // Property access
+                        expr = new PropertyExpression(expr, name_token.value);
+                    }
+                } else {
+                    break;
+                }
+            }
+
+            return expr;
+        }
+
+        // Primary: literals, variables, parentheses, lambdas
+        private Expression parse_primary() throws ExpressionError {
+            // Parenthesized expression or lambda
+            if (match(TokenType.LPAREN)) {
+                // Check if this is a lambda: (x) => expr or () => expr
+                // We need to look ahead for => after the closing paren
+                var saved_position = _position;
+                
+                // Try to parse as just identifiers followed by )
+                var param_names = new Series<string>();
+                bool is_lambda = false;
+                
+                // Empty parens: () => expr
+                if (check(TokenType.RPAREN)) {
+                    advance();
+                    if (match(TokenType.ARROW)) {
+                        is_lambda = true;
+                    } else {
+                        // Just empty parens - error
+                        throw new ExpressionError.INVALID_SYNTAX(
+                            "Empty parentheses are not valid"
+                        );
+                    }
+                } else {
+                    // Try to parse as parameter list
+                    while (!check(TokenType.RPAREN) && !is_at_end()) {
+                        if (check(TokenType.IDENTIFIER)) {
+                            param_names.add(advance().value);
+                            if (!check(TokenType.RPAREN)) {
+                                if (!match(TokenType.COMMA)) {
+                                    break; // Not a lambda parameter list
+                                }
+                            }
+                        } else {
+                            break; // Not a lambda parameter list
+                        }
+                    }
+                    
+                    if (check(TokenType.RPAREN)) {
+                        advance(); // consume )
+                        if (match(TokenType.ARROW)) {
+                            is_lambda = true;
+                        }
+                    }
+                }
+                
+                if (is_lambda) {
+                    // It's a lambda with parenthesized parameters
+                    if (param_names.length != 1) {
+                        throw new ExpressionError.INVALID_SYNTAX(
+                            "Lambda expressions require exactly one parameter"
+                        );
+                    }
+                    var body = parse_ternary();
+                    return new LambdaExpression(param_names.first(), body);
+                } else {
+                    // Reset and parse as grouped expression
+                    _position = saved_position;
+                    var expr = parse_ternary();
+                    expect(TokenType.RPAREN, "Expected ')' after expression");
+                    return new BracketedExpression(expr);
+                }
+            }
+
+            // Lambda: identifier => expr
+            if (check(TokenType.IDENTIFIER)) {
+                // Look ahead for =>
+                var saved_position = _position;
+                var name_token = advance();
+                
+                if (match(TokenType.ARROW)) {
+                    // It's a lambda: x => expr
+                    var body = parse_ternary();
+                    return new LambdaExpression(name_token.value, body);
+                } else {
+                    // Reset and continue as variable
+                    _position = saved_position;
+                }
+            }
+
+            // Literals and variables
+            if (match(TokenType.INTEGER)) {
+                var token = previous();
+                int64 value = int64.parse(token.value);
+                return new LiteralExpression(new NativeElement<int64?>(value));
+            }
+
+            if (match(TokenType.FLOAT)) {
+                var token = previous();
+                double value = double.parse(token.value);
+                return new LiteralExpression(new NativeElement<double?>(value));
+            }
+
+            if (match(TokenType.STRING)) {
+                var token = previous();
+                return new LiteralExpression(new NativeElement<string?>(token.value));
+            }
+
+            if (match(TokenType.TRUE)) {
+                return new LiteralExpression(new NativeElement<bool?>(true));
+            }
+
+            if (match(TokenType.FALSE)) {
+                return new LiteralExpression(new NativeElement<bool?>(false));
+            }
+
+            if (match(TokenType.NULL_LITERAL)) {
+                return new LiteralExpression(new NullElement());
+            }
+
+            // Variable
+            if (match(TokenType.IDENTIFIER)) {
+                var token = previous();
+                return new VariableExpression(token.value);
+            }
+
+            // Error: unexpected token
+            var current = peek();
+            throw new ExpressionError.INVALID_SYNTAX(
+                @"Unexpected token '$(current.value)' at position $(current.position)"
+            );
+        }
+
+        // Parse function call arguments
+        private Series<Expression> parse_arguments() throws ExpressionError {
+            var args = new Series<Expression>();
+
+            if (check(TokenType.RPAREN)) {
+                return args; // Empty argument list
+            }
+
+            args.add(parse_ternary());
+
+            while (match(TokenType.COMMA)) {
+                args.add(parse_ternary());
+            }
+
+            return args;
+        }
+
+        // ==================== Helper Methods ====================
+
+        private bool is_at_end() {
+            return peek().token_type == TokenType.EOF;
+        }
+
+        private Token peek() {
+            if (_position >= _tokens.length) {
+                return new Token(TokenType.EOF, "", _position);
+            }
+            return _tokens[_position];
+        }
+
+        private Token previous() {
+            if (_position <= 0 || _position > _tokens.length) {
+                return new Token(TokenType.EOF, "", _position - 1);
+            }
+            return _tokens[_position - 1];
+        }
+
+        private Token advance() {
+            if (!is_at_end()) {
+                _position++;
+            }
+            return previous();
+        }
+
+        private bool check(TokenType type) {
+            if (is_at_end()) return false;
+            return peek().token_type == type;
+        }
+
+        private bool match(TokenType type) {
+            if (check(type)) {
+                advance();
+                return true;
+            }
+            return false;
+        }
+
+        private Token expect(TokenType type, string message) throws ExpressionError {
+            if (check(type)) {
+                return advance();
+            }
+            var current = peek();
+            throw new ExpressionError.INVALID_SYNTAX(
+                @"$message, got '$(current.value)' at position $(current.position)"
+            );
+        }
+    }
+
+}

+ 388 - 0
src/lib/Expressions/ExpressionTokenizer.vala

@@ -0,0 +1,388 @@
+using Invercargill.DataStructures;
+
+namespace Invercargill.Expressions {
+
+    /**
+     * Token types for expression parsing.
+     */
+    public enum TokenType {
+        // Literals
+        INTEGER,
+        FLOAT,
+        STRING,
+        TRUE,
+        FALSE,
+        NULL_LITERAL,
+
+        // Identifiers and keywords
+        IDENTIFIER,
+
+        // Operators
+        PLUS,           // +
+        MINUS,          // -
+        STAR,           // *
+        SLASH,          // /
+        PERCENT,        // %
+        EQUALS,         // ==
+        NOT_EQUALS,     // !=
+        LESS_THAN,      // <
+        GREATER_THAN,   // >
+        LESS_EQUALS,    // <=
+        GREATER_EQUALS, // >=
+        AND,            // &&
+        OR,             // ||
+        NOT,            // !
+        ASSIGN,         // = (for single equals, used in some contexts)
+
+        // Punctuation
+        DOT,            // .
+        COMMA,          // ,
+        LPAREN,         // (
+        RPAREN,         // )
+        QUESTION,       // ?
+        COLON,          // :
+        ARROW,          // =>
+
+        // Special
+        EOF;
+
+        public string to_string() {
+            switch (this) {
+                case INTEGER: return "INTEGER";
+                case FLOAT: return "FLOAT";
+                case STRING: return "STRING";
+                case TRUE: return "TRUE";
+                case FALSE: return "FALSE";
+                case NULL_LITERAL: return "NULL";
+                case IDENTIFIER: return "IDENTIFIER";
+                case PLUS: return "+";
+                case MINUS: return "-";
+                case STAR: return "*";
+                case SLASH: return "/";
+                case PERCENT: return "%";
+                case EQUALS: return "==";
+                case NOT_EQUALS: return "!=";
+                case LESS_THAN: return "<";
+                case GREATER_THAN: return ">";
+                case LESS_EQUALS: return "<=";
+                case GREATER_EQUALS: return ">=";
+                case AND: return "&&";
+                case OR: return "||";
+                case NOT: return "!";
+                case ASSIGN: return "=";
+                case DOT: return ".";
+                case COMMA: return ",";
+                case LPAREN: return "(";
+                case RPAREN: return ")";
+                case QUESTION: return "?";
+                case COLON: return ":";
+                case ARROW: return "=>";
+                case EOF: return "EOF";
+                default: return "UNKNOWN";
+            }
+        }
+    }
+
+    /**
+     * A token from the expression tokenizer.
+     */
+    public class Token : Object {
+        public TokenType token_type { get; private set; }
+        public string value { get; private set; }
+        public int position { get; private set; }
+
+        public Token(TokenType token_type, string value, int position) {
+            this.token_type = token_type;
+            this.value = value;
+            this.position = position;
+        }
+
+        public string to_string() {
+            return @"Token($(token_type.to_string()), \"$value\", pos=$position)";
+        }
+    }
+
+    /**
+     * Tokenizer for expression strings.
+     * 
+     * Converts an expression string into a stream of tokens for parsing.
+     */
+    public class ExpressionTokenizer : Object {
+
+        private string _input;
+        private int _position;
+        private int _length;
+
+        /**
+         * Creates a new tokenizer for the given input string.
+         * 
+         * @param input The expression string to tokenize
+         */
+        public ExpressionTokenizer(string input) {
+            _input = input;
+            _position = 0;
+            _length = input.length;
+        }
+
+        /**
+         * Tokenizes the entire input and returns all tokens.
+         * 
+         * @return A list of all tokens including the EOF token
+         * @throws ExpressionError if tokenization fails
+         */
+        public Series<Token> tokenize_all() throws ExpressionError {
+            var tokens = new Series<Token>();
+            
+            Token token;
+            while ((token = next_token()).token_type != TokenType.EOF) {
+                tokens.add(token);
+            }
+            tokens.add(token); // Add EOF token
+            
+            return tokens;
+        }
+
+        /**
+         * Gets the next token from the input.
+         * 
+         * @return The next token, or EOF if at end of input
+         * @throws ExpressionError if tokenization fails
+         */
+        public Token next_token() throws ExpressionError {
+            skip_whitespace();
+
+            if (_position >= _length) {
+                return new Token(TokenType.EOF, "", _position);
+            }
+
+            char c = _input[_position];
+            int start_pos = _position;
+
+            // Single character tokens
+            switch (c) {
+                case '+':
+                    _position++;
+                    return new Token(TokenType.PLUS, "+", start_pos);
+                case '*':
+                    _position++;
+                    return new Token(TokenType.STAR, "*", start_pos);
+                case '/':
+                    _position++;
+                    return new Token(TokenType.SLASH, "/", start_pos);
+                case '%':
+                    _position++;
+                    return new Token(TokenType.PERCENT, "%", start_pos);
+                case '.':
+                    _position++;
+                    return new Token(TokenType.DOT, ".", start_pos);
+                case ',':
+                    _position++;
+                    return new Token(TokenType.COMMA, ",", start_pos);
+                case '(':
+                    _position++;
+                    return new Token(TokenType.LPAREN, "(", start_pos);
+                case ')':
+                    _position++;
+                    return new Token(TokenType.RPAREN, ")", start_pos);
+                case '?':
+                    _position++;
+                    return new Token(TokenType.QUESTION, "?", start_pos);
+                case ':':
+                    _position++;
+                    return new Token(TokenType.COLON, ":", start_pos);
+            }
+
+            // Two-character operators
+            if (_position + 1 < _length) {
+                string two_char = _input.substring(_position, 2);
+                
+                if (two_char == "==" ) {
+                    _position += 2;
+                    return new Token(TokenType.EQUALS, "==", start_pos);
+                }
+                if (two_char == "!=") {
+                    _position += 2;
+                    return new Token(TokenType.NOT_EQUALS, "!=", start_pos);
+                }
+                if (two_char == "<=") {
+                    _position += 2;
+                    return new Token(TokenType.LESS_EQUALS, "<=", start_pos);
+                }
+                if (two_char == ">=") {
+                    _position += 2;
+                    return new Token(TokenType.GREATER_EQUALS, ">=", start_pos);
+                }
+                if (two_char == "&&") {
+                    _position += 2;
+                    return new Token(TokenType.AND, "&&", start_pos);
+                }
+                if (two_char == "||") {
+                    _position += 2;
+                    return new Token(TokenType.OR, "||", start_pos);
+                }
+                if (two_char == "=>") {
+                    _position += 2;
+                    return new Token(TokenType.ARROW, "=>", start_pos);
+                }
+            }
+
+            // Single character operators that might be part of two-char
+            if (c == '<') {
+                _position++;
+                return new Token(TokenType.LESS_THAN, "<", start_pos);
+            }
+            if (c == '>') {
+                _position++;
+                return new Token(TokenType.GREATER_THAN, ">", start_pos);
+            }
+            if (c == '!') {
+                _position++;
+                return new Token(TokenType.NOT, "!", start_pos);
+            }
+            if (c == '=') {
+                _position++;
+                return new Token(TokenType.ASSIGN, "=", start_pos);
+            }
+            if (c == '-') {
+                _position++;
+                return new Token(TokenType.MINUS, "-", start_pos);
+            }
+
+            // String literals
+            if (c == '"' || c == '\'') {
+                return read_string(c);
+            }
+
+            // Numbers
+            if (c.isdigit()) {
+                return read_number();
+            }
+
+            // Identifiers and keywords
+            if (c.isalpha() || c == '_') {
+                return read_identifier();
+            }
+
+            throw new ExpressionError.INVALID_SYNTAX(
+                @"Unexpected character '$c' at position $(_position)"
+            );
+        }
+
+        private void skip_whitespace() {
+            while (_position < _length && _input[_position].isspace()) {
+                _position++;
+            }
+        }
+
+        private Token read_string(char quote) throws ExpressionError {
+            int start_pos = _position;
+            _position++; // Skip opening quote
+
+            var sb = new StringBuilder();
+
+            while (_position < _length) {
+                char c = _input[_position];
+
+                if (c == quote) {
+                    _position++; // Skip closing quote
+                    return new Token(TokenType.STRING, sb.str, start_pos);
+                }
+
+                if (c == '\\') {
+                    _position++;
+                    if (_position >= _length) {
+                        throw new ExpressionError.INVALID_SYNTAX(
+                            @"Unterminated string at position $start_pos"
+                        );
+                    }
+                    char escaped = _input[_position];
+                    switch (escaped) {
+                        case 'n': sb.append("\n"); break;
+                        case 't': sb.append("\t"); break;
+                        case 'r': sb.append("\r"); break;
+                        case '\\': sb.append("\\"); break;
+                        case '"': sb.append("\""); break;
+                        case '\'': sb.append("'"); break;
+                        default:
+                            throw new ExpressionError.INVALID_SYNTAX(
+                                @"Unknown escape sequence '\\$escaped' at position $(_position)"
+                            );
+                    }
+                    _position++;
+                } else {
+                    sb.append_c(c);
+                    _position++;
+                }
+            }
+
+            throw new ExpressionError.INVALID_SYNTAX(
+                @"Unterminated string starting at position $start_pos"
+            );
+        }
+
+        private Token read_number() {
+            int start_pos = _position;
+            var sb = new StringBuilder();
+            bool has_decimal = false;
+
+            // Read integer part
+            while (_position < _length && _input[_position].isdigit()) {
+                sb.append_c(_input[_position]);
+                _position++;
+            }
+
+            // Check for decimal point
+            if (_position < _length && _input[_position] == '.') {
+                // Look ahead to make sure it's not a property access
+                if (_position + 1 < _length && _input[_position + 1].isdigit()) {
+                    has_decimal = true;
+                    sb.append_c('.');
+                    _position++;
+
+                    // Read decimal part
+                    while (_position < _length && _input[_position].isdigit()) {
+                        sb.append_c(_input[_position]);
+                        _position++;
+                    }
+                }
+            }
+
+            string value = sb.str;
+            if (has_decimal) {
+                return new Token(TokenType.FLOAT, value, start_pos);
+            } else {
+                return new Token(TokenType.INTEGER, value, start_pos);
+            }
+        }
+
+        private Token read_identifier() {
+            int start_pos = _position;
+            var sb = new StringBuilder();
+
+            while (_position < _length) {
+                char c = _input[_position];
+                if (c.isalnum() || c == '_') {
+                    sb.append_c(c);
+                    _position++;
+                } else {
+                    break;
+                }
+            }
+
+            string value = sb.str;
+
+            // Check for keywords
+            switch (value.down()) {
+                case "true":
+                    return new Token(TokenType.TRUE, value, start_pos);
+                case "false":
+                    return new Token(TokenType.FALSE, value, start_pos);
+                case "null":
+                    return new Token(TokenType.NULL_LITERAL, value, start_pos);
+                default:
+                    return new Token(TokenType.IDENTIFIER, value, start_pos);
+            }
+        }
+    }
+
+}

+ 34 - 0
src/lib/Expressions/ExpressionType.vala

@@ -0,0 +1,34 @@
+
+namespace Invercargill.Expressions {
+
+    /**
+     * Enumeration of all supported expression types.
+     */
+    public enum ExpressionType {
+        LITERAL,
+        VARIABLE,
+        BINARY,
+        UNARY,
+        TERNARY,
+        PROPERTY,
+        FUNCTION_CALL,
+        LAMBDA,
+        BRACKETED;
+
+        public string to_string() {
+            switch (this) {
+                case LITERAL: return "Literal";
+                case VARIABLE: return "Variable";
+                case BINARY: return "Binary";
+                case UNARY: return "Unary";
+                case TERNARY: return "Ternary";
+                case PROPERTY: return "Property";
+                case FUNCTION_CALL: return "FunctionCall";
+                case LAMBDA: return "Lambda";
+                case BRACKETED: return "Bracketed";
+                default: return "Unknown";
+            }
+        }
+    }
+
+}

+ 65 - 0
src/lib/Expressions/ExpressionVisitor.vala

@@ -0,0 +1,65 @@
+
+namespace Invercargill.Expressions {
+
+    /**
+     * Visitor interface for expression traversal and translation.
+     * 
+     * Implement this interface to traverse expression trees for purposes such as:
+     * - SQL translation for ORM functionality
+     * - Expression optimization
+     * - Debugging/logging
+     * - Validation
+     * 
+     * All methods have empty default implementations, so visitors only need
+     * to override the methods they care about.
+     */
+    public interface ExpressionVisitor : Object {
+
+        /**
+         * Visit a literal expression.
+         */
+        public virtual void visit_literal(LiteralExpression expr) {}
+
+        /**
+         * Visit a variable expression.
+         */
+        public virtual void visit_variable(VariableExpression expr) {}
+
+        /**
+         * Visit a binary expression.
+         */
+        public virtual void visit_binary(BinaryExpression expr) {}
+
+        /**
+         * Visit a unary expression.
+         */
+        public virtual void visit_unary(UnaryExpression expr) {}
+
+        /**
+         * Visit a ternary expression.
+         */
+        public virtual void visit_ternary(TernaryExpression expr) {}
+
+        /**
+         * Visit a property expression.
+         */
+        public virtual void visit_property(PropertyExpression expr) {}
+
+        /**
+         * Visit a function call expression.
+         */
+        public virtual void visit_function_call(FunctionCallExpression expr) {}
+
+        /**
+         * Visit a lambda expression.
+         */
+        public virtual void visit_lambda(LambdaExpression expr) {}
+
+        /**
+         * Visit a bracketed expression.
+         */
+        public virtual void visit_bracketed(BracketedExpression expr) {}
+
+    }
+
+}

+ 355 - 0
src/lib/Expressions/Expressions/BinaryExpression.vala

@@ -0,0 +1,355 @@
+using Invercargill.DataStructures;
+using Invercargill.Expressions;
+
+/**
+ * Expression that applies a binary operator to two operand expressions.
+ * 
+ * Supports comparison operators (==, !=, >, <, >=, <=),
+ * logical operators (&&, ||), and arithmetic operators (+, -, *, /, %).
+ * 
+ * Uses the existing Element system for dynamic typing and closure support via parent chain.
+ * 
+ * Uses the visitor pattern for traversal and translation.
+ */
+
+namespace Invercargill.Expressions {
+
+    /**
+     * Expression that applies a binary operator to two operand expressions.
+     */
+    public class BinaryExpression : Object, Expression {
+        /**
+         * The left operand expression.
+         */
+        public Expression left { get; construct; }
+        
+        /**
+         * The right operand expression.
+         */
+        public Expression right { get; construct; }
+        
+        /**
+         * The binary operator to apply.
+         */
+        public BinaryOperator op { get; construct; }
+
+        /**
+         * Creates a new binary expression.
+         * 
+         * @param left The left operand
+         * @param right The right operand
+         * @param op The binary operator
+         */
+        public BinaryExpression(Expression left, Expression right, BinaryOperator op) {
+            Object(left: left, right: right, op: op);
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public ExpressionType expression_type { get { return ExpressionType.BINARY; } }
+
+        /**
+         * {@inheritDoc}
+         */
+        public Element evaluate(EvaluationContext context) throws ExpressionError {
+            // Handle short-circuit evaluation for logical operators
+            if (op == BinaryOperator.AND) {
+                var left_result = left.evaluate(context);
+                bool? left_bool_nullable = left_result.as_or_default<bool?>();
+                if (left_bool_nullable == null) {
+                    throw new ExpressionError.TYPE_MISMATCH("Left operand of && must be boolean");
+                }
+                bool left_bool = left_bool_nullable;
+                if (!left_bool) {
+                    return new NativeElement<bool?>(false);
+                }
+                var right_result = right.evaluate(context);
+                bool? right_bool_nullable = right_result.as_or_default<bool?>();
+                if (right_bool_nullable == null) {
+                    throw new ExpressionError.TYPE_MISMATCH("Right operand of && must be boolean");
+                }
+                bool right_bool = right_bool_nullable;
+                return new NativeElement<bool?>(right_bool);
+            }
+            
+            if (op == BinaryOperator.OR) {
+                var left_result = left.evaluate(context);
+                bool? left_bool_nullable = left_result.as_or_default<bool?>();
+                if (left_bool_nullable == null) {
+                    throw new ExpressionError.TYPE_MISMATCH("Left operand of || must be boolean");
+                }
+                bool left_bool = left_bool_nullable;
+                if (left_bool) {
+                    return new NativeElement<bool?>(true);
+                }
+                var right_result = right.evaluate(context);
+                bool? right_bool_nullable = right_result.as_or_default<bool?>();
+                if (right_bool_nullable == null) {
+                    throw new ExpressionError.TYPE_MISMATCH("Right operand of || must be boolean");
+                }
+                bool right_bool = right_bool_nullable;
+                return new NativeElement<bool?>(right_bool);
+            }
+
+            // Evaluate both operands for non-short-circuit operators
+            var left_val = left.evaluate(context);
+            var right_val = right.evaluate(context);
+
+            // Handle comparison operators
+            if (op.is_comparison()) {
+                return evaluate_comparison(left_val, right_val);
+            }
+
+            // Handle arithmetic operators
+            if (op.is_arithmetic()) {
+                return evaluate_arithmetic(left_val, right_val);
+            }
+
+            throw new ExpressionError.INVALID_OPERATOR("Unknown binary operator: %s".printf(op.to_string()));
+        }
+
+        /**
+         * Evaluates a comparison operation.
+         */
+        private Element evaluate_comparison(Element left_val, Element right_val) throws ExpressionError {
+            // Handle null comparisons first
+            bool left_is_null = left_val is NullElement;
+            bool right_is_null = right_val is NullElement;
+            
+            if (op == BinaryOperator.EQUAL) {
+                if (left_is_null && right_is_null) {
+                    return new NativeElement<bool?>(true);
+                }
+                if (left_is_null || right_is_null) {
+                    return new NativeElement<bool?>(false);
+                }
+                // For non-null values, use value comparison
+                return new NativeElement<bool?>(elements_equal(left_val, right_val));
+            }
+            
+            if (op == BinaryOperator.NOT_EQUAL) {
+                if (left_is_null && right_is_null) {
+                    return new NativeElement<bool?>(false);
+                }
+                if (left_is_null || right_is_null) {
+                    return new NativeElement<bool?>(true);
+                }
+                return new NativeElement<bool?>(!elements_equal(left_val, right_val));
+            }
+
+            // For ordering comparisons, both must be non-null and comparable
+            if (left_is_null || right_is_null) {
+                throw new ExpressionError.TYPE_MISMATCH("Cannot compare null values with ordering operators");
+            }
+
+            // Try numeric comparison first
+            int? cmp_result = try_numeric_compare(left_val, right_val);
+            if (cmp_result != null) {
+                switch (op) {
+                    case GREATER_THAN: return new NativeElement<bool?>(cmp_result > 0);
+                    case LESS_THAN: return new NativeElement<bool?>(cmp_result < 0);
+                    case GREATER_EQUAL: return new NativeElement<bool?>(cmp_result >= 0);
+                    case LESS_EQUAL: return new NativeElement<bool?>(cmp_result <= 0);
+                    default: break;
+                }
+            }
+
+            // Try string comparison
+            string? left_str = left_val.as_or_default<string?>();
+            string? right_str = right_val.as_or_default<string?>();
+            if (left_str != null && right_str != null) {
+                int str_cmp = strcmp(left_str, right_str);
+                switch (op) {
+                    case GREATER_THAN: return new NativeElement<bool?>(str_cmp > 0);
+                    case LESS_THAN: return new NativeElement<bool?>(str_cmp < 0);
+                    case GREATER_EQUAL: return new NativeElement<bool?>(str_cmp >= 0);
+                    case LESS_EQUAL: return new NativeElement<bool?>(str_cmp <= 0);
+                    default: break;
+                }
+            }
+
+            throw new ExpressionError.TYPE_MISMATCH("Cannot compare values of these types");
+        }
+
+        /**
+         * Checks if two elements are equal.
+         */
+        private bool elements_equal(Element left_val, Element right_val) {
+            // Try bool
+            bool? left_bool = left_val.as_or_default<bool?>();
+            bool? right_bool = right_val.as_or_default<bool?>();
+            if (left_bool != null && right_bool != null) {
+                return left_bool == right_bool;
+            }
+            
+            // Try string
+            string? left_str = left_val.as_or_default<string?>();
+            string? right_str = right_val.as_or_default<string?>();
+            if (left_str != null && right_str != null) {
+                return left_str == right_str;
+            }
+            
+            // Try numeric comparison using double (handles int, int64, double, etc.)
+            double? left_double = try_get_numeric(left_val);
+            double? right_double = try_get_numeric(right_val);
+            if (left_double != null && right_double != null) {
+                return left_double == right_double;
+            }
+
+            return false;
+        }
+        
+        /**
+         * Tries to get a numeric value from an element.
+         * Handles int, int64, double, float, etc.
+         */
+        private double? try_get_numeric(Element val) {
+            // Try int
+            int? as_int = val.as_or_default<int?>();
+            if (as_int != null) {
+                return (double)as_int;
+            }
+            
+            // Try int64
+            int64? as_int64 = val.as_or_default<int64?>();
+            if (as_int64 != null) {
+                return (double)as_int64;
+            }
+            
+            // Try long
+            long? as_long = val.as_or_default<long?>();
+            if (as_long != null) {
+                return (double)as_long;
+            }
+            
+            // Try double
+            double? as_double = val.as_or_default<double?>();
+            if (as_double != null) {
+                return as_double;
+            }
+            
+            // Try float
+            float? as_float = val.as_or_default<float?>();
+            if (as_float != null) {
+                return (double)as_float;
+            }
+            
+            return null;
+        }
+
+        /**
+         * Tries to compare two elements numerically.
+         * Returns null if not comparable as numbers.
+         */
+        private int? try_numeric_compare(Element left_val, Element right_val) {
+            // Use try_get_numeric to handle all numeric types
+            double? left_double = try_get_numeric(left_val);
+            double? right_double = try_get_numeric(right_val);
+            
+            if (left_double != null && right_double != null) {
+                if (left_double < right_double) return -1;
+                if (left_double > right_double) return 1;
+                return 0;
+            }
+            
+            return null;
+        }
+
+        /**
+         * Evaluates an arithmetic operation.
+         */
+        private Element evaluate_arithmetic(Element left_val, Element right_val) throws ExpressionError {
+            // Check for null operands
+            if (left_val is NullElement || right_val is NullElement) {
+                throw new ExpressionError.TYPE_MISMATCH("Cannot perform arithmetic on null values");
+            }
+
+            // Use try_get_numeric to handle all numeric types
+            double? left_double = try_get_numeric(left_val);
+            double? right_double = try_get_numeric(right_val);
+            
+            if (left_double == null || right_double == null) {
+                // Special case for + with strings (concatenation)
+                if (op == BinaryOperator.ADD) {
+                    string? left_str = left_val.as_or_default<string?>();
+                    string? right_str = right_val.as_or_default<string?>();
+                    if (left_str != null && right_str != null) {
+                        return new NativeElement<string?>(left_str + right_str);
+                    }
+                }
+                throw new ExpressionError.TYPE_MISMATCH("Arithmetic operators require numeric operands");
+            }
+
+            // Perform arithmetic operation
+            double result = 0.0;
+            switch (op) {
+                case ADD:
+                    result = left_double + right_double;
+                    break;
+                case SUBTRACT:
+                    result = left_double - right_double;
+                    break;
+                case MULTIPLY:
+                    result = left_double * right_double;
+                    break;
+                case DIVIDE:
+                    if (right_double == 0.0) {
+                        throw new ExpressionError.DIVISION_BY_ZERO("Division by zero");
+                    }
+                    result = left_double / right_double;
+                    break;
+                case MODULO:
+                    if (right_double == 0.0) {
+                        throw new ExpressionError.DIVISION_BY_ZERO("Modulo by zero");
+                    }
+                    // For modulo, prefer integer operation if both are integers
+                    int? left_int = left_val.as_or_default<int?>();
+                    int? right_int = right_val.as_or_default<int?>();
+                    if (left_int != null && right_int != null) {
+                        return new NativeElement<int64?>(left_int % right_int);
+                    }
+                    result = left_double % right_double;
+                    break;
+                default:
+                    throw new ExpressionError.INVALID_OPERATOR("Unknown arithmetic operator");
+            }
+
+            // Return as int64 if result is a whole number
+            if (result == (int64)result) {
+                return new NativeElement<int64?>((int64)result);
+            }
+            
+            return new NativeElement<double?>(result);
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public void accept(ExpressionVisitor visitor) {
+            visitor.visit_binary(this);
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public string to_expression_string() {
+            return "(%s %s %s)".printf(left.to_expression_string(), op.to_string(), right.to_expression_string());
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public Type? get_return_type() {
+            if (op.is_comparison() || op.is_logical()) {
+                return typeof(bool);
+            }
+            if (op.is_arithmetic()) {
+                // For arithmetic, we'd need type inference - return double as safe default
+                return typeof(double);
+            }
+            return null;
+        }
+    }
+
+}

+ 47 - 0
src/lib/Expressions/Expressions/BracketedExpression.vala

@@ -0,0 +1,47 @@
+
+namespace Invercargill.Expressions {
+
+    /**
+     * Expression that wraps another expression in parentheses.
+     * 
+     * Used to control operator precedence in expressions. When evaluated,
+     * simply delegates to the inner expression.
+     */
+    public class BracketedExpression : Object, Expression {
+
+        public ExpressionType expression_type { get { return ExpressionType.BRACKETED; } }
+
+        /**
+         * The inner expression wrapped by brackets.
+         */
+        public Expression inner { get; construct set; }
+
+        /**
+         * Creates a new bracketed expression.
+         * 
+         * @param inner The inner expression to wrap
+         */
+        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);
+        }
+
+        public override Type? get_return_type() {
+            return inner.get_return_type();
+        }
+
+        public override string to_expression_string() {
+            return @"($(inner.to_expression_string()))";
+        }
+
+    }
+
+}

+ 143 - 0
src/lib/Expressions/Expressions/FunctionCallExpression.vala

@@ -0,0 +1,143 @@
+using Invercargill.DataStructures;
+
+namespace Invercargill.Expressions {
+
+    /**
+     * Expression that calls a function on a target object.
+     * 
+     * Uses the FunctionAccessor interface to call functions on objects.
+     * Supports passing arguments including lambdas.
+     * 
+     * Example: `a.values.where(s => s.rank > 5)`
+     */
+    public class FunctionCallExpression : Object, Expression {
+
+        public ExpressionType expression_type { get { return ExpressionType.FUNCTION_CALL; } }
+
+        /**
+         * The target expression to call the function on.
+         */
+        public Expression target { get; construct set; }
+
+        /**
+         * The name of the function to call.
+         */
+        public string function_name { get; construct set; }
+
+        /**
+         * The argument expressions to pass to the function.
+         */
+        public Series<Expression> arguments { get; construct set; }
+
+        /**
+         * Creates a new function call expression.
+         * 
+         * @param target The target expression
+         * @param function_name The name of the function
+         * @param arguments Optional Series of argument expressions
+         */
+        public FunctionCallExpression(
+            Expression target,
+            string function_name,
+            Series<Expression>? arguments = null
+        ) {
+            Object(
+                target: target,
+                function_name: function_name,
+                arguments: arguments ?? new Series<Expression>()
+            );
+        }
+
+        /**
+         * Creates a new function call expression with a single argument.
+         * 
+         * @param target The target expression
+         * @param function_name The name of the function
+         * @param arg The single argument expression
+         */
+        public FunctionCallExpression.with_single_arg(
+            Expression target,
+            string function_name,
+            Expression arg
+        ) {
+            var args = new Series<Expression>();
+            args.add(arg);
+            Object(target: target, function_name: function_name, arguments: args);
+        }
+
+        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())"
+                );
+            }
+
+            // Check if function exists
+            if (!accessor.has_function(function_name)) {
+                throw new ExpressionError.NON_EXISTANT_PROPERTY(
+                    @"Function '$function_name' not found on type $(target_value.type_name())"
+                );
+            }
+
+            // Evaluate arguments
+            var evaluated_args = new Series<Element>();
+            foreach (var arg in arguments) {
+                evaluated_args.add(arg.evaluate(context));
+            }
+
+            return accessor.call_function(function_name, evaluated_args, context);
+        }
+
+        public void accept(ExpressionVisitor visitor) {
+            visitor.visit_function_call(this);
+            target.accept(visitor);
+            foreach (var arg in arguments) {
+                arg.accept(visitor);
+            }
+        }
+
+        public override string to_expression_string() {
+            var args_str = new StringBuilder();
+            bool first = true;
+            foreach (var arg in arguments) {
+                if (!first) {
+                    args_str.append(", ");
+                }
+                args_str.append(arg.to_expression_string());
+                first = false;
+            }
+            return @"$(target.to_expression_string()).$function_name($(args_str.str))";
+        }
+
+        // Private helper methods
+
+        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);
+            }
+
+            // Check for Enumerable directly
+            Enumerable? enumerable;
+            if (element.try_get_as(out enumerable)) {
+                return new EnumerableFunctionAccessor(enumerable.to_elements());
+            }
+
+            return null;
+        }
+
+    }
+
+}

+ 110 - 0
src/lib/Expressions/Expressions/LambdaExpression.vala

@@ -0,0 +1,110 @@
+
+namespace Invercargill.Expressions {
+
+    /**
+     * Expression that represents a lambda function.
+     * 
+     * Lambda expressions capture their evaluation context to support closures,
+     * allowing the lambda body to reference variables from outer scopes.
+     * 
+     * Example: `s => s.rank > a.min_rank`
+     */
+    public class LambdaExpression : Object, Expression {
+
+        public ExpressionType expression_type { get { return ExpressionType.LAMBDA; } }
+
+        /**
+         * The parameter name for the lambda.
+         */
+        public string parameter_name { get; construct set; }
+
+        /**
+         * The body expression of the lambda.
+         */
+        public Expression body { get; construct set; }
+
+        /**
+         * The captured context at lambda creation time.
+         */
+        private EvaluationContext? _captured_context;
+
+        /**
+         * Creates a new lambda expression.
+         * 
+         * @param parameter_name The name of the lambda parameter
+         * @param body The body expression
+         */
+        public LambdaExpression(string parameter_name, Expression body) {
+            Object(parameter_name: parameter_name, body: body);
+        }
+
+        /**
+         * Captures the current evaluation context for closure support.
+         * 
+         * This should be called when the lambda is created in an expression,
+         * typically during FunctionCallExpression evaluation.
+         * 
+         * @param context The context to capture
+         */
+        public void capture_context(EvaluationContext context) {
+            _captured_context = context;
+        }
+
+        /**
+         * Evaluates the lambda with a specific argument value.
+         * 
+         * Creates a child scope with the parameter bound to the argument,
+         * then evaluates the body in that scope.
+         * 
+         * @param argument The value to bind to the parameter
+         * @return The result of evaluating the body
+         * @throws ExpressionError if the lambda has no captured context
+         */
+        public Element evaluate_with_argument(Element argument) throws ExpressionError {
+            if (_captured_context == null) {
+                throw new ExpressionError.INVALID_TYPE(
+                    "Lambda has no captured context - capture_context() must be called first"
+                );
+            }
+
+            // 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 as a LambdaElement.
+         * 
+         * This allows the lambda to be passed as an argument to functions.
+         * The context is captured for later use.
+         * 
+         * @param context The evaluation context
+         * @return A LambdaElement wrapping this lambda
+         */
+        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);
+        }
+
+        public override string to_expression_string() {
+            return @"$parameter_name => $(body.to_expression_string())";
+        }
+
+        /**
+         * Checks if this lambda has a captured context.
+         * 
+         * @return true if a context has been captured
+         */
+        public bool has_captured_context() {
+            return _captured_context != null;
+        }
+
+    }
+
+}

+ 114 - 0
src/lib/Expressions/Expressions/LiteralExpression.vala

@@ -0,0 +1,114 @@
+
+namespace Invercargill.Expressions {
+
+    /**
+     * Expression that wraps a constant literal value.
+     * 
+     * Literal expressions represent fixed values like numbers, strings,
+     * booleans, or null in an expression tree.
+     */
+    public class LiteralExpression : Object, Expression {
+
+        public ExpressionType expression_type { get { return ExpressionType.LITERAL; } }
+
+        /**
+         * The literal value wrapped as an Element.
+         */
+        public Element value { get; construct set; }
+
+        /**
+         * Creates a new literal expression with an Element value.
+         * 
+         * @param value The Element value for this literal
+         */
+        public LiteralExpression(Element value) {
+            Object(value: value);
+        }
+
+        /**
+         * Creates a new string literal expression.
+         * 
+         * @param val The string value
+         */
+        public LiteralExpression.for_string(string val) {
+            this(new ValueElement(val));
+        }
+
+        /**
+         * Creates a new integer literal expression.
+         * 
+         * @param val The integer value
+         */
+        public LiteralExpression.for_int(int val) {
+            this(new ValueElement(val));
+        }
+
+        /**
+         * Creates a new double literal expression.
+         * 
+         * @param val The double value
+         */
+        public LiteralExpression.for_double(double val) {
+            this(new ValueElement(val));
+        }
+
+        /**
+         * Creates a new boolean literal expression.
+         * 
+         * @param val The boolean value
+         */
+        public LiteralExpression.for_bool(bool val) {
+            this(new ValueElement(val));
+        }
+
+        /**
+         * Creates a new null literal expression.
+         */
+        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);
+        }
+
+        public override Type? get_return_type() {
+            return value.type();
+        }
+
+        public override string to_expression_string() {
+            if (value.is_null()) {
+                return "null";
+            }
+
+            // Try to format the value nicely
+            string? str_val;
+            if (value.try_get_as(out str_val)) {
+                return @"\"$str_val\"";
+            }
+
+            bool? bool_val;
+            if (value.try_get_as(out bool_val)) {
+                return bool_val ? "true" : "false";
+            }
+
+            int? int_val;
+            if (value.try_get_as(out int_val)) {
+                return int_val.to_string();
+            }
+
+            double? double_val;
+            if (value.try_get_as(out double_val)) {
+                return double_val.to_string();
+            }
+
+            return value.to_string();
+        }
+
+    }
+
+}

+ 74 - 0
src/lib/Expressions/Expressions/PropertyExpression.vala

@@ -0,0 +1,74 @@
+
+namespace Invercargill.Expressions {
+
+    /**
+     * Expression that accesses a property on a target object.
+     * 
+     * Uses the PropertyAccessor interface to navigate properties on objects,
+     * supporting various target types including Object, Properties, and
+     * PropertyAccessor implementations.
+     */
+    public class PropertyExpression : Object, Expression {
+
+        public ExpressionType expression_type { get { return ExpressionType.PROPERTY; } }
+
+        /**
+         * The target expression whose property will be accessed.
+         */
+        public Expression target { get; construct set; }
+
+        /**
+         * The name of the property to access.
+         */
+        public string property_name { get; construct set; }
+
+        /**
+         * Creates a new property expression.
+         * 
+         * @param target The target expression
+         * @param property_name The name of the property to access
+         */
+        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);
+
+            // If the element itself is a PropertyAccessor
+            PropertyAccessor? accessor = null;
+            if (target_value.try_get_as(out accessor)) {
+                return accessor.read_property(property_name);
+            }
+
+            // If it's an Object, wrap it with ObjectPropertyAccessor
+            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, wrap it with PropertiesPropertyAccessor
+            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);
+        }
+
+        public override string to_expression_string() {
+            return @"$(target.to_expression_string()).$property_name";
+        }
+
+    }
+
+}

+ 76 - 0
src/lib/Expressions/Expressions/TernaryExpression.vala

@@ -0,0 +1,76 @@
+
+namespace Invercargill.Expressions {
+
+    /**
+     * Expression that represents a ternary conditional (condition ? true_value : false_value).
+     * 
+     * Evaluates the condition and returns either the true expression or
+     * the false expression based on the result.
+     */
+    public class TernaryExpression : Object, Expression {
+
+        public ExpressionType expression_type { get { return ExpressionType.TERNARY; } }
+
+        /**
+         * The condition expression to evaluate.
+         */
+        public Expression condition { get; construct set; }
+
+        /**
+         * The expression to return if the condition is true.
+         */
+        public Expression true_expression { get; construct set; }
+
+        /**
+         * The expression to return if the condition is false.
+         */
+        public Expression false_expression { get; construct set; }
+
+        /**
+         * Creates a new ternary expression.
+         * 
+         * @param condition The condition expression
+         * @param true_expr The expression for true condition
+         * @param false_expr The expression for false condition
+         */
+        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, got $(cond_val.type_name())"
+            );
+        }
+
+        public void accept(ExpressionVisitor visitor) {
+            visitor.visit_ternary(this);
+            condition.accept(visitor);
+            true_expression.accept(visitor);
+            false_expression.accept(visitor);
+        }
+
+        public override Type? get_return_type() {
+            // Return type could be either branch's type
+            // For simplicity, return null to indicate dynamic typing
+            return null;
+        }
+
+        public override string to_expression_string() {
+            return @"$(condition.to_expression_string()) ? $(true_expression.to_expression_string()) : $(false_expression.to_expression_string())";
+        }
+
+    }
+
+}

+ 126 - 0
src/lib/Expressions/Expressions/UnaryExpression.vala

@@ -0,0 +1,126 @@
+
+namespace Invercargill.Expressions {
+
+    /**
+     * Expression that applies a unary operator to a single operand expression.
+     * 
+     * Supports arithmetic negation (-) and logical negation (!).
+     */
+    public class UnaryExpression : Object, Expression {
+
+        public ExpressionType expression_type { get { return ExpressionType.UNARY; } }
+
+        /**
+         * The operand expression.
+         */
+        public Expression operand { get; construct set; }
+
+        /**
+         * The unary operator to apply.
+         */
+        public UnaryOperator operator { get; construct set; }
+
+        /**
+         * Creates a new unary expression.
+         * 
+         * @param op The unary operator
+         * @param operand The operand expression
+         */
+        public UnaryExpression(UnaryOperator op, Expression operand) {
+            Object(operand: operand, operator: op);
+        }
+
+        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);
+
+                default:
+                    throw new ExpressionError.INVALID_SYNTAX(
+                        @"Unknown unary operator: $(operator.to_string())"
+                    );
+            }
+        }
+
+        public void accept(ExpressionVisitor visitor) {
+            visitor.visit_unary(this);
+            operand.accept(visitor);
+        }
+
+        public override Type? get_return_type() {
+            if (operator == UnaryOperator.NOT) {
+                return typeof(bool);
+            }
+            if (operator == UnaryOperator.NEGATE) {
+                // Return type depends on operand type
+                return null;
+            }
+            return null;
+        }
+
+        public override string to_expression_string() {
+            return @"$(operator.to_string())$(operand.to_expression_string())";
+        }
+
+        // Private helper methods
+
+        private Element evaluate_negate(Element element) throws ExpressionError {
+            // Try int first
+            int? int_val_nullable = element.as_or_default<int?>();
+            if (int_val_nullable != null) {
+                int int_val = (!) int_val_nullable;
+                return new NativeElement<int64?>(-(int64)int_val);
+            }
+
+            // Try int64
+            int64? int64_val_nullable = element.as_or_default<int64?>();
+            if (int64_val_nullable != null) {
+                int64 int64_val = (!) int64_val_nullable;
+                return new NativeElement<int64?>(-int64_val);
+            }
+
+            // Try long
+            long? long_val_nullable = element.as_or_default<long?>();
+            if (long_val_nullable != null) {
+                long long_val = (!) long_val_nullable;
+                return new NativeElement<int64?>(-(int64)long_val);
+            }
+
+            // Try double
+            double? double_val_nullable = element.as_or_default<double?>();
+            if (double_val_nullable != null) {
+                double double_val = (!) double_val_nullable;
+                return new NativeElement<double?>(-double_val);
+            }
+
+            // Try float
+            float? float_val_nullable = element.as_or_default<float?>();
+            if (float_val_nullable != null) {
+                float float_val = (!) float_val_nullable;
+                return new NativeElement<double?>(-(double)float_val);
+            }
+
+            throw new ExpressionError.INVALID_TYPE(
+                @"Cannot negate $(element.type_name()) - expected numeric value"
+            );
+        }
+
+        private Element evaluate_not(Element element) throws ExpressionError {
+            bool? bool_val_nullable = element.as_or_default<bool?>();
+            if (bool_val_nullable != null) {
+                bool bool_val = (!) bool_val_nullable;
+                return new NativeElement<bool?>(!bool_val);
+            }
+            throw new ExpressionError.INVALID_TYPE(
+                @"Logical NOT requires boolean operand, got $(element.type_name())"
+            );
+        }
+
+    }
+
+}

+ 42 - 0
src/lib/Expressions/Expressions/VariableExpression.vala

@@ -0,0 +1,42 @@
+
+namespace Invercargill.Expressions {
+
+    /**
+     * Expression that references a named variable from the evaluation context.
+     * 
+     * Variable expressions are resolved at evaluation time by looking up
+     * the variable name in the current scope chain.
+     */
+    public class VariableExpression : Object, Expression {
+
+        public ExpressionType expression_type { get { return ExpressionType.VARIABLE; } }
+
+        /**
+         * The name of the variable to reference.
+         */
+        public string variable_name { get; construct set; }
+
+        /**
+         * Creates a new variable expression.
+         * 
+         * @param name The name of the variable to reference
+         */
+        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);
+        }
+
+        public override string to_expression_string() {
+            return variable_name;
+        }
+
+    }
+
+}

+ 49 - 0
src/lib/Expressions/FunctionAccessor.vala

@@ -0,0 +1,49 @@
+using Invercargill.DataStructures;
+
+namespace Invercargill.Expressions {
+
+    /**
+     * Interface for calling functions on objects within expressions.
+     * 
+     * FunctionAccessor provides a way to call methods on objects, similar to
+     * how PropertyAccessor provides property access. This is particularly
+     * useful for calling Enumerable methods like where(), select(), first(), etc.
+     * 
+     * The interface is designed to be extensible for ORM use cases where
+     * functions might represent server-side operations.
+     */
+    public interface FunctionAccessor : Object {
+
+        /**
+         * Checks if a function is available on this object.
+         * 
+         * @param function_name The name of the function to check
+         * @return true if the function is available
+         */
+        public abstract bool has_function(string function_name);
+
+        /**
+         * Gets the names of all available functions.
+         * 
+         * @return An Enumerable of function names
+         */
+        public abstract Enumerable<string> get_function_names();
+
+        /**
+         * Calls a function with evaluated arguments.
+         * 
+         * @param function_name The name of the function to call
+         * @param arguments The evaluated argument Elements as a Series
+         * @param context The evaluation context (for lambda capture)
+         * @return The result of the function call
+         * @throws ExpressionError if the function call fails
+         */
+        public abstract Element call_function(
+            string function_name,
+            Series<Element> arguments,
+            EvaluationContext context
+        ) throws ExpressionError;
+
+    }
+
+}

+ 77 - 0
src/lib/Expressions/ObjectPropertyAccessor.vala

@@ -0,0 +1,77 @@
+using Invercargill.DataStructures;
+
+namespace Invercargill.Expressions {
+
+    public class ObjectPropertyAccessor : Object, PropertyAccessor {
+
+        public Object target { get; private set; }
+
+        private Type type;
+        private string type_name;
+        private Dictionary<string, Type> properties;
+
+        public ObjectPropertyAccessor(Object target) {
+            this.target = target;
+            this.type = target.get_type();
+            type_name = type.name();
+            
+            properties = new Dictionary<string, Type>();
+            var object_class = (ObjectClass)type.class_ref();
+            foreach (var prop in object_class.list_properties()) {
+                properties.add (prop.name, prop.value_type);
+            }
+        }
+
+        public Element read_property (string property) throws ExpressionError {
+            var prop_type = get_property_type(property);
+            var value = Value(prop_type);
+            target.get_property (property, ref value);
+            
+            // Handle common types with NativeElement for better type conversion
+            if (prop_type == typeof(string)) {
+                return new NativeElement<string?>(value.get_string());
+            }
+            if (prop_type == typeof(int)) {
+                return new NativeElement<int?>(value.get_int());
+            }
+            if (prop_type == typeof(int64)) {
+                return new NativeElement<int64?>(value.get_int64());
+            }
+            if (prop_type == typeof(long)) {
+                return new NativeElement<long?>(value.get_long());
+            }
+            if (prop_type == typeof(double)) {
+                return new NativeElement<double?>(value.get_double());
+            }
+            if (prop_type == typeof(float)) {
+                return new NativeElement<float?>(value.get_float());
+            }
+            if (prop_type == typeof(bool)) {
+                return new NativeElement<bool?>(value.get_boolean());
+            }
+            if (prop_type.is_a(typeof(Object))) {
+                return new NativeElement<Object?>(value.get_object());
+            }
+            
+            // Default to ValueElement for other types
+            return new ValueElement(value);
+        }
+
+        public bool has_property (string property) {
+            return properties.has(property);
+        }
+
+        public Enumerable<string> get_property_names () {
+            return properties.keys;
+        }
+
+        public override Type get_property_type(string property) throws ExpressionError {
+            Type prop_type;
+            if(!properties.try_get(property, out prop_type)) {
+                throw new ExpressionError.NON_EXISTANT_PROPERTY(@"Object type $type_name does not have a public property called '$property'.");
+            }
+            return prop_type;
+        }
+    }
+
+}

+ 30 - 0
src/lib/Expressions/PropertiesPropertyAccessor.vala

@@ -0,0 +1,30 @@
+
+
+namespace Invercargill.Expressions {
+
+    public class PropertiesPropertyAccessor : Object, PropertyAccessor {
+
+        public Properties target { get; private set; }
+        public PropertiesPropertyAccessor(Properties target) {
+            this.target = target;
+        }
+
+        public Element read_property (string property) throws ExpressionError {
+            Element element;
+            if(target.try_get (property, out element)) {
+                return element;
+            }
+            throw new ExpressionError.NON_EXISTANT_PROPERTY(@"Property '$property' not found");
+        }
+
+        public bool has_property (string property) {
+            return target.has(property);
+        }
+
+        public Enumerable<string> get_property_names () {
+            return target.keys;
+        }
+
+    }
+
+}

+ 20 - 0
src/lib/Expressions/PropertyAccessor.vala

@@ -0,0 +1,20 @@
+
+namespace Invercargill.Expressions {
+
+    public interface PropertyAccessor : Object {
+
+        public abstract Element read_property(string property) throws ExpressionError;
+        public abstract bool has_property(string property);
+        public abstract Enumerable<string> get_property_names();
+        
+        public virtual T read_property_as<T>(string property) throws Error {
+            return read_property(property).as<T>();
+        }
+
+        public virtual Type get_property_type(string property) throws ExpressionError {
+            return read_property(property).type();
+        }
+
+    }
+
+}

+ 64 - 0
src/lib/Expressions/UnaryOperator.vala

@@ -0,0 +1,64 @@
+
+namespace Invercargill.Expressions {
+
+    /**
+     * Enumeration of unary operators supported in expressions.
+     */
+    public enum UnaryOperator {
+        /**
+         * Arithmetic negation (-)
+         */
+        NEGATE,
+
+        /**
+         * Logical negation (!)
+         */
+        NOT;
+
+        /**
+         * Gets the string representation of this operator.
+         * 
+         * @return The operator symbol as a string
+         */
+        public string to_string() {
+            switch (this) {
+                case NEGATE: return "-";
+                case NOT: return "!";
+                default: return "";
+            }
+        }
+
+        /**
+         * Checks if this is an arithmetic operator.
+         * 
+         * @return true if this is an arithmetic operator
+         */
+        public bool is_arithmetic() {
+            return this == NEGATE;
+        }
+
+        /**
+         * Checks if this is a logical operator.
+         * 
+         * @return true if this is a logical operator
+         */
+        public bool is_logical() {
+            return this == NOT;
+        }
+
+        /**
+         * Parses a string to a UnaryOperator.
+         * 
+         * @param str The string to parse
+         * @return The parsed UnaryOperator, or null if invalid
+         */
+        public static UnaryOperator? from_string(string str) {
+            switch (str) {
+                case "-": return NEGATE;
+                case "!": return NOT;
+                default: return null;
+            }
+        }
+    }
+
+}

+ 141 - 0
src/lib/Operators/Contain.vala

@@ -0,0 +1,141 @@
+
+namespace Invercargill.Operators {
+    
+    public static ContainDelegate<T> contain<T>() {
+        var type = typeof(T);
+        if(type == typeof(bool)) {
+            return i => {
+                var value = Value(type);
+                value.set_boolean((bool)i);
+                return value;
+            };
+        }
+        else if(type == typeof(int8)) {
+            return i => {
+                var value = Value(type);
+                value.set_schar((int8)i);
+                return value;
+            };
+        }
+        else if(type == typeof(uchar)) {
+            return i => {
+                var value = Value(type);
+                value.set_uchar((uchar)i);
+                return value;
+            };
+        }
+        else if(type == typeof(int)) {
+            return i => {
+                var value = Value(type);
+                value.set_int((int)i);
+                return value;
+            };
+        }
+        else if(type == typeof(uint)) {
+            return i => {
+                var value = Value(type);
+                value.set_uint((uint)i);
+                return value;
+            };
+        }
+        else if(type == typeof(int64)) {
+            return i => {
+                var value = Value(type);
+                value.set_int64((int64)i);
+                return value;
+            };
+        }
+        else if(type == typeof(uint64)) {
+            return i => {
+                var value = Value(type);
+                value.set_uint64((uint64)i);
+                return value;
+            };
+        }
+        else if(type == typeof(long)) {
+            return i => {
+                var value = Value(type);
+                value.set_long((long)i);
+                return value;
+            };
+        }
+        else if(type == typeof(ulong)) {
+            return i => {
+                var value = Value(type);
+                value.set_ulong((ulong)i);
+                return value;
+            };
+        }
+        else if(type == typeof(float)) {
+            return i => {
+                var value = Value(type);
+                value.set_float((float?)i);
+                return value;
+            };
+        }
+        else if(type == typeof(double)) {
+            return i => {
+                var value = Value(type);
+                value.set_double((double?)i);
+                return value;
+            };
+        }
+        else if(type == typeof(string)) {
+            return i => {
+                var value = Value(type);
+                value.set_string((string)i);
+                return value;
+            };
+        }
+        else if(type == typeof(Type)) {
+            return i => {
+                var value = Value(type);
+                value.set_gtype((Type)i);
+                return value;
+            };
+        }
+        else if(type.is_enum()) {
+            return i => {
+                var value = Value(type);
+                value.set_enum((int)i);
+                return value;
+            };
+        }
+        else if(type.is_flags()) {
+            return i => {
+                var value = Value(type);
+                value.set_flags((uint)i);
+                return value;
+            };
+        }
+        else if(type == typeof(void*)) {
+            return i => {
+                var value = Value(type);
+                value.set_pointer((void*)i);
+                return value;
+            };
+        }
+        else if(type == typeof(ParamSpec)) {
+            return i => {
+                var value = Value(type);
+                value.set_param((ParamSpec)i);
+                return value;
+            };
+        }
+        else if(type.is_a(typeof(Object))) {
+            return i => {
+                var value = Value(type);
+                value.set_object((Object)i);
+                return value;
+            };
+        }
+        else {
+            return i => {
+                var value = Value(type);
+                value.set_boxed((void*)i);
+                return value;
+            };
+        }
+    }
+
+}

+ 18 - 0
src/lib/Promotions/Elements.vala

@@ -9,5 +9,23 @@ namespace Invercargill {
             return select<T>(e => e.assert_as<T>());
         }
 
+    }
+
+    private class ElementsPromotionImplementation : StickyProxyPromotion<Elements, Element>, Sticky<Elements, Element>, Elements {
+
+        protected override Elements adhere (Enumerable<Element> enumerable) {
+            return (Elements)new ElementsPromotionImplementation().wrap(enumerable);
+        }
+
+        protected override Elements passthrough () {
+            return this;
+        }
+
+        public override bool can_wrap (GLib.Type element_type) {
+            return element_type.is_a (typeof(Element));
+        }
+
+
+
     }
 }

+ 2 - 0
src/lib/Promotions/Registration.c

@@ -2,8 +2,10 @@
 #include "invercargill-1.h"
 
 VALA_EXTERN GType invercargill_binary_data_promotion_implementation_get_type (void) G_GNUC_CONST ;
+VALA_EXTERN GType invercargill_elements_promotion_implementation_get_type (void) G_GNUC_CONST ;
 
 __attribute__((constructor))
 static void invercargill_register_internal_promotions() {
     invercargill_register_promotion (INVERCARGILL_TYPE_BINARY_DATA, invercargill_binary_data_promotion_implementation_get_type());
+    invercargill_register_promotion (INVERCARGILL_TYPE_ELEMENTS, invercargill_elements_promotion_implementation_get_type());
 }

+ 33 - 0
src/lib/meson.build

@@ -5,9 +5,13 @@ invercargill_minor = '0'
 invercargill_patch = '0'
 invercargill_version = '@0@.@1@.@2@'.format(invercargill_major, invercargill_minor, invercargill_patch)
 
+cc = meson.get_compiler('c')
+m_lib = cc.find_library('m', required: false)
+
 dependencies = [
     dependency('glib-2.0'),
     dependency('gobject-2.0'),
+    m_lib,  # Math library for fmod
 ]
 
 sources = files('Enumerable.vala')
@@ -133,6 +137,35 @@ sources += files('Operators/Comparison.vala')
 sources += files('Operators/Equality.vala')
 sources += files('Operators/Hash.vala')
 sources += files('Operators/Stringify.vala')
+sources += files('Operators/Contain.vala')
+
+sources += files('Expressions/Expression.vala')
+sources += files('Expressions/ExpressionError.vala')
+sources += files('Expressions/ExpressionType.vala')
+sources += files('Expressions/ExpressionVisitor.vala')
+sources += files('Expressions/EvaluationContext.vala')
+sources += files('Expressions/ExpressionEvaluator.vala')
+sources += files('Expressions/PropertyAccessor.vala')
+sources += files('Expressions/PropertiesPropertyAccessor.vala')
+sources += files('Expressions/ObjectPropertyAccessor.vala')
+sources += files('Expressions/FunctionAccessor.vala')
+sources += files('Expressions/BinaryOperator.vala')
+sources += files('Expressions/UnaryOperator.vala')
+sources += files('Expressions/EnumerableFunctionAccessor.vala')
+sources += files('Expressions/ExpressionTokenizer.vala')
+sources += files('Expressions/ExpressionParser.vala')
+
+sources += files('Expressions/Expressions/LiteralExpression.vala')
+sources += files('Expressions/Expressions/VariableExpression.vala')
+sources += files('Expressions/Expressions/BinaryExpression.vala')
+sources += files('Expressions/Expressions/UnaryExpression.vala')
+sources += files('Expressions/Expressions/TernaryExpression.vala')
+sources += files('Expressions/Expressions/PropertyExpression.vala')
+sources += files('Expressions/Expressions/FunctionCallExpression.vala')
+sources += files('Expressions/Expressions/LambdaExpression.vala')
+sources += files('Expressions/Expressions/BracketedExpression.vala')
+
+sources += files('Expressions/Elements/LambdaElement.vala')
 
 invercargill = shared_library('invercargill-@0@'.format(invercargill_major), sources,
     dependencies: dependencies,

+ 948 - 0
src/tests/Integration/Expressions.vala

@@ -0,0 +1,948 @@
+using Invercargill;
+using Invercargill.DataStructures;
+using Invercargill.Expressions;
+
+class ExprTestPerson : Object {
+    public string name { get; set; }
+    public int age { get; set; }
+    public int rank { get; set; }
+    public bool is_important { get; set; }
+    
+    public ExprTestPerson(string name, int age, int rank = 0, bool is_important = false) {
+        this.name = name;
+        this.age = age;
+        this.rank = rank;
+        this.is_important = is_important;
+    }
+}
+
+class ExprTestContainer : Object {
+    public int min_rank { get; set; }
+    public Enumerable<ExprTestPerson> values { get; set; }
+    
+    public ExprTestContainer(int min_rank, Enumerable<ExprTestPerson> values) {
+        this.min_rank = min_rank;
+        this.values = values;
+    }
+}
+
+void expression_tests() {
+
+    // ==================== Literal Tests ====================
+
+    Test.add_func("/invercargill/expressions/literal_int", () => {
+        var expr = new LiteralExpression(new NativeElement<int?>(42));
+        var props = new PropertyDictionary();
+        var context = new EvaluationContext(props);
+        
+        var result = expr.evaluate(context);
+        assert(result != null);
+        
+        int? value;
+        assert(result.try_get_as<int?>(out value));
+        assert(value == 42);
+    });
+
+    Test.add_func("/invercargill/expressions/literal_string", () => {
+        var expr = new LiteralExpression(new NativeElement<string?>("hello"));
+        var props = new PropertyDictionary();
+        var context = new EvaluationContext(props);
+        
+        var result = expr.evaluate(context);
+        assert(result != null);
+        
+        string? value;
+        assert(result.try_get_as<string?>(out value));
+        assert(value == "hello");
+    });
+
+    Test.add_func("/invercargill/expressions/literal_bool", () => {
+        var expr = new LiteralExpression(new NativeElement<bool?>(true));
+        var props = new PropertyDictionary();
+        var context = new EvaluationContext(props);
+        
+        var result = expr.evaluate(context);
+        assert(result != null);
+        
+        bool? value;
+        assert(result.try_get_as<bool?>(out value));
+        assert(value == true);
+    });
+
+    Test.add_func("/invercargill/expressions/literal_null", () => {
+        var expr = new LiteralExpression(new NullElement());
+        var props = new PropertyDictionary();
+        var context = new EvaluationContext(props);
+        
+        var result = expr.evaluate(context);
+        assert(result != null);
+        assert(result.is_null());
+    });
+
+    // ==================== Variable Tests ====================
+
+    Test.add_func("/invercargill/expressions/variable", () => {
+        var expr = new VariableExpression("x");
+        var props = new PropertyDictionary();
+        props.set("x", new NativeElement<int?>(10));
+        var context = new EvaluationContext(props);
+        
+        var result = expr.evaluate(context);
+        assert(result != null);
+        
+        int? value;
+        assert(result.try_get_as<int?>(out value));
+        assert(value == 10);
+    });
+
+    // ==================== Binary Comparison Tests ====================
+
+    Test.add_func("/invercargill/expressions/binary_equals", () => {
+        var expr = new BinaryExpression(
+            new LiteralExpression(new NativeElement<int?>(5)),
+            new LiteralExpression(new NativeElement<int?>(5)),
+            BinaryOperator.EQUAL
+        );
+        var props = new PropertyDictionary();
+        var context = new EvaluationContext(props);
+        
+        var result = expr.evaluate(context);
+        assert(result != null);
+        
+        bool? value;
+        assert(result.try_get_as<bool?>(out value));
+        assert(value == true);
+    });
+
+    Test.add_func("/invercargill/expressions/binary_not_equals", () => {
+        var expr = new BinaryExpression(
+            new LiteralExpression(new NativeElement<int?>(5)),
+            new LiteralExpression(new NativeElement<int?>(3)),
+            BinaryOperator.NOT_EQUAL
+        );
+        var props = new PropertyDictionary();
+        var context = new EvaluationContext(props);
+        
+        var result = expr.evaluate(context);
+        assert(result != null);
+        
+        bool? value;
+        assert(result.try_get_as<bool?>(out value));
+        assert(value == true);
+    });
+
+    Test.add_func("/invercargill/expressions/binary_greater_than", () => {
+        var expr = new BinaryExpression(
+            new LiteralExpression(new NativeElement<int?>(5)),
+            new LiteralExpression(new NativeElement<int?>(3)),
+            BinaryOperator.GREATER_THAN
+        );
+        var props = new PropertyDictionary();
+        var context = new EvaluationContext(props);
+        
+        var result = expr.evaluate(context);
+        assert(result != null);
+        
+        bool? value;
+        assert(result.try_get_as<bool?>(out value));
+        assert(value == true);
+    });
+
+    Test.add_func("/invercargill/expressions/binary_less_than", () => {
+        var expr = new BinaryExpression(
+            new LiteralExpression(new NativeElement<int?>(3)),
+            new LiteralExpression(new NativeElement<int?>(5)),
+            BinaryOperator.LESS_THAN
+        );
+        var props = new PropertyDictionary();
+        var context = new EvaluationContext(props);
+        
+        var result = expr.evaluate(context);
+        assert(result != null);
+        
+        bool? value;
+        assert(result.try_get_as<bool?>(out value));
+        assert(value == true);
+    });
+
+    // ==================== Binary Logical Tests ====================
+
+    Test.add_func("/invercargill/expressions/binary_and", () => {
+        var expr = new BinaryExpression(
+            new LiteralExpression(new NativeElement<bool?>(true)),
+            new LiteralExpression(new NativeElement<bool?>(true)),
+            BinaryOperator.AND
+        );
+        var props = new PropertyDictionary();
+        var context = new EvaluationContext(props);
+        
+        var result = expr.evaluate(context);
+        assert(result != null);
+        
+        bool? value;
+        assert(result.try_get_as<bool?>(out value));
+        assert(value == true);
+    });
+
+    Test.add_func("/invercargill/expressions/binary_or", () => {
+        var expr = new BinaryExpression(
+            new LiteralExpression(new NativeElement<bool?>(false)),
+            new LiteralExpression(new NativeElement<bool?>(true)),
+            BinaryOperator.OR
+        );
+        var props = new PropertyDictionary();
+        var context = new EvaluationContext(props);
+        
+        var result = expr.evaluate(context);
+        assert(result != null);
+        
+        bool? value;
+        assert(result.try_get_as<bool?>(out value));
+        assert(value == true);
+    });
+
+    // ==================== Binary Arithmetic Tests ====================
+
+    Test.add_func("/invercargill/expressions/binary_arithmetic_add", () => {
+        var expr = new BinaryExpression(
+            new LiteralExpression(new NativeElement<int?>(5)),
+            new LiteralExpression(new NativeElement<int?>(3)),
+            BinaryOperator.ADD
+        );
+        var props = new PropertyDictionary();
+        var context = new EvaluationContext(props);
+        
+        var result = expr.evaluate(context);
+        assert(result != null);
+        
+        int64? value;
+        assert(result.try_get_as<int64?>(out value));
+        assert(value == 8);
+    });
+
+    Test.add_func("/invercargill/expressions/binary_arithmetic_subtract", () => {
+        var expr = new BinaryExpression(
+            new LiteralExpression(new NativeElement<int?>(5)),
+            new LiteralExpression(new NativeElement<int?>(3)),
+            BinaryOperator.SUBTRACT
+        );
+        var props = new PropertyDictionary();
+        var context = new EvaluationContext(props);
+        
+        var result = expr.evaluate(context);
+        assert(result != null);
+        
+        int64? value;
+        assert(result.try_get_as<int64?>(out value));
+        assert(value == 2);
+    });
+
+    Test.add_func("/invercargill/expressions/binary_arithmetic_multiply", () => {
+        var expr = new BinaryExpression(
+            new LiteralExpression(new NativeElement<int?>(5)),
+            new LiteralExpression(new NativeElement<int?>(3)),
+            BinaryOperator.MULTIPLY
+        );
+        var props = new PropertyDictionary();
+        var context = new EvaluationContext(props);
+        
+        var result = expr.evaluate(context);
+        assert(result != null);
+        
+        int64? value;
+        assert(result.try_get_as<int64?>(out value));
+        assert(value == 15);
+    });
+
+    Test.add_func("/invercargill/expressions/binary_arithmetic_divide", () => {
+        var expr = new BinaryExpression(
+            new LiteralExpression(new NativeElement<int?>(15)),
+            new LiteralExpression(new NativeElement<int?>(3)),
+            BinaryOperator.DIVIDE
+        );
+        var props = new PropertyDictionary();
+        var context = new EvaluationContext(props);
+        
+        var result = expr.evaluate(context);
+        assert(result != null);
+        
+        int64? value;
+        assert(result.try_get_as<int64?>(out value));
+        assert(value == 5);
+    });
+
+    Test.add_func("/invercargill/expressions/binary_arithmetic_modulo", () => {
+        var expr = new BinaryExpression(
+            new LiteralExpression(new NativeElement<int?>(17)),
+            new LiteralExpression(new NativeElement<int?>(5)),
+            BinaryOperator.MODULO
+        );
+        var props = new PropertyDictionary();
+        var context = new EvaluationContext(props);
+        
+        var result = expr.evaluate(context);
+        assert(result != null);
+        
+        int64? value;
+        assert(result.try_get_as<int64?>(out value));
+        assert(value == 2);
+    });
+
+    // ==================== Unary Tests ====================
+
+    Test.add_func("/invercargill/expressions/unary_negate", () => {
+        var expr = new UnaryExpression(
+            UnaryOperator.NEGATE,
+            new LiteralExpression(new NativeElement<int?>(5))
+        );
+        var props = new PropertyDictionary();
+        var context = new EvaluationContext(props);
+        
+        var result = expr.evaluate(context);
+        assert(result != null);
+        
+        int64? value;
+        assert(result.try_get_as<int64?>(out value));
+        assert(value == -5);
+    });
+
+    Test.add_func("/invercargill/expressions/unary_not", () => {
+        var expr = new UnaryExpression(
+            UnaryOperator.NOT,
+            new LiteralExpression(new NativeElement<bool?>(false))
+        );
+        var props = new PropertyDictionary();
+        var context = new EvaluationContext(props);
+        
+        var result = expr.evaluate(context);
+        assert(result != null);
+        
+        bool? value;
+        assert(result.try_get_as<bool?>(out value));
+        assert(value == true);
+    });
+
+    // ==================== Ternary Tests ====================
+
+    Test.add_func("/invercargill/expressions/ternary_true", () => {
+        var expr = new TernaryExpression(
+            new LiteralExpression(new NativeElement<bool?>(true)),
+            new LiteralExpression(new NativeElement<string?>("yes")),
+            new LiteralExpression(new NativeElement<string?>("no"))
+        );
+        var props = new PropertyDictionary();
+        var context = new EvaluationContext(props);
+        
+        var result = expr.evaluate(context);
+        assert(result != null);
+        
+        string? value;
+        assert(result.try_get_as<string?>(out value));
+        assert(value == "yes");
+    });
+
+    Test.add_func("/invercargill/expressions/ternary_false", () => {
+        var expr = new TernaryExpression(
+            new LiteralExpression(new NativeElement<bool?>(false)),
+            new LiteralExpression(new NativeElement<string?>("yes")),
+            new LiteralExpression(new NativeElement<string?>("no"))
+        );
+        var props = new PropertyDictionary();
+        var context = new EvaluationContext(props);
+        
+        var result = expr.evaluate(context);
+        assert(result != null);
+        
+        string? value;
+        assert(result.try_get_as<string?>(out value));
+        assert(value == "no");
+    });
+
+    // ==================== Property Access Tests ====================
+
+    Test.add_func("/invercargill/expressions/property_access", () => {
+        var person = new ExprTestPerson("Alice", 30);
+        var expr = new PropertyExpression(
+            new VariableExpression("p"),
+            "name"
+        );
+        var props = new PropertyDictionary();
+        props.set("p", new NativeElement<ExprTestPerson?>(person));
+        var context = new EvaluationContext(props);
+        
+        var result = expr.evaluate(context);
+        assert(result != null);
+        
+        string? value;
+        assert(result.try_get_as<string?>(out value));
+        assert(value == "Alice");
+    });
+
+    Test.add_func("/invercargill/expressions/nested_property_access", () => {
+        var person = new ExprTestPerson("Bob", 25);
+        var expr = new PropertyExpression(
+            new VariableExpression("p"),
+            "age"
+        );
+        var props = new PropertyDictionary();
+        props.set("p", new NativeElement<ExprTestPerson?>(person));
+        var context = new EvaluationContext(props);
+        
+        var result = expr.evaluate(context);
+        assert(result != null);
+        
+        int? value;
+        assert(result.try_get_as<int?>(out value));
+        assert(value == 25);
+    });
+
+    // ==================== Bracketed Tests ====================
+
+    Test.add_func("/invercargill/expressions/bracketed_expression", () => {
+        // (5 + 3) * 2 = 16
+        var inner = new BracketedExpression(
+            new BinaryExpression(
+                new LiteralExpression(new NativeElement<int?>(5)),
+                new LiteralExpression(new NativeElement<int?>(3)),
+                BinaryOperator.ADD
+            )
+        );
+        var expr = new BinaryExpression(
+            inner,
+            new LiteralExpression(new NativeElement<int?>(2)),
+            BinaryOperator.MULTIPLY
+        );
+        var props = new PropertyDictionary();
+        var context = new EvaluationContext(props);
+        
+        var result = expr.evaluate(context);
+        assert(result != null);
+        
+        int64? value;
+        assert(result.try_get_as<int64?>(out value));
+        assert(value == 16);
+    });
+
+    // ==================== Complex Expression Tests ====================
+
+    Test.add_func("/invercargill/expressions/complex_expression", () => {
+        // a > 5 && b < 10
+        var expr = new BinaryExpression(
+            new BinaryExpression(
+                new VariableExpression("a"),
+                new LiteralExpression(new NativeElement<int?>(5)),
+                BinaryOperator.GREATER_THAN
+            ),
+            new BinaryExpression(
+                new VariableExpression("b"),
+                new LiteralExpression(new NativeElement<int?>(10)),
+                BinaryOperator.LESS_THAN
+            ),
+            BinaryOperator.AND
+        );
+        var props = new PropertyDictionary();
+        props.set("a", new NativeElement<int?>(7));
+        props.set("b", new NativeElement<int?>(8));
+        var context = new EvaluationContext(props);
+        
+        var result = expr.evaluate(context);
+        assert(result != null);
+        
+        bool? value;
+        assert(result.try_get_as<bool?>(out value));
+        assert(value == true);
+    });
+
+    // ==================== Lambda Tests ====================
+
+    Test.add_func("/invercargill/expressions/lambda_basic", () => {
+        // x => x > 5
+        var body = new BinaryExpression(
+            new VariableExpression("x"),
+            new LiteralExpression(new NativeElement<int?>(5)),
+            BinaryOperator.GREATER_THAN
+        );
+        var expr = new LambdaExpression("x", body);
+        var props = new PropertyDictionary();
+        var context = new EvaluationContext(props);
+        
+        var result = expr.evaluate(context);
+        assert(result != null);
+        
+        var lambda = result as LambdaElement;
+        assert(lambda != null);
+        assert(lambda.get_lambda().parameter_name == "x");
+    });
+
+    // ==================== Function Call Tests ====================
+
+    Test.add_func("/invercargill/expressions/function_call_where", () => {
+        // Create an enumerable of test persons
+        var persons = new Series<ExprTestPerson>();
+        persons.add(new ExprTestPerson("Alice", 30, 5));
+        persons.add(new ExprTestPerson("Bob", 25, 3));
+        persons.add(new ExprTestPerson("Charlie", 35, 7));
+        
+        // persons.where(p => p.rank > 4)
+        var lambda_body = new BinaryExpression(
+            new PropertyExpression(new VariableExpression("p"), "rank"),
+            new LiteralExpression(new NativeElement<int?>(4)),
+            BinaryOperator.GREATER_THAN
+        );
+        var lambda = new LambdaExpression("p", lambda_body);
+        
+        var args = new Series<Expression>();
+        args.add(lambda);
+        
+        var expr = new FunctionCallExpression(
+            new LiteralExpression(new NativeElement<Enumerable<ExprTestPerson>?>(persons)),
+            "where",
+            args
+        );
+        var props = new PropertyDictionary();
+        var context = new EvaluationContext(props);
+        
+        var result = expr.evaluate(context);
+        assert(result != null);
+        
+        Elements elements;
+        assert(result.try_get_as<Elements>(out elements));
+        
+        // Should have 2 elements (Alice with rank 5, Charlie with rank 7)
+        var count = 0;
+        elements.iterate(e => count++);
+        assert(count == 2);
+    });
+
+    Test.add_func("/invercargill/expressions/function_call_select", () => {
+        // Create an enumerable of test persons
+        var persons = new Series<ExprTestPerson>();
+        persons.add(new ExprTestPerson("Alice", 30));
+        persons.add(new ExprTestPerson("Bob", 25));
+        
+        // persons.select(p => p.name)
+        var lambda_body = new PropertyExpression(new VariableExpression("p"), "name");
+        var lambda = new LambdaExpression("p", lambda_body);
+        
+        var args = new Series<Expression>();
+        args.add(lambda);
+        
+        var expr = new FunctionCallExpression(
+            new LiteralExpression(new NativeElement<Enumerable<ExprTestPerson>?>(persons)),
+            "select",
+            args
+        );
+        var props = new PropertyDictionary();
+        var context = new EvaluationContext(props);
+        
+        var result = expr.evaluate(context);
+        assert(result != null);
+        
+        Elements elements;
+        assert(result.try_get_as<Elements>(out elements));
+        
+        // Should have 2 string elements
+        var names = new Series<string>();
+        elements.iterate(e => {
+            string? name;
+            if(e.try_get_as<string?>(out name)) {
+                names.add(name);
+            }
+        });
+        assert(names.length == 2);
+        
+        // Check names using iterate
+        var idx = 0;
+        names.iterate(n => {
+            if (idx == 0) assert(n == "Alice");
+            if (idx == 1) assert(n == "Bob");
+            idx++;
+        });
+    });
+
+    Test.add_func("/invercargill/expressions/function_call_first", () => {
+        // Create an enumerable of test persons
+        var persons = new Series<ExprTestPerson>();
+        persons.add(new ExprTestPerson("Alice", 30));
+        persons.add(new ExprTestPerson("Bob", 25));
+        
+        // persons.first()
+        var expr = new FunctionCallExpression(
+            new LiteralExpression(new NativeElement<Enumerable<ExprTestPerson>?>(persons)),
+            "first"
+        );
+        var props = new PropertyDictionary();
+        var context = new EvaluationContext(props);
+        
+        var result = expr.evaluate(context);
+        assert(result != null);
+        
+        ExprTestPerson? person;
+        assert(result.try_get_as<ExprTestPerson?>(out person));
+        assert(person.name == "Alice");
+    });
+
+    Test.add_func("/invercargill/expressions/function_call_count", () => {
+        // Create an enumerable of test persons
+        var persons = new Series<ExprTestPerson>();
+        persons.add(new ExprTestPerson("Alice", 30));
+        persons.add(new ExprTestPerson("Bob", 25));
+        persons.add(new ExprTestPerson("Charlie", 35));
+        
+        // persons.count()
+        var expr = new FunctionCallExpression(
+            new LiteralExpression(new NativeElement<Enumerable<ExprTestPerson>?>(persons)),
+            "count"
+        );
+        var props = new PropertyDictionary();
+        var context = new EvaluationContext(props);
+        
+        var result = expr.evaluate(context);
+        assert(result != null);
+        
+        int? count;
+        assert(result.try_get_as<int?>(out count));
+        assert(count == 3);
+    });
+
+    Test.add_func("/invercargill/expressions/function_call_any", () => {
+        // Create an enumerable of test persons
+        var persons = new Series<ExprTestPerson>();
+        persons.add(new ExprTestPerson("Alice", 30, 5));
+        persons.add(new ExprTestPerson("Bob", 25, 3));
+        
+        // persons.any(p => p.rank > 4)
+        var lambda_body = new BinaryExpression(
+            new PropertyExpression(new VariableExpression("p"), "rank"),
+            new LiteralExpression(new NativeElement<int?>(4)),
+            BinaryOperator.GREATER_THAN
+        );
+        var lambda = new LambdaExpression("p", lambda_body);
+        
+        var args = new Series<Expression>();
+        args.add(lambda);
+        
+        var expr = new FunctionCallExpression(
+            new LiteralExpression(new NativeElement<Enumerable<ExprTestPerson>?>(persons)),
+            "any",
+            args
+        );
+        var props = new PropertyDictionary();
+        var context = new EvaluationContext(props);
+        
+        var result = expr.evaluate(context);
+        assert(result != null);
+        
+        bool? any;
+        assert(result.try_get_as<bool?>(out any));
+        assert(any == true);
+    });
+
+    // ==================== Chained Function Tests ====================
+
+    Test.add_func("/invercargill/expressions/chained_where_first", () => {
+        // Create an enumerable of test persons
+        var persons = new Series<ExprTestPerson>();
+        persons.add(new ExprTestPerson("Alice", 30, 5));
+        persons.add(new ExprTestPerson("Bob", 25, 3));
+        persons.add(new ExprTestPerson("Charlie", 35, 7));
+        
+        // persons.where(p => p.rank > 4).first()
+        var lambda_body = new BinaryExpression(
+            new PropertyExpression(new VariableExpression("p"), "rank"),
+            new LiteralExpression(new NativeElement<int?>(4)),
+            BinaryOperator.GREATER_THAN
+        );
+        var lambda = new LambdaExpression("p", lambda_body);
+        
+        var args = new Series<Expression>();
+        args.add(lambda);
+        
+        var where_call = new FunctionCallExpression(
+            new LiteralExpression(new NativeElement<Enumerable<ExprTestPerson>?>(persons)),
+            "where",
+            args
+        );
+        
+        var first_call = new FunctionCallExpression(
+            where_call,
+            "first"
+        );
+        
+        var props = new PropertyDictionary();
+        var context = new EvaluationContext(props);
+        
+        var result = first_call.evaluate(context);
+        assert(result != null);
+        
+        ExprTestPerson? person;
+        assert(result.try_get_as<ExprTestPerson?>(out person));
+        assert(person.name == "Alice"); // First with rank > 4
+    });
+
+    // ==================== Closure Tests ====================
+
+    Test.add_func("/invercargill/expressions/closure_capture", () => {
+        // Test that lambda can capture variables from outer scope
+        // threshold = 4
+        // persons.where(p => p.rank > threshold)
+        var persons = new Series<ExprTestPerson>();
+        persons.add(new ExprTestPerson("Alice", 30, 5));
+        persons.add(new ExprTestPerson("Bob", 25, 3));
+        persons.add(new ExprTestPerson("Charlie", 35, 7));
+        
+        var threshold = 4;
+        
+        // Create context with threshold
+        var props = new PropertyDictionary();
+        props.set("threshold", new NativeElement<int?>(threshold));
+        var parent_context = new EvaluationContext(props);
+        
+        // Lambda body: p.rank > threshold
+        var lambda_body = new BinaryExpression(
+            new PropertyExpression(new VariableExpression("p"), "rank"),
+            new VariableExpression("threshold"),
+            BinaryOperator.GREATER_THAN
+        );
+        var lambda = new LambdaExpression("p", lambda_body);
+        
+        var args = new Series<Expression>();
+        args.add(lambda);
+        
+        var expr = new FunctionCallExpression(
+            new LiteralExpression(new NativeElement<Enumerable<ExprTestPerson>?>(persons)),
+            "where",
+            args
+        );
+        
+        var result = expr.evaluate(parent_context);
+        assert(result != null);
+        
+        Elements elements;
+        assert(result.try_get_as<Elements>(out elements));
+        
+        // Should have 2 elements (Alice with rank 5, Charlie with rank 7)
+        var count = 0;
+        elements.iterate(e => count++);
+        assert(count == 2);
+    });
+
+    // ==================== Parser Tests ====================
+
+    Test.add_func("/invercargill/expressions/parser_literal_int", () => {
+        var expr = ExpressionParser.parse("42");
+        var props = new PropertyDictionary();
+        var context = new EvaluationContext(props);
+        
+        var result = expr.evaluate(context);
+        assert(result != null);
+        
+        int64? value;
+        assert(result.try_get_as<int64?>(out value));
+        assert(value == 42);
+    });
+
+    Test.add_func("/invercargill/expressions/parser_literal_string", () => {
+        var expr = ExpressionParser.parse("\"hello\"");
+        var props = new PropertyDictionary();
+        var context = new EvaluationContext(props);
+        
+        var result = expr.evaluate(context);
+        assert(result != null);
+        
+        string? value;
+        assert(result.try_get_as<string?>(out value));
+        assert(value == "hello");
+    });
+
+    Test.add_func("/invercargill/expressions/parser_literal_bool", () => {
+        var expr = ExpressionParser.parse("true");
+        var props = new PropertyDictionary();
+        var context = new EvaluationContext(props);
+        
+        var result = expr.evaluate(context);
+        assert(result != null);
+        
+        bool? value;
+        assert(result.try_get_as<bool?>(out value));
+        assert(value == true);
+    });
+
+    Test.add_func("/invercargill/expressions/parser_variable", () => {
+        var expr = ExpressionParser.parse("x");
+        var props = new PropertyDictionary();
+        props.set("x", new NativeElement<int?>(10));
+        var context = new EvaluationContext(props);
+        
+        var result = expr.evaluate(context);
+        assert(result != null);
+        
+        int? value;
+        assert(result.try_get_as<int?>(out value));
+        assert(value == 10);
+    });
+
+    Test.add_func("/invercargill/expressions/parser_binary_equals", () => {
+        var expr = ExpressionParser.parse("5 == 5");
+        var props = new PropertyDictionary();
+        var context = new EvaluationContext(props);
+        
+        var result = expr.evaluate(context);
+        assert(result != null);
+        
+        bool? value;
+        assert(result.try_get_as<bool?>(out value));
+        assert(value == true);
+    });
+
+    Test.add_func("/invercargill/expressions/parser_binary_arithmetic", () => {
+        var expr = ExpressionParser.parse("5 + 3");
+        var props = new PropertyDictionary();
+        var context = new EvaluationContext(props);
+        
+        var result = expr.evaluate(context);
+        assert(result != null);
+        
+        int64? value;
+        assert(result.try_get_as<int64?>(out value));
+        assert(value == 8);
+    });
+
+    Test.add_func("/invercargill/expressions/parser_binary_logical", () => {
+        var expr = ExpressionParser.parse("true && false");
+        var props = new PropertyDictionary();
+        var context = new EvaluationContext(props);
+        
+        var result = expr.evaluate(context);
+        assert(result != null);
+        
+        bool? value;
+        assert(result.try_get_as<bool?>(out value));
+        assert(value == false);
+    });
+
+    Test.add_func("/invercargill/expressions/parser_ternary", () => {
+        var expr = ExpressionParser.parse("true ? \"yes\" : \"no\"");
+        var props = new PropertyDictionary();
+        var context = new EvaluationContext(props);
+        
+        var result = expr.evaluate(context);
+        assert(result != null);
+        
+        string? value;
+        assert(result.try_get_as<string?>(out value));
+        assert(value == "yes");
+    });
+
+    Test.add_func("/invercargill/expressions/parser_property_access", () => {
+        var person = new ExprTestPerson("Alice", 30);
+        var expr = ExpressionParser.parse("p.name");
+        var props = new PropertyDictionary();
+        props.set("p", new NativeElement<ExprTestPerson?>(person));
+        var context = new EvaluationContext(props);
+        
+        var result = expr.evaluate(context);
+        assert(result != null);
+        
+        string? value;
+        assert(result.try_get_as<string?>(out value));
+        assert(value == "Alice");
+    });
+
+    Test.add_func("/invercargill/expressions/parser_bracketed", () => {
+        // (5 + 3) * 2 = 16
+        var expr = ExpressionParser.parse("(5 + 3) * 2");
+        var props = new PropertyDictionary();
+        var context = new EvaluationContext(props);
+        
+        var result = expr.evaluate(context);
+        assert(result != null);
+        
+        int64? value;
+        assert(result.try_get_as<int64?>(out value));
+        assert(value == 16);
+    });
+
+    Test.add_func("/invercargill/expressions/parser_unary_negate", () => {
+        var expr = ExpressionParser.parse("-5");
+        var props = new PropertyDictionary();
+        var context = new EvaluationContext(props);
+        
+        var result = expr.evaluate(context);
+        assert(result != null);
+        
+        int64? value;
+        assert(result.try_get_as<int64?>(out value));
+        assert(value == -5);
+    });
+
+    Test.add_func("/invercargill/expressions/parser_unary_not", () => {
+        var expr = ExpressionParser.parse("!false");
+        var props = new PropertyDictionary();
+        var context = new EvaluationContext(props);
+        
+        var result = expr.evaluate(context);
+        assert(result != null);
+        
+        bool? value;
+        assert(result.try_get_as<bool?>(out value));
+        assert(value == true);
+    });
+
+    Test.add_func("/invercargill/expressions/parser_lambda", () => {
+        var expr = ExpressionParser.parse("x => x > 5");
+        var props = new PropertyDictionary();
+        var context = new EvaluationContext(props);
+        
+        var result = expr.evaluate(context);
+        assert(result != null);
+        
+        var lambda = result as LambdaElement;
+        assert(lambda != null);
+        assert(lambda.get_lambda().parameter_name == "x");
+    });
+
+    Test.add_func("/invercargill/expressions/parser_complex", () => {
+        // a > 5 && b < 10
+        var expr = ExpressionParser.parse("a > 5 && b < 10");
+        var props = new PropertyDictionary();
+        props.set("a", new NativeElement<int?>(7));
+        props.set("b", new NativeElement<int?>(8));
+        var context = new EvaluationContext(props);
+        
+        var result = expr.evaluate(context);
+        assert(result != null);
+        
+        bool? value;
+        assert(result.try_get_as<bool?>(out value));
+        assert(value == true);
+    });
+
+    Test.add_func("/invercargill/expressions/parser_operator_precedence", () => {
+        // 2 + 3 * 4 = 14 (not 20)
+        var expr = ExpressionParser.parse("2 + 3 * 4");
+        var props = new PropertyDictionary();
+        var context = new EvaluationContext(props);
+        
+        var result = expr.evaluate(context);
+        assert(result != null);
+        
+        int64? value;
+        assert(result.try_get_as<int64?>(out value));
+        assert(value == 14);
+    });
+
+    Test.add_func("/invercargill/expressions/parser_comparison_chain", () => {
+        // Test chained comparisons with proper precedence
+        var expr = ExpressionParser.parse("5 < 10 && 10 > 5");
+        var props = new PropertyDictionary();
+        var context = new EvaluationContext(props);
+        
+        var result = expr.evaluate(context);
+        assert(result != null);
+        
+        bool? value;
+        assert(result.try_get_as<bool?>(out value));
+        assert(value == true);
+    });
+}

+ 1 - 0
src/tests/TestRunner.vala

@@ -39,6 +39,7 @@ public static int main(string[] args) {
     modifiers_tests();
     data_structures_tests();
     remaining_components_tests();
+    expression_tests();
 
     var result = Test.run();
     

+ 1 - 0
src/tests/meson.build

@@ -30,6 +30,7 @@ sources += files('Integration/EnumerableMethods.vala')
 sources += files('Integration/Modifiers.vala')
 sources += files('Integration/DataStructures.vala')
 sources += files('Integration/RemainingComponents.vala')
+sources += files('Integration/Expressions.vala')
 
 sources += files('Speed/SpeedTest.vala')
 sources += files('Speed/Series.vala')