from std/path/zz/operators import Operator, STANDARD_OPERATORS;
from test/more import *;

ok( typeof Operator eq "Class", "Operator class is importable" );
ok( typeof STANDARD_OPERATORS eq "Array", "STANDARD_OPERATORS is importable" );
is( STANDARD_OPERATORS.length(), 73, "phase 4.8 operator table is populated" );

function find_op ( spelling ) {
	return STANDARD_OPERATORS.first( fn op → op.get_spelling() eq spelling );
}

function find_unary ( spelling ) {
	return STANDARD_OPERATORS.first(
		fn op → op.get_spelling() eq spelling and op.is_unary()
	);
}

function find_binary ( spelling ) {
	return STANDARD_OPERATORS.first(
		fn op → op.get_spelling() eq spelling and op.is_binary()
	);
}

for ( let spelling in [ "+", "-", "√", "!", "¬", "~" ] ) {
	let op := find_unary(spelling);
	ok( op ≢ null, `unary ${spelling} operator exists` );
	ok( op.is_unary(), `unary ${spelling} is marked unary` );
	ok( not op.is_binary(), `unary ${spelling} is not binary` );
	is( op.get_precedence(), 20, `unary ${spelling} precedence is set` );
	ok( op{f} ≢ null, `unary ${spelling} has implementation callback` );
}

ok(
	find_unary("!"){f} ≡ find_unary("¬"){f},
	"logical negation aliases share an implementation callback",
);

for ( let spelling in [ "**" ] ) {
	let op := find_binary(spelling);
	ok( op ≢ null, `binary ${spelling} operator exists` );
	ok( op.is_binary(), `binary ${spelling} is marked binary` );
	is( op.get_precedence(), 13, `binary ${spelling} precedence is set` );
	ok( op.is_right_associative(), `binary ${spelling} is right-associative` );
	ok( op.requires_whitespace(), `binary ${spelling} enforces whitespace` );
	ok( op{f} ≢ null, `binary ${spelling} has implementation callback` );
}

for ( let spelling in [ "×", "*", "÷", "/", "mod" ] ) {
	let op := find_binary(spelling);
	ok( op ≢ null, `binary ${spelling} operator exists` );
	ok( op.is_binary(), `binary ${spelling} is marked binary` );
	is( op.get_precedence(), 12, `binary ${spelling} precedence is set` );
	ok( op{f} ≢ null, `binary ${spelling} has implementation callback` );
}

ok( find_binary("*").requires_whitespace(), "binary * enforces whitespace" );
ok( find_binary("/").requires_whitespace(), "binary / enforces whitespace" );
ok(
	find_binary("×"){f} ≡ find_binary("*"){f},
	"multiplication aliases share an implementation callback",
);
ok(
	find_binary("÷"){f} ≡ find_binary("/"){f},
	"division aliases share an implementation callback",
);

for ( let spelling in [ "+", "-" ] ) {
	let op := find_binary(spelling);
	ok( op ≢ null, `binary ${spelling} operator exists` );
	ok( op.is_binary(), `binary ${spelling} is marked binary` );
	is( op.get_precedence(), 11, `binary ${spelling} precedence is set` );
	ok( op.requires_whitespace(), `binary ${spelling} enforces whitespace` );
	ok( op{f} ≢ null, `binary ${spelling} has implementation callback` );
}

for ( let spelling in [
	"=",
	"≠",
	"<",
	">",
	"≤",
	"<=",
	"≥",
	">=",
	"≶",
	"<=>",
	"≷",
] ) {
	let op := find_binary(spelling);
	ok( op ≢ null, `binary ${spelling} operator exists` );
	ok( op.is_binary(), `binary ${spelling} is marked binary` );
	is( op.get_precedence(), 5, `binary ${spelling} precedence is set` );
	ok( op{f} ≢ null, `binary ${spelling} has implementation callback` );
}

ok(
	find_binary("≤"){f} ≡ find_binary("<="){f},
	"less-than-or-equal aliases share an implementation callback",
);
ok(
	find_binary("≥"){f} ≡ find_binary(">="){f},
	"greater-than-or-equal aliases share an implementation callback",
);
ok(
	find_binary("≶"){f} ≡ find_binary("<=>"){f},
	"numeric comparison aliases share an implementation callback",
);
ok(
	find_binary("≷"){f} ≡ find_binary("<=>"){f},
	"reverse numeric comparison alias shares an implementation callback",
);

{
	let op := find_binary("_");
	ok( op ≢ null, "binary _ operator exists" );
	ok( op.is_binary(), "binary _ is marked binary" );
	is( op.get_precedence(), 10, "binary _ precedence is set" );
	ok( op.requires_whitespace(), "binary _ enforces whitespace" );
	ok( op{f} ≢ null, "binary _ has implementation callback" );
}

for ( let spelling in [
	"eq",
	"ne",
	"gt",
	"ge",
	"lt",
	"le",
	"cmp",
	"eqi",
	"nei",
	"gti",
	"gei",
	"lti",
	"lei",
	"cmpi",
	"~",
] ) {
	let op := find_binary(spelling);
	ok( op ≢ null, `binary ${spelling} operator exists` );
	ok( op.is_binary(), `binary ${spelling} is marked binary` );
	is( op.get_precedence(), 5, `binary ${spelling} precedence is set` );
	ok( op{f} ≢ null, `binary ${spelling} has implementation callback` );
}

ok( not find_binary("~").requires_whitespace(), "binary ~ does not require whitespace" );
ok(
	find_binary("~"){f} ≢ find_unary("~"){f},
	"binary ~ and unary ~ have distinct implementation callbacks",
);

for ( let spec in [
	[ "&", 8 ],
	[ "^", 7 ],
	[ "|", 6 ],
] ) {
	let spelling := spec[0];
	let op := find_binary(spelling);
	ok( op ≢ null, `binary ${spelling} operator exists` );
	ok( op.is_binary(), `binary ${spelling} is marked binary` );
	is( op.get_precedence(), spec[1], `binary ${spelling} precedence is set` );
	ok( op{f} ≢ null, `binary ${spelling} has implementation callback` );
}

for ( let spec in [
	[ "⋀", 3 ],
	[ "and", 3 ],
	[ "⊼", 3 ],
	[ "nand", 3 ],
	[ "⊻", 2 ],
	[ "xor", 2 ],
	[ "⋁", 1 ],
	[ "or", 1 ],
] ) {
	let spelling := spec[0];
	let op := find_binary(spelling);
	ok( op ≢ null, `binary ${spelling} operator exists` );
	ok( op.is_binary(), `binary ${spelling} is marked binary` );
	is( op.get_precedence(), spec[1], `binary ${spelling} precedence is set` );
	ok( op{f} ≢ null, `binary ${spelling} has implementation callback` );
}

ok(
	find_binary("⋀"){f} ≡ find_binary("and"){f},
	"logical and aliases share an implementation callback",
);
ok(
	find_binary("⊼"){f} ≡ find_binary("nand"){f},
	"logical nand aliases share an implementation callback",
);
ok(
	find_binary("⊻"){f} ≡ find_binary("xor"){f},
	"logical xor aliases share an implementation callback",
);
ok(
	find_binary("⋁"){f} ≡ find_binary("or"){f},
	"logical or aliases share an implementation callback",
);

for ( let spec in [
	[ "⋃", 9 ],
	[ "union", 9 ],
	[ "⋂", 9 ],
	[ "intersection", 9 ],
	[ "∖", 9 ],
	[ "\\", 9 ],
	[ "∈", 5 ],
	[ "in", 5 ],
	[ "∉", 5 ],
	[ "⊂", 5 ],
	[ "subsetof", 5 ],
	[ "⊃", 5 ],
	[ "supersetof", 5 ],
	[ "⊂⊃", 5 ],
	[ "equivalentof", 5 ],
] ) {
	let spelling := spec[0];
	let op := find_binary(spelling);
	ok( op ≢ null, `binary ${spelling} operator exists` );
	ok( op.is_binary(), `binary ${spelling} is marked binary` );
	is( op.get_precedence(), spec[1], `binary ${spelling} precedence is set` );
	ok( op{f} ≢ null, `binary ${spelling} has implementation callback` );
}

ok(
	find_binary("⋃"){f} ≡ find_binary("union"){f},
	"set union aliases share an implementation callback",
);
ok(
	find_binary("⋂"){f} ≡ find_binary("intersection"){f},
	"set intersection aliases share an implementation callback",
);
ok(
	find_binary("∖"){f} ≡ find_binary("\\"){f},
	"set difference aliases share an implementation callback",
);
ok( find_binary("\\").requires_whitespace(), "binary \\ enforces whitespace" );
ok(
	find_binary("∈"){f} ≡ find_binary("in"){f},
	"membership aliases share an implementation callback",
);
ok(
	find_binary("⊂"){f} ≡ find_binary("subsetof"){f},
	"subset aliases share an implementation callback",
);
ok(
	find_binary("⊃"){f} ≡ find_binary("supersetof"){f},
	"superset aliases share an implementation callback",
);
ok(
	find_binary("⊂⊃"){f} ≡ find_binary("equivalentof"){f},
	"set equivalence aliases share an implementation callback",
);

for ( let spec in [
	[ "≡", 4 ],
	[ "==", 4 ],
	[ "≢", 4 ],
	[ "!=", 4 ],
	[ "can", 5 ],
] ) {
	let spelling := spec[0];
	let op := find_binary(spelling);
	ok( op ≢ null, `binary ${spelling} operator exists` );
	ok( op.is_binary(), `binary ${spelling} is marked binary` );
	is( op.get_precedence(), spec[1], `binary ${spelling} precedence is set` );
	ok( op{f} ≢ null, `binary ${spelling} has implementation callback` );
}

ok(
	find_binary("≡"){f} ≡ find_binary("=="){f},
	"type-aware equality aliases share an implementation callback",
);
ok(
	find_binary("≢"){f} ≡ find_binary("!="){f},
	"type-aware inequality aliases share an implementation callback",
);

for ( let spelling in [
	"sqrt",
	"not",
	"abs",
	"floor",
	"ceil",
	"round",
	"int",
	"uc",
	"lc",
	"length",
	"typeof",
] ) {
	is( find_op(spelling), null, `word-like unary ${spelling} is not supported` );
}

{
	let op := find_binary("?:");
	ok( op ≢ null, "Elvis operator exists" );
	is( op.get_kind(), "ELVIS", "Elvis operator kind is set" );
	is( op.get_precedence(), 1, "Elvis operator precedence is set" );
	ok( op.lexer_should_ignore(), "Elvis is handled by the guarded lexer branch" );
	is( op{f}, null, "Elvis operator uses parser and evaluator support" );
}

done_testing();
