; ================================================================== ; MikeOS -- The Mike Operating System kernel ; Copyright (C) 2006 - 2022 MikeOS Developers -- see doc/LICENSE.TXT ; ; BASIC CODE INTERPRETER (4.5) ; ================================================================== ; ------------------------------------------------------------------ ; Token types %DEFINE VARIABLE 1 %DEFINE STRING_VAR 2 %DEFINE NUMBER 3 %DEFINE STRING 4 %DEFINE QUOTE 5 %DEFINE CHAR 6 %DEFINE UNKNOWN 7 %DEFINE LABEL 8 ; ------------------------------------------------------------------ ; The BASIC interpreter execution starts here -- a parameter string ; is passed in SI and copied into the first string, unless SI = 0 basic_run_basic: mov word [orig_stack], sp ; Save stack pointer -- we might jump to the ; error printing code and quit in the middle ; some nested loops, and we want to preserve ; the stack mov word [load_point], ax ; AX was passed as starting location of code mov word [prog], ax ; prog = pointer to current execution point in code add bx, ax ; We were passed the .BAS byte size in BX dec bx dec bx mov word [prog_end], bx ; Make note of program end point call clear_ram ; Clear variables etc. from previous run ; of a BASIC program cmp si, 0 ; Passed a parameter string? je mainloop mov di, string_vars ; If so, copy it into $1 call string_copy mainloop: call get_token ; Get a token from the start of the line ;mov si, basic_text ;call os_print_string cmp ax, STRING ; Is the type a string of characters? je .keyword ; If so, let's see if it's a keyword to process cmp ax, VARIABLE ; If it's a variable at the start of the line, je near assign ; this is an assign (eg "X = Y + 5") cmp ax, STRING_VAR ; Same for a string variable (eg $1) je near assign cmp ax, LABEL ; Don't need to do anything here - skip je mainloop mov si, err_syntax ; Otherwise show an error and quit jmp error .keyword: mov si, token ; Start trying to match commands mov di, alert_cmd call string_direct_compare jc near do_alert mov di, askfile_cmd call string_direct_compare jc near do_askfile mov di, break_cmd call string_direct_compare jc near do_break mov di, case_cmd call string_direct_compare jc near do_case mov di, call_cmd call string_direct_compare jc near do_call mov di, cls_cmd call string_direct_compare jc near do_cls mov di, cursor_cmd call string_direct_compare jc near do_cursor mov di, curschar_cmd call string_direct_compare jc near do_curschar mov di, curscol_cmd call string_direct_compare jc near do_curscol mov di, curspos_cmd call string_direct_compare jc near do_curspos mov di, delete_cmd call string_direct_compare jc near do_delete mov di, do_cmd call string_direct_compare jc near do_do mov di, end_cmd call string_direct_compare jc near do_end mov di, else_cmd call string_direct_compare jc near do_else mov di, files_cmd call string_direct_compare jc near do_files mov di, for_cmd call string_direct_compare jc near do_for mov di, getkey_cmd call string_direct_compare jc near do_getkey mov di, gosub_cmd call string_direct_compare jc near do_gosub mov di, goto_cmd call string_direct_compare jc near do_goto mov di, if_cmd call string_direct_compare jc near do_if mov di, include_cmd call string_direct_compare jc near do_include mov di, ink_cmd call string_direct_compare jc near do_ink mov di, input_cmd call string_direct_compare jc near do_input mov di, len_cmd call string_direct_compare jc near do_len mov di, listbox_cmd call string_direct_compare jc near do_listbox mov di, load_cmd call string_direct_compare jc near do_load mov di, loop_cmd call string_direct_compare jc near do_loop mov di, move_cmd call string_direct_compare jc near do_move mov di, next_cmd call string_direct_compare jc near do_next mov di, number_cmd call string_direct_compare jc near do_number mov di, page_cmd call string_direct_compare jc near do_page mov di, pause_cmd call string_direct_compare jc near do_pause mov di, peek_cmd call string_direct_compare jc near do_peek mov di, peekint_cmd call string_direct_compare jc near do_peekint mov di, poke_cmd call string_direct_compare jc near do_poke mov di, pokeint_cmd call string_direct_compare jc near do_pokeint mov di, port_cmd call string_direct_compare jc near do_port mov di, print_cmd call string_direct_compare jc near do_print mov di, rand_cmd call string_direct_compare jc near do_rand mov di, read_cmd call string_direct_compare jc near do_read mov di, rem_cmd call string_direct_compare jc near do_rem mov di, rename_cmd call string_direct_compare jc near do_rename mov di, return_cmd call string_direct_compare jc near do_return mov di, save_cmd call string_direct_compare jc near do_save mov di, serial_cmd call string_direct_compare jc near do_serial mov di, size_cmd call string_direct_compare jc near do_size mov di, sound_cmd call string_direct_compare jc near do_sound mov di, string_cmd call string_direct_compare jc near do_string mov di, waitkey_cmd call string_direct_compare jc near do_waitkey mov si, err_cmd_unknown ; Command not found? jmp error ; ------------------------------------------------------------------ ; CLEAR RAM clear_ram: pusha mov al, 0 mov di, variables mov cx, 52 rep stosb mov di, for_variables mov cx, 52 rep stosb mov di, for_code_points mov cx, 52 rep stosb mov di, do_loop_store mov cx, 10 rep stosb mov byte [gosub_depth], 0 mov byte [loop_in], 0 mov di, gosub_points mov cx, 20 rep stosb mov di, string_vars mov cx, 1024 rep stosb mov byte [ink_colour], 7 ; White ink popa ret ; ------------------------------------------------------------------ ; ASSIGNMENT assign: cmp ax, VARIABLE ; Are we starting with a number var? je .do_num_var mov di, string_vars ; Otherwise it's a string var mov ax, 128 mul bx ; (BX = string number, passed back from get_token) add di, ax push di call get_token mov byte al, [token] cmp al, '=' jne near .error call get_token ; See if second is quote cmp ax, QUOTE je .second_is_quote cmp ax, STRING_VAR jne near .error mov si, string_vars ; Otherwise it's a string var mov ax, 128 mul bx ; (BX = string number, passed back from get_token) add si, ax pop di call string_copy jmp .string_check_for_more .second_is_quote: mov si, token pop di call string_copy .string_check_for_more: push di mov word ax, [prog] ; Save code location in case there's no delimiter mov word [.tmp_loc], ax call get_token ; Any more to deal with in this assignment? mov byte al, [token] cmp al, '+' je .string_theres_more mov word ax, [.tmp_loc] ; Not a delimiter, so step back before the token mov word [prog], ax ; that we just grabbed pop di jmp mainloop ; And go back to the code interpreter! .string_theres_more: call get_token cmp ax, STRING_VAR je .another_string_var cmp ax, QUOTE je .another_quote cmp ax, VARIABLE je .add_number_var jmp .error .another_string_var: pop di mov si, string_vars mov ax, 128 mul bx ; (BX = string number, passed back from get_token) add si, ax mov ax, di mov cx, di mov bx, si call string_join jmp .string_check_for_more .another_quote: pop di mov ax, di mov cx, di mov bx, token call string_join jmp .string_check_for_more .add_number_var: mov ax, 0 mov byte al, [token] call get_var call string_cast_from_int mov bx, ax pop di mov ax, di mov cx, di call string_join jmp .string_check_for_more .do_num_var: mov ax, 0 mov byte al, [token] mov byte [.tmp], al call get_token mov byte al, [token] cmp al, '=' jne near .error call get_token cmp ax, NUMBER je .second_is_num cmp ax, VARIABLE je .second_is_variable cmp ax, STRING je near .second_is_string cmp ax, UNKNOWN jne near .error mov byte al, [token] ; Address of string var? cmp al, '&' jne near .error call get_token ; Let's see if there's a string var cmp ax, STRING_VAR jne near .error mov di, string_vars mov ax, 128 mul bx add di, ax mov bx, di mov byte al, [.tmp] call set_var jmp mainloop .second_is_variable: mov ax, 0 mov byte al, [token] call get_var mov bx, ax mov byte al, [.tmp] call set_var jmp .check_for_more .second_is_num: mov si, token call string_cast_to_int mov bx, ax ; Number to insert in variable table mov ax, 0 mov byte al, [.tmp] call set_var ; The assignment could be simply "X = 5" etc. Or it could be ; "X = Y + 5" -- ie more complicated. So here we check to see if ; there's a delimiter... .check_for_more: mov word ax, [prog] ; Save code location in case there's no delimiter mov word [.tmp_loc], ax call get_token ; Any more to deal with in this assignment? mov byte al, [token] cmp al, '+' je .theres_more cmp al, '-' je .theres_more cmp al, '*' je .theres_more cmp al, '/' je .theres_more cmp al, '%' je .theres_more mov word ax, [.tmp_loc] ; Not a delimiter, so step back before the token mov word [prog], ax ; that we just grabbed jmp mainloop ; And go back to the code interpreter! .theres_more: mov byte [.delim], al call get_token cmp ax, VARIABLE je .handle_variable mov si, token call string_cast_to_int mov bx, ax mov ax, 0 mov byte al, [.tmp] call get_var ; This also points SI at right place in variable table cmp byte [.delim], '+' jne .not_plus add ax, bx jmp .finish .not_plus: cmp byte [.delim], '-' jne .not_minus sub ax, bx jmp .finish .not_minus: cmp byte [.delim], '*' jne .not_times mul bx jmp .finish .not_times: cmp byte [.delim], '/' jne .not_divide cmp bx, 0 je .divide_zero mov dx, 0 div bx jmp .finish .not_divide: mov dx, 0 div bx mov ax, dx ; Get remainder .finish: mov bx, ax mov byte al, [.tmp] call set_var jmp .check_for_more .divide_zero: mov si, err_divide_by_zero jmp error .handle_variable: mov ax, 0 mov byte al, [token] call get_var mov bx, ax mov ax, 0 mov byte al, [.tmp] call get_var cmp byte [.delim], '+' jne .vnot_plus add ax, bx jmp .vfinish .vnot_plus: cmp byte [.delim], '-' jne .vnot_minus sub ax, bx jmp .vfinish .vnot_minus: cmp byte [.delim], '*' jne .vnot_times mul bx jmp .vfinish .vnot_times: cmp byte [.delim], '/' jne .vnot_divide mov dx, 0 div bx jmp .finish .vnot_divide: mov dx, 0 div bx mov ax, dx ; Get remainder .vfinish: mov bx, ax mov byte al, [.tmp] call set_var jmp .check_for_more .second_is_string: ; These are "X = word" functions mov di, token mov si, ink_keyword call string_direct_compare je .is_ink mov si, progstart_keyword call string_direct_compare je .is_progstart mov si, ramstart_keyword call string_direct_compare je .is_ramstart mov si, timer_keyword call string_direct_compare je .is_timer mov si, variables_keyword call string_direct_compare je .is_variables mov si, version_keyword call string_direct_compare je .is_version jmp .error .is_ink: mov ax, 0 mov byte al, [.tmp] mov bx, 0 mov byte bl, [ink_colour] call set_var jmp mainloop .is_progstart: mov ax, 0 mov byte al, [.tmp] mov word bx, [load_point] call set_var jmp mainloop .is_ramstart: mov ax, 0 mov byte al, [.tmp] mov word bx, [prog_end] inc bx inc bx inc bx call set_var jmp mainloop .is_timer: mov ah, 0 int 1Ah mov bx, dx mov ax, 0 mov byte al, [.tmp] call set_var jmp mainloop .is_variables: mov bx, vars_loc mov ax, 0 mov byte al, [.tmp] call set_var jmp mainloop .is_version: call misc_get_api_version mov bh, 0 mov bl, al mov al, [.tmp] call set_var jmp mainloop .error: mov si, err_syntax jmp error .tmp db 0 .tmp_loc dw 0 .delim db 0 ; ================================================================== ; SPECIFIC COMMAND CODE STARTS HERE ; ------------------------------------------------------------------ ; ALERT do_alert: mov bh, [work_page] ; Store the cursor position mov ah, 03h int 10h call get_token cmp ax, QUOTE je .is_quote cmp ax, STRING_VAR je .is_string mov si, err_syntax jmp error .is_string: mov si, string_vars mov ax, 128 mul bx add ax, si jmp .display_message .is_quote: mov ax, token ; First string for alert box .display_message: mov bx, 0 ; Others are blank mov cx, 0 mov dx, 0 ; One-choice box call graphics_dialogue_box mov bh, [work_page] ; Move the cursor back mov ah, 02h int 10h jmp mainloop ;------------------------------------------------------------------- ; ASKFILE do_askfile: mov bh, [work_page] ; Store the cursor position mov ah, 03h int 10h call get_token cmp ax, STRING_VAR jne .error mov si, string_vars ; Get the string location mov ax, 128 mul bx add ax, si mov word [.tmp], ax call graphics_file_selector ; Present the selector mov word di, [.tmp] ; Copy the string mov si, ax call string_copy mov bh, [work_page] ; Move the cursor back mov ah, 02h int 10h jmp mainloop .error: mov si, err_syntax jmp error .data: .tmp dw 0 ; ------------------------------------------------------------------ ; BREAK do_break: mov si, err_break jmp error ; ------------------------------------------------------------------ ; CALL do_call: call get_token cmp ax, NUMBER je .is_number mov ax, 0 mov byte al, [token] call get_var jmp .execute_call .is_number: mov si, token call string_cast_to_int .execute_call: mov bx, 0 mov cx, 0 mov dx, 0 mov di, 0 mov si, 0 call ax jmp mainloop ; ------------------------------------------------------------------ ; CASE do_case: call get_token cmp ax, STRING jne .error mov si, token mov di, upper_keyword call string_direct_compare jc .uppercase mov di, lower_keyword call string_direct_compare jc .lowercase jmp .error .uppercase: call get_token cmp ax, STRING_VAR jne .error mov si, string_vars mov ax, 128 mul bx add ax, si call string_upper_case jmp mainloop .lowercase: call get_token cmp ax, STRING_VAR jne .error mov si, string_vars mov ax, 128 mul bx add ax, si call string_lower_case jmp mainloop .error: mov si, err_syntax jmp error ; ------------------------------------------------------------------ ; CLS do_cls: mov ah, 5 mov byte al, [work_page] int 10h call os_set_text_mode mov ah, 5 mov byte al, [disp_page] int 10h jmp mainloop ; ------------------------------------------------------------------ ; CURSOR do_cursor: call get_token mov si, token mov di, .on_str call string_direct_compare jc .turn_on mov si, token mov di, .off_str call string_direct_compare jc .turn_off mov si, err_syntax jmp error .turn_on: call keyboard_show_cursor jmp mainloop .turn_off: call keyboard_hide_cursor jmp mainloop .on_str db "ON", 0 .off_str db "OFF", 0 ; ------------------------------------------------------------------ ; CURSCHAR do_curschar: call get_token cmp ax, VARIABLE je .is_variable mov si, err_syntax jmp error .is_variable: mov ax, 0 mov byte al, [token] push ax ; Store variable we're going to use mov ah, 08h mov bx, 0 mov byte bh, [work_page] int 10h ; Get char at current cursor location mov bx, 0 ; We only want the lower byte (the char, not attribute) mov bl, al pop ax ; Get the variable back call set_var ; And store the value jmp mainloop ; ------------------------------------------------------------------ ; CURSCOL do_curscol: call get_token cmp ax, VARIABLE jne .error mov ah, 0 mov byte al, [token] push ax mov ah, 8 mov bx, 0 mov byte bh, [work_page] int 10h mov bh, 0 mov bl, ah ; Get colour for higher byte; ignore lower byte (char) pop ax call set_var jmp mainloop .error: mov si, err_syntax jmp error ; ------------------------------------------------------------------ ; CURSPOS do_curspos: mov byte bh, [work_page] mov ah, 3 int 10h call get_token cmp ax, VARIABLE jne .error mov ah, 0 ; Get the column in the first variable mov byte al, [token] mov bx, 0 mov bl, dl call set_var call get_token cmp ax, VARIABLE jne .error mov ah, 0 ; Get the row to the second mov byte al, [token] mov bx, 0 mov bl, dh call set_var jmp mainloop .error: mov si, err_syntax jmp error ; ------------------------------------------------------------------ ; DELETE do_delete: call get_token cmp ax, QUOTE je .is_quote cmp ax, STRING_VAR jne near .error mov si, string_vars mov ax, 128 mul bx add si, ax jmp .get_filename .is_quote: mov si, token .get_filename: mov ax, si call disk_file_exists jc .no_file call disk_remove_file jc .del_fail jmp .returngood .no_file: mov ax, 0 mov byte al, 'R' mov bx, 2 call set_var jmp mainloop .returngood: mov ax, 0 mov byte al, 'R' mov bx, 0 call set_var jmp mainloop .del_fail: mov ax, 0 mov byte al, 'R' mov bx, 1 call set_var jmp mainloop .error: mov si, err_syntax jmp error ; ------------------------------------------------------------------ ; DO do_do: cmp byte [loop_in], 20 je .loop_max mov word di, do_loop_store mov byte al, [loop_in] mov ah, 0 add di, ax mov word ax, [prog] sub ax, 3 stosw inc byte [loop_in] inc byte [loop_in] jmp mainloop .loop_max: mov si, err_doloop_maximum jmp error ;------------------------------------------------------------------- ; ELSE do_else: cmp byte [last_if_true], 1 je .last_true inc word [prog] jmp mainloop .last_true: mov word si, [prog] .next_line: lodsb cmp al, 10 jne .next_line dec si mov word [prog], si jmp mainloop ; ------------------------------------------------------------------ ; END do_end: mov ah, 5 ; Restore active page mov al, 0 int 10h mov byte [work_page], 0 mov byte [disp_page], 0 mov word sp, [orig_stack] ret ; ------------------------------------------------------------------ ; FILES do_files: mov ax, .filelist ; get a copy of the filelist call disk_file_list mov si, ax call keyboard_get_cursor_pos ; move cursor to start of line mov dl, 0 call keyboard_move_cursor mov ah, 9 ; print character function mov bh, [work_page] ; define parameters (page, colour, times) mov bl, [ink_colour] mov cx, 1 .file_list_loop: lodsb ; get a byte from the list cmp al, ',' ; a comma means the next file, so create a new line for it je .nextfile cmp al, 0 ; the list is null terminated je .end_of_list int 10h ; okay, it's not a comma or a null so print it call keyboard_get_cursor_pos ; find the location of the cursor inc dl ; move the cursor forward call keyboard_move_cursor jmp .file_list_loop ; keep going until the list is finished .nextfile: call keyboard_get_cursor_pos ; if the column is over 60 we need a new line cmp dl, 60 jge .newline .next_column: ; print spaces until the next column mov al, ' ' int 10h inc dl call keyboard_move_cursor cmp dl, 15 je .file_list_loop cmp dl, 30 je .file_list_loop cmp dl, 45 je .file_list_loop cmp dl, 60 je .file_list_loop jmp .next_column .newline: call os_print_newline ; create a new line jmp .file_list_loop .end_of_list: call os_print_newline jmp mainloop ; preform next command .data: .filelist times 256 db 0 ; ------------------------------------------------------------------ ; FOR do_for: call get_token ; Get the variable we're using in this loop cmp ax, VARIABLE jne near .error mov ax, 0 mov byte al, [token] mov byte [.tmp_var], al ; Store it in a temporary location for now call get_token mov ax, 0 ; Check it's followed up with '=' mov byte al, [token] cmp al, '=' jne .error call get_token ; Next we want a number cmp ax, VARIABLE je .first_is_var cmp ax, NUMBER jne .error mov si, token ; Convert it call string_cast_to_int jmp .continue .first_is_var: mov ax, 0 ; It's a variable, so get it's value mov al, [token] call get_var ; At this stage, we've read something like "FOR X = 1" ; so let's store that 1 in the variable table .continue: mov bx, ax mov ax, 0 mov byte al, [.tmp_var] call set_var call get_token ; Next we're looking for "TO" cmp ax, STRING jne .error mov ax, token call string_upper_case mov si, token mov di, .to_string call string_direct_compare jnc .error ; So now we're at "FOR X = 1 TO" call get_token cmp ax, VARIABLE je .second_is_var cmp ax, NUMBER jne .error .second_is_number: mov si, token ; Get target number call string_cast_to_int jmp .continue2 .second_is_var: mov ax, 0 ; It's a variable, so get it's value mov al, [token] call get_var .continue2: mov bx, ax mov ax, 0 mov byte al, [.tmp_var] sub al, 65 ; Store target number in table mov di, for_variables add di, ax add di, ax mov ax, bx stosw ; So we've got the variable, assigned it the starting number, and put into ; our table the limit it should reach. But we also need to store the point in ; code after the FOR line we should return to if NEXT X doesn't complete the loop... mov ax, 0 mov byte al, [.tmp_var] sub al, 65 ; Store code position to return to in table mov di, for_code_points add di, ax add di, ax mov word ax, [prog] stosw jmp mainloop .error: mov si, err_syntax jmp error .tmp_var db 0 .to_string db 'TO', 0 ; ------------------------------------------------------------------ ; GETKEY do_getkey: call get_token cmp ax, VARIABLE je .is_variable mov si, err_syntax jmp error .is_variable: mov ax, 0 mov byte al, [token] push ax call keyboard_check_key cmp ax, 48E0h je .up_pressed cmp ax, 50E0h je .down_pressed cmp ax, 4BE0h je .left_pressed cmp ax, 4DE0h je .right_pressed .store: mov bx, 0 mov bl, al pop ax call set_var jmp mainloop .up_pressed: mov ax, 1 jmp .store .down_pressed: mov ax, 2 jmp .store .left_pressed: mov ax, 3 jmp .store .right_pressed: mov ax, 4 jmp .store ; ------------------------------------------------------------------ ; GOSUB do_gosub: call get_token ; Get the number (label) cmp ax, STRING je .is_ok mov si, err_goto_notlabel jmp error .is_ok: mov si, token ; Back up this label mov di, .tmp_token call string_copy mov ax, .tmp_token call string_length mov di, .tmp_token ; Add ':' char to end for searching add di, ax mov al, ':' stosb mov al, 0 stosb inc byte [gosub_depth] mov ax, 0 mov byte al, [gosub_depth] ; Get current GOSUB nest level cmp al, 9 jle .within_limit mov si, err_nest_limit jmp error .within_limit: mov di, gosub_points ; Move into our table of pointers add di, ax ; Table is words (not bytes) add di, ax mov word ax, [prog] stosw ; Store current location before jump mov word ax, [load_point] mov word [prog], ax ; Return to start of program to find label .loop: call get_token cmp ax, LABEL jne .line_loop mov si, token mov di, .tmp_token call string_direct_compare jc mainloop .line_loop: ; Go to end of line mov word si, [prog] mov byte al, [si] inc word [prog] cmp al, 10 jne .line_loop mov word ax, [prog] mov word bx, [prog_end] cmp ax, bx jg .past_end jmp .loop .past_end: mov si, err_label_notfound jmp error .tmp_token times 30 db 0 ; ------------------------------------------------------------------ ; GOTO do_goto: call get_token ; Get the next token cmp ax, STRING je .is_ok mov si, err_goto_notlabel jmp error .is_ok: mov si, token ; Back up this label mov di, .tmp_token call string_copy mov ax, .tmp_token call string_length mov di, .tmp_token ; Add ':' char to end for searching add di, ax mov al, ':' stosb mov al, 0 stosb mov word ax, [load_point] mov word [prog], ax ; Return to start of program to find label .loop: call get_token cmp ax, LABEL jne .line_loop mov si, token mov di, .tmp_token call string_direct_compare jc mainloop .line_loop: ; Go to end of line mov word si, [prog] mov byte al, [si] inc word [prog] cmp al, 10 jne .line_loop mov word ax, [prog] mov word bx, [prog_end] cmp ax, bx jg .past_end jmp .loop .past_end: mov si, err_label_notfound jmp error .tmp_token times 30 db 0 ; ------------------------------------------------------------------ ; IF do_if: call get_token cmp ax, VARIABLE ; If can only be followed by a variable je .num_var cmp ax, STRING_VAR je near .string_var mov si, err_syntax jmp error .num_var: mov ax, 0 mov byte al, [token] call get_var mov dx, ax ; Store value of first part of comparison call get_token ; Get the delimiter mov byte al, [token] cmp al, '=' je .equals cmp al, '>' je .greater cmp al, '<' je .less cmp al, '!' je .not_equals mov si, err_syntax ; If not one of the above, error out jmp error .equals: call get_token ; Is this 'X = Y' (equals another variable?) cmp ax, CHAR je .equals_char mov byte al, [token] call is_letter jc .equals_var mov si, token ; Otherwise it's, eg 'X = 1' (a number) call string_cast_to_int cmp ax, dx ; On to the THEN bit if 'X = num' matches je near .on_to_then jmp .finish_line ; Otherwise skip the rest of the line .equals_char: mov ax, 0 mov byte al, [token] cmp ax, dx je near .on_to_then jmp .finish_line .equals_var: mov ax, 0 mov byte al, [token] call get_var cmp ax, dx ; Do the variables match? je near .on_to_then ; On to the THEN bit if so jmp .finish_line ; Otherwise skip the rest of the line .not_equals: mov byte al, [token + 1] cmp al, '=' jne .error call get_token cmp ax, CHAR je .not_equals_char cmp ax, VARIABLE je .not_equals_var cmp ax, NUMBER je .not_equals_number mov si, err_syntax jmp error .not_equals_char: mov ax, 0 mov byte al, [token] cmp ax, dx jne near .on_to_then jmp .finish_line .not_equals_var: mov ax, 0 mov byte al, [token] call get_var cmp ax, dx jne near .on_to_then jmp .finish_line .not_equals_number: mov si, token call string_cast_to_int cmp ax, dx jne near .on_to_then jmp .finish_line .greater: call get_token ; Greater than a variable or number? mov byte al, [token] call is_letter jc .greater_var mov si, token ; Must be a number here... call string_cast_to_int cmp ax, dx jl near .on_to_then jmp .finish_line .greater_var: ; Variable in this case mov ax, 0 mov byte al, [token] call get_var cmp ax, dx ; Make the comparison! jl .on_to_then jmp .finish_line .less: call get_token mov byte al, [token] call is_letter jc .less_var mov si, token call string_cast_to_int cmp ax, dx jg .on_to_then jmp .finish_line .less_var: mov ax, 0 mov byte al, [token] call get_var cmp ax, dx jg .on_to_then jmp .finish_line .string_var: mov byte [.tmp_string_var], bl call get_token mov byte al, [token] mov byte [.tmp_string_op], al cmp al, '=' je .get_second cmp al, '!' jne .error mov byte al, [token + 1] cmp al, '=' jne .error .get_second: call get_token cmp ax, STRING_VAR je .second_is_string_var cmp ax, QUOTE jne .error mov si, string_vars mov ax, 128 mul bx add si, ax mov di, token mov al, [.tmp_string_op] cmp al, '=' je .string_equals cmp al, '!' je .string_not_equals jmp .error .second_is_string_var: mov si, string_vars mov ax, 128 mul bx add si, ax mov di, string_vars mov bx, 0 mov byte bl, [.tmp_string_var] mov ax, 128 mul bx add di, ax mov al, [.tmp_string_op] cmp al, '!' je .string_not_equals .string_equals: call string_direct_compare jc .on_to_then jmp .finish_line .string_not_equals: call string_direct_compare jnc .on_to_then jmp .finish_line .on_to_then: call get_token mov si, token ; Look for AND for more comparison mov di, and_keyword call string_direct_compare jc do_if mov si, token ; Look for THEN to perform more operations mov di, then_keyword call string_direct_compare jc .then_present mov si, err_syntax jmp error .then_present: ; Continue rest of line like any other command! mov byte [last_if_true], 1 jmp mainloop .finish_line: ; IF wasn't fulfilled, so skip rest of line mov word si, [prog] mov byte al, [si] inc word [prog] cmp al, 10 jne .finish_line mov byte [last_if_true], 0 jmp mainloop .error: mov si, err_syntax jmp error .tmp_string_var db 0 .tmp_string_op db 0 ; ------------------------------------------------------------------ ; INCLUDE do_include: call get_token cmp ax, QUOTE je .is_ok mov si, err_syntax jmp error .is_ok: mov ax, token mov word cx, [prog_end] inc cx ; Add a bit of space after original code inc cx inc cx push cx call disk_load_file jc .load_fail pop cx add cx, bx mov word [prog_end], cx jmp mainloop .load_fail: pop cx mov si, err_file_notfound jmp error ; ------------------------------------------------------------------ ; INK do_ink: call get_token ; Get column cmp ax, VARIABLE je .first_is_var mov si, token call string_cast_to_int mov byte [ink_colour], al jmp mainloop .first_is_var: mov ax, 0 mov byte al, [token] call get_var mov byte [ink_colour], al jmp mainloop ; ------------------------------------------------------------------ ; INPUT do_input: mov byte [.tmpstring], 0 call get_token cmp ax, VARIABLE ; We can only INPUT to variables! je .number_var cmp ax, STRING_VAR je .string_var mov si, err_syntax jmp error .number_var: mov ax, .tmpstring ; Get input from the user mov bx, 6 call keyboard_display_input mov ax, .tmpstring call string_length cmp ax, 0 jne .char_entered mov byte [.tmpstring], '0' ; If enter hit, fill variable with zero mov byte [.tmpstring + 1], 0 .char_entered: mov si, .tmpstring ; Convert to integer format call string_cast_to_int mov bx, ax mov ax, 0 mov byte al, [token] ; Get the variable where we're storing it... call set_var ; ...and store it! call os_print_newline jmp mainloop .string_var: mov ax, 128 mul bx add ax, string_vars mov bx, 128 call keyboard_display_input call os_print_newline jmp mainloop .tmpstring times 6 db 0 ; ----------------------------------------------------------- ; LEN do_len: call get_token cmp ax, STRING_VAR jne .error mov si, string_vars mov ax, 128 mul bx add si, ax mov ax, si call string_length mov word [.num1], ax call get_token cmp ax, VARIABLE je .is_ok mov si, err_syntax jmp error .is_ok: mov ax, 0 mov byte al, [token] mov bl, al jmp .finish .finish: mov bx, [.num1] mov byte al, [token] call set_var mov ax, 0 jmp mainloop .error: mov si, err_syntax jmp error .num1 dw 0 ; ------------------------------------------------------------------ ; LISTBOX do_listbox: mov bh, [work_page] ; Store the cursor position mov ah, 03h int 10h call get_token cmp ax, STRING_VAR jne .error mov si, string_vars mov ax, 128 mul bx add si, ax mov word [.s1], si call get_token cmp ax, STRING_VAR jne .error mov si, string_vars mov ax, 128 mul bx add si, ax mov word [.s2], si call get_token cmp ax, STRING_VAR jne .error mov si, string_vars mov ax, 128 mul bx add si, ax mov word [.s3], si call get_token cmp ax, VARIABLE jne .error mov byte al, [token] mov byte [.var], al mov word ax, [.s1] mov word bx, [.s2] mov word cx, [.s3] call graphics_list_dialogue jc .esc_pressed pusha mov bh, [work_page] ; Move the cursor back mov ah, 02h int 10h popa mov bx, ax mov ax, 0 mov byte al, [.var] call set_var jmp mainloop .esc_pressed: mov ax, 0 mov byte al, [.var] mov bx, 0 call set_var jmp mainloop .error: mov si, err_syntax jmp error .s1 dw 0 .s2 dw 0 .s3 dw 0 .var db 0 ; ------------------------------------------------------------------ ; LOAD do_load: call get_token cmp ax, QUOTE je .is_quote cmp ax, STRING_VAR jne .error mov si, string_vars mov ax, 128 mul bx add si, ax jmp .get_position .is_quote: mov si, token .get_position: mov ax, si call disk_file_exists jc .file_not_exists mov dx, ax ; Store for now call get_token cmp ax, VARIABLE je .second_is_var cmp ax, NUMBER jne .error mov si, token call string_cast_to_int .load_part: mov cx, ax mov ax, dx call disk_load_file mov ax, 0 mov byte al, 'S' call set_var mov ax, 0 mov byte al, 'R' mov bx, 0 call set_var jmp mainloop .second_is_var: mov ax, 0 mov byte al, [token] call get_var jmp .load_part .file_not_exists: mov ax, 0 mov byte al, 'R' mov bx, 1 call set_var call get_token ; Skip past the loading point -- unnecessary now jmp mainloop .error: mov si, err_syntax jmp error ; ------------------------------------------------------------------ ; LOOP do_loop: cmp byte [loop_in], 0 je .no_do dec byte [loop_in] dec byte [loop_in] mov dx, 0 call get_token mov di, token mov si, .endless_word call string_direct_compare jc .loop_back mov si, .while_word call string_direct_compare jc .while_set mov si, .until_word call string_direct_compare jnc .error .get_first_var: call get_token cmp ax, VARIABLE jne .error mov al, [token] call get_var mov cx, ax .check_equals: call get_token cmp ax, UNKNOWN jne .error mov ax, [token] cmp al, '=' je .sign_ok cmp al, '>' je .sign_ok cmp al, '<' je .sign_ok jmp .error .sign_ok: mov byte [.sign], al .get_second_var: call get_token cmp ax, NUMBER je .second_is_num cmp ax, VARIABLE je .second_is_var cmp ax, CHAR jne .error .second_is_char: mov ah, 0 mov al, [token] jmp .check_true .second_is_var: mov al, [token] call get_var jmp .check_true .second_is_num: mov si, token call string_cast_to_int .check_true: mov byte bl, [.sign] cmp bl, '=' je .sign_equals cmp bl, '>' je .sign_greater jmp .sign_lesser .sign_equals: cmp ax, cx jne .false jmp .true .sign_greater: cmp ax, cx jge .false jmp .true .sign_lesser: cmp ax, cx jle .false jmp .true .true: cmp dx, 1 je .loop_back jmp mainloop .false: cmp dx, 1 je mainloop .loop_back: mov word si, do_loop_store mov byte al, [loop_in] mov ah, 0 add si, ax lodsw mov word [prog], ax jmp mainloop .while_set: mov dx, 1 jmp .get_first_var .no_do: mov si, err_loop jmp error .error: mov si, err_syntax jmp error .data: .while_word db "WHILE", 0 .until_word db "UNTIL", 0 .endless_word db "ENDLESS", 0 .sign db 0 ; ------------------------------------------------------------------ ; MOVE do_move: call get_token cmp ax, VARIABLE je .first_is_var mov si, token call string_cast_to_int mov dl, al jmp .onto_second .first_is_var: mov ax, 0 mov byte al, [token] call get_var mov dl, al .onto_second: call get_token cmp ax, VARIABLE je .second_is_var mov si, token call string_cast_to_int mov dh, al jmp .finish .second_is_var: mov ax, 0 mov byte al, [token] call get_var mov dh, al .finish: mov byte bh, [work_page] mov ah, 2 int 10h jmp mainloop ; ------------------------------------------------------------------ ; NEXT do_next: call get_token cmp ax, VARIABLE ; NEXT must be followed by a variable jne .error mov ax, 0 mov byte al, [token] call get_var inc ax ; NEXT increments the variable, of course! mov bx, ax mov ax, 0 mov byte al, [token] sub al, 65 mov si, for_variables add si, ax add si, ax lodsw ; Get the target number from the table inc ax ; (Make the loop inclusive of target number) cmp ax, bx ; Do the variable and target match? je .loop_finished mov ax, 0 ; If not, store the updated variable mov byte al, [token] call set_var mov ax, 0 ; Find the code point and go back mov byte al, [token] sub al, 65 mov si, for_code_points add si, ax add si, ax lodsw mov word [prog], ax jmp mainloop .loop_finished: jmp mainloop .error: mov si, err_syntax jmp error ;------------------------------------------------------------------- ; NUMBER do_number: call get_token ; Check if it's string to number, or number to string cmp ax, STRING_VAR je .is_string cmp ax, VARIABLE je .is_variable jmp .error .is_string: mov si, string_vars mov ax, 128 mul bx add si, ax mov [.tmp], si call get_token mov si, [.tmp] cmp ax, VARIABLE jne .error call string_cast_to_int mov bx, ax mov ax, 0 mov byte al, [token] call set_var jmp mainloop .is_variable: mov ax, 0 ; Get the value of the number mov byte al, [token] call get_var call string_cast_from_int ; Convert to a string mov [.tmp], ax call get_token ; Get the second parameter mov si, [.tmp] cmp ax, STRING_VAR ; Make sure it's a string variable jne .error mov di, string_vars ; Locate string variable mov ax, 128 mul bx add di, ax call string_copy ; Save converted string jmp mainloop .error: mov si, err_syntax jmp error .tmp dw 0 ;------------------------------------------------------------------- ; PAGE do_page: call get_token cmp ax, NUMBER jne .error mov si, token call string_cast_to_int mov byte [work_page], al ; Set work page variable call get_token cmp ax, NUMBER jne .error mov si, token call string_cast_to_int mov byte [disp_page], al ; Set display page variable ; Change display page -- AL should already be present from the string_cast_to_int mov ah, 5 int 10h jmp mainloop .error: mov si, err_syntax jmp error ; ------------------------------------------------------------------ ; PAUSE do_pause: call get_token cmp ax, VARIABLE je .is_var mov si, token call string_cast_to_int jmp .finish .is_var: mov ax, 0 mov byte al, [token] call get_var .finish: call misc_pause jmp mainloop ; ------------------------------------------------------------------ ; PEEK do_peek: call get_token cmp ax, VARIABLE jne .error mov ax, 0 mov byte al, [token] mov byte [.tmp_var], al call get_token cmp ax, VARIABLE je .dereference cmp ax, NUMBER jne .error mov si, token call string_cast_to_int .store: mov si, ax mov bx, 0 mov byte bl, [si] mov ax, 0 mov byte al, [.tmp_var] call set_var jmp mainloop .dereference: mov byte al, [token] call get_var jmp .store .error: mov si, err_syntax jmp error .tmp_var db 0 ; ------------------------------------------------------------------ ; PEEKINT do_peekint: call get_token cmp ax, VARIABLE jne .error .get_second: mov al, [token] mov cx, ax call get_token cmp ax, VARIABLE je .address_is_var cmp ax, NUMBER jne .error .address_is_number: mov si, token call string_cast_to_int jmp .load_data .address_is_var: mov al, [token] call get_var .load_data: mov si, ax mov bx, [si] mov ax, cx call set_var jmp mainloop .error: mov si, err_syntax jmp error ; ------------------------------------------------------------------ ; POKE do_poke: call get_token cmp ax, VARIABLE je .first_is_var cmp ax, NUMBER jne .error mov si, token call string_cast_to_int cmp ax, 255 jg .error mov byte [.first_value], al jmp .onto_second .first_is_var: mov ax, 0 mov byte al, [token] call get_var mov byte [.first_value], al .onto_second: call get_token cmp ax, VARIABLE je .second_is_var cmp ax, NUMBER jne .error mov si, token call string_cast_to_int .got_value: mov di, ax mov ax, 0 mov byte al, [.first_value] mov byte [di], al jmp mainloop .second_is_var: mov ax, 0 mov byte al, [token] call get_var jmp .got_value .error: mov si, err_syntax jmp error .first_value db 0 ; ------------------------------------------------------------------ ; POKEINT do_pokeint: call get_token cmp ax, VARIABLE je .data_is_var cmp ax, NUMBER jne .error .data_is_num: mov si, token call string_cast_to_int jmp .get_second .data_is_var: mov al, [token] call get_var .get_second: mov cx, ax call get_token cmp ax, VARIABLE je .address_is_var cmp ax, NUMBER jne .error .address_is_num: mov si, token call string_cast_to_int jmp .save_data .address_is_var: mov al, [token] call get_var .save_data: mov si, ax mov [si], cx jmp mainloop .error: mov si, err_syntax jmp error ; ------------------------------------------------------------------ ; PORT do_port: call get_token mov si, token mov di, .out_cmd call string_direct_compare jc .do_out_cmd mov di, .in_cmd call string_direct_compare jc .do_in_cmd jmp .error .do_out_cmd: call get_token cmp ax, NUMBER jne .error mov si, token call string_cast_to_int ; Now AX = port number mov dx, ax call get_token cmp ax, NUMBER je .out_is_num cmp ax, VARIABLE je .out_is_var jmp .error .out_is_num: mov si, token call string_cast_to_int call port_byte_out jmp mainloop .out_is_var: mov ax, 0 mov byte al, [token] call get_var call port_byte_out jmp mainloop .do_in_cmd: call get_token cmp ax, NUMBER jne .error mov si, token call string_cast_to_int mov dx, ax call get_token cmp ax, VARIABLE jne .error mov byte cl, [token] call port_byte_in mov bx, 0 mov bl, al mov al, cl call set_var jmp mainloop .error: mov si, err_syntax jmp error .out_cmd db "OUT", 0 .in_cmd db "IN", 0 ; ------------------------------------------------------------------ ; PRINT do_print: call get_token ; Get part after PRINT cmp ax, QUOTE ; What type is it? je .print_quote cmp ax, VARIABLE ; Numerical variable (eg X) je .print_var cmp ax, STRING_VAR ; String variable (eg $1) je .print_string_var cmp ax, STRING ; Special keyword (eg CHR or HEX) je .print_keyword mov si, err_print_type ; We only print quoted strings and vars! jmp error .print_var: mov ax, 0 mov byte al, [token] call get_var ; Get its value call string_cast_from_int ; Convert to string mov si, ax call os_print_string jmp .newline_or_not .print_quote: ; If it's quoted text, print it mov si, token .print_quote_loop: lodsb cmp al, 0 je .newline_or_not mov ah, 09h mov byte bl, [ink_colour] mov byte bh, [work_page] mov cx, 1 int 10h mov ah, 3 int 10h cmp dl, 79 jge .quote_newline inc dl .move_cur_quote: mov byte bh, [work_page] mov ah, 02h int 10h jmp .print_quote_loop .quote_newline: cmp dh, 24 je .move_cur_quote mov dl, 0 inc dh jmp .move_cur_quote .print_string_var: mov si, string_vars mov ax, 128 mul bx add si, ax jmp .print_quote_loop .print_keyword: mov si, token mov di, chr_keyword call string_direct_compare jc .is_chr mov di, hex_keyword call string_direct_compare jc .is_hex mov si, err_syntax jmp error .is_chr: call get_token cmp ax, VARIABLE je .is_chr_variable cmp ax, NUMBER je .is_chr_number .is_chr_variable: mov ax, 0 mov byte al, [token] call get_var jmp .print_chr .is_chr_number: mov si, token call string_cast_to_int .print_chr: mov ah, 09h mov byte bl, [ink_colour] mov byte bh, [work_page] mov cx, 1 int 10h mov ah, 3 ; Move the cursor forward int 10h inc dl cmp dl, 79 jg .end_line ; If it's over the end of the line .move_cur: mov ah, 2 int 10h jmp .newline_or_not .is_hex: call get_token cmp ax, VARIABLE jne .error mov ax, 0 mov byte al, [token] call get_var call string_print_2hex jmp .newline_or_not .end_line: mov dl, 0 inc dh cmp dh, 25 jl .move_cur mov dh, 24 mov dl, 79 jmp .move_cur .error: mov si, err_syntax jmp error .newline_or_not: ; We want to see if the command ends with ';' -- which means that ; we shouldn't print a newline after it finishes. So we store the ; current program location to pop ahead and see if there's the ';' ; character -- otherwise we put the program location back and resume ; the main loop mov word ax, [prog] mov word [.tmp_loc], ax call get_token cmp ax, UNKNOWN jne .ignore mov ax, 0 mov al, [token] cmp al, ';' jne .ignore jmp mainloop ; And go back to interpreting the code! .ignore: mov ah, 5 mov al, [work_page] int 10h mov bh, [work_page] call os_print_newline mov ah, 5 mov al, [disp_page] mov word ax, [.tmp_loc] mov word [prog], ax jmp mainloop .tmp_loc dw 0 ; ------------------------------------------------------------------ ; RAND do_rand: call get_token cmp ax, VARIABLE jne .error mov byte al, [token] mov byte [.tmp], al call get_token cmp ax, NUMBER jne .error mov si, token call string_cast_to_int mov word [.num1], ax call get_token cmp ax, NUMBER jne .error mov si, token call string_cast_to_int mov word [.num2], ax mov word ax, [.num1] mov word bx, [.num2] call math_get_random mov bx, cx mov ax, 0 mov byte al, [.tmp] call set_var jmp mainloop .tmp db 0 .num1 dw 0 .num2 dw 0 .error: mov si, err_syntax jmp error ; ------------------------------------------------------------------ ; READ do_read: call get_token ; Get the next token cmp ax, STRING ; Check for a label je .is_ok mov si, err_goto_notlabel jmp error .is_ok: mov si, token ; Back up this label mov di, .tmp_token call string_copy mov ax, .tmp_token call string_length mov di, .tmp_token ; Add ':' char to end for searching add di, ax mov al, ':' stosb mov al, 0 stosb call get_token ; Now get the offset variable cmp ax, VARIABLE je .second_part_is_var mov si, err_syntax jmp error .second_part_is_var: mov ax, 0 mov byte al, [token] call get_var cmp ax, 0 ; Want to be searching for at least the first byte! jg .var_bigger_than_zero mov si, err_syntax jmp error .var_bigger_than_zero: mov word [.to_skip], ax call get_token ; And now the var to store result into cmp ax, VARIABLE je .third_part_is_var mov si, err_syntax jmp error .third_part_is_var: ; Keep it for later mov ax, 0 mov byte al, [token] mov byte [.var_to_use], al ; OK, so now we have all the stuff we need. Let's search for the label mov word ax, [prog] ; Store current location mov word [.curr_location], ax mov word ax, [load_point] mov word [prog], ax ; Return to start of program to find label .loop: call get_token cmp ax, LABEL jne .line_loop mov si, token mov di, .tmp_token call string_direct_compare jc .found_label .line_loop: ; Go to end of line mov word si, [prog] mov byte al, [si] inc word [prog] cmp al, 10 jne .line_loop mov word ax, [prog] mov word bx, [prog_end] cmp ax, bx jg .past_end jmp .loop .past_end: mov si, err_label_notfound jmp error .found_label: mov word cx, [.to_skip] ; Skip requested number of data entries .data_skip_loop: push cx call get_token pop cx loop .data_skip_loop cmp ax, NUMBER je .data_is_num mov si, err_syntax jmp error .data_is_num: mov si, token call string_cast_to_int mov bx, ax mov ax, 0 mov byte al, [.var_to_use] call set_var mov word ax, [.curr_location] mov word [prog], ax jmp mainloop .curr_location dw 0 .to_skip dw 0 .var_to_use db 0 .tmp_token times 30 db 0 ; ------------------------------------------------------------------ ; REM do_rem: mov word si, [prog] mov byte al, [si] inc word [prog] cmp al, 10 ; Find end of line after REM jne do_rem jmp mainloop ; ------------------------------------------------------------------ ; RENAME do_rename: call get_token cmp ax, STRING_VAR ; Is it a string or a quote? je .first_is_string cmp ax, QUOTE je .first_is_quote jmp .error .first_is_string: mov si, string_vars ; Locate string mov ax, 128 mul bx add si, ax jmp .save_file1 .first_is_quote: mov si, token ; The location of quotes is provided .save_file1: mov word di, .file1 ; The filename is saved to temporary strings because call string_copy ; getting a second quote will overwrite the previous .get_second: call get_token cmp ax, STRING_VAR je .second_is_string cmp ax, QUOTE je .second_is_quote jmp .error .second_is_string: mov si, string_vars ; Locate second string mov ax, 128 mul bx add si, ax jmp .save_file2 .second_is_quote: mov si, token .save_file2: mov word di, .file2 call string_copy .check_exists: mov word ax, .file1 ; Check if the source file exists call disk_file_exists jc .file_not_found ; If it doesn't exists set "R = 1" clc mov ax, .file2 ; The second file is the destination and should not exist call disk_file_exists jnc .file_exists ; If it exists set "R = 3" .rename: mov word ax, .file1 ; Seem to be okay, lets rename mov word bx, .file2 call disk_rename_file jc .rename_failed ; If it failed set "R = 2", usually caused by a read-only disk mov ax, 0 ; It worked sucessfully, so set "R = 0" to indicate no error mov byte al, 'R' mov bx, 0 call set_var jmp mainloop .error: mov si, err_syntax jmp error .file_not_found: mov ax, 0 ; Set R variable to 1 mov byte al, 'R' mov bx, 1 call set_var jmp mainloop .rename_failed: mov ax, 0 ; Set R variable to 2 mov byte al, 'R' mov bx, 2 call set_var jmp mainloop .file_exists: mov ax, 0 mov byte al, 'R' ; Set R variable to 3 mov bx, 3 call set_var jmp mainloop .data: .file1 times 12 db 0 .file2 times 12 db 0 ; ------------------------------------------------------------------ ; RETURN do_return: mov ax, 0 mov byte al, [gosub_depth] cmp al, 0 jne .is_ok mov si, err_return jmp error .is_ok: mov si, gosub_points add si, ax ; Table is words (not bytes) add si, ax lodsw mov word [prog], ax dec byte [gosub_depth] jmp mainloop ; ------------------------------------------------------------------ ; SAVE do_save: call get_token cmp ax, QUOTE je .is_quote cmp ax, STRING_VAR jne near .error mov si, string_vars mov ax, 128 mul bx add si, ax jmp .get_position .is_quote: mov si, token .get_position: mov di, .tmp_filename call string_copy call get_token cmp ax, VARIABLE je .second_is_var cmp ax, NUMBER jne .error mov si, token call string_cast_to_int .set_data_loc: mov word [.data_loc], ax call get_token cmp ax, VARIABLE je .third_is_var cmp ax, NUMBER jne .error mov si, token call string_cast_to_int .check_exists: mov word [.data_size], ax mov word ax, .tmp_filename call disk_file_exists jc .write_file jmp .file_exists_fail .write_file: mov word ax, .tmp_filename mov word bx, [.data_loc] mov word cx, [.data_size] call disk_write_file jc .save_failure mov ax, 0 mov byte al, 'R' mov bx, 0 call set_var jmp mainloop .second_is_var: mov ax, 0 mov byte al, [token] call get_var jmp .set_data_loc .third_is_var: mov ax, 0 mov byte al, [token] call get_var jmp .check_exists .file_exists_fail: mov ax, 0 mov byte al, 'R' mov bx, 2 call set_var jmp mainloop .save_failure: mov ax, 0 mov byte al, 'R' mov bx, 1 call set_var jmp mainloop .error: mov si, err_syntax jmp error .filename_loc dw 0 .data_loc dw 0 .data_size dw 0 .tmp_filename times 15 db 0 ; ------------------------------------------------------------------ ; SERIAL do_serial: call get_token mov si, token mov di, .on_cmd call string_direct_compare jc .do_on_cmd mov di, .send_cmd call string_direct_compare jc .do_send_cmd mov di, .rec_cmd call string_direct_compare jc .do_rec_cmd jmp .error .do_on_cmd: call get_token cmp ax, NUMBER je .do_on_cmd_ok jmp .error .do_on_cmd_ok: mov si, token call string_cast_to_int cmp ax, 1200 je .on_cmd_slow_mode cmp ax, 9600 je .on_cmd_fast_mode jmp .error .on_cmd_fast_mode: mov ax, 0 call port_serial_enable jmp mainloop .on_cmd_slow_mode: mov ax, 1 call port_serial_enable jmp mainloop .do_send_cmd: call get_token cmp ax, NUMBER je .send_number cmp ax, VARIABLE je .send_variable jmp .error .send_number: mov si, token call string_cast_to_int call port_send_via_serial jmp mainloop .send_variable: mov ax, 0 mov byte al, [token] call get_var call port_send_via_serial jmp mainloop .do_rec_cmd: call get_token cmp ax, VARIABLE jne .error mov byte al, [token] mov cx, 0 mov cl, al call port_get_via_serial mov bx, 0 mov bl, al mov al, cl call set_var jmp mainloop .error: mov si, err_syntax jmp error .on_cmd db "ON", 0 .send_cmd db "SEND", 0 .rec_cmd db "REC", 0 ; ------------------------------------------------------------------ ; SIZE do_size: call get_token cmp ax, STRING_VAR je .is_string cmp ax, QUOTE je .is_quote jmp .error .is_string: mov si, string_vars mov ax, 128 mul bx add si, ax mov ax, si jmp .get_size .is_quote: mov ax, token .get_size: call disk_file_size jc .file_not_found mov ax, 0 mov al, 'S' call set_var mov ax, 0 mov al, 'R' mov bx, 0 call set_var jmp mainloop .error: mov si, err_syntax jmp error .file_not_found: mov ax, 0 mov al, [token] mov bx, 0 call set_var mov ax, 0 mov al, 'R' mov bx, 1 call set_var jmp mainloop ; ------------------------------------------------------------------ ; SOUND do_sound: call get_token cmp ax, VARIABLE je .first_is_var mov si, token call string_cast_to_int jmp .done_first .first_is_var: mov ax, 0 mov byte al, [token] call get_var .done_first: call sound_speaker_tone call get_token cmp ax, VARIABLE je .second_is_var mov si, token call string_cast_to_int jmp .finish .second_is_var: mov ax, 0 mov byte al, [token] call get_var .finish: call misc_pause call sound_speaker_off jmp mainloop ;------------------------------------------------------------------- ; STRING do_string: call get_token ; The first parameter is the word 'GET', 'SET', LOAD or SAVE mov si, token mov di, .get_cmd call string_direct_compare jc .set_str mov di, .set_cmd call string_direct_compare jc .get_str mov di, .load_cmd call string_direct_compare jc .load_str mov di, .save_cmd call string_direct_compare jc .save_str jmp .error .set_str: mov cx, 1 jmp .check_second .get_str: mov cx, 2 jmp .check_second .load_str: mov cx, 3 jmp .check_second .save_str: mov cx, 4 .check_second: call get_token ; The next should be a string variable, locate it cmp ax, STRING_VAR jne .error mov si, string_vars mov ax, 128 mul bx add si, ax mov word [.string_loc], si .check_third: call get_token ; Now there should be a number cmp ax, NUMBER je .third_is_number cmp ax, VARIABLE je .third_is_variable jmp .error .third_is_number: mov si, token call string_cast_to_int jmp .got_number .third_is_variable: mov ah, 0 mov al, [token] call get_var jmp .got_number .got_number: cmp ax, 128 jg .outrange cmp ax, 0 je .outrange sub ax, 1 mov dx, ax cmp cx, 3 ; load/save only need two variables je .load_var cmp cx, 4 je .save_var .check_forth: call get_token ; Next a numerical variable cmp ax, VARIABLE jne .error mov byte al, [token] mov byte [.tmp], al cmp cx, 2 je .set_var .get_var: mov word si, [.string_loc] ; Move to string location add si, dx ; Add offset lodsb ; Load data mov ah, 0 mov bx, ax ; Set data in numerical variable mov byte al, [.tmp] call set_var jmp mainloop .set_var: mov byte al, [.tmp] ; Retrieve the variable call get_var ; Get it's value mov di, [.string_loc] ; Locate the string add di, dx ; Add the offset stosb ; Store data jmp mainloop .load_var: inc dx mov si, dx mov di, [.string_loc] call string_copy jmp mainloop .save_var: inc dx mov si, [.string_loc] mov di, dx call string_copy jmp mainloop .error: mov si, err_syntax jmp error .outrange: mov dx, ax dec dx cmp cx, 3 je .load_var cmp cx, 4 je .save_var mov si, err_string_range jmp error .data: .get_cmd db "GET", 0 .set_cmd db "SET", 0 .load_cmd db "LOAD", 0 .save_cmd db "STORE", 0 .string_loc dw 0 .tmp db 0 ; ------------------------------------------------------------------ ; WAITKEY do_waitkey: call get_token cmp ax, VARIABLE je .is_variable mov si, err_syntax jmp error .is_variable: mov ax, 0 mov byte al, [token] push ax call keyboard_wait_for_key cmp ax, 48E0h je .up_pressed cmp ax, 50E0h je .down_pressed cmp ax, 4BE0h je .left_pressed cmp ax, 4DE0h je .right_pressed .store: mov bx, 0 mov bl, al pop ax call set_var jmp mainloop .up_pressed: mov ax, 1 jmp .store .down_pressed: mov ax, 2 jmp .store .left_pressed: mov ax, 3 jmp .store .right_pressed: mov ax, 4 jmp .store ; ================================================================== ; INTERNAL ROUTINES FOR INTERPRETER ; ------------------------------------------------------------------ ; Get value of variable character specified in AL (eg 'A') get_var: mov ah, 0 sub al, 65 mov si, variables add si, ax add si, ax lodsw ret ; ------------------------------------------------------------------ ; Set value of variable character specified in AL (eg 'A') ; with number specified in BX set_var: mov ah, 0 sub al, 65 ; Remove ASCII codes before 'A' mov di, variables ; Find position in table (of words) add di, ax add di, ax mov ax, bx stosw ret ; ------------------------------------------------------------------ ; Get token from current position in prog get_token: mov word si, [prog] lodsb cmp al, 10 je .newline cmp al, ' ' je .newline call is_number jc get_number_token cmp al, '"' je get_quote_token cmp al, 39 ; Quote mark (') je get_char_token cmp al, '$' je near get_string_var_token jmp get_string_token .newline: inc word [prog] jmp get_token get_number_token: mov word si, [prog] mov di, token .loop: lodsb cmp al, 10 je .done cmp al, ' ' je .done call is_number jc .fine mov si, err_char_in_num jmp error .fine: stosb inc word [prog] jmp .loop .done: mov al, 0 ; Zero-terminate the token stosb mov ax, NUMBER ; Pass back the token type ret get_char_token: inc word [prog] ; Move past first quote (') mov word si, [prog] lodsb mov byte [token], al lodsb cmp al, 39 ; Needs to finish with another quote je .is_ok mov si, err_quote_term jmp error .is_ok: inc word [prog] inc word [prog] mov ax, CHAR ret get_quote_token: inc word [prog] ; Move past first quote (") char mov word si, [prog] mov di, token .loop: lodsb cmp al, '"' je .done cmp al, 10 je .error stosb inc word [prog] jmp .loop .done: mov al, 0 ; Zero-terminate the token stosb inc word [prog] ; Move past final quote mov ax, QUOTE ; Pass back token type ret .error: mov si, err_quote_term jmp error get_string_var_token: lodsb mov bx, 0 ; If it's a string var, pass number of string in BX mov bl, al sub bl, 49 inc word [prog] inc word [prog] mov ax, STRING_VAR ret get_string_token: mov word si, [prog] mov di, token .loop: lodsb cmp al, 10 je .done cmp al, ' ' je .done stosb inc word [prog] jmp .loop .done: mov al, 0 ; Zero-terminate the token stosb mov ax, token call string_upper_case mov ax, token call string_length ; How long was the token? cmp ax, 1 ; If 1 char, it's a variable or delimiter je .is_not_string mov si, token ; If the token ends with ':', it's a label add si, ax dec si lodsb cmp al, ':' je .is_label mov ax, STRING ; Otherwise it's a general string of characters ret .is_label: mov ax, LABEL ret .is_not_string: mov byte al, [token] call is_letter jc .is_var mov ax, UNKNOWN ret .is_var: mov ax, VARIABLE ; Otherwise probably a variable ret ; ------------------------------------------------------------------ ; Set carry flag if AL contains ASCII number is_number: cmp al, 48 jl .not_number cmp al, 57 jg .not_number stc ret .not_number: clc ret ; ------------------------------------------------------------------ ; Set carry flag if AL contains ASCII letter is_letter: cmp al, 65 jl .not_letter cmp al, 90 jg .not_letter stc ret .not_letter: clc ret ; ------------------------------------------------------------------ ; Print error message and quit out error: mov ah, 5 ; Revert display page mov al, 0 int 10h mov byte [work_page], 0 mov byte [disp_page], 0 call os_print_newline call os_print_string ; Print error message mov si, line_num_starter call os_print_string ; And now print the line number where the error occurred. We do this ; by working from the start of the program to the current point, ; counting the number of newline characters along the way mov word si, [load_point] mov word bx, [prog] mov cx, 1 .loop: lodsb cmp al, 10 jne .not_newline inc cx .not_newline: cmp si, bx je .finish jmp .loop .finish: mov ax, cx call string_cast_from_int mov si, ax call os_print_string call os_print_newline mov word sp, [orig_stack] ; Restore the stack to as it was when BASIC started ret ; And finish ; Error messages text... err_char_in_num db "Error: unexpected char in number", 0 err_cmd_unknown db "Error: unknown command", 0 err_divide_by_zero db "Error: attempt to divide by zero", 0 err_doloop_maximum db "Error: DO/LOOP nesting limit exceeded", 0 err_file_notfound db "Error: file not found", 0 err_goto_notlabel db "Error: GOTO or GOSUB not followed by label", 0 err_label_notfound db "Error: label not found", 0 err_nest_limit db "Error: FOR or GOSUB nest limit exceeded", 0 err_next db "Error: NEXT without FOR", 0 err_loop db "Error: LOOP without DO", 0 err_print_type db "Error: PRINT not followed by quoted text or variable", 0 err_quote_term db "Error: quoted string or char not terminated correctly", 0 err_return db "Error: RETURN without GOSUB", 0 err_string_range db "Error: string location out of range", 0 err_syntax db "Error: syntax error", 0 err_break db "BREAK CALLED", 0 line_num_starter db " - line ", 0 ; ================================================================== ; DATA SECTION orig_stack dw 0 ; Original stack location when BASIC started prog dw 0 ; Pointer to current location in BASIC code prog_end dw 0 ; Pointer to final byte of BASIC code load_point dw 0 token_type db 0 ; Type of last token read (eg NUMBER, VARIABLE) token times 255 db 0 ; Storage space for the token vars_loc: variables times 26 dw 0 ; Storage space for variables A to Z for_variables times 26 dw 0 ; Storage for FOR loops for_code_points times 26 dw 0 ; Storage for code positions where FOR loops start do_loop_store times 10 dw 0 ; Storage for DO loops loop_in db 0 ; Loop level last_if_true db 1 ; Checking for 'ELSE' ink_colour db 0 ; Text printing colour work_page db 0 ; Page to print to disp_page db 0 ; Page to display alert_cmd db "ALERT", 0 askfile_cmd db "ASKFILE", 0 break_cmd db "BREAK", 0 call_cmd db "CALL", 0 case_cmd db "CASE", 0 cls_cmd db "CLS", 0 cursor_cmd db "CURSOR", 0 curschar_cmd db "CURSCHAR", 0 curscol_cmd db "CURSCOL", 0 curspos_cmd db "CURSPOS", 0 delete_cmd db "DELETE", 0 do_cmd db "DO", 0 else_cmd db "ELSE", 0 end_cmd db "END", 0 files_cmd db "FILES", 0 for_cmd db "FOR", 0 gosub_cmd db "GOSUB", 0 goto_cmd db "GOTO", 0 getkey_cmd db "GETKEY", 0 if_cmd db "IF", 0 include_cmd db "INCLUDE", 0 ink_cmd db "INK", 0 input_cmd db "INPUT", 0 len_cmd db "LEN", 0 listbox_cmd db "LISTBOX", 0 load_cmd db "LOAD", 0 loop_cmd db "LOOP", 0 move_cmd db "MOVE", 0 next_cmd db "NEXT", 0 number_cmd db "NUMBER", 0 page_cmd db "PAGE", 0 pause_cmd db "PAUSE", 0 peek_cmd db "PEEK", 0 peekint_cmd db "PEEKINT", 0 poke_cmd db "POKE", 0 pokeint_cmd db "POKEINT", 0 port_cmd db "PORT", 0 print_cmd db "PRINT", 0 rand_cmd db "RAND", 0 read_cmd db "READ", 0 rem_cmd db "REM", 0 rename_cmd db "RENAME", 0 return_cmd db "RETURN", 0 save_cmd db "SAVE", 0 serial_cmd db "SERIAL", 0 size_cmd db "SIZE", 0 sound_cmd db "SOUND", 0 string_cmd db "STRING", 0 waitkey_cmd db "WAITKEY", 0 and_keyword db "AND", 0 then_keyword db "THEN", 0 chr_keyword db "CHR", 0 hex_keyword db "HEX", 0 lower_keyword db "LOWER", 0 upper_keyword db "UPPER", 0 ink_keyword db "INK", 0 progstart_keyword db "PROGSTART", 0 ramstart_keyword db "RAMSTART", 0 timer_keyword db "TIMER", 0 variables_keyword db "VARIABLES", 0 version_keyword db "VERSION", 0 gosub_depth db 0 gosub_points times 10 dw 0 ; Points in code to RETURN to string_vars times 1024 db 0 ; 8 * 128 byte strings ; ------------------------------------------------------------------