xbasic 0.1.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 mut tio = common::TestIO::new(expected);
	{
		let mut xbb = XBasicBuilder::new(&mut 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!();
			}
		}
	}
	tio.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 wrong_arity() {
	let mut tio = common::TestIO::new("");
	{
		let mut xbb = XBasicBuilder::new(&mut 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."]
		);
	}
	tio.check();
}

#[test]
fn duplicate_native_function() {
	let mut tio = common::TestIO::new("");
	{
		let mut xbb = XBasicBuilder::new(&mut 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."]
		);
	}
	tio.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()
",
		"3\n",
	);
}