xbasic 0.3.1

A library that allows adding a scripting language onto your project with ease. This lets your users write their own arbitrary logic.
Documentation
use xbasic::xbasic::XBasic;

mod common;

#[test]
fn function_empty() {
	common::test_program(
		"
a()

function a()
end function
		",
		"",
	)
}

#[test]
fn function_uncalled() {
	common::test_program(
		"
function a()
    print \"Hello world\"
end function
		",
		"",
	)
}

#[test]
fn function_no_return() {
	common::test_program(
		"
a()

function a()
    print \"hi from function\"
end function
		",
		"hi from function\n",
	)
}

#[test]
fn function_parameter() {
	common::test_program(
		"
a(\"world\", \"hello\")

function a(b, c)
    print c b
end function
		",
		"hello world\n",
	)
}

#[test]
fn function_variable_same_name() {
	common::test_program(
		"
a = 3
print a()
print a

function a()
    return 3 + 4
end function
		",
		"7\n3\n",
	)
}

#[test]
fn function_return() {
	common::test_program(
		"
print a()

function a()
    return 3 + 4
end function
		",
		"7\n",
	)
}

#[test]
fn recursion_ok() {
	common::test_program(
		"
print fib(20)

function fib(n)
    if n = 1 or n = 2 then
        return 1
    end if
    return fib(n - 1) + fib(n - 2)
end function
",
		"6765\n",
	)
}

#[test]
fn recursion_stack_overflow() {
	let tio = common::TestIO::new("");

	let mut xb = XBasic::new(tio);
	assert!(xb
		.run(
			"
function a()
    a()
end function

a()
	"
		)
		.is_err());
	assert!(xb.error_handler.had_errors);
	assert_eq!(
		xb.error_handler.errors,
		["[line 3] Error : Stack overflow."]
	);

	xb.get_io().check();
}

#[test]
fn function_does_not_exist() {
	let tio = common::TestIO::new("");

	let mut xb = XBasic::new(tio);
	assert!(xb
		.run(
			"
a()
	"
		)
		.is_err());
	assert!(xb.error_handler.had_errors);
	assert_eq!(
		xb.error_handler.errors,
		["[line 2] Error at 'a': Not a function."]
	);

	xb.get_io().check();
}

#[test]
fn function_already_defined() {
	let tio = common::TestIO::new("");

	let mut xb = XBasic::new(tio);
	assert!(xb
		.run(
			"
function a()
end function

function a()
end function
	"
		)
		.is_err());
	assert!(xb.error_handler.had_errors);
	assert_eq!(
		xb.error_handler.errors,
		["[line 5] Error at 'a': Function is already defined on line 2."]
	);

	xb.get_io().check();
}

#[test]
fn function_mismatched_arity() {
	let tio = common::TestIO::new("");

	let mut xb = XBasic::new(tio);
	assert!(xb
		.run(
			"
a(1, 2, 3, 4)
function a(b, c, d)
end function
	"
		)
		.is_err());
	assert!(xb.error_handler.had_errors);
	assert_eq!(
		xb.error_handler.errors,
		["[line 2] Error at 'a': Expected 3 arguments, got 4."]
	);

	xb.get_io().check();
}

#[test]
fn function_named_same_as_param() {
	common::test_program(
		"
print a(3)

function a(a)
    return a + 4
end function
		",
		"7\n",
	)
}

#[test]
fn nested_functions() {
	common::test_program(
		"
print a(3)

function a(a)
    return b(a) + 2
end function

function b(c)
    return c + 5
end function
		",
		"10\n",
	)
}

#[test]
fn stack_cool_after_function() {
	common::test_program(
		"
c = 4
print a(3)

b = 3
print b
print c
print b + c

function a(a)
    return b(a) + 2
end function

function b(c)
    return c + 5
end function
		",
		"10\n3\n4\n7\n",
	)
}

#[test]
fn return_in_main() {
	common::test_program(
		"
return
		",
		"",
	)
}

#[test]
fn function_across_multiple_calls() {
	let tio = common::TestIO::new("5\n");

	let mut xb = XBasic::new(tio);
	assert!(xb
		.run(
			"
function a(b)
    return b
end function
	"
		)
		.is_ok());
	assert!(xb.run("print a(5)\n").is_ok());
	assert!(!xb.error_handler.had_errors);

	xb.get_io().check();
}

#[test]
fn function_overwrite_across_runs() {
	let tio = common::TestIO::new("5\n");

	let mut xb = XBasic::new(tio);
	assert!(xb
		.run(
			"
function a(b)
    return b
end function
	"
		)
		.is_ok());
	assert!(xb
		.run(
			"
function a(b)
    return b * 2
end function
	"
		)
		.is_err());
	xb.clear_errors();
	assert!(xb.run("print a(5)\n").is_ok());
	assert!(!xb.error_handler.had_errors);

	xb.get_io().check();
}

#[test]
fn function_variable_edge_case() {
	let tio = common::TestIO::new("done\n");

	let mut xb = XBasic::new(tio);
	assert!(xb
		.run(
			"
function func(b)
a = 0
end function

    func(3)
z = 0
b = 0
    print \"done\"
	",
		)
		.is_ok());
	assert!(!xb.error_handler.had_errors);

	xb.get_io().check();
}

#[test]
fn function_argument_edge_case() {
	let tio = common::TestIO::new("3\ndone\n");

	let mut xb = XBasic::new(tio);
	assert!(xb
		.run(
			"
a = test
function func(b)
c = 0
print b
end function

    func(3)
z = 0
    print \"done\"
	",
		)
		.is_ok());
	assert!(!xb.error_handler.had_errors);

	xb.get_io().check();
}

#[test]
fn function_doesnt_corrupt_stack() {
	let tio = common::TestIO::new("3\n0\n1\n2\n3\n4\n5\n");

	let mut xb = XBasic::new(tio);
	assert!(xb
		.run(
			"
function func(b)
print b
end function

func(3)
for x = 0 to 5
print x
next x
	",
		)
		.is_ok());
	assert!(!xb.error_handler.had_errors);

	xb.get_io().check();
}