diff --git a/.gitignore b/.gitignore index 6048758..a9932ad 100755 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ *.swp disk_images/* tmp-loop/* +detailed.log diff --git a/README.md b/README.md index 2ca1dbb..644c23e 100755 --- a/README.md +++ b/README.md @@ -1,2 +1,3 @@ Kernel written in Assmebly
I'm using a modified JazzOS Bootloader
+And the MikeOS BASIC Interpreter
diff --git a/data/hello.bas b/data/hello.bas new file mode 100644 index 0000000..2632cc6 --- /dev/null +++ b/data/hello.bas @@ -0,0 +1,2 @@ +PRINT "Hello World!" +END diff --git a/data/hello.cws b/data/hello.cws deleted file mode 100644 index 05cc6aa..0000000 --- a/data/hello.cws +++ /dev/null @@ -1 +0,0 @@ -output 'Hello World' diff --git a/log.txt b/log.txt new file mode 100644 index 0000000..e69de29 diff --git a/source/bootload/boot.asm b/source/bootload/boot.asm index 0ecb953..bf5b334 100644 --- a/source/bootload/boot.asm +++ b/source/bootload/boot.asm @@ -10,7 +10,7 @@ bdb_bytes_per_sector: dw 200h ; = 512d bdb_sectors_per_cluster: db 01h ; sector = cluster bdb_reserved_sectors: dw 01h bdb_fat_count: db 02h ; We've got a fat1 and fat2 -bdb_dir_entries_count: dw 0E0h ; Maximum number of root directory entries +bdb_dir_entries_count: dw 0E0h ; = 224d Maximum number of root directory entries bdb_total_sectors: dw 0B40h ; = 2880d bdb_media_descriptor_type: db 0F0h ; ignore bdb_sectors_per_fat: dw 09h @@ -68,7 +68,7 @@ main: shl ax,5 ; ax *= 32 (shifting 5 times) xor dx,dx ; Clear dx (remainder) div word [bdb_bytes_per_sector] ;(32*num of entries)/bytes per sector = total number of sectors we need to read - test dx,dx ; See if there's a remainder + test dx,dx ; See if there's a remainder, ie the root directory doesn't take up a whole number of sectors jz root_dir_after inc ax ; Add one if there's a remainder (this is like rounding up) diff --git a/source/kernel/data.asm b/source/kernel/data.asm index 93b3422..6fe51cd 100644 --- a/source/kernel/data.asm +++ b/source/kernel/data.asm @@ -22,20 +22,23 @@ ebr_volume_label: db 'CRAWOS0.0.6' ; must be 11 bytes ebr_system_id: db 'FAT12 ' ; must be 8 bytes +; String operations fat12_file_name: db ' ' ; 11 free bytes to store a filename in -boot_message: db 'OK] Kernel successfully loaded!\n\n', 0 +boot_message: db 'OK] Kernel successfully loaded!\n"HELP" to see a list of available commands\n', 0 user_input: times 20 db 0 prompt_length: db 20 prompt: db 'sh > ', 0 help_string: db 'HELP', 0 clear_string: db 'CLEAR', 0 reboot_string: db 'REBOOT', 0 -pong_string: db 'PONG', 0 +basic_string: db 'BAS', 0 cat_string: db 'CAT', 0 ls_string: db 'LS', 0 -help_text: db 'This is for Cowards:\n"LS" to list the directory,\n"CAT" to output the contents of a file,\n"HELP" for this helpful text,\n"CLEAR" to clear the screen,\n"REBOOT" or esc to reboot\n', 0 +help_text: db 'This is for Cowards:\n"LS" to list the directory,\n"CAT" to output the contents of a file,\n"BAS" to run a basic script,\n"HELP" for this helpful text,\n"CLEAR" to clear the screen,\n"REBOOT" or esc to reboot\n', 0 +basic_text: db 'BASIC PROGRAM BEGUN:\n', 0 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 ; Disk operations disk_read_fail: db 'Error: Could not read disk\n', 0 diff --git a/source/kernel/features/basic.asm b/source/kernel/features/basic.asm index e69de29..10d34cf 100644 --- a/source/kernel/features/basic.asm +++ b/source/kernel/features/basic.asm @@ -0,0 +1,4349 @@ +; ================================================================== +; 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 index 9b02459..cc72c60 100755 --- a/source/kernel/features/cli.asm +++ b/source/kernel/features/cli.asm @@ -18,7 +18,7 @@ os_start_cli: os_read_cli: pusha mov si, user_input - call os_upper_case ; Make the input uppercase so it's case insensitive + call string_upper_case ; Make the input uppercase so it's case insensitive .output_the_user_input: call os_print_newline @@ -45,6 +45,13 @@ os_read_cli: cmp cl, 1 je power_reboot + ; Basic + mov si, user_input + mov di, basic_string + call os_compare_strings + cmp cl, 1 + je basic + ; Cat mov si, user_input mov di, cat_string @@ -59,13 +66,6 @@ os_read_cli: cmp cl, 1 je ls - ; Pong - mov si, user_input - mov di, pong_string - call os_compare_strings - cmp cl, 1 - je pong - jmp .unkown .unkown: @@ -88,10 +88,9 @@ help: call os_print_string call os_read_cli.finish -pong: - call game_pong +basic: + call util_basic call os_read_cli.finish - cat: call util_cat call os_read_cli.finish diff --git a/source/kernel/features/disk.asm b/source/kernel/features/disk.asm index e9a4327..d0e892b 100644 --- a/source/kernel/features/disk.asm +++ b/source/kernel/features/disk.asm @@ -173,7 +173,7 @@ disk_clear_output_buffer: ; TODO use predefined data for calculations disk_load_file: pusha - call os_string_length ; cl = string length + call string_length ; cl = string length cmp cl, 11 ja .filename_too_long @@ -244,7 +244,7 @@ disk_load_file: disk_write_file: pusha ; Check if file name is too long - call os_string_length ; cl = string length + call string_length ; cl = string length cmp cl, 11 ja .filename_too_long ; Convert file name to a fat filename @@ -302,6 +302,7 @@ disk_write_file: ret ; TODO support long file names +; TODO don't work twice ; Store a list of the files in file_buffer ; in a human readable format ; OUT @@ -374,3 +375,14 @@ disk_list_contents: stosb popa ret + +disk_file_exists: + ret +disk_file_size: + ret +disk_file_list: + ret +disk_remove_file: + ret +disk_rename_file: + ret diff --git a/source/kernel/features/graphics.asm b/source/kernel/features/graphics.asm index e15a90c..766a68b 100644 --- a/source/kernel/features/graphics.asm +++ b/source/kernel/features/graphics.asm @@ -51,3 +51,10 @@ section .data y_end dw 0 colour db 1111b +graphics_dialogue_box: + ret + +graphics_file_selector: + ret +graphics_list_dialogue: + ret diff --git a/source/kernel/features/keyboard.asm b/source/kernel/features/keyboard.asm index 4a2733d..330d19e 100644 --- a/source/kernel/features/keyboard.asm +++ b/source/kernel/features/keyboard.asm @@ -7,10 +7,10 @@ os_read_input: hlt jmp os_read_input -.key_pressed: - mov ah, 10h - int 16h - ret + .key_pressed: + mov ah, 10h + int 16h + ret ; ------------------------------------------- @@ -18,60 +18,73 @@ os_display_input: pusha mov cx, [prompt_length] -.loop: - call .check_key_pressed + .loop: + call .check_key_pressed - jmp .loop + jmp .loop -.check_key_pressed: - call os_read_input + .check_key_pressed: + call os_read_input - cmp al, 08h - je .backspace + cmp al, 08h + je .backspace - cmp al, 0Dh - je .enter_key + cmp al, 0Dh + je .enter_key - cmp al, 1Bh - je .esc_key + cmp al, 1Bh + je .esc_key - cmp cx, 0 - jb .check_key_pressed + cmp cx, 0 + jb .check_key_pressed - call .print_current_input - dec cx - jmp .check_key_pressed + call .print_current_input + dec cx + jmp .check_key_pressed -.esc_key: - call power_reboot + .esc_key: + call power_reboot -.enter_key: - mov al, 0 - stosb - popa - call os_read_cli + .enter_key: + mov al, 0 + stosb + popa + call os_read_cli -.backspace: - call .move_cursor_back ; then .move_cursor_back - call .loop ; Else .loop + .backspace: + call .move_cursor_back ; then .move_cursor_back + call .loop ; Else .loop -.move_cursor_back: - mov ah, 0Eh + .move_cursor_back: + mov ah, 0Eh - mov al, 08h - int 10h - mov al, 20h - int 10h - mov al, 08h - int 10h + mov al, 08h + int 10h + mov al, 20h + int 10h + mov al, 08h + int 10h - dec di - jmp os_display_input + dec di + jmp os_display_input -.print_current_input: - stosb + .print_current_input: + stosb - mov ah, 0Eh - int 10h + mov ah, 0Eh + int 10h + ret + +keyboard_get_cursor_pos: ;TODO + ret +keyboard_wait_for_key: ;TODO + ret +keyboard_show_cursor: + ret +keyboard_hide_cursor: + ret +keyboard_move_cursor: + ret +keyboard_check_key: ret diff --git a/source/kernel/features/math.asm b/source/kernel/features/math.asm index b98cb25..1a03e3c 100644 --- a/source/kernel/features/math.asm +++ b/source/kernel/features/math.asm @@ -52,3 +52,5 @@ math_lba_to_chs: pop ax ret +math_get_random: + ret diff --git a/source/kernel/features/misc.asm b/source/kernel/features/misc.asm new file mode 100644 index 0000000..3c49f31 --- /dev/null +++ b/source/kernel/features/misc.asm @@ -0,0 +1,4 @@ +misc_pause: + ret +misc_get_api_version: + ret diff --git a/source/kernel/features/ports.asm b/source/kernel/features/ports.asm new file mode 100644 index 0000000..d187c7a --- /dev/null +++ b/source/kernel/features/ports.asm @@ -0,0 +1,10 @@ +port_byte_out: + ret +port_byte_in: + ret +port_serial_enable: + ret +port_send_via_serial: + ret +port_get_via_serial: + ret diff --git a/source/kernel/features/sound.asm b/source/kernel/features/sound.asm index e69de29..0f967b5 100644 --- a/source/kernel/features/sound.asm +++ b/source/kernel/features/sound.asm @@ -0,0 +1,4 @@ +sound_speaker_tone: + ret +sound_speaker_off: + ret diff --git a/source/kernel/features/strings.asm b/source/kernel/features/strings.asm index 4f16f7d..3cb4878 100755 --- a/source/kernel/features/strings.asm +++ b/source/kernel/features/strings.asm @@ -11,35 +11,176 @@ os_compare_strings: cld -.compare: - mov al, 0 - scasb - je .di_ended - dec di - lodsb - scasb ; Compare di to si - jne .unequal ; If they are not equal, jump to .unequal - jmp .compare ; Finally, repeat + .compare: + mov al, 0 + scasb + je .di_ended + dec di + lodsb + scasb ; Compare di to si + jne .unequal ; If they are not equal, jump to .unequal + jmp .compare ; Finally, repeat -.unequal: - mov cl, 0 ; Change to 0 if unquality is proven - ret -.di_ended: - lodsb - cmp al, 20h ; 20h = space - je .equal - cmp al, 0 - je .equal - jmp .unequal -.equal: - mov cl, 1 + .unequal: + mov cl, 0 ; Change to 0 if unquality is proven + ret + .di_ended: + lodsb + cmp al, 20h ; 20h = space + je .equal + cmp al, 0 + je .equal + jmp .unequal + .equal: + mov cl, 1 + ret + +; ------------------------------------------------------------------ +; os_string_compare -- See if two strings match +; IN: SI = string one, DI = string two +; OUT: carry set if same, clear if different +; from MikeOS kernel + +string_direct_compare: + pusha + .more: + mov al, [si] ; Retrieve string contents + mov bl, [di] + cmp al, bl ; Compare characters at current location + jne .not_same + 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 + ret + .end: ; Both strings terminated at the same position + popa + stc ; Set carry flag + ret + + +; -------------------------------------------------------------------- +; Copies a string from si to di +; IN: +; si: points to first character of source string +; OUT: +; di: points to first character of destination string +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 + cmp ax, 0 ; if ax is a 0, quit + jne .loop + .done: + popa + ret + +; -------------------------------------------------------------------- +; Joins two strings together into a new string +; IN/OUT: +; ax: string1 +; bx: string2 +; cx: result string + +string_join: + pusha + .copy_string1: + mov si, ax + mov di, cx + call string_copy + mov ax, cx ; TODO check this bit + call string_length + add cx, ax + .copy_string2: + mov si, bx + mov di, dx + call string_copy + .done: + popa + ret + +; ------------------------------------------------------------------------ +; TODO +string_cast_to_int: ret +; ------------------------------------------------------------------------ + +; IN: AX = int +; OUT: AX = string location (output buffer) +string_cast_from_int: ; TODO I think this algorithm could be optimised + 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 + 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 + 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: + popa + ;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 -os_string_length: +string_length: push si xor cl,cl ; Clear the al register .loop: @@ -52,6 +193,8 @@ os_string_length: pop si ret + + ; convert a string to fat's filename format ; It will be capitalised and 11 characters long, ; 8 for the filename, 3 for the extension @@ -60,8 +203,8 @@ os_string_length: ; output: di points to the fat formatted filename os_format_fat_filename: pusha - call os_upper_case - call os_string_length ; Stores the length of the string in cl + 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: lodsb @@ -147,7 +290,7 @@ string_unformat_fat_filename: ; Convert a string to all upper/lower case ; INPUT: si pointing to a string ; OUPUT: the same string in memory will now be capitalised/decapitalised -os_upper_case: ; to upper case +string_upper_case: ; to upper case pusha mov di, si .loop: @@ -187,3 +330,9 @@ os_lower_case: ; to lower case popa ret +string_lower_case: + ret +string_input: + ret +string_print_2hex: + ret diff --git a/source/kernel/features/utils.asm b/source/kernel/features/utils.asm index 7bdd263..f11b14a 100644 --- a/source/kernel/features/utils.asm +++ b/source/kernel/features/utils.asm @@ -17,12 +17,30 @@ util_cat: util_ls: pusha - call disk_clear_output_buffer + call disk_load_root call disk_list_contents mov si, output_buffer call os_print_string + call disk_clear_output_buffer popa ret +util_basic: + pusha + + call disk_load_root + + ; TODO make this more consistent to account for double spaces + ;add si, 3 ; Move si to after 'BAS' + call disk_load_file + + mov si, file_buffer + + mov ax, si + mov si, 0 + call basic_run_basic + + popa + ret diff --git a/source/kernel/kernel.asm b/source/kernel/kernel.asm index 6700bd0..da309db 100644 --- a/source/kernel/kernel.asm +++ b/source/kernel/kernel.asm @@ -10,8 +10,12 @@ start: mov si, boot_message call os_print_string - mov si, help_text - call os_print_string + + ; TESTING + ;mov ax, 3 + ;call string_cast_from_int + ;mov si, stringified_int + ;call os_print_string call os_start_cli hlt @@ -24,16 +28,18 @@ halt: ; FEATURES -- Code to pull into the kernel %INCLUDE "source/kernel/features/text.asm" %INCLUDE "source/kernel/features/keyboard.asm" - %INCLUDE "source/kernel/features/cli.asm" + %INCLUDE "source/kernel/features/ports.asm" %INCLUDE "source/kernel/features/power.asm" %INCLUDE "source/kernel/features/strings.asm" %INCLUDE "source/kernel/features/graphics.asm" + %INCLUDE "source/kernel/features/sound.asm" %INCLUDE "source/kernel/features/disk.asm" %INCLUDE "source/kernel/features/math.asm" %INCLUDE "source/kernel/features/time.asm" %INCLUDE "source/kernel/features/utils.asm" -; GAMES -- Games that I wrote for it - %INCLUDE "source/kernel/games/pong.asm" + %INCLUDE "source/kernel/features/cli.asm" + %INCLUDE "source/kernel/features/misc.asm" + %INCLUDE "source/kernel/features/basic.asm" ; DATA/VARIABLES %INCLUDE "source/kernel/data.asm" diff --git a/source/kernel/programs/ed.asm b/source/kernel/programs/ed.asm new file mode 100644 index 0000000..e69de29 diff --git a/test-linux.sh b/test-linux.sh index af9b2fa..1d9e3fa 100755 --- a/test-linux.sh +++ b/test-linux.sh @@ -5,5 +5,7 @@ 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 -machine memory-backend=pc.ram\ - $1 $2 + -object memory-backend-file,id=pc.ram,size=512m,mem-path=/dev/shm/qemu-ram,share=on\ + -machine memory-backend=pc.ram\ + -d in_asm,int -D ./detailed.log\ + $1 $2 diff --git a/to_implement b/to_implement new file mode 100644 index 0000000..e69de29