basic-lang 0.7.1

The BASIC programming language as it was in the 8-bit era.
Documentation
mod common;
use basic::mach::Runtime;
use common::*;

#[test]
fn test_indirect_error() {
    let mut r = Runtime::default();
    r.enter(r#"10 GOTO 100"#);
    r.enter(r#"RUN"#);
    assert_eq!(exec(&mut r), "?UNDEFINED LINE IN 10:9\n");
}

#[test]
fn test_cont_after_end() {
    let mut r = Runtime::default();
    r.enter(r#"10 A=1"#);
    r.enter(r#"20 END"#);
    r.enter(r#"30 PRINT A"#);
    r.enter(r#"RUN"#);
    assert_eq!(exec(&mut r), "");
    r.enter(r#"CONT"#);
    assert_eq!(exec(&mut r), " 1 \n");
}
#[test]
fn test_cont_after_stop() {
    let mut r = Runtime::default();
    r.enter(r#"10 A=1"#);
    r.enter(r#"20 STOP"#);
    r.enter(r#"30 PRINT A"#);
    r.enter(r#"RUN"#);
    assert_eq!(exec(&mut r), "?BREAK IN 20\n");
    r.enter(r#"CONT"#);
    assert_eq!(exec(&mut r), " 1 \n");
}

#[test]
fn test_dim() {
    let mut r = Runtime::default();
    r.enter(r#"DIM INKEY$(10,10)"#);
    assert_eq!(exec(&mut r), "?SYNTAX ERROR; RESERVED FOR BUILT-IN\n");
    r.enter(r#"DIM LEN(10,10)"#);
    assert_eq!(exec(&mut r), "?SYNTAX ERROR; RESERVED FOR BUILT-IN\n");
    r.enter(r#"DIM X(1000):x(500)=9:?x(501);X(500)"#);
    assert_eq!(exec(&mut r), " 0  9 \n");
    r.enter(r#"z(10)=100:?Z(1);Z(10)"#);
    assert_eq!(exec(&mut r), " 0  100 \n");
}

#[test]
fn test_def_fn() {
    let mut r = Runtime::default();
    r.enter(r#"10 DEF FN(X)=X*2"#);
    r.enter(r#"20 DEF FNA(X,Y)=FN(X)/Y"#);
    r.enter(r#"30 PRINT FNA(1,3)"#);
    r.enter(r#"RUN"#);
    assert_eq!(exec(&mut r), " 0.6666667 \n");
}

#[test]
fn test_deftype() {
    let mut r = Runtime::default();
    r.enter(r#"s$="ess":?s$"#);
    assert_eq!(exec(&mut r), "ess\n");
    r.enter(r#"DEFSTR s:s="foo":?s"#);
    assert_eq!(exec(&mut r), "foo\n");
    r.enter(r#"?s$"#);
    assert_eq!(exec(&mut r), "ess\n");
    r.enter(r#"DEFSTR t:?t"#);
    assert_eq!(exec(&mut r), "\n");
    r.enter(r#"DEFINT i-"#);
    assert_eq!(exec(&mut r), "?SYNTAX ERROR; EXPECTED VARIABLE\n");
    r.enter(r#"DEFINT i-j:i=3.14:?i"#);
    assert_eq!(exec(&mut r), " 3 \n");
    r.enter(r#"DEFINT ii"#);
    assert_eq!(exec(&mut r), "?SYNTAX ERROR\n");
    r.enter(r#"a=1.1:DEFINT a-a:?a"#);
    assert_eq!(exec(&mut r), " 0 \n");
    r.enter(r#"a=1.1:DEFINT a-a:?a"#);
    assert_eq!(exec(&mut r), " 1 \n");
}

#[test]
fn test_erase() {
    let mut r = Runtime::default();
    r.enter(r#"DIM A$(10,10):A$(5,5)="FIVE":PRINT A$(5,5)"#);
    assert_eq!(exec(&mut r), "FIVE\n");
    r.enter(r#"ERASE A$:PRINT A$(5,5)"#);
    assert_eq!(exec(&mut r), "\n");
    r.enter(r#"DIM A$(20):PRINT A$(20)"#);
    assert_eq!(exec(&mut r), "?REDIMENSIONED ARRAY\n");
    r.enter(r#"ERASE A$:DIM A$(20):PRINT A$(20)"#);
    assert_eq!(exec(&mut r), "\n");
}

#[test]
fn test_for_loop_break_with_goto() {
    let mut r = Runtime::default();
    r.enter(r#"10fory=1to2"#);
    r.enter(r#"20forx=8to9"#);
    r.enter(r#"30?y;x"#);
    r.enter(r#"40goto60"#);
    r.enter(r#"50next"#);
    r.enter(r#"60nexty"#);
    r.enter(r#"run"#);
    assert_eq!(exec(&mut r), " 1  8 \n 2  8 \n");
}

#[test]
fn test_for_loop_always_runs_once() {
    let mut r = Runtime::default();
    r.enter(r#"FOR I=3 TO 0:PRINT I:NEXT I"#);
    assert_eq!(exec(&mut r), " 3 \n");
}

#[test]
fn test_for_loop_assign_step_after_var() {
    let mut r = Runtime::default();
    r.enter(r#"I=1:FOR I=3 TO 9 STEP I:PRINT I;:NEXT"#);
    assert_eq!(exec(&mut r), " 3  6  9 \n");
}

#[test]
fn test_gosub_return() {
    let mut r = Runtime::default();
    r.enter(r#"10 GOSUB 100"#);
    r.enter(r#"20 PRINT "WORLD""#);
    r.enter(r#"90 END"#);
    r.enter(r#"100 PRINT "HELLO ";"#);
    r.enter(r#"110 RETURN"#);
    r.enter(r#"RUN"#);
    assert_eq!(exec(&mut r), "HELLO WORLD\n");
}

#[test]
fn test_if_then() {
    let mut r = Runtime::default();
    r.enter(r#"if 1 then ? "one""#);
    assert_eq!(exec(&mut r), "one\n");
}

#[test]
fn test_if_then_else() {
    let mut r = Runtime::default();
    r.enter(r#"if 0 then ? "one" else ? "two";:?2"#);
    assert_eq!(exec(&mut r), "two 2 \n");
    r.enter(r#"if 1 then ? "one" else ? "two":?2"#);
    assert_eq!(exec(&mut r), "one\n");
    r.enter(r#"if 1 then ? "one";:?2"#);
    assert_eq!(exec(&mut r), "one 2 \n");
    r.enter(r#"if 0 then ? "one";:?2"#);
    assert_eq!(exec(&mut r), "");
}

#[test]
fn test_input_to_array() {
    let mut r = Runtime::default();
    r.enter(r#"input a%,b(a%):print a%;: print b(2-a%);"#);
    assert_eq!(exec(&mut r), "? ");
    r.enter(r#"1,2"#);
    assert_eq!(exec(&mut r), " 1  2 \n");
}

#[test]
fn test_let_mid_statement() {
    let mut r = Runtime::default();
    r.enter(r#"A$="PORTLAND, ME":MID$(A$,11)="OR":?A$"#);
    assert_eq!(exec(&mut r), "PORTLAND, OR\n");
    r.enter(r#"LET MID$(A$,11)="MEH":?A$"#);
    assert_eq!(exec(&mut r), "PORTLAND, ME\n");
    r.enter(r#"MID$(A$,0)="Portland":?A$"#);
    assert_eq!(exec(&mut r), "?ILLEGAL FUNCTION CALL; POSITION IS ZERO\n");
    r.enter(r#"MID$(A$,1)="Portland":?A$"#);
    assert_eq!(exec(&mut r), "Portland, ME\n");
    r.enter(r#"MID$(A$,5,1)="LALA":?A$"#);
    assert_eq!(exec(&mut r), "PortLand, ME\n");
    r.enter(r#"MID$(A$,1,0)="LALA":?A$"#);
    assert_eq!(exec(&mut r), "PortLand, ME\n");
    r.enter(r#"A$(5)="PORTLAND, ME":MID$(A$(5),11)="OR":?A$(5)"#);
    assert_eq!(exec(&mut r), "PORTLAND, OR\n");
}

#[test]
fn test_new() {
    let mut r = Runtime::default();
    r.enter(r#"10 A=1"#);
    r.enter(r#"20 NEW"#);
    r.enter(r#"RUN:PRINT 9"#);
    assert_eq!(exec(&mut r), "");
    r.enter(r#"PRINT A"#);
    assert_eq!(exec(&mut r), " 0 \n");
    r.enter(r#"LIST"#);
    assert_eq!(exec(&mut r), "");
}

#[test]
fn test_on_gosub() {
    let mut r = Runtime::default();
    r.enter(r#"10 X=2"#);
    r.enter(r#"20 ON X GOSUB 100,200"#);
    r.enter(r#"30 PRINT 30:END"#);
    r.enter(r#"100 PRINT 100;"#);
    r.enter(r#"200 PRINT 200;"#);
    r.enter(r#"300 RETURN"#);
    r.enter(r#"RUN"#);
    assert_eq!(exec(&mut r), " 200  30 \n");
}

#[test]
fn test_on_gosub_neg() {
    let mut r = Runtime::default();
    r.enter(r#"10 X=-1"#);
    r.enter(r#"20 ON X GOSUB 100,200"#);
    r.enter(r#"30 PRINT 30:END"#);
    r.enter(r#"100 PRINT 100;"#);
    r.enter(r#"200 PRINT 200;"#);
    r.enter(r#"300 RETURN"#);
    r.enter(r#"RUN"#);
    assert_eq!(exec(&mut r), "?ILLEGAL FUNCTION CALL IN 20\n");
}

#[test]
fn test_on_gosub_invalid() {
    let mut r = Runtime::default();
    r.enter(r#"10 X=3"#);
    r.enter(r#"20 ON X GOSUB 100,200"#);
    r.enter(r#"30 PRINT 30:END"#);
    r.enter(r#"100 PRINT 100;"#);
    r.enter(r#"200 PRINT 200;"#);
    r.enter(r#"300 RETURN"#);
    r.enter(r#"RUN"#);
    assert_eq!(exec(&mut r), " 30 \n");
}

#[test]
fn test_read_data() {
    let mut r = Runtime::default();
    r.enter(r#"10 READ A, A$"#);
    r.enter(r#"20 PRINT A; A$"#);
    r.enter(r#"30 DATA 99, "Red Balloons"#);
    r.enter(r#"RUN"#);
    assert_eq!(exec(&mut r), " 99 Red Balloons\n");
}

#[test]
fn test_restore_data() {
    let mut r = Runtime::default();
    r.enter(r#"10 DATA 10"#);
    r.enter(r#"20 DATA 20"#);
    r.enter(r#"30 DATA -30"#);
    r.enter(r#"READ A,B,C:PRINT A;B;C"#);
    assert_eq!(exec(&mut r), " 10  20 -30 \n");
    r.enter(r#"RESTORE:READ A,B,C:PRINT A;B;C"#);
    assert_eq!(exec(&mut r), " 10  20 -30 \n");
    r.enter(r#"RESTORE 30:READ A:PRINT A"#);
    assert_eq!(exec(&mut r), "-30 \n");
}

#[test]
fn test_swap() {
    let mut r = Runtime::default();
    r.enter(r#"SWAP A,B!:PRINT A"#);
    assert_eq!(exec(&mut r), " 0 \n");
    r.enter(r#"A=1:B=2:SWAPA,B:PRINTA;B"#);
    assert_eq!(exec(&mut r), " 2  1 \n");
    r.enter(r#"DEFSTR S:S="S":A$="A":SWAP S,A$:PRINTA$;S"#);
    assert_eq!(exec(&mut r), "SA\n");
    r.enter(r#"A%=127:SWAP A%,B#"#);
    assert_eq!(exec(&mut r), "?TYPE MISMATCH\n");
    r.enter(r#"PRINT A%"#);
    assert_eq!(exec(&mut r), " 127 \n");
}

#[test]
fn test_tron_troff() {
    let mut r = Runtime::default();
    r.enter(r#"10 FOR I = 1 TO 5"#);
    r.enter(r#"20 IF I = 2 THEN TRON"#);
    r.enter(r#"30 IF I = 4 THEN TROFF"#);
    r.enter(r#"40 PRINT I"#);
    r.enter(r#"50 NEXT I"#);
    r.enter(r#"run"#);
    assert_eq!(
        exec(&mut r),
        " 1 \n[30][40] 2 \n[50][20][30][40] 3 \n[50][20][30] 4 \n 5 \n"
    );
}

#[test]
fn test_while_wend_nested() {
    let mut r = Runtime::default();
    r.enter(r#"10 WHILE I<2:I=I+1:PRINT I;"#);
    r.enter(r#"RUN"#);
    assert_eq!(exec(&mut r), "?WHILE WITHOUT WEND IN 10:4\n");
    r.enter(r#"30 WEND"#);
    r.enter(r#"RUN"#);
    assert_eq!(exec(&mut r), " 1  2 \n");
    r.enter(r#"40 WEND"#);
    r.enter(r#"RUN"#);
    assert_eq!(exec(&mut r), "?WEND WITHOUT WHILE IN 40:4\n");
    r.enter(r#"20 WHILE J<2:J=J+1:PRINT J+10;"#);
    r.enter(r#"RUN"#);
    assert_eq!(exec(&mut r), " 1  11  12  2 \n");
    r.enter(r#"35 J=0"#);
    r.enter(r#"RUN"#);
    assert_eq!(exec(&mut r), " 1  11  12  2  11  12 \n");
}

#[test]
fn test_while_wend_not_nested() {
    let mut r = Runtime::default();
    r.enter(r#"10 WHILE A<2:A=A+1:PRINT A;:WEND"#);
    r.enter(r#"20 WHILE B<2:B=B+1:PRINT B;:WEND"#);
    r.enter(r#"RUN"#);
    assert_eq!(exec(&mut r), " 1  2  1  2 \n");
}