├── .gitignore ├── tests ├── program │ ├── cls.bas │ ├── assign.bas │ ├── goto.bas │ ├── while_wend.bas │ ├── rnd.bas │ ├── color.bas │ ├── locate.bas │ ├── modulus.bas │ ├── graphics.bas │ ├── sub.bas │ ├── do_loop.bas │ ├── if_then.bas │ ├── for_next.bas │ ├── function.bas │ ├── array.bas │ ├── exit.bas │ ├── peek_poke.bas │ ├── dim.bas │ ├── select.bas │ └── get_put.bas ├── bitmap │ ├── get_put.bmp │ ├── graphics1.bmp │ └── graphics2.bmp └── headless.rs ├── .github └── img │ ├── logo.png │ ├── profile.png │ ├── raycast.png │ └── hello_world.png ├── src ├── lib.rs ├── tests.rs ├── token │ ├── input.rs │ └── mod.rs ├── main.rs ├── syntax │ ├── literal.rs │ └── expr.rs └── eval │ ├── palette.rs │ └── charset.rs ├── examples ├── hello_world.bas ├── demo.bas ├── sieve.bas └── raycast.bas ├── LICENSE-MIT ├── Cargo.toml ├── LICENSE-APACHE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock -------------------------------------------------------------------------------- /tests/program/cls.bas: -------------------------------------------------------------------------------- 1 | PRINT "one" 2 | 3 | YIELD 4 | CLS 5 | 6 | PRINT "two" -------------------------------------------------------------------------------- /.github/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attackgoat/jw-basic/HEAD/.github/img/logo.png -------------------------------------------------------------------------------- /.github/img/profile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attackgoat/jw-basic/HEAD/.github/img/profile.png -------------------------------------------------------------------------------- /.github/img/raycast.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attackgoat/jw-basic/HEAD/.github/img/raycast.png -------------------------------------------------------------------------------- /tests/bitmap/get_put.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attackgoat/jw-basic/HEAD/tests/bitmap/get_put.bmp -------------------------------------------------------------------------------- /tests/bitmap/graphics1.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attackgoat/jw-basic/HEAD/tests/bitmap/graphics1.bmp -------------------------------------------------------------------------------- /tests/bitmap/graphics2.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attackgoat/jw-basic/HEAD/tests/bitmap/graphics2.bmp -------------------------------------------------------------------------------- /.github/img/hello_world.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attackgoat/jw-basic/HEAD/.github/img/hello_world.png -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod eval; 2 | pub mod syntax; 3 | pub mod token; 4 | 5 | #[cfg(test)] 6 | pub(self) mod tests; 7 | -------------------------------------------------------------------------------- /examples/hello_world.bas: -------------------------------------------------------------------------------- 1 | FOR c = 1 to 16 2 | COLOR c, c + 15 3 | PRINT " Hello, world! " 4 | NEXT -------------------------------------------------------------------------------- /tests/program/assign.bas: -------------------------------------------------------------------------------- 1 | myVar = 42 2 | 3 | PRINT myVar 4 | 5 | myVar = myVar + 1 6 | 7 | PRINT myVar 8 | 9 | PRINT myVar + 1 -------------------------------------------------------------------------------- /tests/program/goto.bas: -------------------------------------------------------------------------------- 1 | 10 PRINT "Hello" 2 | YIELD 3 | GOTO Again 4 | 20 PRINT "Goodbye" 5 | PRINT "BYE!!!" 6 | Again: GOTO 10 -------------------------------------------------------------------------------- /tests/program/while_wend.bas: -------------------------------------------------------------------------------- 1 | a = 0 2 | WHILE a < 2 3 | a = a + 1 4 | PRINT a 5 | WEND 6 | 7 | WHILE a > 0 8 | a = a - 1 9 | PRINT a 10 | WEND -------------------------------------------------------------------------------- /tests/program/rnd.bas: -------------------------------------------------------------------------------- 1 | x% = INT(RND * 6.0) + 1 2 | y% = INT(RND * 6.0) + 1 3 | PRINT "Roll of two dice: "; x%; " and "; y% 4 | PRINT RND 5 | PRINT RND! 6 | PRINT RND() 7 | PRINT RND!() 8 | -------------------------------------------------------------------------------- /tests/program/color.bas: -------------------------------------------------------------------------------- 1 | COLOR 14, 4 2 | PRINT " !DANGER! " 3 | 4 | COLOR 4@, 0@ 5 | PRINT " MAY EXPLODE! " 6 | 7 | PRINT 8 | 9 | COLOR 7 10 | PRINT "(do not shake)" 11 | 12 | COLOR 2 13 | PRINT "GREEN" -------------------------------------------------------------------------------- /tests/program/locate.bas: -------------------------------------------------------------------------------- 1 | CLS 2 | LOCATE 0 3 | PRINT "HELLO" 4 | 5 | LOCATE 15 6 | PRINT "GOODBYE" 7 | 8 | LOCATE 7, 14 9 | PRINT "BASIC" 10 | 11 | LOCATE 0, 30 12 | PRINT 10 13 | 14 | LOCATE 15, 30 15 | PRINT 1 -------------------------------------------------------------------------------- /tests/program/modulus.bas: -------------------------------------------------------------------------------- 1 | PRINT 10@ MOD 2@ 2 | PRINT 10@ MOD 12@ 3 | PRINT 3@ MOD 2@ 4 | 5 | PRINT 10.0! MOD 2.0! 6 | PRINT 10.0! MOD 12.0! 7 | PRINT 3.0! MOD 2.0! 8 | 9 | PRINT 10% MOD 2% 10 | PRINT 10% MOD 12% 11 | PRINT 3% MOD 2% -------------------------------------------------------------------------------- /src/tests.rs: -------------------------------------------------------------------------------- 1 | use super::token::Span; 2 | 3 | pub fn span<'a>(offset: usize, line: u32, bytes: &'a [u8]) -> Span<'a> { 4 | assert!(offset < bytes.len()); 5 | 6 | unsafe { 7 | // Safety: We checked the offset above 8 | Span::new_from_raw_offset(offset, line, &bytes[offset..], ()) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /tests/program/graphics.bas: -------------------------------------------------------------------------------- 1 | LINE (10, 10)-(20, 20), 4@ 2 | 3 | LINE (5, 5), 1@ 4 | 5 | x = 11 6 | y = 10 7 | 8 | LINE (x, y) - (x + 20, y - 10), 2@ 9 | 10 | RECTANGLE (2, 20) - (7, 40), 2@, TRUE 11 | RECTANGLE (3, 21) - (6, 39), 6@ 12 | 13 | YIELD 14 | 15 | PALETTE 4@, &HFF@, &HFF@, &HFF@ 16 | PALETTE 1@, &HFF@, 0@, 0@ 17 | PALETTE 2@, 0@, 0@, &HFF@ 18 | 19 | PSET (50, 0), 15@ 20 | PSET (50, 2), 15@ 21 | PSET (50, 2), 26@ 22 | -------------------------------------------------------------------------------- /tests/program/sub.bas: -------------------------------------------------------------------------------- 1 | SUB mySub 2 | PRINT "A" 3 | END SUB 4 | 5 | SUB mySub2() 6 | PRINT "B" 7 | END SUB 8 | 9 | SUB mySub3 ( ) 10 | mySub 11 | CALL mySub 12 | mySub2 13 | END SUB 14 | 15 | SUB mySub4 (letter$) 16 | PRINT letter 17 | END SUB 18 | 19 | SUB mySub5(lhs$, rhs$) 20 | PRINT lhs, rhs$ 21 | END SUB 22 | 23 | mySub3 24 | mySub4 "C" 25 | CALL mySub4 ("D") 26 | mySub5 "<", ">" 27 | 28 | PRINT "OK" 29 | -------------------------------------------------------------------------------- /examples/demo.bas: -------------------------------------------------------------------------------- 1 | DIM myVar$ = "Variable initialization is optional" 2 | DIM answer% = 42, question? = TRUE 3 | DIM latitude! 4 | 5 | ' Type specifiers are not required: 6 | latitude = 100.0 7 | 8 | IF question THEN 9 | latitude! = -100.0! 10 | END IF 11 | 12 | CLS 13 | PRINT "Hello, there!", myVar 14 | 15 | ' A diagonal red line 16 | LINE (0, 13) - (159, 21), 4@ 17 | 18 | ' Some colorful boxes 19 | FOR c = 25 TO 95 STEP 3 20 | RECTANGLE (c - 5, c - 3) - (159, c), BYTE(c), TRUE 21 | NEXT -------------------------------------------------------------------------------- /tests/program/do_loop.bas: -------------------------------------------------------------------------------- 1 | i = 0 2 | 3 | DO WHILE i < 2 4 | i = i + 1 5 | PRINT i 6 | LOOP 7 | 8 | PRINT 9 | 10 | DO UNTIL i = 0 11 | i = i - 1 12 | PRINT i 13 | LOOP 14 | 15 | PRINT 16 | 17 | DO 18 | i = i + 1 19 | PRINT i 20 | LOOP WHILE i < 2 21 | 22 | PRINT 23 | 24 | DO 25 | i = i - 1 26 | PRINT i 27 | LOOP UNTIL i = 0 28 | 29 | PRINT 30 | 31 | DO 32 | i = i + 1 33 | PRINT i 34 | 35 | IF i = 2 THEN 36 | END 37 | END IF 38 | LOOP -------------------------------------------------------------------------------- /tests/program/if_then.bas: -------------------------------------------------------------------------------- 1 | a = 1 2 | 3 | IF a = 2 THEN 4 | PRINT "Bad1" 5 | END IF 6 | 7 | if a = 2 then 8 | PRINT "bad2" 9 | Else THEN 10 | Print "AOK" 11 | END if 12 | 13 | IF a = 1 THEN 14 | PRINT "OK" 15 | END IF 16 | 17 | b = 2 18 | 19 | if a = 1 AND b = 2 THEN 20 | PRINT "Great!" 21 | END IF 22 | 23 | if a = 2 THEN 24 | PRINT "Not good" 25 | Else if a = 1 THEN 26 | PRINT "Hey" 27 | Else if b = 2 THEN 28 | PRINT "Oh no.." 29 | Else then 30 | PRINT "Boom" 31 | END IF -------------------------------------------------------------------------------- /examples/sieve.bas: -------------------------------------------------------------------------------- 1 | REM See: https://en.wikipedia.org/wiki/Byte_Sieve 2 | REM Eratosthenes Sieve Prime Number Program in BASIC 3 | 1 SIZE = 8190 4 | 2 DIM FLAGS(8191) 5 | 3 PRINT "Only 1 iteration" 6 | 5 COUNT = 0 7 | 6 FOR I = 0 TO SIZE 8 | 7 FLAGS (I) = 1 9 | 8 NEXT I 10 | 9 FOR I = 0 TO SIZE 11 | 10 IF FLAGS (I) = 0 THEN 12 | GOTO 18 13 | END IF 14 | 11 PRIME = I+I + 3 15 | 12 K = I + PRIME 16 | 13 IF K > SIZE THEN 17 | GOTO 17 18 | END IF 19 | 14 FLAGS (K) = 0 20 | 15 K = K + PRIME 21 | 16 GOTO 13 22 | 17 COUNT = COUNT + 1 23 | 18 NEXT I 24 | 19 PRINT COUNT," PRIMES" -------------------------------------------------------------------------------- /tests/program/for_next.bas: -------------------------------------------------------------------------------- 1 | FOR i = 0 TO 3 STEP 1 2 | print i 3 | NEXT 4 | 5 | color 4 6 | 7 | FOR i = 0 TO 3 STEP 2 8 | print i 9 | NEXT 10 | 11 | color 1 12 | 13 | FOR i = 4 TO 6 14 | print i 15 | NEXT 16 | 17 | color 2 18 | 19 | 20 | 21 | a = 0 22 | 23 | 24 | 25 | FOR j = 10 TO 6 step -1 26 | 27 | for k = 2 To 4 28 | FOR l = 9 TO 13 STep 2 29 | a = a + j + k + l 30 | NexT l 31 | 32 | NEXT 33 | print a, j 34 | 35 | NEXT j 36 | 37 | color 6 38 | 39 | x = 4.2 40 | for z = 1.0 to 9.2 step 2.33 41 | x = x + z 42 | next 43 | 44 | print x 45 | print "OK" -------------------------------------------------------------------------------- /tests/program/function.bas: -------------------------------------------------------------------------------- 1 | FUNCTION func1? 2 | END FUNCTION 3 | 4 | FUNCTION func2? 5 | func2 = TRUE 6 | END FUNCTION 7 | 8 | FUNCTION func3? 9 | func3 = func2() 10 | END FUNCTION 11 | 12 | FUNCTION func4%(x%, y%) 13 | IF x > y THEN 14 | func4 = x 15 | ELSE THEN 16 | func4 = y 17 | END IF 18 | END FUNCTION 19 | 20 | globalVar = "Hello, world" 21 | 22 | FUNCTION func5$ 23 | func5 = globalVar 24 | END FUNCTION 25 | 26 | PRINT func1() 27 | PRINT func2() 28 | PRINT func3() 29 | PRINT func4(2,5) 30 | PRINT func4(899,123) 31 | PRINT func5() 32 | PRINT "OK1" 33 | 34 | FUNCTION areEqual?(lhs$, rhs$) 35 | IF lhs = rhs THEN 36 | areEqual = TRUE 37 | ELSE THEN 38 | areEqual = FALSE 39 | END IF 40 | END FUNCTION 41 | 42 | myGlobal = 0 43 | 44 | FUNCTION changeGlobal% 45 | myGlobal = myGlobal + 1 46 | changeGlobal = myGlobal 47 | END FUNCTION 48 | 49 | PRINT areEqual("Apples", "Oranges") 50 | PRINT changeGlobal() 51 | PRINT "OK2" -------------------------------------------------------------------------------- /tests/program/array.bas: -------------------------------------------------------------------------------- 1 | DIM bunch(3 TO 4, 6 TO 8, 45 TO 46, 77 TO 77) 2 | 3 | bunch(3, 6, 45, 77) = 90 4 | bunch(3, 7, 45, 77) = 91 5 | bunch(3, 8, 45, 77) = 92 6 | bunch(4, 6, 45, 77) = 93 7 | bunch(4, 7, 45, 77) = 94 8 | bunch(4, 8, 45, 77) = 95 9 | bunch(3, 6, 46, 77) = 96 10 | bunch(3, 7, 46, 77) = 97 11 | bunch(3, 8, 46, 77) = 98 12 | bunch(4, 6, 46, 77) = 99 13 | bunch(4, 7, 46, 77) = 100 14 | bunch(4, 8, 46, 77) = 101 15 | 16 | PRINT bunch(3, 6, 45, 77) 17 | PRINT bunch(3, 7, 45, 77) 18 | PRINT bunch(3, 8, 45, 77) 19 | PRINT bunch(4, 6, 45, 77) 20 | PRINT bunch(4, 7, 45, 77) 21 | PRINT bunch(4, 8, 45, 77) 22 | PRINT bunch(3, 6, 46, 77) 23 | PRINT bunch(3, 7, 46, 77) 24 | PRINT bunch(3, 8, 46, 77) 25 | PRINT bunch(4, 6, 46, 77) 26 | PRINT bunch(4, 7, 46, 77) 27 | PRINT bunch(4, 8, 46, 77) 28 | 29 | DIM myBooleans?(3) 30 | myBooleans(0) = TRUE 31 | 32 | DIM myBytes@(3) 33 | myBytes(0) = &haa@ 34 | 35 | DIM myFloats!(3) 36 | myFloats(0) = 4.2 37 | 38 | DIM myStrings$(3) 39 | myStrings(0) = ":)" 40 | 41 | PRINT myBooleans(0),myBooleans(1),myBytes(0),myBytes(1),myFloats(0),myFloats(1),myStrings(0),myStrings(1),"!" -------------------------------------------------------------------------------- /tests/program/exit.bas: -------------------------------------------------------------------------------- 1 | total = 0 2 | 3 | FOR i = 1 TO 1000 4 | total = total + 1 5 | EXIT FOR 6 | total = 0 7 | NEXT i 8 | 9 | PRINT total 10 | 11 | DO 12 | total = total + 1 13 | EXIT DO 14 | total = 0 15 | LOOP 16 | 17 | PRINT total 18 | 19 | DO UNTIL FALSE 20 | total = total + 1 21 | EXIT DO 22 | total = 0 23 | LOOP 24 | 25 | PRINT total 26 | 27 | DO WHILE TRUE 28 | total = total + 1 29 | EXIT DO 30 | total = 0 31 | LOOP 32 | 33 | PRINT total 34 | 35 | DO 36 | total = total + 1 37 | EXIT DO 38 | total = 0 39 | LOOP UNTIL FALSE 40 | 41 | PRINT total 42 | 43 | DO 44 | total = total + 1 45 | EXIT DO 46 | total = 0 47 | LOOP WHILE TRUE 48 | 49 | PRINT total 50 | 51 | WHILE TRUE 52 | total = total + 1 53 | EXIT WHILE 54 | total = 0 55 | WEND 56 | 57 | PRINT total 58 | 59 | SUB TEST 60 | total = total + 1 61 | EXIT SUB 62 | total = 0 63 | END SUB 64 | 65 | TEST 66 | 67 | PRINT total 68 | 69 | FUNCTION TEST% 70 | TEST% = total + 1 71 | EXIT FUNCTION 72 | TEST% = 0 73 | END FUNCTION 74 | 75 | PRINT TEST%() -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 John Wells 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "jw-basic" 3 | version = "0.1.0" 4 | authors = ["John Wells "] 5 | edition = "2021" 6 | license = "MIT OR Apache-2.0" 7 | readme = "README.md" 8 | description = "A BASIC language interpreter. Does not conform to existing standards. Mostly a toy." 9 | 10 | [features] 11 | default = [] 12 | profile-with-puffin = ["dep:puffin_egui", "screen-13/profile-with-puffin", "screen-13-egui"] 13 | 14 | [dependencies] 15 | anyhow = "1.0" 16 | bytemuck = "1.12" 17 | clap = { version = "4.1.4", features = ["derive"] } 18 | glam = "0.25" 19 | inline-spirv = "0.1" 20 | log = "0.4" 21 | nom = "7.1" 22 | nom_locate = "4.1" 23 | pretty_env_logger = "0.5" 24 | profiling = "1.0" 25 | puffin_egui = { version = "0.25", optional = true } 26 | rand = "0.8" 27 | screen-13 = { git = "https://github.com/attackgoat/screen-13.git", tag = "v0.9.4" } 28 | screen-13-egui = { git = "https://github.com/attackgoat/screen-13.git", tag = "v0.9.4", optional = true } 29 | serde = { version = "1.0", optional = true, default-features = false, features = ["derive"] } 30 | 31 | [dev-dependencies] 32 | bmp = "0.5" 33 | lazy_static = "1.4" 34 | -------------------------------------------------------------------------------- /tests/program/peek_poke.bas: -------------------------------------------------------------------------------- 1 | PRINT PEEK(0) 2 | PRINT PEEK?(0) 3 | PRINT PEEK@(0) 4 | PRINT PEEK!(0) 5 | PRINT PEEK%(0) 6 | PRINT PEEK$(0) 7 | PRINT "OK1" 8 | 9 | YIELD 10 | CLS 11 | 12 | POKE 0, TRUE 13 | PRINT PEEK(0) 14 | PRINT PEEK?(0) 15 | PRINT PEEK@(0) 16 | PRINT PEEK!(0) 17 | PRINT PEEK%(0) 18 | PRINT PEEK$(0) 19 | PRINT "OK2" 20 | 21 | YIELD 22 | CLS 23 | 24 | POKE 16, 32767 25 | PRINT PEEK(16) 26 | PRINT PEEK?(16) 27 | PRINT PEEK@(16) 28 | PRINT PEEK!(16) 29 | PRINT PEEK%(16) 30 | PRINT PEEK$(16) 31 | PRINT "OK3" 32 | 33 | YIELD 34 | CLS 35 | 36 | POKE 20, 42@ 37 | PRINT PEEK(20) 38 | PRINT PEEK?(20) 39 | PRINT PEEK@(20) 40 | PRINT PEEK!(20) 41 | PRINT PEEK%(20) 42 | PRINT PEEK$(20) 43 | PRINT "OK4" 44 | 45 | YIELD 46 | CLS 47 | 48 | POKE 20, 42.0 49 | PRINT PEEK(20) 50 | PRINT PEEK?(20) 51 | PRINT PEEK@(20) 52 | PRINT PEEK!(20) 53 | PRINT PEEK%(20) 54 | PRINT PEEK$(20) 55 | PRINT "OK5" 56 | 57 | YIELD 58 | CLS 59 | 60 | POKE 20, "Hello there" 61 | PRINT PEEK(20) 62 | PRINT PEEK?(20) 63 | PRINT PEEK@(20) 64 | PRINT PEEK!(20) 65 | PRINT PEEK%(20) 66 | PRINT PEEK$(20) 67 | PRINT "OK6" 68 | 69 | YIELD 70 | CLS 71 | 72 | POKE 24, " here" 73 | PRINT PEEK$(20) 74 | PRINT "OK7" -------------------------------------------------------------------------------- /tests/program/dim.bas: -------------------------------------------------------------------------------- 1 | DIM bool1 = TRUE 2 | DIM bool2? = TRUE XOR FALSE 3 | 4 | DIM byte1 = &hFF@ 5 | DIM byte2@ = 255@ 6 | 7 | DIM int1 = 42 8 | DIM int2% = &H0000002A 9 | 10 | DIM float1 = 42.0 11 | DIM float2! = 42e0 12 | 13 | DIM string1 = "Test" 14 | DIM string2$ = "Te" + "st" 15 | 16 | ' Should print TRUE 17 | PRINT (bool1 = bool2) AND (byte1 = byte2) AND (int1 = int2) AND (float1 = float2) AND (string1 = string2) 18 | 19 | DIM var1%, var2$, var3$ = "!" 20 | 21 | REM Should print "0 0" 22 | PRINT var1, var2, var1 23 | 24 | ' Should print "00" 25 | PRINT var1; var1 26 | 27 | ' Should print "Hello!" 28 | PRINT "Hello" + var3 29 | 30 | DIM varA = "A", varAB = varA + "B", varCAB = "C" + varAB + "!" 31 | PRINT varCAB 32 | 33 | DIM myArray1() 34 | dim myArray2(123) 35 | DIM myArray3(-123 TO 123) 36 | dim myArray4(-123 to 123, 456) 37 | DIM myArray5(-123 TO 123, -456 to 456) 38 | 39 | dim myArray6%() 40 | DIM myArray7%(123) 41 | dim myArray8%(-123 to 123) 42 | DIM myArray9%(-123 TO 123, 456) 43 | dim myArray0%(-123 to 123, -456 TO 456) 44 | 45 | dim myArrayA%(-123 to -99, -456 TO -99) 46 | 47 | myArray9(-56, 66) = 22 48 | myArray9(-56, 67) = 0 49 | myArray9 (-56, 67) = 1 50 | 51 | PRINT myArray9(-56, 67), myArray9(-56, 66), myArray9(-55, 62) 52 | 53 | DIM myArrayB(-1 to 1) 54 | myArrayB(-1) = 1 55 | myArrayB(0) = 2 56 | myArrayB(1) = 3 57 | 58 | PRINT myArrayB(-1), myArrayB(0), myArrayB(1) -------------------------------------------------------------------------------- /tests/program/select.bas: -------------------------------------------------------------------------------- 1 | SUB TESTA(value%) 2 | SELECT CASE value 3 | CASE 0: PRINT value, "is", 0 4 | CASE 1: PRINT value, "is", 1 5 | CASE 2, 3: PRINT value, "is", "2 or 3" 6 | CASE 4 TO 5: PRINT value, "is", "4 to 5" 7 | CASE IS < 7: PRINT value, "is", "< 7" 8 | CASE IS <= 8: PRINT value, "is", "<= 8" 9 | CASE IS > 13::::: PRINT value, "is", "> 13" 10 | CASE IS >= 12: 11 | :::: 12 | PRINT value, "is", ">= 12" 13 | CASE IS <> 10: PRINT value, "is", "<> 10" 14 | CASE IS = 10: PRINT value, "is", "10" 15 | CASE ELSE: PRINT "Error" 16 | END SELECT 17 | END SUB 18 | 19 | SUB TESTB(value$) 20 | SELECT CASE value 21 | CASE "bing": 22 | case "bang" 23 | 24 | CASE "boom":: 25 | 26 | CASE "foo" 27 | PRINT value; "-bar" 28 | 29 | CASE "foo": PRINT "Error" 30 | 31 | CASE ELSE 32 | PRINT value; "-buz" 33 | 34 | END SELECT 35 | END SUB 36 | 37 | SUB TESTC(value%) 38 | SELECT CASE value 39 | CASE 9, 11 TO 13, 15: PRINT value 40 | CASE 16 TO 16, IS = 17: PRINT "B" 41 | CASE IS > 1, IS < -1 42 | PRINT "X" 43 | CASE ELSE 44 | PRINT "Y" 45 | END SELECT 46 | END SUB 47 | 48 | FOR i = 0 TO 15 49 | TESTA i 50 | NEXT 51 | 52 | YIELD 53 | CLS 54 | 55 | TESTB "bing" 56 | TESTB "bang" 57 | TESTB "boom" 58 | TESTB "foo" 59 | TESTB "baz" 60 | 61 | YIELD 62 | CLS 63 | 64 | TESTC 9 65 | TESTC 10 66 | TESTC 11 67 | TESTC 12 68 | TESTC 13 69 | TESTC 14 70 | TESTC 15 71 | TESTC 16 72 | TESTC 17 73 | 74 | YIELD 75 | CLS 76 | 77 | FOR i = -2 TO 2 78 | TESTC i 79 | NEXT -------------------------------------------------------------------------------- /tests/program/get_put.bas: -------------------------------------------------------------------------------- 1 | 2 | RECTANGLE (0, 0) - (159, 95), 4@, TRUE 3 | RECTANGLE (1, 1) - (158, 94), 54@, TRUE 4 | 5 | FOR Y = 0 TO 17 6 | LINE (2, Y * 5 + 2) - (48, Y + 2), BYTE(Y + 32) 7 | LINE (2, Y * 5 + 3) - (48, Y + 2), BYTE(Y + 32) 8 | LINE (2, Y * 5 + 4) - (48, Y + 2), BYTE(Y + 32) 9 | LINE (2, Y * 5 + 5) - (48, Y + 2), BYTE(Y + 32) 10 | LINE (2, Y * 5 + 6) - (48, Y + 2), BYTE(Y + 32) 11 | NEXT 12 | 13 | DIM myRectangle@(3) 14 | GET (0, 0) - (1, 1), myRectangle 15 | PUT (157, 93), (2, 2), myRectangle, AND 16 | PUT (97, 2), (2, 2), myRectangle 17 | 18 | DIM colorfulRectangle@(46 * 90) 19 | GET (2, 2) - (47, 91), colorfulRectangle 20 | PUT (50, 2), (46, 90), colorfulRectangle 21 | 22 | x = 2 23 | width = 4 24 | DIM verticalSlice@(96 * width) 25 | GET (x, 0) - (x + width - 1, 95), verticalSlice 26 | PUT (100, 0), (width, 96), verticalSlice, AND 27 | PUT (100 + width + 1, 0), (width, 96), verticalSlice, OR 28 | PUT (100 + width * 2 + 2, 0), (width, 96), verticalSlice, PSET 29 | PUT (100 + width * 3 + 3, 0), (width, 96), verticalSlice, PRESET 30 | PUT (100 + width * 4 + 4, 0), (width, 96), verticalSlice, XOR 31 | 32 | bigBorderWidth = 120 33 | bigBorderHeight = 50 34 | DIM bigBorderImage@(0 TO (bigBorderWidth * bigBorderHeight) - 1) 35 | FOR by = 0 TO bigBorderHeight - 1 36 | FOR bx = 0 to bigBorderWidth - 1 37 | bigBorderImage(by * bigBorderWidth + bx) = 255@ 38 | NEXT 39 | NEXT 40 | 41 | bigBorderSize = 2 42 | FOR by = 0 TO bigBorderHeight - 1 43 | FOR bx = 0 to bigBorderWidth - 1 44 | IF bx < bigBorderSize OR bx >= bigBorderWidth - bigBorderSize OR by < bigBorderSize OR by >= bigBorderHeight - bigBorderSize THEN 45 | bigBorderImage(by * bigBorderWidth + bx) = 4@ 46 | END IF 47 | NEXT 48 | NEXT 49 | 50 | PUT (160 / 2 - bigBorderWidth / 2 - 1, 96 / 2 - bigBorderHeight / 2 - 1), (bigBorderWidth, bigBorderHeight), bigBorderImage, TSET 51 | 52 | 53 | DIM fullScreen@(0 TO (160 * 96) - 1) 54 | GET (0, 0) - (159, 95), fullScreen 55 | 56 | YIELD 57 | CLS 58 | 59 | PUT (0, 0), (160, 96), fullScreen 60 | -------------------------------------------------------------------------------- /src/token/input.rs: -------------------------------------------------------------------------------- 1 | use { 2 | super::{Span, Token}, 3 | nom::{InputIter, InputLength, InputTake, Needed, Slice}, 4 | std::{ 5 | iter::Enumerate, 6 | ops::{Index, Range, RangeFrom, RangeFull, RangeTo}, 7 | slice::Iter, 8 | }, 9 | }; 10 | 11 | #[derive(Clone, Copy, Debug)] 12 | pub struct Tokens<'a> { 13 | tokens: &'a [Token<'a>], 14 | start: usize, 15 | end: usize, 16 | } 17 | 18 | impl<'a> Tokens<'a> { 19 | pub fn new(tokens: &'a [Token]) -> Tokens<'a> { 20 | Tokens { 21 | tokens, 22 | start: 0, 23 | end: tokens.len(), 24 | } 25 | } 26 | 27 | pub fn is_empty(self) -> bool { 28 | self.tokens[self.start..self.end].is_empty() 29 | } 30 | 31 | pub fn location(self) -> Span<'a> { 32 | self.tokens[self.start].location() 33 | } 34 | } 35 | 36 | impl<'a> Index for Tokens<'a> { 37 | type Output = Token<'a>; 38 | 39 | #[inline] 40 | fn index(&self, index: usize) -> &Self::Output { 41 | &self.tokens[self.start + index] 42 | } 43 | } 44 | 45 | impl<'a> InputIter for Tokens<'a> { 46 | type Item = &'a Token<'a>; 47 | type Iter = Enumerate>>; 48 | type IterElem = Iter<'a, Token<'a>>; 49 | 50 | #[inline] 51 | fn iter_indices(&self) -> Enumerate>> { 52 | self.tokens.iter().enumerate() 53 | } 54 | 55 | #[inline] 56 | fn iter_elements(&self) -> Iter<'a, Token<'a>> { 57 | self.tokens.iter() 58 | } 59 | 60 | #[inline] 61 | fn position

(&self, predicate: P) -> Option 62 | where 63 | P: Fn(Self::Item) -> bool, 64 | { 65 | self.tokens.iter().position(predicate) 66 | } 67 | 68 | #[inline] 69 | fn slice_index(&self, count: usize) -> Result { 70 | if self.tokens.len() >= count { 71 | Ok(count) 72 | } else { 73 | Err(Needed::Unknown) 74 | } 75 | } 76 | } 77 | 78 | impl<'a> InputLength for Tokens<'a> { 79 | #[inline] 80 | fn input_len(&self) -> usize { 81 | self.tokens.len() 82 | } 83 | } 84 | 85 | impl<'a> InputTake for Tokens<'a> { 86 | #[inline] 87 | fn take(&self, count: usize) -> Self { 88 | Tokens { 89 | tokens: &self.tokens[0..count], 90 | start: 0, 91 | end: count, 92 | } 93 | } 94 | 95 | #[inline] 96 | fn take_split(&self, count: usize) -> (Self, Self) { 97 | let (prefix, suffix) = self.tokens.split_at(count); 98 | let first = Tokens { 99 | tokens: prefix, 100 | start: 0, 101 | end: prefix.len(), 102 | }; 103 | let second = Tokens { 104 | tokens: suffix, 105 | start: 0, 106 | end: suffix.len(), 107 | }; 108 | 109 | (second, first) 110 | } 111 | } 112 | 113 | impl<'a> Slice> for Tokens<'a> { 114 | #[inline] 115 | fn slice(&self, range: Range) -> Self { 116 | Tokens { 117 | tokens: self.tokens.slice(range.clone()), 118 | start: self.start + range.start, 119 | end: self.start + range.end, 120 | } 121 | } 122 | } 123 | 124 | impl<'a> Slice> for Tokens<'a> { 125 | #[inline] 126 | fn slice(&self, range: RangeTo) -> Self { 127 | self.slice(0..range.end) 128 | } 129 | } 130 | 131 | impl<'a> Slice> for Tokens<'a> { 132 | #[inline] 133 | fn slice(&self, range: RangeFrom) -> Self { 134 | self.slice(range.start..self.end - self.start) 135 | } 136 | } 137 | 138 | impl<'a> Slice for Tokens<'a> { 139 | #[inline] 140 | fn slice(&self, _: RangeFull) -> Self { 141 | Tokens { 142 | tokens: self.tokens, 143 | start: self.start, 144 | end: self.end, 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | mod eval; 2 | mod syntax; 3 | mod token; 4 | 5 | #[cfg(test)] 6 | pub(self) mod tests; 7 | 8 | use { 9 | self::eval::{Instruction, Interpreter}, 10 | anyhow::Result, 11 | bytemuck::cast_slice, 12 | clap::Parser, 13 | glam::{vec3, Mat4}, 14 | inline_spirv::inline_spirv, 15 | screen_13::prelude::*, 16 | std::{fs::read, sync::Arc}, 17 | }; 18 | 19 | #[cfg(feature = "profile-with-puffin")] 20 | use { 21 | puffin_egui::{profiler_window, puffin::set_scopes_on}, 22 | screen_13_egui::Egui, 23 | }; 24 | 25 | #[derive(Parser, Debug)] 26 | #[command(author, version, about, long_about = None)] 27 | struct Args { 28 | /// File to load and interpret (.bas format) 29 | path: String, 30 | } 31 | 32 | fn main() -> anyhow::Result<()> { 33 | pretty_env_logger::init(); 34 | 35 | #[cfg(feature = "profile-with-puffin")] 36 | set_scopes_on(true); 37 | 38 | let args = Args::parse(); 39 | 40 | let event_loop = EventLoop::new() 41 | .window(|window| { 42 | window 43 | .with_title("JW-Basic v0.1") 44 | .with_inner_size(LogicalSize::new(160 * 4, 96 * 4)) 45 | }) 46 | .build()?; 47 | let present_pipeline = create_present_pipeline(&event_loop.device)?; 48 | 49 | #[cfg(feature = "profile-with-puffin")] 50 | let mut egui = Egui::new(&event_loop.device, event_loop.as_ref()); 51 | 52 | // Run with `RUST_LOG=debug` to see the generated instructions 53 | let program = Instruction::compile(&read(args.path)?)?; 54 | 55 | let mut interpreter = Interpreter::new(&event_loop.device, program)?; 56 | 57 | let mut never_ran = true; 58 | event_loop.run(|frame| { 59 | let was_running = interpreter.is_running(); 60 | if was_running { 61 | never_ran = false; 62 | } 63 | 64 | interpreter 65 | .update(frame.render_graph, frame.events) 66 | .unwrap(); 67 | 68 | if (never_ran || was_running) && !interpreter.is_running() { 69 | interpreter.locate(0, Interpreter::TEXT_ROWS - 1); 70 | interpreter.print("Press any key to continue"); 71 | } 72 | 73 | if !interpreter.is_running() 74 | && frame.events.iter().any(|event| { 75 | matches!(event, Event::WindowEvent { event, .. } if matches!(event, WindowEvent::KeyboardInput { .. })) 76 | }) 77 | { 78 | // Exit when not running and keyboard input is detected 79 | *frame.will_exit = true; 80 | } 81 | 82 | present_framebuffer_image( 83 | &present_pipeline, 84 | frame, 85 | interpreter.framebuffer_image(), 86 | #[cfg(feature = "profile-with-puffin")]&mut egui 87 | ); 88 | })?; 89 | 90 | Ok(()) 91 | } 92 | 93 | fn create_present_pipeline(device: &Arc) -> Result, DisplayError> { 94 | let vert = inline_spirv!( 95 | r#" 96 | #version 460 core 97 | 98 | const float U[6] = {0, 0, 1, 1, 1, 0}; 99 | const float V[6] = {0, 1, 0, 1, 0, 1}; 100 | const float X[6] = {-1, -1, 1, 1, 1, -1}; 101 | const float Y[6] = {-1, 1, -1, 1, -1, 1}; 102 | 103 | vec2 vertex_pos() { 104 | float x = X[gl_VertexIndex]; 105 | float y = Y[gl_VertexIndex]; 106 | 107 | return vec2(x, y); 108 | } 109 | 110 | vec2 vertex_tex() { 111 | float u = U[gl_VertexIndex]; 112 | float v = V[gl_VertexIndex]; 113 | 114 | return vec2(u, v); 115 | } 116 | 117 | layout(push_constant) uniform PushConstants { 118 | layout(offset = 0) mat4 vertex_transform; 119 | } push_constants; 120 | 121 | layout(location = 0) out vec2 texcoord_out; 122 | 123 | void main() { 124 | texcoord_out = vertex_tex(); 125 | gl_Position = push_constants.vertex_transform * vec4(vertex_pos(), 0, 1); 126 | } 127 | "#, 128 | vert 129 | ); 130 | let fragment = inline_spirv!( 131 | r#" 132 | #version 460 core 133 | 134 | layout(binding = 0) uniform sampler2D image_sampler_nne; 135 | 136 | layout(location = 0) in vec2 uv; 137 | 138 | layout(location = 0) out vec4 color; 139 | 140 | void main() { 141 | vec3 image_sample = texture(image_sampler_nne, uv).rgb; 142 | 143 | color = vec4(image_sample, 1.0); 144 | } 145 | "#, 146 | frag 147 | ); 148 | 149 | Ok(Arc::new(GraphicPipeline::create( 150 | device, 151 | GraphicPipelineInfo::new(), 152 | [ 153 | Shader::new_vertex(vert.as_slice()), 154 | Shader::new_fragment(fragment.as_slice()), 155 | ], 156 | )?)) 157 | } 158 | 159 | fn present_framebuffer_image( 160 | present_pipeline: &Arc, 161 | frame: FrameContext, 162 | framebuffer_image: &Arc, 163 | #[cfg(feature = "profile-with-puffin")] egui: &mut Egui, 164 | ) { 165 | let framebuffer_image = frame.render_graph.bind_node(framebuffer_image); 166 | let transform = { 167 | let framebuffer_info = frame.render_graph.node_info(framebuffer_image); 168 | let (framebuffer_width, framebuffer_height) = ( 169 | framebuffer_info.width as f32, 170 | framebuffer_info.height as f32, 171 | ); 172 | let (swapchain_width, swapchain_height) = (frame.width as f32, frame.height as f32); 173 | let scale = 174 | (swapchain_width / framebuffer_width).min(swapchain_height / framebuffer_height); 175 | 176 | Mat4::from_scale(vec3( 177 | scale * framebuffer_width / swapchain_width, 178 | scale * framebuffer_height / swapchain_height, 179 | 1.0, 180 | )) 181 | }; 182 | 183 | frame 184 | .render_graph 185 | .begin_pass("Present") 186 | .bind_pipeline(present_pipeline) 187 | .read_descriptor(0, framebuffer_image) 188 | .clear_color_value(0, frame.swapchain_image, [0x42, 0x42, 0x42, 0xFF]) 189 | .store_color(0, frame.swapchain_image) 190 | .record_subpass(move |subpass, _| { 191 | subpass 192 | .push_constants(cast_slice(&transform.to_cols_array())) 193 | .draw(6, 1, 0, 0); 194 | }); 195 | 196 | #[cfg(feature = "profile-with-puffin")] 197 | egui.run( 198 | frame.window, 199 | frame.events, 200 | frame.swapchain_image, 201 | frame.render_graph, 202 | |ui| { 203 | profiler_window(ui); 204 | }, 205 | ); 206 | } 207 | -------------------------------------------------------------------------------- /src/syntax/literal.rs: -------------------------------------------------------------------------------- 1 | use { 2 | super::{ 3 | super::token::Tokens, bool_lit, bool_ty, debug_location, f32_lit, f32_ty, i32_lit, i32_ty, 4 | str_lit, str_ty, u8_ty, Span, 5 | }, 6 | nom::{ 7 | branch::alt, 8 | combinator::{map, opt, verify}, 9 | sequence::terminated, 10 | IResult, 11 | }, 12 | std::fmt::{Debug, Formatter, Result as FmtResult}, 13 | }; 14 | 15 | #[derive(Clone, Copy, PartialEq)] 16 | pub enum Literal<'a> { 17 | Boolean(bool, Span<'a>), 18 | Byte(u8, Span<'a>), 19 | Float(f32, Span<'a>), 20 | Integer(i32, Span<'a>), 21 | String(&'a str, Span<'a>), 22 | } 23 | 24 | impl<'a> Literal<'a> { 25 | pub fn location(self) -> Span<'a> { 26 | match self { 27 | Self::Boolean(_, res) 28 | | Self::Byte(_, res) 29 | | Self::Float(_, res) 30 | | Self::Integer(_, res) 31 | | Self::String(_, res) => res, 32 | } 33 | } 34 | 35 | pub(super) fn parse(tokens: Tokens<'a>) -> IResult, Self> { 36 | alt(( 37 | Self::parse_bool, 38 | Self::parse_u8, 39 | Self::parse_f32, 40 | Self::parse_i32, 41 | Self::parse_str, 42 | ))(tokens) 43 | } 44 | 45 | fn parse_bool(tokens: Tokens<'a>) -> IResult, Self> { 46 | map(terminated(bool_lit, opt(bool_ty)), |token| { 47 | Self::Boolean(token.boolean_literal().unwrap(), tokens.location()) 48 | })(tokens) 49 | } 50 | 51 | fn parse_f32(tokens: Tokens<'a>) -> IResult, Self> { 52 | map(terminated(f32_lit, opt(f32_ty)), |token| { 53 | Self::Float(token.float_literal().unwrap(), tokens.location()) 54 | })(tokens) 55 | } 56 | 57 | fn parse_i32(tokens: Tokens<'a>) -> IResult, Self> { 58 | map(terminated(i32_lit, opt(i32_ty)), |token| { 59 | Self::Integer(token.integer_literal().unwrap(), tokens.location()) 60 | })(tokens) 61 | } 62 | 63 | fn parse_str(tokens: Tokens<'a>) -> IResult, Self> { 64 | map(terminated(str_lit, opt(str_ty)), |token| { 65 | Self::String(token.string_literal().unwrap(), tokens.location()) 66 | })(tokens) 67 | } 68 | 69 | fn parse_u8(tokens: Tokens<'a>) -> IResult, Self> { 70 | map( 71 | terminated( 72 | verify(i32_lit, |token| { 73 | let val = token.integer_literal().unwrap(); 74 | (0..=0xFF).contains(&val) 75 | }), 76 | u8_ty, 77 | ), 78 | |token| Self::Byte(token.integer_literal().unwrap() as _, tokens.location()), 79 | )(tokens) 80 | } 81 | } 82 | 83 | impl<'a> Debug for Literal<'a> { 84 | fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { 85 | f.write_str("Literal ")?; 86 | 87 | match self { 88 | Self::Boolean(val, _) => f.write_fmt(format_args!("`{val}` Boolean ")), 89 | Self::Byte(val, _) => f.write_fmt(format_args!("`{val}` Byte ")), 90 | Self::Float(val, _) => f.write_fmt(format_args!("`{val}` Float ")), 91 | Self::Integer(val, _) => f.write_fmt(format_args!("`{val}` Integer ")), 92 | Self::String(val, _) => f.write_fmt(format_args!("`{val}` String ")), 93 | }?; 94 | 95 | debug_location(f, self.location()) 96 | } 97 | } 98 | 99 | #[cfg(test)] 100 | mod tests { 101 | use { 102 | super::*, 103 | crate::token::{Span, Token}, 104 | }; 105 | 106 | #[test] 107 | fn bool_typed() { 108 | let span = Span::new(&[]); 109 | let tokens = [Token::BooleanLiteral(true, span), Token::BooleanType(span)]; 110 | let tokens = Tokens::new(&tokens); 111 | 112 | let expected = Literal::Boolean(true, span); 113 | 114 | let (_, result) = Literal::parse(tokens).unwrap(); 115 | 116 | assert_eq!(expected, result); 117 | } 118 | 119 | #[test] 120 | fn bool_untyped() { 121 | let span = Span::new(&[]); 122 | let tokens = [Token::BooleanLiteral(true, span)]; 123 | let tokens = Tokens::new(&tokens); 124 | 125 | let expected = Literal::Boolean(true, span); 126 | 127 | let (_, result) = Literal::parse(tokens).unwrap(); 128 | 129 | assert_eq!(expected, result); 130 | } 131 | 132 | #[test] 133 | fn u8_typed() { 134 | let span = Span::new(&[]); 135 | let tokens = [Token::IntegerLiteral(0xFF, span), Token::ByteType(span)]; 136 | let tokens = Tokens::new(&tokens); 137 | 138 | let expected = Literal::Byte(0xFF, span); 139 | 140 | let (_, result) = Literal::parse(tokens).unwrap(); 141 | 142 | assert_eq!(expected, result); 143 | } 144 | 145 | #[test] 146 | fn f32_typed() { 147 | let span = Span::new(&[]); 148 | let tokens = [Token::FloatLiteral(42.0, span), Token::FloatType(span)]; 149 | let tokens = Tokens::new(&tokens); 150 | 151 | let expected = Literal::Float(42.0, span); 152 | 153 | let (_, result) = Literal::parse(tokens).unwrap(); 154 | 155 | assert_eq!(expected, result); 156 | } 157 | 158 | #[test] 159 | fn f32_untyped() { 160 | let span = Span::new(&[]); 161 | let tokens = [Token::FloatLiteral(42.0, span)]; 162 | let tokens = Tokens::new(&tokens); 163 | 164 | let expected = Literal::Float(42.0, span); 165 | 166 | let (_, result) = Literal::parse(tokens).unwrap(); 167 | 168 | assert_eq!(expected, result); 169 | } 170 | #[test] 171 | fn i32_typed() { 172 | let span = Span::new(&[]); 173 | let tokens = [Token::IntegerLiteral(42, span), Token::IntegerType(span)]; 174 | let tokens = Tokens::new(&tokens); 175 | 176 | let expected = Literal::Integer(42, span); 177 | 178 | let (_, result) = Literal::parse(tokens).unwrap(); 179 | 180 | assert_eq!(expected, result); 181 | } 182 | 183 | #[test] 184 | fn i32_untyped() { 185 | let span = Span::new(&[]); 186 | let tokens = [Token::IntegerLiteral(42, span)]; 187 | let tokens = Tokens::new(&tokens); 188 | 189 | let expected = Literal::Integer(42, span); 190 | 191 | let (_, result) = Literal::parse(tokens).unwrap(); 192 | 193 | assert_eq!(expected, result); 194 | } 195 | #[test] 196 | fn str_typed() { 197 | let span = Span::new(&[]); 198 | let tokens = [Token::StringLiteral("hello", span), Token::StringType(span)]; 199 | let tokens = Tokens::new(&tokens); 200 | 201 | let expected = Literal::String("hello", span); 202 | 203 | let (_, result) = Literal::parse(tokens).unwrap(); 204 | 205 | assert_eq!(expected, result); 206 | } 207 | 208 | #[test] 209 | fn str_untyped() { 210 | let span = Span::new(&[]); 211 | let tokens = [Token::StringLiteral("hello", span)]; 212 | let tokens = Tokens::new(&tokens); 213 | 214 | let expected = Literal::String("hello", span); 215 | 216 | let (_, result) = Literal::parse(tokens).unwrap(); 217 | 218 | assert_eq!(expected, result); 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /examples/raycast.bas: -------------------------------------------------------------------------------- 1 | ' Copyright (c) 2004-2019, Lode Vandevenne 2 | ' 3 | ' All rights reserved. 4 | ' 5 | ' Redistribution and use in source and binary forms, with or without modification, are permitted 6 | ' provided that the following conditions are met: 7 | ' 8 | ' * Redistributions of source code must retain the above copyright notice, this list of 9 | ' conditions and the following disclaimer. 10 | ' * Redistributions in binary form must reproduce the above copyright notice, this list of 11 | ' conditions and the following disclaimer in the documentation and/or other materials provided 12 | ' with the distribution. 13 | ' 14 | ' THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 15 | ' "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 16 | ' LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 17 | ' A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 18 | ' CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 19 | ' EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 20 | ' PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 21 | ' PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 22 | ' LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 23 | ' NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | ' SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | 26 | KeyCodeLeft = 1@ 27 | KeyCodeRight = 2@ 28 | KeyCodeUp = 3@ 29 | KeyCodeDown = 4@ 30 | MapWidth = 24 31 | MapHeight = 24 32 | ScreenWidth = 160 33 | ScreenHeight = 96 34 | 35 | COLOR 13@ 36 | 37 | DIM map@(MapWidth - 1, MapHeight - 1) 38 | 39 | FOR y = 0 TO MapHeight - 1 40 | map(0, y) = 1@ 41 | map(MapWidth - 1, y) = 1@ 42 | NEXT 43 | FOR x = 0 to MapWidth - 1 44 | map(x, 0) = 1@ 45 | map(x, MapHeight - 1) = 1@ 46 | NEXT 47 | 48 | FOR y = 4 TO 8 49 | FOR x = 6 TO 10 50 | map(x, y) = 2@ 51 | NEXT 52 | NEXT 53 | FOR y = 5 TO 7 54 | FOR x = 7 TO 9 55 | map(x, y) = 0@ 56 | NEXT 57 | NEXT 58 | 59 | map(8, 8) = 0@ 60 | 61 | map(15, 4) = 3@ 62 | map(17, 4) = 3@ 63 | map(19, 4) = 3@ 64 | map(15, 6) = 3@ 65 | map(19, 6) = 3@ 66 | map(15, 8) = 3@ 67 | map(17, 8) = 3@ 68 | map(19, 8) = 3@ 69 | 70 | FOR y = 16 TO 22 71 | map(1, y) = 4@ 72 | map(8, y) = 4@ 73 | NEXT 74 | FOR x = 1 to 8 75 | map(x, 16) = 4@ 76 | map(x, 22) = 4@ 77 | NEXT 78 | 79 | FOR y = 16 TO 20 80 | map(3, y) = 4@ 81 | map(8, y) = 4@ 82 | NEXT 83 | FOR x = 3 to 8 84 | map(x, 20) = 4@ 85 | NEXT 86 | 87 | map(3, 18) = 0@ 88 | map(8, 21) = 0@ 89 | 90 | map(6, 18) = 5@ 91 | 92 | ' This is the map we made: 93 | ' [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1], 94 | ' [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], 95 | ' [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], 96 | ' [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], 97 | ' [1,0,0,0,0,0,2,2,2,2,2,0,0,0,0,3,0,3,0,3,0,0,0,1], 98 | ' [1,0,0,0,0,0,2,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,1], 99 | ' [1,0,0,0,0,0,2,0,0,0,2,0,0,0,0,3,0,0,0,3,0,0,0,1], 100 | ' [1,0,0,0,0,0,2,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,1], 101 | ' [1,0,0,0,0,0,2,2,0,2,2,0,0,0,0,3,0,3,0,3,0,0,0,1], 102 | ' [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], 103 | ' [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], 104 | ' [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], 105 | ' [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], 106 | ' [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], 107 | ' [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], 108 | ' [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], 109 | ' [1,4,4,4,4,4,4,4,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], 110 | ' [1,4,0,4,0,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], 111 | ' [1,4,0,0,0,0,5,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], 112 | ' [1,4,0,4,0,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], 113 | ' [1,4,0,4,4,4,4,4,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], 114 | ' [1,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], 115 | ' [1,4,4,4,4,4,4,4,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], 116 | ' [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1] 117 | 118 | posX = 22.0 119 | posY = 12.0 120 | dirX = -1.0 121 | dirY = 0.0 122 | planeX = 0.0 123 | planeY = 0.66 124 | 125 | WHILE TRUE 126 | CLS 127 | startTime = TIMER() 128 | 129 | RECTANGLE (0, 0) - (ScreenWidth - 1, ScreenHeight / 2), 24@, TRUE 130 | RECTANGLE (0, ScreenHeight / 2 + 1) - (ScreenWidth - 1, ScreenHeight - 1), 20@, TRUE 131 | 132 | FOR x = 0 TO ScreenWidth - 1 133 | cameraX = 2.0 * FLOAT(x) / FLOAT(ScreenWidth) - 1.0 134 | 135 | rayDirX = dirX + planeX * cameraX 136 | rayDirY = dirY + planeY * cameraX 137 | 138 | DIM deltaDistX! 139 | IF rayDirX = 0.0 THEN 140 | deltaDistX = 1e30 141 | ELSE THEN 142 | deltaDistX = ABS(1.0 / rayDirX) 143 | END IF 144 | 145 | DIM deltaDistY! 146 | IF rayDirY = 0.0 THEN 147 | deltaDistY = 1e30 148 | ELSE THEN 149 | deltaDistY = ABS(1.0 / rayDirY) 150 | END IF 151 | 152 | mapX = INT(posX) 153 | mapY = INT(posY) 154 | 155 | DIM stepX, stepY, sideDistX!, sideDistY! 156 | IF rayDirX < 0.0 THEN 157 | stepX = -1 158 | sideDistX = (posX - FLOAT(mapX)) * deltaDistX 159 | ELSE THEN 160 | stepX = 1 161 | sideDistX = (FLOAT(mapX) + 1.0 - posX) * deltaDistX 162 | END IF 163 | 164 | IF rayDirY < 0.0 THEN 165 | stepY = -1 166 | sideDistY = (posY - FLOAT(mapY)) * deltaDistY 167 | ELSE THEN 168 | stepY = 1 169 | sideDistY = (FLOAT(mapY) + 1.0 - posY) * deltaDistY 170 | END IF 171 | 172 | DIM side?, hitColor@ 173 | hit? = FALSE 174 | 175 | WHILE NOT hit 176 | ' jump to next map square, either in x-direction, or in y-direction 177 | IF sideDistX < sideDistY THEN 178 | sideDistX = sideDistX + deltaDistX 179 | mapX = mapX + stepX 180 | side = FALSE 181 | ELSE THEN 182 | sideDistY = sideDistY + deltaDistY 183 | mapY = mapY + stepY 184 | side = TRUE 185 | END IF 186 | 187 | ' Check if ray has hit a wall 188 | hitColor = map(mapX, mapY) 189 | IF hitColor > 0@ THEN 190 | hit = TRUE 191 | END IF 192 | WEND 193 | 194 | DIM perpWallDist! 195 | 196 | IF NOT side THEN 197 | perpWallDist = sideDistX - deltaDistX 198 | ELSE THEN 199 | perpWallDist = sideDistY - deltaDistY 200 | END IF 201 | 202 | lineHeight = INT(FLOAT(ScreenHeight) / perpWallDist) 203 | drawStart = -lineHeight / 2 + ScreenHeight / 2 204 | 205 | IF drawStart < 0 THEN 206 | drawStart = 0 207 | END IF 208 | 209 | drawEnd = lineHeight / 2 + ScreenHeight / 2 210 | 211 | IF drawEnd >= ScreenHeight THEN 212 | drawEnd = ScreenHeight - 1 213 | END IF 214 | 215 | IF side THEN 216 | hitColor = hitColor + 8@ 217 | END IF 218 | 219 | LINE (x, drawStart) - (x, drawEnd), hitColor 220 | NEXT 221 | 222 | RECTANGLE (0, 0) - (50, 6), &HFF@, TRUE 223 | 224 | endTime = TIMER() 225 | frameTime! = FLOAT(endTime - startTime) / 1000000.0 226 | fps = INT(1.0 / frameTime) 227 | 228 | LOCATE 0, 0 229 | PRINT "FPS:", fps 230 | 231 | YIELD 232 | 233 | moveSpeed = frameTime * 18.0 234 | rotSpeed = frameTime * 28.0 235 | 236 | IF KEYDOWN(KeyCodeUp) THEN 237 | newX = posX + dirX * moveSpeed 238 | IF map(INT(newX), INT(posY)) = 0@ THEN 239 | posX = newX 240 | END IF 241 | 242 | newY = posY + dirY * moveSpeed 243 | IF map(INT(posX), INT(newY)) = 0@ THEN 244 | posY = newY 245 | END IF 246 | END IF 247 | 248 | IF KEYDOWN(KeyCodeDown) THEN 249 | newX = posX - dirX * moveSpeed 250 | IF map(INT(newX), INT(posY)) = 0@ THEN 251 | posX = newX 252 | END IF 253 | 254 | newY = posY - dirY * moveSpeed 255 | IF map(INT(posX), INT(newY)) = 0@ THEN 256 | posY = newY 257 | END IF 258 | END IF 259 | 260 | IF KEYDOWN(KeyCodeLeft) THEN 261 | oldDirX = dirX 262 | dirX = dirX * COS(-rotSpeed) - dirY * SIN(-rotSpeed) 263 | dirY = oldDirX * SIN(-rotSpeed) + dirY * COS(-rotSpeed) 264 | 265 | oldPlaneX = planeX 266 | planeX = planeX * COS(-rotSpeed) - planeY * SIN(-rotSpeed) 267 | planeY = oldPlaneX * SIN(-rotSpeed) + planeY * COS(-rotSpeed) 268 | END IF 269 | 270 | IF KEYDOWN(KeyCodeRight) THEN 271 | oldDirX = dirX 272 | dirX = dirX * COS(rotSpeed) - dirY * SIN(rotSpeed) 273 | dirY = oldDirX * SIN(rotSpeed) + dirY * COS(rotSpeed) 274 | 275 | oldPlaneX = planeX 276 | planeX = planeX * COS(rotSpeed) - planeY * SIN(rotSpeed) 277 | planeY = oldPlaneX * SIN(rotSpeed) + planeY * COS(rotSpeed) 278 | END IF 279 | WEND -------------------------------------------------------------------------------- /src/eval/palette.rs: -------------------------------------------------------------------------------- 1 | use bytemuck::cast_slice; 2 | 3 | pub fn vga_256() -> Vec { 4 | let color = |r: u8, g: u8, b: u8| [r, g, b, 0xFF]; 5 | let mut res = vec![0; 1_024]; 6 | res.copy_from_slice(cast_slice(&[ 7 | color(0x00, 0x00, 0x00), 8 | color(0x00, 0x00, 0xAA), 9 | color(0x00, 0xAA, 0x00), 10 | color(0x00, 0xAA, 0xAA), 11 | color(0xAA, 0x00, 0x00), 12 | color(0xAA, 0x00, 0xAA), 13 | color(0xAA, 0x55, 0x00), 14 | color(0xAA, 0xAA, 0xAA), 15 | color(0x55, 0x55, 0x55), 16 | color(0x55, 0x55, 0xFF), 17 | color(0x55, 0xFF, 0x55), 18 | color(0x55, 0xFF, 0xFF), 19 | color(0xFF, 0x55, 0x55), 20 | color(0xFF, 0x55, 0xFF), 21 | color(0xFF, 0xFF, 0x55), 22 | color(0xFF, 0xFF, 0xFF), 23 | color(0x00, 0x00, 0x00), 24 | color(0x14, 0x14, 0x14), 25 | color(0x20, 0x20, 0x20), 26 | color(0x2D, 0x2D, 0x2D), 27 | color(0x39, 0x39, 0x39), 28 | color(0x45, 0x45, 0x45), 29 | color(0x51, 0x51, 0x51), 30 | color(0x61, 0x61, 0x61), 31 | color(0x71, 0x71, 0x71), 32 | color(0x82, 0x82, 0x82), 33 | color(0x92, 0x92, 0x92), 34 | color(0xA2, 0xA2, 0xA2), 35 | color(0xB6, 0xB6, 0xB6), 36 | color(0xCA, 0xCA, 0xCA), 37 | color(0xE3, 0xE3, 0xE3), 38 | color(0xFF, 0xFF, 0xFF), 39 | color(0x00, 0x00, 0xFF), 40 | color(0x41, 0x00, 0xFF), 41 | color(0x7D, 0x00, 0xFF), 42 | color(0xBE, 0x00, 0xFF), 43 | color(0xFF, 0x00, 0xFF), 44 | color(0xFF, 0x00, 0xBE), 45 | color(0xFF, 0x00, 0x7D), 46 | color(0xFF, 0x00, 0x41), 47 | color(0xFF, 0x00, 0x00), 48 | color(0xFF, 0x41, 0x00), 49 | color(0xFF, 0x7D, 0x00), 50 | color(0xFF, 0xBE, 0x00), 51 | color(0xFF, 0xFF, 0x00), 52 | color(0xBE, 0xFF, 0x00), 53 | color(0x7D, 0xFF, 0x00), 54 | color(0x41, 0xFF, 0x00), 55 | color(0x00, 0xFF, 0x00), 56 | color(0x00, 0xFF, 0x41), 57 | color(0x00, 0xFF, 0x7D), 58 | color(0x00, 0xFF, 0xBE), 59 | color(0x00, 0xFF, 0xFF), 60 | color(0x00, 0xBE, 0xFF), 61 | color(0x00, 0x7D, 0xFF), 62 | color(0x00, 0x41, 0xFF), 63 | color(0x7D, 0x7D, 0xFF), 64 | color(0x9E, 0x7D, 0xFF), 65 | color(0xBE, 0x7D, 0xFF), 66 | color(0xDF, 0x7D, 0xFF), 67 | color(0xFF, 0x7D, 0xFF), 68 | color(0xFF, 0x7D, 0xDF), 69 | color(0xFF, 0x7D, 0xBE), 70 | color(0xFF, 0x7D, 0x9E), 71 | color(0xFF, 0x7D, 0x7D), 72 | color(0xFF, 0x9E, 0x7D), 73 | color(0xFF, 0xBE, 0x7D), 74 | color(0xFF, 0xDF, 0x7D), 75 | color(0xFF, 0xFF, 0x7D), 76 | color(0xDF, 0xFF, 0x7D), 77 | color(0xBE, 0xFF, 0x7D), 78 | color(0x9E, 0xFF, 0x7D), 79 | color(0x7D, 0xFF, 0x7D), 80 | color(0x7D, 0xFF, 0x9E), 81 | color(0x7D, 0xFF, 0xBE), 82 | color(0x7D, 0xFF, 0xDF), 83 | color(0x7D, 0xFF, 0xFF), 84 | color(0x7D, 0xDF, 0xFF), 85 | color(0x7D, 0xBE, 0xFF), 86 | color(0x7D, 0x9E, 0xFF), 87 | color(0xB6, 0xB6, 0xFF), 88 | color(0xC6, 0xB6, 0xFF), 89 | color(0xDB, 0xB6, 0xFF), 90 | color(0xEB, 0xB6, 0xFF), 91 | color(0xFF, 0xB6, 0xFF), 92 | color(0xFF, 0xB6, 0xEB), 93 | color(0xFF, 0xB6, 0xDB), 94 | color(0xFF, 0xB6, 0xC6), 95 | color(0xFF, 0xB6, 0xB6), 96 | color(0xFF, 0xC6, 0xB6), 97 | color(0xFF, 0xDB, 0xB6), 98 | color(0xFF, 0xEB, 0xB6), 99 | color(0xFF, 0xFF, 0xB6), 100 | color(0xEB, 0xFF, 0xB6), 101 | color(0xDB, 0xFF, 0xB6), 102 | color(0xC6, 0xFF, 0xB6), 103 | color(0xB6, 0xFF, 0xB6), 104 | color(0xB6, 0xFF, 0xC6), 105 | color(0xB6, 0xFF, 0xDB), 106 | color(0xB6, 0xFF, 0xEB), 107 | color(0xB6, 0xFF, 0xFF), 108 | color(0xB6, 0xEB, 0xFF), 109 | color(0xB6, 0xDB, 0xFF), 110 | color(0xB6, 0xC6, 0xFF), 111 | color(0x00, 0x00, 0x71), 112 | color(0x1C, 0x00, 0x71), 113 | color(0x39, 0x00, 0x71), 114 | color(0x55, 0x00, 0x71), 115 | color(0x71, 0x00, 0x71), 116 | color(0x71, 0x00, 0x55), 117 | color(0x71, 0x00, 0x39), 118 | color(0x71, 0x00, 0x1C), 119 | color(0x71, 0x00, 0x00), 120 | color(0x71, 0x1C, 0x00), 121 | color(0x71, 0x39, 0x00), 122 | color(0x71, 0x55, 0x00), 123 | color(0x71, 0x71, 0x00), 124 | color(0x55, 0x71, 0x00), 125 | color(0x39, 0x71, 0x00), 126 | color(0x1C, 0x71, 0x00), 127 | color(0x00, 0x71, 0x00), 128 | color(0x00, 0x71, 0x1C), 129 | color(0x00, 0x71, 0x39), 130 | color(0x00, 0x71, 0x55), 131 | color(0x00, 0x71, 0x71), 132 | color(0x00, 0x55, 0x71), 133 | color(0x00, 0x39, 0x71), 134 | color(0x00, 0x1C, 0x71), 135 | color(0x39, 0x39, 0x71), 136 | color(0x45, 0x39, 0x71), 137 | color(0x55, 0x39, 0x71), 138 | color(0x61, 0x39, 0x71), 139 | color(0x71, 0x39, 0x71), 140 | color(0x71, 0x39, 0x61), 141 | color(0x71, 0x39, 0x55), 142 | color(0x71, 0x39, 0x45), 143 | color(0x71, 0x39, 0x39), 144 | color(0x71, 0x45, 0x39), 145 | color(0x71, 0x55, 0x39), 146 | color(0x71, 0x61, 0x39), 147 | color(0x71, 0x71, 0x39), 148 | color(0x61, 0x71, 0x39), 149 | color(0x55, 0x71, 0x39), 150 | color(0x45, 0x71, 0x39), 151 | color(0x39, 0x71, 0x39), 152 | color(0x39, 0x71, 0x45), 153 | color(0x39, 0x71, 0x55), 154 | color(0x39, 0x71, 0x61), 155 | color(0x39, 0x71, 0x71), 156 | color(0x39, 0x61, 0x71), 157 | color(0x39, 0x55, 0x71), 158 | color(0x39, 0x45, 0x71), 159 | color(0x51, 0x51, 0x71), 160 | color(0x59, 0x51, 0x71), 161 | color(0x61, 0x51, 0x71), 162 | color(0x69, 0x51, 0x71), 163 | color(0x71, 0x51, 0x71), 164 | color(0x71, 0x51, 0x69), 165 | color(0x71, 0x51, 0x61), 166 | color(0x71, 0x51, 0x59), 167 | color(0x71, 0x51, 0x51), 168 | color(0x71, 0x59, 0x51), 169 | color(0x71, 0x61, 0x51), 170 | color(0x71, 0x69, 0x51), 171 | color(0x71, 0x71, 0x51), 172 | color(0x69, 0x71, 0x51), 173 | color(0x61, 0x71, 0x51), 174 | color(0x59, 0x71, 0x51), 175 | color(0x51, 0x71, 0x51), 176 | color(0x51, 0x71, 0x59), 177 | color(0x51, 0x71, 0x61), 178 | color(0x51, 0x71, 0x69), 179 | color(0x51, 0x71, 0x71), 180 | color(0x51, 0x69, 0x71), 181 | color(0x51, 0x61, 0x71), 182 | color(0x51, 0x59, 0x71), 183 | color(0x00, 0x00, 0x41), 184 | color(0x10, 0x00, 0x41), 185 | color(0x20, 0x00, 0x41), 186 | color(0x31, 0x00, 0x41), 187 | color(0x41, 0x00, 0x41), 188 | color(0x41, 0x00, 0x31), 189 | color(0x41, 0x00, 0x20), 190 | color(0x41, 0x00, 0x10), 191 | color(0x41, 0x00, 0x00), 192 | color(0x41, 0x10, 0x00), 193 | color(0x41, 0x20, 0x00), 194 | color(0x41, 0x31, 0x00), 195 | color(0x41, 0x41, 0x00), 196 | color(0x31, 0x41, 0x00), 197 | color(0x20, 0x41, 0x00), 198 | color(0x10, 0x41, 0x00), 199 | color(0x00, 0x41, 0x00), 200 | color(0x00, 0x41, 0x10), 201 | color(0x00, 0x41, 0x20), 202 | color(0x00, 0x41, 0x31), 203 | color(0x00, 0x41, 0x41), 204 | color(0x00, 0x31, 0x41), 205 | color(0x00, 0x20, 0x41), 206 | color(0x00, 0x10, 0x41), 207 | color(0x20, 0x20, 0x41), 208 | color(0x28, 0x20, 0x41), 209 | color(0x31, 0x20, 0x41), 210 | color(0x39, 0x20, 0x41), 211 | color(0x41, 0x20, 0x41), 212 | color(0x41, 0x20, 0x39), 213 | color(0x41, 0x20, 0x31), 214 | color(0x41, 0x20, 0x28), 215 | color(0x41, 0x20, 0x20), 216 | color(0x41, 0x28, 0x20), 217 | color(0x41, 0x31, 0x20), 218 | color(0x41, 0x39, 0x20), 219 | color(0x41, 0x41, 0x20), 220 | color(0x39, 0x41, 0x20), 221 | color(0x31, 0x41, 0x20), 222 | color(0x28, 0x41, 0x20), 223 | color(0x20, 0x41, 0x20), 224 | color(0x20, 0x41, 0x28), 225 | color(0x20, 0x41, 0x31), 226 | color(0x20, 0x41, 0x39), 227 | color(0x20, 0x41, 0x41), 228 | color(0x20, 0x39, 0x41), 229 | color(0x20, 0x31, 0x41), 230 | color(0x20, 0x28, 0x41), 231 | color(0x2D, 0x2D, 0x41), 232 | color(0x31, 0x2D, 0x41), 233 | color(0x35, 0x2D, 0x41), 234 | color(0x3D, 0x2D, 0x41), 235 | color(0x41, 0x2D, 0x41), 236 | color(0x41, 0x2D, 0x3D), 237 | color(0x41, 0x2D, 0x35), 238 | color(0x41, 0x2D, 0x31), 239 | color(0x41, 0x2D, 0x2D), 240 | color(0x41, 0x31, 0x2D), 241 | color(0x41, 0x35, 0x2D), 242 | color(0x41, 0x3D, 0x2D), 243 | color(0x41, 0x41, 0x2D), 244 | color(0x3D, 0x41, 0x2D), 245 | color(0x35, 0x41, 0x2D), 246 | color(0x31, 0x41, 0x2D), 247 | color(0x2D, 0x41, 0x2D), 248 | color(0x2D, 0x41, 0x31), 249 | color(0x2D, 0x41, 0x35), 250 | color(0x2D, 0x41, 0x3D), 251 | color(0x2D, 0x41, 0x41), 252 | color(0x2D, 0x3D, 0x41), 253 | color(0x2D, 0x35, 0x41), 254 | color(0x2D, 0x31, 0x41), 255 | color(0x00, 0x00, 0x00), 256 | color(0x00, 0x00, 0x00), 257 | color(0x00, 0x00, 0x00), 258 | color(0x00, 0x00, 0x00), 259 | color(0x00, 0x00, 0x00), 260 | color(0x00, 0x00, 0x00), 261 | color(0x00, 0x00, 0x00), 262 | color(0x00, 0x00, 0x00), 263 | ])); 264 | 265 | res 266 | } 267 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | https://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | https://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | logo 2 | 3 | # JW-Basic 4 | 5 | [![LoC](https://tokei.rs/b1/github/attackgoat/jw-basic?category=code)](https://github.com/attackgoat/jw-basic) 6 | 7 | A toy language that is somewhat like QBasic. 8 | 9 | _Features:_ 10 | 11 | - Graphics: 160x96 (255 colors & transparent) 12 | - Text: 32x16 (4x5 font) 13 | - Character set: ASCII (32-127) 14 | - Keyboard input 15 | - Multidimensional arrays 16 | 17 | _Design:_ 18 | 19 | - Parses tokens using [`nom`](https://github.com/rust-bakery/nom) and 20 | [`nom_locate`](https://github.com/fflorent/nom_locate) 21 | - Syntax & expression tree parsed with from tokens 22 | - Assembly-like instructions emitted from syntax 23 | - Instructions executed using a register-based virtual machine 24 | - Graphics & text output using [`Screen-13`](https://github.com/attackgoat/screen-13) 25 | - Operates as a library or command-line program 26 | 27 | _Language demonstration:_ 28 | 29 | ``` 30 | ' This is a comment! Types you may use: 31 | ' ?: Boolean 32 | ' @: Unsigned byte 33 | ' %: Signed 32-bit integer 34 | ' !: Single-precision float 35 | ' $: String 36 | 37 | DIM myVar$ = "Variable initialization is optional" 38 | DIM answer% = 42, question? = TRUE 39 | DIM latitude! 40 | 41 | ' Type specifiers are not required: 42 | latitude = 100.0 43 | 44 | IF question THEN 45 | latitude! = -100.0! 46 | END IF 47 | 48 | CLS 49 | PRINT "Hello, there!", myVar 50 | 51 | ' A diagonal red line 52 | LINE (0, 13) - (159, 21), 4@ 53 | 54 | ' Some colorful boxes 55 | FOR c = 25 TO 95 STEP 3 56 | RECTANGLE (c - 5, c - 3) - (159, c), BYTE(c), TRUE 57 | NEXT 58 | 59 | ``` 60 | 61 | _See full specification below_ 62 | 63 | ## Example Programs 64 | 65 | This repository contains several example programs. Access the helpfile with `--help`: 66 | 67 | ``` 68 | A BASIC language interpreter. Does not conform to existing standards. Mostly a toy. 69 | 70 | Usage: jw-basic [OPTIONS] 71 | 72 | Arguments: 73 | 74 | File to load and interpret (.bas format) 75 | 76 | Options: 77 | -h, --help 78 | Print help (see a summary with '-h') 79 | 80 | -V, --version 81 | Print version 82 | ``` 83 | 84 | ### Hello, world! 85 | 86 | [examples/hello_world.bas](examples/hello_world.bas) 87 | 88 | ``` 89 | cargo run examples/hello_world.bas 90 | ``` 91 | 92 | Preview 93 | 94 | ### Raycast 95 | 96 | [examples/raycast.bas](examples/raycast.bas) 97 | 98 | ``` 99 | cargo run examples/raycast.bas 100 | ``` 101 | 102 | Preview 103 | 104 | ## Language Specifications 105 | 106 | ``` 107 | ABS[! | %](expr) | COS[!](expr) | SIN[!](expr) 108 | 109 | Math functions. 110 | 111 | expr: Any expression. 112 | 113 | Examples: 114 | 115 | ABS(-1.0) + COS(4.5) ' Radians of course! 116 | 117 | 118 | CLS 119 | 120 | CLS clears the screen of text and graphics. Color 0 is used. 121 | 122 | 123 | COLOR foreground@[, background@] 124 | 125 | COLOR sets text and graphics colors. 126 | 127 | foreground: The color of characters and any lines or rectangles which do not specify a color. 128 | backround: The backgroud of text characters or the fill-color of rectangles which do not 129 | specify a color. 130 | 131 | Examples: 132 | 133 | COLOR 4, 14 ' Red on yellow, danger! 134 | 135 | 136 | BOOLEAN[?](expr) | BYTE[@](expr) | FLOAT[!](expr) | INT[%](expr) | STR[$](expr) 137 | 138 | Converts an expression to another type. 139 | 140 | expr: Any expression. 141 | 142 | Examples: 143 | 144 | STR(1.0) 145 | 146 | 147 | DIM var[type][(subscripts)] [= value] [, var[type][(subscripts)]] [= value] ... 148 | 149 | DIM declares variables and arrays. Variables may also be simply assigned without DIM. 150 | 151 | var: The name of a variable. 152 | type: Type of data which may be stored: 153 | ? (boolean) 154 | @ (byte) 155 | ! (float) 156 | % (integer) 157 | $ (string) 158 | subscripts: [lower% TO] upper% [, [lower% TO] upper%] ... 159 | lower: Lower bound of the array. The default bound is zero. 160 | upper: Upper bound of the array. Inclusive. 161 | value: Any expression. Not supported with arrays yet. 162 | 163 | Examples: 164 | 165 | DIM name$, myMatrix!(2, -2 TO 2) 166 | DIM total% = 5 167 | myMatrix(2, -2) = 10.0 168 | 169 | 170 | DO [{WHILE | UNTIL} condition] 171 | [..] 172 | LOOP 173 | 174 | DO 175 | [..] 176 | LOOP [{WHILE | UNTIL} condition] 177 | 178 | Repeats a block of statements while a condition is true or until a condition becomes true. 179 | 180 | condition: Any expression which evaluates to a boolean. 181 | 182 | Examples: 183 | 184 | i% = 0 185 | PRINT "Value of i% at beginning "; i% 186 | DO WHILE i% < 10 187 | i% = i% + 1 188 | LOOP 189 | PRINT "Value of i% at end "; i% 190 | 191 | 192 | END {IF | FUNCTION | SUB} 193 | 194 | Ends a program, procedure or block. 195 | 196 | Examples: 197 | 198 | PRINT "Game over." 199 | END 200 | 201 | 202 | EXIT {DO | FOR | WHILE | FUNCTION | SUB} 203 | 204 | Exits a DO, FOR or WHILE loop or a FUNCTION or SUB procedure. 205 | 206 | Examples: 207 | 208 | i% = 0 209 | DO 210 | i% = i% + 1 211 | IF i% = 500 THEN 212 | EXIT DO 213 | END IF 214 | LOOP 215 | PRINT "EXIT at"; i% 216 | 217 | 218 | FOR var = start TO end [STEP step] 219 | [..] 220 | NEXT [var] 221 | 222 | Loop where `var` is incremented (or decremented) from `start` to `end` in `step` increments. 223 | 224 | var: A byte, float, or integer variable defined for the body of the FOR..NEXT statement. 225 | start: Any expression evaluated to become the initial value of `var`. 226 | end: Any expression evaluated to become the inclusive final value of `var`. 227 | step: Any expression evaluated to be added to `var` for each iteration. 228 | 229 | Examples: 230 | 231 | FOR temperature = 96.0 to 104.5 STEP 0.1 232 | PRINT temperature 233 | NEXT 234 | 235 | 236 | FUNCTION name{type}[([var{type}][, var{type}] ... )] 237 | [..] 238 | END FUNCTION 239 | 240 | Declare a function which returns a value. May only be used in the outermost scope, and not 241 | within any other block. Has global access to preceeding variables and functions. Function name 242 | is used as a variable to return a value. 243 | 244 | name: The name of the function. 245 | type: Type of data which may be returned or used as an argument: 246 | ? (boolean) 247 | @ (byte) 248 | ! (float) 249 | % (integer) 250 | $ (string) 251 | var: The name of a variable argument. 252 | 253 | Examples: 254 | 255 | FUNCTION areEqual?(lhs$, rhs$) 256 | IF lhs = rhs THEN 257 | areEqual = TRUE 258 | ELSE THEN 259 | areEqual = FALSE 260 | END IF 261 | END FUNCTION 262 | 263 | myGlobal = 0 264 | 265 | FUNCTION changeGlobal% 266 | myGlobal = myGlobal + 1 267 | changeGlobal = myGlobal 268 | END FUNCTION 269 | 270 | PRINT areEqual("Apples", "Oranges") ' Prints "FALSE" 271 | PRINT changeGlobal() ' Prints "1" 272 | 273 | 274 | GET (x0, y1) - (x1, y2), arrayname[(index)] 275 | PUT (x0, y1), (width, height), arrayname[(index)][, actionverb] 276 | 277 | GET captures a graphics screen image. PUT displays an image captured by GET. 278 | PUT Defaults to TSET. 279 | 280 | x0, y0, x1, y1: Any expressions which evaluates to integers. 281 | width, height: Any expressions which evaluates to integers. 282 | arrayname: The name of the array where the image is stored. 283 | index: The integer array index at which storage of the image begins. 284 | actionverb: A keyword indicating how the image is displayed: 285 | 286 | Keyword Action 287 | ═══════ ═════════════════════════════════════════════ 288 | AND Merges stored image with an existing image. 289 | OR Superimposes stored image on existing image. 290 | PSET Draws stored image, erasing existing image. 291 | PRESET Draws stored image in reverse colors, erasing 292 | existing image. 293 | XOR Draws a stored image or erases a previously 294 | drawn image while preserving the background, 295 | producing animation effects. 296 | TSET Draws stored image, preserving background 297 | where the stored image value is 255. 298 | 299 | Examples: 300 | 301 | DIM fullScreen@(0 TO (160 * 96) - 1) 302 | GET (0, 0) - (159, 95), fullScreen 303 | CLS 304 | PUT (0, 0), (160, 96), fullScreen 305 | 306 | 307 | GOTO [label | line number] 308 | 309 | Jumps directly to a given labelled or numbered line. Fun at parties. 310 | 311 | Examples: 312 | 313 | Again: 314 | PRINT "Dance!" 315 | GOTO Again 316 | 317 | 318 | IF expr THEN 319 | [..] 320 | [ELSE IF expr THEN] 321 | [..] 322 | [ELSE THEN] 323 | [..] 324 | END IF 325 | 326 | Branching logic tree. 327 | 328 | expr: Any expression which evaluates to a boolean. 329 | 330 | 331 | KEYDOWN[@](expr) 332 | 333 | Returns TRUE when a given key is pressed. 334 | 335 | expr: Any expression which evaluates to a byte, see source code for the keys which have 336 | been setup. 337 | 338 | 339 | LINE [(x0, y0) -] (x1, y1), color 340 | 341 | Draws a line between two points. 342 | 343 | x0, y0, x1, y1: Any expressions which evaluates to integers. 344 | color: Any expression which evaluates to a byte. 345 | 346 | 347 | LOCATE row[, col] 348 | 349 | Moves the text output location of the following PRINT statements. 350 | 351 | 352 | numeric-expression1 MOD numeric-expression2 353 | 354 | Divides one number by another and returns the remainder. 355 | 356 | numeric-expression1, numeric-expression2: Any numeric expressions. 357 | 358 | Examples: 359 | 360 | PRINT 19.0 MOD 6.7 'Output is 5.6000004 361 | PRINT 21 MOD 2 'Output is 1 362 | 363 | 364 | PALETTE color, r, g, b 365 | 366 | Changes the currently active palette allowing for colorful animation without re-drawing the 367 | screen. 368 | 369 | color: Any expression which evaluates to a byte in the 0-254 range. 255 (&hFF@) is 370 | transparent. 371 | r, g, b: Any expression which evaluates to a byte. 372 | 373 | 374 | PEEK[? | @ | ! | % | $](address) 375 | 376 | Returns a byte value (by default) stored at a specified memory location. 377 | 378 | address: A byte position in the zero-initialized heap memory; a value in the 379 | range 0 through 16,383. 380 | 381 | Examples: 382 | 383 | myByte = PEEK(420) 384 | someFloat = PEEK!(128) 385 | 386 | 387 | POKE address, expr 388 | 389 | Writes a value to a specified memory location. 390 | 391 | address: A byte position in the zero-initialized heap memory; a value in the 392 | range 0 through 16,383. 393 | expr: Any expression. 394 | 395 | Examples: 396 | 397 | POKE 420, 255@ 398 | POKE 128, 42.0 399 | 400 | 401 | PRINT [expr][; expr][, expr] 402 | 403 | PRINT displays text using the current foreground and background colors at the current cursor 404 | location. 405 | 406 | expr: Any expression. 407 | semicolon: Prints the following expression with zero additional spaces. 408 | comma: Prints the following expression with one additional space. 409 | 410 | Examples: 411 | 412 | PRINT "Hello " + name$ + ". Nice to meet you!", "Welcome to day ", dayOfWeek%; "!" 413 | 414 | 415 | PSET (x, y), color 416 | 417 | Draw a specified point on the screen. 418 | 419 | x, y: Any expression which evaluates to an integer. 420 | color: Any expression which evaluates to a byte. 421 | 422 | 423 | RECTANGLE [(x0, y0) -] (x1, y1), color[, filled] 424 | 425 | Draws a rectangle between two points. 426 | 427 | x0, y0, x1, y1: Any expression which evaluates to an integer. 428 | color: Any expression which evaluates to a byte. 429 | filled: Any expression which evaluates to a boolean. 430 | 431 | 432 | RND[!][()] 433 | 434 | Returns a random float number uniformly distributed in the half-open range [0, 1). 435 | 436 | Examples: 437 | 438 | x% = INT(RND * 6.0) + 1 439 | y% = INT(RND * 6.0) + 1 440 | PRINT "Roll of two dice: "; x%; "and "; y% 441 | 442 | 443 | SELECT CASE testexpression 444 | CASE expressionlist1 445 | [statementblock-1] 446 | CASE expressionlist2 447 | [statementblock-2]... 448 | CASE ELSE 449 | [statementblock-n] 450 | END SELECT 451 | 452 | Executes one of several statement blocks depending on the value of an expression. The 453 | expressionlist arguments can have any of these forms or a combination of them, separated by 454 | commas: 455 | 456 | expression[, expression]... 457 | expression TO expression 458 | IS relational-operator expression 459 | 460 | testexpression: Any expression. 461 | expression: Any expression which evaluates to same type as testexpression. 462 | relation-operator: One of the following relational operators: <, <=, >, >=, <>, or =. Boolean 463 | and string expressions may only use <> and = operators. 464 | 465 | Examples: 466 | 467 | someValue = 45.0 468 | SELECT CASE someValue 469 | CASE IS <= 20.0: PRINT "Too low!" 470 | CASE someValue - 5.0 TO someValue + 5.0: PRINT "Close enough!" 471 | CASE IS > 100.0 472 | PRINT "Too high!" 473 | CASE ELSE 474 | PRINT "Over, but not too high!" 475 | END SELECT 476 | 477 | 478 | SUB name[([var{type}][, var{type}] ... )] 479 | [..] 480 | END SUB 481 | 482 | Declare a subroutine. May only be used in the outermost scope, and not within any other block. 483 | Has global access to preceeding variables and functions. May be called optionally using CALL. 484 | 485 | When calling, if you omit the CALL keyword, also omit the optional parentheses around the 486 | arguments. 487 | 488 | name: The name of the sub. 489 | var: The name of a variable argument. 490 | type: Type of data which may be used as an argument: 491 | ? (boolean) 492 | @ (byte) 493 | ! (float) 494 | % (integer) 495 | $ (string) 496 | 497 | Examples: 498 | 499 | SUB PrintName(name$) 500 | PRINT "Hello", name$ + "!" 501 | END SUB 502 | 503 | PrintName "John" 504 | CALL PrintName("Keli") 505 | 506 | 507 | TIMER[%]() 508 | 509 | Returns the number of microseconds since the program began execution. 510 | 511 | 512 | WHILE expr 513 | [..] 514 | WEND 515 | 516 | Loop which begins if `expr` is TRUE and continues until it is FALSE. 517 | 518 | expr: Any expression which evaluates to a boolean. 519 | 520 | 521 | YIELD 522 | 523 | Pause execution of a program until the next update of the interpreter. Without calling this 524 | execution will continue until the final statement is executed. 525 | ``` 526 | 527 | ## Tests 528 | 529 | In addition to the test programs, there are unit and integration tests of the language. When 530 | something goes wrong *you should* receive an error indicating the line and column number which 531 | caused the issue. 532 | 533 | _Some Vulkan drivers will fail if too many devices are created at once. When testing you may need to 534 | limit the number of threads._ 535 | 536 | Running the tests: 537 | 538 | ``` 539 | cargo test -- --test-threads=1 540 | ``` 541 | 542 | ## Performance Profiling 543 | 544 | To enable profiling with [puffin](https://crates.io/crates/puffin), use the `profile-with-puffin` 545 | feature: 546 | 547 | ```base 548 | cargo run --features profile-with-puffin -- examples/raycast.bas 549 | ``` 550 | 551 | Flamegraph of performance data 552 | 553 | ## Credits 554 | 555 | This project was designed completely for fun and to learn how a language might be developed. I hope 556 | you find something useful that you can bring to your projects, just like I was able to find sources 557 | of inspiration for _JW-Basic_. 558 | 559 | - [QBasic cafe](https://www.qbasic.net/en/reference/qb11/overview.htm) used for reference 560 | documentation 561 | - Parsing code inspired by [`monkey-rust`](https://github.com/Rydgel/monkey-rust) 562 | - Raycasting example inspired by 563 | [Lode's Computer Graphics Tutorial](https://lodev.org/cgtutor/raycasting.html) 564 | 565 | Feel free to submit PRs if you would like to enhance this code or fill out remaining features. 566 | -------------------------------------------------------------------------------- /src/eval/charset.rs: -------------------------------------------------------------------------------- 1 | pub fn ascii_5x6() -> [[[bool; 5]; 6]; 96] { 2 | let characters = [ 3 | r#" 4 | 00000 5 | 00000 6 | 00000 7 | 00000 8 | 00000 9 | 00000 10 | "#, 11 | r#" 12 | 00000 13 | 01000 14 | 01000 15 | 01000 16 | 00000 17 | 01000 18 | "#, 19 | r#" 20 | 00000 21 | 01010 22 | 01010 23 | 00000 24 | 00000 25 | 00000 26 | "#, 27 | r#" 28 | 00000 29 | 01010 30 | 11111 31 | 01010 32 | 11111 33 | 01110 34 | "#, 35 | r#" 36 | 00000 37 | 00111 38 | 01010 39 | 00110 40 | 00101 41 | 01110 42 | "#, 43 | r#" 44 | 00000 45 | 01001 46 | 00010 47 | 00100 48 | 01000 49 | 01001 50 | "#, 51 | r#" 52 | 00000 53 | 00110 54 | 01001 55 | 00110 56 | 01010 57 | 00111 58 | "#, 59 | r#" 60 | 00000 61 | 01000 62 | 01000 63 | 00000 64 | 00000 65 | 00000 66 | "#, 67 | r#" 68 | 00000 69 | 00010 70 | 00100 71 | 01000 72 | 00100 73 | 00010 74 | "#, 75 | r#" 76 | 00000 77 | 00100 78 | 00010 79 | 00001 80 | 00010 81 | 00100 82 | "#, 83 | r#" 84 | 00000 85 | 01010 86 | 00100 87 | 01010 88 | 00000 89 | 00000 90 | "#, 91 | r#" 92 | 00000 93 | 00000 94 | 00100 95 | 01110 96 | 00100 97 | 00000 98 | "#, 99 | r#" 100 | 00000 101 | 00000 102 | 00000 103 | 00000 104 | 00100 105 | 01000 106 | "#, 107 | r#" 108 | 00000 109 | 00000 110 | 00000 111 | 01111 112 | 00000 113 | 00000 114 | "#, 115 | r#" 116 | 00000 117 | 00000 118 | 00000 119 | 00000 120 | 00000 121 | 01000 122 | "#, 123 | r#" 124 | 00000 125 | 00000 126 | 00001 127 | 00010 128 | 00100 129 | 01000 130 | "#, 131 | r#" 132 | 00000 133 | 00100 134 | 01010 135 | 01010 136 | 01010 137 | 00100 138 | "#, 139 | r#" 140 | 00000 141 | 00010 142 | 00110 143 | 00010 144 | 00010 145 | 00010 146 | "#, 147 | r#" 148 | 00000 149 | 00110 150 | 01001 151 | 00010 152 | 00100 153 | 01111 154 | "#, 155 | r#" 156 | 00000 157 | 00110 158 | 01001 159 | 00010 160 | 01001 161 | 00110 162 | "#, 163 | r#" 164 | 00000 165 | 01000 166 | 01001 167 | 00111 168 | 00001 169 | 00001 170 | "#, 171 | r#" 172 | 00000 173 | 01111 174 | 01000 175 | 00110 176 | 00001 177 | 00110 178 | "#, 179 | r#" 180 | 00000 181 | 00100 182 | 01000 183 | 01110 184 | 01001 185 | 00110 186 | "#, 187 | r#" 188 | 00000 189 | 01111 190 | 00001 191 | 00010 192 | 00100 193 | 01000 194 | "#, 195 | r#" 196 | 00000 197 | 00110 198 | 01001 199 | 00110 200 | 01001 201 | 00110 202 | "#, 203 | r#" 204 | 00000 205 | 00110 206 | 01001 207 | 00111 208 | 00001 209 | 00001 210 | "#, 211 | r#" 212 | 00000 213 | 00000 214 | 00100 215 | 00000 216 | 00100 217 | 00000 218 | "#, 219 | r#" 220 | 00000 221 | 00100 222 | 00100 223 | 00000 224 | 00100 225 | 01000 226 | "#, 227 | r#" 228 | 00000 229 | 00010 230 | 00100 231 | 01000 232 | 00100 233 | 00010 234 | "#, 235 | r#" 236 | 00000 237 | 00000 238 | 01110 239 | 00000 240 | 01110 241 | 00000 242 | "#, 243 | r#" 244 | 00000 245 | 01000 246 | 00100 247 | 00010 248 | 00100 249 | 01000 250 | "#, 251 | r#" 252 | 00000 253 | 00110 254 | 01001 255 | 00010 256 | 00000 257 | 00010 258 | "#, 259 | r#" 260 | 00000 261 | 00110 262 | 01001 263 | 01010 264 | 01000 265 | 00110 266 | "#, 267 | r#" 268 | 00000 269 | 00110 270 | 01001 271 | 01111 272 | 01001 273 | 01001 274 | "#, 275 | r#" 276 | 00000 277 | 01110 278 | 01001 279 | 01110 280 | 01001 281 | 01110 282 | "#, 283 | r#" 284 | 00000 285 | 00111 286 | 01000 287 | 01000 288 | 01000 289 | 00111 290 | "#, 291 | r#" 292 | 00000 293 | 01110 294 | 01001 295 | 01001 296 | 01001 297 | 01110 298 | "#, 299 | r#" 300 | 00000 301 | 01111 302 | 01000 303 | 01100 304 | 01000 305 | 01111 306 | "#, 307 | r#" 308 | 00000 309 | 01111 310 | 01000 311 | 01100 312 | 01000 313 | 01000 314 | "#, 315 | r#" 316 | 00000 317 | 00111 318 | 01000 319 | 01011 320 | 01001 321 | 00110 322 | "#, 323 | r#" 324 | 00000 325 | 01001 326 | 01001 327 | 01111 328 | 01001 329 | 01001 330 | "#, 331 | r#" 332 | 00000 333 | 01110 334 | 00100 335 | 00100 336 | 00100 337 | 01110 338 | "#, 339 | r#" 340 | 00000 341 | 00001 342 | 00001 343 | 00001 344 | 01001 345 | 00110 346 | "#, 347 | r#" 348 | 00000 349 | 01001 350 | 01010 351 | 01100 352 | 01010 353 | 01001 354 | "#, 355 | r#" 356 | 00000 357 | 01000 358 | 01000 359 | 01000 360 | 01000 361 | 01111 362 | "#, 363 | r#" 364 | 00000 365 | 01101 366 | 01111 367 | 01001 368 | 01001 369 | 01001 370 | "#, 371 | r#" 372 | 00000 373 | 01001 374 | 01101 375 | 01011 376 | 01001 377 | 01001 378 | "#, 379 | r#" 380 | 00000 381 | 00110 382 | 01001 383 | 01001 384 | 01001 385 | 00110 386 | "#, 387 | r#" 388 | 00000 389 | 01110 390 | 01001 391 | 01110 392 | 01000 393 | 01000 394 | "#, 395 | r#" 396 | 00000 397 | 00110 398 | 01001 399 | 01001 400 | 01011 401 | 00111 402 | "#, 403 | r#" 404 | 00000 405 | 01110 406 | 01001 407 | 01110 408 | 01001 409 | 01001 410 | "#, 411 | r#" 412 | 00000 413 | 00111 414 | 01000 415 | 00110 416 | 00001 417 | 01110 418 | "#, 419 | r#" 420 | 00000 421 | 01110 422 | 00100 423 | 00100 424 | 00100 425 | 00100 426 | "#, 427 | r#" 428 | 00000 429 | 01001 430 | 01001 431 | 01001 432 | 01001 433 | 01111 434 | "#, 435 | r#" 436 | 00000 437 | 01001 438 | 01001 439 | 01001 440 | 01001 441 | 00110 442 | "#, 443 | r#" 444 | 00000 445 | 01001 446 | 01001 447 | 01001 448 | 01111 449 | 00110 450 | "#, 451 | r#" 452 | 00000 453 | 01001 454 | 00110 455 | 00110 456 | 01001 457 | 01001 458 | "#, 459 | r#" 460 | 00000 461 | 01001 462 | 01001 463 | 00111 464 | 00001 465 | 00110 466 | "#, 467 | r#" 468 | 00000 469 | 01111 470 | 00001 471 | 00010 472 | 00100 473 | 01111 474 | "#, 475 | r#" 476 | 00000 477 | 01110 478 | 01000 479 | 01000 480 | 01000 481 | 01110 482 | "#, 483 | r#" 484 | 00000 485 | 00000 486 | 01000 487 | 00100 488 | 00010 489 | 00001 490 | "#, 491 | r#" 492 | 00000 493 | 01110 494 | 00010 495 | 00010 496 | 00010 497 | 01110 498 | "#, 499 | r#" 500 | 00000 501 | 00100 502 | 01010 503 | 00000 504 | 00000 505 | 00000 506 | "#, 507 | r#" 508 | 00000 509 | 00000 510 | 00000 511 | 00000 512 | 00000 513 | 01110 514 | "#, 515 | r#" 516 | 00000 517 | 01000 518 | 00100 519 | 00000 520 | 00000 521 | 00000 522 | "#, 523 | r#" 524 | 00000 525 | 00110 526 | 00001 527 | 00111 528 | 01001 529 | 00110 530 | "#, 531 | r#" 532 | 00000 533 | 01000 534 | 01000 535 | 01100 536 | 01010 537 | 01100 538 | "#, 539 | r#" 540 | 00000 541 | 00000 542 | 00000 543 | 00110 544 | 01000 545 | 00110 546 | "#, 547 | r#" 548 | 00000 549 | 00010 550 | 00010 551 | 00110 552 | 01010 553 | 00110 554 | "#, 555 | r#" 556 | 00000 557 | 00000 558 | 00100 559 | 01010 560 | 01100 561 | 00110 562 | "#, 563 | r#" 564 | 00000 565 | 00010 566 | 00100 567 | 01110 568 | 00100 569 | 00100 570 | "#, 571 | r#" 572 | 00000 573 | 00110 574 | 01010 575 | 00110 576 | 00010 577 | 00100 578 | "#, 579 | r#" 580 | 00000 581 | 01000 582 | 01000 583 | 01100 584 | 01010 585 | 01010 586 | "#, 587 | r#" 588 | 00000 589 | 00000 590 | 00100 591 | 00000 592 | 00100 593 | 00100 594 | "#, 595 | r#" 596 | 00000 597 | 00010 598 | 00010 599 | 00010 600 | 00010 601 | 00100 602 | "#, 603 | r#" 604 | 00000 605 | 01000 606 | 01010 607 | 01100 608 | 01100 609 | 01010 610 | "#, 611 | r#" 612 | 00000 613 | 01000 614 | 01000 615 | 01000 616 | 01000 617 | 01100 618 | "#, 619 | r#" 620 | 00000 621 | 00000 622 | 01110 623 | 01110 624 | 01010 625 | 01010 626 | "#, 627 | r#" 628 | 00000 629 | 00000 630 | 01100 631 | 01010 632 | 01010 633 | 01010 634 | "#, 635 | r#" 636 | 00000 637 | 00000 638 | 00100 639 | 01010 640 | 01010 641 | 00100 642 | "#, 643 | r#" 644 | 00000 645 | 00100 646 | 01010 647 | 01100 648 | 01000 649 | 01000 650 | "#, 651 | r#" 652 | 00000 653 | 00100 654 | 01010 655 | 01110 656 | 00010 657 | 00010 658 | "#, 659 | r#" 660 | 00000 661 | 00000 662 | 01100 663 | 01000 664 | 01000 665 | 01000 666 | "#, 667 | r#" 668 | 00000 669 | 00000 670 | 00110 671 | 01000 672 | 00110 673 | 01110 674 | "#, 675 | r#" 676 | 00000 677 | 00000 678 | 00100 679 | 01110 680 | 00100 681 | 00010 682 | "#, 683 | r#" 684 | 00000 685 | 00000 686 | 00000 687 | 01010 688 | 01010 689 | 01110 690 | "#, 691 | r#" 692 | 00000 693 | 00000 694 | 00000 695 | 01010 696 | 01010 697 | 00100 698 | "#, 699 | r#" 700 | 00000 701 | 00000 702 | 00000 703 | 01001 704 | 01011 705 | 00110 706 | "#, 707 | r#" 708 | 00000 709 | 00000 710 | 00000 711 | 01010 712 | 00100 713 | 01010 714 | "#, 715 | r#" 716 | 00000 717 | 00000 718 | 01010 719 | 01110 720 | 00010 721 | 01100 722 | "#, 723 | r#" 724 | 00000 725 | 00000 726 | 01110 727 | 00100 728 | 01000 729 | 01110 730 | "#, 731 | r#" 732 | 00000 733 | 01100 734 | 00010 735 | 00011 736 | 00010 737 | 01100 738 | "#, 739 | r#" 740 | 00000 741 | 00100 742 | 00100 743 | 00100 744 | 00100 745 | 00100 746 | "#, 747 | r#" 748 | 00000 749 | 00011 750 | 00100 751 | 01100 752 | 00100 753 | 00011 754 | "#, 755 | r#" 756 | 00000 757 | 00101 758 | 01010 759 | 00000 760 | 00000 761 | 00000 762 | "#, 763 | r#" 764 | 00000 765 | 01111 766 | 01001 767 | 01001 768 | 01001 769 | 01111 770 | "#, 771 | ]; 772 | 773 | fn convert_char(char: &str, res: &mut [[bool; 5]; 6]) { 774 | for (row, data) in char.trim().split('\n').enumerate() { 775 | for (col, value) in data.trim().chars().enumerate() { 776 | res[row][col] = value == '1'; 777 | } 778 | } 779 | } 780 | 781 | let mut res = [[[false; 5]; 6]; 96]; 782 | for (index, char) in characters.iter().enumerate() { 783 | convert_char(char, &mut res[index]); 784 | } 785 | 786 | res 787 | } 788 | -------------------------------------------------------------------------------- /tests/headless.rs: -------------------------------------------------------------------------------- 1 | use { 2 | jw_basic::eval::{ascii_5x6, vga_256, Instruction, Interpreter}, 3 | lazy_static::lazy_static, 4 | screen_13::prelude::*, 5 | std::{ 6 | fs::read, 7 | path::{Path, PathBuf}, 8 | sync::Arc, 9 | }, 10 | }; 11 | 12 | lazy_static! { 13 | static ref CARGO_MANIFEST_DIR: PathBuf = PathBuf::from(env!("CARGO_MANIFEST_DIR")); 14 | static ref BITMAP_DIR: PathBuf = CARGO_MANIFEST_DIR.join("tests/bitmap"); 15 | static ref PROGRAM_DIR: PathBuf = CARGO_MANIFEST_DIR.join("tests/program"); 16 | } 17 | 18 | #[test] 19 | fn array() { 20 | let res = Headless::execute("array.bas"); 21 | 22 | res.assert_printed((15, 0), (0, 0), "90"); 23 | res.assert_printed((15, 0), (1, 0), "91"); 24 | res.assert_printed((15, 0), (2, 0), "92"); 25 | res.assert_printed((15, 0), (3, 0), "93"); 26 | res.assert_printed((15, 0), (4, 0), "94"); 27 | res.assert_printed((15, 0), (5, 0), "95"); 28 | res.assert_printed((15, 0), (6, 0), "96"); 29 | res.assert_printed((15, 0), (7, 0), "97"); 30 | res.assert_printed((15, 0), (8, 0), "98"); 31 | res.assert_printed((15, 0), (9, 0), "99"); 32 | res.assert_printed((15, 0), (10, 0), "100"); 33 | res.assert_printed((15, 0), (11, 0), "101"); 34 | res.assert_printed((15, 0), (12, 0), "TRUE FALSE 170 0 4.2 0 :) !"); 35 | } 36 | 37 | #[test] 38 | fn assign() { 39 | let res = Headless::execute("assign.bas"); 40 | 41 | res.assert_printed((15, 0), (0, 0), "42"); 42 | res.assert_printed((15, 0), (1, 0), "43"); 43 | res.assert_printed((15, 0), (2, 0), "44"); 44 | } 45 | 46 | #[test] 47 | fn cls() { 48 | let mut res = Headless::execute("cls.bas"); 49 | 50 | res.assert_printed((15, 0), (0, 0), "one"); 51 | 52 | res.update(&[]); 53 | 54 | res.assert_printed((15, 0), (0, 0), "two"); 55 | } 56 | 57 | #[test] 58 | fn color() { 59 | let res = Headless::execute("color.bas"); 60 | 61 | res.assert_printed((14, 4), (0, 0), " !DANGER! "); 62 | res.assert_printed((4, 0), (1, 1), "MAY EXPLODE!"); 63 | res.assert_printed((7, 0), (3, 0), "(do not shake)"); 64 | res.assert_printed((2, 0), (4, 0), "GREEN"); 65 | } 66 | 67 | #[test] 68 | fn dim() { 69 | let res = Headless::execute("dim.bas"); 70 | 71 | res.assert_printed((15, 0), (0, 0), "TRUE"); 72 | res.assert_printed((15, 0), (1, 0), "0 0"); 73 | res.assert_printed((15, 0), (2, 0), "00"); 74 | res.assert_printed((15, 0), (3, 0), "Hello!"); 75 | res.assert_printed((15, 0), (4, 0), "CAB!"); 76 | res.assert_printed((15, 0), (5, 0), "1 22 0"); 77 | res.assert_printed((15, 0), (6, 0), "1 2 3"); 78 | } 79 | 80 | #[test] 81 | fn do_loop() { 82 | let res = Headless::execute("do_loop.bas"); 83 | 84 | res.assert_printed((15, 0), (0, 0), "1 "); 85 | res.assert_printed((15, 0), (1, 0), "2 "); 86 | res.assert_printed((15, 0), (2, 0), " "); 87 | res.assert_printed((15, 0), (3, 0), "1 "); 88 | res.assert_printed((15, 0), (4, 0), "0 "); 89 | res.assert_printed((15, 0), (5, 0), " "); 90 | res.assert_printed((15, 0), (6, 0), "1 "); 91 | res.assert_printed((15, 0), (7, 0), "2 "); 92 | res.assert_printed((15, 0), (8, 0), " "); 93 | res.assert_printed((15, 0), (9, 0), "1 "); 94 | res.assert_printed((15, 0), (10, 0), "0 "); 95 | res.assert_printed((15, 0), (11, 0), " "); 96 | res.assert_printed((15, 0), (12, 0), "1 "); 97 | res.assert_printed((15, 0), (13, 0), "2 "); 98 | } 99 | 100 | #[test] 101 | fn exit() { 102 | let res = Headless::execute("exit.bas"); 103 | 104 | res.assert_printed((15, 0), (0, 0), "1 "); 105 | res.assert_printed((15, 0), (1, 0), "2 "); 106 | res.assert_printed((15, 0), (2, 0), "3 "); 107 | res.assert_printed((15, 0), (3, 0), "4 "); 108 | res.assert_printed((15, 0), (4, 0), "5 "); 109 | res.assert_printed((15, 0), (5, 0), "6 "); 110 | res.assert_printed((15, 0), (6, 0), "7 "); 111 | res.assert_printed((15, 0), (7, 0), "8 "); 112 | res.assert_printed((15, 0), (8, 0), "9 "); 113 | } 114 | 115 | #[test] 116 | fn for_next() { 117 | let res = Headless::execute("for_next.bas"); 118 | 119 | res.assert_printed((15, 0), (0, 0), "0"); 120 | res.assert_printed((15, 0), (1, 0), "1"); 121 | res.assert_printed((15, 0), (2, 0), "2"); 122 | res.assert_printed((15, 0), (3, 0), "3"); 123 | res.assert_printed((4, 0), (4, 0), "0"); 124 | res.assert_printed((4, 0), (5, 0), "2"); 125 | res.assert_printed((1, 0), (6, 0), "4"); 126 | res.assert_printed((1, 0), (7, 0), "5"); 127 | res.assert_printed((1, 0), (8, 0), "6"); 128 | res.assert_printed((2, 0), (9, 0), "216 10"); 129 | res.assert_printed((2, 0), (10, 0), "423 9"); 130 | res.assert_printed((2, 0), (11, 0), "621 8"); 131 | res.assert_printed((2, 0), (12, 0), "810 7"); 132 | res.assert_printed((2, 0), (13, 0), "990 6"); 133 | res.assert_printed((6, 0), (14, 0), "22.18"); 134 | res.assert_printed((6, 0), (15, 0), "OK"); 135 | } 136 | 137 | #[test] 138 | fn function() { 139 | let res = Headless::execute("function.bas"); 140 | 141 | res.assert_printed((15, 0), (0, 0), "FALSE "); 142 | res.assert_printed((15, 0), (1, 0), "TRUE "); 143 | res.assert_printed((15, 0), (2, 0), "TRUE "); 144 | res.assert_printed((15, 0), (3, 0), "5 "); 145 | res.assert_printed((15, 0), (4, 0), "899 "); 146 | res.assert_printed((15, 0), (5, 0), "Hello, world "); 147 | res.assert_printed((15, 0), (6, 0), "OK1 "); 148 | res.assert_printed((15, 0), (7, 0), "FALSE "); 149 | res.assert_printed((15, 0), (8, 0), "1 "); 150 | res.assert_printed((15, 0), (9, 0), "OK2 "); 151 | } 152 | 153 | #[test] 154 | fn get_put() { 155 | let mut res = Headless::execute("get_put.bas"); 156 | 157 | // If you make changes to get_put.bas use this line to update the file! 158 | // res.save_framebuffer("get_put.bmp"); 159 | 160 | res.assert_bitmap("get_put.bmp"); 161 | 162 | res.update(&[]); 163 | 164 | res.assert_bitmap("get_put.bmp"); 165 | } 166 | 167 | #[test] 168 | fn goto() { 169 | let mut res = Headless::execute("goto.bas"); 170 | 171 | res.assert_printed((15, 0), (0, 0), "Hello"); 172 | 173 | res.update(&[]); 174 | 175 | res.assert_printed((15, 0), (1, 0), "Hello"); 176 | 177 | res.update(&[]); 178 | 179 | res.assert_printed((15, 0), (2, 0), "Hello"); 180 | } 181 | 182 | #[test] 183 | fn graphics() { 184 | let mut res = Headless::execute("graphics.bas"); 185 | 186 | // If you make changes to graphics.bas use this line to update the file! 187 | // res.save_framebuffer("graphics1.bmp"); 188 | 189 | res.assert_bitmap("graphics1.bmp"); 190 | 191 | res.update(&[]); 192 | 193 | res.assert_bitmap("graphics2.bmp"); 194 | } 195 | 196 | #[test] 197 | fn if_then() { 198 | let res = Headless::execute("if_then.bas"); 199 | 200 | res.assert_printed((15, 0), (0, 0), "AOK"); 201 | res.assert_printed((15, 0), (1, 0), "OK"); 202 | res.assert_printed((15, 0), (2, 0), "Great!"); 203 | res.assert_printed((15, 0), (3, 0), "Hey"); 204 | } 205 | 206 | #[test] 207 | fn locate() { 208 | let res = Headless::execute("locate.bas"); 209 | 210 | res.assert_printed((15, 0), (0, 0), "HELLO"); 211 | res.assert_printed((15, 0), (0, 30), "10"); 212 | res.assert_printed((15, 0), (7, 14), "BASIC"); 213 | res.assert_printed((15, 0), (15, 0), "GOODBYE"); 214 | res.assert_printed((15, 0), (15, 30), "1"); 215 | } 216 | 217 | #[test] 218 | fn modulus() { 219 | let res = Headless::execute("modulus.bas"); 220 | 221 | res.assert_printed((15, 0), (0, 0), "0"); 222 | res.assert_printed((15, 0), (1, 0), "10"); 223 | res.assert_printed((15, 0), (2, 0), "1"); 224 | res.assert_printed((15, 0), (3, 0), "0"); 225 | res.assert_printed((15, 0), (4, 0), "10"); 226 | res.assert_printed((15, 0), (5, 0), "1"); 227 | res.assert_printed((15, 0), (6, 0), "0"); 228 | res.assert_printed((15, 0), (7, 0), "10"); 229 | res.assert_printed((15, 0), (8, 0), "1"); 230 | } 231 | 232 | #[test] 233 | fn peek_poke() { 234 | let mut res = Headless::execute("peek_poke.bas"); 235 | 236 | res.assert_printed((15, 0), (0, 0), "0 "); 237 | res.assert_printed((15, 0), (1, 0), "FALSE "); 238 | res.assert_printed((15, 0), (2, 0), "0 "); 239 | res.assert_printed((15, 0), (3, 0), "0 "); 240 | res.assert_printed((15, 0), (4, 0), "0 "); 241 | res.assert_printed((15, 0), (5, 0), " "); 242 | res.assert_printed((15, 0), (6, 0), "OK1 "); 243 | 244 | res.update(&[]); 245 | 246 | res.assert_printed((15, 0), (0, 0), "1 "); 247 | res.assert_printed((15, 0), (1, 0), "TRUE "); 248 | res.assert_printed((15, 0), (2, 0), "1 "); 249 | res.assert_printed((15, 0), (3, 0), "0.000000000000000000000000000000"); 250 | res.assert_printed((15, 0), (4, 0), "000000000000001 "); 251 | res.assert_printed((15, 0), (5, 0), "1 "); 252 | res.assert_printed((15, 0), (6, 0), " "); 253 | res.assert_printed((15, 0), (7, 0), "OK2 "); 254 | 255 | res.update(&[]); 256 | 257 | res.assert_printed((15, 0), (0, 0), "255 "); 258 | res.assert_printed((15, 0), (1, 0), "FALSE "); 259 | res.assert_printed((15, 0), (2, 0), "255 "); 260 | res.assert_printed((15, 0), (3, 0), "0.000000000000000000000000000000"); 261 | res.assert_printed((15, 0), (4, 0), "000000000045916 "); 262 | res.assert_printed((15, 0), (5, 0), "32767 "); 263 | res.assert_printed((15, 0), (6, 0), " "); 264 | res.assert_printed((15, 0), (7, 0), "OK3 "); 265 | 266 | res.update(&[]); 267 | 268 | res.assert_printed((15, 0), (0, 0), "42 "); 269 | res.assert_printed((15, 0), (1, 0), "FALSE "); 270 | res.assert_printed((15, 0), (2, 0), "42 "); 271 | res.assert_printed((15, 0), (3, 0), "0.000000000000000000000000000000"); 272 | res.assert_printed((15, 0), (4, 0), "000000000000059 "); 273 | res.assert_printed((15, 0), (5, 0), "42 "); 274 | res.assert_printed((15, 0), (6, 0), "* "); 275 | res.assert_printed((15, 0), (7, 0), "OK4 "); 276 | 277 | res.update(&[]); 278 | 279 | res.assert_printed((15, 0), (0, 0), "0 "); 280 | res.assert_printed((15, 0), (1, 0), "FALSE "); 281 | res.assert_printed((15, 0), (2, 0), "0 "); 282 | res.assert_printed((15, 0), (3, 0), "42 "); 283 | res.assert_printed((15, 0), (4, 0), "1109917696 "); 284 | res.assert_printed((15, 0), (5, 0), " "); 285 | res.assert_printed((15, 0), (6, 0), "OK5 "); 286 | 287 | res.update(&[]); 288 | 289 | res.assert_printed((15, 0), (0, 0), "72 "); 290 | res.assert_printed((15, 0), (1, 0), "FALSE "); 291 | res.assert_printed((15, 0), (2, 0), "72 "); 292 | res.assert_printed((15, 0), (3, 0), "1143139100000000000000000000 "); 293 | res.assert_printed((15, 0), (4, 0), "1819043144 "); 294 | res.assert_printed((15, 0), (5, 0), "Hello there "); 295 | res.assert_printed((15, 0), (6, 0), "OK6 "); 296 | 297 | res.update(&[]); 298 | 299 | res.assert_printed((15, 0), (0, 0), "Hell here "); 300 | res.assert_printed((15, 0), (1, 0), "OK7 "); 301 | } 302 | 303 | #[test] 304 | fn rnd() { 305 | let res = Headless::execute("rnd.bas"); 306 | 307 | res.assert_printed((15, 0), (0, 0), "Roll of two dice: "); 308 | res.assert_printed((15, 0), (0, 20), "and "); 309 | } 310 | 311 | #[test] 312 | fn select() { 313 | let mut res = Headless::execute("select.bas"); 314 | 315 | res.assert_printed((15, 0), (0, 0), "0 is 0 "); 316 | res.assert_printed((15, 0), (1, 0), "1 is 1 "); 317 | res.assert_printed((15, 0), (2, 0), "2 is 2 or 3 "); 318 | res.assert_printed((15, 0), (3, 0), "3 is 2 or 3 "); 319 | res.assert_printed((15, 0), (4, 0), "4 is 4 to 5 "); 320 | res.assert_printed((15, 0), (5, 0), "5 is 4 to 5 "); 321 | res.assert_printed((15, 0), (6, 0), "6 is < 7 "); 322 | res.assert_printed((15, 0), (7, 0), "7 is <= 8 "); 323 | res.assert_printed((15, 0), (8, 0), "8 is <= 8 "); 324 | res.assert_printed((15, 0), (9, 0), "9 is <> 10 "); 325 | res.assert_printed((15, 0), (10, 0), "10 is 10 "); 326 | res.assert_printed((15, 0), (11, 0), "11 is <> 10 "); 327 | res.assert_printed((15, 0), (12, 0), "12 is >= 12 "); 328 | res.assert_printed((15, 0), (13, 0), "13 is >= 12 "); 329 | res.assert_printed((15, 0), (14, 0), "14 is > 13 "); 330 | 331 | res.update(&[]); 332 | 333 | res.assert_printed((15, 0), (0, 0), "foo-bar "); 334 | res.assert_printed((15, 0), (1, 0), "baz-buz "); 335 | 336 | res.update(&[]); 337 | 338 | res.assert_printed((15, 0), (0, 0), "9 "); 339 | res.assert_printed((15, 0), (1, 0), "X "); 340 | res.assert_printed((15, 0), (2, 0), "11 "); 341 | res.assert_printed((15, 0), (3, 0), "12 "); 342 | res.assert_printed((15, 0), (4, 0), "13 "); 343 | res.assert_printed((15, 0), (5, 0), "X "); 344 | res.assert_printed((15, 0), (6, 0), "15 "); 345 | res.assert_printed((15, 0), (7, 0), "B "); 346 | res.assert_printed((15, 0), (8, 0), "B "); 347 | 348 | res.update(&[]); 349 | 350 | res.assert_printed((15, 0), (0, 0), "X "); 351 | res.assert_printed((15, 0), (1, 0), "Y "); 352 | res.assert_printed((15, 0), (2, 0), "Y "); 353 | res.assert_printed((15, 0), (3, 0), "Y "); 354 | res.assert_printed((15, 0), (4, 0), "X "); 355 | } 356 | 357 | #[test] 358 | fn sub() { 359 | let res = Headless::execute("sub.bas"); 360 | 361 | res.assert_printed((15, 0), (0, 0), "A "); 362 | res.assert_printed((15, 0), (1, 0), "A "); 363 | res.assert_printed((15, 0), (2, 0), "B "); 364 | res.assert_printed((15, 0), (3, 0), "C "); 365 | res.assert_printed((15, 0), (4, 0), "D "); 366 | res.assert_printed((15, 0), (5, 0), "< > "); 367 | res.assert_printed((15, 0), (6, 0), "OK "); 368 | } 369 | 370 | #[test] 371 | fn while_wend() { 372 | let res = Headless::execute("while_wend.bas"); 373 | 374 | res.assert_printed((15, 0), (0, 0), "1"); 375 | res.assert_printed((15, 0), (1, 0), "2"); 376 | res.assert_printed((15, 0), (2, 0), "1"); 377 | res.assert_printed((15, 0), (3, 0), "0"); 378 | } 379 | 380 | /// Helper to pick a queue family for submitting device commands. 381 | fn device_queue_family_index(device: &Device, flags: vk::QueueFlags) -> Option { 382 | device 383 | .physical_device 384 | .queue_families 385 | .iter() 386 | .enumerate() 387 | .find(|(_, properties)| properties.queue_flags.contains(flags)) 388 | .map(|(index, _)| index) 389 | } 390 | 391 | struct Headless { 392 | device: Arc, 393 | framebuffer: [u8; Self::FRAMEBUFFER_LEN], 394 | interpreter: Interpreter, 395 | charset: [[[bool; 5]; 6]; 96], 396 | palette: Vec, 397 | } 398 | 399 | impl Headless { 400 | const FRAMEBUFFER_LEN: usize = 401 | 4 * (Interpreter::FRAMEBUFFER_WIDTH * Interpreter::FRAMEBUFFER_HEIGHT) as usize; 402 | 403 | fn execute(program: &str) -> Self { 404 | let program = Instruction::compile(&read(PROGRAM_DIR.join(program)).unwrap()).unwrap(); 405 | 406 | let device = Arc::new(Device::create_headless(DeviceInfo::new()).unwrap()); 407 | let interpreter = Interpreter::new(&device, program).unwrap(); 408 | 409 | let mut res = Self { 410 | charset: ascii_5x6(), 411 | device, 412 | framebuffer: [0; Self::FRAMEBUFFER_LEN], 413 | interpreter, 414 | palette: vga_256(), 415 | }; 416 | 417 | res.update(&[]); 418 | 419 | res 420 | } 421 | 422 | fn assert_bitmap(&self, path: impl AsRef) { 423 | use bmp::open; 424 | 425 | let image = open(BITMAP_DIR.join(path)).unwrap(); 426 | 427 | for y in 0..Interpreter::FRAMEBUFFER_HEIGHT { 428 | for x in 0..Interpreter::FRAMEBUFFER_WIDTH { 429 | let expected_pixel = image.get_pixel(x, y); 430 | let (actual_r, actual_g, actual_b, _) = self.pixel(x, y); 431 | 432 | assert_eq!(expected_pixel.r, actual_r); 433 | assert_eq!(expected_pixel.g, actual_g); 434 | assert_eq!(expected_pixel.b, actual_b); 435 | } 436 | } 437 | } 438 | 439 | fn assert_printed(&self, color: (u8, u8), location: (u32, u32), s: &str) { 440 | let (foreground, background) = color; 441 | let foreground = self.color(foreground); 442 | let background = self.color(background); 443 | 444 | let (row, col) = location; 445 | let (x, y) = (col * 5, row * 6); 446 | 447 | for (char_index, char) in s.as_bytes().iter().copied().enumerate() { 448 | assert!(char >= 32); 449 | assert!(char <= 127); 450 | 451 | for char_y in 0..6u32 { 452 | for char_x in 0..5u32 { 453 | let char = char - 32; 454 | let (expected_red, expected_green, expected_blue) = 455 | if self.charset[char as usize][char_y as usize][char_x as usize] { 456 | foreground 457 | } else { 458 | background 459 | }; 460 | 461 | let pixel_x = x + char_x + char_index as u32 * 5; 462 | let pixel_y = y + char_y; 463 | let (actual_red, actual_green, actual_blue, actual_alpha) = 464 | self.pixel(pixel_x, pixel_y); 465 | 466 | let msg = format!( 467 | "character {char_index} `{}` at row={row} col={col}, x={pixel_x}, y={pixel_y}", 468 | (char + 32) as char, 469 | ); 470 | 471 | assert_eq!(expected_red, actual_red, "red {}", msg); 472 | assert_eq!(expected_green, actual_green, "green {}", msg); 473 | assert_eq!(expected_blue, actual_blue, "blue {}", msg); 474 | 475 | // The framebuffer alpha should always be fully opaque 476 | assert_eq!(actual_alpha, 0xFF); 477 | } 478 | } 479 | } 480 | } 481 | 482 | fn color(&self, index: u8) -> (u8, u8, u8) { 483 | let start = index * 4; 484 | let end = start + 3; 485 | let data = &self.palette[start as usize..end as usize]; 486 | 487 | (data[0], data[1], data[2]) 488 | } 489 | 490 | fn pixel(&self, x: u32, y: u32) -> (u8, u8, u8, u8) { 491 | let start = y * 4 * Interpreter::FRAMEBUFFER_WIDTH + x * 4; 492 | let end = start + 4; 493 | let data = &self.framebuffer[start as usize..end as usize]; 494 | 495 | (data[0], data[1], data[2], data[3]) 496 | } 497 | 498 | #[allow(unused)] 499 | fn save_framebuffer(&self, path: impl AsRef) { 500 | use bmp::{Image, Pixel}; 501 | 502 | let mut image = Image::new( 503 | Interpreter::FRAMEBUFFER_WIDTH, 504 | Interpreter::FRAMEBUFFER_HEIGHT, 505 | ); 506 | 507 | for y in 0..Interpreter::FRAMEBUFFER_HEIGHT { 508 | for x in 0..Interpreter::FRAMEBUFFER_WIDTH { 509 | let (r, g, b, _) = self.pixel(x, y); 510 | image.set_pixel(x, y, Pixel::new(r, g, b)); 511 | } 512 | } 513 | 514 | image.save(BITMAP_DIR.join(path)).unwrap(); 515 | } 516 | 517 | fn update(&mut self, events: &[Event<()>]) { 518 | let mut render_graph = RenderGraph::new(); 519 | self.interpreter.update(&mut render_graph, events).unwrap(); 520 | 521 | let framebuffer_image = render_graph.bind_node(self.interpreter.framebuffer_image()); 522 | let framebuffer = { 523 | let temp_buf = render_graph.bind_node( 524 | Buffer::create( 525 | &self.device, 526 | BufferInfo::new_mappable( 527 | (4 * Interpreter::FRAMEBUFFER_WIDTH * Interpreter::FRAMEBUFFER_HEIGHT) as _, 528 | vk::BufferUsageFlags::TRANSFER_DST, 529 | ), 530 | ) 531 | .unwrap(), 532 | ); 533 | render_graph 534 | .copy_image_to_buffer(framebuffer_image, temp_buf) 535 | .unbind_node(temp_buf) 536 | }; 537 | 538 | let queue_family_index = device_queue_family_index( 539 | &self.device, 540 | vk::QueueFlags::COMPUTE | vk::QueueFlags::GRAPHICS | vk::QueueFlags::TRANSFER, 541 | ) 542 | .unwrap(); 543 | 544 | render_graph 545 | .resolve() 546 | .submit(&mut HashPool::new(&self.device), queue_family_index, 0) 547 | .unwrap() 548 | .wait_until_executed() 549 | .unwrap(); 550 | self.framebuffer 551 | .copy_from_slice(Buffer::mapped_slice(&framebuffer)); 552 | } 553 | } 554 | -------------------------------------------------------------------------------- /src/syntax/expr.rs: -------------------------------------------------------------------------------- 1 | use { 2 | super::{ 3 | abs_token, add_op, and_op, bool_ty, cbool_token, cbyte_token, cfloat_token, cint_token, 4 | comma_punc, cos_token, cstr_token, debug_location, div_op, eq_op, f32_ty, gt_op, gte_op, 5 | i32_ty, key_down_token, l_paren_punc, l_sq_bracket_punc, lt_op, lte_op, mod_token, mul_op, 6 | ne_op, not_op, or_op, peek_token, r_paren_punc, r_sq_bracket_punc, rnd_token, sin_token, 7 | str_ty, sub_op, timer_token, u8_ty, xor_op, Literal, Span, Token, Tokens, Type, Variable, 8 | }, 9 | nom::{ 10 | branch::alt, 11 | bytes::complete::take, 12 | combinator::{map, opt, value}, 13 | multi::separated_list0, 14 | sequence::{delimited, pair, preceded, tuple}, 15 | IResult, 16 | }, 17 | std::fmt::{Debug, Formatter, Result as FmtResult}, 18 | }; 19 | 20 | #[derive(Clone)] 21 | #[cfg_attr(test, derive(PartialEq))] 22 | pub enum Expression<'a> { 23 | // Values 24 | Literal(Literal<'a>), 25 | Tuple(Vec, Span<'a>), 26 | Variable(Variable<'a>), 27 | // Operations 28 | Infix(Infix<'a>, Box, Box, Span<'a>), 29 | Prefix(Prefix, Box, Span<'a>), 30 | // Functions 31 | Function(Variable<'a>, Vec, Span<'a>), 32 | ConvertBoolean(Box, Span<'a>), 33 | ConvertByte(Box, Span<'a>), 34 | ConvertFloat(Box, Span<'a>), 35 | ConvertInteger(Box, Span<'a>), 36 | ConvertString(Box, Span<'a>), 37 | Abs(Option, Box, Span<'a>), 38 | Sin(Box, Span<'a>), 39 | Cos(Box, Span<'a>), 40 | Peek(Option, Box, Span<'a>), 41 | KeyDown(Box, Span<'a>), 42 | Random(Span<'a>), 43 | Timer(Span<'a>), 44 | } 45 | 46 | impl<'a> Expression<'a> { 47 | pub fn location(&self) -> Span { 48 | match self { 49 | Self::Literal(lit) => lit.location(), 50 | Self::Variable(id) => id.location, 51 | Self::Tuple(.., res) 52 | | Self::Infix(.., res) 53 | | Self::Prefix(.., res) 54 | | Self::Function(.., res) 55 | | Self::ConvertBoolean(_, res) 56 | | Self::ConvertByte(_, res) 57 | | Self::ConvertFloat(_, res) 58 | | Self::ConvertInteger(_, res) 59 | | Self::ConvertString(_, res) 60 | | Self::Abs(.., res) 61 | | Self::Sin(_, res) 62 | | Self::Cos(_, res) 63 | | Self::Peek(.., res) 64 | | Self::KeyDown(_, res) 65 | | Self::Random(res) 66 | | Self::Timer(res) => *res, 67 | } 68 | } 69 | 70 | pub(super) fn parse(tokens: Tokens<'a>) -> IResult, Self> { 71 | if let Ok(tuple) = Self::parse_tuple(tokens) { 72 | return Ok(tuple); 73 | } 74 | 75 | Self::parse_pratt(tokens, 0) 76 | } 77 | 78 | fn parse_infix( 79 | tokens: Tokens<'a>, 80 | lhs: Box, 81 | precedence: usize, 82 | ) -> IResult, Self> { 83 | let location = tokens.location(); 84 | let (tokens, op) = Infix::parse(tokens)?; 85 | let (tokens, rhs) = Self::parse_pratt(tokens, precedence)?; 86 | 87 | Ok((tokens, Self::Infix(op, lhs, Box::new(rhs), location))) 88 | } 89 | 90 | fn parse_lhs(tokens: Tokens<'a>) -> IResult, Self> { 91 | alt(( 92 | map( 93 | tuple(( 94 | cbool_token, 95 | opt(bool_ty), 96 | l_paren_punc, 97 | map(Self::parse, Box::new), 98 | r_paren_punc, 99 | )), 100 | |(_, _, _, expr, _)| Self::ConvertBoolean(expr, tokens.location()), 101 | ), 102 | map( 103 | tuple(( 104 | cbyte_token, 105 | opt(u8_ty), 106 | l_paren_punc, 107 | map(Self::parse, Box::new), 108 | r_paren_punc, 109 | )), 110 | |(_, _, _, expr, _)| Self::ConvertByte(expr, tokens.location()), 111 | ), 112 | map( 113 | tuple(( 114 | cfloat_token, 115 | opt(f32_ty), 116 | l_paren_punc, 117 | map(Self::parse, Box::new), 118 | r_paren_punc, 119 | )), 120 | |(_, _, _, expr, _)| Self::ConvertFloat(expr, tokens.location()), 121 | ), 122 | map( 123 | tuple(( 124 | cint_token, 125 | opt(i32_ty), 126 | l_paren_punc, 127 | map(Self::parse, Box::new), 128 | r_paren_punc, 129 | )), 130 | |(_, _, _, expr, _)| Self::ConvertInteger(expr, tokens.location()), 131 | ), 132 | map( 133 | tuple(( 134 | cstr_token, 135 | opt(str_ty), 136 | l_paren_punc, 137 | map(Self::parse, Box::new), 138 | r_paren_punc, 139 | )), 140 | |(_, _, _, expr, _)| Self::ConvertString(expr, tokens.location()), 141 | ), 142 | map( 143 | tuple(( 144 | abs_token, 145 | opt(alt(( 146 | value(Type::Float, f32_ty), 147 | value(Type::Integer, i32_ty), 148 | ))), 149 | l_paren_punc, 150 | map(Self::parse, Box::new), 151 | r_paren_punc, 152 | )), 153 | |(_, ty, _, expr, _)| Self::Abs(ty, expr, tokens.location()), 154 | ), 155 | map( 156 | tuple(( 157 | sin_token, 158 | opt(f32_ty), 159 | l_paren_punc, 160 | map(Self::parse, Box::new), 161 | r_paren_punc, 162 | )), 163 | |(_, _, _, expr, _)| Self::Sin(expr, tokens.location()), 164 | ), 165 | map( 166 | tuple(( 167 | cos_token, 168 | opt(f32_ty), 169 | l_paren_punc, 170 | map(Self::parse, Box::new), 171 | r_paren_punc, 172 | )), 173 | |(_, _, _, expr, _)| Self::Cos(expr, tokens.location()), 174 | ), 175 | Self::parse_peek, 176 | map( 177 | tuple(( 178 | key_down_token, 179 | opt(bool_ty), 180 | l_paren_punc, 181 | map(Self::parse, Box::new), 182 | r_paren_punc, 183 | )), 184 | |(_, _, _, expr, _)| Self::KeyDown(expr, tokens.location()), 185 | ), 186 | map( 187 | tuple(( 188 | rnd_token, 189 | opt(f32_ty), 190 | opt(pair(l_paren_punc, r_paren_punc)), 191 | )), 192 | |(_, _, _)| Self::Random(tokens.location()), 193 | ), 194 | map( 195 | tuple((timer_token, opt(i32_ty), l_paren_punc, r_paren_punc)), 196 | |(_, _, _, _)| Self::Timer(tokens.location()), 197 | ), 198 | map( 199 | tuple(( 200 | Variable::parse, 201 | l_paren_punc, 202 | separated_list0(comma_punc, Self::parse), 203 | r_paren_punc, 204 | )), 205 | |(id, _, exprs, _)| Self::Function(id, exprs, tokens.location()), 206 | ), 207 | map(Variable::parse, Self::Variable), 208 | map(Literal::parse, Self::Literal), 209 | Self::parse_prefix, 210 | delimited(l_paren_punc, Self::parse, r_paren_punc), 211 | ))(tokens) 212 | } 213 | 214 | fn parse_peek(tokens: Tokens<'a>) -> IResult, Self> { 215 | map( 216 | pair( 217 | preceded(peek_token, opt(Type::parse)), 218 | delimited(l_paren_punc, map(Self::parse, Box::new), r_paren_punc), 219 | ), 220 | |(ty, expr)| Self::Peek(ty, expr, tokens.location()), 221 | )(tokens) 222 | } 223 | 224 | fn parse_pratt(tokens: Tokens<'a>, precedence: usize) -> IResult, Self> { 225 | let (tokens, lhs) = Self::parse_lhs(tokens)?; 226 | 227 | Self::parse_rhs(tokens, lhs, precedence) 228 | } 229 | 230 | fn parse_prefix(tokens: Tokens<'a>) -> IResult, Self> { 231 | map( 232 | pair(Prefix::parse, map(Self::parse_lhs, Box::new)), 233 | |(op, expr)| Self::Prefix(op, expr, tokens.location()), 234 | )(tokens) 235 | } 236 | 237 | fn parse_rhs(tokens: Tokens<'a>, lhs: Self, precedence: usize) -> IResult, Self> { 238 | if tokens.is_empty() { 239 | return Ok((tokens, lhs)); 240 | } 241 | 242 | let (_, next_token) = take(1usize)(tokens)?; 243 | let next_precedence = Self::precedence(next_token[0]); 244 | 245 | if precedence < next_precedence { 246 | let (tokens, rhs) = Self::parse_infix(tokens, Box::new(lhs), next_precedence)?; 247 | 248 | Self::parse_rhs(tokens, rhs, precedence) 249 | } else { 250 | Ok((tokens, lhs)) 251 | } 252 | } 253 | 254 | fn parse_tuple(tokens: Tokens<'a>) -> IResult, Self> { 255 | map( 256 | delimited( 257 | l_sq_bracket_punc, 258 | separated_list0(comma_punc, Self::parse), 259 | r_sq_bracket_punc, 260 | ), 261 | |exprs| Self::Tuple(exprs, tokens.location()), 262 | )(tokens) 263 | } 264 | 265 | fn precedence(token: Token) -> usize { 266 | match token { 267 | Token::And(_) | Token::Mod(_) | Token::Or(_) | Token::Xor(_) => 1, 268 | Token::Equal(_) 269 | | Token::NotEqual(_) 270 | | Token::LessThanEqual(_) 271 | | Token::GreaterThanEqual(_) 272 | | Token::LessThan(_) 273 | | Token::GreaterThan(_) => 2, 274 | Token::Add(_) | Token::Subtract(_) => 3, 275 | Token::Multiply(_) | Token::Divide(_) => 4, 276 | _ => 0, 277 | } 278 | } 279 | } 280 | 281 | impl<'a> Debug for Expression<'a> { 282 | fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { 283 | match self { 284 | Self::Literal(lit) => lit.fmt(f)?, 285 | Self::Tuple(exprs, location) => { 286 | f.write_str("Tuple (")?; 287 | 288 | let mut first = true; 289 | for expr in exprs { 290 | if first { 291 | first = false; 292 | } else { 293 | f.write_str(", ")?; 294 | } 295 | 296 | expr.fmt(f)?; 297 | } 298 | 299 | f.write_str(") ")?; 300 | debug_location(f, *location)?; 301 | } 302 | Self::Variable(var) => var.fmt(f)?, 303 | Self::Infix(op, lhs, rhs, location) => { 304 | f.write_str("Infix (")?; 305 | lhs.fmt(f)?; 306 | f.write_str(" ")?; 307 | op.fmt(f)?; 308 | f.write_str(" ")?; 309 | rhs.fmt(f)?; 310 | f.write_str(") ")?; 311 | debug_location(f, *location)?; 312 | } 313 | Self::Prefix(op, expr, location) => { 314 | f.write_str("Prefix (")?; 315 | op.fmt(f)?; 316 | f.write_str(" ")?; 317 | expr.fmt(f)?; 318 | f.write_str(") ")?; 319 | debug_location(f, *location)?; 320 | } 321 | Self::Function(id, exprs, location) => { 322 | f.write_fmt(format_args!("Function `{}` ", id.name))?; 323 | 324 | if let Some(ty) = id.ty { 325 | f.write_fmt(format_args!("{} ", ty))?; 326 | } 327 | 328 | f.write_str("(")?; 329 | 330 | let mut first = true; 331 | for expr in exprs { 332 | if first { 333 | first = false; 334 | } else { 335 | f.write_str(", ")?; 336 | } 337 | 338 | expr.fmt(f)?; 339 | } 340 | 341 | f.write_str(") ")?; 342 | debug_location(f, *location)?; 343 | } 344 | Self::ConvertBoolean(expr, location) => { 345 | f.write_str("ConvertBoolean (")?; 346 | expr.fmt(f)?; 347 | f.write_str(") ")?; 348 | debug_location(f, *location)?; 349 | } 350 | Self::ConvertByte(expr, location) => { 351 | f.write_str("ConvertByte (")?; 352 | expr.fmt(f)?; 353 | f.write_str(") ")?; 354 | debug_location(f, *location)?; 355 | } 356 | Self::ConvertFloat(expr, location) => { 357 | f.write_str("ConvertFloat (")?; 358 | expr.fmt(f)?; 359 | f.write_str(") ")?; 360 | debug_location(f, *location)?; 361 | } 362 | Self::ConvertInteger(expr, location) => { 363 | f.write_str("ConvertInteger (")?; 364 | expr.fmt(f)?; 365 | f.write_str(") ")?; 366 | debug_location(f, *location)?; 367 | } 368 | Self::ConvertString(expr, location) => { 369 | f.write_str("ConvertString (")?; 370 | expr.fmt(f)?; 371 | f.write_str(") ")?; 372 | debug_location(f, *location)?; 373 | } 374 | Self::Abs(ty, expr, location) => { 375 | f.write_str("Abs ")?; 376 | 377 | if let Some(ty) = ty { 378 | f.write_fmt(format_args!("{} ", ty))?; 379 | } 380 | 381 | f.write_str("(")?; 382 | expr.fmt(f)?; 383 | f.write_str(") ")?; 384 | debug_location(f, *location)?; 385 | } 386 | Self::Sin(expr, location) => { 387 | f.write_str("Sin (")?; 388 | expr.fmt(f)?; 389 | f.write_str(") ")?; 390 | debug_location(f, *location)?; 391 | } 392 | Self::Cos(expr, location) => { 393 | f.write_str("Cos (")?; 394 | expr.fmt(f)?; 395 | f.write_str(") ")?; 396 | debug_location(f, *location)?; 397 | } 398 | Self::Peek(ty, expr, location) => { 399 | f.write_str("Peek ")?; 400 | 401 | if let Some(ty) = ty { 402 | f.write_fmt(format_args!("{} ", ty))?; 403 | } 404 | 405 | f.write_str("(")?; 406 | expr.fmt(f)?; 407 | f.write_str(") ")?; 408 | debug_location(f, *location)?; 409 | } 410 | Self::KeyDown(expr, location) => { 411 | f.write_str("KeyDown (")?; 412 | expr.fmt(f)?; 413 | f.write_str(") ")?; 414 | debug_location(f, *location)?; 415 | } 416 | Self::Random(location) => { 417 | f.write_str("Rnd ")?; 418 | debug_location(f, *location)?; 419 | } 420 | Self::Timer(location) => { 421 | f.write_str("Timer ")?; 422 | debug_location(f, *location)?; 423 | } 424 | } 425 | 426 | Ok(()) 427 | } 428 | } 429 | 430 | #[derive(Clone, Copy)] 431 | #[cfg_attr(test, derive(PartialEq))] 432 | pub enum Prefix { 433 | Minus, 434 | Negate, 435 | } 436 | 437 | impl Prefix { 438 | fn parse(tokens: Tokens) -> IResult { 439 | alt((map(sub_op, |_| Self::Minus), map(not_op, |_| Self::Negate)))(tokens) 440 | } 441 | } 442 | 443 | impl Debug for Prefix { 444 | fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { 445 | match self { 446 | Self::Minus => f.write_str("Minus"), 447 | Self::Negate => f.write_str("Negate"), 448 | } 449 | } 450 | } 451 | 452 | #[derive(Clone, Copy)] 453 | #[cfg_attr(test, derive(PartialEq))] 454 | pub enum Infix<'a> { 455 | Add, 456 | Subtract, 457 | Divide, 458 | Modulus, 459 | Multiply, 460 | Bitwise(Bitwise), 461 | Relation(Relation<'a>), 462 | } 463 | 464 | impl<'a> Infix<'a> { 465 | fn parse(tokens: Tokens<'a>) -> IResult, Self> { 466 | alt(( 467 | map(add_op, |_| Self::Add), 468 | map(sub_op, |_| Self::Subtract), 469 | map(div_op, |_| Self::Divide), 470 | map(mul_op, |_| Self::Multiply), 471 | map(mod_token, |_| Self::Modulus), 472 | map(Bitwise::parse, Self::Bitwise), 473 | map(Relation::parse, Self::Relation), 474 | ))(tokens) 475 | } 476 | } 477 | 478 | impl<'a> Debug for Infix<'a> { 479 | fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { 480 | match self { 481 | Self::Add => f.write_str("Add"), 482 | Self::Subtract => f.write_str("Subtract"), 483 | Self::Divide => f.write_str("Divide"), 484 | Self::Modulus => f.write_str("Modulus"), 485 | Self::Multiply => f.write_str("Multiply"), 486 | Self::Bitwise(Bitwise::And) => f.write_str("And"), 487 | Self::Bitwise(Bitwise::Not) => f.write_str("Not"), 488 | Self::Bitwise(Bitwise::Or) => f.write_str("Or"), 489 | Self::Bitwise(Bitwise::Xor) => f.write_str("Xor"), 490 | Self::Relation(Relation::Equal(_)) => f.write_str("Equal"), 491 | Self::Relation(Relation::GreaterThan(_)) => f.write_str("GreaterThan"), 492 | Self::Relation(Relation::GreaterThanEqual(_)) => f.write_str("GreaterThanEqual"), 493 | Self::Relation(Relation::LessThan(_)) => f.write_str("LessThan"), 494 | Self::Relation(Relation::LessThanEqual(_)) => f.write_str("LessThanEqual"), 495 | Self::Relation(Relation::NotEqual(_)) => f.write_str("NotEqual"), 496 | } 497 | } 498 | } 499 | 500 | impl<'a> std::fmt::Display for Infix<'a> { 501 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 502 | match self { 503 | Self::Add => f.write_str("+"), 504 | Self::Subtract => f.write_str("-"), 505 | Self::Divide => f.write_str("/"), 506 | Self::Modulus => f.write_str("%"), 507 | Self::Multiply => f.write_str("*"), 508 | Self::Bitwise(r) => ::fmt(r, f), 509 | Self::Relation(r) => ::fmt(r, f), 510 | } 511 | } 512 | } 513 | 514 | #[derive(Clone, Copy, Debug)] 515 | #[cfg_attr(test, derive(PartialEq))] 516 | pub enum Bitwise { 517 | Not, 518 | And, 519 | Or, 520 | Xor, 521 | } 522 | 523 | impl Bitwise { 524 | fn parse(tokens: Tokens) -> IResult { 525 | alt(( 526 | map(not_op, |_| Self::Not), 527 | map(and_op, |_| Self::And), 528 | map(or_op, |_| Self::Or), 529 | map(xor_op, |_| Self::Xor), 530 | ))(tokens) 531 | } 532 | } 533 | 534 | impl std::fmt::Display for Bitwise { 535 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 536 | f.write_str(match self { 537 | Self::Not => "NOT", 538 | Self::And => "AND", 539 | Self::Or => "OR", 540 | Self::Xor => "XOR", 541 | }) 542 | } 543 | } 544 | 545 | #[derive(Clone, Copy, Debug)] 546 | #[cfg_attr(test, derive(PartialEq))] 547 | pub enum Relation<'a> { 548 | Equal(Span<'a>), 549 | NotEqual(Span<'a>), 550 | GreaterThanEqual(Span<'a>), 551 | LessThanEqual(Span<'a>), 552 | GreaterThan(Span<'a>), 553 | LessThan(Span<'a>), 554 | } 555 | 556 | impl<'a> Relation<'a> { 557 | pub fn location(self) -> Span<'a> { 558 | match self { 559 | Self::Equal(res) 560 | | Self::GreaterThan(res) 561 | | Self::GreaterThanEqual(res) 562 | | Self::LessThan(res) 563 | | Self::LessThanEqual(res) 564 | | Self::NotEqual(res) => res, 565 | } 566 | } 567 | 568 | pub fn parse(tokens: Tokens<'a>) -> IResult, Self> { 569 | alt(( 570 | map(eq_op, |_| Self::Equal(tokens.location())), 571 | map(ne_op, |_| Self::NotEqual(tokens.location())), 572 | map(gte_op, |_| Self::GreaterThanEqual(tokens.location())), 573 | map(lte_op, |_| Self::LessThanEqual(tokens.location())), 574 | map(gt_op, |_| Self::GreaterThan(tokens.location())), 575 | map(lt_op, |_| Self::LessThan(tokens.location())), 576 | ))(tokens) 577 | } 578 | } 579 | 580 | impl<'a> std::fmt::Display for Relation<'a> { 581 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 582 | f.write_str(match self { 583 | Self::Equal(_) => "=", 584 | Self::NotEqual(_) => "<>", 585 | Self::GreaterThanEqual(_) => ">=", 586 | Self::LessThanEqual(_) => "<=", 587 | Self::GreaterThan(_) => ">", 588 | Self::LessThan(_) => "<", 589 | }) 590 | } 591 | } 592 | 593 | #[cfg(test)] 594 | mod tests { 595 | use { 596 | super::{ 597 | super::{tests::same_parsed, Token}, 598 | *, 599 | }, 600 | crate::tests::span, 601 | }; 602 | 603 | #[test] 604 | fn literal() { 605 | let input = b"TRUE"; 606 | 607 | let expected = Expression::Literal(Literal::Boolean(true, span(0, 1, input))); 608 | 609 | let (_, tokens) = Token::lex(input).unwrap(); 610 | let (_, result) = Expression::parse(Tokens::new(&tokens)).unwrap(); 611 | 612 | assert_eq!(expected, result); 613 | } 614 | 615 | #[test] 616 | fn infix() { 617 | let input = b"1 + 2"; 618 | 619 | let expected = Expression::Infix( 620 | Infix::Add, 621 | Box::new(Expression::Literal(Literal::Integer(1, span(0, 1, input)))), 622 | Box::new(Expression::Literal(Literal::Integer(2, span(4, 1, input)))), 623 | span(2, 1, input), 624 | ); 625 | 626 | let (_, tokens) = Token::lex(input).unwrap(); 627 | let (_, result) = Expression::parse(Tokens::new(&tokens)).unwrap(); 628 | 629 | assert_eq!(expected, result); 630 | } 631 | 632 | #[test] 633 | fn infix_parens() { 634 | let input = b"(1 + 2)"; 635 | 636 | let expected = Expression::Infix( 637 | Infix::Add, 638 | Box::new(Expression::Literal(Literal::Integer(1, span(1, 1, input)))), 639 | Box::new(Expression::Literal(Literal::Integer(2, span(5, 1, input)))), 640 | span(3, 1, input), 641 | ); 642 | 643 | let (_, tokens) = Token::lex(input).unwrap(); 644 | let (_, result) = Expression::parse(Tokens::new(&tokens)).unwrap(); 645 | 646 | assert_eq!(expected, result); 647 | } 648 | 649 | #[test] 650 | fn infix_order() { 651 | let input = b"6 * 2 + 1"; 652 | 653 | let expected = Expression::Infix( 654 | Infix::Add, 655 | Box::new(Expression::Infix( 656 | Infix::Multiply, 657 | Box::new(Expression::Literal(Literal::Integer(6, span(0, 1, input)))), 658 | Box::new(Expression::Literal(Literal::Integer(2, span(4, 1, input)))), 659 | span(2, 1, input), 660 | )), 661 | Box::new(Expression::Literal(Literal::Integer(1, span(8, 1, input)))), 662 | span(6, 1, input), 663 | ); 664 | 665 | let (_, tokens) = Token::lex(input).unwrap(); 666 | let (_, result) = Expression::parse(Tokens::new(&tokens)).unwrap(); 667 | 668 | assert_eq!(expected, result, "{:#?}", result); 669 | } 670 | 671 | #[test] 672 | fn infix_relations() { 673 | let input = b"1 = 1 AND TRUE = FALSE"; 674 | 675 | let expected = Expression::Infix( 676 | Infix::Bitwise(Bitwise::And), 677 | Box::new(Expression::Infix( 678 | Infix::Relation(Relation::Equal(span(2, 1, input))), 679 | Box::new(Expression::Literal(Literal::Integer(1, span(0, 1, input)))), 680 | Box::new(Expression::Literal(Literal::Integer(1, span(4, 1, input)))), 681 | span(2, 1, input), 682 | )), 683 | Box::new(Expression::Infix( 684 | Infix::Relation(Relation::Equal(span(15, 1, input))), 685 | Box::new(Expression::Literal(Literal::Boolean( 686 | true, 687 | span(10, 1, input), 688 | ))), 689 | Box::new(Expression::Literal(Literal::Boolean( 690 | false, 691 | span(17, 1, input), 692 | ))), 693 | span(15, 1, input), 694 | )), 695 | span(6, 1, input), 696 | ); 697 | 698 | let (_, tokens) = Token::lex(input).unwrap(); 699 | let (_, result) = Expression::parse(Tokens::new(&tokens)).unwrap(); 700 | 701 | assert_eq!(expected, result); 702 | } 703 | 704 | #[test] 705 | fn prefix_found() { 706 | let input = b"-a + 1"; 707 | 708 | let expected = Expression::Infix( 709 | Infix::Add, 710 | Box::new(Expression::Prefix( 711 | Prefix::Minus, 712 | Box::new(Expression::Variable(Variable { 713 | name: "a", 714 | location: span(1, 1, input), 715 | ty: None, 716 | })), 717 | span(0, 1, input), 718 | )), 719 | Box::new(Expression::Literal(Literal::Integer(1, span(5, 1, input)))), 720 | span(3, 1, input), 721 | ); 722 | 723 | let (_, tokens) = Token::lex(input).unwrap(); 724 | let (_, result) = Expression::parse(Tokens::new(&tokens)).unwrap(); 725 | 726 | assert_eq!(expected, result); 727 | } 728 | 729 | #[test] 730 | fn prefix_not_found() { 731 | let input = b"-5 + 1"; 732 | 733 | let expected = Expression::Infix( 734 | Infix::Add, 735 | Box::new(Expression::Literal(Literal::Integer(-5, span(0, 1, input)))), 736 | Box::new(Expression::Literal(Literal::Integer(1, span(5, 1, input)))), 737 | span(3, 1, input), 738 | ); 739 | 740 | let (_, tokens) = Token::lex(input).unwrap(); 741 | let (_, result) = Expression::parse(Tokens::new(&tokens)).unwrap(); 742 | 743 | assert_eq!(expected, result); 744 | } 745 | 746 | #[test] 747 | fn math_expressions1() { 748 | let input = b"sin(1.2)"; 749 | 750 | let expected = Expression::Sin( 751 | Box::new(Expression::Literal(Literal::Float(1.2, span(4, 1, input)))), 752 | span(0, 1, input), 753 | ); 754 | 755 | let (_, tokens) = Token::lex(input).unwrap(); 756 | let (_, result) = Expression::parse(Tokens::new(&tokens)).unwrap(); 757 | 758 | assert_eq!(expected, result); 759 | } 760 | 761 | #[test] 762 | fn math_expressions2() { 763 | let input = b"1.0 + sin(1.2) * 2.0"; 764 | 765 | let expected = Expression::Infix( 766 | Infix::Add, 767 | Box::new(Expression::Literal(Literal::Float(1.0, span(0, 1, input)))), 768 | Box::new(Expression::Infix( 769 | Infix::Multiply, 770 | Box::new(Expression::Sin( 771 | Box::new(Expression::Literal(Literal::Float(1.2, span(10, 1, input)))), 772 | span(6, 1, input), 773 | )), 774 | Box::new(Expression::Literal(Literal::Float(2.0, span(17, 1, input)))), 775 | span(15, 1, input), 776 | )), 777 | span(4, 1, input), 778 | ); 779 | 780 | let (_, tokens) = Token::lex(input).unwrap(); 781 | let (_, result) = Expression::parse(Tokens::new(&tokens)).unwrap(); 782 | 783 | assert_eq!(expected, result); 784 | } 785 | 786 | #[test] 787 | fn op_precedence() { 788 | assert!(same_parsed(b"DIM a = 1 + 1", b"DIM a = 1 + 1",)); 789 | assert!(same_parsed(b"DIM a = 6 * 2 + 1", b"DIM a = (6 * 2) + 1",)); 790 | assert!(same_parsed( 791 | b"DIM a = 6 * 2 + 16 / 2", 792 | b"DIM a = (6 * 2) + (16 / 2)", 793 | )); 794 | assert!(same_parsed(b"DIM a = -5", b"DIM a = (-5)",)); 795 | assert!(same_parsed(b"DIM a = -5 + 1", b"DIM a = (-5) + 1",)); 796 | assert!(same_parsed( 797 | b"DIM a = 6 * -2 + 16 / 2", 798 | b"DIM a = (6 * (-2)) + (16 / 2)", 799 | )); 800 | assert!(same_parsed( 801 | b"DIM a = -6 + -2 - -8", 802 | b"DIM a = ((-6) + (-2)) - (-8)", 803 | )); 804 | assert!(same_parsed( 805 | b"DIM a = -6 + -2 - -8", 806 | b"DIM a = (((-6) + (-2)) - (-8))", 807 | )); 808 | assert!(same_parsed( 809 | b"DIM a = -6 * -2 / -8", 810 | b"DIM a = ((-6) * (-2)) / (-8)", 811 | )); 812 | assert!(same_parsed( 813 | b"DIM a = (-6 * -2 / -8)", 814 | b"DIM a = ((-6) * (-2)) / (-8)", 815 | )); 816 | assert!(same_parsed( 817 | b"DIM a = NOT TRUE AND NOT FALSE", 818 | b"DIM a = (NOT TRUE) AND (NOT FALSE)", 819 | )); 820 | assert!(same_parsed( 821 | b"DIM a = TRUE AND FALSE <> TRUE AND TRUE", 822 | b"DIM a = (TRUE AND (FALSE <> TRUE)) AND TRUE", 823 | )); 824 | assert!(same_parsed( 825 | b"DIM a = 1 = 1 AND TRUE = FALSE", 826 | b"DIM a = (1 = 1) AND (TRUE = FALSE)", 827 | )); 828 | assert!(same_parsed( 829 | b"DIM a = b OR c OR d", 830 | b"DIM a = (b OR c) OR d", 831 | )); 832 | assert!(same_parsed( 833 | b"DIM a = 1.0 + COS(2.0) * 3.0", 834 | b"DIM a = 1.0 + (COS(2.0) * 3.0)", 835 | )); 836 | 837 | // The rest should *NOT* match 838 | assert!(!same_parsed(b"DIM a = 1 + TRUE", b"DIM a = 1 + 2",)); 839 | } 840 | } 841 | -------------------------------------------------------------------------------- /src/token/mod.rs: -------------------------------------------------------------------------------- 1 | mod input; 2 | 3 | pub(super) use self::input::Tokens; 4 | 5 | use { 6 | nom::{ 7 | branch::alt, 8 | bytes::complete::{escaped, tag, tag_no_case, take, take_until}, 9 | character::complete::{ 10 | alpha1, alphanumeric1, char, digit1, hex_digit1, line_ending, multispace0, one_of, 11 | satisfy, space0, 12 | }, 13 | combinator::{map, map_res, not, opt, recognize, rest, value}, 14 | multi::{many0, many1}, 15 | number::complete::float, 16 | sequence::{delimited, pair, preceded, terminated}, 17 | IResult, InputLength, 18 | }, 19 | nom_locate::LocatedSpan, 20 | std::{ 21 | fmt::{Debug, Formatter, Result as FmtResult}, 22 | str::from_utf8_unchecked, 23 | str::FromStr, 24 | }, 25 | }; 26 | 27 | pub type Span<'a> = LocatedSpan<&'a [u8]>; 28 | 29 | pub fn debug_location(f: &mut Formatter<'_>, location: Span) -> FmtResult { 30 | f.write_fmt(format_args!( 31 | "@ {}:{}", 32 | location.location_offset(), 33 | location.location_line() 34 | )) 35 | } 36 | 37 | pub fn location_string(location: Span) -> String { 38 | format!( 39 | "Line {}, column {}", 40 | location.location_line(), 41 | location.get_column(), 42 | ) 43 | } 44 | 45 | #[derive(Debug)] 46 | pub struct AsciiError; 47 | 48 | macro_rules! token { 49 | ($func_name: ident, $tag_string: literal, $output_token: expr) => { 50 | fn $func_name(s: Span<'_>) -> IResult, Token> { 51 | map(tag($tag_string), |_| $output_token(s))(s) 52 | } 53 | }; 54 | } 55 | 56 | #[derive(Clone, Copy)] 57 | #[cfg_attr(test, derive(PartialEq))] 58 | pub enum Token<'a> { 59 | Illegal(Span<'a>), 60 | Comment(Span<'a>), 61 | Identifer(&'a str, Span<'a>), 62 | // Literals 63 | BooleanLiteral(bool, Span<'a>), 64 | FloatLiteral(f32, Span<'a>), 65 | IntegerLiteral(i32, Span<'a>), 66 | StringLiteral(&'a str, Span<'a>), 67 | // Operators 68 | Add(Span<'a>), 69 | Subtract(Span<'a>), 70 | Multiply(Span<'a>), 71 | Divide(Span<'a>), 72 | Equal(Span<'a>), 73 | NotEqual(Span<'a>), 74 | GreaterThanEqual(Span<'a>), 75 | LessThanEqual(Span<'a>), 76 | GreaterThan(Span<'a>), 77 | LessThan(Span<'a>), 78 | Not(Span<'a>), 79 | And(Span<'a>), 80 | Or(Span<'a>), 81 | Xor(Span<'a>), 82 | Pset(Span<'a>), 83 | Preset(Span<'a>), 84 | Tset(Span<'a>), 85 | // Reserved Words 86 | ConvertBoolean(Span<'a>), 87 | ConvertByte(Span<'a>), 88 | ConvertFloat(Span<'a>), 89 | ConvertInteger(Span<'a>), 90 | ConvertString(Span<'a>), 91 | Abs(Span<'a>), 92 | Sin(Span<'a>), 93 | Call(Span<'a>), 94 | Case(Span<'a>), 95 | Cos(Span<'a>), 96 | ClearScreen(Span<'a>), 97 | Color(Span<'a>), 98 | Dimension(Span<'a>), 99 | Do(Span<'a>), 100 | Else(Span<'a>), 101 | End(Span<'a>), 102 | Exit(Span<'a>), 103 | For(Span<'a>), 104 | Function(Span<'a>), 105 | Get(Span<'a>), 106 | Goto(Span<'a>), 107 | If(Span<'a>), 108 | Is(Span<'a>), 109 | KeyDown(Span<'a>), 110 | Line(Span<'a>), 111 | Locate(Span<'a>), 112 | Loop(Span<'a>), 113 | Mod(Span<'a>), 114 | Next(Span<'a>), 115 | Palette(Span<'a>), 116 | Peek(Span<'a>), 117 | Poke(Span<'a>), 118 | Print(Span<'a>), 119 | Put(Span<'a>), 120 | Rectangle(Span<'a>), 121 | Rnd(Span<'a>), 122 | Select(Span<'a>), 123 | Step(Span<'a>), 124 | Sub(Span<'a>), 125 | Then(Span<'a>), 126 | Timer(Span<'a>), 127 | To(Span<'a>), 128 | Until(Span<'a>), 129 | Wend(Span<'a>), 130 | While(Span<'a>), 131 | Yield(Span<'a>), 132 | // Punctuation 133 | Colon(Span<'a>), 134 | Comma(Span<'a>), 135 | Continuation(Span<'a>), 136 | EndOfLine(Span<'a>), 137 | LeftParenthesis(Span<'a>), 138 | RightParenthesis(Span<'a>), 139 | LeftCurlyBracket(Span<'a>), 140 | RightCurlyBracket(Span<'a>), 141 | LeftSquareBracket(Span<'a>), 142 | RightSquareBracket(Span<'a>), 143 | Semicolon(Span<'a>), 144 | // Type Specifiers 145 | BooleanType(Span<'a>), 146 | ByteType(Span<'a>), 147 | FloatType(Span<'a>), 148 | IntegerType(Span<'a>), 149 | StringType(Span<'a>), 150 | } 151 | 152 | impl<'a> Token<'a> { 153 | pub fn ascii_str(v: &'a [u8]) -> Result<&'a str, AsciiError> { 154 | for &char in v { 155 | if !Self::valid_ascii_subset(char.into()) { 156 | return Err(AsciiError); 157 | } 158 | } 159 | 160 | Ok(unsafe { 161 | // SAFETY: we checked that the bytes `v` are valid UTF-8. 162 | from_utf8_unchecked(v) 163 | }) 164 | } 165 | 166 | pub fn boolean_literal(self) -> Option { 167 | match self { 168 | Self::BooleanLiteral(val, _) => Some(val), 169 | _ => None, 170 | } 171 | } 172 | 173 | pub fn float_literal(self) -> Option { 174 | match self { 175 | Self::FloatLiteral(val, _) => Some(val), 176 | _ => None, 177 | } 178 | } 179 | 180 | pub fn integer_literal(self) -> Option { 181 | match self { 182 | Self::IntegerLiteral(val, _) => Some(val), 183 | _ => None, 184 | } 185 | } 186 | 187 | pub fn identifier(self) -> Option<&'a str> { 188 | match self { 189 | Self::Identifer(val, _) => Some(val), 190 | _ => None, 191 | } 192 | } 193 | 194 | pub fn lex(bytes: &'a [u8]) -> IResult, Vec> { 195 | let (tokens, mut result) = delimited( 196 | multispace0, 197 | many0(delimited( 198 | space0, 199 | alt(( 200 | Self::lex_comment, 201 | Self::lex_integer, 202 | Self::lex_ty, 203 | Self::lex_op, 204 | Self::lex_punctuation, 205 | Self::lex_str, 206 | Self::lex_float, 207 | Self::lex_hex, 208 | Self::lex_word, 209 | Self::lex_illegal, 210 | )), 211 | space0, 212 | )), 213 | multispace0, 214 | )(Span::new(bytes))?; 215 | 216 | assert!(tokens.is_empty()); 217 | 218 | // Remove comments 219 | result.retain(|token| !matches!(token, Self::Comment(_))); 220 | 221 | Ok((tokens, result)) 222 | } 223 | 224 | fn lex_comment(input: Span<'a>) -> IResult, Self> { 225 | value( 226 | Self::Comment(input), 227 | preceded( 228 | multispace0, 229 | pair( 230 | alt((map(char('\''), |_| ()), map(tag("REM "), |_| ()))), 231 | alt((take_until("\n"), rest)), 232 | ), 233 | ), 234 | )(input) 235 | } 236 | 237 | fn lex_float(input: Span<'a>) -> IResult, Self> { 238 | map(float, |f| Self::FloatLiteral(f, input))(input) 239 | } 240 | 241 | fn lex_hex(input: Span<'a>) -> IResult, Self> { 242 | map( 243 | map_res( 244 | preceded( 245 | tag_no_case("&h"), 246 | recognize(many1(terminated(hex_digit1, opt(char('_'))))), 247 | ), 248 | |input: Span<'a>| { 249 | // TODO: Remove the underscores! 250 | i32::from_str_radix(Self::ascii_str(input.fragment()).unwrap(), 16) 251 | }, 252 | ), 253 | |int| Self::IntegerLiteral(int, input), 254 | )(input) 255 | } 256 | 257 | fn lex_illegal(input: Span<'a>) -> IResult, Self> { 258 | map(take(1usize), |_| Self::Illegal(input))(input) 259 | } 260 | 261 | fn lex_integer(input: Span<'a>) -> IResult, Self> { 262 | token!(sub, "-", Token::Subtract); 263 | 264 | map( 265 | terminated( 266 | pair::<_, _, i32, _, _, _>( 267 | map(opt(value(-1i32, sub)), |mag| mag.unwrap_or(1)), 268 | map_res( 269 | map_res(digit1, |num: Span<'a>| Self::ascii_str(num.fragment())), 270 | FromStr::from_str, 271 | ), 272 | ), 273 | not(one_of(".eE")), 274 | ), 275 | |(mag, num)| Self::IntegerLiteral(mag * num, input), 276 | )(input) 277 | } 278 | 279 | fn lex_op(input: Span<'a>) -> IResult, Self> { 280 | token!(add, "+", Token::Add); 281 | token!(sub, "-", Token::Subtract); 282 | token!(mul, "*", Token::Multiply); 283 | token!(div, "/", Token::Divide); 284 | token!(eq, "=", Token::Equal); 285 | token!(ne, "<>", Token::NotEqual); 286 | token!(gte, ">=", Token::GreaterThanEqual); 287 | token!(gt, ">", Token::GreaterThan); 288 | token!(lte, "<=", Token::LessThanEqual); 289 | token!(lt, "<", Token::LessThan); 290 | token!(not, "NOT", Token::Not); 291 | token!(and, "AND", Token::And); 292 | token!(or, "OR", Token::Or); 293 | token!(xor, "XOR", Token::Xor); 294 | token!(pset, "PSET", Token::Pset); 295 | token!(preset, "PRESET", Token::Preset); 296 | token!(tset, "TSET", Token::Tset); 297 | 298 | alt(( 299 | add, sub, mul, div, eq, ne, gte, gt, lte, lt, not, and, or, xor, pset, preset, tset, 300 | ))(input) 301 | } 302 | 303 | fn lex_punctuation(input: Span<'a>) -> IResult, Self> { 304 | token!(colon, ":", Token::Colon); 305 | token!(comma, ",", Token::Comma); 306 | token!(continuation, "_", Token::Continuation); 307 | token!(l_curly_bracket, "{", Token::LeftCurlyBracket); 308 | token!(l_paren, "(", Token::LeftParenthesis); 309 | token!(l_sq_bracket, "[", Token::LeftSquareBracket); 310 | token!(r_curly_bracket, "}", Token::RightCurlyBracket); 311 | token!(r_paren, ")", Token::RightParenthesis); 312 | token!(r_sq_bracket, "]", Token::RightSquareBracket); 313 | token!(semicolon, ";", Token::Semicolon); 314 | 315 | alt(( 316 | colon, 317 | comma, 318 | continuation, 319 | l_curly_bracket, 320 | l_paren, 321 | l_sq_bracket, 322 | r_curly_bracket, 323 | r_paren, 324 | r_sq_bracket, 325 | semicolon, 326 | map(many1(line_ending), |_| Self::EndOfLine(input)), 327 | ))(input) 328 | } 329 | 330 | fn lex_str(input: Span<'a>) -> IResult, Self> { 331 | fn escape(s: Span) -> IResult { 332 | escaped( 333 | satisfy(|c| c != '\\' && c != '\"' && Token::valid_ascii_subset(c)), 334 | '\\', 335 | one_of("\\\""), 336 | )(s) 337 | } 338 | 339 | map( 340 | delimited( 341 | tag("\""), 342 | map_res(escape, |s| Self::ascii_str(s.fragment())), 343 | tag("\""), 344 | ), 345 | |str| Self::StringLiteral(str, input), 346 | )(input) 347 | } 348 | 349 | fn lex_ty(input: Span<'a>) -> IResult, Self> { 350 | token!(boolean, "?", Token::BooleanType); 351 | token!(byte, "@", Token::ByteType); 352 | token!(integer, "%", Token::IntegerType); 353 | token!(float, "!", Token::FloatType); 354 | token!(string, "$", Token::StringType); 355 | 356 | alt((boolean, byte, integer, float, string))(input) 357 | } 358 | 359 | fn lex_word(input: Span<'a>) -> IResult, Self> { 360 | map_res( 361 | recognize(pair( 362 | alt((alpha1::, _>, tag("_"))), 363 | many0(alt((alphanumeric1, tag("_")))), 364 | )), 365 | |s| { 366 | Self::ascii_str(s.fragment()).map(|str| match str.to_ascii_uppercase().as_str() { 367 | "BOOLEAN" => Self::ConvertBoolean(input), 368 | "BYTE" => Self::ConvertByte(input), 369 | "FLOAT" => Self::ConvertFloat(input), 370 | "INT" => Self::ConvertInteger(input), 371 | "STR" => Self::ConvertString(input), 372 | "ABS" => Self::Abs(input), 373 | "SIN" => Self::Sin(input), 374 | "CALL" => Self::Call(input), 375 | "CASE" => Self::Case(input), 376 | "COS" => Self::Cos(input), 377 | "CLS" => Self::ClearScreen(input), 378 | "COLOR" => Self::Color(input), 379 | "DIM" => Self::Dimension(input), 380 | "DO" => Self::Do(input), 381 | "ELSE" => Self::Else(input), 382 | "END" => Self::End(input), 383 | "EXIT" => Self::Exit(input), 384 | "FOR" => Self::For(input), 385 | "FUNCTION" => Self::Function(input), 386 | "GET" => Self::Get(input), 387 | "GOTO" => Self::Goto(input), 388 | "IF" => Self::If(input), 389 | "IS" => Self::Is(input), 390 | "KEYDOWN" => Self::KeyDown(input), 391 | "LINE" => Self::Line(input), 392 | "LOCATE" => Self::Locate(input), 393 | "LOOP" => Self::Loop(input), 394 | "MOD" => Self::Mod(input), 395 | "NEXT" => Self::Next(input), 396 | "PALETTE" => Self::Palette(input), 397 | "PEEK" => Self::Peek(input), 398 | "POKE" => Self::Poke(input), 399 | "PRINT" => Self::Print(input), 400 | "PUT" => Self::Put(input), 401 | "RECTANGLE" => Self::Rectangle(input), 402 | "RND" => Self::Rnd(input), 403 | "SELECT" => Self::Select(input), 404 | "STEP" => Self::Step(input), 405 | "SUB" => Self::Sub(input), 406 | "THEN" => Self::Then(input), 407 | "TIMER" => Self::Timer(input), 408 | "TO" => Self::To(input), 409 | "UNTIL" => Self::Until(input), 410 | "WEND" => Self::Wend(input), 411 | "WHILE" => Self::While(input), 412 | "YIELD" => Self::Yield(input), 413 | "TRUE" => Self::BooleanLiteral(true, input), 414 | "FALSE" => Self::BooleanLiteral(false, input), 415 | _ => Self::Identifer(str, input), 416 | }) 417 | }, 418 | )(input) 419 | } 420 | 421 | pub fn location(&self) -> Span<'a> { 422 | match self { 423 | Self::Illegal(s) 424 | | Self::Comment(s) 425 | | Self::Identifer(_, s) 426 | | Self::BooleanLiteral(_, s) 427 | | Self::FloatLiteral(_, s) 428 | | Self::IntegerLiteral(_, s) 429 | | Self::StringLiteral(_, s) 430 | | Self::Add(s) 431 | | Self::Subtract(s) 432 | | Self::Multiply(s) 433 | | Self::Divide(s) 434 | | Self::Equal(s) 435 | | Self::NotEqual(s) 436 | | Self::GreaterThanEqual(s) 437 | | Self::LessThanEqual(s) 438 | | Self::GreaterThan(s) 439 | | Self::LessThan(s) 440 | | Self::Not(s) 441 | | Self::And(s) 442 | | Self::Or(s) 443 | | Self::Xor(s) 444 | | Self::Pset(s) 445 | | Self::Preset(s) 446 | | Self::Tset(s) 447 | | Self::ConvertBoolean(s) 448 | | Self::ConvertByte(s) 449 | | Self::ConvertFloat(s) 450 | | Self::ConvertInteger(s) 451 | | Self::ConvertString(s) 452 | | Self::Abs(s) 453 | | Self::Sin(s) 454 | | Self::Call(s) 455 | | Self::Case(s) 456 | | Self::Cos(s) 457 | | Self::ClearScreen(s) 458 | | Self::Color(s) 459 | | Self::Dimension(s) 460 | | Self::Do(s) 461 | | Self::Else(s) 462 | | Self::End(s) 463 | | Self::Exit(s) 464 | | Self::For(s) 465 | | Self::Function(s) 466 | | Self::Get(s) 467 | | Self::Goto(s) 468 | | Self::If(s) 469 | | Self::Is(s) 470 | | Self::KeyDown(s) 471 | | Self::Line(s) 472 | | Self::Locate(s) 473 | | Self::Loop(s) 474 | | Self::Mod(s) 475 | | Self::Next(s) 476 | | Self::Palette(s) 477 | | Self::Peek(s) 478 | | Self::Poke(s) 479 | | Self::Print(s) 480 | | Self::Put(s) 481 | | Self::Rectangle(s) 482 | | Self::Rnd(s) 483 | | Self::Select(s) 484 | | Self::Step(s) 485 | | Self::Sub(s) 486 | | Self::Then(s) 487 | | Self::Timer(s) 488 | | Self::To(s) 489 | | Self::Until(s) 490 | | Self::Wend(s) 491 | | Self::While(s) 492 | | Self::Yield(s) 493 | | Self::Colon(s) 494 | | Self::Comma(s) 495 | | Self::Continuation(s) 496 | | Self::EndOfLine(s) 497 | | Self::LeftParenthesis(s) 498 | | Self::RightParenthesis(s) 499 | | Self::LeftCurlyBracket(s) 500 | | Self::RightCurlyBracket(s) 501 | | Self::LeftSquareBracket(s) 502 | | Self::RightSquareBracket(s) 503 | | Self::Semicolon(s) 504 | | Self::BooleanType(s) 505 | | Self::ByteType(s) 506 | | Self::IntegerType(s) 507 | | Self::FloatType(s) 508 | | Self::StringType(s) => *s, 509 | } 510 | } 511 | 512 | pub fn string_literal(self) -> Option<&'a str> { 513 | match self { 514 | Self::StringLiteral(val, _) => Some(val), 515 | _ => None, 516 | } 517 | } 518 | 519 | /// This is the subset of ascii JW-Basic supports 520 | fn valid_ascii_subset(c: char) -> bool { 521 | // 32 -> 127 inclusive 522 | matches!(c, '\x20'..='\x7F') 523 | } 524 | } 525 | 526 | impl<'a> Debug for Token<'a> { 527 | fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { 528 | match self { 529 | Self::Illegal(..) => f.write_str("Illegal"), 530 | Self::Comment(..) => f.write_str("Comment"), 531 | Self::Identifer(id, _) => f.write_fmt(format_args!("Identifer `{id}`")), 532 | Self::BooleanLiteral(bool, ..) => f.write_fmt(format_args!("BooleanLiteral `{bool}`")), 533 | Self::FloatLiteral(f32, ..) => f.write_fmt(format_args!("FloatLiteral `{f32}`")), 534 | Self::IntegerLiteral(i32, ..) => f.write_fmt(format_args!("IntegerLiteral `{i32}`")), 535 | Self::StringLiteral(str, ..) => f.write_fmt(format_args!("StringLiteral `{str}`")), 536 | Self::Add(..) => f.write_str("Add"), 537 | Self::Subtract(..) => f.write_str("Subtract"), 538 | Self::Multiply(..) => f.write_str("Multiply"), 539 | Self::Divide(..) => f.write_str("Divide"), 540 | Self::Equal(..) => f.write_str("Equal"), 541 | Self::NotEqual(..) => f.write_str("NotEqual"), 542 | Self::GreaterThanEqual(..) => f.write_str("GreaterThanEqual"), 543 | Self::LessThanEqual(..) => f.write_str("LessThanEqual"), 544 | Self::GreaterThan(..) => f.write_str("GreaterThan"), 545 | Self::LessThan(..) => f.write_str("LessThan"), 546 | Self::Not(..) => f.write_str("Not"), 547 | Self::And(..) => f.write_str("And"), 548 | Self::Or(..) => f.write_str("Or"), 549 | Self::Xor(..) => f.write_str("Xor"), 550 | Self::Pset(..) => f.write_str("Pset"), 551 | Self::Preset(..) => f.write_str("Preset"), 552 | Self::Tset(..) => f.write_str("Tset"), 553 | Self::ConvertBoolean(..) => f.write_str("ConvertBoolean"), 554 | Self::ConvertByte(..) => f.write_str("ConvertByte"), 555 | Self::ConvertFloat(..) => f.write_str("ConvertFloat"), 556 | Self::ConvertInteger(..) => f.write_str("ConvertInteger"), 557 | Self::ConvertString(..) => f.write_str("ConvertString"), 558 | Self::Abs(..) => f.write_str("Abs"), 559 | Self::Sin(..) => f.write_str("Sin"), 560 | Self::Call(..) => f.write_str("Call"), 561 | Self::Case(..) => f.write_str("Case"), 562 | Self::Cos(..) => f.write_str("Cos"), 563 | Self::ClearScreen(..) => f.write_str("ClearScreen"), 564 | Self::Color(..) => f.write_str("Color"), 565 | Self::Dimension(..) => f.write_str("Dimension"), 566 | Self::Do(..) => f.write_str("Do"), 567 | Self::Else(..) => f.write_str("Else"), 568 | Self::End(..) => f.write_str("End"), 569 | Self::Exit(..) => f.write_str("End"), 570 | Self::For(..) => f.write_str("For"), 571 | Self::Function(..) => f.write_str("Function"), 572 | Self::Get(..) => f.write_str("Get"), 573 | Self::Goto(..) => f.write_str("Goto"), 574 | Self::If(..) => f.write_str("If"), 575 | Self::Is(..) => f.write_str("Is"), 576 | Self::KeyDown(..) => f.write_str("KeyDown"), 577 | Self::Line(..) => f.write_str("Line"), 578 | Self::Locate(..) => f.write_str("Locate"), 579 | Self::Loop(..) => f.write_str("Loop"), 580 | Self::Mod(..) => f.write_str("Mod"), 581 | Self::Next(..) => f.write_str("Next"), 582 | Self::Palette(..) => f.write_str("Pallete"), 583 | Self::Peek(..) => f.write_str("Peek"), 584 | Self::Poke(..) => f.write_str("Poke"), 585 | Self::Print(..) => f.write_str("Print"), 586 | Self::Put(..) => f.write_str("Put"), 587 | Self::Rectangle(..) => f.write_str("Rectangle"), 588 | Self::Rnd(..) => f.write_str("Return"), 589 | Self::Select(..) => f.write_str("Select"), 590 | Self::Step(..) => f.write_str("Step"), 591 | Self::Sub(..) => f.write_str("Sub"), 592 | Self::Then(..) => f.write_str("Then"), 593 | Self::Timer(..) => f.write_str("Timer"), 594 | Self::To(..) => f.write_str("To"), 595 | Self::Until(..) => f.write_str("Until"), 596 | Self::Wend(..) => f.write_str("Wend"), 597 | Self::While(..) => f.write_str("While"), 598 | Self::Yield(..) => f.write_str("Yield"), 599 | Self::Colon(..) => f.write_str("Colon"), 600 | Self::Comma(..) => f.write_str("Comma"), 601 | Self::Continuation(..) => f.write_str("Continuation"), 602 | Self::EndOfLine(..) => f.write_str("EndOfLine"), 603 | Self::LeftParenthesis(..) => f.write_str("LeftParenthesis"), 604 | Self::RightParenthesis(..) => f.write_str("RightParenthesis"), 605 | Self::LeftCurlyBracket(..) => f.write_str("LeftCurlyBracket"), 606 | Self::RightCurlyBracket(..) => f.write_str("RightCurlyBracket"), 607 | Self::LeftSquareBracket(..) => f.write_str("LeftSquareBracket"), 608 | Self::RightSquareBracket(..) => f.write_str("RightSquareBracket"), 609 | Self::Semicolon(..) => f.write_str("Semicolon"), 610 | Self::BooleanType(..) => f.write_str("BooleanType"), 611 | Self::ByteType(..) => f.write_str("ByteType"), 612 | Self::FloatType(..) => f.write_str("FloatType"), 613 | Self::IntegerType(..) => f.write_str("IntegerType"), 614 | Self::StringType(..) => f.write_str("StringType"), 615 | }?; 616 | 617 | f.write_str(" ")?; 618 | debug_location(f, self.location()) 619 | } 620 | } 621 | 622 | impl<'a> InputLength for Token<'a> { 623 | #[inline] 624 | fn input_len(&self) -> usize { 625 | 1 626 | } 627 | } 628 | 629 | #[cfg(test)] 630 | mod tests { 631 | use {super::*, crate::tests::span}; 632 | 633 | #[test] 634 | fn basic_tokens() { 635 | let input = b"=\n\t=\n="; 636 | 637 | let expected = vec![ 638 | Token::Equal(span(0, 1, input)), 639 | Token::EndOfLine(span(1, 1, input)), 640 | Token::Equal(span(3, 2, input)), 641 | Token::EndOfLine(span(4, 2, input)), 642 | Token::Equal(span(5, 3, input)), 643 | ]; 644 | 645 | let (_, result) = Token::lex(input).unwrap(); 646 | 647 | assert_eq!(expected, result); 648 | 649 | assert_eq!(result[2].location().get_line_beginning(), b"\t="); 650 | assert_eq!(result[2].location().get_column(), 2); 651 | assert_eq!(result[2].location().location_line(), 2); 652 | 653 | assert_eq!(result[4].location().get_line_beginning(), b"="); 654 | assert_eq!(result[4].location().get_column(), 1); 655 | assert_eq!(result[4].location().location_line(), 3); 656 | } 657 | 658 | #[test] 659 | fn keyword_tokens() { 660 | let input = b"DIM five = 5\n\ 661 | DIM ten = 10\n\ 662 | DIM add = FUNCTION(x, y) {\n\ 663 | x + y\n\ 664 | }\n\ 665 | DIM result = add(five, ten)"; 666 | 667 | let expected = vec![ 668 | Token::Dimension(span(0, 1, input)), 669 | Token::Identifer("five", span(4, 1, input)), 670 | Token::Equal(span(9, 1, input)), 671 | Token::IntegerLiteral(5, span(11, 1, input)), 672 | Token::EndOfLine(span(12, 1, input)), 673 | Token::Dimension(span(13, 2, input)), 674 | Token::Identifer("ten", span(17, 2, input)), 675 | Token::Equal(span(21, 2, input)), 676 | Token::IntegerLiteral(10, span(23, 2, input)), 677 | Token::EndOfLine(span(25, 2, input)), 678 | Token::Dimension(span(26, 3, input)), 679 | Token::Identifer("add", span(30, 3, input)), 680 | Token::Equal(span(34, 3, input)), 681 | Token::Function(span(36, 3, input)), 682 | Token::LeftParenthesis(span(44, 3, input)), 683 | Token::Identifer("x", span(45, 3, input)), 684 | Token::Comma(span(46, 3, input)), 685 | Token::Identifer("y", span(48, 3, input)), 686 | Token::RightParenthesis(span(49, 3, input)), 687 | Token::LeftCurlyBracket(span(51, 3, input)), 688 | Token::EndOfLine(span(52, 3, input)), 689 | Token::Identifer("x", span(53, 4, input)), 690 | Token::Add(span(55, 4, input)), 691 | Token::Identifer("y", span(57, 4, input)), 692 | Token::EndOfLine(span(58, 4, input)), 693 | Token::RightCurlyBracket(span(59, 5, input)), 694 | Token::EndOfLine(span(60, 5, input)), 695 | Token::Dimension(span(61, 6, input)), 696 | Token::Identifer("result", span(65, 6, input)), 697 | Token::Equal(span(72, 6, input)), 698 | Token::Identifer("add", span(74, 6, input)), 699 | Token::LeftParenthesis(span(77, 6, input)), 700 | Token::Identifer("five", span(78, 6, input)), 701 | Token::Comma(span(82, 6, input)), 702 | Token::Identifer("ten", span(84, 6, input)), 703 | Token::RightParenthesis(span(87, 6, input)), 704 | ]; 705 | 706 | let (_, result) = Token::lex(input).unwrap(); 707 | 708 | assert_eq!(expected, result); 709 | } 710 | 711 | #[test] 712 | fn escaped_strings() { 713 | let input = br#"DIM a = "te\"st""#; 714 | 715 | let expected = vec![ 716 | Token::Dimension(span(0, 1, input)), 717 | Token::Identifer("a", span(4, 1, input)), 718 | Token::Equal(span(6, 1, input)), 719 | Token::StringLiteral(r#"te\"st"#, span(8, 1, input)), 720 | ]; 721 | 722 | let (_, result) = Token::lex(input).unwrap(); 723 | 724 | assert_eq!(expected, result); 725 | 726 | assert_eq!( 727 | expected[3].location().get_line_beginning(), 728 | b"DIM a = \"te\\\"st\"" 729 | ); 730 | assert_eq!(expected[3].location().get_column(), 9); 731 | assert_eq!(expected[3].location().location_line(), 1); 732 | } 733 | 734 | #[test] 735 | fn string_literals() { 736 | let input = b"\"foobar\""; 737 | 738 | assert_eq!( 739 | vec![Token::StringLiteral("foobar", span(0, 1, input))], 740 | Token::lex(input).unwrap().1 741 | ); 742 | 743 | let input = b"\"foo bar\""; 744 | 745 | assert_eq!( 746 | vec![Token::StringLiteral("foo bar", span(0, 1, input))], 747 | Token::lex(input).unwrap().1 748 | ); 749 | 750 | let input = b"\"foo\\\\bar\""; 751 | 752 | assert_eq!( 753 | vec![Token::StringLiteral("foo\\\\bar", span(0, 1, input))], 754 | Token::lex(input).unwrap().1 755 | ); 756 | 757 | let input = b"\"foo\\\"bar\""; 758 | 759 | assert_eq!( 760 | vec![Token::StringLiteral("foo\\\"bar", span(0, 1, input))], 761 | Token::lex(input).unwrap().1 762 | ); 763 | 764 | let input = b"\"foo\nbar\""; 765 | 766 | assert_eq!( 767 | vec![ 768 | Token::Illegal(span(0, 1, input)), 769 | Token::Identifer("foo", span(1, 1, input)), 770 | Token::EndOfLine(span(4, 1, input)), 771 | Token::Identifer("bar", span(5, 2, input)), 772 | Token::Illegal(span(8, 2, input)), 773 | ], 774 | Token::lex(input).unwrap().1 775 | ); 776 | 777 | let input = b"\"foo\xF0\x9F\x92\xA9bar\""; 778 | 779 | assert_eq!( 780 | vec![ 781 | Token::Illegal(span(0, 1, input)), 782 | Token::Identifer("foo", span(1, 1, input)), 783 | Token::Illegal(span(4, 1, input)), 784 | Token::Illegal(span(5, 1, input)), 785 | Token::Illegal(span(6, 1, input)), 786 | Token::Illegal(span(7, 1, input)), 787 | Token::Identifer("bar", span(8, 1, input)), 788 | Token::Illegal(span(11, 1, input)), 789 | ], 790 | Token::lex(input).unwrap().1 791 | ); 792 | } 793 | 794 | #[test] 795 | fn integer_literals() { 796 | let input = b"042"; 797 | 798 | assert_eq!( 799 | vec![Token::IntegerLiteral(42, span(0, 1, input))], 800 | Token::lex(input).unwrap().1 801 | ); 802 | 803 | let input = b"042%"; 804 | 805 | assert_eq!( 806 | vec![ 807 | Token::IntegerLiteral(42, span(0, 1, input)), 808 | Token::IntegerType(span(3, 1, input)) 809 | ], 810 | Token::lex(input).unwrap().1 811 | ); 812 | 813 | let input = b"&hff"; 814 | 815 | assert_eq!( 816 | vec![Token::IntegerLiteral(255, span(0, 1, input))], 817 | Token::lex(input).unwrap().1 818 | ); 819 | 820 | let input = b"&HfFfF"; 821 | 822 | assert_eq!( 823 | vec![Token::IntegerLiteral(65535, span(0, 1, input))], 824 | Token::lex(input).unwrap().1 825 | ); 826 | } 827 | 828 | #[test] 829 | fn if_tree() { 830 | let input = b"IF (a = 10) {\n\ 831 | RETURN a\n\ 832 | } ELSE IF (a <> 20) {\n\ 833 | RETURN NOT a\n\ 834 | } ELSE IF (a > 20) {\n\ 835 | RETURN -30 / 40 * 50\n\ 836 | } ELSE IF (a < 30) {\n\ 837 | RETURN TRUE\n\ 838 | }\n\ 839 | RETURN FALSE"; 840 | 841 | let expected = vec![ 842 | Token::If(span(0, 1, input)), 843 | Token::LeftParenthesis(span(3, 1, input)), 844 | Token::Identifer("a", span(4, 1, input)), 845 | Token::Equal(span(6, 1, input)), 846 | Token::IntegerLiteral(10, span(8, 1, input)), 847 | Token::RightParenthesis(span(10, 1, input)), 848 | Token::LeftCurlyBracket(span(12, 1, input)), 849 | Token::EndOfLine(span(13, 1, input)), 850 | Token::Identifer("RETURN", span(14, 2, input)), 851 | Token::Identifer("a", span(21, 2, input)), 852 | Token::EndOfLine(span(22, 2, input)), 853 | Token::RightCurlyBracket(span(23, 3, input)), 854 | Token::Else(span(25, 3, input)), 855 | Token::If(span(30, 3, input)), 856 | Token::LeftParenthesis(span(33, 3, input)), 857 | Token::Identifer("a", span(34, 3, input)), 858 | Token::NotEqual(span(36, 3, input)), 859 | Token::IntegerLiteral(20, span(39, 3, input)), 860 | Token::RightParenthesis(span(41, 3, input)), 861 | Token::LeftCurlyBracket(span(43, 3, input)), 862 | Token::EndOfLine(span(44, 3, input)), 863 | Token::Identifer("RETURN", span(45, 4, input)), 864 | Token::Not(span(52, 4, input)), 865 | Token::Identifer("a", span(56, 4, input)), 866 | Token::EndOfLine(span(57, 4, input)), 867 | Token::RightCurlyBracket(span(58, 5, input)), 868 | Token::Else(span(60, 5, input)), 869 | Token::If(span(65, 5, input)), 870 | Token::LeftParenthesis(span(68, 5, input)), 871 | Token::Identifer("a", span(69, 5, input)), 872 | Token::GreaterThan(span(71, 5, input)), 873 | Token::IntegerLiteral(20, span(73, 5, input)), 874 | Token::RightParenthesis(span(75, 5, input)), 875 | Token::LeftCurlyBracket(span(77, 5, input)), 876 | Token::EndOfLine(span(78, 5, input)), 877 | Token::Identifer("RETURN", span(79, 6, input)), 878 | Token::IntegerLiteral(-30, span(86, 6, input)), 879 | Token::Divide(span(90, 6, input)), 880 | Token::IntegerLiteral(40, span(92, 6, input)), 881 | Token::Multiply(span(95, 6, input)), 882 | Token::IntegerLiteral(50, span(97, 6, input)), 883 | Token::EndOfLine(span(99, 6, input)), 884 | Token::RightCurlyBracket(span(100, 7, input)), 885 | Token::Else(span(102, 7, input)), 886 | Token::If(span(107, 7, input)), 887 | Token::LeftParenthesis(span(110, 7, input)), 888 | Token::Identifer("a", span(111, 7, input)), 889 | Token::LessThan(span(113, 7, input)), 890 | Token::IntegerLiteral(30, span(115, 7, input)), 891 | Token::RightParenthesis(span(117, 7, input)), 892 | Token::LeftCurlyBracket(span(119, 7, input)), 893 | Token::EndOfLine(span(120, 7, input)), 894 | Token::Identifer("RETURN", span(121, 8, input)), 895 | Token::BooleanLiteral(true, span(128, 8, input)), 896 | Token::EndOfLine(span(132, 8, input)), 897 | Token::RightCurlyBracket(span(133, 9, input)), 898 | Token::EndOfLine(span(134, 9, input)), 899 | Token::Identifer("RETURN", span(135, 10, input)), 900 | Token::BooleanLiteral(false, span(142, 10, input)), 901 | ]; 902 | 903 | let (_, result) = Token::lex(input).unwrap(); 904 | 905 | assert_eq!(expected, result); 906 | } 907 | 908 | #[test] 909 | fn id_with_numbers() { 910 | let input = b"hello2 hel301oo120"; 911 | 912 | let expected = vec![ 913 | Token::Identifer("hello2", span(0, 1, input)), 914 | Token::Identifer("hel301oo120", span(7, 1, input)), 915 | ]; 916 | 917 | let (_, result) = Token::lex(input).unwrap(); 918 | 919 | assert_eq!(expected, result); 920 | } 921 | 922 | #[test] 923 | fn array_tokens() { 924 | let input = b"[1, 2]"; 925 | 926 | let expected = vec![ 927 | Token::LeftSquareBracket(span(0, 1, input)), 928 | Token::IntegerLiteral(1, span(1, 1, input)), 929 | Token::Comma(span(2, 1, input)), 930 | Token::IntegerLiteral(2, span(4, 1, input)), 931 | Token::RightSquareBracket(span(5, 1, input)), 932 | ]; 933 | 934 | let (_, result) = Token::lex(input).unwrap(); 935 | 936 | assert_eq!(expected, result); 937 | } 938 | 939 | #[test] 940 | fn hash_tokens() { 941 | let input = b"{\"hello\": \"world\"}"; 942 | 943 | let expected = vec![ 944 | Token::LeftCurlyBracket(span(0, 1, input)), 945 | Token::StringLiteral("hello", span(1, 1, input)), 946 | Token::Colon(span(8, 1, input)), 947 | Token::StringLiteral("world", span(10, 1, input)), 948 | Token::RightCurlyBracket(span(17, 1, input)), 949 | ]; 950 | 951 | let (_, result) = Token::lex(input).unwrap(); 952 | 953 | assert_eq!(expected, result); 954 | } 955 | } 956 | --------------------------------------------------------------------------------