1 ; ==================================================================
2 ; MikeOS -- The Mike Operating System kernel
3 ; Copyright (C) 2006 - 2011 MikeOS Developers -- see doc/LICENSE.TXT
5 ; BASIC CODE INTERPRETER
6 ; ==================================================================
10 [ORG 0x0000000000200000]
14 ; ------------------------------------------------------------------
27 ; ------------------------------------------------------------------
28 ; The BASIC interpreter execution starts here...
31 mov [orig_stack
], rsp
; Save stack pointer -- we might jump to the
32 ; error printing code and quit in the middle
33 ; some nested loops, and we want to preserve
35 mov rax
, basic_prog
;embedded test program for a quick DOS test
36 mov rbx
, 8192 ;default size for test program (not critical)
37 mov [load_point
], rax
; rax was passed as starting location of code
39 mov [prog
], rax
; prog = pointer to current execution point in code
41 add rbx
, rax
; We were passed the .BAS byte size in rbx
43 mov [prog_end
], rbx
; Make note of program end point
46 call clear_ram
; Clear variables etc. from previous run
52 call get_token
; Get a token from the start of the line
54 cmp rax
, STRING
; Is the type a string of characters?
55 je .keyword
; If so, let's see if it's a keyword to process
57 cmp rax
, VARIABLE
; If it's a variable at the start of the line,
58 je near assign
; this is an assign (eg "X = Y + 5")
60 cmp rax
, STRING_VAR
; Same for a string variable (eg $1)
63 cmp rax
, LABEL ; Don't need to do anything here - skip
66 mov rsi
, err_syntax
; Otherwise show an error and quit
71 mov rsi
, token
; Start trying to match commands
102 call b_string_compare
106 call b_string_compare
110 call b_string_compare
114 call b_string_compare
118 call b_string_compare
122 call b_string_compare
126 call b_string_compare
130 call b_string_compare
134 call b_string_compare
138 call b_string_compare
142 call b_string_compare
146 call b_string_compare
150 call b_string_compare
154 call b_string_compare
158 call b_string_compare
162 call b_string_compare
166 call b_string_compare
170 call b_string_compare
174 call b_string_compare
178 call b_string_compare
181 mov rsi
, err_cmd_unknown
; Command not found?
185 ; ------------------------------------------------------------------
195 mov rdi
, for_variables
199 mov rdi
, for_code_points
203 mov byte [gosub_depth
], 0
205 mov rdi
, gosub_points
216 ; ------------------------------------------------------------------
220 cmp rax
, VARIABLE
; Are we starting with a number var?
223 mov rdi
, string_vars
; Otherwise it's a string var
225 mul rbx
; (rbx = string number, passed back from get_token)
242 mov rsi
, string_vars
; Otherwise it's a string var
244 mul rbx
; (rbx = string number, passed back from get_token)
276 je .second_is_variable
279 je near .second_is_string
284 mov byte al, [token
] ; Address of string var?
288 call get_token
; Let's see if there's a string var
321 mov rbx
, rax
; Number to insert in variable table
329 ; The assignment could be simply "X = 5" etc. Or it could be
330 ; "X = Y + 5" -- ie more complicated. So here we check to see if
331 ; there's a delimiter...
334 mov rax
, [prog
] ; Save code location in case there's no delimiter
337 call get_token
; Any more to deal with in this assignment?
350 mov rax
, [.tmp_loc
] ; Not a delimiter, so step back before the token
351 mov [prog
], rax
; that we just grabbed
353 jmp mainloop
; And go back to the code interpreter!
357 mov byte [.delim
], al
370 call get_var
; This also points rsi at right place in variable table
372 cmp byte [.delim
], '+'
379 cmp byte [.delim
], '-'
386 cmp byte [.delim
], '*'
393 cmp byte [.delim
], '/'
403 mov rax
, rdx
; Get remainder
426 cmp byte [.delim
], '+'
433 cmp byte [.delim
], '-'
440 cmp byte [.delim
], '*'
447 cmp byte [.delim
], '/'
457 mov rax
, rdx
; Get remainder
469 mov rsi
, progstart_keyword
470 call b_string_compare
473 mov rsi
, ramstart_keyword
474 call b_string_compare
483 mov rbx
, [load_point
]
513 ; ==================================================================
514 ; SPECIFIC COMMAND CODE STARTS HERE
516 ; ------------------------------------------------------------------
529 mov rax
, token
; First string for alert box
530 xor rbx
, rbx
; Others are blank
532 mov dx, 0 ; One-choice box
536 ; ------------------------------------------------------------------
566 ; ------------------------------------------------------------------
574 ; ------------------------------------------------------------------
582 call b_string_compare
587 call b_string_compare
606 ; ------------------------------------------------------------------
622 push rax
; Store variable we're going to use
626 ; int 10h ; Get char at current cursor location
628 xor rbx
, rbx
; We only want the lower byte (the char, not attribute)
631 pop rax
; Get the variable back
633 call set_var
; And store the value
638 ; ------------------------------------------------------------------
642 mov rsp
, [orig_stack
]
646 ; ------------------------------------------------------------------
650 call get_token
; Get the variable we're using in this loop
657 mov byte [.tmp_var
], al ; Store it in a temporary location for now
661 xor rax
, rax
; Check it's followed up with '='
666 call get_token
; Next we want a number
671 mov rsi
, token
; Convert it
675 ; At this stage, we've read something like "FOR X = 1"
676 ; so let's store that 1 in the variable table
680 mov byte al, [.tmp_var
]
684 call get_token
; Next we're looking for "TO"
690 call b_string_uppercase
694 call b_string_compare
697 ; So now we're at "FOR X = 1 TO"
704 mov rsi
, token
; Get target number
710 mov byte al, [.tmp_var
]
712 sub al, 65 ; Store target number in table
713 mov rdi
, for_variables
720 ; So we've got the variable, assigned it the starting number, and put into
721 ; our table the limit it should reach. But we also need to store the point in
722 ; code after the FOR line we should return to if NEXT X doesn't complete the loop...
726 mov byte al, [.tmp_var
]
728 sub al, 65 ; Store code position to return to in table
729 mov rdi
, for_code_points
746 .to_string
db 'TO', 0
749 ; ------------------------------------------------------------------
766 call b_input_key_check
778 ; ------------------------------------------------------------------
782 call get_token
; Get the number (label)
787 mov rsi
, err_goto_notlabel
791 mov rsi
, token
; Back up this label
798 mov rdi
, .tmp_token
; Add ':' char to end for searching
805 inc byte [gosub_depth
]
808 mov byte al, [gosub_depth
] ; Get current GOSUB nest level
813 mov rsi
, err_nest_limit
817 mov rdi
, gosub_points
; Move into our table of pointers
818 add rdi
, rax
; Table is words (not bytes)
821 stosw ; Store current location before jump
824 mov rax
, [load_point
]
825 mov [prog
], rax
; Return to start of program to find label
835 call b_string_compare
838 .
line_loop: ; Go to end of line
853 mov rsi
, err_label_notfound
856 .tmp_token times
30 db 0
859 ; ------------------------------------------------------------------
863 call get_token
; Get the next token
868 mov rsi
, err_goto_notlabel
872 mov rsi
, token
; Back up this label
879 mov rdi
, .tmp_token
; Add ':' char to end for searching
886 mov rax
, [load_point
]
887 mov [prog
], rax
; Return to start of program to find label
897 call b_string_compare
900 .
line_loop: ; Go to end of line
916 mov rsi
, err_label_notfound
919 .tmp_token times
30 db 0
922 ; ------------------------------------------------------------------
928 cmp rax
, VARIABLE
; If can only be followed by a variable
942 mov rdx
, rax
; Store value of first part of comparison
944 call get_token
; Get the delimiter
953 mov rsi
, err_syntax
; If not one of the above, error out
957 call get_token
; Is this 'X = Y' (equals another variable?)
966 mov rsi
, token
; Otherwise it's, eg 'X = 1' (a number)
969 cmp rax
, rdx
; On to the THEN bit if 'X = num' matches
972 jmp .finish_line
; Otherwise skip the rest of the line
989 cmp rax
, rdx
; Do the variables match?
990 je near .on_to_then
; On to the THEN bit if so
992 jmp .finish_line
; Otherwise skip the rest of the line
995 call get_token
; Greater than a variable or number?
1000 mov rsi
, token
; Must be a number here...
1001 call b_string_to_int
1008 .
greater_var: ; Variable in this case
1010 mov byte al, [token
]
1014 cmp rax
, rdx
; Make the comparison!
1021 mov byte al, [token
]
1026 call b_string_to_int
1035 mov byte al, [token
]
1045 mov byte [.tmp_string_var
], bl
1049 mov byte al, [token
]
1055 je .second_is_string_var
1060 mov rsi
, string_vars
1065 call b_string_compare
1070 .
second_is_string_var:
1071 mov rsi
, string_vars
1076 mov rdi
, string_vars
1078 mov byte bl, [.tmp_string_var
]
1083 call b_string_compare
1092 mov rdi
, then_keyword
1093 call b_string_compare
1100 .
then_present: ; Continue rest of line like any other command!
1103 .
finish_line: ; IF wasn't fulfilled, so skip rest of line
1116 .tmp_string_var
db 0
1119 ; ------------------------------------------------------------------
1123 mov al, 0 ; Clear string from previous usage
1130 cmp rax
, VARIABLE
; We can only INPUT to variables!
1140 mov rdi
, .tmpstring
; Get input from the user
1145 call b_string_length
1149 mov byte [.tmpstring
], '0' ; If enter hit, fill variable with zero
1150 mov byte [.tmpstring
+ 1], 0
1153 mov rsi
, .tmpstring
; Convert to integer format
1154 call b_string_to_int
1158 mov byte al, [token
] ; Get the variable where we're storing it...
1159 call set_var
; ...and store it!
1161 call b_print_newline
1173 mov rdi
, string_vars
1183 call b_print_newline
1187 .tmpstring times
128 db 0
1190 ; ------------------------------------------------------------------
1201 mov rsi
, string_vars
1212 ; call b_file_exists
1215 mov rdx
, rax
; Store for now
1226 call b_string_to_int
1248 mov byte al, [token
]
1258 call get_token
; Skip past the loading point -- unnecessary now
1267 ; ------------------------------------------------------------------
1277 call b_string_to_int
1283 mov byte al, [token
]
1294 call b_string_to_int
1300 mov byte al, [token
]
1310 ; ------------------------------------------------------------------
1316 cmp rax
, VARIABLE
; NEXT must be followed by a variable
1320 mov byte al, [token
]
1323 inc rax
; NEXT increments the variable, of course!
1328 mov byte al, [token
]
1331 mov rsi
, for_variables
1334 lodsw ; Get the target number from the table
1336 inc rax
; (Make the loop inclusive of target number)
1337 cmp rax
, rbx
; Do the variable and target match?
1340 xor rax
, rax
; If not, store the updated variable
1341 mov byte al, [token
]
1344 xor rax
, rax
; Find the code point and go back
1345 mov byte al, [token
]
1347 mov rsi
, for_code_points
1365 ; ------------------------------------------------------------------
1375 call b_string_to_int
1380 mov byte al, [token
]
1388 ; ------------------------------------------------------------------
1398 mov byte al, [token
]
1399 mov byte [.tmp_var
], al
1410 call b_string_to_int
1417 mov byte al, [.tmp_var
]
1423 mov byte al, [token
]
1435 ; ------------------------------------------------------------------
1448 call b_string_to_int
1453 mov byte [.first_value
], al
1459 mov byte al, [token
]
1462 mov byte [.first_value
], al
1474 call b_string_to_int
1479 mov byte al, [.first_value
]
1486 mov byte al, [token
]
1497 ; ------------------------------------------------------------------
1505 call b_string_compare
1509 call b_string_compare
1520 call b_string_to_int
; Now rax = port number
1534 call b_string_to_int
1535 ; call b_port_byte_out
1540 mov byte al, [token
]
1543 ; call b_port_byte_out
1552 call b_string_to_int
1559 mov byte cl, [token
]
1561 ; call b_port_byte_in
1575 .out_cmd
db "OUT", 0
1579 ; ------------------------------------------------------------------
1583 call get_token
; Get part after PRINT
1585 cmp rax
, QUOTE
; What type is it?
1588 cmp rax
, VARIABLE
; Numerical variable (eg X)
1591 cmp rax
, STRING_VAR
; String variable (eg $1)
1592 je .print_string_var
1594 cmp rax
, STRING
; Special keyword (eg CHR or HEX)
1597 mov rsi
, err_print_type
; We only print quoted strings and vars!
1602 mov byte al, [token
]
1603 call get_var
; Get its value
1606 call b_int_to_string
; Convert to string
1611 .
print_quote: ; If it's quoted text, print it
1618 mov rsi
, string_vars
1628 mov rdi
, chr_keyword
1629 call b_string_compare
1632 mov rdi
, hex_keyword
1633 call b_string_compare
1646 mov byte al, [token
]
1661 mov byte al, [token
]
1664 call b_debug_dump_al
;print_2hex
1673 ; We want to see if the command ends with ';' -- which means that
1674 ; we shouldn't print a newline after it finishes. So we store the
1675 ; current program location to pop ahead and see if there's the ';'
1676 ; character -- otherwise we put the program location back and resume
1690 jmp mainloop
; And go back to interpreting the code!
1693 call b_print_newline
1703 ; ------------------------------------------------------------------
1711 mov byte al, [token
]
1719 call b_string_to_int
1727 call b_string_to_int
1750 ; ------------------------------------------------------------------
1757 cmp al, 10 ; Find end of line after REM
1763 ; ------------------------------------------------------------------
1768 mov byte al, [gosub_depth
]
1776 mov rsi
, gosub_points
1777 add rsi
, rax
; Table is words (not bytes)
1781 dec byte [gosub_depth
]
1786 ; ------------------------------------------------------------------
1797 mov rsi
, string_vars
1807 mov rdi
, .tmp_filename
1819 call b_string_to_int
1822 mov [.data_loc
], rax
1833 call b_string_to_int
1836 mov [.data_size
], rax
1838 mov rax
, .tmp_filename
1839 mov rbx
, [.data_loc
]
1840 mov rcx
, [.data_size
]
1854 mov byte al, [token
]
1860 mov byte al, [token
]
1881 .tmp_filename times
15 db 0
1884 ; ------------------------------------------------------------------
1892 call b_string_compare
1896 call b_string_compare
1900 call b_string_compare
1913 call b_string_to_int
1915 je .on_cmd_slow_mode
1917 je .on_cmd_fast_mode
1923 ; call b_serial_port_enable
1928 ; call b_serial_port_enable
1943 call b_string_to_int
1944 ; call b_send_via_serial
1949 mov byte al, [token
]
1951 ; call b_send_via_serial
1959 mov byte al, [token
]
1963 ; call b_get_via_serial
1977 .send_cmd
db "SEND", 0
1978 .rec_cmd
db "REC", 0
1981 ; ------------------------------------------------------------------
1991 call b_string_to_int
1996 mov byte al, [token
]
2008 call b_string_to_int
2013 mov byte al, [token
]
2023 ; ------------------------------------------------------------------
2036 mov byte al, [token
]
2040 call b_input_key_wait
2081 ; ==================================================================
2082 ; INTERNAL ROUTINES FOR INTERPRETER
2084 ; ------------------------------------------------------------------
2085 ; Get value of variable character specified in AL (eg 'A')
2096 ; ------------------------------------------------------------------
2097 ; Set value of variable character specified in AL (eg 'A')
2098 ; with number specified in rbx
2102 sub al, 65 ; Remove ASCII codes before 'A'
2104 mov rdi
, variables
; Find position in table (of words)
2112 ; ------------------------------------------------------------------
2113 ; Get token from current position in prog
2134 cmp al, 39 ; Quote mark (')
2138 je near get_string_var_token
2140 jmp get_string_token
2161 mov rsi
, err_char_in_num
2170 mov al, 0 ; Zero-terminate the token
2173 mov rax
, NUMBER
; Pass back the token type
2177 inc qword [prog
] ; Move past first quote (')
2182 mov byte [token
], al
2185 cmp al, 39 ; Needs to finish with another quote
2188 mov rsi
, err_quote_term
2199 inc qword [prog
] ; Move past first quote (") char
2200 mov qword rsi
, [prog
]
2213 mov al, 0 ; Zero-terminate the token
2215 inc qword [prog
] ; Move past final quote
2217 mov rax
, QUOTE
; Pass back token type
2221 mov rsi
, err_quote_term
2224 get_string_var_token:
2226 xor rbx
, rbx
; If it's a string var, pass number of string in rbx
2237 mov qword rsi
, [prog
]
2251 mov al, 0 ; Zero-terminate the token
2255 call b_string_uppercase
2258 call b_string_length
; How long was the token?
2259 cmp rcx
, 1 ; If 1 char, it's a variable or delimiter
2262 mov rsi
, token
; If the token ends with ':', it's a label
2269 mov rax
, STRING
; Otherwise it's a general string of characters
2277 mov byte al, [token
]
2285 mov rax
, VARIABLE
; Otherwise probably a variable
2289 ; ------------------------------------------------------------------
2290 ; Set carry flag if AL contains ASCII number
2304 ; ------------------------------------------------------------------
2305 ; Set carry flag if AL contains ASCII letter
2320 ; ------------------------------------------------------------------
2321 ; Print error message and quit out
2324 call b_print_newline
2325 call b_print_string
; Print error message
2326 call b_print_newline
2328 mov rsp
, [orig_stack
] ; Restore the stack to as it was when BASIC started
2333 ; Error messages text...
2335 err_char_in_num
db "Error: unexpected character in number", 0
2336 err_quote_term
db "Error: quoted string or character not terminated correctly", 0
2337 err_print_type
db "Error: PRINT command not followed by quoted text or variable", 0
2338 err_cmd_unknown
db "Error: unknown command", 0
2339 err_goto_notlabel
db "Error: GOTO or GOSUB not followed by label", 0
2340 err_label_notfound
db "Error: GOTO or GOSUB label not found", 0
2341 err_return
db "Error: RETURN without GOSUB", 0
2342 err_nest_limit
db "Error: FOR or GOSUB nest limit exceeded", 0
2343 err_next
db "Error: NEXT without FOR", 0
2344 err_syntax
db "Error: syntax error", 0
2348 ; ==================================================================
2351 orig_stack
dq 0 ; Original stack location when BASIC started
2353 prog
dq 0 ; Pointer to current location in BASIC code
2354 prog_end
dq 0 ; Pointer to final byte of BASIC code
2358 token_type
db 0 ; Type of last token read (eg NUMBER, VARIABLE)
2359 token times
255 db 0 ; Storage space for the token
2360 tstring times
255 db 0
2363 variables times
26 dq 0 ; Storage space for variables A to Z
2365 for_variables times
26 dq 0 ; Storage for FOR loops
2367 for_code_points times
26 dq 0 ; Storage for code positions where FOR loops start
2369 alert_cmd
db "ALERT", 0
2370 call_cmd
db "CALL", 0
2372 cursor_cmd
db "CURSOR", 0
2373 curschar_cmd
db "CURSCHAR", 0
2376 gosub_cmd
db "GOSUB", 0
2377 goto_cmd
db "GOTO", 0
2378 getkey_cmd
db "GETKEY", 0
2380 input_cmd
db "INPUT", 0
2381 load_cmd
db "LOAD", 0
2382 move_cmd
db "MOVE", 0
2383 next_cmd
db "NEXT", 0
2384 pause_cmd
db "PAUSE", 0
2385 peek_cmd
db "PEEK", 0
2386 poke_cmd
db "POKE", 0
2387 port_cmd
db "PORT", 0
2388 print_cmd
db "PRINT", 0
2390 rand_cmd
db "RAND", 0
2391 return_cmd
db "RETURN", 0
2392 save_cmd
db "SAVE", 0
2393 serial_cmd
db "SERIAL", 0
2394 sound_cmd
db "SOUND", 0
2395 waitkey_cmd
db "WAITKEY", 0
2397 then_keyword
db "THEN", 0
2398 chr_keyword
db "CHR", 0
2399 hex_keyword
db "HEX", 0
2401 progstart_keyword
db "PROGSTART", 0
2402 ramstart_keyword
db "RAMSTART", 0
2405 gosub_points times
10 dq 0 ; Points in code to RETURN to
2407 string_vars times
1024 db 0 ; 8 * 128 byte strings
2410 ; ------------------------------------------------------------------
2413 DB 'PRINT "Please type your name: ";',13,10
2416 DB 'PRINT "Hello ";',13,10
2417 DB 'PRINT $N;',13,10
2418 DB 'PRINT ", welcome to MikeOS Basic (in 64-bit mode)!"',13,10
2420 DB 'PRINT "It supports FOR...NEXT loops and simple integer maths..."',13,10
2422 DB 'FOR I = 1 TO 15',13,10
2423 DB 'J = I * I',13,10
2424 DB 'K = J * I',13,10
2425 DB 'L = K * I',13,10
2426 DB 'PRINT I ;',13,10
2427 DB 'PRINT " ";',13,10
2428 DB 'PRINT J ;',13,10
2429 DB 'PRINT " ";',13,10
2430 DB 'PRINT K ;',13,10
2431 DB 'PRINT " ";',13,10
2435 DB 'PRINT " ...and IF...THEN and GOSUB and lots of other stuff. Bye!"',13,10