1 ; ==================================================================
2 ; basic.asm - BASIC Interpreter for BareMetal OS
5 ; Based on the "OS-independent" version of MIBASIC by Neville Watkin
6 ; which is based on MIKEOS BASIC by Mike Saunders
7 ; ==================================================================
10 [ORG 0x0000000000200000]
14 ; ------------------------------------------------------------------
25 ; ------------------------------------------------------------------
26 ; The BASIC intepreter execution starts here...
29 mov dword [orig_stack
], esp ; Save stack pointer -- we might jump to the
30 ; error printing code and quit in the middle
31 ; some nested loops, and we want to preserve
33 mov eax, basic_prog
;embedded test program for a quick DOS test
34 mov ebx, 8192 ;default size for test program (not critical)
35 mov dword [load_point
], eax ; EAX was passed as starting location of code
36 mov dword [prog
], eax ; prog = pointer to current execution point in code
37 add ebx, eax ; We were passed the .BAS byte size in BX
40 mov dword [prog_end
], ebx ; Make note of program end point
41 call clear_ram
; Clear variables etc. from previous run
44 call get_token
; Get a token from the start of the line
45 cmp eax, STRING
; Is the type a string of characters?
46 je .keyword
; If so, let's see if it's a keyword to process
47 cmp eax, VARIABLE
; If it's a variable at the start of the line,
48 je near assign
; this is an assign (eg "X = Y + 5")
49 cmp eax, STRING_VAR
; Same for a string variable (eg $1)
51 cmp eax, LABEL ; Don't need to do anything here - skip
53 mov esi, err_syntax
; Otherwise show an error and quit
56 mov esi, token
; Start trying to match commands
94 ; call b_string_compare
100 call b_string_compare
103 call b_string_compare
106 call b_string_compare
109 call b_string_compare
112 call b_string_compare
115 call b_string_compare
118 call b_string_compare
121 call b_string_compare
124 call b_string_compare
127 ; call b_string_compare
130 call b_string_compare
133 call b_string_compare
136 call b_string_compare
138 mov esi, err_cmd_unknown
; Command not found?
141 ; ------------------------------------------------------------------
148 mov edi, for_variables
151 mov edi, for_code_points
154 mov byte [gosub_depth
], 0
155 mov edi, gosub_points
162 ; ------------------------------------------------------------------
165 cmp eax, VARIABLE
; Are we starting with a number var?
167 mov edi, string_vars
; Otherwise it's a string var
169 mul ebx ; (EBX = string number, passed back from get_token)
181 mov esi, string_vars
; Otherwise it's a string var
183 mul ebx ; (EBX = string number, passed back from get_token)
205 je .second_is_variable
207 je near .second_is_string
210 mov byte al, [token
] ; Address of string var?
213 call get_token
; Let's see if there's a string var
235 mov ebx, eax ; Number to insert in variable table
239 ; The assignment could be simply "X = 5" etc. Or it could be
240 ; "X = Y + 5" -- ie more complicated. So here we check to see if
241 ; there's a delimiter...
243 mov dword eax, [prog
] ; Save code location in case there's no delimiter
244 mov dword [.tmp_loc
], eax
245 call get_token
; Any more to deal with in this assignment?
257 mov dword eax, [.tmp_loc
] ; Not a delimiter, so step back before the token
258 mov dword [prog
], eax ; that we just grabbed
259 jmp mainloop
; And go back to the code interpreter!
261 mov byte [.delim
], al
270 call get_var
; This also points ESI at right place in variable table
271 cmp byte [.delim
], '+'
276 cmp byte [.delim
], '-'
281 cmp byte [.delim
], '*'
286 cmp byte [.delim
], '/'
294 mov eax, edx ; Get remainder
308 cmp byte [.delim
], '+'
313 cmp byte [.delim
], '-'
318 cmp byte [.delim
], '*'
323 cmp byte [.delim
], '/'
331 mov eax, edx ; Get remainder
339 mov esi, progstart_keyword
340 call b_string_compare
342 mov esi, ramstart_keyword
343 call b_string_compare
349 mov dword ebx, [load_point
]
355 mov dword ebx, [prog_end
]
367 ; ==================================================================
368 ; SPECIFIC COMMAND CODE STARTS HERE
369 ; ------------------------------------------------------------------
378 mov eax, token
; First string for alert box
379 mov ebx, 0 ; Others are blank
381 mov edx, 0 ; One-choice box
384 ; ------------------------------------------------------------------
405 ; ------------------------------------------------------------------
410 ; ------------------------------------------------------------------
416 call b_string_compare
420 call b_string_compare
432 ; ------------------------------------------------------------------
443 push rax
; Store variable we're going to use
446 ; int 10h ; Get char at current cursor location
447 mov ebx, 0 ; We only want the lower byte (the char, not attribute)
449 pop rax
; Get the variable back
450 call set_var
; And store the value
452 ; ------------------------------------------------------------------
455 mov dword esp, [orig_stack
]
457 ; ------------------------------------------------------------------
460 call get_token
; Get the variable we're using in this loop
465 mov byte [.tmp_var
], al ; Store it in a temporary location for now
467 mov eax, 0 ; Check it's followed up with '='
471 call get_token
; Next we want a number
474 mov esi, token
; Convert it
476 ; At this stage, we've read something like "FOR X = 1"
477 ; so let's store that 1 in the variable table
480 mov byte al, [.tmp_var
]
482 call get_token
; Next we're looking for "TO"
486 call b_string_uppercase
489 call b_string_compare
491 ; So now we're at "FOR X = 1 TO"
495 mov esi, token
; Get target number
499 mov byte al, [.tmp_var
]
500 sub al, 65 ; Store target number in table
501 mov edi, for_variables
506 ; So we've got the variable, assigned it the starting number, and put into
507 ; our table the limit it should reach. But we also need to store the point in
508 ; code after the FOR line we should return to if NEXT X doesn't complete the loop...
510 mov byte al, [.tmp_var
]
511 sub al, 65 ; Store code position to return to in table
512 mov edi, for_code_points
515 mov dword eax, [prog
]
522 .to_string
db 'TO', 0
523 ; ------------------------------------------------------------------
535 call b_input_key_check
541 ; ------------------------------------------------------------------
544 call get_token
; Get the number (label)
547 mov esi, err_goto_notlabel
550 mov esi, token
; Back up this label
555 mov edi, .tmp_token
; Add ':' char to end for searching
561 inc byte [gosub_depth
]
563 mov byte al, [gosub_depth
] ; Get current GOSUB nest level
566 mov esi, err_nest_limit
569 mov edi, gosub_points
; Move into our table of pointers
570 add edi, eax ; Table is words (not bytes)
572 mov dword eax, [prog
]
573 stosw ; Store current location before jump
574 mov dword eax, [load_point
]
575 mov dword [prog
], eax ; Return to start of program to find label
582 call b_string_compare
584 .
line_loop: ; Go to end of line
585 mov dword esi, [prog
]
590 mov dword eax, [prog
]
591 mov dword ebx, [prog_end
]
596 mov esi, err_label_notfound
598 .
tmp_token: times
30 db 0
599 ; ------------------------------------------------------------------
602 call get_token
; Get the next token
605 mov esi, err_goto_notlabel
608 mov esi, token
; Back up this label
613 mov edi, .tmp_token
; Add ':' char to end for searching
619 mov dword eax, [load_point
]
620 mov dword [prog
], eax ; Return to start of program to find label
627 call b_string_compare
629 .
line_loop: ; Go to end of line
630 mov dword esi, [prog
]
635 mov dword eax, [prog
]
636 mov dword ebx, [prog_end
]
641 mov esi, err_label_notfound
643 .
tmp_token: times
30 db 0
644 ; ------------------------------------------------------------------
648 cmp eax, VARIABLE
; If can only be followed by a variable
658 mov edx, eax ; Store value of first part of comparison
659 call get_token
; Get the delimiter
667 mov esi, err_syntax
; If not one of the above, error out
670 call get_token
; Is this 'X = Y' (equals another variable?)
676 mov esi, token
; Otherwise it's, eg 'X = 1' (a number)
678 cmp eax, edx ; On to the THEN bit if 'X = num' matches
680 jmp .finish_line
; Otherwise skip the rest of the line
691 cmp eax, edx ; Do the variables match?
692 je near .on_to_then
; On to the THEN bit if so
693 jmp .finish_line
; Otherwise skip the rest of the line
695 call get_token
; Greater than a variable or number?
699 mov esi, token
; Must be a number here...
704 .
greater_var: ; Variable in this case
708 cmp eax, edx ; Make the comparison!
729 mov byte [.tmp_string_var
], bl
736 je .second_is_string_var
744 call b_string_compare
747 .
second_is_string_var:
754 mov byte bl, [.tmp_string_var
]
758 call b_string_compare
764 mov edi, then_keyword
765 call b_string_compare
769 .
then_present: ; Continue rest of line like any other command!
771 .
finish_line: ; IF wasn't fulfilled, so skip rest of line
772 mov dword esi, [prog
]
782 ; ------------------------------------------------------------------
785 mov al, 0 ; Clear string from previous usage
790 cmp eax, VARIABLE
; We can only INPUT to variables!
797 mov rdi
, .tmpstring
; Get input from the user
803 mov byte [.tmpstring
], '0' ; If enter hit, fill variable with zero
804 mov byte [.tmpstring
+ 1], 0
806 mov esi, .tmpstring
; Convert to integer format
810 mov byte al, [token
] ; Get the variable where we're storing it...
811 call set_var
; ...and store it!
817 mov rcx
, 120 ; Limit to max of 120 chars
828 .
tmpstring: times
128 db 0
829 ; ------------------------------------------------------------------
835 ; cmp eax, STRING_VAR
837 ; mov esi, string_vars
847 ; jc .file_not_exists
848 ; mov edx, eax ; Store for now
855 ; call b_string_to_int
870 ; mov byte al, [token]
878 ; call get_token ; Skip past the loading point -- unnecessary now
881 ; mov esi, err_syntax
883 ; ------------------------------------------------------------------
914 ; ------------------------------------------------------------------
918 cmp eax, VARIABLE
; NEXT must be followed by a variable
923 inc eax ; NEXT increments the variable, of course!
928 mov esi, for_variables
931 lodsw ; Get the target number from the table
932 inc eax ; (Make the loop inclusive of target number)
933 cmp eax, ebx ; Do the variable and target match?
935 mov eax, 0 ; If not, store the updated variable
938 mov eax, 0 ; Find the code point and go back
941 mov esi, for_code_points
945 mov dword [prog
], eax
952 ; ------------------------------------------------------------------
968 ; ------------------------------------------------------------------
976 mov byte [.tmp_var
], al
989 mov byte al, [.tmp_var
]
1000 ; ------------------------------------------------------------------
1009 call b_string_to_int
1012 mov byte [.first_value
], al
1016 mov byte al, [token
]
1018 mov byte [.first_value
], al
1026 call b_string_to_int
1030 mov byte al, [.first_value
]
1035 mov byte al, [token
]
1042 ; ------------------------------------------------------------------
1048 call b_string_compare
1051 call b_string_compare
1059 call b_string_to_int
; Now EAX = port number
1069 call b_string_to_int
1070 ; call b_port_byte_out
1074 mov byte al, [token
]
1076 ; call b_port_byte_out
1083 call b_string_to_int
1088 mov byte cl, [token
]
1089 ; call b_port_byte_in
1098 .out_cmd
db "OUT", 0
1100 ; ------------------------------------------------------------------
1103 call get_token
; Get part after PRINT
1104 cmp eax, QUOTE
; What type is it?
1106 cmp eax, VARIABLE
; Numerical variable (eg X)
1108 cmp eax, STRING_VAR
; String variable (eg $1)
1109 je .print_string_var
1110 cmp eax, STRING
; Special keyword (eg CHR or HEX)
1112 mov esi, err_print_type
; We only print quoted strings and vars!
1116 mov byte al, [token
]
1117 call get_var
; Get its value
1118 call b_int_to_string
; Convert to string
1122 .
print_quote: ; If it's quoted text, print it
1127 mov esi, string_vars
1135 mov edi, chr_keyword
1136 call b_string_compare
1138 mov edi, hex_keyword
1139 call b_string_compare
1148 mov byte al, [token
]
1158 mov byte al, [token
]
1160 call b_debug_dump_ax
1166 ; We want to see if the command ends with ';' -- which means that
1167 ; we shouldn't print a newline after it finishes. So we store the
1168 ; current program location to pop ahead and see if there's the ';'
1169 ; character -- otherwise we put the program location back and resume
1172 mov dword eax, [prog
]
1173 mov dword [.tmp_loc
], eax
1181 jmp mainloop
; And go back to interpreting the code!
1183 call b_print_newline
1184 mov dword eax, [.tmp_loc
]
1185 mov dword [prog
], eax
1188 ; ------------------------------------------------------------------
1194 mov byte al, [token
]
1200 call b_string_to_int
1201 mov dword [.num1
], eax
1206 call b_string_to_int
1207 mov dword [.num2
], eax
1208 mov dword eax, [.num1
]
1209 mov dword ebx, [.num2
]
1222 ; ------------------------------------------------------------------
1225 mov dword esi, [prog
]
1228 cmp al, 10 ; Find end of line after REM
1231 ; ------------------------------------------------------------------
1235 mov byte al, [gosub_depth
]
1241 mov esi, gosub_points
1242 add esi, eax ; Table is words (not bytes)
1245 mov dword [prog
], eax
1246 dec byte [gosub_depth
]
1248 ; ------------------------------------------------------------------
1254 ; cmp eax, STRING_VAR
1256 ; mov esi, string_vars
1264 ; mov edi, .tmp_filename
1265 ; call b_string_copy
1272 ; call b_string_to_int
1274 ; mov dword [.data_loc], eax
1281 ; call b_string_to_int
1283 ; mov dword [.data_size], eax
1284 ; mov dword eax, .tmp_filename
1285 ; mov dword ebx, [.data_loc]
1286 ; mov dword ecx, [.data_size]
1296 ; mov byte al, [token]
1301 ; mov byte al, [token]
1303 ; jmp .set_data_size
1311 ; mov esi, err_syntax
1316 ;.tmp_filename: times 15 db 0
1317 ; ------------------------------------------------------------------
1323 call b_string_compare
1326 call b_string_compare
1329 call b_string_compare
1339 call b_string_to_int
1341 je .on_cmd_slow_mode
1343 je .on_cmd_fast_mode
1347 ; call b_serial_port_enable
1351 ; call b_serial_port_enable
1362 call b_string_to_int
1367 mov byte al, [token
]
1375 mov byte al, [token
]
1388 .send_cmd
db "SEND", 0
1389 .rec_cmd
db "REC", 0
1390 ; ------------------------------------------------------------------
1397 call b_string_to_int
1401 mov byte al, [token
]
1409 call b_string_to_int
1413 mov byte al, [token
]
1419 ; ------------------------------------------------------------------
1429 mov byte al, [token
]
1431 call b_input_key_wait
1458 ; ==================================================================
1459 ; INTERNAL ROUTINES FOR INTERPRETER
1460 ; ------------------------------------------------------------------
1461 ; Get value of variable character specified in AL (eg 'A')
1469 ; ------------------------------------------------------------------
1470 ; Set value of variable character specified in AL (eg 'A')
1471 ; with number specified in EBX
1474 sub al, 65 ; Remove ASCII codes before 'A'
1475 mov edi, variables
; Find position in table (of words)
1481 ; ------------------------------------------------------------------
1482 ; Get token from current position in prog
1484 mov dword esi, [prog
]
1488 cmp al, 13 ;allow for CRLF
1496 cmp al, 39 ; Quote mark (')
1499 je near get_string_var_token
1500 jmp get_string_token
1505 mov dword esi, [prog
]
1511 cmp al, 13 ;allow for CRLF
1517 mov esi, err_char_in_num
1524 mov al, 0 ; Zero-terminate the token
1526 mov eax, NUMBER
; Pass back the token type
1529 inc dword [prog
] ; Move past first quote (')
1530 mov dword esi, [prog
]
1532 mov byte [token
], al
1534 cmp al, 39 ; Needs to finish with another quote
1536 mov esi, err_quote_term
1544 inc dword [prog
] ; Move past first quote (") char
1545 mov dword esi, [prog
]
1557 mov al, 0 ; Zero-terminate the token
1559 inc dword [prog
] ; Move past final quote
1560 mov eax, QUOTE
; Pass back token type
1563 mov esi, err_quote_term
1565 get_string_var_token:
1567 mov ebx, 0 ; If it's a string var, pass number of string in EBX
1576 mov dword esi, [prog
]
1582 cmp al, 13 ;allow for CRLF
1590 mov al, 0 ; Zero-terminate the token
1593 call b_string_uppercase
1595 call b_string_length
; How long was the token?
1596 cmp eax, 1 ; If 1 char, it's a variable or delimiter
1598 mov esi, token
; If the token ends with ':', it's a label
1604 mov eax, STRING
; Otherwise it's a general string of characters
1610 mov byte al, [token
]
1616 mov eax, VARIABLE
; Otherwise probably a variable
1618 ; ------------------------------------------------------------------
1619 ; Set carry flag if AL contains ASCII number
1630 ; ------------------------------------------------------------------
1631 ; Set carry flag if AL contains ASCII letter
1642 ; ------------------------------------------------------------------
1643 ; Print error message and quit out
1645 call b_print_newline
1646 call b_print_string
; Print error message
1647 call b_print_newline
1648 mov dword esp, [orig_stack
] ; Restore the stack to as it was when BASIC started
1650 ; ------------------------------------------------------------------
1654 ; Error messages text...
1655 err_char_in_num
db "Error: unexpected character in number", 0
1656 err_quote_term
db "Error: quoted string or character not terminated correctly", 0
1657 err_print_type
db "Error: PRINT command not followed by quoted text or variable", 0
1658 err_cmd_unknown
db "Error: unknown command", 0
1659 err_goto_notlabel
db "Error: GOTO or GOSUB not followed by label", 0
1660 err_label_notfound
db "Error: GOTO or GOSUB label not found", 0
1661 err_return
db "Error: RETURN without GOSUB", 0
1662 err_nest_limit
db "Error: FOR or GOSUB nest limit exceeded", 0
1663 err_next
db "Error: NEXT without FOR", 0
1664 err_syntax
db "Error: syntax error", 0
1665 ; ==================================================================
1667 orig_stack
dd 0 ; Original stack location when BASIC started
1668 prog
dd 0 ; Pointer to current location in BASIC code
1669 prog_end
dd 0 ; Pointer to final byte of BASIC code
1671 token_type
db 0 ; Type of last token read (eg NUMBER, VARIABLE)
1672 token: times
255 db 0 ; Storage space for the token
1673 variables: times
26 dd 0 ; Storage space for variables A to Z
1674 for_variables: times
26 dd 0 ; Storage for FOR loops
1675 for_code_points: times
26 dd 0 ; Storage for code positions where FOR loops start
1676 alert_cmd
db "ALERT", 0
1677 call_cmd
db "CALL", 0
1679 cursor_cmd
db "CURSOR", 0
1680 curschar_cmd
db "CURSCHAR", 0
1683 gosub_cmd
db "GOSUB", 0
1684 goto_cmd
db "GOTO", 0
1685 getkey_cmd
db "GETKEY", 0
1687 input_cmd
db "INPUT", 0
1688 load_cmd
db "LOAD", 0
1689 move_cmd
db "MOVE", 0
1690 next_cmd
db "NEXT", 0
1691 pause_cmd
db "PAUSE", 0
1692 peek_cmd
db "PEEK", 0
1693 poke_cmd
db "POKE", 0
1694 port_cmd
db "PORT", 0
1695 print_cmd
db "PRINT", 0
1697 rand_cmd
db "RAND", 0
1698 return_cmd
db "RETURN", 0
1699 save_cmd
db "SAVE", 0
1700 serial_cmd
db "SERIAL", 0
1701 sound_cmd
db "SOUND", 0
1702 waitkey_cmd
db "WAITKEY", 0
1703 then_keyword
db "THEN", 0
1704 chr_keyword
db "CHR", 0
1705 hex_keyword
db "HEX", 0
1706 progstart_keyword
db "PROGSTART", 0
1707 ramstart_keyword
db "RAMSTART", 0
1709 gosub_points: times
10 dd 0 ; Points in code to RETURN to
1710 string_vars: times
1024 db 0 ; 8 * 128 byte strings
1711 ; ------------------------------------------------------------------
1714 DB 'PRINT "Please type your name: ";',13,10
1717 DB 'PRINT "Hello "',13,10
1719 DB 'PRINT ", welcome to MikeOS Basic!"',13,10
1721 DB 'PRINT "It supports FOR...NEXT loops and simple integer maths..."',13,10
1723 DB 'FOR I = 1 TO 15',13,10
1724 DB 'J = I * I',13,10
1725 DB 'K = J * I',13,10
1726 DB 'L = K * I',13,10
1728 DB 'PRINT " "',13,10
1730 DB 'PRINT " "',13,10
1732 DB 'PRINT " "',13,10
1736 DB 'PRINT " ...and IF...THEN and GOSUB and lots of other stuff. Bye!"',13,10