From e4fc63134fb92d057e80294efc731bf09ff13cfb Mon Sep 17 00:00:00 2001 From: deadvey Date: Mon, 9 Feb 2026 20:41:49 +0000 Subject: [PATCH] BASIC interpreter --- .gitignore | 0 LICENSE | 0 README.md | 0 build-linux.sh | 0 data/README.md | 0 data/hello.bas | 4 +- source/kernel/data.asm | 2 + source/kernel/features/basic.asm | 8697 ++++++++++++++-------------- source/kernel/features/cli.asm | 2 +- source/kernel/features/disk.asm | 46 +- source/kernel/features/power.asm | 0 source/kernel/features/strings.asm | 259 +- source/kernel/features/text.asm | 0 source/kernel/features/utils.asm | 1 - source/kernel/kernel.asm | 10 +- test-linux.sh | 4 +- 16 files changed, 4501 insertions(+), 4524 deletions(-) mode change 100755 => 100644 .gitignore mode change 100755 => 100644 LICENSE mode change 100755 => 100644 README.md mode change 100755 => 100644 build-linux.sh mode change 100755 => 100644 data/README.md mode change 100755 => 100644 source/kernel/features/cli.asm mode change 100755 => 100644 source/kernel/features/power.asm mode change 100755 => 100644 source/kernel/features/strings.asm mode change 100755 => 100644 source/kernel/features/text.asm diff --git a/.gitignore b/.gitignore old mode 100755 new mode 100644 diff --git a/LICENSE b/LICENSE old mode 100755 new mode 100644 diff --git a/README.md b/README.md old mode 100755 new mode 100644 diff --git a/build-linux.sh b/build-linux.sh old mode 100755 new mode 100644 diff --git a/data/README.md b/data/README.md old mode 100755 new mode 100644 diff --git a/data/hello.bas b/data/hello.bas index 2632cc6..fe8118b 100644 --- a/data/hello.bas +++ b/data/hello.bas @@ -1,2 +1,4 @@ -PRINT "Hello World!" +FOR A = 1 TO 10 +PRINT A +NEXT A END diff --git a/source/kernel/data.asm b/source/kernel/data.asm index 6fe51cd..b74148c 100644 --- a/source/kernel/data.asm +++ b/source/kernel/data.asm @@ -40,6 +40,7 @@ command_result_text: db 'You typed: ', 0 unknown_command: db 'Error: Unkown Command..\n ', 0 stringified_int: db 0,0,0,0,0,0 ; Can store up to 6 digits +int_tmp: dw 0 ; Disk operations disk_read_fail: db 'Error: Could not read disk\n', 0 file_not_found: db 'File not found\n', 0 @@ -59,3 +60,4 @@ read_write_flag: db 02 empty_byte: db 0 empty_word: dw 0 empty_dword: dd 0 +tmp: db '97', 0 diff --git a/source/kernel/features/basic.asm b/source/kernel/features/basic.asm index 10d34cf..e7fd7f0 100644 --- a/source/kernel/features/basic.asm +++ b/source/kernel/features/basic.asm @@ -1,4349 +1,4348 @@ -; ================================================================== -; 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 string_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 string_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 - - -; ------------------------------------------------------------------ - +; ================================================================== +; 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 string_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 string_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 + + +; ------------------------------------------------------------------ + diff --git a/source/kernel/features/cli.asm b/source/kernel/features/cli.asm old mode 100755 new mode 100644 index cc72c60..d514a6a --- a/source/kernel/features/cli.asm +++ b/source/kernel/features/cli.asm @@ -17,7 +17,7 @@ os_start_cli: os_read_cli: pusha - mov si, user_input + mov ax, user_input call string_upper_case ; Make the input uppercase so it's case insensitive .output_the_user_input: diff --git a/source/kernel/features/disk.asm b/source/kernel/features/disk.asm index d0e892b..074dc74 100644 --- a/source/kernel/features/disk.asm +++ b/source/kernel/features/disk.asm @@ -308,6 +308,7 @@ disk_write_file: ; OUT ; output_buffer: the list (string) disk_list_contents: + call disk_load_root pusha mov si, root_buffer mov di, output_buffer @@ -322,38 +323,22 @@ disk_list_contents: dec cx cmp cx, 0 jne .space_loop - - .file_attribute: - add si, 11d + mov ax, [si+28d] + call string_cast_from_int + push si + mov si, stringified_int + lodsb + .output_file_size_loop: + stosb lodsb - push si - - mov si, read_only - cmp al, 1h - je .add_string - mov si, hidden - cmp al, 2h - je .add_string - mov si, system - cmp al, 4h - je .add_string - mov si, volume - cmp al, 8h - je .add_string - mov si, directory - cmp al, 10h - je .add_string - mov si, archive - cmp al, 20h - je .add_string - - .after_file_attributes: + cmp al, 0 + jne .output_file_size_loop + .after_fs_loop: pop si - mov al, 0Ah stosb - add si, 20d + add si, 20h lodsb ; +1 dec si ; -1 cmp al, 0 ; You've come to the end of the root entries @@ -361,13 +346,6 @@ disk_list_contents: cmp al, 00E5h ; E5h is a marker that the Fat entry is available je .finish jmp .loop - .add_string: - .add_string_loop: - lodsb - cmp ax, 00h - je .after_file_attributes - stosb - jmp .add_string_loop .finish: mov cx, output_buffer sub di, cx diff --git a/source/kernel/features/power.asm b/source/kernel/features/power.asm old mode 100755 new mode 100644 diff --git a/source/kernel/features/strings.asm b/source/kernel/features/strings.asm old mode 100755 new mode 100644 index 3cb4878..ef77143 --- a/source/kernel/features/strings.asm +++ b/source/kernel/features/strings.asm @@ -1,11 +1,11 @@ ; How string comparison works -; DI => scasb compares value stored in DI which is 's' with 's' stored in AX register -; and then increments DI if DF is 0 -; v -; memory address 1: |s|n|a|t|0| -; memory address 2: |s|n|a|k|e|0| -; ^ -; SI => lodsb loads value stored at SI to AX and then increments SI if DF is 0 +; DI => scasb compares value stored in DI which is 's' with 's' stored in AX register +; and then increments DI if DF is 0 +; v +; memory address 1: |s|n|a|t|0| +; memory address 2: |s|n|a|k|e|0| +; ^ +; SI => lodsb loads value stored at SI to AX and then increments SI if DF is 0 ; Additionaly, if the di string ends and the relevant character in si contains a space, it will still return ; the strings as being equal, this is to allow for command line arquments os_compare_strings: @@ -44,22 +44,22 @@ os_compare_strings: string_direct_compare: pusha .more: - mov al, [si] ; Retrieve string contents + mov al, [si] ; Retrieve string contents mov bl, [di] - cmp al, bl ; Compare characters at current location + cmp al, bl ; Compare characters at current location jne .not_same - cmp al, 0 ; End of first string? Must also be end of second + cmp al, 0 ; End of first string? Must also be end of second je .end inc si inc di jmp .more - .not_same: ; If unequal lengths with same beginning, the byte - popa ; comparison fails at shortest string terminator - clc ; Clear carry flag + .not_same: ; If unequal lengths with same beginning, the byte + popa ; comparison fails at shortest string terminator + clc ; Clear carry flag ret - .end: ; Both strings terminated at the same position + .end: ; Both strings terminated at the same position popa - stc ; Set carry flag + stc ; Set carry flag ret @@ -73,8 +73,8 @@ string_copy: pusha .loop: ; TODO could this cause issue when a character is > 1 byte? - lodsb ; Load si character into ax and increment si by 1 - stosb ; Store ax in di location and increment di by 1 + lodsb ; Load si character into ax and increment si by 1 + stosb ; Store ax in di location and increment di by 1 cmp ax, 0 ; if ax is a 0, quit jne .loop .done: @@ -107,89 +107,84 @@ string_join: ; ------------------------------------------------------------------------ ; TODO +; Converts a null terminated string to an integer +; IN: SI = string location (max 5 chars, up to '65536') +; OUT: AX = number string_cast_to_int: - ret + pusha + xor cx,cx + .loop: + xor ax,ax + lodsb + cmp al, 0 + je .finish + sub al, 30h + mov bx, ax + mov ax, 10 + mul cx ; Multiple the current value by 10 + add ax, bx ; Then add the new digit + mov cx, ax + jmp .loop + .finish: + mov [int_tmp], cx + popa + mov ax, [int_tmp] + ret ; ------------------------------------------------------------------------ +; IN: AX = integer (unsigned) +; OUT: AX -> null-terminated string -; IN: AX = int -; OUT: AX = string location (output buffer) -string_cast_from_int: ; TODO I think this algorithm could be optimised +string_cast_from_int: pusha - ; Initialise base to decimal (base 10) - mov bx, 10 - ; clear the strigified int location - mov cx, 6 - push ax - xor ax,ax - .clear_loop: - stosb - dec cx - cmp cx, 0 - ja .clear_loop - ; setup - pop ax - push ax - mov cx, -1 - ; loop over number to get the value of count (cx) - .count_loop: - xor dx,dx - div bx ; ax = ax // bx - inc cx - cmp ax, 0 - ja .count_loop ; Jump if greater - pop ax - ; loop over count downwards until count is 0 + cld ; Write backwards mov di, stringified_int - .stringify_loop: - mov cx,1 ;DEL - push cx - push bx - push ax - ; Generate power of 10 - mov ax, 1 - mov cx,0 ;DEL - cmp cx, 1 - jl .after_power_loop - .power_loop: - mul bx - dec cx - cmp cx, 0 - ja .power_loop - .after_power_loop: - mov bx,ax + + mov bx, 10 ; base + mov cx, 0 ; digit count + + .convert_loop: + xor dx, dx + div bx ; AX = AX / base, DX = remainder + add dl, '0' + push dx ; store digit + inc cx + test ax, ax + jnz .convert_loop + + .write_loop: pop ax - pop cx - ; ax = ax DIV bx - ; dx = ax MOD bx - xor dx,dx - div bx - pop bx - add ax, 30h stosb - mov ax, dx - dec cx - cmp cx, 0 - ja .stringify_loop - .finish: + loop .write_loop + + mov al, 0 + stosb ; null terminator + popa - ;mov ax, stringified_int + mov ax, stringified_int ret +;---------------------------------------------------------------------------- + ; Get the length of a string ; 'hello world', 0 is 11 characters long (excluding the terminator) -; input: si points to the string to be counted -; output: cl holds the length +; input: ax points to the string to be counted +; output: ax holds the length string_length: push si - xor cl,cl ; Clear the al register + push cx + mov si, ax + xor ax,ax ; Clear the cx register + xor cx,cx .loop: lodsb cmp al, 0 je .finish - inc cl + inc cx jmp .loop .finish: + mov ax, cx + pop cx pop si ret @@ -198,15 +193,17 @@ string_length: ; convert a string to fat's filename format ; It will be capitalised and 11 characters long, ; 8 for the filename, 3 for the extension -; eg: 'file.txt' -> 'FILE TXT' +; eg: 'file.txt' -> 'FILE TXT' ; input: si points to filename, di points to a free 11 bytes in memory ; output: di points to the fat formatted filename os_format_fat_filename: - pusha - call string_upper_case - call string_length ; Stores the length of the string in cl - xor ch,ch ; Clear ch to reset it to 0 - .character_loop: + pusha + call string_upper_case + mov ax, si + call string_length ; Stores the length of the string in cl + mov cl, al + xor ch,ch ; Clear ch to reset it to 0 + .character_loop: lodsb cmp al, 0 je .finish @@ -215,10 +212,10 @@ os_format_fat_filename: stosb inc ch jmp .character_loop - - .add_spaces: ; Add the number of spaces as bl holds + + .add_spaces: ; Add the number of spaces as bl holds mov al, ' ' ; 20h = space - + ; Work out the number of spaces in between ; the name and extension. ; 8 - name_length(ch) @@ -231,9 +228,9 @@ os_format_fat_filename: je .character_loop dec bl jmp .spaces_loop - .finish: - popa - ret + .finish: + popa + ret ; Does the inverse of the previous ; Converts a fat filename back to human readable @@ -287,51 +284,53 @@ string_unformat_fat_filename: popa ret +; ------------------------------------------------------------------------------------- + ; Convert a string to all upper/lower case -; INPUT: si pointing to a string +; INPUT: ax pointing to a string ; OUPUT: the same string in memory will now be capitalised/decapitalised string_upper_case: ; to upper case - pusha - mov di, si + pusha + mov si,ax + mov di,si .loop: - lodsb ; Load the character into al - inc di - cmp al, 0 - je .finish ; If it's null then the string is finished - cmp al, 7Ah ; 7Ah = 'z' - jns .loop ; Ignore if it's more than 'z' - cmp al, 61h ; 61h = 'a' - js .loop ; Ignore if it's less than 'a' - sub al, 20h ; Otherwise subtract 20h to capitalise it - dec di - stosb ; Store the new value - jmp .loop ; Next character + lodsb ; Load the character into al + inc di + cmp al, 0 + je .finish ; If it's null then the string is finished + cmp al, 7Ah ; 7Ah = 'z' + jns .loop ; Ignore if it's more than 'z' + cmp al, 61h ; 61h = 'a' + js .loop ; Ignore if it's less than 'a' + sub al, 20h ; Otherwise subtract 20h to capitalise it + dec di + stosb ; Store the new value + jmp .loop ; Next character .finish: - popa - ret - -os_lower_case: ; to lower case - pusha - mov di, si -.loop: - lodsb ; Load the character into al - inc di - cmp al, 0 - je .finish ; If it's null then the string is finished - cmp al, 5Ah ; 5Ah = 'Z' - jns .loop ; Ignore if it's more than 'Z' - cmp al, 41h ; 41h = 'A' - js .loop ; Ignore if it's less than 'A' - add al, 20h ; Otherwise subtract 20h to capitalise it - dec di - stosb ; Store the new value - jmp .loop ; Next character -.finish: - popa - ret - -string_lower_case: + popa ret + +string_lower_case: ; to lower case + pusha + mov si, ax + mov di, si +.loop: + lodsb ; Load the character into al + inc di + cmp al, 0 + je .finish ; If it's null then the string is finished + cmp al, 5Ah ; 5Ah = 'Z' + jns .loop ; Ignore if it's more than 'Z' + cmp al, 41h ; 41h = 'A' + js .loop ; Ignore if it's less than 'A' + add al, 20h ; Otherwise subtract 20h to capitalise it + dec di + stosb ; Store the new value + jmp .loop ; Next character +.finish: + popa + ret + string_input: ret string_print_2hex: diff --git a/source/kernel/features/text.asm b/source/kernel/features/text.asm old mode 100755 new mode 100644 diff --git a/source/kernel/features/utils.asm b/source/kernel/features/utils.asm index f11b14a..347eaec 100644 --- a/source/kernel/features/utils.asm +++ b/source/kernel/features/utils.asm @@ -17,7 +17,6 @@ util_cat: util_ls: pusha - call disk_load_root call disk_list_contents mov si, output_buffer diff --git a/source/kernel/kernel.asm b/source/kernel/kernel.asm index da309db..fa7c67c 100644 --- a/source/kernel/kernel.asm +++ b/source/kernel/kernel.asm @@ -10,12 +10,10 @@ start: mov si, boot_message call os_print_string - - ; TESTING - ;mov ax, 3 - ;call string_cast_from_int - ;mov si, stringified_int - ;call os_print_string + + mov si, tmp + mov ah, 0Eh + int 10h call os_start_cli hlt diff --git a/test-linux.sh b/test-linux.sh index 1d9e3fa..93aa5dc 100755 --- a/test-linux.sh +++ b/test-linux.sh @@ -4,8 +4,8 @@ sudo make sudo chown $(whoami) disk_images/* qemu-system-i386\ -drive file=disk_images/crawos.img,if=floppy,format=raw\ - -m 512m\ - -object memory-backend-file,id=pc.ram,size=512m,mem-path=/dev/shm/qemu-ram,share=on\ + -m 8m\ + -object memory-backend-file,id=pc.ram,size=8m,mem-path=/dev/shm/qemu-ram,share=on\ -machine memory-backend=pc.ram\ -d in_asm,int -D ./detailed.log\ $1 $2