|
@@ -1145,4 +1145,301 @@ void expression_tests() {
|
|
|
assert(retrieved != null);
|
|
assert(retrieved != null);
|
|
|
assert(retrieved.accessor is IterateFunctionAccessor);
|
|
assert(retrieved.accessor is IterateFunctionAccessor);
|
|
|
});
|
|
});
|
|
|
|
|
+
|
|
|
|
|
+ // ==================== Global Format Function Tests ====================
|
|
|
|
|
+
|
|
|
|
|
+ Test.add_func("/invercargill/expressions/format_string", () => {
|
|
|
|
|
+ // format("Hello, %s!", "World")
|
|
|
|
|
+ var expr = ExpressionParser.parse("format(\"Hello, %s!\", 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: %i", 42)
|
|
|
|
|
+ var expr = ExpressionParser.parse("format(\"Count: %i\", 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("%s #%i: %s", category, id, title)
|
|
|
|
|
+ var expr = ExpressionParser.parse("format(\"%s #%i: %s\", 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: %.2f", 19.99)
|
|
|
|
|
+ var expr = ExpressionParser.parse("format(\"Price: %.2f\", 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", () => {
|
|
|
|
|
+ // format("Hex: 0x%x", 255)
|
|
|
|
|
+ var expr = ExpressionParser.parse("format(\"Hex: 0x%x\", 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: 0xff");
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ Test.add_func("/invercargill/expressions/format_percent", () => {
|
|
|
|
|
+ // Test that %% in format string becomes single %
|
|
|
|
|
+ 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
|
|
|
|
|
+ });
|
|
|
}
|
|
}
|