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 values { get; set; } public ExprTestContainer(int min_rank, Enumerable 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(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(out value)); assert(value == 42); }); Test.add_func("/invercargill/expressions/literal_string", () => { var expr = new LiteralExpression(new NativeElement("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(out value)); assert(value == "hello"); }); Test.add_func("/invercargill/expressions/literal_bool", () => { var expr = new LiteralExpression(new NativeElement(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(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(10)); var context = new EvaluationContext(props); var result = expr.evaluate(context); assert(result != null); int value; assert(result.try_get_as(out value)); assert(value == 10); }); // ==================== Binary Comparison Tests ==================== Test.add_func("/invercargill/expressions/binary_equals", () => { var expr = new BinaryExpression( new LiteralExpression(new NativeElement(5)), new LiteralExpression(new NativeElement(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(out value)); assert(value == true); }); Test.add_func("/invercargill/expressions/binary_not_equals", () => { var expr = new BinaryExpression( new LiteralExpression(new NativeElement(5)), new LiteralExpression(new NativeElement(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(out value)); assert(value == true); }); Test.add_func("/invercargill/expressions/binary_greater_than", () => { var expr = new BinaryExpression( new LiteralExpression(new NativeElement(5)), new LiteralExpression(new NativeElement(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(out value)); assert(value == true); }); Test.add_func("/invercargill/expressions/binary_less_than", () => { var expr = new BinaryExpression( new LiteralExpression(new NativeElement(3)), new LiteralExpression(new NativeElement(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(out value)); assert(value == true); }); // ==================== Binary Logical Tests ==================== Test.add_func("/invercargill/expressions/binary_and", () => { var expr = new BinaryExpression( new LiteralExpression(new NativeElement(true)), new LiteralExpression(new NativeElement(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(out value)); assert(value == true); }); Test.add_func("/invercargill/expressions/binary_or", () => { var expr = new BinaryExpression( new LiteralExpression(new NativeElement(false)), new LiteralExpression(new NativeElement(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(out value)); assert(value == true); }); // ==================== Binary Arithmetic Tests ==================== Test.add_func("/invercargill/expressions/binary_arithmetic_add", () => { var expr = new BinaryExpression( new LiteralExpression(new NativeElement(5)), new LiteralExpression(new NativeElement(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(out value)); assert(value == 8); }); Test.add_func("/invercargill/expressions/binary_arithmetic_subtract", () => { var expr = new BinaryExpression( new LiteralExpression(new NativeElement(5)), new LiteralExpression(new NativeElement(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(out value)); assert(value == 2); }); Test.add_func("/invercargill/expressions/binary_arithmetic_multiply", () => { var expr = new BinaryExpression( new LiteralExpression(new NativeElement(5)), new LiteralExpression(new NativeElement(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(out value)); assert(value == 15); }); Test.add_func("/invercargill/expressions/binary_arithmetic_divide", () => { var expr = new BinaryExpression( new LiteralExpression(new NativeElement(15)), new LiteralExpression(new NativeElement(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(out value)); assert(value == 5); }); Test.add_func("/invercargill/expressions/binary_arithmetic_modulo", () => { var expr = new BinaryExpression( new LiteralExpression(new NativeElement(17)), new LiteralExpression(new NativeElement(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(out value)); assert(value == 2); }); // ==================== Unary Tests ==================== Test.add_func("/invercargill/expressions/unary_negate", () => { var expr = new UnaryExpression( UnaryOperator.NEGATE, new LiteralExpression(new NativeElement(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(out value)); assert(value == -5); }); Test.add_func("/invercargill/expressions/unary_not", () => { var expr = new UnaryExpression( UnaryOperator.NOT, new LiteralExpression(new NativeElement(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(out value)); assert(value == true); }); // ==================== Ternary Tests ==================== Test.add_func("/invercargill/expressions/ternary_true", () => { var expr = new TernaryExpression( new LiteralExpression(new NativeElement(true)), new LiteralExpression(new NativeElement("yes")), new LiteralExpression(new NativeElement("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(out value)); assert(value == "yes"); }); Test.add_func("/invercargill/expressions/ternary_false", () => { var expr = new TernaryExpression( new LiteralExpression(new NativeElement(false)), new LiteralExpression(new NativeElement("yes")), new LiteralExpression(new NativeElement("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(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(person)); var context = new EvaluationContext(props); var result = expr.evaluate(context); assert(result != null); string value; assert(result.try_get_as(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(person)); var context = new EvaluationContext(props); var result = expr.evaluate(context); assert(result != null); int value; assert(result.try_get_as(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(5)), new LiteralExpression(new NativeElement(3)), BinaryOperator.ADD ) ); var expr = new BinaryExpression( inner, new LiteralExpression(new NativeElement(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(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(5)), BinaryOperator.GREATER_THAN ), new BinaryExpression( new VariableExpression("b"), new LiteralExpression(new NativeElement(10)), BinaryOperator.LESS_THAN ), BinaryOperator.AND ); var props = new PropertyDictionary(); props.set("a", new NativeElement(7)); props.set("b", new NativeElement(8)); var context = new EvaluationContext(props); var result = expr.evaluate(context); assert(result != null); bool value; assert(result.try_get_as(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(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(); 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(4)), BinaryOperator.GREATER_THAN ); var lambda = new LambdaExpression("p", lambda_body); var args = new Series(); args.add(lambda); var expr = new FunctionCallExpression( new LiteralExpression(new NativeElement?>(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(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(); 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(); args.add(lambda); var expr = new FunctionCallExpression( new LiteralExpression(new NativeElement>(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(out elements)); // Should have 2 string elements var names = new Series(); elements.iterate(e => { string? name; if(e.try_get_as(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(); persons.add(new ExprTestPerson("Alice", 30)); persons.add(new ExprTestPerson("Bob", 25)); // persons.first() var expr = new FunctionCallExpression( new LiteralExpression(new NativeElement>(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(out person)); assert(person.name == "Alice"); }); Test.add_func("/invercargill/expressions/function_call_count", () => { // Create an enumerable of test persons var persons = new Series(); 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>(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(out count)); assert(count == 3); }); Test.add_func("/invercargill/expressions/function_call_any", () => { // Create an enumerable of test persons var persons = new Series(); 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(4)), BinaryOperator.GREATER_THAN ); var lambda = new LambdaExpression("p", lambda_body); var args = new Series(); args.add(lambda); var expr = new FunctionCallExpression( new LiteralExpression(new NativeElement?>(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(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(); 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(4)), BinaryOperator.GREATER_THAN ); var lambda = new LambdaExpression("p", lambda_body); var args = new Series(); args.add(lambda); var where_call = new FunctionCallExpression( new LiteralExpression(new NativeElement?>(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(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(); 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(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(); args.add(lambda); var expr = new FunctionCallExpression( new LiteralExpression(new NativeElement?>(persons)), "where", args ); var result = expr.evaluate(parent_context); assert(result != null); Elements elements; assert(result.try_get_as(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(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(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(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(10)); var context = new EvaluationContext(props); var result = expr.evaluate(context); assert(result != null); int value; assert(result.try_get_as(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(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(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(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(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(person)); var context = new EvaluationContext(props); var result = expr.evaluate(context); assert(result != null); string value; assert(result.try_get_as(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(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(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(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(7)); props.set("b", new NativeElement(8)); var context = new EvaluationContext(props); var result = expr.evaluate(context); assert(result != null); bool value; assert(result.try_get_as(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(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(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? lot; assert(result.try_get_as>(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(1)); elements[1] = new LiteralExpression(new NativeElement(2)); elements[2] = new LiteralExpression(new NativeElement(3)); var expr = new LotLiteralExpression(elements); var props = new PropertyDictionary(); var context = new EvaluationContext(props); var result = expr.evaluate(context); assert(result != null); Enumerable? lot; assert(result.try_get_as?>(out lot)); assert(lot != null); var values = new Series(); 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("a")); elements[1] = new LiteralExpression(new NativeElement("b")); elements[2] = new LiteralExpression(new NativeElement("c")); var expr = new LotLiteralExpression(elements); var props = new PropertyDictionary(); var context = new EvaluationContext(props); var result = expr.evaluate(context); assert(result != null); Enumerable? lot; assert(result.try_get_as>(out lot)); assert(lot != null); var values = new Series(); 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? lot; assert(result.try_get_as?>(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(); args.add(new NativeElement(0)); args.add(new NativeElement(5)); var result = accessor.call_function("range", args, new EvaluationContext(new PropertyDictionary())); assert(result != null); Enumerable? range; assert(result.try_get_as?>(out range)); assert(range != null); var values = new Series(); 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(); args.add(new NativeElement(42)); var result = accessor.call_function("single", args, new EvaluationContext(new PropertyDictionary())); assert(result != null); Enumerable? single; assert(result.try_get_as?>(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(); var result = accessor.call_function("nothing", args, new EvaluationContext(new PropertyDictionary())); assert(result != null); Enumerable? empty; assert(result.try_get_as>(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(); args.add(new NativeElement(0)); args.add(new NativeElement(10)); args.add(new NativeElement(2)); var result = accessor.call_function("range", args, new EvaluationContext(new PropertyDictionary())); assert(result != null); Enumerable? range; assert(result.try_get_as?>(out range)); assert(range != null); var values = new Series(); 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(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("World")); var context = new EvaluationContext(props); var result = expr.evaluate(context); assert(result != null); string value; assert(result.try_get_as(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(42)); var context = new EvaluationContext(props); var result = expr.evaluate(context); assert(result != null); string value; assert(result.try_get_as(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("Item")); props.set("id", new NativeElement(123)); props.set("title", new NativeElement("Test Product")); var context = new EvaluationContext(props); var result = expr.evaluate(context); assert(result != null); string value; assert(result.try_get_as(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(19.99)); var context = new EvaluationContext(props); var result = expr.evaluate(context); assert(result != null); string value; assert(result.try_get_as(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(255)); var context = new EvaluationContext(props); var result = expr.evaluate(context); assert(result != null); string value; assert(result.try_get_as(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(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(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(person)); var context = new EvaluationContext(props); var result = expr.evaluate(context); assert(result != null); string value; assert(result.try_get_as(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(); 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(container)); var context = new EvaluationContext(props); var result = expr.evaluate(context); assert(result != null); int value; assert(result.try_get_as(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(person)); var context = new EvaluationContext(props); var result = expr.evaluate(context); assert(result != null); bool value; assert(result.try_get_as(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(); 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?>(persons)); var context = new EvaluationContext(props); var result = expr.evaluate(context); assert(result != null); Elements elements; assert(result.try_get_as(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(); 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?>(persons)); var context = new EvaluationContext(props); var result = expr.evaluate(context); assert(result != null); string value; assert(result.try_get_as(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(); 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(container)); var context = new EvaluationContext(props); var result = expr.evaluate(context); assert(result != null); Elements elements; assert(result.try_get_as(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(person)); var context = new EvaluationContext(props); var result = expr.evaluate(context); assert(result != null); string value; assert(result.try_get_as(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(person)); var context = new EvaluationContext(props); var result = expr.evaluate(context); assert(result != null); int64? value; assert(result.try_get_as(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(person)); var context = new EvaluationContext(props); var result = expr.evaluate(context); assert(result != null); bool value; assert(result.try_get_as(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(); 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(container)); var context = new EvaluationContext(props); var result = expr.evaluate(context); assert(result != null); bool value; assert(result.try_get_as(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("hello")); var context = new EvaluationContext(props); var result = expr.evaluate(context); assert(result != null); int value; assert(result.try_get_as(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("")); var context = new EvaluationContext(props); var result = expr.evaluate(context); assert(result != null); bool value; assert(result.try_get_as(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(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("")); var context = new EvaluationContext(props); var result = expr.evaluate(context); assert(result != null); bool value; assert(result.try_get_as(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("hello ")); var context = new EvaluationContext(props); var result = expr.evaluate(context); assert(result != null); string value; assert(result.try_get_as(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(" hello")); var context = new EvaluationContext(props); var result = expr.evaluate(context); assert(result != null); string value; assert(result.try_get_as(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("hello")); var context = new EvaluationContext(props); var result = expr.evaluate(context); assert(result != null); string value; assert(result.try_get_as(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("HELLO")); var context = new EvaluationContext(props); var result = expr.evaluate(context); assert(result != null); string value; assert(result.try_get_as(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("hello")); var context = new EvaluationContext(props); var result = expr.evaluate(context); assert(result != null); bool value; assert(result.try_get_as(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("hello")); var context = new EvaluationContext(props); var result = expr.evaluate(context); assert(result != null); bool value; assert(result.try_get_as(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("hello")); var context = new EvaluationContext(props); var result = expr.evaluate(context); assert(result != null); bool value; assert(result.try_get_as(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("hello")); var context = new EvaluationContext(props); var result = expr.evaluate(context); assert(result != null); string value; assert(result.try_get_as(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("hello")); var context = new EvaluationContext(props); var result = expr.evaluate(context); assert(result != null); string value; assert(result.try_get_as(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("a,b,c")); var context = new EvaluationContext(props); var result = expr.evaluate(context); assert(result != null); Enumerable? parts; assert(result.try_get_as>(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("hello")); var context = new EvaluationContext(props); var result = expr.evaluate(context); assert(result != null); string value; assert(result.try_get_as(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("hello")); var context = new EvaluationContext(props); var result = expr.evaluate(context); assert(result != null); string value; assert(result.try_get_as(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("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("hello")); var context = new EvaluationContext(props); var result = expr.evaluate(context); assert(result != null); string value; assert(result.try_get_as(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(" hello ")); var context = new EvaluationContext(props); var result = expr.evaluate(context); assert(result != null); string value; assert(result.try_get_as(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(" hello ")); var context = new EvaluationContext(props); var result = expr.evaluate(context); assert(result != null); bool value; assert(result.try_get_as(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("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(42); assert(element.stringify() == "42"); }); Test.add_func("/invercargill/expressions/element_stringify_int_negative", () => { // Test negative int element → decimal representation var element = new NativeElement(-17); assert(element.stringify() == "-17"); }); Test.add_func("/invercargill/expressions/element_stringify_long", () => { // Test long element → decimal representation var element = new NativeElement(123456789L); assert(element.stringify() == "123456789"); }); Test.add_func("/invercargill/expressions/element_stringify_int64", () => { // Test int64 element → decimal representation var element = new NativeElement(9876543210LL); assert(element.stringify() == "9876543210"); }); Test.add_func("/invercargill/expressions/element_stringify_double", () => { // Test double element → decimal representation var element = new NativeElement(3.5); assert(element.stringify() == "3.5"); }); Test.add_func("/invercargill/expressions/element_stringify_float", () => { // Test float element → decimal representation var element = new NativeElement(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(true); assert(element.stringify() == "true"); }); Test.add_func("/invercargill/expressions/element_stringify_bool_false", () => { // Test bool element false → "false" var element = new NativeElement(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(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("World")); var context = new EvaluationContext(props); var result = expr.evaluate(context); assert(result != null); string value; assert(result.try_get_as(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(2)); props.set("b", new NativeElement(3)); props.set("c", new NativeElement(5)); var context = new EvaluationContext(props); var result = expr.evaluate(context); assert(result != null); string value; assert(result.try_get_as(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(10)); props.set("b", new NativeElement(32)); var context = new EvaluationContext(props); var result = expr.evaluate(context); assert(result != null); string value; assert(result.try_get_as(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(person)); var context = new EvaluationContext(props); var result = expr.evaluate(context); assert(result != null); string value; assert(result.try_get_as(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("World")); var context = new EvaluationContext(props); var result = expr.evaluate(context); assert(result != null); string value; assert(result.try_get_as(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(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(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("test")); var context = new EvaluationContext(props); var result = expr.evaluate(context); assert(result != null); string value; assert(result.try_get_as(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(true)); var context = new EvaluationContext(props); var result = expr.evaluate(context); assert(result != null); string value; assert(result.try_get_as(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(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(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(true)); var context = new EvaluationContext(props); var result = expr.evaluate(context); assert(result != null); string value; assert(result.try_get_as(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(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(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(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(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(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(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(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(42)); var context = new EvaluationContext(props); var result = expr.evaluate(context); assert(result != null); string value; assert(result.try_get_as(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(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(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() 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(); 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?>(vector)); var context = new EvaluationContext(props); var result = expr.evaluate(context); assert(result != null); string value; assert(result.try_get_as(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(); params.add(new NativeElement(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(out value)); assert(value == true); }); Test.add_func("/invercargill/expressions/parameter_multiple", () => { // Multiple parameters: $0 + $1 var params = new Series(); params.add(new NativeElement(3)); params.add(new NativeElement(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(out value)); assert(value == 7); }); Test.add_func("/invercargill/expressions/parameter_with_variable", () => { // Parameter with variable: x > $0 var params = new Series(); params.add(new NativeElement(5)); var expr = ExpressionParser.parse_with_params("x > $0", params); var props = new PropertyDictionary(); props.set("x", new NativeElement(10)); var context = new EvaluationContext(props); var result = expr.evaluate(context); bool value; assert(result.try_get_as(out value)); assert(value == true); }); Test.add_func("/invercargill/expressions/parameter_string", () => { // String parameter: $0 == "hello" var params = new Series(); params.add(new NativeElement("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(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(); params.add(new NativeElement(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(out value)); assert(value == "Alice"); }); Test.add_func("/invercargill/expressions/parameter_in_ternary", () => { // Parameter in ternary: $0 ? "yes" : "no" var params = new Series(); params.add(new NativeElement(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(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(); 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(); params.add(new NativeElement(2)); var expr = ExpressionParser.parse_with_params("items.where(i => i.rank > $0)", params); var props = new PropertyDictionary(); props.set("items", new NativeElement?>(persons)); var context = new EvaluationContext(props); var result = expr.evaluate(context); Elements elements; assert(result.try_get_as(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(); params.add(new NativeElement(0)); params.add(new NativeElement(100)); var expr = ExpressionParser.parse_with_params("$0 <= x && x <= $1", params); var props = new PropertyDictionary(); props.set("x", new NativeElement(50)); var context = new EvaluationContext(props); var result = expr.evaluate(context); bool value; assert(result.try_get_as(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(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(42)); assert(expr.expression_type == ExpressionType.PARAMETER); }); }