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::expr::ExprValue;
use xbasic::xbasic::XBasicBuilder;

mod common;

fn test_program_native_functions<T: 'static>(
	functions: Vec<(&'static str, u8, T)>,
	program: &'static str,
	expected: &'static str,
) where
	T: Fn(Vec<ExprValue>, &mut common::TestIO) -> ExprValue,
{
	let tio = common::TestIO::new(expected);

	let mut xbb = XBasicBuilder::new(tio);

	for (name, arity, native_fn) in functions {
		assert!(xbb
			.define_function(name.to_owned(), arity, native_fn)
			.is_ok());
	}

	let mut xb = xbb.build();

	match xb.run(program) {
		Ok(_) => (),
		Err(_) => {
			for error in xb.error_handler.errors {
				println!("{}", error);
			}
			panic!();
		}
	}

	xb.get_io().check();
}

#[test]
fn basic_native_function() {
	test_program_native_functions(
		vec![("test", 0, |_, _: &mut _| ExprValue::Decimal(123.4))],
		"
print test()
    ",
		"123.4\n",
	);
}

#[test]
fn basic_native_function_argument_order() {
	test_program_native_functions(
		vec![("test", 2, |args: Vec<ExprValue>, _: &mut _| {
			ExprValue::Decimal(args[0].clone().into_decimal() / args[1].clone().into_decimal())
		})],
		"
print test(300, 2)
    ",
		"150\n",
	);
}

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

	let mut xbb = XBasicBuilder::new(tio);
	assert!(xbb
		.define_function("arity".to_string(), 2, |_, _| ExprValue::Decimal(0.0))
		.is_ok(),);
	let mut xb = xbb.build();
	assert!(xb
		.run(
			"
arity(1, 2, 3)
	"
		)
		.is_err());
	assert!(xb.error_handler.had_errors);
	assert_eq!(
		xb.error_handler.errors,
		["[line 2] Error at 'arity': Expected 2 arguments, got 3."]
	);

	xb.get_io().check();
}

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

	let mut xbb = XBasicBuilder::new(tio);
	assert!(xbb
		.define_function("arity".to_string(), 2, |_, _| ExprValue::Decimal(0.0))
		.is_ok());
	assert!(xbb
		.define_function("arity".to_string(), 2, |_, _| ExprValue::Decimal(5.0))
		.is_err());
	let mut xb = xbb.build();
	assert!(xb
		.run(
			"
arity(1, 2, 3)
	"
		)
		.is_err());
	assert!(xb.error_handler.had_errors);
	assert_eq!(
		xb.error_handler.errors,
		["[line 2] Error at 'arity': Expected 2 arguments, got 3."]
	);

	xb.get_io().check();
}

#[test]
fn sum_args() {
	test_program_native_functions(
		vec![("sum", 5, |args: Vec<ExprValue>, _: &mut _| {
			let mut sum: f64 = 0.0;
			for arg in args {
				sum += arg.into_decimal();
			}
			ExprValue::Decimal(sum)
		})],
		"
print sum(1, 3.4, 5.6, 789, -204)
    ",
		"595\n",
	);
}

#[test]
fn native_same_name_function_as_user() {
	test_program_native_functions(
		vec![("sum", 5, |args: Vec<ExprValue>, _: &mut _| {
			let mut sum: f64 = 0.0;
			for arg in args {
				sum += arg.into_decimal();
			}
			ExprValue::Decimal(sum)
		})],
		"
function sum()
    print 1 - 2
end function

sum()
",
		"-1\n",
	);
}

#[test]
fn native_doesnt_corrupt_stack() {
	test_program_native_functions(
		vec![("sum", 5, |args: Vec<ExprValue>, _: &mut _| {
			let mut sum: f64 = 0.0;
			for arg in args {
				sum += arg.into_decimal();
			}
			ExprValue::Decimal(sum)
		})],
		"
function sum(a, b, c, d, e)
    return
end function
sum(1, 2, 3, 4, 5)
for x = 0 to 5
print x
next x
",
		"0\n1\n2\n3\n4\n5\n",
	);
}