zuzu-rust 0.2.0

Rust implementation of ZuzuScript
Documentation
from std/path/zz import ZZPath;
from std/path/z/parser import Parser;
from std/path/zz/operators import STANDARD_OPERATORS;
from test/more import *;

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 p := new Parser( allowed_operators: STANDARD_OPERATORS );

for ( let spelling in [ "+", "-", "√", "!", "¬", "~" ] ) {
	let ast := parse_one( p, `${spelling} value` );
	is( ast{t}, "un", `unary ${spelling} parses as unary expression` );
	is( ast{op}, spelling, `unary ${spelling} spelling is preserved` );
	is( ast{e}{t}, "path", `unary ${spelling} operand is parsed` );
}

let nested := parse_one( p, "! - value" );
is( nested{t}, "un", "nested unary outer expression parses" );
is( nested{op}, "!", "nested unary outer operator is preserved" );
is( nested{e}{op}, "-", "nested unary inner operator is preserved" );

for ( let spelling in [
	"**",
	"×",
	"*",
	"÷",
	"/",
	"mod",
	"+",
	"-",
	"=",
	"≠",
	"<",
	">",
	"≤",
	"<=",
	"≥",
	">=",
	"≶",
	"<=>",
	"≷",
	"&",
	"^",
	"|",
	"⋀",
	"and",
	"⊼",
	"nand",
	"⊻",
	"xor",
	"⋁",
	"or",
	"⋃",
	"union",
	"⋂",
	"intersection",
	"∖",
	"\\",
	"∈",
	"in",
	"∉",
	"⊂",
	"subsetof",
	"⊃",
	"supersetof",
	"⊂⊃",
	"equivalentof",
	"≡",
	"==",
	"≢",
	"!=",
	"can",
] ) {
	let right := spelling eq "can" ? "\"length\"" : "right";
	let ast := parse_one( p, `left ${spelling} ${right}` );
	is( ast{t}, "bin", `binary ${spelling} parses as binary expression` );
	is( ast{op}, spelling, `binary ${spelling} spelling is preserved` );
	is( ast{l}{t}, "path", `binary ${spelling} left operand is parsed` );
	ok( ast{r}{t} in [ "path", "str" ], `binary ${spelling} right operand is parsed` );
}

for ( let spelling in [
	"_",
	"eq",
	"ne",
	"gt",
	"ge",
	"lt",
	"le",
	"cmp",
	"eqi",
	"nei",
	"gti",
	"gei",
	"lti",
	"lei",
	"cmpi",
	"~",
] ) {
	let ast := parse_one( p, `left ${spelling} right` );
	is( ast{t}, "bin", `binary ${spelling} parses as binary expression` );
	is( ast{op}, spelling, `binary ${spelling} spelling is preserved` );
	is( ast{l}{t}, "path", `binary ${spelling} left operand is parsed` );
	is( ast{r}{t}, "path", `binary ${spelling} right operand is parsed` );
}

let pow := parse_one( p, "2 ** 3 ** 2" );
is( pow{op}, "**", "exponentiation parses" );
is( pow{r}{t}, "bin", "exponentiation groups to the right" );
is( pow{r}{op}, "**", "right nested exponentiation is preserved" );

let mult_prec := parse_one( p, "2 + 3 × 4" );
is( mult_prec{op}, "+", "addition is the outer expression" );
is( mult_prec{r}{op}, "×", "multiplication binds tighter than addition" );

let cmp_prec := parse_one( p, "2 + 3 = 5" );
is( cmp_prec{op}, "=", "comparison is the outer expression" );
is( cmp_prec{l}{op}, "+", "addition binds tighter than comparison" );

let concat_prec := parse_one( p, "\"a\" _ \"b\" eq \"ab\"" );
is( concat_prec{op}, "eq", "string comparison is the outer expression" );
is( concat_prec{l}{op}, "_", "concatenation binds tighter than comparison" );

let bitwise_prec := parse_one( p, "1 | 2 ^ 3 & 4" );
is( bitwise_prec{op}, "|", "bitwise or is the outer expression" );
is( bitwise_prec{r}{op}, "^", "bitwise xor binds tighter than or" );
is( bitwise_prec{r}{r}{op}, "&", "bitwise and binds tighter than xor" );

let logical_prec := parse_one( p, "a or b xor c and d" );
is( logical_prec{op}, "or", "logical or is the outer expression" );
is( logical_prec{r}{op}, "xor", "logical xor binds tighter than or" );
is( logical_prec{r}{r}{op}, "and", "logical and binds tighter than xor" );

let logical_sign_prec := parse_one( p, "a ⋁ b ⊻ c ⋀ d" );
is( logical_sign_prec{op}, "⋁", "symbol logical or is the outer expression" );
is(
	logical_sign_prec{r}{op},
	"⊻",
	"symbol logical xor binds tighter than or",
);
is(
	logical_sign_prec{r}{r}{op},
	"⋀",
	"symbol logical and binds tighter than xor",
);

let set_prec := parse_one( p, "a ⋃ b & c" );
is( set_prec{op}, "&", "bitwise and is the outer expression" );
is( set_prec{l}{op}, "⋃", "set union binds tighter than bitwise and" );

let member_prec := parse_one( p, "a ⋃ b ∈ c" );
is( member_prec{op}, "∈", "membership test is the outer expression" );
is( member_prec{l}{op}, "⋃", "set union binds tighter than membership" );

let type_eq_prec := parse_one( p, "a ∈ b == c" );
is( type_eq_prec{op}, "==", "type-aware equality is the outer expression" );
is( type_eq_prec{l}{op}, "∈", "membership binds tighter than type-aware equality" );

let ternary := parse_one( p, "zero ? one : two" );
is( ternary{t}, "ternary", "full ternary parses" );
is( ternary{c}{s}[0]{n}, "zero", "full ternary condition is preserved" );
is( ternary{a}{s}[0]{n}, "one", "full ternary true branch is preserved" );
is( ternary{b}{s}[0]{n}, "two", "full ternary false branch is preserved" );

let elvis := parse_one( p, "zero ?: two" );
is( elvis{t}, "elvis", "Elvis ternary parses" );
is( elvis{c}{s}[0]{n}, "zero", "Elvis condition is preserved" );
is( elvis{b}{s}[0]{n}, "two", "Elvis fallback is preserved" );

let desc := parse_one( p, "body[**/value]" );
is( desc{s}[0]{q}[0]{t}, "path", "descendant path still parses in filters" );
is( desc{s}[0]{q}[0]{s}[0]{k}, "desc", "bare ** remains a path segment" );

like(
	exception( function () {
		parse_one( p, "sqrt value" );
	} ),
	/Expected EOF/,
	"word-like sqrt unary is not a ZZPath operator",
);

like(
	exception( function () {
		new ZZPath( path: "2*3" );
	} ),
	/requires whitespace|Expected EOF|Unexpected token/,
	"path-ambiguous * requires whitespace as a binary operator",
);

like(
	exception( function () {
		new ZZPath( path: "left \\right" );
	} ),
	/requires whitespace|Expected EOF|Unexpected token/,
	"binary \\ requires whitespace as a binary operator",
);

done_testing();