19 Commits a1ab95e0c7 ... 25020cc4a9

Author SHA1 Message Date
  clanker 25020cc4a9 fix: transfer delegate ownership in PropertyMappingBuilder ((owned) casts) — valac emits copy-and-destroy for plain param-to-owned-field delegate assignments, freeing the stored closure when the builder method returns and leaving every long-lived PropertyMapper reading freed memory on map_into/map_from (the intermittent, layout-dependent SIGSEGV that surfaced far from the cause, e.g. during Statum slot signing) 5 days ago
  clanker fd2a7d29c7 Drop libc.so.6 from runtime deps (implicit on Linux, absent on musl) 1 week ago
  clanker 0418fb4398 Declare gcc build dep in MANIFEST 1 week ago
  clanker 0b9dae6b5f Add USM packaging: MANIFEST.usm, usm-scripts, .usmignore 1 week ago
  clanker 9992e252d0 fix(mapping): keep PropertyMapper target alive after builder disposal 1 week ago
  Billy Barrow 169d933579 feat(expressions): add DateTimeFunctionAccessor for DateTime functions 5 months ago
  Billy Barrow 1028887ad9 fix(expressions): add explicit type checks before casting 5 months ago
  Billy Barrow 10c3a74123 feat(expressions): add literal type suffixes and char support 5 months ago
  Billy Barrow 752d726731 feat(expressions): allow expr() to be called without parameters 5 months ago
  Billy Barrow 905bf49545 refactor(expressions): change parse_with_params to accept enumerable 5 months ago
  Billy Barrow dc782a4e7e feat(expressions): add parameterized expression support with $N syntax 5 months ago
  Billy Barrow 0583c95dea Clean up 5 months ago
  Billy Barrow d7f39f81b5 feat(expressions): add nullable type support and handlebars-style format function 5 months ago
  Billy Barrow 7446b2204e fix(datastructures): handle null index in remove_first_where 6 months ago
  Billy Barrow 956b152ae9 feat(expressions): add to_value method for type conversion 6 months ago
  Billy Barrow 897cdf3e70 feat(expressions): add TypeAccessorRegistry and string accessors 6 months ago
  Billy Barrow 4427901b4d feat(expressions): add global functions and fix nested property access 6 months ago
  Billy Barrow 132e8fd399 feat(expressions): add lot literals and global iterate functions 6 months ago
  Billy Barrow 24613fa490 Expressive vibes 6 months ago
65 changed files with 11640 additions and 71 deletions
  1. 2 1
      .gitignore
  2. 12 0
      .usmignore
  3. 8 20
      MANIFEST.usm
  4. 4 1
      src/lib/DataStructures/Vector.vala
  5. 2 0
      src/lib/Delegates.vala
  6. 283 18
      src/lib/Element.vala
  7. 5 0
      src/lib/Enumerable.vala
  8. 66 0
      src/lib/Expressions/AccessorFactory.vala
  9. 129 0
      src/lib/Expressions/BinaryOperator.vala
  10. 15 0
      src/lib/Expressions/Convenience.vala
  11. 624 0
      src/lib/Expressions/DateTimeFunctionAccessor.vala
  12. 89 0
      src/lib/Expressions/Elements/GlobalFunctionsElement.vala
  13. 68 0
      src/lib/Expressions/Elements/LambdaElement.vala
  14. 611 0
      src/lib/Expressions/EnumerableFunctionAccessor.vala
  15. 177 0
      src/lib/Expressions/EvaluationContext.vala
  16. 53 0
      src/lib/Expressions/Expression.vala
  17. 17 0
      src/lib/Expressions/ExpressionError.vala
  18. 146 0
      src/lib/Expressions/ExpressionEvaluator.vala
  19. 535 0
      src/lib/Expressions/ExpressionParser.vala
  20. 563 0
      src/lib/Expressions/ExpressionTokenizer.vala
  21. 38 0
      src/lib/Expressions/ExpressionType.vala
  22. 80 0
      src/lib/Expressions/ExpressionVisitor.vala
  23. 355 0
      src/lib/Expressions/Expressions/BinaryExpression.vala
  24. 47 0
      src/lib/Expressions/Expressions/BracketedExpression.vala
  25. 123 0
      src/lib/Expressions/Expressions/FunctionCallExpression.vala
  26. 85 0
      src/lib/Expressions/Expressions/GlobalFunctionCallExpression.vala
  27. 110 0
      src/lib/Expressions/Expressions/LambdaExpression.vala
  28. 216 0
      src/lib/Expressions/Expressions/LiteralExpression.vala
  29. 306 0
      src/lib/Expressions/Expressions/LotLiteralExpression.vala
  30. 52 0
      src/lib/Expressions/Expressions/ParameterExpression.vala
  31. 65 0
      src/lib/Expressions/Expressions/PropertyExpression.vala
  32. 76 0
      src/lib/Expressions/Expressions/TernaryExpression.vala
  33. 126 0
      src/lib/Expressions/Expressions/UnaryExpression.vala
  34. 42 0
      src/lib/Expressions/Expressions/VariableExpression.vala
  35. 49 0
      src/lib/Expressions/FunctionAccessor.vala
  36. 228 0
      src/lib/Expressions/GlobalFunctionAccessor.vala
  37. 218 0
      src/lib/Expressions/IterateFunctionAccessor.vala
  38. 101 0
      src/lib/Expressions/LotPropertyAccessor.vala
  39. 90 0
      src/lib/Expressions/ObjectPropertyAccessor.vala
  40. 30 0
      src/lib/Expressions/PropertiesPropertyAccessor.vala
  41. 20 0
      src/lib/Expressions/PropertyAccessor.vala
  42. 433 0
      src/lib/Expressions/StringFunctionAccessor.vala
  43. 123 0
      src/lib/Expressions/StringPropertyAccessor.vala
  44. 273 0
      src/lib/Expressions/TypeAccessorRegistry.vala
  45. 64 0
      src/lib/Expressions/UnaryOperator.vala
  46. 4 4
      src/lib/Interfaces.vala
  47. 6 6
      src/lib/Interfaces/AddressableBytes.vala
  48. 0 6
      src/lib/Interfaces/BinaryData.vala
  49. 0 6
      src/lib/Interfaces/Grouped.vala
  50. 23 8
      src/lib/Mapping/PropertyMapper.vala
  51. 426 0
      src/lib/Nullable.c
  52. 181 0
      src/lib/Nullable.vala
  53. 141 0
      src/lib/Operators/Contain.vala
  54. 18 0
      src/lib/Promotions/Elements.vala
  55. 2 0
      src/lib/Promotions/Registration.c
  56. 150 0
      src/lib/Safety.vala
  57. 53 1
      src/lib/meson.build
  58. 2307 0
      src/tests/Integration/Expressions.vala
  59. 964 0
      src/tests/Integration/NativeElement.vala
  60. 578 0
      src/tests/Integration/Nullable.vala
  61. 3 0
      src/tests/TestRunner.vala
  62. 3 0
      src/tests/meson.build
  63. 10 0
      usm-scripts/build.sh
  64. 10 0
      usm-scripts/install.sh
  65. 2 0
      usm-scripts/post-install.sh

+ 2 - 1
.gitignore

@@ -4,4 +4,5 @@ build.ninja
 compile_commands.json
 /tests
 .ninja_log
-.ninja_deps
+.ninja_deps
+/builddir

+ 12 - 0
.usmignore

@@ -0,0 +1,12 @@
+# Build trees at any depth (directory-only: the directory and all beneath it)
+builddir*/
+
+# Database artifacts by basename at any depth
+*.sqlite*
+db.sqlite
+
+# Foreign build tree at the package root
+build/
+
+# Local agent session state (directory-only)
+.kilo/

+ 8 - 20
MANIFEST.usm

@@ -15,36 +15,24 @@
   "depends": {
     "runtime": [
       "lib:libglib-2.0.so.0",
-      "lib:libgobject-2.0.so.0",
-      "lib:libgee-0.8.so.2",
-      "lib:libc.so.6",
-      "lib:libjson-glib-1.0.so",
-      "lib:libgio-2.0.so.0",
-      "lib:libpcre2-8.so.0",
-      "lib:libffi.so.8",
-      "lib:libgmodule-2.0.so.0",
-      "lib:libz.so.1",
-      "lib:libmount.so.1",
-      "lib:libselinux.so.1",
-      "lib:libblkid.so.1"
+      "lib:libgobject-2.0.so.0"
     ],
     "build": [
       "bin:valac",
       "bin:meson",
+      "bin:ninja",
+      "bin:gcc",
       "bin:g-ir-compiler",
       "pc:glib-2.0.pc",
-      "pc:gobject-2.0.pc",
-      "pc:gio-2.0.pc",
-      "pc:gee-0.8.pc",
-      "pc:json-glib-1.0.pc"
+      "pc:gobject-2.0.pc"
     ],
     "manage": [
       "bin:bash"
     ]
   },
   "execs": {
-    "build": "scripts/build.sh",
-    "install": "scripts/install.sh",
-    "post_install": "scripts/post-install.sh"
+    "build": "usm-scripts/build.sh",
+    "install": "usm-scripts/install.sh",
+    "postInstall": "usm-scripts/post-install.sh"
   }
-}
+}

+ 4 - 1
src/lib/DataStructures/Vector.vala

@@ -230,7 +230,10 @@ namespace Invercargill.DataStructures {
         }
 
         public void remove_first_where(Invercargill.PredicateDelegate<T> predicate) {
-            remove_internal(first_index_of(predicate));
+            var index = first_index_of(predicate);
+            if (index != null) {
+                remove_internal(index);
+            }
         }
 
         public void remove_all_where(Invercargill.PredicateDelegate<T> predicate) {

+ 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);
+
 }

+ 283 - 18
src/lib/Element.vala

@@ -28,10 +28,10 @@ namespace Invercargill {
         }
 
         public virtual string type_name() {
-            return type().name();
+            return type()?.name() ?? "UNKNOWN";
         }
 
-        public virtual T? @as<T>() throws ElementError {
+        public virtual T @as<T>() throws ElementError {
             T result;
             if(try_get_as<T>(out result)) {
                 return result;
@@ -39,14 +39,6 @@ namespace Invercargill {
             throw new ElementError.INVALID_CONVERSION(@"Could not convert from $(type_name()) to $(typeof(T).name()).");
         }
 
-        public virtual T? as_or_default<T>() {
-            T result;
-            if(try_get_as<T>(out result)) {
-                return result;
-            }
-            return null;
-        }
-
         public virtual T assert_as<T>() {
             T result;
             if(try_get_as<T>(out result)) {
@@ -56,20 +48,214 @@ namespace Invercargill {
             assert_not_reached();
         }
 
+        public virtual Value to_value(Type requested_type) throws Error {
+            var value = Value(requested_type);
+            if(requested_type.is_a(Type.BOOLEAN)) {
+                value.set_boolean(as<bool>());
+                return value;
+            }
+            if(requested_type.is_a(Type.CHAR)) {
+                value.set_schar(as<int8>());
+                return value;
+            }
+            if(requested_type.is_a(Type.DOUBLE)) {
+                value.set_double(as<double?>());
+                return value;
+            }
+            if(requested_type.is_a(Type.ENUM)) {
+                value.set_enum(as<int>());
+                return value;
+            }
+            if(requested_type.is_a(Type.FLAGS)) {
+                value.set_flags(as<int>());
+                return value;
+            }
+            if(requested_type.is_a(Type.FLOAT)) {
+                value.set_float(as<float?>());
+                return value;
+            }
+            if(requested_type.is_a(Type.INT)) {
+                value.set_int(as<int>());
+                return value;
+            }
+            if(requested_type.is_a(Type.INT64)) {
+                value.set_int64(as<int64?>());
+                return value;
+            }
+            if(requested_type.is_a(Type.LONG)) {
+                value.set_long(as<long>());
+                return value;
+            }
+            if(requested_type.is_a(Type.STRING)) {
+                value.set_string(as<string>());
+                return value;
+            }
+            if(requested_type.is_a(Type.UCHAR)) {
+                value.set_uchar(as<uchar>());
+                return value;
+            }
+            if(requested_type.is_a(Type.UINT)) {
+                value.set_uint(as<uint>());
+                return value;
+            }
+            if(requested_type.is_a(Type.UINT64)) {
+                value.set_uint64(as<uint64?>());
+                return value;
+            }
+            if(requested_type.is_a(Type.ULONG)) {
+                value.set_ulong(as<ulong>());
+                return value;
+            }
+
+            value.set_object(as<Object>());
+            return value;
+        }
 
         public virtual string to_string() {
             return @"Element[$(type_name())]";
         }
 
+        public virtual string stringify() {
+            if (is_null()) {
+                return "";
+            }
+            
+            // Try string
+            string? str_val;
+            if (try_get_as<string>(out str_val)) {
+                return str_val ?? "";
+            }
+            
+            var type = type();
+            if(type.is_a(typeof(Stringifyable))) {
+                return assert_as<Stringifyable>().to_string();
+            }
+            if(type == typeof(bool)) {
+                return assert_as<bool>().to_string();
+            } else if(type == typeof(char)) {
+                return assert_as<char>().to_string();
+            } else if(type == typeof(uchar)) {
+                return assert_as<uchar>().to_string();
+            } else if(type == typeof(int)) {
+                return assert_as<int>().to_string();
+            } else if(type == typeof(uint)) {
+                return assert_as<uint>().to_string();
+            } else if(type == typeof(long)) {
+                return assert_as<long>().to_string();
+            } else if(type == typeof(ulong)) {
+                return assert_as<ulong>().to_string();
+            } else if(type == typeof(int64)) {
+                return assert_as<int64?>().to_string();
+            } else if(type == typeof(uint64)) {
+                return assert_as<uint64?>().to_string();
+            } else if(type == typeof(float)) {
+                return "%g".printf(assert_as<float?>());
+            } else if(type == typeof(double)) {
+                return "%g".printf(assert_as<double?>());
+            } else if(type == typeof(string)) {
+                return assert_as<string>().to_string();
+            }
+
+            // Fallback to default to_string()
+            return to_string();
+        }
+
+        public virtual string? as_string_or_null() {
+            string res;
+            if(!is_null() && assignable_to_type(typeof(string))){
+                if(try_get_as<string>(out res))
+                    return res;
+            }
+            return null;
+        }
+
+        public virtual bool? as_bool_or_null() {
+            bool res;
+            if(!is_null() && assignable_to_type(typeof(bool))){
+                if(try_get_as<bool>(out res))
+                    return res;
+            }
+            return null;
+        }
+
+        public virtual int? as_int_or_null() {
+            int res;
+            if(!is_null() && assignable_to_type(typeof(int))){
+                if(try_get_as<int>(out res))
+                    return res;
+            }
+            return null;
+        }
+
+        public virtual uint? as_uint_or_null() {
+            uint res;
+            if(!is_null() && assignable_to_type(typeof(uint))){
+                if(try_get_as<uint>(out res))
+                    return res;
+            }
+            return null;
+        }
+
+        public virtual Object? as_object_or_null() {
+            Object res;
+            if(!is_null() && assignable_to_type(typeof(Object))){
+                if(try_get_as<Object>(out res))
+                    return res;
+            }
+            return null;
+        }
+
+        public virtual float? as_float_or_null() {
+            float? res;
+            if(!is_null() && assignable_to_type(typeof(float?))){
+                if(try_get_as<float?>(out res))
+                    return res;
+            }
+            return null;
+        }
+
+        public virtual double? as_double_or_null() {
+            double? res;
+            if(!is_null() && assignable_to_type(typeof(double?))){
+                if(try_get_as<double?>(out res))
+                    return res;
+            }
+            return null;
+        }
+
+        public virtual int64? as_int64_or_null() {
+            int64? res;
+            if(!is_null() && assignable_to_type(typeof(int64?))){
+                if(try_get_as<int64?>(out res))
+                    return res;
+            }
+            return null;
+        }
+
+        public virtual long? as_long_or_null() {
+            long res;
+            if(!is_null() && assignable_to_type(typeof(long))){
+                if(try_get_as<long>(out res))
+                    return res;
+            }
+            return null;
+        }
+
     }
 
 
     public class NativeElement<T> : Object, Element {
 
         private T object;
+        private bool _is_null;
+        public bool is_reference_type { get; private set; }
+        public bool is_nullable { get; private set; }
         
         public NativeElement(T obj) {
             object = obj;
+            _is_null = Nullable.is_null<T>(obj);
+            is_reference_type = Nullable.is_reference<T>(obj);
+            is_nullable = Nullable.is_nullable<T>(obj);
         }
 
         public bool assignable_to_type(GLib.Type type) {
@@ -87,42 +273,121 @@ namespace Invercargill {
         }
 
         public bool is_null() {
-            return false;
+            return is_nullable && _is_null;
         }
 
         public bool try_get_as<TOut>(out TOut result) {
-            if(typeof(T).is_a(typeof(Enumerable)) && typeof(TOut) == typeof(Elements)) {
+            if(!is_null() && typeof(T).is_a(typeof(Enumerable)) && typeof(TOut) == typeof(Elements)) {
                 result = (TOut)((Enumerable)object).to_elements();
                 return true;
             }
             if(assignable_to<TOut>()) {
-                result = (TOut)object;
-                return true;
+                return Nullable.convert_nullability<T, TOut>(object, out result);
             }
             result = null;
             return false;
         }
+
+        public string type_name() {
+            return Nullable.get_type_name<T>(object);
+        }
     }
 
     public class NullElement : Object, Element {
 
         public bool assignable_to_type(GLib.Type type) {
-            return true;
+            return type.is_a(Type.NONE);
         }
         public GLib.Type? type() {
-            return null;
+            return Type.NONE;
         }
         public bool is_null() {
             return true;
         }
         public bool try_get_as<T>(out T result) {
             result = null;
-            return true;
+            return false;
         }
         public string to_string() {
-            return "Element[null]";
+            return "NullElement[null]";
+        }
+
+    }
+
+    public class ValueElement : Object, Element {
+
+        private Value value;
+
+        public ValueElement(Value value) {
+            this.value = value;
         }
 
+        /**
+         * Gets the raw Value contained in this element.
+         * This is useful for extracting objects that implement interfaces
+         * where generic type checking doesn't work correctly.
+         */
+        public Value get_value() {
+            return 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;
+            }
+            if(Value.type_transformable(value.type(), type)) {
+                return true;
+            }
+            return false;
+        }
+        public GLib.Type? type() {
+            return value.type();
+        }
+        public bool is_null() {
+            var value_type = value.type();
+            if(value_type == Type.INVALID) {
+                return true;
+            }
+            if(value_type.is_a(Type.STRING)) {
+                return value.get_string() == null;
+            }
+            if(value_type.is_a(Type.BOXED)) {
+                return value.get_boxed() == null;
+            }
+            if(value_type.is_a(Type.OBJECT)) {
+                return value.get_object() == 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;
+            }
+
+            if(value_type.is_a(requested_type)) {
+                if(safely_unwrap_value<T>(value, out result))
+                    return true;
+            }
+
+            // Try value transformation for reference types
+            var converted = Value(requested_type);
+            if(value.transform(ref converted)) {
+                if(safely_unwrap_value<T>(converted, out result))
+                    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);
         }

+ 66 - 0
src/lib/Expressions/AccessorFactory.vala

@@ -0,0 +1,66 @@
+
+namespace Invercargill.Expressions {
+
+    /**
+     * Factory interface for creating PropertyAccessor instances for specific types.
+     * 
+     * Implementations of this interface are responsible for creating PropertyAccessor
+     * instances that can read properties from objects of a specific type.
+     * 
+     * Example usage:
+     * {{{
+     * public class StringPropertyAccessorFactory : Object, PropertyAccessorFactory {
+     *     public PropertyAccessor? create(Element element) {
+     *         string? value;
+     *         if (element.try_get_as(out value)) {
+     *             return new StringPropertyAccessor(value);
+     *         }
+     *         return null;
+     *     }
+     * }
+     * }}}
+     */
+    public interface PropertyAccessorFactory : Object {
+
+        /**
+         * Creates a PropertyAccessor for the given Element if applicable.
+         * 
+         * @param element The Element to create an accessor for
+         * @return A PropertyAccessor if this factory handles the element's type, null otherwise
+         */
+        public abstract PropertyAccessor? create(Element element);
+
+    }
+
+    /**
+     * Factory interface for creating FunctionAccessor instances for specific types.
+     * 
+     * Implementations of this interface are responsible for creating FunctionAccessor
+     * instances that can call functions on objects of a specific type.
+     * 
+     * Example usage:
+     * {{{
+     * public class StringFunctionAccessorFactory : Object, FunctionAccessorFactory {
+     *     public FunctionAccessor? create(Element element) {
+     *         string? value;
+     *         if (element.try_get_as(out value)) {
+     *             return new StringFunctionAccessor(value);
+     *         }
+     *         return null;
+     *     }
+     * }
+     * }}}
+     */
+    public interface FunctionAccessorFactory : Object {
+
+        /**
+         * Creates a FunctionAccessor for the given Element if applicable.
+         * 
+         * @param element The Element to create an accessor for
+         * @return A FunctionAccessor if this factory handles the element's type, null otherwise
+         */
+        public abstract FunctionAccessor? create(Element element);
+
+    }
+
+}

+ 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;
+            }
+        }
+    }
+
+}

+ 15 - 0
src/lib/Expressions/Convenience.vala

@@ -0,0 +1,15 @@
+
+namespace Invercargill.Expressions {
+
+    public Expression expr(string expression, Element? first = null, ...) throws ExpressionError {
+        if(first == null) {
+            return ExpressionParser.parse(expression);
+        }
+        return ExpressionParser.parse_with_params(expression, Wrap.va_list<Element>(first, va_list()));
+    }
+
+    public Element elem<T>(T value) {
+        return new NativeElement<T>(value);
+    }
+
+}

+ 624 - 0
src/lib/Expressions/DateTimeFunctionAccessor.vala

@@ -0,0 +1,624 @@
+using Invercargill.DataStructures;
+using Invercargill.Wrappers;
+
+namespace Invercargill.Expressions {
+
+    /**
+     * Function accessor for DateTime types.
+     * 
+     * Provides access to DateTime functions:
+     * - `compare(other)`: Compares this datetime with another, returns -1, 0, or 1
+     * - `difference(other)`: Returns the difference in seconds between two datetimes
+     * - `equal(other)`: Checks if two datetimes are equal
+     * - `format(format_string)`: Formats the datetime using a custom format string
+     * - `format_iso8601()`: Returns the datetime in ISO 8601 format
+     * - `get_day_of_month()`: Returns the day of the month (1-31)
+     * - `get_day_of_week()`: Returns the day of the week (0=Sunday, 1=Monday, etc.)
+     * - `get_day_of_year()`: Returns the day of the year (1-366)
+     * - `get_hour()`: Returns the hour (0-23)
+     * - `get_microsecond()`: Returns the microsecond (0-999999)
+     * - `get_minute()`: Returns the minute (0-59)
+     * - `get_second()`: Returns the second (0-59)
+     * - `get_seconds()`: Returns the total seconds since Unix epoch
+     * - `get_timezone()`: Returns the timezone identifier
+     * - `get_timezone_abbreviation()`: Returns the timezone abbreviation
+     * - `get_utc_offset()`: Returns the UTC offset in seconds
+     * - `get_week_numbering_year()`: Returns the week-numbering year
+     * - `get_week_of_year()`: Returns the week of the year (1-53)
+     * - `get_year()`: Returns the year
+     * - `get_ymd()`: Returns a lot containing [year, month, day]
+     * - `is_daylight_savings()`: Checks if daylight savings time is in effect
+     * - `to_local()`: Converts to local timezone
+     * - `to_timezone(tz)`: Converts to the specified timezone
+     * - `to_unix()`: Returns Unix timestamp (seconds)
+     * - `to_unix_usec()`: Returns Unix timestamp with microseconds
+     * - `to_utc()`: Converts to UTC
+     * 
+     * Example usage in expressions:
+     * {{{
+     * created_at.get_year()                    // 2024
+     * created_at.format("%Y-%m-%d")            // "2024-03-15"
+     * created_at.to_utc()                      // DateTime in UTC
+     * created_at.difference(other_date)        // seconds difference
+     * }}}
+     */
+    public class DateTimeFunctionAccessor : Object, FunctionAccessor {
+
+        /**
+         * The DateTime value being accessed.
+         */
+        private DateTime? _value;
+
+        /**
+         * Creates a new DateTimeFunctionAccessor.
+         * 
+         * @param value The DateTime value to call functions on
+         */
+        public DateTimeFunctionAccessor(DateTime? value) {
+            _value = value;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public bool has_function(string function_name) {
+            switch (function_name) {
+                case "compare":
+                case "difference":
+                case "equal":
+                case "format":
+                case "format_iso8601":
+                case "get_day_of_month":
+                case "get_day_of_week":
+                case "get_day_of_year":
+                case "get_hour":
+                case "get_microsecond":
+                case "get_minute":
+                case "get_second":
+                case "get_seconds":
+                case "get_timezone":
+                case "get_timezone_abbreviation":
+                case "get_utc_offset":
+                case "get_week_numbering_year":
+                case "get_week_of_year":
+                case "get_year":
+                case "get_ymd":
+                case "is_daylight_savings":
+                case "to_local":
+                case "to_timezone":
+                case "to_unix":
+                case "to_unix_usec":
+                case "to_utc":
+                    return true;
+                default:
+                    return false;
+            }
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public Enumerable<string> get_function_names() {
+            return new Wrappers.Array<string>(new string[]{
+                "compare",
+                "difference",
+                "equal",
+                "format",
+                "format_iso8601",
+                "get_day_of_month",
+                "get_day_of_week",
+                "get_day_of_year",
+                "get_hour",
+                "get_microsecond",
+                "get_minute",
+                "get_second",
+                "get_seconds",
+                "get_timezone",
+                "get_timezone_abbreviation",
+                "get_utc_offset",
+                "get_week_numbering_year",
+                "get_week_of_year",
+                "get_year",
+                "get_ymd",
+                "is_daylight_savings",
+                "to_local",
+                "to_timezone",
+                "to_unix",
+                "to_unix_usec",
+                "to_utc"
+            });
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public Element call_function(
+            string function_name,
+            Series<Element> arguments,
+            EvaluationContext context
+        ) throws ExpressionError {
+            
+            // Handle null DateTime
+            if (_value == null) {
+                return handle_null_function(function_name, arguments);
+            }
+
+            switch (function_name) {
+                case "compare":
+                    return call_compare(arguments);
+                
+                case "difference":
+                    return call_difference(arguments);
+                
+                case "equal":
+                    return call_equal(arguments);
+                
+                case "format":
+                    return call_format(arguments);
+                
+                case "format_iso8601":
+                    return call_format_iso8601(arguments);
+                
+                case "get_day_of_month":
+                    return call_get_day_of_month(arguments);
+                
+                case "get_day_of_week":
+                    return call_get_day_of_week(arguments);
+                
+                case "get_day_of_year":
+                    return call_get_day_of_year(arguments);
+                
+                case "get_hour":
+                    return call_get_hour(arguments);
+                
+                case "get_microsecond":
+                    return call_get_microsecond(arguments);
+                
+                case "get_minute":
+                    return call_get_minute(arguments);
+                
+                case "get_second":
+                    return call_get_second(arguments);
+                
+                case "get_seconds":
+                    return call_get_seconds(arguments);
+                
+                case "get_timezone":
+                    return call_get_timezone(arguments);
+                
+                case "get_timezone_abbreviation":
+                    return call_get_timezone_abbreviation(arguments);
+                
+                case "get_utc_offset":
+                    return call_get_utc_offset(arguments);
+                
+                case "get_week_numbering_year":
+                    return call_get_week_numbering_year(arguments);
+                
+                case "get_week_of_year":
+                    return call_get_week_of_year(arguments);
+                
+                case "get_year":
+                    return call_get_year(arguments);
+                
+                case "get_ymd":
+                    return call_get_ymd(arguments);
+                
+                case "is_daylight_savings":
+                    return call_is_daylight_savings(arguments);
+                
+                case "to_local":
+                    return call_to_local(arguments);
+                
+                case "to_timezone":
+                    return call_to_timezone(arguments);
+                
+                case "to_unix":
+                    return call_to_unix(arguments);
+                
+                case "to_unix_usec":
+                    return call_to_unix_usec(arguments);
+                
+                case "to_utc":
+                    return call_to_utc(arguments);
+                
+                default:
+                    throw new ExpressionError.FUNCTION_NOT_FOUND(
+                        @"Unknown DateTime function: $function_name"
+                    );
+            }
+        }
+
+        /**
+         * Handles function calls on null DateTime values.
+         */
+        private Element handle_null_function(string function_name, Series<Element> arguments) 
+            throws ExpressionError {
+            
+            switch (function_name) {
+                case "compare":
+                case "difference":
+                    // Return null for comparison/difference with null
+                    return new NullElement();
+                
+                case "equal":
+                    // Check if comparing with another null
+                    expect_argument_count("equal", arguments, 1);
+                    var args = to_array(arguments);
+                    DateTime? other = get_datetime_argument(args[0], "other");
+                    return new NativeElement<bool>(other == null);
+                
+                default:
+                    // Most DateTime functions return null when called on null
+                    return new NullElement();
+            }
+        }
+
+        /**
+         * Compares this datetime with another.
+         * Returns -1 if this < other, 0 if equal, 1 if this > other.
+         */
+        private Element call_compare(Series<Element> arguments) throws ExpressionError {
+            expect_argument_count("compare", arguments, 1);
+            var args = to_array(arguments);
+            DateTime? other = get_datetime_argument(args[0], "other");
+            
+            if (other == null) {
+                return new NullElement();
+            }
+            
+            return new NativeElement<int>(_value.compare(other));
+        }
+
+        /**
+         * Returns the difference in seconds between two datetimes.
+         */
+        private Element call_difference(Series<Element> arguments) throws ExpressionError {
+            expect_argument_count("difference", arguments, 1);
+            var args = to_array(arguments);
+            DateTime? other = get_datetime_argument(args[0], "other");
+            
+            if (other == null) {
+                return new NullElement();
+            }
+            
+            TimeSpan diff = _value.difference(other);
+            // TimeSpan is in microseconds, convert to seconds
+            return new NativeElement<int64?>(diff / TimeSpan.SECOND);
+        }
+
+        /**
+         * Checks if two datetimes are equal.
+         */
+        private Element call_equal(Series<Element> arguments) throws ExpressionError {
+            expect_argument_count("equal", arguments, 1);
+            var args = to_array(arguments);
+            DateTime? other = get_datetime_argument(args[0], "other");
+            
+            if (other == null) {
+                return new NativeElement<bool>(false);
+            }
+            
+            return new NativeElement<bool>(_value.equal(other));
+        }
+
+        /**
+         * Formats the datetime using a custom format string.
+         */
+        private Element call_format(Series<Element> arguments) throws ExpressionError {
+            expect_argument_count("format", arguments, 1);
+            var args = to_array(arguments);
+            string? format_string = get_string_argument(args[0], "format_string");
+            
+            if (format_string == null) {
+                return new NativeElement<string>("");
+            }
+            
+            return new NativeElement<string>(_value.format(format_string));
+        }
+
+        /**
+         * Returns the datetime in ISO 8601 format.
+         */
+        private Element call_format_iso8601(Series<Element> arguments) throws ExpressionError {
+            expect_argument_count("format_iso8601", arguments, 0);
+            // ISO 8601 format: YYYY-MM-DDTHH:MM:SS+TZ:00
+            return new NativeElement<string>(_value.format("%Y-%m-%dT%H:%M:%S%z"));
+        }
+
+        /**
+         * Returns the day of the month (1-31).
+         */
+        private Element call_get_day_of_month(Series<Element> arguments) throws ExpressionError {
+            expect_argument_count("get_day_of_month", arguments, 0);
+            return new NativeElement<int>(_value.get_day_of_month());
+        }
+
+        /**
+         * Returns the day of the week (0=Sunday, 1=Monday, etc.).
+         */
+        private Element call_get_day_of_week(Series<Element> arguments) throws ExpressionError {
+            expect_argument_count("get_day_of_week", arguments, 0);
+            return new NativeElement<int>(_value.get_day_of_week());
+        }
+
+        /**
+         * Returns the day of the year (1-366).
+         */
+        private Element call_get_day_of_year(Series<Element> arguments) throws ExpressionError {
+            expect_argument_count("get_day_of_year", arguments, 0);
+            return new NativeElement<int>(_value.get_day_of_year());
+        }
+
+        /**
+         * Returns the hour (0-23).
+         */
+        private Element call_get_hour(Series<Element> arguments) throws ExpressionError {
+            expect_argument_count("get_hour", arguments, 0);
+            return new NativeElement<int>(_value.get_hour());
+        }
+
+        /**
+         * Returns the microsecond (0-999999).
+         */
+        private Element call_get_microsecond(Series<Element> arguments) throws ExpressionError {
+            expect_argument_count("get_microsecond", arguments, 0);
+            return new NativeElement<int>(_value.get_microsecond());
+        }
+
+        /**
+         * Returns the minute (0-59).
+         */
+        private Element call_get_minute(Series<Element> arguments) throws ExpressionError {
+            expect_argument_count("get_minute", arguments, 0);
+            return new NativeElement<int>(_value.get_minute());
+        }
+
+        /**
+         * Returns the second (0-59).
+         */
+        private Element call_get_second(Series<Element> arguments) throws ExpressionError {
+            expect_argument_count("get_second", arguments, 0);
+            return new NativeElement<int>(_value.get_second());
+        }
+
+        /**
+         * Returns the total seconds since Unix epoch.
+         */
+        private Element call_get_seconds(Series<Element> arguments) throws ExpressionError {
+            expect_argument_count("get_seconds", arguments, 0);
+            return new NativeElement<int64?>(_value.to_unix());
+        }
+
+        /**
+         * Returns the timezone identifier.
+         */
+        private Element call_get_timezone(Series<Element> arguments) throws ExpressionError {
+            expect_argument_count("get_timezone", arguments, 0);
+            TimeZone tz = _value.get_timezone();
+            // TimeZone doesn't have a direct identifier getter, use abbreviation
+            return new NativeElement<string>(tz.get_abbreviation((int)_value.to_unix()));
+        }
+
+        /**
+         * Returns the timezone abbreviation.
+         */
+        private Element call_get_timezone_abbreviation(Series<Element> arguments) throws ExpressionError {
+            expect_argument_count("get_timezone_abbreviation", arguments, 0);
+            TimeZone tz = _value.get_timezone();
+            return new NativeElement<string>(tz.get_abbreviation((int)_value.to_unix()));
+        }
+
+        /**
+         * Returns the UTC offset in seconds.
+         */
+        private Element call_get_utc_offset(Series<Element> arguments) throws ExpressionError {
+            expect_argument_count("get_utc_offset", arguments, 0);
+            TimeZone tz = _value.get_timezone();
+            int offset = (int)(tz.get_offset((int)_value.to_unix()) / TimeSpan.SECOND);
+            return new NativeElement<int>(offset);
+        }
+
+        /**
+         * Returns the week-numbering year.
+         */
+        private Element call_get_week_numbering_year(Series<Element> arguments) throws ExpressionError {
+            expect_argument_count("get_week_numbering_year", arguments, 0);
+            return new NativeElement<int>(_value.get_week_numbering_year());
+        }
+
+        /**
+         * Returns the week of the year (1-53).
+         */
+        private Element call_get_week_of_year(Series<Element> arguments) throws ExpressionError {
+            expect_argument_count("get_week_of_year", arguments, 0);
+            return new NativeElement<int>(_value.get_week_of_year());
+        }
+
+        /**
+         * Returns the year.
+         */
+        private Element call_get_year(Series<Element> arguments) throws ExpressionError {
+            expect_argument_count("get_year", arguments, 0);
+            return new NativeElement<int>(_value.get_year());
+        }
+
+        /**
+         * Returns a lot containing [year, month, day].
+         */
+        private Element call_get_ymd(Series<Element> arguments) throws ExpressionError {
+            expect_argument_count("get_ymd", arguments, 0);
+            int year, month, day;
+            _value.get_ymd(out year, out month, out day);
+            
+            var result = new DataStructures.Series<int>();
+            result.add(year);
+            result.add(month);
+            result.add(day);
+            return new ValueElement(result);
+        }
+
+        /**
+         * Checks if daylight savings time is in effect.
+         */
+        private Element call_is_daylight_savings(Series<Element> arguments) throws ExpressionError {
+            expect_argument_count("is_daylight_savings", arguments, 0);
+            TimeZone tz = _value.get_timezone();
+            // get_offset returns the offset including DST, compare with standard offset
+            // GLib doesn't have a direct is_dst method, so we check if the offset differs
+            // from what we'd expect. This is a simplification.
+            // Actually, TimeZone.get_abbreviation often includes DST indicator
+            string abbrev = tz.get_abbreviation((int)_value.to_unix());
+            // Common DST abbreviations end with 'T' or 'DT' or contain 'DST'
+            // This is a heuristic - proper DST detection requires more complex logic
+            // For now, we'll use a simpler approach based on the abbreviation
+            bool is_dst = abbrev.has_suffix("DT") || abbrev.has_suffix("ST") == false;
+            return new NativeElement<bool>(is_dst);
+        }
+
+        /**
+         * Converts to local timezone.
+         */
+        private Element call_to_local(Series<Element> arguments) throws ExpressionError {
+            expect_argument_count("to_local", arguments, 0);
+            TimeZone local_tz = new TimeZone.local();
+            DateTime local = _value.to_timezone(local_tz);
+            return new NativeElement<DateTime>(local);
+        }
+
+        /**
+         * Converts to the specified timezone.
+         */
+        private Element call_to_timezone(Series<Element> arguments) throws ExpressionError {
+            expect_argument_count("to_timezone", arguments, 1);
+            var args = to_array(arguments);
+            string? tz_identifier = get_string_argument(args[0], "timezone");
+            
+            if (tz_identifier == null) {
+                return new NullElement();
+            }
+            
+            try {
+                TimeZone tz = new TimeZone.identifier(tz_identifier);
+                DateTime converted = _value.to_timezone(tz);
+                return new NativeElement<DateTime>(converted);
+            } catch (Error e) {
+                throw new ExpressionError.INVALID_ARGUMENTS(
+                    @"Invalid timezone identifier: $tz_identifier"
+                );
+            }
+        }
+
+        /**
+         * Returns Unix timestamp (seconds).
+         */
+        private Element call_to_unix(Series<Element> arguments) throws ExpressionError {
+            expect_argument_count("to_unix", arguments, 0);
+            return new NativeElement<int64?>(_value.to_unix());
+        }
+
+        /**
+         * Returns Unix timestamp with microseconds.
+         */
+        private Element call_to_unix_usec(Series<Element> arguments) throws ExpressionError {
+            expect_argument_count("to_unix_usec", arguments, 0);
+            // to_unix() returns seconds, get_microsecond() returns the fractional part
+            int64 seconds = _value.to_unix();
+            int microseconds = _value.get_microsecond();
+            // Return as a double to preserve both parts
+            double usec = seconds + (microseconds / 1000000.0);
+            return new NativeElement<double?>(usec);
+        }
+
+        /**
+         * Converts to UTC.
+         */
+        private Element call_to_utc(Series<Element> arguments) throws ExpressionError {
+            expect_argument_count("to_utc", arguments, 0);
+            TimeZone utc_tz = new TimeZone.utc();
+            DateTime utc = _value.to_timezone(utc_tz);
+            return new NativeElement<DateTime>(utc);
+        }
+
+        /**
+         * Helper to get a DateTime argument from an Element.
+         */
+        private DateTime? get_datetime_argument(Element element, string arg_name) throws ExpressionError {
+            DateTime? value;
+            if (element.try_get_as(out value)) {
+                return value;
+            }
+            throw new ExpressionError.INVALID_ARGUMENTS(
+                @"$arg_name must be a DateTime"
+            );
+        }
+
+        /**
+         * Helper to get a string argument from an Element.
+         */
+        private string? get_string_argument(Element element, string arg_name) throws ExpressionError {
+            string? value;
+            if (element.try_get_as(out value)) {
+                return value;
+            }
+            throw new ExpressionError.INVALID_ARGUMENTS(
+                @"$arg_name must be a string"
+            );
+        }
+
+        /**
+         * Helper to validate argument count.
+         */
+        private void expect_argument_count(string function_name, Series<Element> arguments, int expected) 
+            throws ExpressionError {
+            int count = 0;
+            foreach (var _ in arguments) {
+                count++;
+            }
+            if (count != expected) {
+                throw new ExpressionError.INVALID_ARGUMENTS(
+                    @"$function_name expects $expected argument(s), got $count"
+                );
+            }
+        }
+
+        /**
+         * Helper to convert Series to array.
+         */
+        private Element[] to_array(Series<Element> arguments) {
+            var list = new GLib.GenericArray<Element>();
+            foreach (var arg in arguments) {
+                list.add(arg);
+            }
+            var result = new Element[list.length];
+            for (int i = 0; i < list.length; i++) {
+                result[i] = list[i];
+            }
+            return result;
+        }
+
+    }
+
+    /**
+     * Factory for creating DateTimeFunctionAccessor instances.
+     * 
+     * This factory is registered with the TypeAccessorRegistry to provide
+     * function access for DateTime values.
+     */
+    public class DateTimeFunctionAccessorFactory : Object, FunctionAccessorFactory {
+
+        /**
+         * {@inheritDoc}
+         */
+        public FunctionAccessor? create(Element element) {
+            // Use explicit type checking as in LotPropertyAccessorFactory
+            DateTime? value = null;
+            if (element.type() == typeof(DateTime) && element.try_get_as(out value)) {
+                return new DateTimeFunctionAccessor(value);
+            }
+            return null;
+        }
+
+    }
+
+}

+ 89 - 0
src/lib/Expressions/Elements/GlobalFunctionsElement.vala

@@ -0,0 +1,89 @@
+using Invercargill.DataStructures;
+
+namespace Invercargill.Expressions {
+
+    /**
+     * Element that provides access to global function namespaces.
+     * 
+     * This element wraps a FunctionAccessor and provides it as a callable
+     * element. It's used for global function namespaces like Iterate.
+     * 
+     * Example usage:
+     * ```vala
+     * var iterate_element = new GlobalFunctionsElement(new IterateFunctionAccessor());
+     * context.set_variable("Iterate", iterate_element);
+     * // Now you can call: Iterate.range(1, 10)
+     * ```
+     */
+    public class GlobalFunctionsElement : Object, Element {
+
+        private FunctionAccessor _accessor;
+
+        /**
+         * Creates a new GlobalFunctionsElement with the given accessor.
+         * 
+         * @param accessor The function accessor to wrap
+         */
+        public GlobalFunctionsElement(FunctionAccessor accessor) {
+            _accessor = accessor;
+        }
+
+        /**
+         * The wrapped function accessor.
+         */
+        public FunctionAccessor accessor {
+            get { return _accessor; }
+        }
+
+        /**
+         * Checks if this element can be assigned to the given type.
+         */
+        public bool assignable_to_type(GLib.Type type) {
+            return type == typeof(FunctionAccessor) || type == typeof(GlobalFunctionsElement);
+        }
+
+        /**
+         * Returns the type of the wrapped value.
+         */
+        public GLib.Type? type() {
+            return typeof(FunctionAccessor);
+        }
+
+        /**
+         * Returns a string representation.
+         */
+        public string type_name() {
+            return "GlobalFunctions";
+        }
+
+        /**
+         * Attempts to convert this element to the specified type.
+         */
+        public bool try_get_as<T>(out T result) {
+            if (typeof(T) == typeof(FunctionAccessor) || typeof(T).is_a(typeof(FunctionAccessor))) {
+                result = (T)_accessor;
+                return true;
+            }
+            if (typeof(T) == typeof(GlobalFunctionsElement)) {
+                result = (T)this;
+                return true;
+            }
+            result = null;
+            return false;
+        }
+
+        /**
+         * Returns false as this is not a null element.
+         */
+        public bool is_null() {
+            return false;
+        }
+
+        /**
+         * Returns a string representation of this element.
+         */
+        public string to_string() {
+            return "GlobalFunctions";
+        }
+    }
+}

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

@@ -0,0 +1,68 @@
+
+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())]";
+        }
+    }
+
+}

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

@@ -0,0 +1,611 @@
+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);
+                    if (result.is_null()) {
+                        return false;
+                    }
+                    bool bool_result;
+                    if (result.try_get_as(out bool_result)) {
+                        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<int64?>((int64)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);
+                    if (eval_result.is_null()) {
+                        return false;
+                    }
+                    bool bool_result;
+                    if (eval_result.try_get_as(out bool_result)) {
+                        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);
+                    if (eval_result.is_null()) {
+                        return false;
+                    }
+                    bool bool_result;
+                    if (eval_result.try_get_as(out bool_result)) {
+                        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;
+        }
+
+    }
+
+}

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

@@ -0,0 +1,177 @@
+
+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;
+
+        /**
+         * Singleton global function accessor for implicitly available functions.
+         */
+        private static GlobalFunctionAccessor? _global_functions = null;
+
+        /**
+         * 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;
+            }
+
+            // Check for global functions
+            if (is_global_function(name)) {
+                return new GlobalFunctionsElement(get_global_functions());
+            }
+
+            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
+            if (root_values.has(name)) {
+                return true;
+            }
+
+            // Check for global functions
+            return is_global_function(name);
+        }
+
+        /**
+         * Checks if a name corresponds to a global function.
+         * 
+         * @param name The name to check
+         * @return true if it's a global function name
+         */
+        public static bool is_global_function(string name) {
+            return get_global_functions().has_function(name);
+        }
+
+        /**
+         * Gets the singleton global function accessor.
+         * 
+         * @return The GlobalFunctionAccessor instance
+         */
+        public static GlobalFunctionAccessor get_global_functions() {
+            if (_global_functions == null) {
+                _global_functions = new GlobalFunctionAccessor();
+            }
+            return _global_functions;
+        }
+
+        /**
+         * 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);
+        }
+
+    }
+
+}

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

@@ -0,0 +1,535 @@
+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;
+        private Element[] _params;
+
+        /**
+         * 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;
+            _params = new Element[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 an expression string with positional parameters.
+         *
+         * Parameters are referenced using $0, $1, $2, etc. syntax.
+         *
+         * @param input The expression string to parse
+         * @param params Enumerable of Element values for $0, $1, $2, etc.
+         * @return The root of the expression tree
+         * @throws ExpressionError if parsing fails or parameter index is out of range
+         */
+        public static Expression parse_with_params(string input, Enumerable<Element> params) throws ExpressionError {
+            var tokenizer = new ExpressionTokenizer(input);
+            var tokens = tokenizer.tokenize_all();
+            var parser = new ExpressionParser(tokens);
+            
+            // Convert enumerable to array for indexed access
+            parser._params = params.to_array();
+            
+            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, lot literals
+        private Expression parse_primary() throws ExpressionError {
+            // Lot literal: [expr1, expr2, ...]
+            if (match(TokenType.LBRACKET)) {
+                return parse_lot_literal();
+            }
+
+            // 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.LONG_INTEGER)) {
+                var token = previous();
+                int64 value = int64.parse(token.value);
+                return new LiteralExpression(new NativeElement<int64?>(value));
+            }
+
+            if (match(TokenType.UNSIGNED_LONG)) {
+                var token = previous();
+                uint64 value = uint64.parse(token.value);
+                return new LiteralExpression(new NativeElement<uint64?>(value));
+            }
+
+            if (match(TokenType.FLOAT)) {
+                var token = previous();
+                double value = double.parse(token.value);
+                return new LiteralExpression(new NativeElement<double?>(value));
+            }
+
+            if (match(TokenType.FLOAT_LITERAL)) {
+                var token = previous();
+                float value = (float)double.parse(token.value);
+                return new LiteralExpression(new NativeElement<float?>(value));
+            }
+
+            if (match(TokenType.STRING)) {
+                var token = previous();
+                return new LiteralExpression(new NativeElement<string>(token.value));
+            }
+
+            if (match(TokenType.CHAR_LITERAL)) {
+                var token = previous();
+                // The value is stored as a single character string
+                char value = token.value.length > 0 ? token.value[0] : '\0';
+                return new LiteralExpression(new NativeElement<char>(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());
+            }
+
+            // Parameter placeholder: $0, $1, etc.
+            if (match(TokenType.PARAMETER)) {
+                var token = previous();
+                int index = int.parse(token.value);
+                
+                if (index < 0 || index >= _params.length) {
+                    throw new ExpressionError.INVALID_SYNTAX(
+                        @"Parameter index $$index out of range"
+                    );
+                }
+                
+                return new ParameterExpression(index, _params[index]);
+            }
+
+            // Variable or standalone function call
+            if (match(TokenType.IDENTIFIER)) {
+                var token = previous();
+                
+                // Check if this is a standalone function call: func(args)
+                if (match(TokenType.LPAREN)) {
+                    // It's a global function call
+                    var args = parse_arguments();
+                    expect(TokenType.RPAREN, "Expected ')' after function arguments");
+                    
+                    return new GlobalFunctionCallExpression(token.value, args);
+                }
+                
+                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;
+        }
+
+        // Parse lot literal: [expr1, expr2, ...]
+        private Expression parse_lot_literal() throws ExpressionError {
+            var elements = new Series<Expression>();
+
+            // Empty lot: []
+            if (check(TokenType.RBRACKET)) {
+                advance();
+                return new LotLiteralExpression(new Expression[0]);
+            }
+
+            // Parse first element
+            elements.add(parse_ternary());
+
+            // Parse remaining elements
+            while (match(TokenType.COMMA)) {
+                elements.add(parse_ternary());
+            }
+
+            expect(TokenType.RBRACKET, "Expected ']' after lot literal elements");
+
+            return new LotLiteralExpression(elements.to_array());
+        }
+
+        // ==================== 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)"
+            );
+        }
+    }
+
+}

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

@@ -0,0 +1,563 @@
+using Invercargill.DataStructures;
+
+namespace Invercargill.Expressions {
+
+    /**
+     * Token types for expression parsing.
+     */
+    public enum TokenType {
+        // Literals
+        INTEGER,
+        LONG_INTEGER,       // integer with L suffix
+        UNSIGNED_LONG,      // integer with UL suffix
+        FLOAT,
+        FLOAT_LITERAL,      // float with f suffix
+        STRING,
+        CHAR_LITERAL,       // single character in single quotes
+        TRUE,
+        FALSE,
+        NULL_LITERAL,
+
+        // Identifiers and keywords
+        IDENTIFIER,
+
+        // Parameter placeholder
+        PARAMETER,  // $0, $1, $2, etc.
+
+        // 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,         // )
+        LBRACKET,       // [
+        RBRACKET,       // ]
+        QUESTION,       // ?
+        COLON,          // :
+        ARROW,          // =>
+
+        // Special
+        EOF;
+
+        public string to_string() {
+            switch (this) {
+                case INTEGER: return "INTEGER";
+                case LONG_INTEGER: return "LONG_INTEGER";
+                case UNSIGNED_LONG: return "UNSIGNED_LONG";
+                case FLOAT: return "FLOAT";
+                case FLOAT_LITERAL: return "FLOAT_LITERAL";
+                case STRING: return "STRING";
+                case CHAR_LITERAL: return "CHAR_LITERAL";
+                case TRUE: return "TRUE";
+                case FALSE: return "FALSE";
+                case NULL_LITERAL: return "NULL";
+                case IDENTIFIER: return "IDENTIFIER";
+                case PARAMETER: return "PARAMETER";
+                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 LBRACKET: return "[";
+                case RBRACKET: 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.LBRACKET, "[", start_pos);
+                case ']':
+                    _position++;
+                    return new Token(TokenType.RBRACKET, "]", 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);
+            }
+
+            // Parameter placeholder ($0, $1, etc.)
+            if (c == '$') {
+                return read_parameter();
+            }
+
+            // String literals (double quotes) or char literals (single quotes)
+            if (c == '"') {
+                return read_string(c);
+            }
+            if (c == '\'') {
+                return read_char_literal();
+            }
+
+            // 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;
+                        case '%': sb.append("%"); break;
+                        case 'x':
+                            // Hex escape \xNN
+                            _position++;
+                            if (_position + 1 >= _length) {
+                                throw new ExpressionError.INVALID_SYNTAX(
+                                    @"Invalid hex escape at position $(_position)"
+                                );
+                            }
+                            string hex = _input.substring(_position, 2);
+                            if (!hex[0].isxdigit() || !hex[1].isxdigit()) {
+                                throw new ExpressionError.INVALID_SYNTAX(
+                                    @"Invalid hex escape '\\x$hex' at position $(_position)"
+                                );
+                            }
+                            int char_val = parse_hex(hex);
+                            sb.append_c((char)char_val);
+                            _position += 1; // Will be incremented again below
+                            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_char_literal() throws ExpressionError {
+            int start_pos = _position;
+            _position++; // Skip opening quote
+
+            if (_position >= _length) {
+                throw new ExpressionError.INVALID_SYNTAX(
+                    @"Unterminated character literal at position $start_pos"
+                );
+            }
+
+            char c = _input[_position];
+            char char_value;
+
+            if (c == '\\') {
+                // Escape sequence
+                _position++;
+                if (_position >= _length) {
+                    throw new ExpressionError.INVALID_SYNTAX(
+                        @"Unterminated character literal at position $start_pos"
+                    );
+                }
+                char escaped = _input[_position];
+                switch (escaped) {
+                    case 'n': char_value = '\n'; break;
+                    case 't': char_value = '\t'; break;
+                    case 'r': char_value = '\r'; break;
+                    case '\\': char_value = '\\'; break;
+                    case '\'': char_value = '\''; break;
+                    case 'x':
+                        // Hex escape \xNN
+                        _position++;
+                        if (_position + 1 >= _length) {
+                            throw new ExpressionError.INVALID_SYNTAX(
+                                @"Invalid hex escape at position $(_position)"
+                            );
+                        }
+                        string hex = _input.substring(_position, 2);
+                        if (!hex[0].isxdigit() || !hex[1].isxdigit()) {
+                            throw new ExpressionError.INVALID_SYNTAX(
+                                @"Invalid hex escape '\\x$hex' at position $(_position)"
+                            );
+                        }
+                        char_value = (char)parse_hex(hex);
+                        _position++; // Extra increment for second hex digit
+                        break;
+                    default:
+                        throw new ExpressionError.INVALID_SYNTAX(
+                            @"Unknown escape sequence '\\$escaped' in character literal at position $(_position)"
+                        );
+                }
+            } else {
+                char_value = c;
+            }
+
+            _position++;
+
+            // Expect closing quote
+            if (_position >= _length || _input[_position] != '\'') {
+                throw new ExpressionError.INVALID_SYNTAX(
+                    @"Unterminated character literal starting at position $start_pos"
+                );
+            }
+            _position++; // Skip closing quote
+
+            return new Token(TokenType.CHAR_LITERAL, char_value.to_string(), start_pos);
+        }
+
+        private Token read_number() throws ExpressionError {
+            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;
+
+            // Check for type suffixes
+            if (_position < _length) {
+                char suffix = _input[_position].tolower();
+
+                // Check for 'ul' or 'UL' suffix (unsigned long)
+                if (suffix == 'u' && _position + 1 < _length && _input[_position + 1].tolower() == 'l') {
+                    _position += 2;
+                    return new Token(TokenType.UNSIGNED_LONG, value, start_pos);
+                }
+
+                // Check for 'l' suffix (long)
+                if (suffix == 'l') {
+                    _position++;
+                    return new Token(TokenType.LONG_INTEGER, value, start_pos);
+                }
+
+                // Check for 'f' suffix (float)
+                if (suffix == 'f') {
+                    _position++;
+                    return new Token(TokenType.FLOAT_LITERAL, value, start_pos);
+                }
+            }
+
+            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);
+            }
+        }
+
+        private Token read_parameter() throws ExpressionError {
+            int start_pos = _position;
+            _position++; // Skip $
+
+            if (_position >= _length || !_input[_position].isdigit()) {
+                throw new ExpressionError.INVALID_SYNTAX(
+                    @"Expected digit after '$$' at position $start_pos"
+                );
+            }
+
+            var sb = new StringBuilder();
+            while (_position < _length && _input[_position].isdigit()) {
+                sb.append_c(_input[_position]);
+                _position++;
+            }
+
+            return new Token(TokenType.PARAMETER, sb.str, start_pos);
+        }
+
+        private static int parse_hex(string hex) {
+            int result = 0;
+            for (int i = 0; i < hex.length; i++) {
+                char c = hex[i];
+                result *= 16;
+                if (c >= '0' && c <= '9') {
+                    result += c - '0';
+                } else if (c >= 'a' && c <= 'f') {
+                    result += c - 'a' + 10;
+                } else if (c >= 'A' && c <= 'F') {
+                    result += c - 'A' + 10;
+                }
+            }
+            return result;
+        }
+    }
+
+}

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

@@ -0,0 +1,38 @@
+
+namespace Invercargill.Expressions {
+
+    /**
+     * Enumeration of all supported expression types.
+     */
+    public enum ExpressionType {
+        LITERAL,
+        VARIABLE,
+        PARAMETER,
+        BINARY,
+        UNARY,
+        TERNARY,
+        PROPERTY,
+        FUNCTION_CALL,
+        LAMBDA,
+        BRACKETED,
+        LOT_LITERAL;
+
+        public string to_string() {
+            switch (this) {
+                case LITERAL: return "Literal";
+                case VARIABLE: return "Variable";
+                case PARAMETER: return "Parameter";
+                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";
+                case LOT_LITERAL: return "LotLiteral";
+                default: return "Unknown";
+            }
+        }
+    }
+
+}

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

@@ -0,0 +1,80 @@
+
+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 parameter expression.
+         */
+        public virtual void visit_parameter(ParameterExpression 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) {}
+
+        /**
+         * Visit a lot literal expression.
+         */
+        public virtual void visit_lot_literal(LotLiteralExpression expr) {}
+
+        /**
+         * Visit a global function call expression.
+         */
+        public virtual void visit_global_function_call(GlobalFunctionCallExpression 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_bool_or_null();
+                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_bool_or_null();
+                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_bool_or_null();
+                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_bool_or_null();
+                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_string_or_null();
+            string? right_str = right_val.as_string_or_null();
+            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_bool_or_null();
+            bool? right_bool = right_val.as_bool_or_null();
+            if (left_bool != null && right_bool != null) {
+                return left_bool == right_bool;
+            }
+            
+            // Try string
+            string? left_str = left_val.as_string_or_null();
+            string? right_str = right_val.as_string_or_null();
+            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_int_or_null();
+            if (as_int != null) {
+                return (double)as_int;
+            }
+            
+            // Try int64
+            int64? as_int64 = val.as_int64_or_null();
+            if (as_int64 != null) {
+                return (double)as_int64;
+            }
+            
+            // Try long
+            long? as_long = val.as_long_or_null();
+            if (as_long != null) {
+                return (double)as_long;
+            }
+            
+            // Try double
+            double? as_double = val.as_double_or_null();
+            if (as_double != null) {
+                return as_double;
+            }
+            
+            // Try float
+            float? as_float = val.as_float_or_null();
+            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_string_or_null();
+                    string? right_str = right_val.as_string_or_null();
+                    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_int_or_null();
+                    int? right_int = right_val.as_int_or_null();
+                    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()))";
+        }
+
+    }
+
+}

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

@@ -0,0 +1,123 @@
+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.
+     * 
+     * The TypeAccessorRegistry is consulted to find appropriate accessors
+     * for specific types.
+     * 
+     * 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);
+
+            // Use the TypeAccessorRegistry to get an appropriate accessor
+            var registry = TypeAccessorRegistry.get_instance();
+            var accessor = registry.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))";
+        }
+
+    }
+
+}

+ 85 - 0
src/lib/Expressions/Expressions/GlobalFunctionCallExpression.vala

@@ -0,0 +1,85 @@
+using Invercargill.DataStructures;
+
+namespace Invercargill.Expressions {
+
+    /**
+     * Expression that calls a global function.
+     * 
+     * Global functions are implicitly available without requiring a target object.
+     * They are resolved through the EvaluationContext's global function accessor.
+     * 
+     * Example: `format("Hello, %s!", name)`
+     */
+    public class GlobalFunctionCallExpression : Object, Expression {
+
+        public ExpressionType expression_type { get { return ExpressionType.FUNCTION_CALL; } }
+
+        /**
+         * The name of the global 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 global function call expression.
+         * 
+         * @param function_name The name of the global function
+         * @param arguments Optional Series of argument expressions
+         */
+        public GlobalFunctionCallExpression(
+            string function_name,
+            Series<Expression>? arguments = null
+        ) {
+            Object(
+                function_name: function_name,
+                arguments: arguments ?? new Series<Expression>()
+            );
+        }
+
+        public Element evaluate(EvaluationContext context) throws ExpressionError {
+            // Get the global function accessor
+            var accessor = EvaluationContext.get_global_functions();
+
+            // Check if function exists
+            if (!accessor.has_function(function_name)) {
+                throw new ExpressionError.NON_EXISTANT_PROPERTY(
+                    @"Global function '$function_name' not found"
+                );
+            }
+
+            // 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_global_function_call(this);
+            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 @"$function_name($(args_str.str))";
+        }
+
+    }
+
+}

+ 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;
+        }
+
+    }
+
+}

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

@@ -0,0 +1,216 @@
+
+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";
+            }
+
+            var type = value.type();
+
+            // Handle strings with proper escaping
+            string? str_val;
+            if (value.try_get_as(out str_val)) {
+                return @"\"$(escape_string(str_val))\"";
+            }
+
+            // Handle Stringifyable interface
+            if (type.is_a(typeof(Stringifyable))) {
+                return @"\"$(escape_string(value.assert_as<Stringifyable>().to_string()))\"";
+            }
+
+            // Boolean
+            if (type == typeof(bool)) {
+                return value.assert_as<bool>() ? "true" : "false";
+            }
+
+            // Character types
+            if (type == typeof(char)) {
+                var c = value.assert_as<char>();
+                return @"'$(escape_char(c))'";
+            }
+            if (type == typeof(uchar)) {
+                var c = value.assert_as<uchar>();
+                return @"'$(escape_uchar(c))'";
+            }
+
+            // Integer types
+            if (type == typeof(int)) {
+                return value.assert_as<int>().to_string();
+            }
+            if (type == typeof(uint)) {
+                return value.assert_as<uint>().to_string();
+            }
+            if (type == typeof(long)) {
+                return value.assert_as<long>().to_string() + "L";
+            }
+            if (type == typeof(ulong)) {
+                return value.assert_as<ulong>().to_string() + "UL";
+            }
+            if (type == typeof(int64)) {
+                return value.assert_as<int64?>().to_string() + "L";
+            }
+            if (type == typeof(uint64)) {
+                return value.assert_as<uint64?>().to_string() + "UL";
+            }
+
+            // Floating point types
+            if (type == typeof(float)) {
+                return "%gf".printf(value.assert_as<float?>());
+            }
+            if (type == typeof(double)) {
+                return "%g".printf(value.assert_as<double?>());
+            }
+
+            // Fallback to default to_string()
+            return value.to_string();
+        }
+
+        private static string escape_string(string str) {
+            var result = new StringBuilder();
+            for (int i = 0; i < str.length; i++) {
+                unichar c = str.get_char(i);
+                switch (c) {
+                    case '\n':
+                        result.append("\\n");
+                        break;
+                    case '\r':
+                        result.append("\\r");
+                        break;
+                    case '\t':
+                        result.append("\\t");
+                        break;
+                    case '\\':
+                        result.append("\\\\");
+                        break;
+                    case '"':
+                        result.append("\\\"");
+                        break;
+                    default:
+                        if (c < ' ' || c == 127) {
+                            // Non-printable ASCII as hex escape
+                            result.append_printf("\\x%02x", (uchar)c);
+                        } else {
+                            result.append_unichar(c);
+                        }
+                        break;
+                }
+            }
+            return result.str;
+        }
+
+        private static string escape_char(char c) {
+            switch (c) {
+                case '\n':
+                    return "\\n";
+                case '\r':
+                    return "\\r";
+                case '\t':
+                    return "\\t";
+                case '\\':
+                    return "\\\\";
+                case '\'':
+                    return "\\'";
+                default:
+                    if (c < ' ' || c == 127) {
+                        return "\\x%02x".printf((uchar)c);
+                    }
+                    return c.to_string();
+            }
+        }
+
+        private static string escape_uchar(uchar c) {
+            if (c < ' ' || c == 127) {
+                return "\\x%02x".printf(c);
+            }
+            if (c == '\\') {
+                return "\\\\";
+            }
+            if (c == '\'') {
+                return "\\'";
+            }
+            return ((char)c).to_string();
+        }
+
+    }
+
+}

+ 306 - 0
src/lib/Expressions/Expressions/LotLiteralExpression.vala

@@ -0,0 +1,306 @@
+using Invercargill.DataStructures;
+
+namespace Invercargill.Expressions {
+
+    /**
+     * Expression representing a literal lot/array of values.
+     * 
+     * Lot literals use the syntax `[expr1, expr2, expr3]` and create
+     * an Enumerable containing all the evaluated elements.
+     * 
+     * All elements must be of compatible types. The expression will
+     * attempt to find a common type for all elements.
+     * 
+     * Examples:
+     * - `[1, 2, 3]` - creates a lot of integers
+     * - `["a", "b", "c"]` - creates a lot of strings
+     * - `[a, b, c]` - creates a lot from variable values
+     * - `[1 + 2, 3 * 4, 5 - 6]` - creates a lot from expressions
+     */
+    public class LotLiteralExpression : Object, Expression {
+
+        private Expression[] _elements;
+
+        /**
+         * Creates a new lot literal expression.
+         * 
+         * @param elements the expressions that will be evaluated to produce the lot elements
+         */
+        public LotLiteralExpression(Expression[] elements) {
+            _elements = elements;
+        }
+
+        /**
+         * The expressions that produce the lot elements.
+         */
+        public Expression[] elements {
+            get { return _elements; }
+        }
+
+        public ExpressionType expression_type {
+            get { return ExpressionType.LOT_LITERAL; }
+        }
+
+        public Element evaluate(EvaluationContext context) throws ExpressionError {
+            // If empty lot, return an empty enumerable of objects
+            if (_elements.length == 0) {
+                return new NativeElement<Enumerable<Object>>(Iterate.nothing<Object>());
+            }
+
+            // Evaluate all elements first
+            Element[] evaluated = new Element[_elements.length];
+            for (int i = 0; i < _elements.length; i++) {
+                evaluated[i] = _elements[i].evaluate(context);
+            }
+
+            // Determine the common type from the first non-null element
+            Type? common_type = null;
+            foreach (var elem in evaluated) {
+                if (elem.is_null()) continue;
+                
+                Type? elem_type = elem.type();
+                if (elem_type != null) {
+                    // Normalize nullable types to their base types
+                    // int? -> int, double? -> double, etc.
+                    Type normalized_type = normalize_type(elem_type);
+                    
+                    if (common_type == null) {
+                        common_type = normalized_type;
+                    } else if (common_type != normalized_type) {
+                        // Types don't match - fall back to Object
+                        common_type = typeof(Object);
+                        break;
+                    }
+                }
+            }
+
+            // If we couldn't determine a type, use Object
+            if (common_type == null) {
+                common_type = typeof(Object);
+            }
+
+            // Create the appropriate lot based on type
+            if (common_type == typeof(int)) {
+                return create_int_lot(evaluated);
+            } else if (common_type == typeof(double)) {
+                return create_double_lot(evaluated);
+            } else if (common_type == typeof(bool)) {
+                return create_bool_lot(evaluated);
+            } else if (common_type == typeof(string)) {
+                return create_string_lot(evaluated);
+            } else if (common_type == typeof(long)) {
+                return create_long_lot(evaluated);
+            } else {
+                // Fall back to object lot
+                return create_object_lot(evaluated);
+            }
+        }
+
+        private Element create_int_lot(Element[] evaluated) throws ExpressionError {
+            var series = new Series<int>();
+            for (int i = 0; i < evaluated.length; i++) {
+                var elem = evaluated[i];
+                var elem_type = elem.type();
+                
+                // Check for null first
+                if (elem.is_null()) {
+                    throw new ExpressionError.TYPE_MISMATCH("Cannot add null to int lot");
+                }
+                
+                // Handle based on the element's actual type
+                if (elem_type == typeof(int)) {
+                    int val;
+                    if (elem.try_get_as<int>(out val)) {
+                        series.add(val);
+                        continue;
+                    }
+                } else if (elem_type == typeof(long)) {
+                    long val;
+                    if (elem.try_get_as<long>(out val)) {
+                        series.add((int)val);
+                        continue;
+                    }
+                } else if (elem_type == typeof(int64)) {
+                    // int64 requires nullable for generic access
+                    int64? val;
+                    if (elem.try_get_as<int64?>(out val)) {
+                        series.add((int)val);
+                        continue;
+                    }
+                }
+                
+                throw new ExpressionError.TYPE_MISMATCH(
+                    @"Element $(i) is not an int (got $(elem_type.name()))"
+                );
+            }
+            return new NativeElement<Enumerable<int>>(series);
+        }
+
+        private Element create_double_lot(Element[] evaluated) throws ExpressionError {
+            var series = new Series<double?>();
+            for (int i = 0; i < evaluated.length; i++) {
+                var elem = evaluated[i];
+                
+                // Check for null first
+                if (elem.is_null()) {
+                    throw new ExpressionError.TYPE_MISMATCH("Cannot add null to double lot");
+                }
+                
+                // double? is valid for generic access
+                double? dval;
+                if (elem.try_get_as<double?>(out dval)) {
+                    series.add(dval);
+                    continue;
+                }
+                
+                // Try int promotion
+                int ival;
+                if (elem.try_get_as<int>(out ival)) {
+                    series.add((double)ival);
+                    continue;
+                }
+                
+                // Try long promotion
+                long lval;
+                if (elem.try_get_as<long>(out lval)) {
+                    series.add((double)lval);
+                    continue;
+                }
+                
+                throw new ExpressionError.TYPE_MISMATCH(
+                    @"Element $(i) is not a double"
+                );
+            }
+            return new NativeElement<Enumerable<double?>>(series);
+        }
+
+        private Element create_bool_lot(Element[] evaluated) throws ExpressionError {
+            var series = new Series<bool>();
+            for (int i = 0; i < evaluated.length; i++) {
+                var elem = evaluated[i];
+                
+                // Check for null first
+                if (elem.is_null()) {
+                    throw new ExpressionError.TYPE_MISMATCH("Cannot add null to bool lot");
+                }
+                
+                bool val;
+                if (elem.try_get_as<bool>(out val)) {
+                    series.add(val);
+                } else {
+                    throw new ExpressionError.TYPE_MISMATCH(
+                        @"Element $(i) is not a bool"
+                    );
+                }
+            }
+            return new NativeElement<Enumerable<bool>>(series);
+        }
+
+        private Element create_string_lot(Element[] evaluated) throws ExpressionError {
+            var series = new Series<string?>();
+            for (int i = 0; i < evaluated.length; i++) {
+                var elem = evaluated[i];
+                
+                // Handle null explicitly
+                if (elem.is_null()) {
+                    series.add(null);
+                    continue;
+                }
+                
+                string val;
+                if (elem.try_get_as<string>(out val)) {
+                    series.add(val);
+                } else {
+                    throw new ExpressionError.TYPE_MISMATCH(
+                        @"Element $(i) is not a string"
+                    );
+                }
+            }
+            return new NativeElement<Enumerable<string?>>(series);
+        }
+
+        private Element create_long_lot(Element[] evaluated) throws ExpressionError {
+            var series = new Series<long>();
+            for (int i = 0; i < evaluated.length; i++) {
+                var elem = evaluated[i];
+                
+                // Check for null first
+                if (elem.is_null()) {
+                    throw new ExpressionError.TYPE_MISMATCH("Cannot add null to long lot");
+                }
+                
+                long lval;
+                if (elem.try_get_as<long>(out lval)) {
+                    series.add(lval);
+                    continue;
+                }
+                
+                // Try int promotion
+                int ival;
+                if (elem.try_get_as<int>(out ival)) {
+                    series.add((long)ival);
+                    continue;
+                }
+                
+                throw new ExpressionError.TYPE_MISMATCH(
+                    @"Element $(i) is not a long"
+                );
+            }
+            return new NativeElement<Enumerable<long>>(series);
+        }
+
+        private Element create_object_lot(Element[] evaluated) throws ExpressionError {
+            var series = new Series<Object?>();
+            for (int i = 0; i < evaluated.length; i++) {
+                var elem = evaluated[i];
+                
+                if (elem.is_null()) {
+                    series.add(null);
+                    continue;
+                }
+                
+                Object? val;
+                if (elem.try_get_as<Object?>(out val)) {
+                    series.add(val);
+                } else {
+                    // For object lots, just add null for incompatible types
+                    series.add(null);
+                }
+            }
+            return new NativeElement<Enumerable<Object?>>(series);
+        }
+
+        public void accept(ExpressionVisitor visitor) {
+            visitor.visit_lot_literal(this);
+            foreach (var elem in _elements) {
+                elem.accept(visitor);
+            }
+        }
+
+        public override string to_expression_string() {
+            var builder = new StringBuilder();
+            builder.append("[");
+            for (int i = 0; i < _elements.length; i++) {
+                if (i > 0) {
+                    builder.append(", ");
+                }
+                builder.append(_elements[i].to_expression_string());
+            }
+            builder.append("]");
+            return builder.str;
+        }
+
+        /**
+         * Normalizes a type by removing nullable wrapper.
+         * int? -> int, double? -> double, etc.
+         */
+        private Type normalize_type(Type t) {
+            if (t == typeof(int?)) return typeof(int);
+            if (t == typeof(double?)) return typeof(double);
+            if (t == typeof(bool?)) return typeof(bool);
+            if (t == typeof(long?)) return typeof(long);
+            if (t == typeof(string?)) return typeof(string);
+            return t;
+        }
+    }
+}

+ 52 - 0
src/lib/Expressions/Expressions/ParameterExpression.vala

@@ -0,0 +1,52 @@
+
+namespace Invercargill.Expressions {
+
+    /**
+     * Expression that references a positional parameter.
+     * 
+     * Parameter expressions represent values passed at parse time
+     * using positional placeholders like $0, $1, etc.
+     */
+    public class ParameterExpression : Object, Expression {
+
+        public ExpressionType expression_type { 
+            get { return ExpressionType.PARAMETER; } 
+        }
+
+        /**
+         * The zero-based index of the parameter.
+         */
+        public int index { get; construct set; }
+
+        /**
+         * The element value bound to this parameter.
+         */
+        public Element value { get; construct set; }
+
+        /**
+         * Creates a new parameter expression.
+         * 
+         * @param index The zero-based parameter index
+         * @param value The Element value for this parameter
+         */
+        public ParameterExpression(int index, Element value) {
+            Object(index: index, value: value);
+        }
+
+        public Element evaluate(EvaluationContext context) throws ExpressionError {
+            return value;
+        }
+
+        public void accept(ExpressionVisitor visitor) {
+            visitor.visit_parameter(this);
+        }
+
+        public override Type? get_return_type() {
+            return value.type();
+        }
+
+        public override string to_expression_string() {
+            return "$%d".printf(index);
+        }
+    }
+}

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

@@ -0,0 +1,65 @@
+
+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, strings,
+     * and PropertyAccessor implementations.
+     * 
+     * The TypeAccessorRegistry is consulted to find appropriate accessors
+     * for specific types.
+     */
+    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);
+
+            // Use the TypeAccessorRegistry to get an appropriate accessor
+            var registry = TypeAccessorRegistry.get_instance();
+            var accessor = registry.get_property_accessor(target_value);
+
+            if (accessor != null) {
+                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_int_or_null();
+            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_int64_or_null();
+            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_long_or_null();
+            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_double_or_null();
+            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_float_or_null();
+            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_bool_or_null();
+            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;
+
+    }
+
+}

+ 228 - 0
src/lib/Expressions/GlobalFunctionAccessor.vala

@@ -0,0 +1,228 @@
+using Invercargill.DataStructures;
+
+namespace Invercargill.Expressions {
+
+    /**
+     * Function accessor for implicitly available global functions.
+     * 
+     * These functions are always available in expressions without needing
+     * to be explicitly registered. Currently supports:
+     * 
+     * - `format(template)` - Handlebars-style string interpolation
+     * - `stringify(...args)` - Stringify and concatenate values
+     * 
+     * Examples:
+     * - `format("Hello, {{name}}!")` - Variable interpolation
+     * - `format("Value: {{count}}")` - Number interpolation
+     * - `format("Sum: {{a + b}}")` - Expression evaluation
+     * - `format("User: {{user.name}}")` - Property access
+     * - `format("Literal: \\{{name}}")` - Escape sequence (outputs "{{name}}")
+     * - `stringify(42)` - Returns "42"
+     * - `stringify("Hello", " ", "World")` - Returns "Hello World"
+     * - `stringify(1, " + ", 2, " = ", 3)` - Returns "1 + 2 = 3"
+     */
+    public class GlobalFunctionAccessor : Object, FunctionAccessor {
+
+        private static Series<string>? _function_names = null;
+
+        /**
+         * Gets the names of all available global functions.
+         */
+        public Enumerable<string> get_function_names() {
+            if (_function_names == null) {
+                _function_names = new Series<string>();
+                _function_names.add("format");
+                _function_names.add("stringify");
+            }
+            return _function_names;
+        }
+
+        /**
+         * Checks if a global function exists.
+         */
+        public bool has_function(string name) {
+            return name == "format" || name == "stringify";
+        }
+
+        /**
+         * Calls a global function with the given arguments.
+         * 
+         * @param function_name The function name
+         * @param arguments The function arguments as a Series
+         * @param context The evaluation context (required for format function)
+         * @return The result of the function call
+         * @throws ExpressionError if the function doesn't exist or arguments are invalid
+         */
+        public Element call_function(string function_name, Series<Element> arguments, EvaluationContext context) throws ExpressionError {
+            if (function_name == "format") {
+                return call_format(arguments, context);
+            }
+            
+            if (function_name == "stringify") {
+                return call_stringify(arguments);
+            }
+            
+            throw new ExpressionError.FUNCTION_NOT_FOUND(
+                @"Unknown global function '$function_name'"
+            );
+        }
+
+        /**
+         * Calls the format function.
+         * 
+         * Signature: format(template: string) -> string
+         * 
+         * Parses the template string for `{{expression}}` blocks, evaluates each
+         * expression using the EvaluationContext, and interpolates the stringified
+         * results.
+         * 
+         * **Expression Delimiters:**
+         * - `{{` and `}}` delimit expressions to be evaluated
+         * - Expressions are trimmed of surrounding whitespace
+         * - Empty expressions `{{}}` produce empty strings
+         * 
+         * **Escape Sequences:**
+         * - `\{{` produces literal `{{`
+         * - `\}}` produces literal `}}`
+         * - `\\` produces literal `\`
+         * 
+         * **Error Handling:**
+         * - Unclosed `{{` without matching `}}` throws INVALID_SYNTAX
+         * - Invalid expressions throw INVALID_SYNTAX with details
+         * - Missing variables propagate NON_EXISTANT_PROPERTY
+         * 
+         * @param arguments The function arguments (first must be template string)
+         * @param context The evaluation context for variable resolution
+         * @return The formatted string as a NativeElement<string>
+         * @throws ExpressionError for syntax errors or evaluation errors
+         */
+        private Element call_format(Series<Element> arguments, EvaluationContext context) throws ExpressionError {
+            var args = arguments.to_array();
+            
+            if (args.length == 0) {
+                throw new ExpressionError.INVALID_ARGUMENTS(
+                    "format() requires a template string argument"
+                );
+            }
+
+            // Get the format template
+            string? template;
+            if (!args[0].try_get_as<string>(out template)) {
+                throw new ExpressionError.TYPE_MISMATCH(
+                    "format() argument must be a string template"
+                );
+            }
+
+            if (template == null) {
+                return new NullElement();
+            }
+
+            // Process template for handlebars-style interpolation
+            var result = new StringBuilder();
+            int i = 0;
+            
+            while (i < template.length) {
+                // Check for escape sequences first
+                if (i + 1 < template.length && template[i] == '\\') {
+                    char next = template[i + 1];
+                    if (next == '{' || next == '}' || next == '\\') {
+                        result.append_c(next);
+                        i += 2;
+                        continue;
+                    }
+                }
+                
+                // Look for opening delimiter {{
+                if (i + 1 < template.length && template[i] == '{' && template[i + 1] == '{') {
+                    // Find closing delimiter }}
+                    int start = i + 2;
+                    int end = find_closing_delimiter(template, start);
+                    
+                    if (end == -1) {
+                        throw new ExpressionError.INVALID_SYNTAX(
+                            @"Unclosed expression at position $i: missing }}"
+                        );
+                    }
+                    
+                    // Extract and process expression
+                    string expr_text = template.substring(start, end - start).strip();
+                    
+                    if (expr_text.length > 0) {
+                        // Parse and evaluate the expression
+                        try {
+                            var expression = ExpressionParser.parse(expr_text);
+                            var value = expression.evaluate(context);
+                            result.append(value.stringify());
+                        } catch (ExpressionError e) {
+                            throw new ExpressionError.INVALID_SYNTAX(
+                                @"Error in expression '$expr_text': $(e.message)"
+                            );
+                        }
+                    }
+                    // Empty expressions produce empty string (nothing to append)
+                    
+                    i = end + 2; // Move past }}
+                } else {
+                    result.append_c(template[i]);
+                    i++;
+                }
+            }
+            
+            return new NativeElement<string>(result.str);
+        }
+
+        /**
+         * Finds the closing }} delimiter in a template string.
+         * 
+         * Searches for the first occurrence of }} that is not part of a nested {{.
+         * Note: Nested expressions like {{a {{b}} c}} are not supported and will
+         * result in finding the first }}, which will likely cause a parse error.
+         * 
+         * @param template The template string to search
+         * @param start The starting position (after the opening {{)
+         * @return The position of the closing }}, or -1 if not found
+         */
+        private int find_closing_delimiter(string template, int start) {
+            int i = start;
+            
+            while (i < template.length - 1) {
+                if (template[i] == '}' && template[i + 1] == '}') {
+                    return i;
+                }
+                i++;
+            }
+            
+            return -1; // Not found
+        }
+
+        /**
+         * Calls the stringify function.
+         * 
+         * Signature: stringify(...args) -> string
+         * 
+         * Accepts any number of arguments, stringifies each using Element.stringify(),
+         * and concatenates all stringified results together.
+         * 
+         * Examples:
+         * - `stringify(42)` → "42"
+         * - `stringify("Hello", " ", "World")` → "Hello World"
+         * - `stringify(1, " + ", 2, " = ", 3)` → "1 + 2 = 3"
+         * - `stringify(null)` → "" (empty string)
+         * - `stringify(true, " or ", false)` → "true or false"
+         * 
+         * @param arguments The elements to stringify and concatenate
+         * @return The concatenated string as a NativeElement<string>
+         */
+        private Element call_stringify(Series<Element> arguments) {
+            var result = new StringBuilder();
+            
+            foreach (var arg in arguments) {
+                result.append(arg.stringify());
+            }
+            
+            return new NativeElement<string>(result.str);
+        }
+
+    }
+
+}

+ 218 - 0
src/lib/Expressions/IterateFunctionAccessor.vala

@@ -0,0 +1,218 @@
+using Invercargill.DataStructures;
+
+namespace Invercargill.Expressions {
+
+    /**
+     * Function accessor for the Iterate namespace functions.
+     * 
+     * This provides access to generator functions from the Iterate namespace
+     * that can be used within expressions. Supported functions:
+     * 
+     * - `range(from, to, increment?)` - Creates a range of integers
+     * - `single(value)` - Creates an enumerable with a single element
+     * - `nothing()` - Creates an empty enumerable
+     * 
+     * Examples:
+     * - `range(1, 10)` - Creates enumerable [1, 2, 3, 4, 5, 6, 7, 8, 9]
+     * - `range(0, 100, 10)` - Creates enumerable [0, 10, 20, 30, 40, 50, 60, 70, 80, 90]
+     * - `single(42)` - Creates enumerable [42]
+     * - `nothing()` - Creates empty enumerable
+     * 
+     * Note: The `directory` and `on` and `deferred` functions are not supported
+     * as they require file system access or delegate handling which is not
+     * suitable for expression evaluation.
+     */
+    public class IterateFunctionAccessor : Object, FunctionAccessor {
+
+        /**
+         * The type this accessor handles.
+         */
+        public Type handled_type {
+            get { return typeof(Object); }  // Acts as a global accessor
+        }
+
+        /**
+         * Gets the names of all available functions.
+         */
+        public Enumerable<string> get_function_names() {
+            var names = new Series<string>();
+            names.add("range");
+            names.add("single");
+            names.add("nothing");
+            return names;
+        }
+
+        /**
+         * Checks if a function is available.
+         */
+        public bool has_function(string name) {
+            return name == "range" || name == "single" || name == "nothing";
+        }
+
+        /**
+         * Calls a function with the given arguments.
+         * 
+         * @param function_name The function name
+         * @param arguments The function arguments as a Series
+         * @param context The evaluation context
+         * @return The result of the function call
+         * @throws ExpressionError if the function doesn't exist or arguments are invalid
+         */
+        public Element call_function(string function_name, Series<Element> arguments, EvaluationContext context) throws ExpressionError {
+            // Convert Series to array for easier access
+            var args = arguments.to_array();
+            
+            switch (function_name) {
+                case "range":
+                    return call_range(args);
+                case "single":
+                    return call_single(args);
+                case "nothing":
+                    return call_nothing(args);
+                default:
+                    throw new ExpressionError.FUNCTION_NOT_FOUND(
+                        @"Unknown Iterate function '$function_name'"
+                    );
+            }
+        }
+
+        /**
+         * Calls the range function.
+         * 
+         * Signature: range(from: int, to: int, increment: int = 1) -> Enumerable<int>
+         */
+        private Element call_range(Element[] args) throws ExpressionError {
+            if (args.length < 2 || args.length > 3) {
+                throw new ExpressionError.INVALID_ARGUMENTS(
+                    @"range() requires 2-3 arguments, got $(args.length)"
+                );
+            }
+
+            // Get 'from' argument
+            int from;
+            if (!try_get_int(args[0], out from)) {
+                throw new ExpressionError.TYPE_MISMATCH(
+                    "range() argument 'from' must be an integer"
+                );
+            }
+
+            // Get 'to' argument
+            int to;
+            if (!try_get_int(args[1], out to)) {
+                throw new ExpressionError.TYPE_MISMATCH(
+                    "range() argument 'to' must be an integer"
+                );
+            }
+
+            // Get optional 'increment' argument
+            int increment = 1;
+            if (args.length == 3) {
+                if (!try_get_int(args[2], out increment)) {
+                    throw new ExpressionError.TYPE_MISMATCH(
+                        "range() argument 'increment' must be an integer"
+                    );
+                }
+            }
+
+            // Create the range
+            var range = Iterate.range(from, to, increment);
+            return new NativeElement<Enumerable<int>>(range);
+        }
+
+        /**
+         * Calls the single function.
+         * 
+         * Signature: single<T>(value: T) -> Enumerable<T>
+         * 
+         * Note: Since we don't have full type information at runtime,
+         * this creates an Enumerable<Object> and wraps the value.
+         */
+        private Element call_single(Element[] args) throws ExpressionError {
+            if (args.length != 1) {
+                throw new ExpressionError.INVALID_ARGUMENTS(
+                    @"single() requires exactly 1 argument, got $(args.length)"
+                );
+            }
+
+            var arg = args[0];
+
+            if (arg.is_null()) {
+                // Null value - create Object? enumerable
+                var single = Iterate.single<Object>(null);
+                return new NativeElement<Enumerable<Object>>(single);
+            }
+
+            // Determine the type and create appropriate enumerable
+            int int_val;
+            long long_val;
+            double double_val;
+            bool bool_val;
+            string string_val;
+            Object object_val;
+
+            if (arg.try_get_as<int>(out int_val)) {
+                var single = Iterate.single<int>(int_val);
+                return new NativeElement<Enumerable<int>>(single);
+            } else if (arg.try_get_as<long>(out long_val)) {
+                var single = Iterate.single<long>(long_val);
+                return new NativeElement<Enumerable<long>>(single);
+            } else if (arg.try_get_as<double?>(out double_val)) {
+                var single = Iterate.single<double?>(double_val);
+                return new NativeElement<Enumerable<double?>>(single);
+            } else if (arg.try_get_as<bool>(out bool_val)) {
+                var single = Iterate.single<bool>(bool_val);
+                return new NativeElement<Enumerable<bool>>(single);
+            } else if (arg.try_get_as<string>(out string_val)) {
+                var single = Iterate.single<string>(string_val);
+                return new NativeElement<Enumerable<string>>(single);
+            } else if (arg.try_get_as<Object>(out object_val)) {
+                var single = Iterate.single<Object>(object_val);
+                return new NativeElement<Enumerable<Object>>(single);
+            } else {
+                // Unknown type - wrap as null object
+                var single = Iterate.single<Object>(null);
+                return new NativeElement<Enumerable<Object>>(single);
+            }
+        }
+
+        /**
+         * Calls the nothing function.
+         * 
+         * Signature: nothing<T>() -> Enumerable<T>
+         * 
+         * Note: Returns an empty Enumerable<Object> since we don't have
+         * type parameters in expressions.
+         */
+        private Element call_nothing(Element[] args) throws ExpressionError {
+            if (args.length != 0) {
+                throw new ExpressionError.INVALID_ARGUMENTS(
+                    @"nothing() takes no arguments, got $(args.length)"
+                );
+            }
+
+            var nothing = Iterate.nothing<Object>();
+            return new NativeElement<Enumerable<Object>>(nothing);
+        }
+
+        /**
+         * Helper to try getting an int from an element.
+         */
+        private bool try_get_int(Element elem, out int result) {
+            result = 0;
+            
+            int int_val;
+            if (elem.try_get_as<int>(out int_val)) {
+                result = int_val;
+                return true;
+            }
+            
+            long long_val;
+            if (elem.try_get_as<long>(out long_val)) {
+                result = (int)long_val;
+                return true;
+            }
+            
+            return false;
+        }
+    }
+}

+ 101 - 0
src/lib/Expressions/LotPropertyAccessor.vala

@@ -0,0 +1,101 @@
+using Invercargill.DataStructures;
+
+namespace Invercargill.Expressions {
+
+    /**
+     * Property accessor for types implementing the Lot<T> interface.
+     * 
+     * Provides access to Lot properties:
+     * - `length`: The number of items in the Lot (uint)
+     * 
+     * Example usage in expressions:
+     * {{{
+     * items.length          // Returns the number of items in the Lot
+     * }}}
+     */
+    public class LotPropertyAccessor : Object, PropertyAccessor {
+
+        /**
+         * The Lot value being accessed.
+         */
+        private Lot<Object>? _lot;
+
+        /**
+         * Creates a new LotPropertyAccessor.
+         * 
+         * @param lot The Lot value to access properties on
+         */
+        public LotPropertyAccessor(Lot<Object>? lot) {
+            _lot = lot;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public bool has_property(string property) {
+            return property == "length";
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public Enumerable<string> get_property_names() {
+            return new Wrappers.Array<string>(new string[]{"length"});
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public Element read_property(string property) throws ExpressionError {
+            switch (property) {
+                case "length":
+                    return new NativeElement<uint>(_lot != null ? _lot.length : 0);
+                
+                default:
+                    throw new ExpressionError.NON_EXISTANT_PROPERTY(
+                        @"Unknown Lot property: $property"
+                    );
+            }
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public Type get_property_type(string property) throws ExpressionError {
+            switch (property) {
+                case "length":
+                    return typeof(uint);
+                
+                default:
+                    throw new ExpressionError.NON_EXISTANT_PROPERTY(
+                        @"Unknown Lot property: $property"
+                    );
+            }
+        }
+
+    }
+
+    /**
+     * Factory for creating LotPropertyAccessor instances.
+     * 
+     * This factory is registered with the TypeAccessorRegistry to provide
+     * property access for Lot<T> values.
+     */
+    public class LotPropertyAccessorFactory : Object, PropertyAccessorFactory {
+
+        /**
+         * {@inheritDoc}
+         */
+        public PropertyAccessor? create(Element element) {
+            // Check if the element's value implements Lot
+            // We need to check for Lot<Object> since we can't check for Lot<T> generically
+            Lot? lot = null;
+            if (element.type() == typeof(Lot) && element.try_get_as(out lot)) {
+                return new LotPropertyAccessor(lot);
+            }
+            return null;
+        }
+
+    }
+
+}

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

@@ -0,0 +1,90 @@
+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 {
+            // Convert underscores to dashes for GObject property lookup
+            // e.g., "min_rank" becomes "min-rank"
+            var gobject_property = property.replace("_", "-");
+            var prop_type = get_property_type(gobject_property);
+            var value = Value(prop_type);
+            target.get_property (gobject_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());
+            //  }
+            //  // Check if the type implements Enumerable interface
+            //  // This handles Enumerable<T> properties properly
+            //  if (prop_type.is_a(typeof(Enumerable<Object?>))) {
+            //      // Return as ValueElement which can hold the enumerable
+            //      return new ValueElement(value);
+            //  }
+            //  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) {
+            // Convert underscores to dashes for GObject property lookup
+            // e.g., "min_rank" becomes "min-rank"
+            var gobject_property = property.replace("_", "-");
+            return properties.has(gobject_property);
+        }
+
+        public Enumerable<string> get_property_names () {
+            return properties.keys;
+        }
+
+        public override Type get_property_type(string property) throws ExpressionError {
+            // Property names are already in dashed form in the dictionary
+            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();
+        }
+
+    }
+
+}

+ 433 - 0
src/lib/Expressions/StringFunctionAccessor.vala

@@ -0,0 +1,433 @@
+using Invercargill.DataStructures;
+using Invercargill.Wrappers;
+
+namespace Invercargill.Expressions {
+
+    /**
+     * Function accessor for string types.
+     * 
+     * Provides access to string functions:
+     * - `chug()`: Removes leading whitespace
+     * - `chomp()`: Removes trailing whitespace
+     * - `upper()`: Converts to uppercase
+     * - `lower()`: Converts to lowercase
+     * - `reverse()`: Reverses the string
+     * - `substring(start, len)`: Extracts a substring
+     * - `contains(substring)`: Checks if contains substring
+     * - `has_prefix(prefix)`: Checks if starts with prefix
+     * - `has_suffix(suffix)`: Checks if ends with suffix
+     * - `replace(old, new)`: Replaces occurrences of old with new
+     * - `split(separator)`: Splits string into Enumerable<string>
+     * - `char_at(index)`: Gets character at index
+     * 
+     * Example usage in expressions:
+     * {{{
+     * name.upper()                    // "hello" → "HELLO"
+     * name.contains("ell")            // "hello" → true
+     * name.substring(1, 3)            // "hello" → "ell"
+     * name.split(",")                 // "a,b,c" → Enumerable<string>
+     * }}}
+     */
+    public class StringFunctionAccessor : Object, FunctionAccessor {
+
+        /**
+         * The string value being accessed.
+         */
+        private string? _value;
+
+        /**
+         * Creates a new StringFunctionAccessor.
+         * 
+         * @param value The string value to call functions on
+         */
+        public StringFunctionAccessor(string? value) {
+            _value = value;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public bool has_function(string function_name) {
+            switch (function_name) {
+                case "chug":
+                case "chomp":
+                case "upper":
+                case "lower":
+                case "reverse":
+                case "substring":
+                case "contains":
+                case "has_prefix":
+                case "has_suffix":
+                case "replace":
+                case "split":
+                case "char_at":
+                    return true;
+                default:
+                    return false;
+            }
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public Enumerable<string> get_function_names() {
+            return new Wrappers.Array<string>(new string[]{
+                "chug",
+                "chomp",
+                "upper",
+                "lower",
+                "reverse",
+                "substring",
+                "contains",
+                "has_prefix",
+                "has_suffix",
+                "replace",
+                "split",
+                "char_at"
+            });
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public Element call_function(
+            string function_name,
+            Series<Element> arguments,
+            EvaluationContext context
+        ) throws ExpressionError {
+            
+            // Handle null string - most functions return null or empty
+            if (_value == null) {
+                return handle_null_function(function_name, arguments);
+            }
+
+            switch (function_name) {
+                case "chug":
+                    return call_chug(arguments);
+                
+                case "chomp":
+                    return call_chomp(arguments);
+                
+                case "upper":
+                    return call_upper(arguments);
+                
+                case "lower":
+                    return call_lower(arguments);
+                
+                case "reverse":
+                    return call_reverse(arguments);
+                
+                case "substring":
+                    return call_substring(arguments);
+                
+                case "contains":
+                    return call_contains(arguments);
+                
+                case "has_prefix":
+                    return call_has_prefix(arguments);
+                
+                case "has_suffix":
+                    return call_has_suffix(arguments);
+                
+                case "replace":
+                    return call_replace(arguments);
+                
+                case "split":
+                    return call_split(arguments);
+                
+                case "char_at":
+                    return call_char_at(arguments);
+                
+                default:
+                    throw new ExpressionError.FUNCTION_NOT_FOUND(
+                        @"Unknown string function: $function_name"
+                    );
+            }
+        }
+
+        /**
+         * Handles function calls on null strings.
+         */
+        private Element handle_null_function(string function_name, Series<Element> arguments) 
+            throws ExpressionError {
+            
+            switch (function_name) {
+                case "split":
+                    // Return empty Enumerable<string> for null split
+                    return new ValueElement(new Wrappers.Array<string>(new string[0]));
+                
+                default:
+                    // Most string functions return null when called on null
+                    return new NullElement();
+            }
+        }
+
+        /**
+         * Removes leading whitespace.
+         */
+        private Element call_chug(Series<Element> arguments) throws ExpressionError {
+            expect_argument_count("chug", arguments, 0);
+            return new NativeElement<string>(_value.chomp().chug());
+        }
+
+        /**
+         * Removes trailing whitespace.
+         */
+        private Element call_chomp(Series<Element> arguments) throws ExpressionError {
+            expect_argument_count("chomp", arguments, 0);
+            return new NativeElement<string>(_value.chomp());
+        }
+
+        /**
+         * Converts to uppercase.
+         */
+        private Element call_upper(Series<Element> arguments) throws ExpressionError {
+            expect_argument_count("upper", arguments, 0);
+            return new NativeElement<string>(_value.up());
+        }
+
+        /**
+         * Converts to lowercase.
+         */
+        private Element call_lower(Series<Element> arguments) throws ExpressionError {
+            expect_argument_count("lower", arguments, 0);
+            return new NativeElement<string>(_value.down());
+        }
+
+        /**
+         * Reverses the string.
+         */
+        private Element call_reverse(Series<Element> arguments) throws ExpressionError {
+            expect_argument_count("reverse", arguments, 0);
+            return new NativeElement<string>(_value.reverse());
+        }
+
+        /**
+         * Extracts a substring.
+         */
+        private Element call_substring(Series<Element> arguments) throws ExpressionError {
+            var args = to_array(arguments);
+            
+            if (args.length == 1) {
+                // substring(start) - from start to end
+                int start = get_int_argument(args[0], "substring start");
+                if (start < 0) {
+                    start = _value.length + start;
+                }
+                if (start < 0 || start > _value.length) {
+                    return new NativeElement<string>("");
+                }
+                return new NativeElement<string>(_value.substring(start));
+            } else if (args.length == 2) {
+                // substring(start, length)
+                int start = get_int_argument(args[0], "substring start");
+                int len = get_int_argument(args[1], "substring length");
+                if (start < 0) {
+                    start = _value.length + start;
+                }
+                if (start < 0 || start > _value.length) {
+                    return new NativeElement<string>("");
+                }
+                return new NativeElement<string>(_value.substring(start, len));
+            } else {
+                throw new ExpressionError.INVALID_ARGUMENTS(
+                    @"substring expects 1 or 2 arguments, got $(args.length)"
+                );
+            }
+        }
+        
+        /**
+         * Helper to get an integer from an Element, handling various numeric types.
+         */
+        private int get_int_argument(Element element, string arg_name) throws ExpressionError {
+            // Try int first
+            int int_val;
+            if (element.try_get_as(out int_val)) {
+                return int_val;
+            }
+            // Try int64
+            int64? int64_val;
+            if (element.try_get_as(out int64_val)) {
+                return (int)int64_val;
+            }
+            // Try long
+            long? long_val;
+            if (element.try_get_as(out long_val)) {
+                return (int)long_val;
+            }
+            // Try double (for cases where someone passes a float)
+            double? double_val;
+            if (element.try_get_as(out double_val)) {
+                return (int)double_val;
+            }
+            throw new ExpressionError.INVALID_ARGUMENTS(
+                @"$arg_name must be an integer"
+            );
+        }
+
+        /**
+         * Checks if string contains substring.
+         */
+        private Element call_contains(Series<Element> arguments) throws ExpressionError {
+            expect_argument_count("contains", arguments, 1);
+            var args = to_array(arguments);
+            string? substring;
+            if (!args[0].try_get_as(out substring)) {
+                return new NativeElement<bool>(false);
+            }
+            if (substring == null) {
+                return new NativeElement<bool>(false);
+            }
+            return new NativeElement<bool>(_value.contains(substring));
+        }
+
+        /**
+         * Checks if string starts with prefix.
+         */
+        private Element call_has_prefix(Series<Element> arguments) throws ExpressionError {
+            expect_argument_count("has_prefix", arguments, 1);
+            var args = to_array(arguments);
+            string? prefix;
+            if (!args[0].try_get_as(out prefix)) {
+                return new NativeElement<bool>(false);
+            }
+            if (prefix == null) {
+                return new NativeElement<bool>(false);
+            }
+            return new NativeElement<bool>(_value.has_prefix(prefix));
+        }
+
+        /**
+         * Checks if string ends with suffix.
+         */
+        private Element call_has_suffix(Series<Element> arguments) throws ExpressionError {
+            expect_argument_count("has_suffix", arguments, 1);
+            var args = to_array(arguments);
+            string? suffix;
+            if (!args[0].try_get_as(out suffix)) {
+                return new NativeElement<bool>(false);
+            }
+            if (suffix == null) {
+                return new NativeElement<bool>(false);
+            }
+            return new NativeElement<bool>(_value.has_suffix(suffix));
+        }
+
+        /**
+         * Replaces occurrences of old with new.
+         */
+        private Element call_replace(Series<Element> arguments) throws ExpressionError {
+            expect_argument_count("replace", arguments, 2);
+            var args = to_array(arguments);
+            string? old_str;
+            string? new_str;
+            if (!args[0].try_get_as(out old_str)) {
+                return new NativeElement<string>(_value);
+            }
+            if (!args[1].try_get_as(out new_str)) {
+                new_str = "";
+            }
+            if (old_str == null) {
+                return new NativeElement<string>(_value);
+            }
+            return new NativeElement<string>(_value.replace(old_str, new_str ?? ""));
+        }
+
+        /**
+         * Splits string into Enumerable<string>.
+         */
+        private Element call_split(Series<Element> arguments) throws ExpressionError {
+            expect_argument_count("split", arguments, 1);
+            var args = to_array(arguments);
+            string? separator;
+            if (!args[0].try_get_as(out separator)) {
+                separator = null;
+            }
+            
+            if (separator == null || separator.length == 0) {
+                // Split by each character
+                var result = new string[_value.length];
+                for (int i = 0; i < _value.length; i++) {
+                    result[i] = _value.substring(i, 1);
+                }
+                return new ValueElement(new Wrappers.Array<string>(result));
+            }
+            
+            string[] parts = _value.split(separator);
+            return new ValueElement(new Wrappers.Array<string>(parts));
+        }
+
+        /**
+         * Gets character at index.
+         */
+        private Element call_char_at(Series<Element> arguments) throws ExpressionError {
+            expect_argument_count("char_at", arguments, 1);
+            var args = to_array(arguments);
+            int index = get_int_argument(args[0], "char_at index");
+            
+            if (index < 0) {
+                index = _value.length + index;
+            }
+            
+            if (index < 0 || index >= _value.length) {
+                return new NullElement();
+            }
+            
+            return new NativeElement<string>(_value.substring(index, 1));
+        }
+
+        /**
+         * Helper to validate argument count.
+         */
+        private void expect_argument_count(string function_name, Series<Element> arguments, int expected) 
+            throws ExpressionError {
+            int count = 0;
+            foreach (var _ in arguments) {
+                count++;
+            }
+            if (count != expected) {
+                throw new ExpressionError.INVALID_ARGUMENTS(
+                    @"$function_name expects $expected argument(s), got $count"
+                );
+            }
+        }
+
+        /**
+         * Helper to convert Series to array.
+         */
+        private Element[] to_array(Series<Element> arguments) {
+            var list = new GLib.GenericArray<Element>();
+            foreach (var arg in arguments) {
+                list.add(arg);
+            }
+            var result = new Element[list.length];
+            for (int i = 0; i < list.length; i++) {
+                result[i] = list[i];
+            }
+            return result;
+        }
+
+    }
+
+    /**
+     * Factory for creating StringFunctionAccessor instances.
+     * 
+     * This factory is registered with the TypeAccessorRegistry to provide
+     * function access for string values.
+     */
+    public class StringFunctionAccessorFactory : Object, FunctionAccessorFactory {
+
+        /**
+         * {@inheritDoc}
+         */
+        public FunctionAccessor? create(Element element) {
+            string? value;
+            if (element.try_get_as(out value)) {
+                return new StringFunctionAccessor(value);
+            }
+            return null;
+        }
+
+    }
+
+}

+ 123 - 0
src/lib/Expressions/StringPropertyAccessor.vala

@@ -0,0 +1,123 @@
+using Invercargill.DataStructures;
+using Invercargill.Wrappers;
+
+namespace Invercargill.Expressions {
+
+    /**
+     * Property accessor for string types.
+     * 
+     * Provides access to string properties:
+     * - `length`: The length of the string (int)
+     * - `empty`: Whether the string is empty (bool)
+     * - `is_null`: Whether the string is null (bool)
+     * - `is_empty_or_null`: Whether the string is empty or null (bool)
+     * 
+     * Example usage in expressions:
+     * {{{
+     * name.length          // Returns the length of name
+     * name.empty           // Returns true if name is ""
+     * name.is_null         // Returns true if name is null
+     * name.is_empty_or_null // Returns true if name is "" or null
+     * }}}
+     */
+    public class StringPropertyAccessor : Object, PropertyAccessor {
+
+        /**
+         * The string value being accessed.
+         */
+        private string? _value;
+
+        /**
+         * Creates a new StringPropertyAccessor.
+         * 
+         * @param value The string value to access properties on
+         */
+        public StringPropertyAccessor(string? value) {
+            _value = value;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public bool has_property(string property) {
+            return property == "length" ||
+                   property == "empty" ||
+                   property == "is_null" ||
+                   property == "is_empty_or_null";
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public Enumerable<string> get_property_names() {
+            return new Wrappers.Array<string>(new string[]{"length", "empty", "is_null", "is_empty_or_null"});
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public Element read_property(string property) throws ExpressionError {
+            switch (property) {
+                case "length":
+                    return new NativeElement<int>(_value == null ? 0 : _value.length);
+                
+                case "empty":
+                    return new NativeElement<bool>(_value != null && _value.length == 0);
+                
+                case "is_null":
+                    return new NativeElement<bool>(_value == null);
+                
+                case "is_empty_or_null":
+                    return new NativeElement<bool>(_value == null || _value.length == 0);
+                
+                default:
+                    throw new ExpressionError.NON_EXISTANT_PROPERTY(
+                        @"Unknown string property: $property"
+                    );
+            }
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public Type get_property_type(string property) throws ExpressionError {
+            switch (property) {
+                case "length":
+                    return typeof(int?);
+                
+                case "empty":
+                case "is_null":
+                case "is_empty_or_null":
+                    return typeof(bool?);
+                
+                default:
+                    throw new ExpressionError.NON_EXISTANT_PROPERTY(
+                        @"Unknown string property: $property"
+                    );
+            }
+        }
+
+    }
+
+    /**
+     * Factory for creating StringPropertyAccessor instances.
+     * 
+     * This factory is registered with the TypeAccessorRegistry to provide
+     * property access for string values.
+     */
+    public class StringPropertyAccessorFactory : Object, PropertyAccessorFactory {
+
+        /**
+         * {@inheritDoc}
+         */
+        public PropertyAccessor? create(Element element) {
+            string? value;
+            if (element.try_get_as(out value)) {
+                return new StringPropertyAccessor(value);
+            }
+            return null;
+        }
+
+    }
+
+}

+ 273 - 0
src/lib/Expressions/TypeAccessorRegistry.vala

@@ -0,0 +1,273 @@
+using Invercargill.DataStructures;
+
+namespace Invercargill.Expressions {
+
+    /**
+     * Property accessor for NullElement that supports the is_null property.
+     */
+    internal class NullElementPropertyAccessor : Object, PropertyAccessor {
+        
+        private NullElement null_element;
+        
+        public NullElementPropertyAccessor(NullElement element) {
+            this.null_element = element;
+        }
+        
+        public bool has_property(string property) {
+            return property == "is_null";
+        }
+        
+        public Element read_property(string property) throws ExpressionError {
+            if (property == "is_null") {
+                return new NativeElement<bool>(true);
+            }
+            throw new ExpressionError.NON_EXISTANT_PROPERTY(@"NullElement does not have a property called '$property'.");
+        }
+        
+        public Enumerable<string> get_property_names() {
+            var names = new Series<string>();
+            names.add("is_null");
+            return names;
+        }
+        
+        public override Type get_property_type(string property) throws ExpressionError {
+            if (property == "is_null") {
+                return typeof(bool);
+            }
+            throw new ExpressionError.NON_EXISTANT_PROPERTY(@"NullElement does not have a property called '$property'.");
+        }
+    }
+
+    /**
+     * Registry for type-specific property and function accessors.
+     * 
+     * This class manages the registration of PropertyAccessorFactory and
+     * FunctionAccessorFactory instances for specific types. When an expression
+     * needs to access a property or call a function on an Element, the registry
+     * is consulted to find an appropriate accessor.
+     * 
+     * The registry supports:
+     * - Registering factories for specific types
+     * - Fallback to default behavior (GObject properties, Enumerable functions)
+     * - Global singleton access for convenience
+     * 
+     * Example usage:
+     * {{{
+     * var registry = new TypeAccessorRegistry();
+     * registry.register_property_accessor(new StringPropertyAccessorFactory());
+     * registry.register_function_accessor(new StringFunctionAccessorFactory());
+     * 
+     * var accessor = registry.get_property_accessor(element);
+     * if (accessor != null && accessor.has_property("length")) {
+     *     var length = accessor.read_property("length");
+     * }
+     * }}}
+     */
+    public class TypeAccessorRegistry : Object {
+
+        /**
+         * Singleton instance for global access.
+         */
+        private static TypeAccessorRegistry? _instance = null;
+
+        /**
+         * Gets the global singleton instance, creating it if necessary.
+         * 
+         * @return The global TypeAccessorRegistry instance
+         */
+        public static TypeAccessorRegistry get_instance() {
+            if (_instance == null) {
+                _instance = new TypeAccessorRegistry();
+                _instance.register_defaults();
+            }
+            return _instance;
+        }
+
+        /**
+         * Resets the singleton instance (useful for testing).
+         */
+        public static void reset_instance() {
+            _instance = null;
+        }
+
+        /**
+         * Registered property accessor factories.
+         */
+        private GenericArray<PropertyAccessorFactory> _property_factories =
+            new GenericArray<PropertyAccessorFactory>();
+
+        /**
+         * Registered function accessor factories.
+         */
+        private GenericArray<FunctionAccessorFactory> _function_factories =
+            new GenericArray<FunctionAccessorFactory>();
+
+        /**
+         * Registers a property accessor factory.
+         * 
+         * Factories are consulted in registration order; the first factory
+         * that returns a non-null accessor is used.
+         * 
+         * @param factory The factory to register
+         */
+        public void register_property_accessor(PropertyAccessorFactory factory) {
+            _property_factories.add(factory);
+        }
+
+        /**
+         * Registers a function accessor factory.
+         * 
+         * Factories are consulted in registration order; the first factory
+         * that returns a non-null accessor is used.
+         * 
+         * @param factory The factory to register
+         */
+        public void register_function_accessor(FunctionAccessorFactory factory) {
+            _function_factories.add(factory);
+        }
+
+        /**
+         * Gets a property accessor for the given Element.
+         * 
+         * This method consults all registered factories in order until one
+         * returns a non-null accessor. If no registered factory handles the
+         * element, fallback behavior is used (GObject properties).
+         * 
+         * @param element The Element to get a property accessor for
+         * @return A PropertyAccessor if available, null otherwise
+         */
+        public PropertyAccessor? get_property_accessor(Element element) {
+            // Try registered factories first
+            foreach (var factory in _property_factories) {
+                var accessor = factory.create(element);
+                if (accessor != null) {
+                    return accessor;
+                }
+            }
+
+            // Fallback: Check if element directly implements PropertyAccessor
+            PropertyAccessor? accessor = null;
+            if (element.type() == typeof(PropertyAccessor) && element.try_get_as(out accessor)) {
+                return accessor;
+            }
+
+            // Special handling for NullElement - supports is_null property
+            if (element is NullElement) {
+                return new NullElementPropertyAccessor((NullElement) element);
+            }
+
+            // Fallback: Use ObjectPropertyAccessor for Objects
+            Object? obj;
+            if (element.try_get_as(out obj)) {
+                return new ObjectPropertyAccessor(obj);
+            }
+            return null;
+        }
+
+        /**
+         * Gets a function accessor for the given Element.
+         * 
+         * This method consults all registered factories in order until one
+         * returns a non-null accessor. If no registered factory handles the
+         * element, fallback behavior is used (Enumerable functions).
+         * 
+         * @param element The Element to get a function accessor for
+         * @return A FunctionAccessor if available, null otherwise
+         */
+        public FunctionAccessor? get_function_accessor(Element element) {
+            // Check for GlobalFunctionsElement first (for global functions)
+            if (element is GlobalFunctionsElement) {
+                return ((GlobalFunctionsElement) element).accessor;
+            }
+
+            // SPECIAL HANDLING FOR ValueElement containing Enumerable
+            // Generic interface type checking (e.g., value_type.is_a(typeof(Enumerable)))
+            // doesn't work correctly for parameterized types in GObject, so we need
+            // to extract the object directly from the Value and check if it implements Enumerable.
+            // This must be done BEFORE the try_get_as calls below, as those can fail
+            // for interface types in certain scenarios.
+            if (element is ValueElement) {
+                var value_element = (ValueElement) element;
+                var value = value_element.get_value();
+                var value_type = value.type();
+                
+                // Check if the value holds an object (directly or via interface)
+                // For interface types, value_type.is_a(Type.OBJECT) returns false,
+                // but we can still extract the object using get_object()
+                if (value_type.is_a(Type.OBJECT) || value_type.is_interface()) {
+                    var obj = value.get_object();
+                    if (obj is Enumerable) {
+                        return new EnumerableFunctionAccessor(((Enumerable)obj).to_elements());
+                    }
+                }
+            }
+
+            // Try registered factories
+            foreach (var factory in _function_factories) {
+                var accessor = factory.create(element);
+                if (accessor != null) {
+                    return accessor;
+                }
+            }
+
+            // Fallback: Check if element directly implements FunctionAccessor
+            FunctionAccessor? accessor;
+            if (element.try_get_as(out accessor)) {
+                return accessor;
+            }
+            
+            // Fallback: Check for Elements (Enumerable wrapper)
+            Elements? elements;
+            if (element.try_get_as(out elements)) {
+                return new EnumerableFunctionAccessor(elements);
+            }
+
+            // Fallback: Check for Enumerable directly
+            Enumerable? enumerable;
+            if (element.try_get_as(out enumerable)) {
+                return new EnumerableFunctionAccessor(enumerable.to_elements());
+            }
+
+            return null;
+        }
+
+        /**
+         * Checks if a property accessor is available for the given Element.
+         * 
+         * @param element The Element to check
+         * @return true if a property accessor is available
+         */
+        public bool has_property_accessor(Element element) {
+            return get_property_accessor(element) != null;
+        }
+
+        /**
+         * Checks if a function accessor is available for the given Element.
+         * 
+         * @param element The Element to check
+         * @return true if a function accessor is available
+         */
+        public bool has_function_accessor(Element element) {
+            return get_function_accessor(element) != null;
+        }
+
+        /**
+         * Registers default accessor factories.
+         *
+         * This is called automatically when the singleton is created.
+         * Currently registers:
+         * - StringPropertyAccessorFactory for string properties
+         * - LotPropertyAccessorFactory for Lot<T> properties
+         * - StringFunctionAccessorFactory for string functions
+         * - DateTimeFunctionAccessorFactory for DateTime functions
+         */
+        private void register_defaults() {
+            register_property_accessor(new StringPropertyAccessorFactory());
+            register_property_accessor(new LotPropertyAccessorFactory());
+            register_function_accessor(new StringFunctionAccessorFactory());
+            register_function_accessor(new DateTimeFunctionAccessorFactory());
+        }
+
+    }
+
+}

+ 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;
+            }
+        }
+    }
+
+}

+ 4 - 4
src/lib/Interfaces.vala

@@ -1,19 +1,19 @@
 namespace Invercargill {
 
 
-    public interface Equatable<T> {
+    public interface Equatable<T> : Object {
         public abstract bool equals(T other);
     }
 
-    public interface Hashable {
+    public interface Hashable : Object {
         public abstract uint hash_code();
     }
 
-    public interface Comparable<T> {
+    public interface Comparable<T> : Object {
         public abstract int compare(T other);
     }
 
-    public interface Stringifyable {
+    public interface Stringifyable : Object {
         public abstract string to_string();
     }
 

+ 6 - 6
src/lib/Interfaces/AddressableBytes.vala

@@ -71,7 +71,7 @@ namespace Invercargill {
         }
 
 
-        public virtual void set_all_int32(uint index, Enumerable<int32?> values, Endianness endianness) throws IndexError {
+        public virtual void set_all_int32(uint index, Enumerable<int32> values, Endianness endianness) throws IndexError {
             foreach(var value in values) {
                 set_int32(index, value, endianness);
             }
@@ -95,7 +95,7 @@ namespace Invercargill {
             set_data(index, chunk);
         }
 
-        public virtual void set_all_uint32(uint index, Enumerable<uint32?> values, Endianness endianness) throws IndexError {
+        public virtual void set_all_uint32(uint index, Enumerable<uint32> values, Endianness endianness) throws IndexError {
             foreach(var value in values) {
                 set_uint32(index, value, endianness);
             }
@@ -119,7 +119,7 @@ namespace Invercargill {
             set_data(index, chunk);
         }
 
-        public virtual void set_all_int16(uint index, Enumerable<int16?> values, Endianness endianness) throws IndexError {
+        public virtual void set_all_int16(uint index, Enumerable<int16> values, Endianness endianness) throws IndexError {
             foreach(var value in values) {
                 set_int16(index, value, endianness);
             }
@@ -143,7 +143,7 @@ namespace Invercargill {
             set_data(index, chunk);
         }
 
-        public virtual void set_all_uint16(uint index, Enumerable<uint16?> values, Endianness endianness) throws IndexError {
+        public virtual void set_all_uint16(uint index, Enumerable<uint16> values, Endianness endianness) throws IndexError {
             foreach(var value in values) {
                 set_uint16(index, value, endianness);
             }
@@ -155,7 +155,7 @@ namespace Invercargill {
             set_data(index, chunk);
         }
 
-        public virtual void set_all_int8(uint index, Enumerable<int8?> values, Endianness endianness) throws IndexError {
+        public virtual void set_all_int8(uint index, Enumerable<int8> values, Endianness endianness) throws IndexError {
             foreach(var value in values) {
                 set_int8(index, value, endianness);
             }
@@ -166,7 +166,7 @@ namespace Invercargill {
             set_data(index, chunk);
         }
 
-        public virtual void set_all_uint8(uint index, Enumerable<uint8?> values, Endianness endianness) throws IndexError {
+        public virtual void set_all_uint8(uint index, Enumerable<uint8> values, Endianness endianness) throws IndexError {
             foreach(var value in values) {
                 set_uint8(index, value, endianness);
             }

+ 0 - 6
src/lib/Interfaces/BinaryData.vala

@@ -1,6 +0,0 @@
-
-
-namespace Invercargill {
-
-
-}

+ 0 - 6
src/lib/Interfaces/Grouped.vala

@@ -1,6 +0,0 @@
-
-namespace Invercargill {
-
-    
-
-}

+ 23 - 8
src/lib/Mapping/PropertyMapper.vala

@@ -11,7 +11,7 @@ namespace Invercargill.Mapping {
     public class PropertyMapper<T> : Object, Mapper<T, Properties> {
 
         private Vector<PropertyMapping<T>> mappings;
-        private ObjectConstructor<T> constructor;
+        private ObjectConstructor<T>? constructor;
 
         public static PropertyMapper<T> build_for<T>(Func<PropertyMapperBuilder<T>> func) {
             int location = (int)func;
@@ -22,7 +22,7 @@ namespace Invercargill.Mapping {
             });
         }
 
-        internal PropertyMapper(Vector<PropertyMapping<T>> mappings, ObjectConstructor<T> constructor) {
+        internal PropertyMapper(Vector<PropertyMapping<T>> mappings, ObjectConstructor<T>? constructor) {
             this.mappings = mappings;
             this.constructor = constructor;
         }
@@ -50,7 +50,7 @@ namespace Invercargill.Mapping {
         }
 
         public T materialise(Properties properties) throws Error {
-            var obj = constructor();
+            var obj = constructor != null ? constructor() : (T) Object.new(typeof(T));
             map_into(obj, properties);
             return obj;
         }
@@ -80,6 +80,17 @@ namespace Invercargill.Mapping {
 
     private class PropertyMapping<T> {
         public string name;
+        // Delegate fields are owned (the default) — but valac only
+        // emits the ownership transfer (storing the closure's destroy
+        // notify instead of destroying it at scope exit) for assignments
+        // marked (owned) or lambda literals. The plain param-to-field
+        // assignments below therefore used to free every stored closure
+        // the moment the builder method returned, leaving long-lived
+        // mappers with dangling setter/getter targets — a use-after-free
+        // read on each map_into/map_from that only faulted when the
+        // allocator reused the block (intermittent, layout-dependent
+        // SIGSEGVs far away from the cause, e.g. during Statum slot
+        // signing).
         public PropertyGetter<T, Element> getter;
         public PropertySetter<T, Element> setter;
         public PropertyPredicate<T>? null_check;
@@ -95,7 +106,11 @@ namespace Invercargill.Mapping {
 
         public PropertyMapperBuilder() {
             mappings = new Vector<PropertyMapping<T>>();
-            constructor = () => Object.new(typeof(T));
+            // No default constructor closure: a lambda capturing `this` would
+            // dangle once the (usually transient) builder is finalized while
+            // the built PropertyMapper lives on. materialise() falls back to
+            // the mapper's own T when no constructor was set.
+            constructor = null;
         }
 
         public virtual PropertyMappingBuilder<T> map<TProp>(string name, owned PropertyGetter<T, TProp> getter, owned PropertySetter<T, TProp> setter) {
@@ -186,22 +201,22 @@ namespace Invercargill.Mapping {
         }
 
         public PropertyMappingBuilder<T> null_when(owned PropertyPredicate<T> predicate) {
-            mapping.null_check = predicate;
+            mapping.null_check = (owned)predicate;
             return this;
         }
 
         public PropertyMappingBuilder<T> undefined_when(owned PropertyPredicate<T> predicate) {
-            mapping.undefined_check = predicate;
+            mapping.undefined_check = (owned)predicate;
             return this;
         }
 
         public PropertyMappingBuilder<T> when_null(owned PropertyDefaultSetter<T> setter) {
-            mapping.null_setter = setter;
+            mapping.null_setter = (owned)setter;
             return this;
         }
         
         public PropertyMappingBuilder<T> when_undefined(owned PropertyDefaultSetter<T> setter) {
-            mapping.undefined_setter = setter;
+            mapping.undefined_setter = (owned)setter;
             return this;
         }
 

+ 426 - 0
src/lib/Nullable.c

@@ -0,0 +1,426 @@
+/* Nullable.c - Production-ready nullable type detection and manipulation for Vala
+ * 
+ * This file provides functions to:
+ * - Detect if a generic type T is nullable (boxed) at runtime
+ * - Check if a nullable value is null
+ * - Get the raw value from a nullable (with default for null)
+ * - Create a boxed copy of a value
+ * 
+ */
+
+#include <glib-object.h>
+#include <string.h>
+#include <stdio.h>
+
+/* ============================================================================
+ * Internal Helpers
+ * ============================================================================ */
+
+static gboolean
+_nullable_type_is_boxable (GType t_type)
+{
+    switch (t_type) {
+        case G_TYPE_INT:
+        case G_TYPE_UINT:
+        case G_TYPE_LONG:
+        case G_TYPE_ULONG:
+        case G_TYPE_INT64:
+        case G_TYPE_UINT64:
+        case G_TYPE_FLOAT:
+        case G_TYPE_DOUBLE:
+        case G_TYPE_BOOLEAN:
+        case G_TYPE_CHAR:
+        case G_TYPE_UCHAR:
+        case G_TYPE_ENUM:
+        case G_TYPE_FLAGS:
+            return TRUE;
+        default:
+            return FALSE;
+    }
+}
+
+/* Check if a type is always boxed in Vala generics (int64, uint64, float, double)
+ * These types are ALWAYS boxed regardless of nullability, so no boxing/unboxing
+ * transformation is needed for safe_cast. */
+static gboolean
+_nullable_type_is_always_boxed (GType t_type)
+{
+    switch (t_type) {
+        case G_TYPE_INT64:
+        case G_TYPE_UINT64:
+        case G_TYPE_FLOAT:
+        case G_TYPE_DOUBLE:
+            return TRUE;
+        default:
+            return FALSE;
+    }
+}
+
+/* Get the size of a boxable type for memcpy/memset operations.
+ * Note: int64, uint64, float, double are excluded as they are always boxed. */
+static gsize
+_nullable_type_size (GType t_type)
+{
+    switch (t_type) {
+        case G_TYPE_INT:
+        case G_TYPE_UINT:
+            return sizeof (gint);
+        case G_TYPE_LONG:
+        case G_TYPE_ULONG:
+            return sizeof (glong);
+        case G_TYPE_BOOLEAN:
+            return sizeof (gboolean);
+        case G_TYPE_CHAR:
+        case G_TYPE_UCHAR:
+            return sizeof (gchar);
+        case G_TYPE_ENUM:
+            return sizeof (gint);
+        case G_TYPE_FLAGS:
+            return sizeof (guint);
+        /* int64, uint64, float, double excluded - always boxed */
+        default:
+            return sizeof (gpointer);
+    }
+}
+
+/* ============================================================================
+ * Type Detection Functions
+ * ============================================================================ */
+
+gboolean
+is_boxed_nullable_impl (GType t_type, gpointer t_dup_func, gpointer t_destroy_func,
+                        gconstpointer value)
+{
+    GDestroyNotify destroy_func = (GDestroyNotify) t_destroy_func;
+    
+    if (destroy_func == NULL) {
+        return FALSE;
+    }
+    
+    if (!G_TYPE_IS_FUNDAMENTAL (t_type)) {
+        return FALSE;
+    }
+    
+    return _nullable_type_is_boxable (t_type);
+}
+
+gboolean
+is_nullable_impl (GType t_type, gpointer t_dup_func, gpointer t_destroy_func,
+                  gconstpointer value)
+{
+    GDestroyNotify destroy_func = (GDestroyNotify) t_destroy_func;
+    
+    /* For reference types (non-fundamental like Object), always nullable */
+    if (!G_TYPE_IS_FUNDAMENTAL (t_type)) {
+        return TRUE;
+    }
+    
+    /* Strings are always nullable reference types */
+    if (t_type == G_TYPE_STRING) {
+        return TRUE;
+    }
+    
+    /* For boxable value types (int, bool, etc.):
+     * - If destroy_func is set, it's a nullable type (int?, bool?, etc.)
+     * - If destroy_func is NULL, it's a non-nullable type (int, bool, etc.)
+     */
+    if (_nullable_type_is_boxable (t_type)) {
+        return (destroy_func != NULL);
+    }
+    
+    /* For pointer types without a destroy func, not nullable */
+    if (t_type == G_TYPE_POINTER) {
+        return FALSE;
+    }
+    
+    /* For other types with destroy_func, consider them nullable reference types */
+    return (destroy_func != NULL);
+}
+
+gboolean
+is_reference_impl (GType t_type, gpointer t_dup_func, gpointer t_destroy_func,
+                   gconstpointer value)
+{
+    GDestroyNotify destroy_func = (GDestroyNotify) t_destroy_func;
+    
+    if (!G_TYPE_IS_FUNDAMENTAL (t_type)) {
+        return TRUE;
+    }
+    
+    if (t_type == G_TYPE_STRING) {
+        return TRUE;
+    }
+    
+    if (_nullable_type_is_boxable (t_type)) {
+        return FALSE;
+    }
+    
+    if (t_type == G_TYPE_POINTER) {
+        return FALSE;
+    }
+    
+    return (destroy_func != NULL);
+}
+
+/* ============================================================================
+ * Null Check Functions
+ * ============================================================================ */
+
+gboolean
+is_null_impl (GType t_type, gpointer t_dup_func, gpointer t_destroy_func,
+              gconstpointer value)
+{
+    GDestroyNotify destroy_func = (GDestroyNotify) t_destroy_func;
+    
+    if (destroy_func == NULL && G_TYPE_IS_FUNDAMENTAL (t_type) && 
+        _nullable_type_is_boxable (t_type)) {
+        return FALSE;
+    }
+    
+    return (value == NULL);
+}
+
+/* ============================================================================
+ * Value Access Functions
+ * 
+ * IMPORTANT: For nullable types, these functions return the value directly.
+ * Vala will handle ownership appropriately based on the function signature.
+ * ============================================================================ */
+
+/**
+ * unbox_or_default_impl - Get value or default for null
+ * 
+ * For nullable types, if value is not null, returns value (ownership transferred).
+ * If value is null, returns a copy of default_value.
+ * 
+ * The caller owns the returned value.
+ */
+gpointer
+unbox_or_default_impl (GType t_type, gpointer t_dup_func, gpointer t_destroy_func,
+                       gconstpointer value, gconstpointer default_value)
+{
+    GBoxedCopyFunc dup_func = (GBoxedCopyFunc) t_dup_func;
+    
+    /* If value is not null, return it (transfer ownership) */
+    if (value != NULL) {
+        return (gpointer) value;
+    }
+    
+    /* If default is null, return null */
+    if (default_value == NULL) {
+        return NULL;
+    }
+    
+    /* Return a copy of the default value if we have a dup function */
+    if (dup_func != NULL) {
+        return dup_func ((gpointer) default_value);
+    }
+    
+    /* No dup function - return the default as-is (for non-nullable types) */
+    return (gpointer) default_value;
+}
+
+/**
+ * box_copy_impl - Create a boxed copy of a value
+ */
+gpointer
+box_copy_impl (GType t_type, gpointer t_dup_func, gpointer t_destroy_func,
+               gconstpointer value)
+{
+    GBoxedCopyFunc dup_func = (GBoxedCopyFunc) t_dup_func;
+    GDestroyNotify destroy_func = (GDestroyNotify) t_destroy_func;
+    
+    if (value == NULL) {
+        return NULL;
+    }
+    
+    /* If there's a dup function, use it */
+    if (dup_func != NULL) {
+        return dup_func ((gpointer) value);
+    }
+    
+    /* For nullable value types (destroy_func set but no dup_func),
+     * we need to allocate and copy the value manually */
+    if (destroy_func != NULL &&
+        G_TYPE_IS_FUNDAMENTAL (t_type) &&
+        _nullable_type_is_boxable (t_type)) {
+        gsize size = _nullable_type_size (t_type);
+        gpointer boxed = g_malloc (size);
+        memcpy (boxed, value, size);
+        return boxed;
+    }
+    
+    /* For non-nullable types or reference types without dup_func,
+     * return as-is (the value IS the pointer for non-nullable value types) */
+    return (gpointer) value;
+}
+
+/**
+ * free_boxed_impl - Free a boxed value
+ */
+void
+free_boxed_impl (GType t_type, gpointer t_dup_func, gpointer t_destroy_func,
+                 gpointer value)
+{
+    GDestroyNotify destroy_func = (GDestroyNotify) t_destroy_func;
+    
+    if (destroy_func != NULL && value != NULL) {
+        destroy_func (value);
+    }
+}
+
+/* ============================================================================
+ * Type Information Helpers
+ * ============================================================================ */
+
+static __thread char _nullable_type_name_buffer[256];
+
+const char*
+get_type_name_impl (GType t_type, gpointer t_dup_func, gpointer t_destroy_func,
+                    gconstpointer value)
+{
+    GDestroyNotify destroy_func = (GDestroyNotify) t_destroy_func;
+    const char* base_name = g_type_name (t_type);
+    
+    if (base_name == NULL) {
+        base_name = "unknown";
+    }
+    
+    if (destroy_func == NULL) {
+        snprintf (_nullable_type_name_buffer, sizeof (_nullable_type_name_buffer),
+                  "%s (non-nullable)", base_name);
+    } else if (_nullable_type_is_boxable (t_type) && G_TYPE_IS_FUNDAMENTAL (t_type)) {
+        snprintf (_nullable_type_name_buffer, sizeof (_nullable_type_name_buffer),
+                  "%s? (nullable)", base_name);
+    } else {
+        snprintf (_nullable_type_name_buffer, sizeof (_nullable_type_name_buffer),
+                  "%s (reference)", base_name);
+    }
+    
+    return _nullable_type_name_buffer;
+}
+
+/* ============================================================================
+ * Convert Nullability Functions
+ *
+ * Safely convert between types with the same GType but different nullability.
+ * For example: int <-> int?, bool <-> bool?, etc.
+ * ============================================================================ */
+
+/**
+ * convert_nullability_impl - Convert between types with same GType but different nullability
+ *
+ * Note: Parameter order is determined by Vala's generic method compilation:
+ * Vala passes all type info for all generic types first, then values.
+ *
+ * @from_type: GType of the source value
+ * @from_dup_func: Dup function for source type (from Vala generic)
+ * @from_destroy_func: Destroy function for source type (from Vala generic)
+ * @to_type: GType of the target type
+ * @to_dup_func: Dup function for target type (from Vala generic)
+ * @to_destroy_func: Destroy function for target type (from Vala generic)
+ * @from_value: The source value to convert
+ * @out_output: Pointer to store the output value
+ *
+ * Returns: TRUE if conversion succeeded, FALSE if source was null and target is non-nullable
+ *
+ * When returning FALSE, the output is set to a zeroed default value.
+ *
+ * Note: int64, uint64, float, double are always boxed in Vala generics,
+ * so they are treated like reference types (pointer pass-through only).
+ */
+gboolean
+convert_nullability_impl (
+    GType from_type, gpointer from_dup_func, gpointer from_destroy_func,
+    GType to_type, gpointer to_dup_func, gpointer to_destroy_func,
+    gconstpointer from_value,
+    gpointer out_output
+)
+{
+    GDestroyNotify from_destroy = (GDestroyNotify) from_destroy_func;
+    GDestroyNotify to_destroy = (GDestroyNotify) to_destroy_func;
+    GBoxedCopyFunc to_dup = (GBoxedCopyFunc) to_dup_func;
+    
+    /* Determine if types are boxable value types */
+    gboolean from_is_boxable = (G_TYPE_IS_FUNDAMENTAL (from_type) &&
+                                 _nullable_type_is_boxable (from_type));
+    gboolean to_is_boxable = (G_TYPE_IS_FUNDAMENTAL (to_type) &&
+                               _nullable_type_is_boxable (to_type));
+    
+    /* Check for always-boxed types (int64, uint64, float, double)
+     * These are ALWAYS boxed in Vala generics, treat like reference types */
+    gboolean from_is_always_boxed = _nullable_type_is_always_boxed (from_type);
+    gboolean to_is_always_boxed = _nullable_type_is_always_boxed (to_type);
+    
+    /* For non-boxable types, always-boxed types, or reference types:
+     * Simple pointer pass-through with optional duplication.
+     * Return TRUE since no conversion is needed - the reference is just passed through. */
+    if (!from_is_boxable || !to_is_boxable ||
+        from_is_always_boxed || to_is_always_boxed) {
+        if (to_dup != NULL && from_value != NULL) {
+            *(gpointer*) out_output = to_dup ((gpointer) from_value);
+        } else {
+            *(gpointer*) out_output = (gpointer) from_value;
+        }
+        return TRUE;
+    }
+    
+    /* For boxable value types, determine nullability from destroy_func */
+    gboolean from_is_nullable = (from_destroy != NULL);
+    gboolean to_is_nullable = (to_destroy != NULL);
+    
+    /* IMPORTANT: For non-nullable value types, Vala passes the value directly
+     * cast as a pointer (e.g., (gpointer)(gintptr)42), NOT a pointer to the value.
+     * For nullable types, it's a real pointer to heap memory. */
+    
+    /* Case: Both non-nullable - value is embedded in pointer, just copy it */
+    if (!from_is_nullable && !to_is_nullable) {
+        /* The value IS the pointer for non-nullable types */
+        *(gpointer*) out_output = (gpointer) from_value;
+        return TRUE;
+    }
+    
+    /* Case: Both nullable - copy pointer or null */
+    if (from_is_nullable && to_is_nullable) {
+        if (from_value == NULL) {
+            *(gpointer*) out_output = NULL;
+        } else if (to_dup != NULL) {
+            *(gpointer*) out_output = to_dup ((gpointer) from_value);
+        } else {
+            *(gpointer*) out_output = (gpointer) from_value;
+        }
+        return TRUE;
+    }
+    
+    /* Case: Non-nullable to nullable - box the value (value is in pointer) */
+    if (!from_is_nullable && to_is_nullable) {
+        if (to_dup != NULL) {
+            /* The from_value is the actual value cast to pointer, need to box it */
+            gsize size = _nullable_type_size (from_type);
+            gpointer boxed = g_malloc (size);
+            memcpy (boxed, &from_value, size);
+            *(gpointer*) out_output = boxed;
+        } else {
+            /* Shouldn't happen for nullable types, but handle it */
+            *(gpointer*) out_output = (gpointer) from_value;
+        }
+        return TRUE;
+    }
+    
+    /* Case: Nullable to non-nullable - unbox or default */
+    if (from_is_nullable && !to_is_nullable) {
+        if (from_value == NULL) {
+            /* Set to zeroed default, return false */
+            *(gpointer*) out_output = NULL;
+            return FALSE;
+        } else {
+            /* Unbox: the from_value is a real pointer to heap memory,
+             * copy the value from heap into the output pointer */
+            *(gpointer*) out_output = *(gpointer*) from_value;
+            return TRUE;
+        }
+    }
+    
+    /* Should not reach here */
+    return FALSE;
+}

+ 181 - 0
src/lib/Nullable.vala

@@ -0,0 +1,181 @@
+/* Nullable.vala - Production-ready nullable type detection and manipulation
+ * 
+ * This library provides functions to:
+ * - Detect if a generic type T is nullable (boxed) at runtime
+ * - Check if a nullable value is null
+ * - Get the raw value from a nullable (with default for null)
+ * - Create a boxed copy of a value
+ * 
+ * Usage:
+ *   using Nullable;
+ *   
+ *   int x = 42;
+ *   int? y = null;
+ *   
+ *   is_nullable(x);     // false
+ *   is_nullable(y);     // true
+ *   is_null(y);         // true
+ *   unbox(y, 0);        // 0 (default value since y is null)
+ *   box(x);             // creates a boxed copy
+ */
+
+namespace Invercargill.Nullable {
+    
+    /**
+     * Check if T is a boxed nullable value type (like int?)
+     *
+     * Returns true for nullable value types: int?, bool?, long?, etc.
+     * Returns false for: int, bool, string, Object, etc.
+     *
+     * This checks specifically for boxed value types - types that are
+     * normally stored unboxed but are boxed when nullable.
+     *
+     * Example:
+     *   int x = 42;
+     *   int? y = 42;
+     *   is_boxed_nullable(x);  // false
+     *   is_boxed_nullable(y);  // true
+     */
+    [CCode (cname = "is_boxed_nullable_impl")]
+    public extern bool is_boxed_nullable<T> (T value);
+    
+    /**
+     * Check if a value is null or represents a null state
+     *
+     * For reference types: returns true if value is null
+     * For nullable value types (int?, bool?, etc.): returns true if value is null
+     * For non-nullable value types (int, bool, etc.): returns true (cannot hold null, so "is nullable" in type sense)
+     *
+     * This is the inverse of the old is_non_nullable - it tells you if the type
+     * itself represents a nullable/non-nullable state.
+     *
+     * Example:
+     *   int x = 42;
+     *   int? y = null;
+     *   string? s = null;
+     *   is_nullable(x);  // true (non-nullable type, but "is nullable" in type sense)
+     *   is_nullable(y);  // true (y is null)
+     *   is_nullable(s);  // true (s is null)
+     */
+    [CCode (cname = "is_nullable_impl")]
+    public extern bool is_nullable<T> (T value);
+    
+    /**
+     * Check if T is a reference type (like string, Object)
+     * 
+     * Returns true for: string, Object, arrays, etc.
+     * Returns false for: int, int?, bool, bool?, etc.
+     */
+    [CCode (cname = "is_reference_impl")]
+    public extern bool is_reference<T> (T value);
+    
+    /**
+     * Check if a nullable value is null
+     * 
+     * For non-nullable value types, always returns false.
+     * For nullable types and reference types, checks if the value is null.
+     * 
+     * Example:
+     *   int? x = null;
+     *   int? y = 42;
+     *   is_null(x);  // true
+     *   is_null(y);  // false
+     */
+    [CCode (cname = "is_null_impl")]
+    public extern bool is_null<T> (T value);
+    
+    /**
+     * Get the value or a default if null (returns unowned reference)
+     * 
+     * Returns the value if not null, otherwise returns the default.
+     * 
+     * IMPORTANT: The returned value is NOT a copy - it's either the original
+     * value or the original default. Do NOT free it.
+     * 
+     * Example:
+     *   int? x = null;
+     *   unbox(x, 0);  // returns 0
+     *   int? y = 42;
+     *   unbox(y, 0);  // returns 42
+     */
+    [CCode (cname = "unbox_or_default_impl")]
+    public extern unowned T unbox<T> (T value, T default_value);
+    
+    /**
+     * Get the value or a default if null (alias for unbox)
+     */
+    [CCode (cname = "unbox_or_default_impl")]
+    public extern unowned T get_value<T> (T value, T default_value);
+    
+    /**
+     * Create a boxed copy of a value
+     * 
+     * For nullable value types, this creates a heap-allocated copy.
+     * For reference types, this creates a copy or increments reference count.
+     * 
+     * The caller owns the returned value and should free it with free_boxed()
+     * or the appropriate free function.
+     * 
+     * Example:
+     *   int x = 42;
+     *   var boxed = box(x);  // creates a boxed int
+     *   free_boxed(boxed);   // free it when done
+     */
+    [CCode (cname = "box_copy_impl")]
+    public extern T box<T> (T value);
+    
+    /**
+     * Free a boxed value created with box()
+     * 
+     * Frees a value created with box(). For reference types, this
+     * decrements the reference count or calls the appropriate free function.
+     */
+    [CCode (cname = "free_boxed_impl")]
+    public extern void free_boxed<T> (T value);
+    
+    /**
+     * Get a human-readable type name with nullability info
+     *
+     * Example:
+     *   int x = 42;
+     *   int? y = 42;
+     *   get_type_name(x);  // "gint (non-nullable)"
+     *   get_type_name(y);  // "gint? (nullable)"
+     */
+    [CCode (cname = "get_type_name_impl")]
+    public extern unowned string get_type_name<T> (T value);
+    
+    /**
+     * Convert between types with same GType but different nullability
+     *
+     * This function handles all combinations of nullable/non-nullable conversions
+     * for value types that share the same GType (e.g., int <-> int?).
+     *
+     * Returns true if conversion succeeded, false if source was null and target
+     * is non-nullable (output is set to default value in this case).
+     *
+     * Behavior matrix:
+     *   int -> int:     Direct copy, always returns true
+     *   int -> int?:    Box the value, always returns true
+     *   int? -> int:    Unbox if not null, returns false if null (output=0)
+     *   int? -> int?:   Copy pointer or null, always returns true
+     *
+     * Note: int64, uint64, float, double are always boxed in Vala generics,
+     * so they behave like reference types (pointer pass-through only).
+     *
+     * Example:
+     *   int? x = 42;
+     *   int y;
+     *   if (convert_nullability(x, out y)) {
+     *     // y is now 42
+     *   }
+     *
+     *   int? z = null;
+     *   int w;
+     *   if (!convert_nullability(z, out w)) {
+     *     // w is now 0 (default), z was null
+     *   }
+     */
+    [CCode (cname = "convert_nullability_impl")]
+    public extern bool convert_nullability<TFrom, TTo> (TFrom input, out TTo output);
+}

+ 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());
 }

+ 150 - 0
src/lib/Safety.vala

@@ -269,4 +269,154 @@ namespace Invercargill {
         return null;
     }
 
+    internal bool safely_unwrap_value<T>(Value value, out T result) {
+        var requested_type = typeof(T);
+
+        if(requested_type.is_a(Type.BOOLEAN)) {
+            result = (T)value.get_boolean();
+            return true;
+        }
+        if(requested_type.is_a(Type.BOXED)) {
+            result = (T)value.get_boxed();
+            return true;
+        }
+        if(requested_type.is_a(Type.CHAR)) {
+            result = (T)value.get_schar();
+            return true;
+        }
+        if(requested_type.is_a(Type.DOUBLE)) {
+            result = (T)((double?)value.get_double());
+            return true;
+        }
+        if(requested_type.is_a(Type.ENUM)) {
+            result = (T)value.get_enum();
+            return true;
+        }
+        if(requested_type.is_a(Type.FLAGS)) {
+            result = (T)value.get_flags();
+            return true;
+        }
+        if(requested_type.is_a(Type.FLOAT)) {
+            result = (T)((float?)value.get_float());
+            return true;
+        }
+        if(requested_type.is_a(Type.INT)) {
+            result = (T)value.get_int();
+            return true;
+        }
+        if(requested_type.is_a(Type.INT64)) {
+            result = (T)value.get_int64();
+            return true;
+        }
+        if(requested_type.is_a(Type.LONG)) {
+            result = (T)value.get_long();
+            return true;
+        }
+        if(requested_type.is_a(Type.OBJECT) || requested_type.is_a(Type.INTERFACE)) {
+            result = (T)value.get_object();
+            return true;
+        }
+        if(requested_type.is_a(Type.PARAM)) {
+            result = (T)value.get_param();
+            return true;
+        }
+        if(requested_type.is_a(Type.POINTER)) {
+            result = (T)value.get_pointer();
+            return true;
+        }
+        if(requested_type.is_a(Type.STRING)) {
+            result = (T)value.get_string();
+            return true;
+        }
+        if(requested_type.is_a(Type.UCHAR)) {
+            result = (T)value.get_uchar();
+            return true;
+        }
+        if(requested_type.is_a(Type.UINT)) {
+            result = (T)value.get_uint();
+            return true;
+        }
+        if(requested_type.is_a(Type.UINT64)) {
+            result = (T)value.get_uint64();
+            return true;
+        }
+        if(requested_type.is_a(Type.ULONG)) {
+            result = (T)value.get_ulong();
+            return true;
+        }
+
+        result = null;
+        return false;
+    }
+
+    internal bool safely_wrap_value<T>(T input, out Value value) {
+        var type = typeof(T);
+        value = Value(type);
+        if(type.is_a(Type.BOOLEAN)) {
+            value.set_boolean((bool)input);
+            return true;
+        }
+		if(type.is_a(Type.BOXED)) {
+            value.set_boxed(input);
+            return true;
+        }
+		if(type.is_a(Type.CHAR)) {
+            value.set_schar((int8)input);
+            return true;
+        }
+		if(type.is_a(Type.DOUBLE)) {
+            value.set_double((double?)input);
+            return true;
+        }
+		if(type.is_a(Type.ENUM)) {
+            value.set_enum((int)input);
+            return true;
+        }
+		if(type.is_a(Type.FLAGS)) {
+            value.set_flags((int)input);
+            return true;
+        }
+		if(type.is_a(Type.FLOAT)) {
+            value.set_float((float?)input);
+            return true;
+        }
+		if(type.is_a(Type.INT)) {
+            value.set_int((int)input);
+            return true;
+        }
+		if(type.is_a(Type.INT64)) {
+            value.set_int64((int64)input);
+            return true;
+        }
+		if(type.is_a(Type.LONG)) {
+            value.set_long((long)input);
+            return true;
+        }
+		if(type.is_a(Type.OBJECT) || type.is_a(Type.INTERFACE)) {
+            value.set_object((Object)input);
+            return true;
+        }
+		if(type.is_a(Type.STRING)) {
+            value.set_string((string)input);
+            return true;
+        }
+		if(type.is_a(Type.UCHAR)) {
+            value.set_uchar((uchar)input);
+            return true;
+        }
+		if(type.is_a(Type.UINT)) {
+            value.set_uint((uint)input);
+            return true;
+        }
+		if(type.is_a(Type.UINT64)) {
+            value.set_uint64((uint64)input);
+            return true;
+        }
+		if(type.is_a(Type.ULONG)) {
+            value.set_ulong((ulong)input);
+            return true;
+        }
+        return false;
+    }
+
 }

+ 53 - 1
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')
@@ -90,6 +94,10 @@ sources += files('Promotions/BinaryData.vala')
 sources += files('Promotions/Elements.vala')
 sources += files('Promotions/Registration.c')
 
+# Nullable type support (C implementation + Vala bindings)
+sources += files('Nullable.c')
+sources += files('Nullable.vala')
+
 sources += files('Interfaces/Lot.vala')
 sources += files('Interfaces/ImmutableLot.vala')
 sources += files('Interfaces/ReadOnlyCollection.vala')
@@ -133,12 +141,56 @@ 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/Convenience.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/IterateFunctionAccessor.vala')
+sources += files('Expressions/GlobalFunctionAccessor.vala')
+sources += files('Expressions/ExpressionTokenizer.vala')
+sources += files('Expressions/ExpressionParser.vala')
+sources += files('Expressions/AccessorFactory.vala')
+sources += files('Expressions/TypeAccessorRegistry.vala')
+sources += files('Expressions/StringPropertyAccessor.vala')
+sources += files('Expressions/LotPropertyAccessor.vala')
+sources += files('Expressions/StringFunctionAccessor.vala')
+sources += files('Expressions/DateTimeFunctionAccessor.vala')
+
+sources += files('Expressions/Expressions/LiteralExpression.vala')
+sources += files('Expressions/Expressions/VariableExpression.vala')
+sources += files('Expressions/Expressions/ParameterExpression.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/Expressions/LotLiteralExpression.vala')
+sources += files('Expressions/Expressions/GlobalFunctionCallExpression.vala')
+
+sources += files('Expressions/Elements/LambdaElement.vala')
+sources += files('Expressions/Elements/GlobalFunctionsElement.vala')
 
 invercargill = shared_library('invercargill-@0@'.format(invercargill_major), sources,
     dependencies: dependencies,
     install: true,
     vala_gir: 'invercargill-@0@.gir'.format(invercargill_major),
-    install_dir: [true, true, true, true]
+    install_dir: [true, true, true, true],
+    vala_vapi: 'invercargill-@0@.vapi'.format(invercargill_major),
+    vala_header: 'invercargill-@0@.h'.format(invercargill_major)
 )
 invercargill_dep = declare_dependency(link_with: invercargill, include_directories: include_directories('.'))
 

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

@@ -0,0 +1,2307 @@
+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);
+        
+        int64? count;
+        assert(result.try_get_as<int64?>(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);
+    });
+
+    // ==================== Lot Literal Tests ====================
+
+    Test.add_func("/invercargill/expressions/lot_literal_empty", () => {
+        var expr = new LotLiteralExpression(new Expression[0]);
+        var props = new PropertyDictionary();
+        var context = new EvaluationContext(props);
+        
+        var result = expr.evaluate(context);
+        assert(result != null);
+        
+        Enumerable<Object?>? lot;
+        assert(result.try_get_as<Enumerable<Object?>>(out lot));
+        assert(lot != null);
+        
+        var count = 0;
+        foreach (var item in lot) {
+            count++;
+        }
+        assert(count == 0);
+    });
+
+    Test.add_func("/invercargill/expressions/lot_literal_integers", () => {
+        var elements = new Expression[3];
+        elements[0] = new LiteralExpression(new NativeElement<int>(1));
+        elements[1] = new LiteralExpression(new NativeElement<int>(2));
+        elements[2] = new LiteralExpression(new NativeElement<int>(3));
+        
+        var expr = new LotLiteralExpression(elements);
+        var props = new PropertyDictionary();
+        var context = new EvaluationContext(props);
+        
+        var result = expr.evaluate(context);
+        assert(result != null);
+        
+        Enumerable<int>? lot;
+        assert(result.try_get_as<Enumerable<int>?>(out lot));
+        assert(lot != null);
+        
+        var values = new Series<int>();
+        foreach (var item in lot) {
+            values.add(item);
+        }
+        
+        assert(values.length == 3);
+        
+        var idx = 0;
+        foreach (var val in values) {
+            if (idx == 0) assert(val == 1);
+            else if (idx == 1) assert(val == 2);
+            else if (idx == 2) assert(val == 3);
+            idx++;
+        }
+    });
+
+    Test.add_func("/invercargill/expressions/lot_literal_strings", () => {
+        var elements = new Expression[3];
+        elements[0] = new LiteralExpression(new NativeElement<string>("a"));
+        elements[1] = new LiteralExpression(new NativeElement<string>("b"));
+        elements[2] = new LiteralExpression(new NativeElement<string>("c"));
+        
+        var expr = new LotLiteralExpression(elements);
+        var props = new PropertyDictionary();
+        var context = new EvaluationContext(props);
+        
+        var result = expr.evaluate(context);
+        assert(result != null);
+        
+        Enumerable<string?>? lot;
+        assert(result.try_get_as<Enumerable<string?>>(out lot));
+        assert(lot != null);
+        
+        var values = new Series<string?>();
+        foreach (var item in lot) {
+            values.add(item);
+        }
+        
+        assert(values.length == 3);
+    });
+
+    Test.add_func("/invercargill/expressions/lot_literal_parser", () => {
+        var expr = ExpressionParser.parse("[1, 2, 3]");
+        var props = new PropertyDictionary();
+        var context = new EvaluationContext(props);
+        
+        var result = expr.evaluate(context);
+        assert(result != null);
+        
+        Enumerable<int>? lot;
+        assert(result.try_get_as<Enumerable<int>?>(out lot));
+        assert(lot != null);
+        
+        var count = 0;
+        foreach (var item in lot) {
+            count++;
+        }
+        assert(count == 3);
+    });
+
+    // ==================== Iterate Function Tests ====================
+
+    Test.add_func("/invercargill/expressions/iterate_range", () => {
+        var accessor = new IterateFunctionAccessor();
+        var args = new Series<Element>();
+        args.add(new NativeElement<int>(0));
+        args.add(new NativeElement<int>(5));
+        
+        var result = accessor.call_function("range", args, new EvaluationContext(new PropertyDictionary()));
+        assert(result != null);
+        
+        Enumerable<int>? range;
+        assert(result.try_get_as<Enumerable<int>?>(out range));
+        assert(range != null);
+        
+        var values = new Series<int>();
+        foreach (var item in range) {
+            values.add(item);
+        }
+        
+        assert(values.length == 5);
+    });
+
+    Test.add_func("/invercargill/expressions/iterate_single", () => {
+        var accessor = new IterateFunctionAccessor();
+        var args = new Series<Element>();
+        args.add(new NativeElement<int>(42));
+        
+        var result = accessor.call_function("single", args, new EvaluationContext(new PropertyDictionary()));
+        assert(result != null);
+        
+        Enumerable<int>? single;
+        assert(result.try_get_as<Enumerable<int>?>(out single));
+        assert(single != null);
+        
+        var count = 0;
+        foreach (var item in single) {
+            assert(item == 42);
+            count++;
+        }
+        assert(count == 1);
+    });
+
+    Test.add_func("/invercargill/expressions/iterate_nothing", () => {
+        var accessor = new IterateFunctionAccessor();
+        var args = new Series<Element>();
+        
+        var result = accessor.call_function("nothing", args, new EvaluationContext(new PropertyDictionary()));
+        assert(result != null);
+        
+        Enumerable<Object?>? empty;
+        assert(result.try_get_as<Enumerable<Object?>>(out empty));
+        assert(empty != null);
+        
+        var count = 0;
+        foreach (var item in empty) {
+            count++;
+        }
+        assert(count == 0);
+    });
+
+    Test.add_func("/invercargill/expressions/iterate_range_with_step", () => {
+        var accessor = new IterateFunctionAccessor();
+        var args = new Series<Element>();
+        args.add(new NativeElement<int>(0));
+        args.add(new NativeElement<int>(10));
+        args.add(new NativeElement<int>(2));
+        
+        var result = accessor.call_function("range", args, new EvaluationContext(new PropertyDictionary()));
+        assert(result != null);
+        
+        Enumerable<int>? range;
+        assert(result.try_get_as<Enumerable<int>?>(out range));
+        assert(range != null);
+        
+        var values = new Series<int>();
+        foreach (var item in range) {
+            values.add(item);
+        }
+        
+        assert(values.length == 5); // 0, 2, 4, 6, 8
+    });
+
+    Test.add_func("/invercargill/expressions/global_functions_element", () => {
+        var accessor = new IterateFunctionAccessor();
+        var global_element = new GlobalFunctionsElement(accessor);
+        
+        var props = new PropertyDictionary();
+        props.set("Iterate", global_element);
+        var context = new EvaluationContext(props);
+        
+        // Get the variable
+        var expr = new VariableExpression("Iterate");
+        var result = expr.evaluate(context);
+        assert(result != null);
+        
+        GlobalFunctionsElement? retrieved;
+        assert(result.try_get_as<GlobalFunctionsElement?>(out retrieved));
+        assert(retrieved != null);
+        assert(retrieved.accessor is IterateFunctionAccessor);
+    });
+
+    // ==================== Global Format Function Tests ====================
+
+    Test.add_func("/invercargill/expressions/format_string", () => {
+        // format("Hello, {{name}}!")
+        var expr = ExpressionParser.parse("format(\"Hello, {{name}}!\")");
+        var props = new PropertyDictionary();
+        props.set("name", new NativeElement<string>("World"));
+        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, World!");
+    });
+
+    Test.add_func("/invercargill/expressions/format_integer", () => {
+        // format("Count: {{count}}")
+        var expr = ExpressionParser.parse("format(\"Count: {{count}}\")");
+        var props = new PropertyDictionary();
+        props.set("count", new NativeElement<int>(42));
+        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 == "Count: 42");
+    });
+
+    Test.add_func("/invercargill/expressions/format_multiple", () => {
+        // format("{{category}} #{{id}}: {{title}}")
+        var expr = ExpressionParser.parse("format(\"{{category}} #{{id}}: {{title}}\")");
+        var props = new PropertyDictionary();
+        props.set("category", new NativeElement<string>("Item"));
+        props.set("id", new NativeElement<int>(123));
+        props.set("title", new NativeElement<string>("Test Product"));
+        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 == "Item #123: Test Product");
+    });
+
+    Test.add_func("/invercargill/expressions/format_float", () => {
+        // format("Price: {{price}}")
+        var expr = ExpressionParser.parse("format(\"Price: {{price}}\")");
+        var props = new PropertyDictionary();
+        props.set("price", new NativeElement<double?>(19.99));
+        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 == "Price: 19.99");
+    });
+
+    Test.add_func("/invercargill/expressions/format_hex", () => {
+        // Note: Handlebars format doesn't support hex formatting natively
+        // Using stringify to convert the value
+        var expr = ExpressionParser.parse("format(\"Hex: {{value}}\")");
+        var props = new PropertyDictionary();
+        props.set("value", new NativeElement<int>(255));
+        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 == "Hex: 255");
+    });
+
+    Test.add_func("/invercargill/expressions/format_percent", () => {
+        // Test that \% produces literal % (escape sequence)
+        var expr = ExpressionParser.parse("format(\"100\\% complete\")");
+        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 == "100% complete");
+    });
+
+    Test.add_func("/invercargill/expressions/format_no_args", () => {
+        // format("Just a string")
+        var expr = ExpressionParser.parse("format(\"Just a string\")");
+        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 == "Just a string");
+    });
+
+    // ==================== Nested Property Access Parser Tests ====================
+
+    Test.add_func("/invercargill/expressions/parser_nested_property_simple", () => {
+        // Test: p.name (simple property access via parser - same as existing test but confirms it works)
+        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_nested_property_deep", () => {
+        // Test: container.min_rank (accessing property on a container object)
+        var persons = new Series<ExprTestPerson>();
+        persons.add(new ExprTestPerson("Alice", 30, 5));
+        var container = new ExprTestContainer(3, persons);
+        
+        var expr = ExpressionParser.parse("container.min_rank");
+        var props = new PropertyDictionary();
+        props.set("container", new NativeElement<ExprTestContainer?>(container));
+        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 == 3);
+    });
+
+    Test.add_func("/invercargill/expressions/parser_nested_property_with_comparison", () => {
+        // Test: p.age > 18 (property access in comparison)
+        var person = new ExprTestPerson("Bob", 25);
+        var expr = ExpressionParser.parse("p.age > 18");
+        var props = new PropertyDictionary();
+        props.set("p", new NativeElement<ExprTestPerson?>(person));
+        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_nested_property_in_lambda", () => {
+        // Test: items.where(i => i.rank > 2) - nested property in lambda
+        var persons = new Series<ExprTestPerson>();
+        persons.add(new ExprTestPerson("Alice", 30, 5));
+        persons.add(new ExprTestPerson("Bob", 25, 1));
+        persons.add(new ExprTestPerson("Charlie", 35, 3));
+        
+        var expr = ExpressionParser.parse("items.where(i => i.rank > 2)");
+        var props = new PropertyDictionary();
+        props.set("items", new NativeElement<Enumerable<ExprTestPerson>?>(persons));
+        var context = new EvaluationContext(props);
+        
+        var result = expr.evaluate(context);
+        assert(result != null);
+        
+        Elements elements;
+        assert(result.try_get_as<Elements>(out elements));
+        
+        var count = 0;
+        elements.iterate(e => count++);
+        assert(count == 2); // Alice (rank 5) and Charlie (rank 3)
+    });
+
+    Test.add_func("/invercargill/expressions/parser_nested_property_chained_functions", () => {
+        // Test: items.where(i => i.rank > 2).first().name
+        var persons = new Series<ExprTestPerson>();
+        persons.add(new ExprTestPerson("Alice", 30, 5));
+        persons.add(new ExprTestPerson("Bob", 25, 1));
+        persons.add(new ExprTestPerson("Charlie", 35, 3));
+        
+        var expr = ExpressionParser.parse("items.where(i => i.rank > 2).first().name");
+        var props = new PropertyDictionary();
+        props.set("items", new NativeElement<Enumerable<ExprTestPerson>?>(persons));
+        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"); // First item with rank > 2
+    });
+
+    Test.add_func("/invercargill/expressions/parser_nested_property_closure", () => {
+        // Test: items.where(i => i.rank > container.min_rank) - closure with nested property
+        var persons = new Series<ExprTestPerson>();
+        persons.add(new ExprTestPerson("Alice", 30, 5));
+        persons.add(new ExprTestPerson("Bob", 25, 1));
+        persons.add(new ExprTestPerson("Charlie", 35, 3));
+        var container = new ExprTestContainer(2, persons);
+        
+        var expr = ExpressionParser.parse("container.values.where(i => i.rank > container.min_rank)");
+        var props = new PropertyDictionary();
+        props.set("container", new NativeElement<ExprTestContainer?>(container));
+        var context = new EvaluationContext(props);
+        
+        var result = expr.evaluate(context);
+        assert(result != null);
+        
+        Elements elements;
+        assert(result.try_get_as<Elements>(out elements));
+        
+        var count = 0;
+        elements.iterate(e => count++);
+        assert(count == 2); // Alice (rank 5) and Charlie (rank 3), both > min_rank (2)
+    });
+
+    Test.add_func("/invercargill/expressions/parser_nested_property_ternary", () => {
+        // Test: p.age >= 18 ? p.name + " is adult" : p.name + " is minor"
+        var person = new ExprTestPerson("Bob", 25);
+        var expr = ExpressionParser.parse("p.age >= 18 ? p.name + \" is adult\" : p.name + \" is minor\"");
+        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 == "Bob is adult");
+    });
+
+    Test.add_func("/invercargill/expressions/parser_nested_property_arithmetic", () => {
+        // Test: p.age + 5 (property in arithmetic)
+        var person = new ExprTestPerson("Alice", 30);
+        var expr = ExpressionParser.parse("p.age + 5");
+        var props = new PropertyDictionary();
+        props.set("p", new NativeElement<ExprTestPerson?>(person));
+        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 == 35);
+    });
+
+    Test.add_func("/invercargill/expressions/parser_nested_property_boolean", () => {
+        // Test: p.is_important (boolean property)
+        var person = new ExprTestPerson("Important", 30, 5, true);
+        var expr = ExpressionParser.parse("p.is_important");
+        var props = new PropertyDictionary();
+        props.set("p", new NativeElement<ExprTestPerson?>(person));
+        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_nested_property_complex_expression", () => {
+        // Complex expression from the requirements:
+        // a.values.where(s => s.rank > a.min_rank).first().is_important
+        var persons = new Series<ExprTestPerson>();
+        persons.add(new ExprTestPerson("Alice", 30, 1, false));
+        persons.add(new ExprTestPerson("Bob", 25, 5, true));
+        persons.add(new ExprTestPerson("Charlie", 35, 3, false));
+        var container = new ExprTestContainer(2, persons);
+        
+        var expr = ExpressionParser.parse("a.values.where(s => s.rank > a.min_rank).first().is_important");
+        var props = new PropertyDictionary();
+        props.set("a", new NativeElement<ExprTestContainer?>(container));
+        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); // Bob has rank 5 > min_rank 2, and is_important = true
+    });
+
+    // ==================== String Property Accessor Tests ====================
+
+    Test.add_func("/invercargill/expressions/string_length", () => {
+        var expr = ExpressionParser.parse("s.length");
+        var props = new PropertyDictionary();
+        props.set("s", new NativeElement<string>("hello"));
+        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 == 5);
+    });
+
+    Test.add_func("/invercargill/expressions/string_empty", () => {
+        var expr = ExpressionParser.parse("s.empty");
+        var props = new PropertyDictionary();
+        props.set("s", new NativeElement<string>(""));
+        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/string_is_null", () => {
+        var expr = ExpressionParser.parse("s.is_null");
+        var props = new PropertyDictionary();
+        props.set("s", new NullElement());
+        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/string_is_empty_or_null", () => {
+        // Test with empty string
+        var expr = ExpressionParser.parse("s.is_empty_or_null");
+        var props = new PropertyDictionary();
+        props.set("s", new NativeElement<string>(""));
+        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);
+    });
+
+    // ==================== String Function Accessor Tests ====================
+
+    Test.add_func("/invercargill/expressions/string_chomp", () => {
+        var expr = ExpressionParser.parse("s.chomp()");
+        var props = new PropertyDictionary();
+        props.set("s", new NativeElement<string>("hello  "));
+        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/string_chug", () => {
+        var expr = ExpressionParser.parse("s.chug()");
+        var props = new PropertyDictionary();
+        props.set("s", new NativeElement<string>("  hello"));
+        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/string_upper", () => {
+        var expr = ExpressionParser.parse("s.upper()");
+        var props = new PropertyDictionary();
+        props.set("s", new NativeElement<string>("hello"));
+        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/string_lower", () => {
+        var expr = ExpressionParser.parse("s.lower()");
+        var props = new PropertyDictionary();
+        props.set("s", new NativeElement<string>("HELLO"));
+        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/string_contains", () => {
+        var expr = ExpressionParser.parse("s.contains(\"ell\")");
+        var props = new PropertyDictionary();
+        props.set("s", new NativeElement<string>("hello"));
+        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/string_has_prefix", () => {
+        var expr = ExpressionParser.parse("s.has_prefix(\"hel\")");
+        var props = new PropertyDictionary();
+        props.set("s", new NativeElement<string>("hello"));
+        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/string_has_suffix", () => {
+        var expr = ExpressionParser.parse("s.has_suffix(\"llo\")");
+        var props = new PropertyDictionary();
+        props.set("s", new NativeElement<string>("hello"));
+        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/string_replace", () => {
+        var expr = ExpressionParser.parse("s.replace(\"l\", \"x\")");
+        var props = new PropertyDictionary();
+        props.set("s", new NativeElement<string>("hello"));
+        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 == "hexxo");
+    });
+
+    Test.add_func("/invercargill/expressions/string_substring", () => {
+        var expr = ExpressionParser.parse("s.substring(1, 3)");
+        var props = new PropertyDictionary();
+        props.set("s", new NativeElement<string>("hello"));
+        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 == "ell");
+    });
+
+    Test.add_func("/invercargill/expressions/string_split", () => {
+        var expr = ExpressionParser.parse("s.split(\",\")");
+        var props = new PropertyDictionary();
+        props.set("s", new NativeElement<string>("a,b,c"));
+        var context = new EvaluationContext(props);
+        
+        var result = expr.evaluate(context);
+        assert(result != null);
+        
+        Enumerable<string?>? parts;
+        assert(result.try_get_as<Enumerable<string?>>(out parts));
+        assert(parts != null);
+        
+        var count = 0;
+        foreach (var part in parts) {
+            count++;
+        }
+        assert(count == 3);
+    });
+
+    Test.add_func("/invercargill/expressions/string_char_at", () => {
+        var expr = ExpressionParser.parse("s.char_at(1)");
+        var props = new PropertyDictionary();
+        props.set("s", new NativeElement<string>("hello"));
+        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 == "e");
+    });
+
+    Test.add_func("/invercargill/expressions/string_char_at_negative_index", () => {
+        // Test negative index (from end)
+        var expr = ExpressionParser.parse("s.char_at(-1)");
+        var props = new PropertyDictionary();
+        props.set("s", new NativeElement<string>("hello"));
+        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 == "o");
+    });
+
+    Test.add_func("/invercargill/expressions/string_char_at_out_of_bounds", () => {
+        // Test out of bounds returns null
+        var expr = ExpressionParser.parse("s.char_at(100)");
+        var props = new PropertyDictionary();
+        props.set("s", new NativeElement<string>("hello"));
+        var context = new EvaluationContext(props);
+        
+        var result = expr.evaluate(context);
+        assert(result != null);
+        assert(result.is_null());
+    });
+
+    Test.add_func("/invercargill/expressions/string_reverse", () => {
+        var expr = ExpressionParser.parse("s.reverse()");
+        var props = new PropertyDictionary();
+        props.set("s", new NativeElement<string>("hello"));
+        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 == "olleh");
+    });
+
+    Test.add_func("/invercargill/expressions/string_chained_operations", () => {
+        // Test chaining: s.chug().chomp().upper()
+        var expr = ExpressionParser.parse("s.chug().chomp().upper()");
+        var props = new PropertyDictionary();
+        props.set("s", new NativeElement<string>("  hello  "));
+        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/string_in_complex_expression", () => {
+        // Test string operations in a complex expression
+        // name.chug().chomp().length > 3
+        var expr = ExpressionParser.parse("name.chug().chomp().length > 3");
+        var props = new PropertyDictionary();
+        props.set("name", new NativeElement<string>("  hello  "));
+        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);
+    });
+
+    // ==================== Element.stringify() Tests ====================
+
+    Test.add_func("/invercargill/expressions/element_stringify_null", () => {
+        // Test null element → empty string
+        var element = new NullElement();
+        assert(element.stringify() == "");
+    });
+
+    Test.add_func("/invercargill/expressions/element_stringify_string", () => {
+        // Test string element → returns the string
+        var element = new NativeElement<string>("hello world");
+        assert(element.stringify() == "hello world");
+    });
+
+    Test.add_func("/invercargill/expressions/element_stringify_string_null", () => {
+        // Test null string element → empty string
+        var element = new NullElement();
+        assert(element.stringify() == "");
+    });
+
+    Test.add_func("/invercargill/expressions/element_stringify_int", () => {
+        // Test int element → decimal representation
+        var element = new NativeElement<int>(42);
+        assert(element.stringify() == "42");
+    });
+
+    Test.add_func("/invercargill/expressions/element_stringify_int_negative", () => {
+        // Test negative int element → decimal representation
+        var element = new NativeElement<int>(-17);
+        assert(element.stringify() == "-17");
+    });
+
+    Test.add_func("/invercargill/expressions/element_stringify_long", () => {
+        // Test long element → decimal representation
+        var element = new NativeElement<long>(123456789L);
+        assert(element.stringify() == "123456789");
+    });
+
+    Test.add_func("/invercargill/expressions/element_stringify_int64", () => {
+        // Test int64 element → decimal representation
+        var element = new NativeElement<int64?>(9876543210LL);
+        assert(element.stringify() == "9876543210");
+    });
+
+    Test.add_func("/invercargill/expressions/element_stringify_double", () => {
+        // Test double element → decimal representation
+        var element = new NativeElement<double?>(3.5);
+        assert(element.stringify() == "3.5");
+    });
+
+    Test.add_func("/invercargill/expressions/element_stringify_float", () => {
+        // Test float element → decimal representation
+        var element = new NativeElement<float?>(2.5f);
+        assert(element.stringify() == "2.5");
+    });
+
+    Test.add_func("/invercargill/expressions/element_stringify_bool_true", () => {
+        // Test bool element true → "true"
+        var element = new NativeElement<bool>(true);
+        assert(element.stringify() == "true");
+    });
+
+    Test.add_func("/invercargill/expressions/element_stringify_bool_false", () => {
+        // Test bool element false → "false"
+        var element = new NativeElement<bool>(false);
+        assert(element.stringify() == "false");
+    });
+
+    Test.add_func("/invercargill/expressions/element_stringify_object_fallback", () => {
+        // Test other types → falls back to to_string()
+        var person = new ExprTestPerson("Alice", 30);
+        var element = new NativeElement<ExprTestPerson?>(person);
+        // The default to_string() returns "Element[type_name()]"
+        assert(element.stringify().contains("ExprTestPerson"));
+    });
+
+    // ==================== Handlebars format() Function Tests ====================
+
+    Test.add_func("/invercargill/expressions/format_handlebars_simple", () => {
+        // Simple variable interpolation: format("Hello, {{name}}!") with name="World"
+        var expr = ExpressionParser.parse("format(\"Hello, {{name}}!\")");
+        var props = new PropertyDictionary();
+        props.set("name", new NativeElement<string>("World"));
+        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, World!");
+    });
+
+    Test.add_func("/invercargill/expressions/format_handlebars_multiple", () => {
+        // Multiple interpolations: format("{{a}} + {{b}} = {{c}}")
+        var expr = ExpressionParser.parse("format(\"{{a}} + {{b}} = {{c}}\")");
+        var props = new PropertyDictionary();
+        props.set("a", new NativeElement<int>(2));
+        props.set("b", new NativeElement<int>(3));
+        props.set("c", new NativeElement<int>(5));
+        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 == "2 + 3 = 5");
+    });
+
+    Test.add_func("/invercargill/expressions/format_handlebars_expression", () => {
+        // Expression evaluation: format("Sum: {{a + b}}")
+        var expr = ExpressionParser.parse("format(\"Sum: {{a + b}}\")");
+        var props = new PropertyDictionary();
+        props.set("a", new NativeElement<int>(10));
+        props.set("b", new NativeElement<int>(32));
+        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 == "Sum: 42");
+    });
+
+    Test.add_func("/invercargill/expressions/format_handlebars_property_access", () => {
+        // Property access: format("User: {{user.name}}")
+        var person = new ExprTestPerson("Alice", 30);
+        var expr = ExpressionParser.parse("format(\"User: {{user.name}}, Age: {{user.age}}\")");
+        var props = new PropertyDictionary();
+        props.set("user", 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 == "User: Alice, Age: 30");
+    });
+
+    Test.add_func("/invercargill/expressions/format_handlebars_escape_braces", () => {
+        // Escape sequences: format("Literal: \\{{name}}") → "Literal: {{name}}"
+        // In Vala source: "\\\\{{" -> runtime string: "\\{{" -> parsed string: "\{{" -> format outputs: "{{"
+        var expr = ExpressionParser.parse("format(\"Literal: \\\\{{name}}\")");
+        var props = new PropertyDictionary();
+        props.set("name", new NativeElement<string>("World"));
+        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 == "Literal: {{name}}");
+    });
+
+    Test.add_func("/invercargill/expressions/format_handlebars_escape_backslash", () => {
+        // Escape backslash: format("Path: C:\\\\Users") → "Path: C:\Users"
+        // In Vala source: "\\\\\\\\" -> runtime string: "\\\\" -> parsed string: "\\" -> format outputs: "\"
+        // To get a literal backslash in the format output, we need "\\" in the parsed string
+        var expr = ExpressionParser.parse("format(\"Path: C:\\\\\\\\Users\")");
+        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 == "Path: C:\\Users");
+    });
+
+    Test.add_func("/invercargill/expressions/format_handlebars_empty_expression", () => {
+        // Empty expression: format("Empty: {{}}") → "Empty: "
+        var expr = ExpressionParser.parse("format(\"Empty: {{}}\")");
+        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 == "Empty: ");
+    });
+
+    Test.add_func("/invercargill/expressions/format_handlebars_whitespace_in_expression", () => {
+        // Whitespace in expression should be trimmed
+        var expr = ExpressionParser.parse("format(\"Value: {{  name  }}\")");
+        var props = new PropertyDictionary();
+        props.set("name", new NativeElement<string>("test"));
+        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 == "Value: test");
+    });
+
+    Test.add_func("/invercargill/expressions/format_handlebars_bool", () => {
+        // Boolean interpolation
+        var expr = ExpressionParser.parse("format(\"Is active: {{active}}\")");
+        var props = new PropertyDictionary();
+        props.set("active", new NativeElement<bool>(true));
+        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 == "Is active: true");
+    });
+
+    Test.add_func("/invercargill/expressions/format_handlebars_null", () => {
+        // Null interpolation → empty string
+        var expr = ExpressionParser.parse("format(\"Value: '{{value}}'\")");
+        var props = new PropertyDictionary();
+        props.set("value", new NullElement());
+        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 == "Value: ''");
+    });
+
+    Test.add_func("/invercargill/expressions/format_handlebars_no_placeholders", () => {
+        // Template with no placeholders
+        var expr = ExpressionParser.parse("format(\"Just a plain string\")");
+        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 == "Just a plain string");
+    });
+
+    Test.add_func("/invercargill/expressions/format_handlebars_ternary", () => {
+        // Ternary expression in handlebars
+        var expr = ExpressionParser.parse("format(\"Status: {{active ? \\\"active\\\" : \\\"inactive\\\"}}\")");
+        var props = new PropertyDictionary();
+        props.set("active", new NativeElement<bool>(true));
+        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 == "Status: active");
+    });
+
+    // ==================== stringify() Global Function Tests ====================
+
+    Test.add_func("/invercargill/expressions/stringify_single_int", () => {
+        // Single argument: stringify(42) → "42"
+        var expr = ExpressionParser.parse("stringify(42)");
+        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 == "42");
+    });
+
+    Test.add_func("/invercargill/expressions/stringify_single_string", () => {
+        // Single string argument: stringify("hello") → "hello"
+        var expr = ExpressionParser.parse("stringify(\"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/stringify_multiple_strings", () => {
+        // Multiple arguments: stringify("Hello", " ", "World") → "Hello World"
+        var expr = ExpressionParser.parse("stringify(\"Hello\", \" \", \"World\")");
+        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 World");
+    });
+
+    Test.add_func("/invercargill/expressions/stringify_mixed_types", () => {
+        // Mixed types: stringify(1, " + ", 2, " = ", 3) → "1 + 2 = 3"
+        var expr = ExpressionParser.parse("stringify(1, \" + \", 2, \" = \", 3)");
+        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 == "1 + 2 = 3");
+    });
+
+    Test.add_func("/invercargill/expressions/stringify_no_arguments", () => {
+        // No arguments: stringify() → ""
+        var expr = ExpressionParser.parse("stringify()");
+        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 == "");
+    });
+
+    Test.add_func("/invercargill/expressions/stringify_null", () => {
+        // Null argument: stringify(null) → ""
+        var expr = ExpressionParser.parse("stringify(null)");
+        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 == "");
+    });
+
+    Test.add_func("/invercargill/expressions/stringify_bool", () => {
+        // Boolean arguments: stringify(true, " or ", false) → "true or false"
+        var expr = ExpressionParser.parse("stringify(true, \" or \", false)");
+        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 == "true or false");
+    });
+
+    Test.add_func("/invercargill/expressions/stringify_variable", () => {
+        // Stringify with variable
+        var expr = ExpressionParser.parse("stringify(\"The answer is: \", answer)");
+        var props = new PropertyDictionary();
+        props.set("answer", new NativeElement<int>(42));
+        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 == "The answer is: 42");
+    });
+
+    Test.add_func("/invercargill/expressions/stringify_expression", () => {
+        // Stringify with expression evaluation
+        var expr = ExpressionParser.parse("stringify(\"Sum: \", 2 + 3)");
+        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 == "Sum: 5");
+    });
+
+    Test.add_func("/invercargill/expressions/stringify_float", () => {
+        // Stringify floating point number
+        var expr = ExpressionParser.parse("stringify(3.5)");
+        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 == "3.5");
+    });
+
+    // ==================== Lot Length Formatting Bug Tests ====================
+    // BUG: format strings referencing lot.length print large random numbers
+    // Root cause: NativeElement.try_get_as<TOut>() generic value type casts
+    // may read garbage memory when T=uint
+
+    Test.add_func("/invercargill/expressions/format_lot_length_bug", () => {
+        // Create a Vector with 3 elements (known length)
+        var vector = new Vector<int>();
+        vector.add(1);
+        vector.add(2);
+        vector.add(3);
+        
+        // Use format string with {{lot.length}}
+        var expr = ExpressionParser.parse("format(\"Length: {{lot.length}}\")");
+        var props = new PropertyDictionary();
+        props.set("lot", new NativeElement<Lot<int>?>(vector));
+        var context = new EvaluationContext(props);
+        
+        var result = expr.evaluate(context);
+        assert(result != null);
+        
+        string value;
+        assert(result.try_get_as<string>(out value));
+        // This assertion should fail due to the bug - it will show a large
+        // random number instead of "3"
+        assert(value == "Length: 3");
+    });
+
+    // ==================== Parameterized Expression Tests ====================
+
+    Test.add_func("/invercargill/expressions/parameter_single", () => {
+        // Single parameter: $0 > 5
+        var params = new Series<Element>();
+        params.add(new NativeElement<int>(10));
+        var expr = ExpressionParser.parse_with_params("$0 > 5", params);
+        var context = new EvaluationContext(new PropertyDictionary());
+        var result = expr.evaluate(context);
+        
+        bool value;
+        assert(result.try_get_as<bool>(out value));
+        assert(value == true);
+    });
+
+    Test.add_func("/invercargill/expressions/parameter_multiple", () => {
+        // Multiple parameters: $0 + $1
+        var params = new Series<Element>();
+        params.add(new NativeElement<int>(3));
+        params.add(new NativeElement<int>(4));
+        var expr = ExpressionParser.parse_with_params("$0 + $1", params);
+        var context = new EvaluationContext(new PropertyDictionary());
+        var result = expr.evaluate(context);
+        
+        int64? value;
+        assert(result.try_get_as<int64?>(out value));
+        assert(value == 7);
+    });
+
+    Test.add_func("/invercargill/expressions/parameter_with_variable", () => {
+        // Parameter with variable: x > $0
+        var params = new Series<Element>();
+        params.add(new NativeElement<int>(5));
+        var expr = ExpressionParser.parse_with_params("x > $0", params);
+        var props = new PropertyDictionary();
+        props.set("x", new NativeElement<int>(10));
+        var context = new EvaluationContext(props);
+        var result = expr.evaluate(context);
+        
+        bool value;
+        assert(result.try_get_as<bool>(out value));
+        assert(value == true);
+    });
+
+    Test.add_func("/invercargill/expressions/parameter_string", () => {
+        // String parameter: $0 == "hello"
+        var params = new Series<Element>();
+        params.add(new NativeElement<string>("hello"));
+        var expr = ExpressionParser.parse_with_params("$0 == \"hello\"", params);
+        var context = new EvaluationContext(new PropertyDictionary());
+        var result = expr.evaluate(context);
+        
+        bool value;
+        assert(result.try_get_as<bool>(out value));
+        assert(value == true);
+    });
+
+    Test.add_func("/invercargill/expressions/parameter_complex_object", () => {
+        // Complex object parameter: $0.name
+        var person = new ExprTestPerson("Alice", 30);
+        var params = new Series<Element>();
+        params.add(new NativeElement<ExprTestPerson>(person));
+        var expr = ExpressionParser.parse_with_params("$0.name", params);
+        var context = new EvaluationContext(new PropertyDictionary());
+        var result = expr.evaluate(context);
+        
+        string value;
+        assert(result.try_get_as<string>(out value));
+        assert(value == "Alice");
+    });
+
+    Test.add_func("/invercargill/expressions/parameter_in_ternary", () => {
+        // Parameter in ternary: $0 ? "yes" : "no"
+        var params = new Series<Element>();
+        params.add(new NativeElement<bool>(true));
+        var expr = ExpressionParser.parse_with_params("$0 ? \"yes\" : \"no\"", params);
+        var context = new EvaluationContext(new PropertyDictionary());
+        var result = expr.evaluate(context);
+        
+        string value;
+        assert(result.try_get_as<string>(out value));
+        assert(value == "yes");
+    });
+
+    Test.add_func("/invercargill/expressions/parameter_in_lambda", () => {
+        // Parameter in lambda: items.where(i => i.rank > $0)
+        var persons = new Series<ExprTestPerson>();
+        persons.add(new ExprTestPerson("Alice", 30, 5));
+        persons.add(new ExprTestPerson("Bob", 25, 1));
+        persons.add(new ExprTestPerson("Charlie", 35, 3));
+        
+        var params = new Series<Element>();
+        params.add(new NativeElement<int>(2));
+        var expr = ExpressionParser.parse_with_params("items.where(i => i.rank > $0)", params);
+        var props = new PropertyDictionary();
+        props.set("items", new NativeElement<Enumerable<ExprTestPerson>?>(persons));
+        var context = new EvaluationContext(props);
+        var result = expr.evaluate(context);
+        
+        Elements elements;
+        assert(result.try_get_as<Elements>(out elements));
+        
+        var count = 0;
+        elements.iterate(e => count++);
+        assert(count == 2); // Alice (rank 5) and Charlie (rank 3)
+    });
+
+    Test.add_func("/invercargill/expressions/parameter_range_check", () => {
+        // Range check with two parameters: $0 <= x && x <= $1
+        var params = new Series<Element>();
+        params.add(new NativeElement<int>(0));
+        params.add(new NativeElement<int>(100));
+        var expr = ExpressionParser.parse_with_params("$0 <= x && x <= $1", params);
+        var props = new PropertyDictionary();
+        props.set("x", new NativeElement<int>(50));
+        var context = new EvaluationContext(props);
+        var result = expr.evaluate(context);
+        
+        bool value;
+        assert(result.try_get_as<bool>(out value));
+        assert(value == true);
+    });
+
+    Test.add_func("/invercargill/expressions/parameter_expression_string", () => {
+        // Test to_expression_string returns $N format
+        var expr = new ParameterExpression(0, new NativeElement<int>(42));
+        assert(expr.to_expression_string() == "$0");
+    });
+
+    Test.add_func("/invercargill/expressions/parameter_expression_type", () => {
+        // Test expression_type is PARAMETER
+        var expr = new ParameterExpression(0, new NativeElement<int>(42));
+        assert(expr.expression_type == ExpressionType.PARAMETER);
+    });
+}

+ 964 - 0
src/tests/Integration/NativeElement.vala

@@ -0,0 +1,964 @@
+using Invercargill;
+using Invercargill.Nullable;
+
+// Test helper class for reference type testing
+private class NativeElementTestObject : Object {
+    public int value { get; set; }
+    public NativeElementTestObject(int value) {
+        this.value = value;
+    }
+}
+
+// Test helper class implementing Stringifyable
+private class NativeElementTestStringifyable : Object, Stringifyable {
+    public string data { get; set; }
+    public NativeElementTestStringifyable(string data) {
+        this.data = data;
+    }
+    public string to_string() {
+        return @"Stringifyable[$data]";
+    }
+}
+
+void native_element_tests() {
+
+    // ============================================================================
+    // NativeElement Construction Tests
+    // ============================================================================
+
+    Test.add_func("/invercargill/native_element/construction/int_non_nullable", () => {
+        var elem = new NativeElement<int>(42);
+        assert_true(elem != null);
+        assert_true(elem.is_reference_type == false);
+        assert_true(elem.is_nullable == false);
+        assert_true(elem.is_null() == false);
+    });
+
+    Test.add_func("/invercargill/native_element/construction/int_nullable_with_value", () => {
+        int? x = 42;
+        var elem = new NativeElement<int?>(x);
+        assert_true(elem != null);
+        assert_true(elem.is_reference_type == false);
+        assert_true(elem.is_nullable == true);
+        assert_true(elem.is_null() == false);
+    });
+
+    Test.add_func("/invercargill/native_element/construction/int_nullable_null", () => {
+        int? x = null;
+        var elem = new NativeElement<int?>(x);
+        assert_true(elem != null);
+        assert_true(elem.is_reference_type == false);
+        assert_true(elem.is_nullable == true);
+        assert_true(elem.is_null() == true);
+    });
+
+    Test.add_func("/invercargill/native_element/construction/bool_non_nullable", () => {
+        var elem = new NativeElement<bool>(true);
+        assert_true(elem != null);
+        assert_true(elem.is_reference_type == false);
+        assert_true(elem.is_nullable == false);
+        assert_true(elem.is_null() == false);
+    });
+
+    Test.add_func("/invercargill/native_element/construction/bool_nullable_with_value", () => {
+        bool? x = false;
+        var elem = new NativeElement<bool?>(x);
+        assert_true(elem != null);
+        assert_true(elem.is_reference_type == false);
+        assert_true(elem.is_nullable == true);
+        assert_true(elem.is_null() == false);
+    });
+
+    Test.add_func("/invercargill/native_element/construction/bool_nullable_null", () => {
+        bool? x = null;
+        var elem = new NativeElement<bool?>(x);
+        assert_true(elem != null);
+        assert_true(elem.is_reference_type == false);
+        assert_true(elem.is_nullable == true);
+        assert_true(elem.is_null() == true);
+    });
+
+    Test.add_func("/invercargill/native_element/construction/string_non_nullable", () => {
+        var elem = new NativeElement<string>("hello");
+        assert_true(elem != null);
+        assert_true(elem.is_reference_type == true);
+        assert_true(elem.is_nullable == true);  // string is always nullable
+        assert_true(elem.is_null() == false);
+    });
+
+    Test.add_func("/invercargill/native_element/construction/string_nullable_with_value", () => {
+        string? s = "world";
+        var elem = new NativeElement<string?>(s);
+        assert_true(elem != null);
+        assert_true(elem.is_reference_type == true);
+        assert_true(elem.is_nullable == true);
+        assert_true(elem.is_null() == false);
+    });
+
+    Test.add_func("/invercargill/native_element/construction/string_nullable_null", () => {
+        string? s = null;
+        var elem = new NativeElement<string?>(s);
+        assert_true(elem != null);
+        assert_true(elem.is_reference_type == true);
+        assert_true(elem.is_nullable == true);
+        assert_true(elem.is_null() == true);
+    });
+
+    Test.add_func("/invercargill/native_element/construction/object_non_nullable", () => {
+        var obj = new NativeElementTestObject(42);
+        var elem = new NativeElement<NativeElementTestObject>(obj);
+        assert_true(elem != null);
+        assert_true(elem.is_reference_type == true);
+        assert_true(elem.is_nullable == true);  // Objects are always nullable
+        assert_true(elem.is_null() == false);
+    });
+
+    Test.add_func("/invercargill/native_element/construction/object_nullable_with_value", () => {
+        NativeElementTestObject? obj = new NativeElementTestObject(99);
+        var elem = new NativeElement<NativeElementTestObject?>(obj);
+        assert_true(elem != null);
+        assert_true(elem.is_reference_type == true);
+        assert_true(elem.is_nullable == true);
+        assert_true(elem.is_null() == false);
+    });
+
+    Test.add_func("/invercargill/native_element/construction/object_nullable_null", () => {
+        NativeElementTestObject? obj = null;
+        var elem = new NativeElement<NativeElementTestObject?>(obj);
+        assert_true(elem != null);
+        assert_true(elem.is_reference_type == true);
+        assert_true(elem.is_nullable == true);
+        assert_true(elem.is_null() == true);
+    });
+
+    // ============================================================================
+    // type() method tests
+    // ============================================================================
+
+    Test.add_func("/invercargill/native_element/type/int_returns_int_type", () => {
+        var elem = new NativeElement<int>(42);
+        assert_true(elem.type() == typeof(int));
+    });
+
+    Test.add_func("/invercargill/native_element/type/int_nullable_returns_int_type", () => {
+        int? x = 42;
+        var elem = new NativeElement<int?>(x);
+        assert_true(elem.type() == typeof(int?));
+    });
+
+    Test.add_func("/invercargill/native_element/type/string_returns_string_type", () => {
+        var elem = new NativeElement<string>("hello");
+        assert_true(elem.type() == typeof(string));
+    });
+
+    Test.add_func("/invercargill/native_element/type/object_returns_object_type", () => {
+        var obj = new NativeElementTestObject(42);
+        var elem = new NativeElement<NativeElementTestObject>(obj);
+        assert_true(elem.type() == typeof(NativeElementTestObject));
+    });
+
+    // ============================================================================
+    // type_name() method tests
+    // ============================================================================
+
+    Test.add_func("/invercargill/native_element/type_name/int_type_name", () => {
+        var elem = new NativeElement<int>(42);
+        var name = elem.type_name();
+        assert_true(name != null);
+        assert_true("int" in name || "gint" in name);
+    });
+
+    Test.add_func("/invercargill/native_element/type_name/string_type_name", () => {
+        var elem = new NativeElement<string>("hello");
+        var name = elem.type_name();
+        assert_true(name != null);
+    });
+
+    Test.add_func("/invercargill/native_element/type_name/object_type_name", () => {
+        var obj = new NativeElementTestObject(42);
+        var elem = new NativeElement<NativeElementTestObject>(obj);
+        var name = elem.type_name();
+        assert_true(name != null);
+        assert_true("NativeElementTestObject" in name);
+    });
+
+    // ============================================================================
+    // is_type() method tests
+    // ============================================================================
+
+    Test.add_func("/invercargill/native_element/is_type/int_is_int", () => {
+        var elem = new NativeElement<int>(42);
+        assert_true(elem.is_type(typeof(int)) == true);
+        assert_true(elem.is_type(typeof(string)) == false);
+    });
+
+    Test.add_func("/invercargill/native_element/is_type/object_is_correct_type", () => {
+        var obj = new NativeElementTestObject(42);
+        var elem = new NativeElement<NativeElementTestObject>(obj);
+        assert_true(elem.is_type(typeof(NativeElementTestObject)) == true);
+        assert_true(elem.is_type(typeof(Object)) == true);  // Inheritance
+        assert_true(elem.is_type(typeof(string)) == false);
+    });
+
+    // ============================================================================
+    // assignable_to_type() tests
+    // ============================================================================
+
+    Test.add_func("/invercargill/native_element/assignable_to_type/int_assignable_to_int", () => {
+        var elem = new NativeElement<int>(42);
+        assert_true(elem.assignable_to_type(typeof(int)) == true);
+        assert_true(elem.assignable_to_type(typeof(string)) == false);
+    });
+
+    Test.add_func("/invercargill/native_element/assignable_to_type/object_assignable_to_object", () => {
+        var obj = new NativeElementTestObject(42);
+        var elem = new NativeElement<NativeElementTestObject>(obj);
+        assert_true(elem.assignable_to_type(typeof(NativeElementTestObject)) == true);
+        assert_true(elem.assignable_to_type(typeof(Object)) == true);
+    });
+
+    // ============================================================================
+    // assignable_to<T>() tests
+    // ============================================================================
+
+    Test.add_func("/invercargill/native_element/assignable_to/int_assignable_to_int", () => {
+        var elem = new NativeElement<int>(42);
+        assert_true(elem.assignable_to<int>() == true);
+        assert_true(elem.assignable_to<string>() == false);
+    });
+
+    Test.add_func("/invercargill/native_element/assignable_to/object_assignable_to_object", () => {
+        var obj = new NativeElementTestObject(42);
+        var elem = new NativeElement<NativeElementTestObject>(obj);
+        assert_true(elem.assignable_to<NativeElementTestObject>() == true);
+        assert_true(elem.assignable_to<Object>() == true);
+    });
+
+    // ============================================================================
+    // @is<T>() tests
+    // ============================================================================
+
+    Test.add_func("/invercargill/native_element/is/int_is_int", () => {
+        var elem = new NativeElement<int>(42);
+        assert_true(elem.is<int>() == true);
+        assert_true(elem.is<string>() == false);
+    });
+
+    Test.add_func("/invercargill/native_element/is/object_is_correct_type", () => {
+        var obj = new NativeElementTestObject(42);
+        var elem = new NativeElement<NativeElementTestObject>(obj);
+        assert_true(elem.is<NativeElementTestObject>() == true);
+        assert_true(elem.is<Object>() == true);
+        assert_true(elem.is<string>() == false);
+    });
+
+    // ============================================================================
+    // try_get_as<T>() tests - non-nullable value types
+    // ============================================================================
+
+    Test.add_func("/invercargill/native_element/try_get_as/int_to_int", () => {
+        var elem = new NativeElement<int>(42);
+        int result;
+        assert_true(elem.try_get_as<int>(out result) == true);
+        assert_true(result == 42);
+    });
+
+    Test.add_func("/invercargill/native_element/try_get_as/int_to_int_zero", () => {
+        var elem = new NativeElement<int>(0);
+        int result;
+        assert_true(elem.try_get_as<int>(out result) == true);
+        assert_true(result == 0);
+    });
+
+    Test.add_func("/invercargill/native_element/try_get_as/int_to_int_negative", () => {
+        var elem = new NativeElement<int>(-42);
+        int result;
+        assert_true(elem.try_get_as<int>(out result) == true);
+        assert_true(result == -42);
+    });
+
+    Test.add_func("/invercargill/native_element/try_get_as/bool_to_bool_true", () => {
+        var elem = new NativeElement<bool>(true);
+        bool result;
+        assert_true(elem.try_get_as<bool>(out result) == true);
+        assert_true(result == true);
+    });
+
+    Test.add_func("/invercargill/native_element/try_get_as/bool_to_bool_false", () => {
+        var elem = new NativeElement<bool>(false);
+        bool result;
+        assert_true(elem.try_get_as<bool>(out result) == true);
+        assert_true(result == false);
+    });
+
+    Test.add_func("/invercargill/native_element/try_get_as/long_to_long", () => {
+        var elem = new NativeElement<long>(123456789L);
+        long result;
+        assert_true(elem.try_get_as<long>(out result) == true);
+        assert_true(result == 123456789L);
+    });
+
+    // ============================================================================
+    // try_get_as<T>() tests - nullable value types with values
+    // ============================================================================
+
+    Test.add_func("/invercargill/native_element/try_get_as/int_nullable_to_int", () => {
+        int? x = 42;
+        var elem = new NativeElement<int?>(x);
+        int result;
+        assert_true(elem.try_get_as<int>(out result) == true);
+        assert_true(result == 42);
+    });
+
+    Test.add_func("/invercargill/native_element/try_get_as/int_nullable_to_int_nullable", () => {
+        int? x = 42;
+        var elem = new NativeElement<int?>(x);
+        int? result;
+        assert_true(elem.try_get_as<int?>(out result) == true);
+        assert_true(result != null);
+        assert_true(result == 42);
+    });
+
+    Test.add_func("/invercargill/native_element/try_get_as/bool_nullable_to_bool", () => {
+        bool? x = true;
+        var elem = new NativeElement<bool?>(x);
+        bool result;
+        assert_true(elem.try_get_as<bool>(out result) == true);
+        assert_true(result == true);
+    });
+
+    Test.add_func("/invercargill/native_element/try_get_as/bool_nullable_to_bool_nullable", () => {
+        bool? x = false;
+        var elem = new NativeElement<bool?>(x);
+        bool? result;
+        assert_true(elem.try_get_as<bool?>(out result) == true);
+        assert_true(result != null);
+        assert_true(result == false);
+    });
+
+    // ============================================================================
+    // try_get_as<T>() tests - nullable value types with null
+    // ============================================================================
+
+    Test.add_func("/invercargill/native_element/try_get_as/int_nullable_null_to_int_fails", () => {
+        int? x = null;
+        var elem = new NativeElement<int?>(x);
+        int result;
+        // Should fail because null cannot be converted to non-nullable int
+        assert_true(elem.try_get_as<int>(out result) == false);
+    });
+
+    Test.add_func("/invercargill/native_element/try_get_as/int_nullable_null_to_int_nullable", () => {
+        int? x = null;
+        var elem = new NativeElement<int?>(x);
+        int? result;
+        assert_true(elem.try_get_as<int?>(out result) == true);
+        assert_true(result == null);
+    });
+
+    Test.add_func("/invercargill/native_element/try_get_as/bool_nullable_null_to_bool_fails", () => {
+        bool? x = null;
+        var elem = new NativeElement<bool?>(x);
+        bool result;
+        // Should fail because null cannot be converted to non-nullable bool
+        assert_true(elem.try_get_as<bool>(out result) == false);
+    });
+
+    Test.add_func("/invercargill/native_element/try_get_as/bool_nullable_null_to_bool_nullable", () => {
+        bool? x = null;
+        var elem = new NativeElement<bool?>(x);
+        bool? result;
+        assert_true(elem.try_get_as<bool?>(out result) == true);
+        assert_true(result == null);
+    });
+
+    // ============================================================================
+    // try_get_as<T>() tests - reference types
+    // ============================================================================
+
+    Test.add_func("/invercargill/native_element/try_get_as/string_to_string", () => {
+        var elem = new NativeElement<string>("hello");
+        string result;
+        assert_true(elem.try_get_as<string>(out result) == true);
+        assert_true(result == "hello");
+    });
+
+    Test.add_func("/invercargill/native_element/try_get_as/string_nullable_to_string", () => {
+        string? s = "world";
+        var elem = new NativeElement<string?>(s);
+        string result;
+        assert_true(elem.try_get_as<string>(out result) == true);
+        assert_true(result == "world");
+    });
+
+    Test.add_func("/invercargill/native_element/try_get_as/string_nullable_null_to_string_succeeds", () => {
+        string? s = null;
+        var elem = new NativeElement<string?>(s);
+        string result;
+        // For reference types, convert_nullability returns TRUE even for null
+        // because no conversion is needed - the reference is just passed through
+        assert_true(elem.try_get_as<string>(out result) == true);
+        assert_true(result == null);
+    });
+
+    Test.add_func("/invercargill/native_element/try_get_as/object_to_object", () => {
+        var obj = new NativeElementTestObject(42);
+        var elem = new NativeElement<NativeElementTestObject>(obj);
+        NativeElementTestObject result;
+        assert_true(elem.try_get_as<NativeElementTestObject>(out result) == true);
+        assert_true(result != null);
+        assert_true(result.value == 42);
+    });
+
+    Test.add_func("/invercargill/native_element/try_get_as/object_nullable_to_object", () => {
+        NativeElementTestObject? obj = new NativeElementTestObject(99);
+        var elem = new NativeElement<NativeElementTestObject?>(obj);
+        NativeElementTestObject result;
+        assert_true(elem.try_get_as<NativeElementTestObject>(out result) == true);
+        assert_true(result != null);
+        assert_true(result.value == 99);
+    });
+
+    Test.add_func("/invercargill/native_element/try_get_as/object_nullable_null_to_object_succeeds", () => {
+        NativeElementTestObject? obj = null;
+        var elem = new NativeElement<NativeElementTestObject?>(obj);
+        NativeElementTestObject result;
+        // For reference types, convert_nullability returns TRUE even for null
+        // because no conversion is needed - the reference is just passed through
+        assert_true(elem.try_get_as<NativeElementTestObject>(out result) == true);
+        assert_true(result == null);
+    });
+
+    Test.add_func("/invercargill/native_element/try_get_as/object_nullable_null_to_object_nullable", () => {
+        NativeElementTestObject? obj = null;
+        var elem = new NativeElement<NativeElementTestObject?>(obj);
+        NativeElementTestObject? result;
+        assert_true(elem.try_get_as<NativeElementTestObject?>(out result) == true);
+        assert_true(result == null);
+    });
+
+    // ============================================================================
+    // try_get_as<T>() tests - type conversion failures
+    // ============================================================================
+
+    Test.add_func("/invercargill/native_element/try_get_as/int_to_string_fails", () => {
+        var elem = new NativeElement<int>(42);
+        string result;
+        assert_true(elem.try_get_as<string>(out result) == false);
+    });
+
+    Test.add_func("/invercargill/native_element/try_get_as/string_to_int_fails", () => {
+        var elem = new NativeElement<string>("hello");
+        int result;
+        assert_true(elem.try_get_as<int>(out result) == false);
+    });
+
+    Test.add_func("/invercargill/native_element/try_get_as/object_to_wrong_type_fails", () => {
+        var obj = new NativeElementTestObject(42);
+        var elem = new NativeElement<NativeElementTestObject>(obj);
+        string result;
+        assert_true(elem.try_get_as<string>(out result) == false);
+    });
+
+    // ============================================================================
+    // @as<T>() tests - successful conversions
+    // ============================================================================
+
+    Test.add_func("/invercargill/native_element/as/int_to_int", () => {
+        var elem = new NativeElement<int>(42);
+        try {
+            var result = elem.as<int>();
+            assert_true(result == 42);
+        } catch (Error e) {
+            assert_not_reached();
+        }
+    });
+
+    Test.add_func("/invercargill/native_element/as/string_to_string", () => {
+        var elem = new NativeElement<string>("hello");
+        try {
+            var result = elem.as<string>();
+            assert_true(result == "hello");
+        } catch (Error e) {
+            assert_not_reached();
+        }
+    });
+
+    Test.add_func("/invercargill/native_element/as/object_to_object", () => {
+        var obj = new NativeElementTestObject(42);
+        var elem = new NativeElement<NativeElementTestObject>(obj);
+        try {
+            var result = elem.as<NativeElementTestObject>();
+            assert_true(result != null);
+            assert_true(result.value == 42);
+        } catch (Error e) {
+            assert_not_reached();
+        }
+    });
+
+    // ============================================================================
+    // @as<T>() tests - failed conversions (should throw)
+    // ============================================================================
+
+    Test.add_func("/invercargill/native_element/as/int_to_string_throws", () => {
+        var elem = new NativeElement<int>(42);
+        try {
+            var result = elem.as<string>();
+            assert_not_reached();  // Should have thrown
+        } catch (ElementError e) {
+            // Expected
+        } catch (Error e) {
+            assert_not_reached();
+        }
+    });
+
+    Test.add_func("/invercargill/native_element/as/string_to_int_throws", () => {
+        var elem = new NativeElement<string>("hello");
+        try {
+            var result = elem.as<int>();
+            assert_not_reached();  // Should have thrown
+        } catch (ElementError e) {
+            // Expected
+        } catch (Error e) {
+            assert_not_reached();
+        }
+    });
+
+    // ============================================================================
+    // assert_as<T>() tests
+    // ============================================================================
+
+    Test.add_func("/invercargill/native_element/assert_as/int_to_int", () => {
+        var elem = new NativeElement<int>(42);
+        var result = elem.assert_as<int>();
+        assert_true(result == 42);
+    });
+
+    Test.add_func("/invercargill/native_element/assert_as/string_to_string", () => {
+        var elem = new NativeElement<string>("hello");
+        var result = elem.assert_as<string>();
+        assert_true(result == "hello");
+    });
+
+    Test.add_func("/invercargill/native_element/assert_as/object_to_object", () => {
+        var obj = new NativeElementTestObject(42);
+        var elem = new NativeElement<NativeElementTestObject>(obj);
+        var result = elem.assert_as<NativeElementTestObject>();
+        assert_true(result != null);
+        assert_true(result.value == 42);
+    });
+
+    // ============================================================================
+    // Nullability conversion tests (matching Nullable.vala tests)
+    // ============================================================================
+
+    Test.add_func("/invercargill/native_element/nullability/int_to_int_nullable", () => {
+        var elem = new NativeElement<int>(42);
+        int? result;
+        assert_true(elem.try_get_as<int?>(out result) == true);
+        assert_true(result != null);
+        assert_true(result == 42);
+    });
+
+    Test.add_func("/invercargill/native_element/nullability/int_nullable_to_int_with_value", () => {
+        int? x = 42;
+        var elem = new NativeElement<int?>(x);
+        int result;
+        assert_true(elem.try_get_as<int>(out result) == true);
+        assert_true(result == 42);
+    });
+
+    Test.add_func("/invercargill/native_element/nullability/int_nullable_to_int_null", () => {
+        int? x = null;
+        var elem = new NativeElement<int?>(x);
+        int result;
+        // Should fail because null cannot be converted to non-nullable
+        assert_true(elem.try_get_as<int>(out result) == false);
+    });
+
+    Test.add_func("/invercargill/native_element/nullability/int_nullable_to_int_nullable_with_value", () => {
+        int? x = 42;
+        var elem = new NativeElement<int?>(x);
+        int? result;
+        assert_true(elem.try_get_as<int?>(out result) == true);
+        assert_true(result != null);
+        assert_true(result == 42);
+    });
+
+    Test.add_func("/invercargill/native_element/nullability/int_nullable_to_int_nullable_null", () => {
+        int? x = null;
+        var elem = new NativeElement<int?>(x);
+        int? result;
+        assert_true(elem.try_get_as<int?>(out result) == true);
+        assert_true(result == null);
+    });
+
+    Test.add_func("/invercargill/native_element/nullability/bool_to_bool_nullable", () => {
+        var elem = new NativeElement<bool>(true);
+        bool? result;
+        assert_true(elem.try_get_as<bool?>(out result) == true);
+        assert_true(result != null);
+        assert_true(result == true);
+    });
+
+    Test.add_func("/invercargill/native_element/nullability/bool_nullable_to_bool_with_value", () => {
+        bool? x = true;
+        var elem = new NativeElement<bool?>(x);
+        bool result;
+        assert_true(elem.try_get_as<bool>(out result) == true);
+        assert_true(result == true);
+    });
+
+    Test.add_func("/invercargill/native_element/nullability/bool_nullable_to_bool_null", () => {
+        bool? x = null;
+        var elem = new NativeElement<bool?>(x);
+        bool result;
+        // Should fail because null cannot be converted to non-nullable
+        assert_true(elem.try_get_as<bool>(out result) == false);
+    });
+
+    Test.add_func("/invercargill/native_element/nullability/long_to_long_nullable", () => {
+        var elem = new NativeElement<long>(123456789L);
+        long? result;
+        assert_true(elem.try_get_as<long?>(out result) == true);
+        assert_true(result != null);
+        assert_true(result == 123456789L);
+    });
+
+    Test.add_func("/invercargill/native_element/nullability/long_nullable_to_long_with_value", () => {
+        long? x = 123456789L;
+        var elem = new NativeElement<long?>(x);
+        long result;
+        assert_true(elem.try_get_as<long>(out result) == true);
+        assert_true(result == 123456789L);
+    });
+
+    Test.add_func("/invercargill/native_element/nullability/long_nullable_to_long_null", () => {
+        long? x = null;
+        var elem = new NativeElement<long?>(x);
+        long result;
+        // Should fail because null cannot be converted to non-nullable
+        assert_true(elem.try_get_as<long>(out result) == false);
+    });
+
+    // ============================================================================
+    // Edge cases and boundary values
+    // ============================================================================
+
+    Test.add_func("/invercargill/native_element/edge/int_min_value", () => {
+        var elem = new NativeElement<int>(int.MIN);
+        int result;
+        assert_true(elem.try_get_as<int>(out result) == true);
+        assert_true(result == int.MIN);
+    });
+
+    Test.add_func("/invercargill/native_element/edge/int_max_value", () => {
+        var elem = new NativeElement<int>(int.MAX);
+        int result;
+        assert_true(elem.try_get_as<int>(out result) == true);
+        assert_true(result == int.MAX);
+    });
+
+    Test.add_func("/invercargill/native_element/edge/long_min_value", () => {
+        var elem = new NativeElement<long>(long.MIN);
+        long result;
+        assert_true(elem.try_get_as<long>(out result) == true);
+        assert_true(result == long.MIN);
+    });
+
+    Test.add_func("/invercargill/native_element/edge/long_max_value", () => {
+        var elem = new NativeElement<long>(long.MAX);
+        long result;
+        assert_true(elem.try_get_as<long>(out result) == true);
+        assert_true(result == long.MAX);
+    });
+
+    Test.add_func("/invercargill/native_element/edge/empty_string", () => {
+        var elem = new NativeElement<string>("");
+        assert_true(elem.is_null() == false);
+        string result;
+        assert_true(elem.try_get_as<string>(out result) == true);
+        assert_true(result == "");
+    });
+
+    Test.add_func("/invercargill/native_element/edge/string_nullable_empty", () => {
+        string? s = "";
+        var elem = new NativeElement<string?>(s);
+        assert_true(elem.is_null() == false);
+        string? result;
+        assert_true(elem.try_get_as<string?>(out result) == true);
+        assert_true(result != null);
+        assert_true(result == "");
+    });
+
+    // ============================================================================
+    // Sequence tests (multiple conversions)
+    // ============================================================================
+
+    Test.add_func("/invercargill/native_element/sequence/int_to_nullable_to_int", () => {
+        var elem = new NativeElement<int>(42);
+        
+        // First conversion: int -> int?
+        int? temp;
+        assert_true(elem.try_get_as<int?>(out temp) == true);
+        assert_true(temp != null);
+        assert_true(temp == 42);
+        
+        // Create new element from nullable
+        var elem2 = new NativeElement<int?>(temp);
+        
+        // Second conversion: int? -> int
+        int result;
+        assert_true(elem2.try_get_as<int>(out result) == true);
+        assert_true(result == 42);
+    });
+
+    Test.add_func("/invercargill/native_element/sequence/null_nullable_to_int_to_nullable", () => {
+        int? x = null;
+        var elem = new NativeElement<int?>(x);
+        
+        // First conversion: int? (null) -> int (should fail)
+        int temp;
+        assert_true(elem.try_get_as<int>(out temp) == false);
+        
+        // Second conversion: int? (null) -> int? (null)
+        int? result;
+        assert_true(elem.try_get_as<int?>(out result) == true);
+        assert_true(result == null);
+    });
+
+    // ============================================================================
+    // Interface implementation tests
+    // ============================================================================
+
+    Test.add_func("/invercargill/native_element/interface/implements_element", () => {
+        Element elem = new NativeElement<int>(42);
+        assert_true(elem != null);
+        assert_true(elem.is_null() == false);
+        assert_true(elem.type() == typeof(int));
+    });
+
+    Test.add_func("/invercargill/native_element/interface/polymorphic_usage", () => {
+        Element e1 = new NativeElement<int>(42);
+        Element e2 = new NativeElement<string>("hello");
+        Element e3 = new NativeElement<NativeElementTestObject>(new NativeElementTestObject(99));
+        
+        assert_true(e1.type() == typeof(int));
+        assert_true(e2.type() == typeof(string));
+        assert_true(e3.type() == typeof(NativeElementTestObject));
+    });
+
+    // ============================================================================
+    // is_null() consistency tests
+    // ============================================================================
+
+    Test.add_func("/invercargill/native_element/is_null/non_nullable_never_null", () => {
+        var elem = new NativeElement<int>(0);
+        // Non-nullable types should never report as null
+        assert_true(elem.is_null() == false);
+    });
+
+    Test.add_func("/invercargill/native_element/is_null/nullable_with_value_not_null", () => {
+        int? x = 0;
+        var elem = new NativeElement<int?>(x);
+        assert_true(elem.is_null() == false);
+    });
+
+    Test.add_func("/invercargill/native_element/is_null/nullable_null_is_null", () => {
+        int? x = null;
+        var elem = new NativeElement<int?>(x);
+        assert_true(elem.is_null() == true);
+    });
+
+    Test.add_func("/invercargill/native_element/is_null/string_not_null", () => {
+        var elem = new NativeElement<string>("hello");
+        assert_true(elem.is_null() == false);
+    });
+
+    Test.add_func("/invercargill/native_element/is_null/string_null_is_null", () => {
+        string? s = null;
+        var elem = new NativeElement<string?>(s);
+        assert_true(elem.is_null() == true);
+    });
+
+    // ============================================================================
+    // is_reference_type consistency tests
+    // ============================================================================
+
+    Test.add_func("/invercargill/native_element/is_reference_type/value_types_not_reference", () => {
+        var elem_int = new NativeElement<int>(42);
+        var elem_bool = new NativeElement<bool>(true);
+        var elem_long = new NativeElement<long>(123L);
+        
+        assert_true(elem_int.is_reference_type == false);
+        assert_true(elem_bool.is_reference_type == false);
+        assert_true(elem_long.is_reference_type == false);
+    });
+
+    Test.add_func("/invercargill/native_element/is_reference_type/reference_types_are_reference", () => {
+        var elem_str = new NativeElement<string>("hello");
+        var elem_obj = new NativeElement<NativeElementTestObject>(new NativeElementTestObject(42));
+        
+        assert_true(elem_str.is_reference_type == true);
+        assert_true(elem_obj.is_reference_type == true);
+    });
+
+    // ============================================================================
+    // is_nullable property consistency tests
+    // ============================================================================
+
+    Test.add_func("/invercargill/native_element/is_nullable/non_nullable_value_types", () => {
+        var elem_int = new NativeElement<int>(42);
+        var elem_bool = new NativeElement<bool>(true);
+        var elem_long = new NativeElement<long>(123L);
+        
+        assert_true(elem_int.is_nullable == false);
+        assert_true(elem_bool.is_nullable == false);
+        assert_true(elem_long.is_nullable == false);
+    });
+
+    Test.add_func("/invercargill/native_element/is_nullable/nullable_value_types", () => {
+        int? xi = 42;
+        bool? xb = true;
+        long? xl = 123L;
+        
+        var elem_int = new NativeElement<int?>(xi);
+        var elem_bool = new NativeElement<bool?>(xb);
+        var elem_long = new NativeElement<long?>(xl);
+        
+        assert_true(elem_int.is_nullable == true);
+        assert_true(elem_bool.is_nullable == true);
+        assert_true(elem_long.is_nullable == true);
+    });
+
+    Test.add_func("/invercargill/native_element/is_nullable/reference_types_always_nullable", () => {
+        var elem_str = new NativeElement<string>("hello");
+        var elem_obj = new NativeElement<NativeElementTestObject>(new NativeElementTestObject(42));
+        
+        // Reference types are always nullable
+        assert_true(elem_str.is_nullable == true);
+        assert_true(elem_obj.is_nullable == true);
+    });
+
+    // ============================================================================
+    // char and uchar tests
+    // ============================================================================
+
+    Test.add_func("/invercargill/native_element/char/non_nullable", () => {
+        var elem = new NativeElement<char>('a');
+        assert_true(elem.is_reference_type == false);
+        assert_true(elem.is_nullable == false);
+        assert_true(elem.is_null() == false);
+        
+        char result;
+        assert_true(elem.try_get_as<char>(out result) == true);
+        assert_true(result == 'a');
+    });
+
+    Test.add_func("/invercargill/native_element/char/nullable_with_value", () => {
+        char? c = 'z';
+        var elem = new NativeElement<char?>(c);
+        assert_true(elem.is_reference_type == false);
+        assert_true(elem.is_nullable == true);
+        assert_true(elem.is_null() == false);
+        
+        char? result;
+        assert_true(elem.try_get_as<char?>(out result) == true);
+        assert_true(result != null);
+        assert_true(result == 'z');
+    });
+
+    Test.add_func("/invercargill/native_element/char/nullable_null", () => {
+        char? c = null;
+        var elem = new NativeElement<char?>(c);
+        assert_true(elem.is_reference_type == false);
+        assert_true(elem.is_nullable == true);
+        assert_true(elem.is_null() == true);
+        
+        char? result;
+        assert_true(elem.try_get_as<char?>(out result) == true);
+        assert_true(result == null);
+    });
+
+    Test.add_func("/invercargill/native_element/uchar/non_nullable", () => {
+        var elem = new NativeElement<uchar>(255);
+        assert_true(elem.is_reference_type == false);
+        assert_true(elem.is_nullable == false);
+        assert_true(elem.is_null() == false);
+        
+        uchar result;
+        assert_true(elem.try_get_as<uchar>(out result) == true);
+        assert_true(result == 255);
+    });
+
+    Test.add_func("/invercargill/native_element/uchar/nullable_with_value", () => {
+        uchar? c = 128;
+        var elem = new NativeElement<uchar?>(c);
+        assert_true(elem.is_reference_type == false);
+        assert_true(elem.is_nullable == true);
+        assert_true(elem.is_null() == false);
+        
+        uchar? result;
+        assert_true(elem.try_get_as<uchar?>(out result) == true);
+        assert_true(result != null);
+        assert_true(result == 128);
+    });
+
+    Test.add_func("/invercargill/native_element/uchar/nullable_null", () => {
+        uchar? c = null;
+        var elem = new NativeElement<uchar?>(c);
+        assert_true(elem.is_reference_type == false);
+        assert_true(elem.is_nullable == true);
+        assert_true(elem.is_null() == true);
+        
+        uchar? result;
+        assert_true(elem.try_get_as<uchar?>(out result) == true);
+        assert_true(result == null);
+    });
+
+    // ============================================================================
+    // uint and ulong tests
+    // ============================================================================
+
+    Test.add_func("/invercargill/native_element/uint/non_nullable", () => {
+        var elem = new NativeElement<uint>(42u);
+        assert_true(elem.is_reference_type == false);
+        assert_true(elem.is_nullable == false);
+        
+        uint result;
+        assert_true(elem.try_get_as<uint>(out result) == true);
+        assert_true(result == 42u);
+    });
+
+    Test.add_func("/invercargill/native_element/uint/nullable_with_value", () => {
+        uint? x = 99u;
+        var elem = new NativeElement<uint?>(x);
+        assert_true(elem.is_reference_type == false);
+        assert_true(elem.is_nullable == true);
+        
+        uint? result;
+        assert_true(elem.try_get_as<uint?>(out result) == true);
+        assert_true(result == 99u);
+    });
+
+    Test.add_func("/invercargill/native_element/ulong/non_nullable", () => {
+        var elem = new NativeElement<ulong>(123456789UL);
+        assert_true(elem.is_reference_type == false);
+        assert_true(elem.is_nullable == false);
+        
+        ulong result;
+        assert_true(elem.try_get_as<ulong>(out result) == true);
+        assert_true(result == 123456789UL);
+    });
+
+    Test.add_func("/invercargill/native_element/ulong/nullable_with_value", () => {
+        ulong? x = 123456789UL;
+        var elem = new NativeElement<ulong?>(x);
+        assert_true(elem.is_reference_type == false);
+        assert_true(elem.is_nullable == true);
+        
+        ulong? result;
+        assert_true(elem.try_get_as<ulong?>(out result) == true);
+        assert_true(result == 123456789UL);
+    });
+}

+ 578 - 0
src/tests/Integration/Nullable.vala

@@ -0,0 +1,578 @@
+using Invercargill;
+using Invercargill.Nullable;
+
+void nullable_tests() {
+
+    // ============================================================================
+    // is_boxed_nullable tests
+    // ============================================================================
+
+    Test.add_func("/invercargill/nullable/is_boxed_nullable/int_non_nullable", () => {
+        int x = 42;
+        assert_true(!is_boxed_nullable(x));
+    });
+
+    Test.add_func("/invercargill/nullable/is_boxed_nullable/int_nullable_with_value", () => {
+        int? x = 42;
+        assert_true(is_boxed_nullable(x));
+    });
+
+    Test.add_func("/invercargill/nullable/is_boxed_nullable/int_nullable_null", () => {
+        int? x = null;
+        assert_true(is_boxed_nullable(x));
+    });
+
+    Test.add_func("/invercargill/nullable/is_boxed_nullable/bool_non_nullable", () => {
+        bool x = true;
+        assert_true(!is_boxed_nullable(x));
+    });
+
+    Test.add_func("/invercargill/nullable/is_boxed_nullable/bool_nullable", () => {
+        bool? x = true;
+        assert_true(is_boxed_nullable(x));
+    });
+
+    Test.add_func("/invercargill/nullable/is_boxed_nullable/long_non_nullable", () => {
+        long x = 123456789L;
+        assert_true(!is_boxed_nullable(x));
+    });
+
+    Test.add_func("/invercargill/nullable/is_boxed_nullable/long_nullable", () => {
+        long? x = 123456789L;
+        assert_true(is_boxed_nullable(x));
+    });
+
+    Test.add_func("/invercargill/nullable/is_boxed_nullable/string_not_boxed", () => {
+        string s = "hello";
+        assert_true(!is_boxed_nullable(s));
+    });
+
+    Test.add_func("/invercargill/nullable/is_boxed_nullable/string_nullable_not_boxed", () => {
+        string? s = "hello";
+        assert_true(!is_boxed_nullable(s));
+    });
+
+    Test.add_func("/invercargill/nullable/is_boxed_nullable/object_not_boxed", () => {
+        var obj = new Object();
+        assert_true(!is_boxed_nullable(obj));
+    });
+
+    // ============================================================================
+    // is_nullable tests
+    // ============================================================================
+
+    // is_nullable checks if the TYPE is nullable (can hold null), not if the value IS null
+    // - Non-nullable value types (int, bool, etc.) -> FALSE
+    // - Nullable value types (int?, bool?, etc.) -> TRUE
+    // - Reference types (string, Object, etc.) -> TRUE (always nullable)
+    Test.add_func("/invercargill/nullable/is_nullable/int_non_nullable", () => {
+        int x = 42;
+        // int is a non-nullable value type, so is_nullable returns FALSE
+        assert_true(!is_nullable(x));
+    });
+
+    Test.add_func("/invercargill/nullable/is_nullable/int_nullable_with_value", () => {
+        int? x = 42;
+        // int? is a nullable value type, so is_nullable returns TRUE
+        assert_true(is_nullable(x));
+    });
+
+    Test.add_func("/invercargill/nullable/is_nullable/int_nullable_null", () => {
+        int? x = null;
+        // int? is a nullable value type, so is_nullable returns TRUE
+        assert_true(is_nullable(x));
+    });
+
+    Test.add_func("/invercargill/nullable/is_nullable/string_is_nullable", () => {
+        string s = "hello";
+        // string is a reference type, always nullable
+        assert_true(is_nullable(s));
+    });
+
+    Test.add_func("/invercargill/nullable/is_nullable/string_nullable_is_nullable", () => {
+        string? s = "hello";
+        // string? is also a nullable type
+        assert_true(is_nullable(s));
+    });
+
+    Test.add_func("/invercargill/nullable/is_nullable/string_nullable_null", () => {
+        string? s = null;
+        // string? is a nullable type
+        assert_true(is_nullable(s));
+    });
+
+    Test.add_func("/invercargill/nullable/is_nullable/bool_non_nullable", () => {
+        bool x = true;
+        // bool is a non-nullable value type
+        assert_true(!is_nullable(x));
+    });
+
+    Test.add_func("/invercargill/nullable/is_nullable/bool_nullable_with_value", () => {
+        bool? x = false;
+        // bool? is a nullable value type
+        assert_true(is_nullable(x));
+    });
+
+    Test.add_func("/invercargill/nullable/is_nullable/bool_nullable_null", () => {
+        bool? x = null;
+        // bool? is a nullable value type
+        assert_true(is_nullable(x));
+    });
+
+    Test.add_func("/invercargill/nullable/is_nullable/object_is_nullable", () => {
+        var obj = new Object();
+        // Object is a reference type, always nullable
+        assert_true(is_nullable(obj));
+    });
+
+    Test.add_func("/invercargill/nullable/is_nullable/object_nullable_is_nullable", () => {
+        Object? obj = new Object();
+        // Object? is also a nullable type
+        assert_true(is_nullable(obj));
+    });
+
+    // ============================================================================
+    // is_null tests
+    // ============================================================================
+
+    Test.add_func("/invercargill/nullable/is_null/int_non_nullable_never_null", () => {
+        int x = 42;
+        assert_true(!is_null(x));
+    });
+
+    Test.add_func("/invercargill/nullable/is_null/int_nullable_with_value", () => {
+        int? x = 42;
+        assert_true(!is_null(x));
+    });
+
+    Test.add_func("/invercargill/nullable/is_null/int_nullable_null", () => {
+        int? x = null;
+        assert_true(is_null(x));
+    });
+
+    Test.add_func("/invercargill/nullable/is_null/string_with_value", () => {
+        string s = "hello";
+        assert_true(!is_null(s));
+    });
+
+    Test.add_func("/invercargill/nullable/is_null/string_nullable_null", () => {
+        string? s = null;
+        assert_true(is_null(s));
+    });
+
+    Test.add_func("/invercargill/nullable/is_null/object_with_value", () => {
+        var obj = new Object();
+        assert_true(!is_null(obj));
+    });
+
+    Test.add_func("/invercargill/nullable/is_null/object_null", () => {
+        Object? obj = null;
+        assert_true(is_null(obj));
+    });
+
+    // ============================================================================
+    // is_reference tests
+    // ============================================================================
+
+    Test.add_func("/invercargill/nullable/is_reference/int_not_reference", () => {
+        int x = 42;
+        assert_true(!is_reference(x));
+    });
+
+    Test.add_func("/invercargill/nullable/is_reference/int_nullable_not_reference", () => {
+        int? x = 42;
+        assert_true(!is_reference(x));
+    });
+
+    Test.add_func("/invercargill/nullable/is_reference/string_is_reference", () => {
+        string s = "hello";
+        assert_true(is_reference(s));
+    });
+
+    Test.add_func("/invercargill/nullable/is_reference/object_is_reference", () => {
+        var obj = new Object();
+        assert_true(is_reference(obj));
+    });
+
+    Test.add_func("/invercargill/nullable/is_reference/object_nullable_is_reference", () => {
+        Object? obj = new Object();
+        assert_true(is_reference(obj));
+    });
+
+    // ============================================================================
+    // unbox tests
+    // ============================================================================
+
+    Test.add_func("/invercargill/nullable/unbox/nullable_with_value", () => {
+        int? x = 42;
+        var result = unbox(x, 0);
+        assert_true(result == 42);
+    });
+
+    Test.add_func("/invercargill/nullable/unbox/nullable_null_returns_default", () => {
+        int? x = null;
+        var result = unbox(x, 99);
+        assert_true(result == 99);
+    });
+
+    Test.add_func("/invercargill/nullable/unbox/non_nullable_returns_value", () => {
+        int x = 42;
+        var result = unbox(x, 0);
+        assert_true(result == 42);
+    });
+
+    Test.add_func("/invercargill/nullable/unbox/string_nullable_with_value", () => {
+        string? s = "hello";
+        var result = unbox(s, "default");
+        assert_true(result == "hello");
+    });
+
+    Test.add_func("/invercargill/nullable/unbox/string_nullable_null", () => {
+        string? s = null;
+        var result = unbox(s, "default");
+        assert_true(result == "default");
+    });
+
+    Test.add_func("/invercargill/nullable/unbox/bool_nullable_with_value", () => {
+        bool? x = true;
+        var result = unbox(x, false);
+        assert_true(result == true);
+    });
+
+    Test.add_func("/invercargill/nullable/unbox/bool_nullable_null", () => {
+        bool? x = null;
+        var result = unbox(x, true);
+        assert_true(result == true);
+    });
+
+    Test.add_func("/invercargill/nullable/unbox/long_nullable_with_value", () => {
+        long? x = 123456789L;
+        var result = unbox(x, 0L);
+        assert_true(result == 123456789L);
+    });
+
+    Test.add_func("/invercargill/nullable/unbox/long_nullable_null", () => {
+        long? x = null;
+        var result = unbox(x, -1L);
+        assert_true(result == -1L);
+    });
+
+    // ============================================================================
+    // box tests
+    // Note: Vala automatically manages memory for the return value of box<T>(),
+    // so we don't need to call free_boxed manually - it would cause double-free.
+    // ============================================================================
+
+    Test.add_func("/invercargill/nullable/box/int_boxes_successfully", () => {
+        int x = 42;
+        var boxed = box(x);
+        // For non-nullable value types, box returns the value as-is
+        assert_true(boxed == 42);
+    });
+
+    Test.add_func("/invercargill/nullable/box/int_nullable_boxes_successfully", () => {
+        int? x = 42;
+        var boxed = box(x);
+        // For nullable value types, box creates a new boxed copy
+        assert_true(boxed != null);
+        assert_true(boxed == 42);
+    });
+
+    Test.add_func("/invercargill/nullable/box/null_returns_null", () => {
+        int? x = null;
+        var boxed = box(x);
+        // Boxing null returns null
+        assert_true(boxed == null);
+    });
+
+    Test.add_func("/invercargill/nullable/box/string_boxes_successfully", () => {
+        string s = "hello";
+        var boxed = box(s);
+        assert_true(boxed != null);
+        assert_true(boxed == "hello");
+    });
+
+    Test.add_func("/invercargill/nullable/box/bool_boxes_successfully", () => {
+        bool x = true;
+        var boxed = box(x);
+        // For non-nullable value types, box returns the value as-is
+        assert_true(boxed == true);
+    });
+
+    Test.add_func("/invercargill/nullable/box/long_boxes_successfully", () => {
+        long x = 123456789L;
+        var boxed = box(x);
+        // For non-nullable value types, box returns the value as-is
+        assert_true(boxed == 123456789L);
+    });
+
+    // ============================================================================
+    // get_type_name tests
+    // ============================================================================
+
+    Test.add_func("/invercargill/nullable/get_type_name/int_non_nullable", () => {
+        int x = 42;
+        var name = get_type_name(x);
+        assert_true(name != null);
+        assert_true("gint" in name || "int" in name);
+    });
+
+    Test.add_func("/invercargill/nullable/get_type_name/int_nullable", () => {
+        int? x = 42;
+        var name = get_type_name(x);
+        assert_true(name != null);
+        assert_true("gint" in name || "int" in name);
+    });
+
+    Test.add_func("/invercargill/nullable/get_type_name/string", () => {
+        string s = "hello";
+        var name = get_type_name(s);
+        assert_true(name != null);
+        assert_true("gchar" in name || "str" in name);
+    });
+
+    // ============================================================================
+    // convert_nullability tests
+    // ============================================================================
+
+    Test.add_func("/invercargill/nullable/convert_nullability/int_to_int", () => {
+        int x = 42;
+        int y;
+        var result = convert_nullability(x, out y);
+        assert_true(result == true);
+        assert_true(y == 42);
+    });
+
+    Test.add_func("/invercargill/nullable/convert_nullability/int_to_int_zero", () => {
+        int x = 0;
+        int y;
+        var result = convert_nullability(x, out y);
+        assert_true(result == true);
+        assert_true(y == 0);
+    });
+
+    Test.add_func("/invercargill/nullable/convert_nullability/int_to_int_negative", () => {
+        int x = -42;
+        int y;
+        var result = convert_nullability(x, out y);
+        assert_true(result == true);
+        assert_true(y == -42);
+    });
+
+    Test.add_func("/invercargill/nullable/convert_nullability/int_to_int_nullable", () => {
+        int x = 42;
+        int? y;
+        var result = convert_nullability(x, out y);
+        assert_true(result == true);
+        assert_true(y != null);
+        assert_true(y == 42);
+    });
+
+    Test.add_func("/invercargill/nullable/convert_nullability/int_nullable_to_int_with_value", () => {
+        int? x = 42;
+        int y;
+        var result = convert_nullability(x, out y);
+        assert_true(result == true);
+        assert_true(y == 42);
+    });
+
+    Test.add_func("/invercargill/nullable/convert_nullability/int_nullable_to_int_null", () => {
+        int? x = null;
+        int y;
+        var result = convert_nullability(x, out y);
+        assert_true(result == false);
+        assert_true(y == 0); // Default value
+    });
+
+    Test.add_func("/invercargill/nullable/convert_nullability/int_nullable_to_int_nullable_with_value", () => {
+        int? x = 42;
+        int? y;
+        var result = convert_nullability(x, out y);
+        assert_true(result == true);
+        assert_true(y != null);
+        assert_true(y == 42);
+    });
+
+    Test.add_func("/invercargill/nullable/convert_nullability/int_nullable_to_int_nullable_null", () => {
+        int? x = null;
+        int? y;
+        var result = convert_nullability(x, out y);
+        assert_true(result == true);
+        assert_true(y == null);
+    });
+
+    Test.add_func("/invercargill/nullable/convert_nullability/bool_to_bool", () => {
+        bool x = true;
+        bool y;
+        var result = convert_nullability(x, out y);
+        assert_true(result == true);
+        assert_true(y == true);
+    });
+
+    Test.add_func("/invercargill/nullable/convert_nullability/bool_to_bool_false", () => {
+        bool x = false;
+        bool y;
+        var result = convert_nullability(x, out y);
+        assert_true(result == true);
+        assert_true(y == false);
+    });
+
+    Test.add_func("/invercargill/nullable/convert_nullability/bool_nullable_to_bool_with_value", () => {
+        bool? x = true;
+        bool y;
+        var result = convert_nullability(x, out y);
+        assert_true(result == true);
+        assert_true(y == true);
+    });
+
+    Test.add_func("/invercargill/nullable/convert_nullability/bool_nullable_to_bool_null", () => {
+        bool? x = null;
+        bool y;
+        var result = convert_nullability(x, out y);
+        assert_true(result == false);
+        assert_true(y == false); // Default value
+    });
+
+    Test.add_func("/invercargill/nullable/convert_nullability/long_to_long", () => {
+        long x = 123456789L;
+        long y;
+        var result = convert_nullability(x, out y);
+        assert_true(result == true);
+        assert_true(y == 123456789L);
+    });
+
+    Test.add_func("/invercargill/nullable/convert_nullability/long_nullable_to_long_with_value", () => {
+        long? x = (long)9876543210;
+        long y;
+        var result = convert_nullability(x, out y);
+        assert_true(result == true);
+        assert_true(y == (long)9876543210);
+    });
+
+    Test.add_func("/invercargill/nullable/convert_nullability/long_nullable_to_long_null", () => {
+        long? x = null;
+        long y;
+        var result = convert_nullability(x, out y);
+        assert_true(result == false);
+        assert_true(y == 0L); // Default value
+    });
+
+    // ============================================================================
+    // Edge cases and corner cases
+    // ============================================================================
+
+    Test.add_func("/invercargill/nullable/edge/int_min_value", () => {
+        int x = int.MIN;
+        int y;
+        var result = convert_nullability(x, out y);
+        assert_true(result == true);
+        assert_true(y == int.MIN);
+    });
+
+    Test.add_func("/invercargill/nullable/edge/int_max_value", () => {
+        int x = int.MAX;
+        int y;
+        var result = convert_nullability(x, out y);
+        assert_true(result == true);
+        assert_true(y == int.MAX);
+    });
+
+    Test.add_func("/invercargill/nullable/edge/long_min_value", () => {
+        long x = long.MIN;
+        long y;
+        var result = convert_nullability(x, out y);
+        assert_true(result == true);
+        assert_true(y == long.MIN);
+    });
+
+    Test.add_func("/invercargill/nullable/edge/long_max_value", () => {
+        long x = long.MAX;
+        long y;
+        var result = convert_nullability(x, out y);
+        assert_true(result == true);
+        assert_true(y == long.MAX);
+    });
+
+
+    Test.add_func("/invercargill/nullable/edge/empty_string", () => {
+        string s = "";
+        assert_true(!is_null(s));
+        assert_true(is_reference(s));
+    });
+
+    Test.add_func("/invercargill/nullable/edge/string_nullable_empty", () => {
+        string? s = "";
+        assert_true(!is_null(s));
+        assert_true(is_reference(s));
+    });
+
+    Test.add_func("/invercargill/nullable/edge/char_non_nullable", () => {
+        char c = 'a';
+        assert_true(!is_boxed_nullable(c));
+    });
+
+    Test.add_func("/invercargill/nullable/edge/char_nullable", () => {
+        char? c = 'a';
+        assert_true(is_boxed_nullable(c));
+    });
+
+    Test.add_func("/invercargill/nullable/edge/uchar_non_nullable", () => {
+        uchar c = 255;
+        assert_true(!is_boxed_nullable(c));
+    });
+
+    Test.add_func("/invercargill/nullable/edge/uchar_nullable", () => {
+        uchar? c = 255;
+        assert_true(is_boxed_nullable(c));
+    });
+
+    // ============================================================================
+    // Multiple conversions in sequence
+    // ============================================================================
+
+    Test.add_func("/invercargill/nullable/sequence/int_to_nullable_to_int", () => {
+        int x = 42;
+        int? temp;
+        int y;
+        
+        var result1 = convert_nullability(x, out temp);
+        assert_true(result1 == true);
+        assert_true(temp != null);
+        assert_true(temp == 42);
+        
+        var result2 = convert_nullability(temp, out y);
+        assert_true(result2 == true);
+        assert_true(y == 42);
+    });
+
+    Test.add_func("/invercargill/nullable/sequence/null_nullable_to_int_to_nullable", () => {
+        int? x = null;
+        int temp;
+        int? y;
+        
+        var result1 = convert_nullability(x, out temp);
+        assert_true(result1 == false);
+        assert_true(temp == 0);
+        
+        var result2 = convert_nullability(temp, out y);
+        assert_true(result2 == true);
+        assert_true(y != null);
+        assert_true(y == 0);
+    });
+
+    // ============================================================================
+    // get_value alias tests
+    // ============================================================================
+
+    Test.add_func("/invercargill/nullable/get_value/with_value", () => {
+        int? x = 42;
+        var result = get_value(x, 0);
+        assert_true(result == 42);
+    });
+
+    Test.add_func("/invercargill/nullable/get_value/null_returns_default", () => {
+        int? x = null;
+        var result = get_value(x, 99);
+        assert_true(result == 99);
+    });
+}

+ 3 - 0
src/tests/TestRunner.vala

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

+ 3 - 0
src/tests/meson.build

@@ -30,6 +30,9 @@ 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('Integration/Nullable.vala')
+sources += files('Integration/NativeElement.vala')
 
 sources += files('Speed/SpeedTest.vala')
 sources += files('Speed/Series.vala')

+ 10 - 0
usm-scripts/build.sh

@@ -0,0 +1,10 @@
+#!/bin/bash
+set -e
+
+src_dir=$(pwd)
+build_dir=$1
+
+cd ${build_dir}
+
+meson setup ${src_dir}/src --prefix=${PREFIX} --libdir=${LIBDIR} --bindir=${BINDIR} --includedir=${INCLUDEDIR}
+ninja

+ 10 - 0
usm-scripts/install.sh

@@ -0,0 +1,10 @@
+#!/bin/bash
+set -e
+
+src_dir=$(pwd)
+build_dir=$1
+install_dir=$2
+
+cd ${build_dir}
+
+meson install --destdir ${install_dir}

+ 2 - 0
usm-scripts/post-install.sh

@@ -0,0 +1,2 @@
+#!/bin/bash
+ldconfig