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 if_test_true() {
	common::test_program(
		"if 3 = 3 then\nprint \"1\"\nend if\nprint \"done\"\n",
		"1\ndone\n",
	);
}

#[test]
fn if_test_false() {
	common::test_program(
		"if 3 = 4 then\nprint \"1\"\nend if\nprint \"done\"\n",
		"done\n",
	);
}

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

	let mut xb = XBasic::new(tio);
	assert!(xb.run("if 3 = 4 then\n").is_err());
	assert!(xb.error_handler.had_errors);
	assert_eq!(
		xb.error_handler.errors,
		["[line 2] Error at end: Expected END IF after IF statement."]
	);

	xb.get_io().check();
}

// TODO
/*#[test]
fn if_single_line() {
	common::test_program("if 3 = 3 then print \"hi\"\n", "hi\n")
}*/

#[test]
fn if_else_true() {
	common::test_program(
		"if 3 = 3 then\nprint \"1\"\nelse\nprint \"2\"\nend if\nprint \"done\"\n",
		"1\ndone\n",
	);
}

#[test]
fn if_else_false() {
	common::test_program(
		"if 3 = 4 then\nprint \"1\"\nelse\nprint \"2\"\nend if\nprint \"done\"\n",
		"2\ndone\n",
	);
}

#[test]
fn if_elseif() {
	common::test_program(
		"if 3 = 4 then
    print \"1\"
    elseif 5 = 3 then
    print \"2\"
    end if
    print \"done\"
    ",
		"done\n",
	);

	common::test_program(
		"if 3 = 3 then
    print \"1\"
    elseif 5 = 5 then
    print \"2\"
    end if
    print \"done\"
    ",
		"1\ndone\n",
	);

	common::test_program(
		"if 3 = 4 then
    print \"1\"
    elseif 5 = 5 then
    print \"2\"
    end if
    print \"done\"
    ",
		"2\ndone\n",
	);
}

#[test]
fn if_elseif_else() {
	common::test_program(
		"if 3 = 4 then
    print \"1\"
    elseif 5 = 3 then
    print \"2\"
    else
    print 3
    end if
    print \"done\"
    ",
		"3\ndone\n",
	);

	common::test_program(
		"if 3 = 3 then
    print \"1\"
    elseif 5 = 5 then
    print \"2\"
    else
    print 3
    end if
    print \"done\"
    ",
		"1\ndone\n",
	);

	common::test_program(
		"if 3 = 4 then
    print \"1\"
    elseif 5 = 5 then
    print \"2\"
    else
    print 3
    end if
    print \"done\"
    ",
		"2\ndone\n",
	);
}

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

	let mut xb = XBasic::new(tio);
	assert!(xb
		.run(
			"if 5 = 3 then
    print \"hi\"
elseif 3 = 5
    print \"bye\"\
end if
	"
		)
		.is_err());
	assert!(xb.error_handler.had_errors);
	assert_eq!(
		xb.error_handler.errors,
		["[line 3] Error at newline: Expected THEN after ELSEIF statement condition."]
	);

	xb.get_io().check();
}

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

	let mut xb = XBasic::new(tio);
	assert!(xb
		.run(
			"if 5 = 3 then
    print \"hi\"
	"
		)
		.is_err());
	assert!(xb.error_handler.had_errors);
	assert_eq!(
		xb.error_handler.errors,
		["[line 3] Error at end: Expected END IF after IF statement."]
	);

	xb.get_io().check();
}

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

	let mut xb = XBasic::new(tio);
	assert!(xb
		.run(
			"
if 6 = 5 then
    if 5 = 3 then
        print \"hi\"
    end if
    a = 5
	"
		)
		.is_err());
	assert!(xb.error_handler.had_errors);
	assert_eq!(
		xb.error_handler.errors,
		["[line 7] Error at end: Expected END IF after IF statement."]
	);

	xb.get_io().check();
}

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

	let mut xb = XBasic::new(tio);
	assert!(xb
		.run(
			"
for x = 0 to 5
    for y = 0 to 5
        if 6 = 5 then
            if 5 = 3 then
                print \"hi\"
        end if
        a = 5
    next y
next x
	"
		)
		.is_err());
	assert!(xb.error_handler.had_errors);
	assert_eq!(
		xb.error_handler.errors,
		["[line 9] Error at 'y': Expected expression."]
	);

	xb.get_io().check();
}

#[test]
fn if_assignment_edgecase_1() {
	common::test_program(
		"
if 4 = 0 then
    b = 0
end if
a = 4
    ",
		"",
	);
}

#[test]
fn if_assignment_edgecase_2() {
	common::test_program(
		"
a = 0
if 4 = 0 then
    a = 1
end if
a = 4
    ",
		"",
	);
}

#[test]
fn if_assignment_edgecase_3() {
	common::test_program(
		"
if 4 = 0 then
    a = 0
end if
a = 4
    ",
		"",
	);
}

#[test]
fn while_loop() {
	common::test_program(
		"while a < 10
        print a
        a = a + 1
    wend
    ",
		"0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n",
	);
}

#[test]
fn for_loop() {
	common::test_program(
		"for x = 0 to 10
    print x
next x
    ",
		"0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n",
	)
}

#[test]
fn for_loop_backwards() {
	common::test_program(
		"for x = 10 to 0
    print x
next x
    ",
		"",
	)
}

#[test]
fn for_loop_same_number() {
	common::test_program(
		"for x = 10 to 10
    print x
next x
    ",
		"10\n",
	)
}

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

	let mut xb = XBasic::new(tio);
	assert!(xb
		.run(
			"for x = 0 to 10
    print x
next y
	"
		)
		.is_err());
	assert!(xb.error_handler.had_errors);
	assert_eq!(
		xb.error_handler.errors,
		["[line 3] Error at 'y': Incorrect variable after NEXT."]
	);

	xb.get_io().check();
}

#[test]
fn for_loop_edge_case() {
	common::test_program(
		"x = 15
	for x = 0 to x

	    print x

	next x
	print \"x is\" x


	",
		"0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\nx is 16\n",
	)
}

#[test]
fn nested_for_loop() {
	common::test_program(
		"
    a = 0
	for x = 1 to 192
        for y = 1 to 108
            a += 1
        next y
	next x
	print a


	",
		"20736\n",
	);
}