from std/path/z/parser import Parser;
from std/path/z/evaluate import Evaluator;
from std/path/z/context import Ctx;
from std/path/z/operators import Operator;
from test/more import *;
from std/dump import Dumper;
function parse_one ( p, src ) {
let terms := p.parse_top_level_terms(src);
is( terms.length(), 1, `one top-level term: ${src}` );
return terms[0];
}
let e := new Evaluator();
let p := new Parser( allowed_operators: e.operator_definitions() );
let elvis_p := new Parser(
allowed_operators: [
new Operator(
spelling: "?:",
kind: "ELVIS",
precedence: 1,
),
],
);
{
let ast := parse_one( p, "( 2 + 5 * 1 + 3 ) / 2" );
let got := e.eval_expr( ast, null );
is( got[0].primitive_value, 5, "evaluated a simple mathematical expression AST" );
}
{
let ctx := new Ctx( root: { x: 1 }, meta: { level: 2, trace: "on" } );
let nested := e.nested_ctx( ctx );
is( nested.meta(){level}, 3, "nested_ctx increments meta level" );
ok( not nested.meta().exists( "trace" ), "nested_ctx meta only keeps level" );
}
{
let ast := parse_one( p, "3 * 3 > 8" );
let got := e.eval_expr( ast, null );
is( got[0].primitive_value, true, "evaluated a simple true comparison AST" );
}
{
let ast := parse_one( p, "3 > 8" );
let got := e.eval_expr( ast, null );
is( got[0].primitive_value, false, "evaluated a simple false comparison AST" );
}
{
let ast := parse_one( p, "0 && bad-function()" );
let got := e.eval_expr( ast, null );
is( got[0].primitive_value, false, "short-circuit with &&" );
}
{
let ast := parse_one( p, "1 || bad-function()" );
let got := e.eval_expr( ast, null );
is( got[0].primitive_value, true, "short-circuit with ||" );
}
{
let ast := parse_one( elvis_p, "0 ?: 7" );
let got := e.eval_expr( ast, null );
is( got[0].primitive_value, 7, "opt-in Elvis evaluates fallback for falsey left side" );
}
{
let ast := parse_one( elvis_p, "5 ?: bad-function()" );
let got := e.eval_expr( ast, null );
is( got[0].primitive_value, 5, "opt-in Elvis short-circuits truthy left side" );
}
{
let ast := parse_one( p, "1|2|4&11" );
let got := e.eval_expr( ast, null );
is( got[0].primitive_value, 3, "bitwise" );
}
{
let ast := parse_one( p, "!0" );
let got := e.eval_expr( ast, null );
is( got[0].primitive_value, true, "unary not uses operator callback" );
}
{
let ast := parse_one( p, "~1" );
let got := e.eval_expr( ast, null );
is( got[0].primitive_value, ~1, "bitwise not uses operator callback" );
}
{
let ast := parse_one( p, "/foo/bar" );
let ctx := new Ctx( root: { foo: { bar: "baz" } } );
let got := e.eval_expr( ast, ctx );
is( got[0].primitive_value, "baz", "simple path" );
}
{
let ast := parse_one( p, "/foo[bar]" );
let ctx := new Ctx( root: { foo: { bar: "baz" } } );
let got := e.eval_expr( ast, ctx );
is( got[0].primitive_value, { bar: "baz" }, "simple path with filter" );
}
{
let ast := parse_one( p, "/foo[bar != \"bat\"]" );
let ctx := new Ctx( root: { foo: { bar: "baz" } } );
let got := e.eval_expr( ast, ctx );
is( got[0].primitive_value, { bar: "baz" }, "simple path with filter and expression" );
}
{
let ast := parse_one( p, "/foo[bar]/type()" );
let ctx := new Ctx( root: { foo: { bar: "baz" } } );
let got := e.eval_expr( ast, ctx );
is( got[0].primitive_value, "map", "simple path with filter and function (type)" );
}
{
let ast := parse_one( p, "/foo[bar]/key()" );
let ctx := new Ctx( root: { foo: { bar: "baz" } } );
let got := e.eval_expr( ast, ctx );
is( got[0].primitive_value, "foo", "simple path with filter and function (key)" );
}
{
let ast := parse_one( p, "key(/foo[bar])" );
let ctx := new Ctx( root: { foo: { bar: "baz" } } );
let got := e.eval_expr( ast, ctx );
is( got[0].primitive_value, "foo", "simple path with filter and function (key) with arg" );
}
{
let ast := parse_one( p, "sum(values/*, mixed/*)" );
let ctx := new Ctx(
root: {
values: [ 1, "2", 3.5 ],
mixed: [ 1, "oops", 2.5, null, "7" ],
},
);
let got := e.eval_expr( ast, ctx );
is( got[0].primitive_value, 17, "sum() ignores non-numeric nodes across multiple expressions" );
}
{
let ast := parse_one( p, "min(values/*, mixed/*)" );
let ctx := new Ctx(
root: {
values: [ 1, "2", 3.5 ],
mixed: [ 1, "oops", 2.5, null, "7" ],
},
);
let got := e.eval_expr( ast, ctx );
is( got[0].primitive_value, 1, "min() ignores non-numeric nodes across multiple expressions" );
}
{
let ast := parse_one( p, "max(values/*, mixed/*)" );
let ctx := new Ctx(
root: {
values: [ 1, "2", 3.5 ],
mixed: [ 1, "oops", 2.5, null, "7" ],
},
);
let got := e.eval_expr( ast, ctx );
is( got[0].primitive_value, 7, "max() ignores non-numeric nodes across multiple expressions" );
}
{
let ast := parse_one( p, "floor(solo, 9.9)" );
let ctx := new Ctx( root: { solo: 1.2 } );
let got := e.eval_expr( ast, ctx );
is( got[0].primitive_value, 1, "floor() supports one-or-more expressions" );
}
{
let ast := parse_one( p, "round(solo, 9.9)" );
let ctx := new Ctx( root: { solo: 1.2 } );
let got := e.eval_expr( ast, ctx );
is( got[0].primitive_value, 1, "round() supports one-or-more expressions" );
}
{
let ast := parse_one( p, "/foo/escape()" );
let ctx := new Ctx( root: { foo: "<tag attr=\"x\"> & 'y'" } );
let got := e.eval_expr( ast, ctx );
is(
got[0].primitive_value,
"<tag attr="x"> & 'y'",
"escape() with zero args uses the current node",
);
}
{
let ast := parse_one( p, "escape(left, right)" );
let ctx := new Ctx( root: { left: "<l>", right: "\"r\"" } );
let got := e.eval_expr( ast, ctx );
is( got.length(), 2, "escape() supports one-or-more expressions" );
is( got[0].primitive_value, "<l>", "escape() escaped first expression value" );
is( got[1].primitive_value, ""r"", "escape() escaped second expression value" );
}
{
let ast := parse_one( p, "/foo/unescape()" );
let ctx := new Ctx( root: { foo: "<a>&"b"" } );
let got := e.eval_expr( ast, ctx );
is( got[0].primitive_value, "<a>&\"b\"", "unescape() with zero args uses the current node" );
}
{
let ast := parse_one( p, "unescape(left, right)" );
let ctx := new Ctx(
root: {
left: "<l>",
right: "'r'",
dec: "A",
hex: "A",
},
);
let got := e.eval_expr( ast, ctx );
is( got.length(), 2, "unescape() supports one-or-more expressions" );
is( got[0].primitive_value, "<l>", "unescape() decoded first expression value" );
is( got[1].primitive_value, "'r'", "unescape() decoded second expression value" );
}
{
let ast := parse_one( p, "unescape(dec, hex)" );
let ctx := new Ctx( root: { dec: "A", hex: "A" } );
let got := e.eval_expr( ast, ctx );
is( got.length(), 2, "unescape() decodes numeric XML entities for one-or-more expressions" );
is( got[0].primitive_value, "A", "unescape() decodes decimal XML numeric entities" );
is( got[1].primitive_value, "A", "unescape() decodes hexadecimal XML numeric entities" );
}
{
let ast := parse_one( p, "/foo/upper-case()" );
let ctx := new Ctx( root: { foo: "MiXeD" } );
let got := e.eval_expr( ast, ctx );
is( got[0].primitive_value, "MIXED", "upper-case() with zero args uses the current node" );
}
{
let ast := parse_one( p, "upper-case(left, right)" );
let ctx := new Ctx( root: { left: "Ada", right: "beTa" } );
let got := e.eval_expr( ast, ctx );
is( got.length(), 2, "upper-case() supports one-or-more expressions" );
is( got[0].primitive_value, "ADA", "upper-case() uppercases first expression value" );
is( got[1].primitive_value, "BETA", "upper-case() uppercases second expression value" );
}
{
let ast := parse_one( p, "/foo/lower-case()" );
let ctx := new Ctx( root: { foo: "MiXeD" } );
let got := e.eval_expr( ast, ctx );
is( got[0].primitive_value, "mixed", "lower-case() with zero args uses the current node" );
}
{
let ast := parse_one( p, "lower-case(left, right)" );
let ctx := new Ctx( root: { left: "Ada", right: "beTa" } );
let got := e.eval_expr( ast, ctx );
is( got.length(), 2, "lower-case() supports one-or-more expressions" );
is( got[0].primitive_value, "ada", "lower-case() lowercases first expression value" );
is( got[1].primitive_value, "beta", "lower-case() lowercases second expression value" );
}
{
let ast := parse_one( p, "/foo/index-of(\"cd\")" );
let ctx := new Ctx( root: { foo: "abcdef" } );
let got := e.eval_expr( ast, ctx );
is( got[0].primitive_value, 2, "index-of() with one arg uses the current node-set" );
}
{
let ast := parse_one( p, "index-of(\"na\", values/*)" );
let ctx := new Ctx( root: { values: [ "banana", "cabana" ] } );
let got := e.eval_expr( ast, ctx );
is( got.length(), 2, "index-of() supports explicit expression input" );
is( got[0].primitive_value, 2, "index-of() finds first match in first input string" );
is( got[1].primitive_value, 4, "index-of() finds first match in second input string" );
}
{
let ast := parse_one( p, "/foo/last-index-of(\"na\")" );
let ctx := new Ctx( root: { foo: "banana" } );
let got := e.eval_expr( ast, ctx );
is( got[0].primitive_value, 4, "last-index-of() with one arg uses the current node-set" );
}
{
let ast := parse_one( p, "last-index-of(\"zz\", values/*)" );
let ctx := new Ctx( root: { values: [ "banana", "cabana" ] } );
let got := e.eval_expr( ast, ctx );
is( got.length(), 2, "last-index-of() supports explicit expression input" );
is( got[0].primitive_value, -1, "last-index-of() returns -1 when not found (first input)" );
is( got[1].primitive_value, -1, "last-index-of() returns -1 when not found (second input)" );
}
{
let ast := parse_one( p, "/foo/substring(1, 3)" );
let ctx := new Ctx( root: { foo: "abcdef" } );
let got := e.eval_expr( ast, ctx );
is( got[0].primitive_value, "bcd", "substring() with two args uses the current node-set" );
}
{
let ast := parse_one( p, "substring(values/*, 2, 2)" );
let ctx := new Ctx( root: { values: [ "banana", "cabana" ] } );
let got := e.eval_expr( ast, ctx );
is( got.length(), 2, "substring() supports explicit expression input" );
is( got[0].primitive_value, "na", "substring() slices first input string" );
is( got[1].primitive_value, "ba", "substring() slices second input string" );
}
{
let ast := parse_one( p, "/foo/format(\"[%s]\")" );
let ctx := new Ctx( root: { foo: "banana" } );
let got := e.eval_expr( ast, ctx );
is( got[0].primitive_value, "[banana]", "format() with one arg uses the current node-set" );
}
{
let ast := parse_one( p, "format(\"[%s]\", values/*)" );
let ctx := new Ctx( root: { values: [ "banana", "cabana" ] } );
let got := e.eval_expr( ast, ctx );
is( got.length(), 2, "format() supports explicit expression input" );
is( got[0].primitive_value, "[banana]", "format() formats first input string" );
is( got[1].primitive_value, "[cabana]", "format() formats second input string" );
}
{
let ast := parse_one( p, "/foo/string-length()" );
let ctx := new Ctx( root: { foo: "banana" } );
let got := e.eval_expr( ast, ctx );
is( got[0].primitive_value, 6, "string-length() with zero args uses the current node-set" );
}
{
let ast := parse_one( p, "string-length(values/*)" );
let ctx := new Ctx( root: { values: [ "banana", "cabana" ] } );
let got := e.eval_expr( ast, ctx );
is( got.length(), 2, "string-length() supports explicit expression input" );
is( got[0].primitive_value, 6, "string-length() reports first input length" );
is( got[1].primitive_value, 6, "string-length() reports second input length" );
}
{
let ast := parse_one( p, "/foo/match(\"^ba\")" );
let ctx := new Ctx( root: { foo: "banana" } );
let got := e.eval_expr( ast, ctx );
is( got[0].primitive_value, true, "match() with one arg uses the current node-set" );
}
{
let ast := parse_one( p, "match(\"na$\", values/*)" );
let ctx := new Ctx( root: { values: [ "banana", "cabana" ] } );
let got := e.eval_expr( ast, ctx );
is( got.length(), 2, "match() supports explicit expression input" );
is( got[0].primitive_value, true, "match() tests first input string" );
is( got[1].primitive_value, true, "match() tests second input string" );
}
{
let ast := parse_one( p, "/foo/replace(\"na\", \"NA\")" );
let ctx := new Ctx( root: { foo: "banana" } );
let got := e.eval_expr( ast, ctx );
is( got[0].primitive_value, "baNAna", "replace() with two args uses the current node-set" );
}
{
let ast := parse_one( p, "replace(\"na\", \"NA\", values/*)" );
let ctx := new Ctx( root: { values: [ "banana", "cabana" ] } );
let got := e.eval_expr( ast, ctx );
is( got.length(), 2, "replace() supports explicit expression input" );
is( got[0].primitive_value, "baNAna", "replace() transforms first input string" );
is( got[1].primitive_value, "cabaNA", "replace() transforms second input string" );
}
{
let ast := parse_one( p, "join(\"|\", values/*)" );
let ctx := new Ctx( root: { values: [ "banana", "cabana" ] } );
let got := e.eval_expr( ast, ctx );
is( got[0].primitive_value, "banana|cabana", "join() supports explicit expression input" );
}
done_testing();