zuzu-rust 0.6.0

Rust implementation of ZuzuScript
Documentation
from test/more import *;
from std/internals import
	class_name,
	classof,
	object_slots,
	ansi_esc,
	ref_id,
	make_instance,
	setprop,
	getprop,
	setupperprop,
	getupperprop,
	load_module;
class Thing {
	let Number count := 7;
}
let x := new Thing();
is( class_name(x), "Thing", "class_name returns object class name" );
ok( classof(x) instanceof Class, "classof returns a Class value" );
ok( classof(x) ≡ Thing, "classof returns object class" );
is( typeof object_slots(x), "Dict", "object_slots returns dict for objects" );
is( object_slots(x){count}, 7, "object_slots exposes public members" );
ok( ref_id(x) ≢ null, "ref_id returns a value for object references" );
is( class_name(123), null, "class_name returns null for non-objects" );
is( classof(null), null, "classof returns null for null" );
is( classof(123), null, "classof returns null for numbers" );
is( classof("abc"), null, "classof returns null for strings" );
is( classof(true), null, "classof returns null for booleans" );
ok( classof([]) ≡ Array, "classof returns Array for arrays" );
ok( classof({}) ≡ Dict, "classof returns Dict for dicts" );
let internals_set_class := classof(<< >>);
ok( internals_set_class instanceof Class, "classof returns a Class for sets" );
ok(
	new internals_set_class() instanceof Set,
	"classof returns constructable Set class for sets",
);
ok( classof(<<< >>>) ≡ Bag, "classof returns Bag for bags" );
ok(
	classof(new PairList()) ≡ PairList,
	"classof returns PairList for pairlists",
);
ok(
	classof(new Pair(pair: [ "key", "value" ])) ≡ Pair,
	"classof returns Pair for pairs",
);
is( length ansi_esc(), 1, "ansi_esc returns one character" );
is( getprop("path-phase"), null, "getprop returns null for missing key" );
setprop( "path-phase", "root" );
if ( true ) {
	is( getprop("path-phase"), "root", "nested block inherits props" );
	setprop( "path-phase", "child" );
	is( getprop("path-phase"), "child", "child scope can shadow value" );
}
is( getprop("path-phase"), "root", "parent scope is unchanged by child" );
function set_myprop ( value, level := 1 ) {
	setupperprop( level, "myprop", value );
}
is( getprop("myprop"), null, "myprop starts as null in current scope" );
{
	set_myprop(42);
	is( getprop("myprop"), 42, "setupperprop writes caller scope by level" );
}
is( getprop("myprop"), null, "setupperprop did not leak outside block scope" );
function get_myprop ( level := 1 ) {
	return getupperprop( level, "myprop" );
}
{
	setprop( "myprop", 99 );
	is( get_myprop(1), 99, "getupperprop reads caller scope by level" );
}
like(
	exception( function() {
		setprop( 123, "bad" );
	} ),
	/getprop|setprop key must be String/,
	"setprop rejects non-string keys"
);
let loaded_module := load_module( "test/internals_loader" );
is( typeof loaded_module, "Dict", "load_module returns a Dict" );
is( loaded_module{public_value}, 41, "load_module exposes public values" );
is(
	loaded_module{_secret_value},
	99,
	"load_module exposes underscore-prefixed values",
);
is(
	loaded_module{public_add}( 2, 3 ),
	5,
	"load_module exposes public functions",
);
is(
	loaded_module{_secret_add}( 1, 2 ),
	102,
	"load_module exposes underscore-prefixed functions",
);
is(
	typeof loaded_module{LoaderProbe},
	"Class",
	"load_module exposes classes",
);
let loaded_add := load_module( "test/internals_loader", "public_add" );
is( loaded_add( 4, 5 ), 9, "load_module can return one named symbol" );
is(
	load_module( "test/internals_loader", "_secret_value" ),
	99,
	"load_module can return one underscore-prefixed symbol",
);
like(
	exception( function() {
		load_module( "test/internals_loader", "missing_symbol" );
	} ),
	/no export|not export|missing/i,
	"load_module throws for a missing symbol",
);
like(
	exception( function() {
		load_module( "test/internals_missing" );
	} ),
	/not found|cannot find|unable to resolve/i,
	"load_module throws for a missing module",
);
__global__{internals_builds} := 0;
class BuildProbe {
	let Number value := 5;
	let String label := "unset";

	method __build__ () {
		__global__{internals_builds} := __global__{internals_builds} + 1;
		value := value + 10;
	}

	method describe () {
		return label _ ":" _ value;
	}
}
let built := new BuildProbe( label: "new" );
is( __global__{internals_builds}, 1, "new calls __build__" );
is( built.describe(), "new:15", "__build__ can mutate normal instances" );
let raw := make_instance( BuildProbe, { value: 7, label: "raw" } );
is( __global__{internals_builds}, 1, "make_instance skips __build__" );
is( class_name(raw), "BuildProbe", "make_instance returns an instance" );
is( raw.describe(), "raw:7", "make_instance installs slot values" );
let defaulted := make_instance(BuildProbe);
is( __global__{internals_builds}, 1, "make_instance without slots skips __build__" );
is( defaulted.describe(), "unset:5", "make_instance keeps field defaults" );
__global__{internals_parent_builds} := 0;
__global__{internals_child_builds} := 0;
class BuildParent {
	let Number parent_value := 2;

	method __build__ () {
		__global__{internals_parent_builds} :=
			__global__{internals_parent_builds} + 1;
		parent_value := parent_value + 100;
	}
}
class BuildChild extends BuildParent {
	let Number child_value := 3;

	method __build__ () {
		__global__{internals_child_builds} :=
			__global__{internals_child_builds} + 1;
		child_value := child_value + 1000;
	}

}
let inherited := make_instance(
	BuildChild,
	{
		parent_value: 4,
		child_value: 6,
	},
);
is(
	__global__{internals_parent_builds},
	0,
	"make_instance skips inherited __build__ hooks",
);
is(
	__global__{internals_child_builds},
	0,
	"make_instance skips subclass __build__ hooks",
);
let inherited_slots := object_slots(inherited);
is(
	inherited_slots{parent_value} + inherited_slots{child_value},
	10,
	"make_instance initializes inherited fields",
);
done_testing();