from std/eval import eval;
from std/math import Math;
from test/more import *;

let counter := 1;
is( eval("counter + 1;"), 2, "eval reads caller variables" );
eval("counter += 4;");
is( counter, 5, "eval assignment mutates existing caller variable" );

const String locked := "fixed";
let const_error := exception( function () {
	eval( "locked := \"changed\";" );
} );
ok(
	const_error instanceof Exception,
	"eval respects caller const bindings",
);
is( locked, "fixed", "failed eval assignment leaves caller const unchanged" );

eval("let eval_local := 42;");
let leak_error := exception( function () {
	eval("eval_local;");
} );
ok(
	leak_error instanceof Exception,
	"eval let declarations do not leak to caller scope",
);

let collision_result := null;
let collision_error := exception( function () {
	collision_result := eval(
		"from std/math import Math; Math.pi;"
	);
} );
ok(
	collision_error ≡ null,
	"eval imports do not collide with caller imports",
);
is(
	collision_result,
	Math.pi,
	"eval import collision expression still returns result",
);

eval("from std/string import split;");
let import_leak_error := exception( function () {
	eval( "split(\"a,b\", \",\");" );
} );
ok(
	import_leak_error instanceof Exception,
	"eval imports do not leak to caller scope",
);

eval("function eval_helper () { return 9; }");
let function_leak_error := exception( function () {
	eval("eval_helper();");
} );
ok(
	function_leak_error instanceof Exception,
	"eval function declarations do not leak to caller scope",
);

eval("class EvalLocalThing;");
let class_leak_error := exception( function () {
	eval("new EvalLocalThing();");
} );
ok(
	class_leak_error instanceof Exception,
	"eval class declarations do not leak to caller scope",
);

done_testing();
