; +--------------------------------------------------------+
; |       >o.o<   Meow5: A very conCATenative language     |
; +--------------------------------------------------------+

; Meow5 Constants
%assign INPUT_SIZE 1024 ; size of input buffer
%assign COMPILE   00000001b ; flag: can be compiled
%assign IMMEDIATE 00000010b ; flag: can be called
%assign RUNCOMP   00000100b ; flag: runs in comp mode

; Linux Constants
%assign STDIN 0
%assign STDOUT 1
%assign STDERR 2
%assign SYS_EXIT 1
%assign SYS_READ 3
%assign SYS_WRITE 4
%assign SYS_OPEN 5
%assign SYS_CLOSE 6

; TODO add SYS_CREATE 8

; ----------------------------------------------------------
; BSS - reserved space
; ----------------------------------------------------------
section .bss
mode: resb 4            ; IMMEDATE or COMPILE
var_radix: resb 4       ; decimal=10, hex=16, etc.
input_file: resb 4      ; input file desc. (STDIN, etc.)
last: resb 4            ; Pointer to last def tail
here: resb 4            ; Will point to compile_area
free: resb 4            ; Will point to data_area
stack_start: resb 4     ; Will point to first stack addr
token_buffer: resb 32   ; For get_token
name_buffer:  resb 32   ; For 'def' (copy of token)
compile_area: resb 4096 ; We inline ("compile") here!
data_area: resb 1024    ; All variables go here!

input_buffer: resb INPUT_SIZE ; input from user (or file?)
input_buffer_end: resb 4      ; current last addr of input
input_buffer_pos: resb 4      ; current position in input
input_eof: resb 4             ; flag 1=EOF reached

; Return address for immediate mode execution only
return_addr:   resb 4    ; To "push/pop" return stack

; ----------------------------------------------------------
; MACROS!
; ----------------------------------------------------------

; PRINTSTR "Foo bar."
%macro PRINTSTR 1
    ; Param is the string to print - put it in data section
    ; with macro-local label %%str.  No need for null
    ; termination.
    %strlen mystr_len %1 ; and get length for later
    section .data
        %%mystr: db %1
    ; now the executable part
    section .text
        pusha ; preserve all registers
        ; Print the string
        mov ebx, STDOUT
        mov edx, mystr_len
        mov ecx, %%mystr
        mov eax, SYS_WRITE
        int 0x80
        popa ; restore all registers
%endmacro ; PRINTSTR

; Macro to compile in a number printing diagnostic message.
; DEBUG "<string>", <valid second param of MOV>
%macro DEBUG 2
    PRINTSTR %1
    ; Make this safe to plop absolutely anywhere
    ; by pushing the 4 registers used.
    push eax ;A
    push ebx ;B
    push ecx ;C
    push edx ;D
    ; Second param is the source expression for this
    ; MOV instruction - we'll print this value as a
    ; 32bit (4 byte, dword, 8 digit) hex num.
    ; We must perform the MOV now before the register
    ; values are overwritten by printing the string.
    mov eax, %2
    ; Now print the value. We'll use the stack as a
    ; scratch space to construct the ASCII string of the
    ; hex value. Only 9 bytes are needed (8 digits +
    ; newline), but due to a tricky "fencepost" issue,
    ; I've elected to leave room for 10 bytes and
    ; "waste" the first one.
    lea ebx, [esp - 10]  ; make room for string
    mov    ecx, 8 ; counter - 8 characters
    %%digit_loop:
        mov edx, eax
        and edx, 0x0f   ; just keep lowest 4 bits
        cmp edx, 9      ; bigger than 9?
        jg  %%af        ; yes, print 'a'-'f'
        add edx, '0'    ; no, turn it into ascii number
        jmp %%continue
    %%af:
        add edx, 'a'-10 ; because 10 is 'a'...
    %%continue:
        mov byte [ebx + ecx], dl ; store character
        ror eax, 4               ; rotate 4 bits
        dec ecx                  ; update counter
        jnz %%digit_loop         ; loop
    ; Print hex number string
    mov byte [ebx + 9], 0x0A ; add newline
    lea ecx, [ebx+1]         ; because ecx went 8...1
    mov ebx, STDOUT
    mov edx, 9 ; 8 hex digits + newline
    mov eax, SYS_WRITE
    int 0x80
    ; Restore all registers. (Reverse order)
    pop edx ;D
    pop ecx ;C
    pop ebx ;B
    pop eax ;A
%endmacro ; DEBUG print

%macro CALLDEF 1 ; takes label/addr of def to call
    ; For faking call/ret to def as if 'twas a function
    ; within assembly while creating the meow5 executable.
    ; Note that '%%return_to' is a macro-local label.
        mov dword [return_addr], %%return_to ; CALLDEF
        jmp %1                               ; CALLDEF
    %%return_to:                             ; CALLDEF
%endmacro

%macro STARTDEF 1 ; takes name of def to make
    ; Start a definition
    %1:
%endmacro

%macro RETURN_CODE 0
    mov eax, [return_addr] ; RETURN
    jmp eax                ; RETURN
%endmacro

%macro ENDDEF 3
    ; End a definiton with a tail, etc.
    ; params:
    ;   %1 - name for label (must be NASM-safe)
    ;   %2 - string name for find
    ;   %3 - 32 bits of flags
    ; Here ends the machine code for the def:
    end_%1:
        ; If we've called this in immediate mode, we'll
        ; This part won't be inlined, so it won't get
        ; in the way of the flow of "compiled" code.
        RETURN_CODE
    tail_%1:
        dd LAST_DEF_TAIL ; 32b address, linked list
        %define LAST_DEF_TAIL tail_%1
        dd (end_%1 - %1)  ; 32b length of def machine code
        dd (tail_%1 - %1) ; 32b distance from tail to start
        dd %3             ; 32b flags for def
        db %2, 0          ; xxb null-terminated name string
%endmacro

; Memory offsets for each item in tail:
%define T_CODE_LEN    4
%define T_CODE_OFFSET 8
%define T_FLAGS       12
%define T_NAME        16

; ----------------------------------------------------------
; TEXT - executable program - starting with defs
; ----------------------------------------------------------
section .text

; Keep track of def addresses for linked list.
; We start at 0 (null pointer) to indicate end of list.
%define LAST_DEF_TAIL 0

%macro EXIT_CODE 0
    pop ebx ; param1: exit code
    mov eax, SYS_EXIT
    int 0x80
%endmacro
STARTDEF exit
    EXIT_CODE
ENDDEF exit, "exit", (COMPILE | IMMEDIATE) 

; Gets length of null-terminated string
%macro STRLEN_CODE 0
    pop eax
    mov ecx, 0     ; byte counter will contain len
%%find_null:
    cmp byte [eax + ecx], 0 ; null term?
    je %%strlen_done         ; yes, done
    inc ecx                 ; no, continue
    jmp %%find_null         ; loop
%%strlen_done:
    push ecx           ; return len
%endmacro
STARTDEF strlen ; (straddr) strlen (straddr len)
    STRLEN_CODE
ENDDEF strlen, "strlen", (IMMEDIATE | COMPILE)

; Prints a string by address and length
%macro LEN_PRINT_CODE 0
    pop edx            ; strlen from stack
    pop ecx            ; string address from stack
    mov ebx, STDOUT    ; write destination file
    mov eax, SYS_WRITE ; syscall
    int 0x80           ; interrupt to linux!
%endmacro

; Prints a null-terminated string by address on stack.
%macro PRINT_CODE 0
    pop eax
    push eax ; one for strlen
    push eax ; one for write
    STRLEN_CODE  ; (after: straddr, len)
    LEN_PRINT_CODE
%endmacro
STARTDEF print ; (straddr) print ()
    PRINT_CODE
ENDDEF print, "print", (IMMEDIATE | COMPILE)

%macro INLINE_CODE 0
    pop esi ; param1: tail of def to inline
    mov edi, [here]    ; destination
    mov eax, [esi + T_CODE_LEN]    ; get len of code
    mov ebx, [esi + T_CODE_OFFSET] ; get start of code
    sub esi, ebx    ; set start of code for movsb
    mov ecx, eax    ; set len of code for movsb
    rep movsb       ; copy [esi]...[esi+ecx] into [edi]
    ;add [here], eax ; save current position
    mov [here], edi ; movsb updates edi for us
%endmacro
STARTDEF inline
    INLINE_CODE
ENDDEF inline, "inline", (IMMEDIATE)

; Given a tail addr, leaves def's flags AND the tail addr
%macro GET_FLAGS_CODE 0
    mov ebp, [esp] ; get tail addr without popping
    mov eax, [ebp + T_FLAGS] ; get flags!
    push eax
%endmacro
STARTDEF get_flags ; (tail_addr) get_flags (tailaddr flags)
    GET_FLAGS_CODE
ENDDEF get_flags, "get_flags", (IMMEDIATE | COMPILE)

; Consumes def flags, leaves truthy/falsy if RUNCOMP
; flag existed. (Non-zero is true!)
%macro IS_RUNCOMP_CODE 0
    pop eax ; param: flags
    and eax, RUNCOMP ; AND mask to leave truthy/falsy
    push eax
%endmacro
STARTDEF is_runcomp ; (flags) is_runcomp (true/false)
    IS_RUNCOMP_CODE
ENDDEF is_runcomp, "is_runcomp", (IMMEDIATE | COMPILE)

%macro FIND_CODE 0
    pop ebp ; param1 - start of def string to find
    ; in-def register use:
    ;   al  - to-find name character being checked
	;   ebx - start of dict def's name string
	;   ecx - byte offset counter (each string character)
    ;   edx - dictionary list pointer
    ; search backwards from last def
    mov edx, [last]
%%test_def:
    cmp edx, 0  ; a null pointer (0) is end of list
    je %%not_found
    ; First, see if this def is for the mode we're
    ; currently in (IMMEDIATE vs COMPILE):
    mov eax, [mode]
    and eax, [edx + T_FLAGS] ; see if mode bit is set in def tail
    cmp eax, 0
    jz %%try_next_def ; bit wasn't set to match this mode
    ; Now we'll compare name to find vs this dictionary name
    ; (ebx vs edx) byte-by-byte until a mismatch or one hits
    ; a 0 terminator first.  Only having all correct letters
    ; AND hitting 0 at the same time is a match.
    lea ebx, [edx + T_NAME] ; set dict. def name pointer
    mov ecx, 0          ; reset byte offset counter
%%compare_names_loop:
	mov al, [ebp + ecx] ; get next to-find name byte
    cmp al, [ebx + ecx] ; compare with next dict def byte
    jne %%try_next_def  ; found a mismatch!
    cmp al, 0           ; both hit 0 terminator at same time
    je %%found_it
	inc ecx
	jmp %%compare_names_loop
%%try_next_def:
    mov edx, [edx]   ; follow the tail! (linked list)
    jmp %%test_def
%%not_found:
    push 0   ; return 0 to indicate not found
    jmp %%done
%%found_it:
    push edx ; return  pointer to tail of dictionary def
%%done:
%endmacro
STARTDEF find
    FIND_CODE
ENDDEF find, "find", (IMMEDIATE)

; Gets input from a file, filling input_buffer and resetting
; input_buffer_pos.
%macro GET_INPUT_CODE 0
    pusha ; preserve all reg
    ; Fill input buffer via linux 'read' syscall
    mov ebx, [input_file] ; file descriptor (default STDIN)
    mov ecx, input_buffer ; buffer for read
    mov edx, INPUT_SIZE   ; max bytes to read
    mov eax, SYS_READ     ; linux syscall 'read'
    int 0x80              ; syscall interrupt!
    cmp eax, 0            ; 0=EOF, -1=error
    jg %%normal
    mov dword [input_eof], 1  ; set EOF reached
%%normal:
    lea ebx, [input_buffer + eax] ; end of current input
    mov dword [input_buffer_end], ebx ; save it
;    cmp eax, INPUT_SIZE   ; we read less than full buffer?
;    jge %%done            ; No, continue
;    mov byte [input_buffer + eax], 0 ; Yes, null-terminate
;%%done:
    mov dword [input_buffer_pos], input_buffer ; reset pos
    popa ; restore all reg
%endmacro
STARTDEF get_input
    GET_INPUT_CODE
ENDDEF get_input, "get_input", (IMMEDIATE | COMPILE)

; Comments in source! Eats input until newline (0x0a)
STARTDEF comment
.reset:
    mov esi, [input_buffer_pos] ; set input index
    cmp dword [input_eof], 1
    je .done ; we hit eof at some point, we're done
    mov ebx, [input_buffer_end] ; store for comparison
.check:
    cmp esi, ebx    ; have we hit end pos?
    jl .continue    ; no, keep going
    GET_INPUT_CODE  ; yes, get some
    jmp .reset      ; got more input, reset and continue
.continue:
    mov al, [esi]   ; input addr + position index
    cmp al, 0       ; end of input (null terminator)?
    je .done        ; yes, return
    cmp al, 0x0a    ; newline?
    je .done        ; yup, done with comment line
    inc esi         ; 'eat' input
    jmp .check      ; loop
.done:
    mov [input_buffer_pos], esi ; save input index
ENDDEF comment, "#", (IMMEDIATE | COMPILE | RUNCOMP)

; Skips any characters space and below from input buffer.
%macro EAT_SPACES_CODE 0
.reset:
    mov esi, [input_buffer_pos] ; set input index
    cmp dword [input_eof], 1
    je .done ; we hit eof at some point, we're done
    mov ebx, [input_buffer_end] ; store for comparison
.check:
    cmp esi, ebx    ; have we hit end pos?
    jl .continue    ; no, keep going
    GET_INPUT_CODE  ; yes, get some
    jmp .reset      ; got more input, reset and continue
.continue:
    mov al, [esi]   ; input addr + position index
    cmp al, 0       ; end of input (null terminator)?
    je .done        ; yes, return
    cmp al, 0x20    ; anything space and below?
    jg .done        ; nope, we're done
    inc esi         ; 'eat' space by advancing input
    jmp .check      ; loop
.done:
    mov [input_buffer_pos], esi ; save input index
%endmacro
STARTDEF eat_spaces
    EAT_SPACES_CODE
ENDDEF eat_spaces, "eat_spaces", (IMMEDIATE | COMPILE)

; Gets a space-separated "token" of input.
; Returns a null-terminated string OR 0 if we're out of
; input.
%macro GET_TOKEN_CODE 0
; was:
;  ebx = input   <-- esi
;  edx = output  <-- edi
    mov esi, [input_buffer_pos] ; input source index
    mov edi, token_buffer       ; destination index
.get_char:
    cmp esi, [input_buffer_end] ; need to get more input?
    jl .skip_read               ; no, keep going
    GET_INPUT_CODE              ; yes, get some
    cmp dword [input_eof], 1
    je .return ; we hit eof, we're done
    mov esi, [input_buffer_pos] ; reset source index
.skip_read:
    mov al, [esi]       ; input addr + position index
    cmp al, 0x20        ; end of token (spece or lower?)
    jle .end_of_token   ; yes
    mov byte [edi], al  ; write character
    inc esi ; next source
    inc edi ; next destination
    jmp .get_char
.end_of_token:
    cmp edi, token_buffer ; did we write anything?
    jg .return_token      ; yes, push the token addr
    push dword 0          ; no, push 0 ("no token")
    jmp .return
.return_token:
    mov [input_buffer_pos], esi ; save position
    mov byte [edi], 0           ; null-terminate token str
    push dword token_buffer     ; return str address
.return:
%endmacro
STARTDEF get_token
    GET_TOKEN_CODE
ENDDEF get_token, "get_token", (IMMEDIATE)

; Copy null-terminated string.
%macro COPYSTR_CODE 0
    pop edi ; dest
    pop esi ; source
    mov ecx, 0 ; index
%%copy_char:
    mov  al, [esi + ecx] ; from source
    mov  [edi + ecx], al ; to dest
    inc  ecx
    cmp al, 0            ; hit terminator?
    jnz %%copy_char
%endmacro
STARTDEF copystr ; (sourceaddr, destaddr) copystr ()
    COPYSTR_CODE
ENDDEF copystr, "copystr", (IMMEDIATE | COMPILE)

STARTDEF def
    mov dword [mode], COMPILE
    ; get name from next token and store it...
    EAT_SPACES_CODE
    GET_TOKEN_CODE     ; leaves source addr for copystr
    push name_buffer  ; dest
    COPYSTR_CODE      ; copy name into name_buffer
    ; copy the here pointer so we have the start address
    ; of the def
    mov eax, [here]
    push eax ; leave 'here' on stack - the start of the def
ENDDEF def, "def", (IMMEDIATE)

; This exists just so we can inline it at the end of
; definitions with the semicolon (;) def
STARTDEF return
    RETURN_CODE
ENDDEF return, "return", (IMMEDIATE)

; Does what ENDDEF macro does, but into memory at runtime.
%macro SEMICOLON_CODE 0
    ; End of Machine Code
    ; 'here' currently points to the end of the new def's
    ; machine code. We need to save that.
    mov eax, [here]
    push eax ; push end of machine code to stack
    ; Return Code
    ; Inline 'return' before the tail to allow our new
    ; def to be callable in immdiate mode.
    ; (Future improvement: Don't include this if this is
    ;  not an immediate-capable def!)
    push tail_return ; push what to inline on stack
    INLINE_CODE      ; inline the 'return' machine code
    ; Start of Tail
    ; The above inline will have advanced 'here' again.
    mov eax, [here] ; Current 'here' position
    mov ecx, eax    ; another copy, for tail start calc
    ; Link previous def 'last'
    mov ebx, [last] ; get prev tail pointer 'last'
    mov [eax], ebx ; link it here
    mov [last], eax ; and store this tail as new 'last'
    add eax, 4 ; advance 'here' 4 bytes
    ; Store length of new def's machine code
    pop ebx ; get end of machine code addr pushed above
    pop edx ; get start of machine code addr pushed by ':'
    sub ebx, edx ; calc length of machine code
    mov [eax], ebx
    add eax, 4 ; advance 'here' 4 bytes
    ; Store distance from start of tail to start of machine
    ; code.
    sub ecx, edx ; tail - start of mc
    mov [eax], ecx
    add eax, 4 ; advance 'here' 4 bytes
    ; Store flags
    ; dd %3             ; 32b flags for def
    ; NOTE: Temporarily hard-coded value!
    mov dword [eax], (IMMEDIATE | COMPILE)
    add eax, 4 ; advance 'here' 4 bytes
    push eax ; save a copy of 'here'
    ; Store name string
    ; db %2, 0          ; xxb null-terminated name string
    push name_buffer  ; source
    push eax          ; destination
    COPYSTR_CODE      ; copy name into tail
    ; Call strlen so we know how much string name we
    ; wrote to the tail:
    push name_buffer
    STRLEN_CODE
    pop ebx ; get string len pushed by STRLEN_CODE
    pop eax ; get saved 'here' position
    add eax, ebx ; advance 'here' by that amt
    inc eax      ; plus one for the null
    ; Store here in 'here'
    mov [here], eax
    ; return us to immediate mode now that we're done
    mov dword [mode], IMMEDIATE
%endmacro
STARTDEF semicolon
    SEMICOLON_CODE
ENDDEF semicolon, ";", (COMPILE | RUNCOMP)

; Takes an addr and number from stack, writes string
; representation (not null-terminated) of number to the
; address and returns number of bytes (characters) written.
%macro NUM2STR_CODE 0
    pop ebp ; address of string destination
    pop eax ; number
    mov ecx, 0 ; counter of digit characters
    mov ebx, [var_radix]
%%divide_next:    ; idiv divides
    mov edx, 0   ; div actually divides edx:eax / ebx!
    div ebx      ; eax / ebx = eax, remainder in edx
    cmp edx, 9   ; digit bigger than 9? (radix allows a-z)
    jg %%toalpha  ; yes, convert to 'a'-'z'
    add edx, '0' ; no, convert to '0'-'9'
    jmp %%store_char
%%toalpha:
    add edx, ('a'-10) ; to convert 10 to 'a'
%%store_char:
    push edx ; put on stack (pop later to reverse order)
    inc ecx
    cmp eax, 0        ; are we done converting?
    jne %%divide_next  ; no, loop
    mov eax, ecx      ; yes, store counter as return value
    mov ecx, 0        ; now we'll count up
%%store_next:
    pop edx  ; popping to reverse order
    mov [ebp + ecx], edx  ; store it at addr!
    inc ecx
    cmp ecx, eax      ; are we done storing?
    jl %%store_next
    push eax  ; return num chars written
%endmacro
STARTDEF num2str ; (num addr -- bytes_written)
    NUM2STR_CODE
ENDDEF num2str, "num2str", (IMMEDIATE | COMPILE)

; To be called when we're in a quoted string (a start quote
; was found). Puts string's address on the stack.
STARTDEF quote
    mov esi, [input_buffer_pos] ; string source
    inc esi             ; move past initial quote '"'

    ; In immediate mode, we'll write the string to free
    ; memory. In compile mode, we'll change edi to point
    ; to "here" where the def is being compiled instead.
    mov edi, [free]

    cmp dword [mode], COMPILE ; compile mode?
    jne .copy_char            ; no, skip it

    ; Setup destination for "compiled" string
    mov edi, [here]
    push edi        ; save position for 'call' opcode
    add edi, 5      ; leave space for 'call' opcode

    ; copy string (and handle escapes) to wherever edi points!
.copy_char:
    cmp esi, [input_buffer_end] ; need to get more input?
    jl .skip_read               ; no, keep going
    GET_INPUT_CODE              ; yes, get some
    cmp dword [input_eof], 1
    je .quote_done ; we hit eof, we're done
    mov esi, [input_buffer_pos] ; reset source index
.skip_read:
    mov al, [esi]       ; get char from source
    cmp al, '"'         ; look for endquote
    je .end_quote
    cmp al, '\'         ; escape sequence
    je .insert_esc
    mov [edi], al         ; copy char to desination
    inc esi             ; next source char
    inc edi             ; next desination pos
    jmp .copy_char      ; loop
.insert_esc:
    ; read the next character to determine what to do:
    inc esi
    mov al, [esi]
    cmp al, '\' ; literal backslash
    jne .esc2
        mov byte [edi], '\'
        inc esi
        inc edi
        jmp .copy_char
    .esc2:
    cmp al, '$' ; literal $
    jne .esc3
        mov byte [edi], '$'
        inc esi
        inc edi
        jmp .copy_char
    .esc3:
    cmp al, 'n' ; newline
    jne .esc4
        mov byte [edi], 0xa
        inc esi
        inc edi
        jmp .copy_char
    .esc4:
.end_quote:
    lea eax, [esi + 1]          ; get next input position
    mov [input_buffer_pos], eax ; save it
    mov [edi], byte 0           ; terminate str null
    ; Done copying string to either "free" memory or
    ; to the "here" compiled def area.

    ; Check again if we're in immediate or compile mode
    cmp dword [mode], IMMEDIATE
    je .finish_immediate

    ; do compile mode stuff
    inc edi
    mov [here], edi        ; save new "here"
    pop edx                ; back to destination for opcode
    mov byte [edx], 0xE8   ; i386 CALL relative
    sub edi, edx           ; calc length of string into edi
    sub edi, 5             ; and subtract the opcode len!
    mov dword [edx+1], edi ; is jump length
    jmp .end_if

    ; else
.finish_immediate:
    ; do immediate stuff
    push dword [free]           ; yes, push string addr
    lea eax, [edi + 1]          ; calc next "free" space
    mov [free], eax             ; save it

.end_if:
    EAT_SPACES_CODE             ; advance to next token
.quote_done:
ENDDEF quote, 'quote', (IMMEDIATE | COMPILE)

; Attempts to parse num from string using radix.
; Doesn't handle negative sign. Leaves just 0
; (false) on stack if not successful.
%macro STR2NUM_CODE 0 ; (str_addr -- [num] success)
    pop ebp ; address of input token
    mov eax, 0 ; result
    mov ebx, 0 ; char conversion
    mov ecx, 0 ; char counter/pointer
    mov edx, [var_radix]
.next_char:
    mov bl, [ebp + ecx] ; put char in bl
    cmp bl, 0           ; null terminator?
    je .return_num      ; yup, return value
    inc ecx
    ; Multiply the current value by the radix to prepare for
    ; the next, less significant digit. If we're starting
    ; out, the current value is 0, which is no problem.
    imul eax, edx
    cmp bl, '0'         ; ASCII less than '0' is invalid
    jl .error
    cmp bl, '9'         ; is it '0'-'9'?
    jg .try_upper       ; no, try 'A'-'Z'
    sub bl, '0'         ; yes, convert ASCII '0' to 0
    jmp .add_value
.try_upper:
    cmp bl, 'A'
    jl .error
    cmp bl, 'Z'
    jg .try_lower
    sub bl, ('A'-10) ; it's uppercase, convert 'A' to 10
    jmp .add_value
.try_lower:
    cmp bl, 'z'
    jg .error
    sub bl, ('a'-10) ; it's lowercase, convert 'a' to 10
    jmp .add_value
.add_value:
    ; Make sure the number is within the radix
    cmp bl, dl ; edx has radix
    jg .error  ; greater than radix
    add eax, ebx   ; bl has converted char's value
    jmp .next_char ; loop
.error:
    push 0         ; failure code (false)
    jmp .str2num_done
.return_num:
    cmp ecx, 0     ; did we actually get any chars?
    je .error      ; no, empty token string! error
    push eax       ; push number
    push 1         ; success (true)
.str2num_done:
%endmacro
STARTDEF str2num ; (str_addr -- [num] success)
    STR2NUM_CODE
ENDDEF str2num, 'str2num', (IMMEDIATE | COMPILE)

%macro RADIX_CODE 0
    pop eax
    mov [var_radix], eax
%endmacro
STARTDEF radix
    RADIX_CODE
ENDDEF radix, 'radix', (IMMEDIATE | COMPILE)
STARTDEF hex
    mov dword [var_radix], 16
ENDDEF hex, 'hex', (IMMEDIATE | COMPILE)
STARTDEF oct
    mov dword [var_radix], 8 
ENDDEF oct, 'oct', (IMMEDIATE | COMPILE)
STARTDEF bin
    mov dword [var_radix], 2
ENDDEF bin, 'bin', (IMMEDIATE | COMPILE)
STARTDEF decimal
    mov dword [var_radix], 10
ENDDEF decimal, 'decimal', (IMMEDIATE | COMPILE)

; see if token starts with number. if it does, parse it
STARTDEF number
    GET_TOKEN_CODE
    STR2NUM_CODE
    pop eax              ; return value from str2num
    cmp eax, 0           ; did it fail?
    je .invalid_number
    cmp dword [mode], COMPILE
    je .compile_number
    ; We got number in IMMEDIATE mode, so just keep the
    ; value on the stack and keep going!
    jmp .done
.compile_number:
    ; like 'quote' and 'var', this writes a raw x86
    ; opcode to push an immediate value on the stack
    ; at runtime
    pop eax ; get number from stack
    mov edx, [here]          ; compile var code here
    mov byte [edx], 0x68     ; i386 opcode for PUSH imm32
    mov dword [edx + 1], eax ; the number literal
    add edx, 5               ; update here
    mov [here], edx
    jmp .done
.invalid_number:
    ; If we got here, there was a token that started with a
    ; digit, but could not be parsed as a number. We're
    ; defining that as a fatal error.
    PRINTSTR 'Error parsing "'
    push token_buffer
    CALLDEF print
    PRINTSTR `" as a number.\n`
    EXIT_CODE
.done:
ENDDEF number, 'number', (IMMEDIATE | COMPILE)

; Call with num to to be printed on the stack
%macro PRINTNUM_CODE 0
    ; param2 address desination for number str
    mov eax, [free] ; use free space temporarily
    push eax ; addr for num2str
    NUM2STR_CODE ; leaves length of string
    pop ebx
    mov eax, [free]
    push eax ; addr
    push ebx ; len
    LEN_PRINT_CODE
%endmacro
STARTDEF printnum
    PRINTNUM_CODE
ENDDEF printnum, 'printnum', (IMMEDIATE | COMPILE)

%macro PRINT_FMT_CODE 0
    pop esi ; string addr from stack is source pointer
    mov ecx, 0   ; length of string to print
.examine_char:
    mov al, [esi + ecx]  ; get next char
    cmp al, '$'
    je .print_num
    cmp al, 0 ; regular end of string!
    je .print_the_rest
    inc ecx              ; neither, keep going
    jmp .examine_char
.print_num:
    ; first print the string segment before the num
    pop eax  ; get number to print from stack
    push esi ; str addr (save a copy)
    push ecx ; str len  (save a copy)
    push eax ; num to print
    push esi ; str addr
    push ecx ; str len
    LEN_PRINT_CODE
    PRINTNUM_CODE ; print number from stack
    pop ecx ; restore str len
    pop esi ; restore str addr
    ; reset string to *after* the '$' placeholder and
    ; keep printing
    lea esi, [esi + ecx + 1]
    mov ecx, 0
    jmp .examine_char
.print_the_rest:
    ; now we just need to print a "normal" string at
    ; the end, so push the start address and print!
    push esi ; print just needs start address
    PRINT_CODE
%endmacro
STARTDEF print_fmt
    PRINT_FMT_CODE
ENDDEF print_fmt, 'print$', (IMMEDIATE | COMPILE)

STARTDEF say
    PRINT_FMT_CODE
    mov eax, [free]
    mov byte [eax], 0xa ; '\n'
    push eax    ; addr of string
    push 1      ; length to print
    LEN_PRINT_CODE
ENDDEF say, 'say', (IMMEDIATE | COMPILE)

; Given a mode (dword) on the stack, prints the matching
; modes (immediate/compile/runcomp).
%macro PRINTMODE_CODE 0
    pop eax ; get mode dword
    mov ebx, eax
    and ebx, IMMEDIATE
    jz %%try_compile
    push eax ; save
    PRINTSTR 'IMMEDIATE '
    pop eax ; restore
%%try_compile:
    mov ebx, eax
    and ebx, COMPILE
    jz %%try_runcomp
    push eax ; save
    PRINTSTR 'COMPILE '
    pop eax ; restore
%%try_runcomp:
    mov ebx, eax
    and ebx, RUNCOMP
    jz %%done
    push eax ; save
    PRINTSTR 'RUNCOMP '
    pop eax ; restore
%%done:
%endmacro
STARTDEF printmode
    PRINTMODE_CODE
ENDDEF printmode, 'printmode', (IMMEDIATE | COMPILE)

%macro PRINTSTACK_CODE 0
    mov ecx, [stack_start]
    sub ecx, esp ; difference between start and current
%%dword_loop:
    cmp ecx, 0           ; reached start?
    jl %%done            ; yup, done
    mov eax, [esp + ecx] ; no, print this value
    push ecx ; preserve
    push eax ; print this value
    PRINTNUM_CODE
    PRINTSTR " "
    pop ecx  ; restore
    sub ecx, 4 ; reduce stack index by dword
    jmp %%dword_loop
%%done:
    PRINTSTR `\n`
%endmacro
STARTDEF printstack
    PRINTSTACK_CODE
ENDDEF printstack, 'ps', (IMMEDIATE | COMPILE)

; Takes def tail addr, prints meta-info (from tail)
; and prints a hex dump of the def's machine code.
; Example: inspect foo
%macro INSPECT_CODE 0
    EAT_SPACES_CODE
    GET_TOKEN_CODE ; get address of next token's string
    FIND_CODE      ; get tail of def matching token
    pop esi ; get tail addr
    lea eax, [esi + T_NAME]
    push esi ; preserve tail addr
    push eax
    PRINT_CODE 
    PRINTSTR ": "
    pop esi ; restore tail addr
    mov eax, [esi + T_CODE_LEN]
    push esi ; preserve tail addr
    ; param 1: num to be stringified
    push eax
    PRINTNUM_CODE
    PRINTSTR " bytes "
    pop esi ; restore tail
    mov eax, [esi + T_FLAGS]
    push esi ; preserve tail addr
    push eax
    PRINTMODE_CODE
    PRINTSTR `\n    `
    ; Now do a hex dump of the machine code for
    ; this word!
    pop esi ; get tail addr
    mov ecx, [esi + T_CODE_LEN]    ; len of code
    mov eax, [esi + T_CODE_OFFSET] ; offset of code start
    sub esi, eax ; esi is now start addr of code
    add ecx, esi ; ecx is now end addr of code
    mov dword ebx, [var_radix] ; save current radix
    mov dword [var_radix], 16  ; set to hex
    push ebx
%%byte_loop:
    cmp ecx, esi ; end addr reached?
    je %%done     ; yup
    mov al, [esi] ; no, print this value
    push ecx ; preserve end addr
    push eax ; print this value
    PRINTNUM_CODE
    PRINTSTR " "
    pop ecx ; restore end addr
    inc esi ; move to next addr
    jmp %%byte_loop
%%done:
    PRINTSTR `\n`
    pop ebx
    mov dword [var_radix], ebx ; restore previous radix
%endmacro
STARTDEF inspect
    INSPECT_CODE
ENDDEF inspect, 'inspect', (IMMEDIATE)

; Print all def names
STARTDEF all_names
    mov esi, [last] ; tail addr of last def defined
.print_loop:
    lea eax, [esi + T_NAME] ; name
    mov esi, [esi] ; get prev tail pointer
    push esi       ; preserve it
    push eax ; name for print
    PRINT_CODE
    PRINTSTR " " ; space between names
    pop esi ; restore tail
    cmp esi, 0        ; done?
    jne .print_loop ; nope, keep going!
    PRINTSTR `\n`
ENDDEF all_names, 'all', (IMMEDIATE)

STARTDEF add
    pop eax
    pop ebx
    add eax, ebx
    push eax
ENDDEF add, '+', (IMMEDIATE | COMPILE)

STARTDEF sub
    pop ebx
    pop eax
    sub eax, ebx
    push eax
ENDDEF sub, '-', (IMMEDIATE | COMPILE)

STARTDEF mul
    pop eax
    pop ebx
    imul eax, ebx
    push eax
ENDDEF mul, '*', (IMMEDIATE | COMPILE)

STARTDEF div
    mov edx, 0
    pop ebx
    pop eax
    idiv ebx
    push edx ; remainder
    push eax ; answer (quotient)
ENDDEF div, '/', (IMMEDIATE | COMPILE)

STARTDEF inc
    pop ecx
    inc ecx
    push ecx
ENDDEF inc, 'inc', (IMMEDIATE | COMPILE)

STARTDEF dec
    pop ecx
    dec ecx
    push ecx
ENDDEF dec, 'dec', (IMMEDIATE | COMPILE)

STARTDEF popstack
    pop eax
ENDDEF popstack, 'pop', (IMMEDIATE | COMPILE)

STARTDEF dupstack
    pop eax
    push eax
    push eax
ENDDEF dupstack, 'dup', (IMMEDIATE | COMPILE)

; Immediate mode 'if' (?) skips whatever token follows
; if the "condition" on the stack is false (0).
STARTDEF if
    pop eax
    test eax, eax ; zero?
    jnz .continue_for_true
    EAT_SPACES_CODE
    GET_TOKEN_CODE
    pop eax ; throw away token!
.continue_for_true:
ENDDEF if, '?', (IMMEDIATE)

; Compile mode 'if' (?) skips the next def's code.
; So we bake in a conditional jump over the next
; def's inlined code!
STARTDEF if_compiled
    EAT_SPACES_CODE
    GET_TOKEN_CODE ; MUST be a def
    FIND_CODE      ; get tail of def matching token
    pop esi ; tail of def to inline
    mov eax, [esi + T_CODE_LEN] ; get len of code
    push esi ; put tail back on stack for inline
    mov edx, [here]           ; compile code here
    mov byte [edx], 0x58      ; i386 opcode: pop eax
    mov byte [edx+1], 0x85    ; i386 opcode: test eax
    mov byte [edx+2], 0xc0    ;              (continued)
    mov byte [edx+3], 0x0f    ; i386 opcode: jz
    mov byte [edx+4], 0x84    ;              (continued)
    mov dword [edx+5], eax    ; length of def's code
    add edx, 9
    mov [here], edx
    INLINE_CODE
ENDDEF if_compiled, '?', (COMPILE | RUNCOMP)

; Compile mode "loop" keeps replaying a def so long as the
; top of the stack contains "true" (non-zero)
;  [jnz][...def...][jmp]
; ^  |               |  ^
; +--|------B--------+  |
;    +--------A---------+
; A=len(def) + len(jmp)
; B=A + len(jnz)
STARTDEF loop_compiled
%assign LEN_BEFORE 10
%assign LEN_AFTER 5
    EAT_SPACES_CODE
    GET_TOKEN_CODE ; MUST be a def
    FIND_CODE      ; get tail of def matching token
    pop esi ; tail of def to inline
    mov eax, [esi + T_CODE_LEN] ; get len of code
    add eax, LEN_AFTER ; add length of unconditional jump at end
    push eax ; preserve length of def for end
    push esi ; put tail back on stack for inline
    ; Compile in the conditional jump over the def
    ; (Note that unlike "?" (if), it leaves test value on stack!)
    mov edx, [here]           ; compile code here
    mov byte [edx], 0x58      ; i386 opcode: pop eax
    mov byte [edx+1], 0x50    ; i386 opcode: push eax
    mov byte [edx+2], 0x85    ; i386 opcode: test eax
    mov byte [edx+3], 0xc0    ;              (continued)
    mov byte [edx+4], 0x0f    ; i386 opcode: jz
    mov byte [edx+5], 0x84    ;              (continued)
    mov dword [edx+6], eax    ; distance to jump
    add edx, LEN_BEFORE
    mov [here], edx
    INLINE_CODE
    ; Compile in the unconditional jump to the start
    pop eax ; retrieve length of def plus "after"
    add eax, LEN_BEFORE       ; now add the "before" len
    neg eax                   ; negate the jump (go back!)
    mov edx, [here]           ; compile var code here
    mov byte [edx], 0xe9      ; i386 opcode: jmp
    mov dword [edx+1], eax    ; distance to jump
    add edx, LEN_AFTER
    mov [here], edx
ENDDEF loop_compiled, 'loop?', (COMPILE | RUNCOMP)

; 'var' reserves a new space in memory (4 bytes for now) and
; creates a new def that puts the ADDRESS of that memory on
; the stack when it is called.
STARTDEF var
    mov dword [mode], COMPILE
    ; get name from next token and store it...
    EAT_SPACES_CODE
    GET_TOKEN_CODE
    push name_buffer  ; dest
    COPYSTR_CODE      ; copy name into name_buffer
    mov eax, [free]   ; get current free space addr
    mov edx, [here]           ; compile var code here
    push edx ; save it for semicolon!
    mov byte [edx], 0x68      ; i386 opcode for PUSH imm32
    mov dword [edx + 1], eax  ; address of var space
    add edx, 5                ; update here
    mov [here], edx
    add eax, 4                ; update free pointer
    mov [free], eax
    SEMICOLON_CODE
ENDDEF var, 'var', (IMMEDIATE | COMPILE)

STARTDEF setvar
    pop edi ; address from stack
    pop eax ; value from stack
    mov [edi], eax ; set it!
ENDDEF setvar, 'set', (IMMEDIATE | COMPILE)

STARTDEF getvar
    pop esi ; address from stack
    mov eax, [esi] ; get it!
    push eax ; put it on the stack
ENDDEF getvar, 'get', (IMMEDIATE | COMPILE)


; *******************************************
; *               ELF HEADER!               *
; *******************************************
; This is defined in meow5's program data. It
; gets filled in with the correct values for
; whatever def we're "compiling" via:
;
;   elf <def name>
;
section .data
%assign elf_va 0x08048000 ; elf virt mem start address
elf_header:
    ; **********************************************
    ; ELF Identification (16 bytes)
    db 0x7F,'ELF' ; Magic String
    db          1 ; "File class" 32 bit
    db          1 ; "Data encoding" 1=LSB (x86 little endian)
    db          1 ; "File version" ELF version (1="current")
    times 9 db  0 ; padding (to fill up 16 bytes)
    ; Section Header Table
    dw          2 ; type      - 2="Executable file"
    dw          3 ; machine   - 3="Intel 80386"
    dd          1 ; version   - 1="Current"
    ; For reasons I do not understand, I had not had any
    ; luck setting this program entry to the beginning of
    ; the virtual memory start address and loading the file
    ; starting immediately after the ELF header where the
    ; def's machine code was written. It always segfaulted.
    ;
    ; Update: I still don't *really* get that when working
    ; just the ELF documentation, but I've accepted that
    ; Linux mmaps a file to an entire page at a time, so
    ; there are certain alignment-y rules at play here. We
    ; don't fight it.
    dd elf_va + elf_size ; entry - execution start address
    dd phdr1 - elf_header ; phoff - bytes to program header
    dd          0 ; shoff     - 0 for no section header
    dd          0 ; flags     - processor-specific flags
    dw   hdr_size ; ehsize    - this header bytes, see below
    dw  phdr_size ; phentsize - program header size
    dw          1 ; phnum     - program header count
    dw          0 ; shentsize - section header size (none)
    dw          0 ; shnum     - section header count
    dw          0 ; shstrndx  - section header offset
    hdr_size equ $ - elf_header ; calulate elf header size

    ; **********************************************
    ; Program Header 1 - The one and only.
phdr1:
    dd         1 ; type of program header (1=PT_LOAD)
    dd         0 ; offset in file of data to load
    dd    elf_va ; target memory start address
    dd    elf_va ; target physical memory, same
prog_bytes1:
    dd         0 ; bytes to load from file (placeholder)
prog_bytes2:
    dd         0 ; bytes of memory to allocate (placeholder)
    dd         7 ; flags: 1=exec, 2=write, 4=read (7=RWX)
    dd         0 ; Memory alignment
       ; **********************************************

    ; Calculate program header size. Only need to do this
    ; once. All program headers are the same size. (And now
    ; there's just one again. So that's doubly true.)
    phdr_size equ $ - phdr1 ; calculate program header size

    ; End of entire ELF header.
    ; Calculate size of ELF header, used above.
    elf_size equ $ - elf_header

temp_test_string: db 'HELLO WORLD' ; 11 bytes test

section .text

STARTDEF elf
    EAT_SPACES_CODE
    GET_TOKEN_CODE ; get address of next token's string
    FIND_CODE      ; get tail of def matching token
    pop esi ; addr of tail (and keep in esi the whole time)
    mov eax, [esi + T_CODE_LEN]    ; get len of code

    ; Overwrite the placeholder program size values in the
    ; ELF program header with this def's code size.
    add eax, elf_size
    mov [prog_bytes1], eax ; file
    mov [prog_bytes2], eax ; memory

    ; We can create and open at the same time!
    ; From open(2) man page:
    ;   "A call to creat() is equivalent to calling open()
    ; with flags equal to O_CREAT|O_WRONLY|O_TRUNC."
    ; I got the flags by searching all of /usr/include and
    ; finding /usr/include/asm-generic/fcntl.h
    ; That yielded (along with bizarre comment "not fcntl"):
    ;   #define O_CREAT   00000100
    ;   #define O_WRONLY  00000001
    ;   #define O_TRUNC   00001000
    ; which are apparently octal values??? (NOT binary)
    ; Hence this flag value for 'open':
    mov ecx, (0100o | 0001o | 1000o)
    ; ebx contains null-terminated def name (FIND_CODE)
    mov edx, 755o ; mode (permissions)
    mov eax, SYS_OPEN
    int 80h ; now eax will contain the new file desc.
    push ebx   ; SAVE def tail address for end
    ; Write ELF header
    mov edx, elf_size ; bytes to write
    mov ecx, elf_header      ; source address
    mov ebx, eax ; the fd for writing (opened/created above)
    mov eax, SYS_WRITE
    int 80h
    ;
    ; Write def (executable program segment)
    mov edx, [esi + T_CODE_LEN] ; bytes to write
    mov eax, [esi + T_CODE_OFFSET] ; for source addr
    mov ecx, esi ; tail addr
    sub ecx, eax ; source addr (code offset from tail)
    mov eax, SYS_WRITE ; ebx still has fd
    int 80h
    ; Write our memory (for data program segment)
;    mov edx, 11 ; bytes to write
;    mov ecx, temp_test_string ; for source addr
;    mov eax, SYS_WRITE ; ebx still has fd
;    int 80h
    ; Close file (ebx still has fd)
    mov eax, SYS_CLOSE
    int 80h
    ; File name pushed above. Display message about
    ; having written file.
    PRINTSTR 'Wrote to "'
    PRINT_CODE ; print fname from stack (see "SAVE" above)
    PRINTSTR `".\n`
ENDDEF elf, 'elf', (IMMEDIATE)

; ----------------------------------------------------------
; PROGRAM START!
; ----------------------------------------------------------
global _start
_start:
    cld    ; use increment order for certain cmds

    ; Start in immediate mode - execute def immediately!
    mov dword [mode], IMMEDIATE

    ; Default to input file descriptor STDIN. We can change
    ; this to make get_input read from different sources.
    mov dword [input_file], STDIN

	; Here points to the current spot where we're going to
	; inline ("compile") the next def
    mov dword [here], compile_area

    ; Free points to the next free space in the data area
    ; where all variables and non-stack data goes.
    mov dword [free], data_area

    ; Store the first stack address so we can reference it
    ; later (such as printing contents of stack). Subtract 4
    ; so that we mark the *next* position as the first (sich
    ; it's the first position to which we'll push a value).
    lea eax, [esp - 4]
    mov [stack_start], eax

    ; Store last tail for dictionary searches (note that
	; find just happens to be the last def defined in the
	; dictionary at the moment).
    mov dword [last], LAST_DEF_TAIL

    ; In order to signal that we need to read input on
    ; start, set both the current read index and the end
    ; location to the start of the buffer.  Currently, the
    ; 'eat_spaces' def will see that and read more input.
    mov dword [input_buffer_pos], input_buffer
    mov dword [input_buffer_end], input_buffer
    mov dword [input_eof], 0 ; EOF flag

    ; Start off parsing and printing numbers as decimals.
    mov dword [var_radix], 10


; ----------------------------------------------------------
; Interpreter!
; ----------------------------------------------------------
get_next_token:
    mov eax, [input_eof]
    CALLDEF eat_spaces ; skip whitespace
    cmp dword [input_eof], 1 ; end of input?
    je .end_of_input ; yes, time to die
    ; Get the next character in the input stream to see what
    ; it is. Check for end of input, quotes, and numbers.
    mov esi, [input_buffer_pos] ; source
    mov al, [esi]               ; first char
.try_quote:
    cmp al, '"'         ; next char a quote?
    jne .try_num        ; nope, continue
    CALLDEF quote      ; yes, get string, leaves addr
    jmp get_next_token
.try_num:
    cmp al, '0'
    jl .try_token ; nope!
    cmp al, '9'
    jg .try_token ; nope!
    CALLDEF number     ; parse number, leaves value
    jmp get_next_token
.try_token:
    CALLDEF get_token
    pop eax    ; get_token returns address or 0
    cmp eax, 0
    je .end_of_input    ; all out of tokens!
    push token_buffer   ; for find
    CALLDEF find       ; find token, returns tail addr
    pop eax             ; find's return value
    cmp eax, 0          ; did find fail?
    je .token_not_found ; yup
    push eax ; find successful, put result back
    cmp dword [mode], IMMEDIATE
    je .exec_def
    ; We're in compile mode...
    CALLDEF get_flags
    CALLDEF is_runcomp
    pop eax    ; get result
    cmp eax, 0 ; if NOT equal, def was RUNCOMP
    jne .exec_def ; yup, RUNCOMP
    CALLDEF inline ; nope, "compile" it.
    jmp get_next_token
.exec_def:
    ; Run current def in immediate mode!
    ; We currently have the tail of a found def.
    pop ebx ; addr of def tail left on stack by 'find'
    mov eax, [ebx + T_CODE_OFFSET]
    sub ebx, eax ; set to start of def's machine code
    CALLDEF ebx ; call def with that addr (via reg)
    jmp get_next_token
.end_of_input:
    push 0 ; exit status
    CALLDEF exit
.token_not_found:
    ; Putting strings together this way is quite painful...
    ; "Could not find def "foo" while looking in <mode> mode."
    PRINTSTR 'Could not find def "'
    push token_buffer
    CALLDEF print
    PRINTSTR '" while looking in '
    mov eax, [mode]
    push eax
    PRINTMODE_CODE
    PRINTSTR ` mode.\n`
    jmp get_next_token
