; ----------------------------------------------------------------------------- ; Compilation Instructions (NASM) ; To compile this programme into a raw binary boot sector, use the following command: ; nasm -f bin dePaz.asm -o dePaz.img ; .img can be loaded as a floppy disk in VirtualBox ; ----------------------------------------------------------------------------- org 0x7C00 ; Set the origin address for a standard BIOS boot sector %define BUFFER_SIZE 120 ; Define the maximum size for the input buffer %define var_abc 0x7E00 ; Memory address to store lowercase variables (a-z) %define buffer 0x7E34 ; Memory address for the input buffer %define var_ABC 0x7F00 ; Memory address to store uppercase functions (A-Z) start: mov si, msg_welcome ; Load the welcome message pointer call print_string ; Print the welcome message to the screen eerror: mov sp, 0xffff ; Reset the stack pointer to ensure a clean state xor ax, ax ; Clear the AX register main_loop: mov si, buffer ; Point the Source Index to the start of the buffer add si, BUFFER_SIZE ; Move to the end of the buffer to begin clearing cleanbuff: dec si ; Move backwards through the buffer mov byte [si], 0 ; Nullify the current byte to clear previous input cmp si, buffer ; Have we reached the start of the buffer? jnz cleanbuff ; If not, continue the cleaning cycle mov si, buffer ; Reset SI to the start of the buffer for reading call read_line ; Await and read user input call print_newline ; Print a line break after user input mov si, di ; Copy the Destination Index (end of input) to SI dec si ; Step back past the null terminator dec si ; Step back to the last actual character typed lodsb ; Load the last character into AL cmp al, 'A' ; Check if the character is below 'A' jb continueToEval ; If it is, this is a standard expression to evaluate cmp al, 'Z' ; Check if the character is 'Z' or below jbe saveBuffer ; If it is an uppercase letter, jump to save it as a function continueToEval: mov si, buffer ; Point SI back to the start of the buffer call eval_expr ; Begin evaluating the expression call print_number ; Print the resulting numerical value call print_newline ; Print a line break for formatting jmp main_loop ; Loop back to prompt for the next command ; ----------------------------- saveBuffer: sub al, 'A' ; Convert the ASCII uppercase letter to a 0-25 index mov ah, 0 ; Clear AH to safely use AX for multiplication mov bx, BUFFER_SIZE ; Load the buffer size constraint mul bx ; Multiply index by buffer size to find memory offset mov di, var_ABC ; Point DI to the base address for function storage add di, ax ; Add the calculated offset for the specific letter mov si, buffer ; Point SI to the input buffer containing the code mov cx, BUFFER_SIZE ; Set counter to copy the entire buffer length .saveCycle: lodsb ; Load a byte from the input buffer stosb ; Store the byte into the function memory space loop .saveCycle ; Repeat until the buffer size is exhausted jmp continueToEval ; Proceed to evaluate the expression now that it is saved ; ----------------------------- skip_spaces: .skip: lodsb ; Load the next character cmp al, ' ' ; Check if it is a space je .skip ; If it is a space, keep skipping dec si ; Step back so the non-space character can be read again ret ; Return to caller skip_to_closing: call skip_spaces ; Advance past any whitespace .next: lodsb ; Load the next character cmp al, ')' ; Check if it is a closing parenthesis je .done ; If it is, we have reached the end of the block jmp .next ; If not, keep reading forward .done: ret ; Return to caller ; ----------------------------- eval_expr: call skip_spaces ; Ignore preceding whitespace lodsb ; Load the next token into AL cmp al, '(' ; Is it the start of a new expression? je .parse_expr ; If so, branch to parse the operator cmp al, '0' ; Is the character below '0'? jb eerror ; If so, it is invalid (not a number or letter) cmp al, '9' ; Is the character a digit ('0'-'9')? jbe .itsANumber ; If so, branch to integer parsing cmp al, 'A' ; Is the character below 'A'? jb eerror ; If so, it is an invalid symbol cmp al, 'Z' ; Is the character an uppercase letter? jbe .itsAFunction ; If so, branch to evaluate a stored function cmp al, 'a' ; Is the character below 'a'? jb eerror ; If so, it is an invalid symbol cmp al, 'z' ; Is the character a lowercase letter? jbe .itsALetter ; If so, branch to retrieve variable value jmp eerror ; If it matches none of the above, throw an error .itsANumber: dec si ; Step back to re-read the first digit xor bx, bx ; Initialise BX to 0 to accumulate the number .f11: lodsb ; Load the digit character sub al, '0' ; Convert from ASCII to integer value cmp al, 10 ; Check if the value is a valid digit (0-9) cbw ; Convert byte to word (sign-extend AL into AX) xchg ax, bx ; Swap accumulated value into AX, current digit into BX jnc .f12 ; If not a valid digit, carry flag is clear, end parsing mov cx, 10 ; Set multiplier to 10 (base 10 numbers) mul cx ; Multiply the accumulated total by 10 add bx, ax ; Add the new digit to the total jmp .f11 ; Loop to read the next potential digit .f12: dec si ; Step back to point SI to the first non-digit character ret ; Return with the fully parsed number in AX .itsAFunction: sub al, 'A' ; Convert uppercase letter to a 0-25 index mov ah, 0 ; Clear AH mov bx, BUFFER_SIZE ; Load the defined buffer size mul bx ; Calculate the memory offset for this function push si ; Preserve the current string pointer mov si, var_ABC ; Base address for stored functions add si, ax ; Add offset to point to the correct function code call eval_expr ; Recursively evaluate the stored function code pop si ; Restore the original string pointer ret ; Return the evaluated result in AX .itsALetter: sub al, 'a' ; Convert lowercase letter to a 0-25 index mov ah, 0 ; Clear AH mov bx, 2 ; Variables are stored as 2-byte words mul bx ; Calculate the memory offset for this variable push si ; Preserve the current string pointer mov si, var_abc ; Base address for stored variables add si, ax ; Add offset to point to the variable's value lodsw ; Load the 16-bit variable value into AX pop si ; Restore the original string pointer ret ; Return the variable's value ; ----------------------------- .parse_expr: call skip_spaces ; Ignore spaces following the opening parenthesis lodsb ; Load the operator character cmp al, '+' ; Is it the addition operator? je .add ; Branch to addition logic cmp al, '*' ; Is it the multiplication operator? je .mul ; Branch to multiplication logic cmp al, '-' ; Is it the subtraction operator? je .subtract ; Branch to subtraction logic cmp al, 'e' ; Is it the assignment operator ('e')? je .assign ; Branch to assignment logic cmp al, 'i' ; Is it the conditional operator ('i')? je .if_expr ; Branch to 'if' logic cmp al, 'w' ; Is it the loop operator ('w')? je .for_expr ; Branch to 'while' logic jmp eerror ; Unrecognised operator triggers an error .add: mov ax, 0 ; Initialise the accumulator for addition push ax ; Push the initial total onto the stack .cyclAdd: lodsb ; Look ahead at the next character cmp al, ')' ; Have we reached the end of the addition block? je .endAdd ; If so, finish addition cmp al, ' ' ; Is it a space? je .cyclAdd ; Ignore spaces and continue checking dec si ; Step back to allow eval_expr to read the token call eval_expr ; Evaluate the next argument pop bx ; Retrieve the running total from the stack add ax, bx ; Add the evaluated argument to the total push ax ; Push the new total back onto the stack jmp .cyclAdd ; Loop to process further arguments .endAdd: pop ax ; Retrieve the final calculated total into AX ret ; Return the result .mul: call eval_expr ; Evaluate the first operand push ax ; Store the first operand on the stack call eval_expr ; Evaluate the second operand pop bx ; Retrieve the first operand mul bx ; Multiply the operands (AX * BX) push ax ; Store the result to preserve it call skip_to_closing ; Consume any remaining tokens up to the closing parenthesis pop ax ; Retrieve the preserved result ret ; Return the result .subtract: call eval_expr ; Evaluate the first operand push ax ; Store the first operand on the stack call eval_expr ; Evaluate the second operand mov bx, ax ; Move the second operand into BX pop ax ; Retrieve the first operand into AX sub ax, bx ; Subtract the second operand from the first (AX - BX) push ax ; Store the result to preserve it call skip_to_closing ; Consume any remaining tokens pop ax ; Retrieve the preserved result ret ; Return the result .assign: call skip_spaces ; Ignore spaces before the variable name lodsb ; Load the variable name character cmp al, 'a' ; Check if it is below lowercase 'a' jb eerror ; Invalid variable name cmp al, 'z' ; Check if it is a valid lowercase letter jbe .assignLetter ; Proceed to variable assignment jmp eerror ; Throw an error if it falls outside the range .assignLetter: sub al, 'a' ; Convert letter to 0-25 index mov ah, 0 ; Clear AH mov bx, 2 ; Word size multiplier mul bx ; Calculate memory offset mov di, var_abc ; Base address for lowercase variables add di, ax ; Add offset to find the target memory location push di ; Preserve the target memory address call eval_expr ; Evaluate the expression to assign pop di ; Retrieve the target memory address mov [di], ax ; Store the evaluated result into the variable's memory push ax ; Preserve the evaluated result call skip_to_closing ; Consume remaining tokens until block closure pop ax ; Retrieve the result to pass back up the chain ret ; Return the assigned value .for_expr: push si ; Save the pointer at the start of the condition call eval_expr ; Evaluate the loop condition cmp ax, 0 ; Is the condition false (zero)? jz .Fskip_then ; If false, exit the loop logic call eval_expr ; If true, evaluate the body of the loop call skip_to_closing ; Advance to the end of the loop body pop si ; Restore pointer back to the condition jmp .for_expr ; Restart the loop cycle .Fskip_then: mov cx, 1 ; Set parenthesis depth counter to 1 call come_expr ; Consume unexecuted tokens to safely skip the loop body pop di ; Clean up the saved SI pointer from the stack mov ax, 0 ; Return 0 to signify loop completion ret ; Return to caller .if_expr: call eval_expr ; Evaluate the 'if' condition cmp ax, 0 ; Is the condition false (zero)? jz .skip_then ; If false, branch to skip the 'then' expression call eval_expr ; If true, evaluate the 'then' expression push ax ; Preserve the result of the 'then' expression call skip_to_closing ; Consume remaining tokens in the 'if' block pop ax ; Retrieve the result ret ; Return the result .skip_then: mov cx, 1 ; Set parenthesis depth counter to 1 call come_expr ; Safely consume the unexecuted 'then' expression mov ax, 0 ; Return 0 for a false conditional path ret ; Return to caller come_expr: lodsb ; Load the next character cmp al, '(' ; Is it an opening parenthesis? jz come_add ; Increase the depth counter cmp al, ')' ; Is it a closing parenthesis? jz come_sub ; Decrease the depth counter cmp al, 0 ; Have we hit a null terminator unexpectedly? jz eerror ; Throw an error if end of string is reached jmp come_expr ; Ignore other characters and loop come_add: add cx, 2 ; Increase depth counter (adjusted for logic flow) come_sub: dec cx ; Decrease depth counter cmp cx, 0 ; Are we back to the original nesting level? jz come_end ; If so, we have successfully skipped the expression jmp come_expr ; If not, continue consuming characters come_end: ret ; Return after skipping ; ----------------------------- print_number: mov bx, 10 ; Set divisor to 10 for base-10 extraction xor cx, cx ; Clear CX to act as a digit counter .next_digit: xor dx, dx ; Clear DX for division div bx ; Divide AX by 10 (remainder goes to DX) push dx ; Push the remainder (the current digit) onto the stack inc cx ; Increment the digit counter test ax, ax ; Check if the quotient in AX is 0 jnz .next_digit ; If not, loop to extract the next digit .print_loop: pop dx ; Retrieve the most significant digit from the stack add dl, '0' ; Convert the integer digit to an ASCII character mov ah, 0x0E ; BIOS teletype function mov al, dl ; Move the ASCII character to AL for printing int 0x10 ; Trigger BIOS video interrupt to print character loop .print_loop ; Loop until all digits in CX are printed ret ; Return to caller ; ----------------------------- print_newline: mov si, msg_newline ; Point SI to the carriage return/line feed string print_string: lodsb ; Load the next character of the string cmp al, 0 ; Check if it is the null terminator jz .done ; If so, printing is complete cmp al, 0x08 ; Check if the character is a backspace jz print_string ; Ignore backspaces in static string printing mov ah, 0x0E ; BIOS teletype function int 0x10 ; Print the character to screen jmp print_string ; Loop to print the next character .done: ret ; Return to caller read_line: mov di, buffer ; Point DI to the start of the input buffer inc di ; Leave space for formatting operations .delChar: dec di ; Step the buffer pointer back by one (handles backspace) .read_char: mov ah, 0x00 ; BIOS keyboard read function int 0x16 ; Await and read a keystroke cmp al, 0x0D ; Check if the 'Enter' key was pressed (Carriage Return) je .done ; If so, finish reading the line mov ah, 0x0E ; BIOS teletype function int 0x10 ; Echo the typed character to the screen cmp al, 0x08 ; Check if the 'Backspace' key was pressed je .delChar ; If so, branch to delete character logic stosb ; Store the valid character into the buffer at DI jmp .read_char ; Loop to read the next keystroke .done: mov al, 0 ; Prepare a null terminator stosb ; Append the null terminator to the end of the buffered string ret ; Return to caller ; ----------------------------- msg_welcome db "(dPaz)" ; Startup identifier string msg_newline db 10, 13, 0 ; Line feed, carriage return, and null terminator times 510 - ($ - $$) db 0 ; Pad the remainder of the boot sector with zeroes dw 0xAA55 ; Mandatory 2-byte boot signature for BIOS recognition