;; Copyright (C) 2017 Jeremiah Orians
;; This file is part of stage0.
;;
;; stage0 is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;;
;; stage0 is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with stage0.  If not, see <http://www.gnu.org/licenses/>.

	section .text
	global _start

	;; Register usage:
	;; RAX, RSI, RDI => Temps
	;; R12 => MALLOC
	;; R13 => HEAD
	;; R14 => Output_file
	;; R15 => Input_file

	;; Struct format: (size 32)
	;; NEXT => 0
	;; TYPE => 8
	;; TEXT => 16
	;; EXPRESSION => 24

	;; Types
	;; None => 0
	;; MACRO => 1
	;; STRING => 2

; Where the ELF Header is going to hit
; Simply jump to _start
; Our main function
_start:
	pop rax                     ; Get the number of arguments
	pop rdi                     ; Get the program name
	pop rdi                     ; Get the actual input name
	mov rsi, 0                  ; prepare read_only
	mov rax, 2                  ; the syscall number for open()
	syscall                     ; Now open that damn file
	mov r15, rax                ; Preserve the file pointer we were given

	pop rdi                     ; Get the actual output name
	mov rsi, 577                ; Prepare file as O_WRONLY|O_CREAT|O_TRUNC
	mov rdx, 384                ; Prepare file as RW for owner only (600 in octal)
	mov rax, 2                  ; the syscall number for open()
	syscall                     ; Now open that damn file
	cmp rax, 0                  ; Check for missing output
	jg _start_out               ; Have real input
	mov rax, 1                  ; Use stdout

_start_out:
	mov r14, rax                ; Preserve the file pointer we were given

	mov rax, 12                 ; the Syscall # for SYS_BRK
	mov rdi, 0                  ; Get current brk
	syscall                     ; Let the kernel do the work
	mov r12, rax                ; Set our malloc pointer

	call Tokenize_Line          ; Get all lines
	mov rax, r13                ; prepare for Reverse_List
	call Reverse_List           ; Correct order
	mov r13, rax                ; Update HEAD
	call Identify_Macros        ; Find the DEFINEs
	call Line_Macro             ; Apply the DEFINEs
	call Process_String         ; Handle strings
	call Eval_Immediates        ; Handle Numbers
	call Preserve_Other         ; Collect the remaining
	call Print_Hex              ; Output our results

Done:
	; program completed Successfully
	mov rdi, 0                  ; All is well
	mov rax, 0x3c               ; put the exit syscall number in eax
	syscall                     ; Call it a good day


;; Tokenize_Line Function
;; Using input file R15 and Head R13
;; Creates a linked list of structs
;; Uses RBX for in_set strings, RCX for Int C and RDX for Struct Token* p
Tokenize_Line:
	push rbx                    ; Protect RBX
	push rcx                    ; Protect RCX
	push rdx                    ; Protect RDX
restart:
	call fgetc                  ; Read a char
	cmp rax, -4                 ; Check for EOF
	je done                     ; File is collected

	movzx rax, al               ; We have to zero extend it to use it
	mov rcx, rax                ; Protect C

	mov rbx, comments           ; Get pointer to "#;"
	call In_Set                 ; Check for comments
	cmp rax, 1                  ; If comments
	je Purge_LineComment        ; try again

	mov rax, rcx                ; put C in place for check
	mov rbx, terminators        ; Get pointer to "\n\t "
	call In_Set                 ; Check for terminators
	cmp rax, 1                  ; If terminator
	je restart                  ; try again

	mov rax, 32                 ; Malloc the struct P
	call malloc                 ; Get pointer to P
	mov rdx, rax                ; Protect P
	mov [rdx], r13              ; P->NEXT = HEAD
	mov r13, rdx                ; HEAD = P

	mov rax, rcx                ; put C in place for check
	mov rbx, string_char        ; Get pointer to "\"'"
	call In_Set                 ; Check for string chars
	cmp rax, 1                  ; If string char
	je Store_String             ; Get string

	call Store_Atom             ; Get whole token
	jmp restart

done:
	pop rdx                     ; Restore RDX
	pop rcx                     ; Restore RCX
	pop rbx                     ; Restore RBX
	ret


;; fgetc function
;; Receives FILE* in R15
;; Returns -4 (EOF) or char in RAX
fgetc:
	mov rax, -4                 ; Put EOF in rax
	push rax                    ; Assume bad (If nothing read, value will remain EOF)
	lea rsi, [rsp]              ; Get stack address
	mov rdi, r15                ; Where are we reading from
	mov rax, 0                  ; the syscall number for read
	push rdx                    ; Protect RDX
	mov rdx,  1                 ; set the size of chars we want
	push r11                    ; Protect r11
	syscall                     ; call the Kernel
	pop r11                     ; Restore r11
	pop rdx                     ; Restore RDX
	pop rax                     ; Get either char or EOF
	ret


;; Malloc isn't actually required if the program being built fits in the initial memory
;; However, it doesn't take much to add it.
;; Requires R12 to be initialized and RAX to have the number of desired bytes
malloc:
	mov rdi, r12                ; Using the current pointer
	add rdi, rax                ; Request the number of desired bytes
	mov rax, 12                 ; the Syscall # for SYS_BRK
	push rcx                    ; Protect rcx
	push r11                    ; Protect r11
	syscall                     ; call the Kernel
	pop r11                     ; Restore r11
	pop rcx                     ; Restore rcx
	mov rax, r12                ; Return pointer
	mov r12, rdi                ; Update pointer
	ret


;; Purge_LineComment function
;; Reads chars until LF and jumps to restart
Purge_LineComment:
	call fgetc                  ; Get a char
	movzx rax, al               ; Zero extend
	cmp rax, 10                 ; While not LF
	jne Purge_LineComment       ; Keep reading
	jmp restart


;; Store_String Function
;; Receives C in RCX, HEAD in RDX and Input file in R15
;; Uses RBX for terminator, RCX for C and RDX for string
Store_String:
	push rbx                    ; Protect RBX
	push rcx                    ; Protect RCX
	push rdx                    ; Protect RDX

	mov rax, 2                  ; Using TYPE STRING
	mov [rdx+8], rax            ; HEAD->TYPE = STRING
	mov rax, 256                ; Malloc the string
	call malloc                 ; Get pointer to P
	mov [rdx+16], rax           ; HEAD->TEXT = STRING
	mov rbx, rcx                ; Protect terminator
	mov rdx, rax                ; Protect string pointer
Store_String_Loop:
	mov [rdx], cl               ; write byte
	call fgetc                  ; read next char
	movzx rax, al               ; Zero extend it
	mov rcx, rax                ; Update C
	add rdx, 1                  ; STRING = STRING + 1
	cmp rcx, rbx                ; See if we hit terminator
	jne Store_String_Loop       ; Otherwise keep looping

	pop rdx                     ; Restore RDX
	pop rcx                     ; Restore RCX
	pop rbx                     ; Restore RBX
	mov rax, rdx                ; return HEAD
	jmp restart


;; Store_Atom Function
;; Receives C in RCX, HEAD in RDX and Input file in R15
;; Uses RBX for in_set strings, RCX for C and RDX for string
Store_Atom:
	push rbx                    ; Protect RBX
	push rcx                    ; Protect RCX
	push rdx                    ; Protect RDX

	mov rax, 256                ; Malloc the string
	call malloc                 ; Get pointer to P
	mov [rdx+16], rax           ; HEAD->TEXT = STRING
	mov rbx, terminators        ; Get pointer to "\n\t "
	mov rdx, rax                ; Protect string pointer
Store_Atom_loop:
	mov [rdx], cl               ; write byte
	call fgetc                  ; read next char
	movzx rax, al               ; Zero extend it
	mov rcx, rax                ; Update C
	add rdx, 1                  ; STRING = STRING + 1
	call In_Set                 ; Check for terminators
	cmp rax, 0                  ; Check for "\n\t "
	je Store_Atom_loop          ; Loop otherwise

	pop rdx                     ; Restore RDX
	pop rcx                     ; Restore RCX
	pop rbx                     ; Restore RBX
	mov rax, rdx                ; return HEAD
	ret


;; In_Set function
;; Receives Char C in RAX and CHAR* in RBX
;; Returns 1 if true, zero if false in RAX
In_Set:
	push rbx                    ; Protect RBX
	push rcx                    ; Protect RCX
In_Set_loop:
	mov cl, [rbx]               ; Read char
	movzx rcx, cl               ; Zero extend it

	cmp rax, rcx                ; See if they match
	je In_Set_True              ; return true

	cmp rcx, 0                  ; Check for NULL
	je In_Set_False             ; return false

	add rbx, 1                  ; s = s + 1
	jmp In_Set_loop             ; Keep looping

In_Set_True:
	mov rax, 1                  ; Set True
	pop rcx                     ; Restore RCX
	pop rbx                     ; Restore RBX
	ret

In_Set_False:
	mov rax, 0                  ; Set FALSE
	pop rcx                     ; Restore RCX
	pop rbx                     ; Restore RBX
	ret

;; Char sets
terminators:
	db 10, 9, 32, 0

comments:
	db 35, 59, 0

string_char:
	db 34, 39, 0


;; Reverse_List function
;; Receives List in RAX
;; Returns the list reversed in RAX
Reverse_List:
	push rbx                    ; Protect RBX
	push rcx                    ; Protect RCX
	mov rbx, rax                ; Set HEAD
	mov rax, 0                  ; ROOT = NULL
Reverse_List_Loop:
	cmp rbx, 0                  ; WHILE HEAD != NULL
	je Reverse_List_Done        ; Stop otherwise

	mov rcx, [rbx]              ; NEXT = HEAD->NEXT
	mov [rbx], rax              ; HEAD->NEXT = ROOT
	mov rax, rbx                ; ROOT = HEAD
	mov rbx, rcx                ; HEAD = NEXT
	jmp Reverse_List_Loop       ; Keep Going

Reverse_List_Done:
	pop rcx                     ; Restore RCX
	pop rbx                     ; Restore RBX
	ret


;; Identify_Macros function
;; Receives List in RAX
;; Updates the list in place; does not modify registers
;; Uses RBX for DEFINE, RCX for I
Identify_Macros:
	push rax                    ; Protect RAX
	push rbx                    ; Protect RBX
	push rcx                    ; Protect RCX
	push rdx                    ; Protect RDX
	mov rbx, DEFINE_str         ; Setup define string
	mov rcx, rax                ; I = HEAD
Identify_Macros_Loop:
	mov rax, [rcx+16]           ; I->TEXT
	call match                  ; IF "DEFINE" == I->TEXT
	cmp rax, 0                  ; Check if match
	jne Identify_Macros_Next    ; Skip the work

	;; Deal with MACRO
	mov rax, 1                  ; Using MACRO
	mov [rcx+8], rax            ; I->TYPE = MACRO

	mov rax, [rcx]              ; I->NEXT
	mov rax, [rax+16]           ; I->NEXT->TEXT
	mov [rcx+16], rax           ; I->TEXT = I->NEXT->TEXT

	mov rax, [rcx]              ; I->NEXT
	mov rax, [rax]              ; I->NEXT->NEXT
	mov rax, [rax+16]           ; I->NEXT->NEXT->TEXT
	mov [rcx+24], rax           ; I->EXPRESSION = I->NEXT->NEXT->TEXT

	mov rax, [rcx]              ; I->NEXT
	mov rax, [rax]              ; I->NEXT->NEXT
	mov rax, [rax]              ; I->NEXT->NEXT->NEXT
	mov [rcx], rax              ; I->NEXT = I->NEXT->NEXT->NEXT

Identify_Macros_Next:
	mov rcx, [rcx]              ; I = I->NEXT
	cmp rcx, 0                  ; Check for NULL
	jne Identify_Macros_Loop    ; Keep looping otherwise

	pop rdx                     ; Restore RDX
	pop rcx                     ; Restore RCX
	pop rbx                     ; Restore RBX
	pop rax                     ; Restore RAX
	ret

DEFINE_str:
	db 68, 69, 70, 73, 78, 69, 0


;; match function
;; Receives CHAR* in RAX and CHAR* in RBX
;; Returns 0 (TRUE) or 1 (FALSE) in RAX
match:
	push rbx                    ; Protect RBX
	push rcx                    ; Protect RCX
	push rdx                    ; Protect RDX
	mov rcx, rax                ; S1 in place
	mov rdx, rbx                ; S2 in place
match_Loop:
	mov al, [rcx]               ; S1[0]
	movzx rax, al               ; Make it useful
	mov bl, [rdx]               ; S2[0]
	movzx rbx, bl               ; Make it useful
	cmp rax, rbx                ; See if they match
	jne match_False             ; If not

	add rcx, 1                  ; S1 = S1 + 1
	add rdx, 1                  ; S2 = S2 + 1
	cmp rax, 0                  ; If reached end of string
	je match_Done               ; Perfect match
	jmp match_Loop              ; Otherwise keep looping

match_False:
	mov rax, 1                  ; Return false
match_Done:
	pop rdx                     ; Restore RDX
	pop rcx                     ; Restore RCX
	pop rbx                     ; Restore RBX
	ret


;; Line_Macro function
;; Receives List in RAX
;; Updates the list in place; does not modify registers
;; Uses RAX for I, RBX for I->TEXT, RCX for I->EXPRESSION
Line_Macro:
	push rax                    ; Protect RAX
	push rbx                    ; Protect RBX
	push rcx                    ; Protect RCX
	push rdx                    ; Protect RDX
Line_Macro_Loop:
	mov rbx, [rax+8]            ; I->TYPE
	cmp rbx, 1                  ; IF MACRO == I->TYPE
	jne Line_Macro_Next         ; Otherwise move on

	;; Is a macro apply
	mov rbx, [rax+16]           ; I->TEXT
	mov rcx, [rax+24]           ; I->EXPRESSION
	mov rax, [rax]              ; I->NEXT
	call Set_Expression         ; Apply it
	jmp Line_Macro_Loop         ; Move on to next

Line_Macro_Next:
	mov rax, [rax]              ; I->NEXT
	cmp rax, 0                  ; Check for NULL
	jne Line_Macro_Loop         ; Keep going

	pop rdx                     ; Restore RDX
	pop rcx                     ; Restore RCX
	pop rbx                     ; Restore RBX
	pop rax                     ; Restore RAX
	ret


;; Set_Expression function
;; Receives List in RAX, CHAR* in RBX and CHAR* in RCX
;; Updates the list in place; does not modify registers
;; Uses RBX for C, RCX for EXP and RDX for I
Set_Expression:
	push rax                    ; Protect RAX
	push rbx                    ; Protect RBX
	push rcx                    ; Protect RCX
	push rdx                    ; Protect RDX
	mov rdx, rax                ; Set I
Set_Expression_Loop:
	mov rax, [rdx+8]            ; I->TYPE
	cmp rax, 1                  ; IF MACRO == I->TYPE
	je Set_Expression_Next      ; Ignore and move on

	mov rax, [rdx+16]           ; I->TEXT
	call match                  ; Check for match
	cmp rax, 0                  ; If match
	jne Set_Expression_Next     ; Otherwise next

	;; We have a non-macro match
	mov [rdx+24], rcx           ; I->EXPRESSION = EXP

Set_Expression_Next:
	mov rdx, [rdx]              ; I = I->NEXT
	cmp rdx, 0                  ; IF NULL == I
	jne Set_Expression_Loop     ; Otherwise keep looping

	pop rdx                     ; Restore RDX
	pop rcx                     ; Restore RCX
	pop rbx                     ; Restore RBX
	pop rax                     ; Restore RAX
	ret


;; Process_String function
;; Receives List in RAX
;; Update the list in place; does not modify registers
;; Uses RBX for I->TEXT, RCX for I and RDX for S
Process_String:
	push rax                    ; Protect RAX
	push rbx                    ; Protect RBX
	push rcx                    ; Protect RCX
	push rdx                    ; Protect RDX
	mov rcx, rax                ; I = HEAD
Process_String_loop:
	mov rax, [rcx+8]            ; I->TYPE
	cmp rax, 2                  ; IF STRING == I->TYPE
	jne Process_String_Next     ; Skip to next

	mov rbx, [rcx+16]           ; I->TEXT
	mov al, [rbx]               ; I->TEXT[0]
	movzx rax, al               ; make it useful
	cmp rax, 39                 ; IF '\'' == I->TEXT[0]
	jne Process_String_Raw      ; Deal with '"'

	;; Deal with '\''
	add rbx, 1                  ; I->TEXT + 1
	mov [rcx+24], rbx           ; I->EXPRESSION = I->TEXT + 1
	jmp Process_String_Next     ; Move on to next

Process_String_Raw:
	mov rax, rbx                ; Get length of I->TEXT
	call string_length          ; Do it
	shr rax, 2                  ; LENGTH = LENGTH >> 2
	add rax, 1                  ; LENGTH = LENGTH + 1
	shl rax, 3                  ; LENGTH = LENGTH << 3
	call malloc                 ; Get string
	mov rdx, rbx                ; S = I->TEXT
	add rdx, 1                  ; S = S + 1
	mov [rcx+24], rax           ; I->EXPRESSION = hexify
	mov rbx, rax                ; Put hexify buffer in rbx

Process_String_Raw_Loop:
	mov al, [rdx]               ; Read 1 chars
	movzx rax, al               ; Make it useful
	add rdx, 1                  ; S = S + 1
	cmp al, 0                   ; Check for NULL
	pushf                       ; Protect condition
	call hex8                   ; write them all
	popf                        ; restore condition
	jne Process_String_Raw_Loop ; Keep looping

Process_String_Next:
	mov rcx, [rcx]              ; I = I->NEXT
	cmp rcx, 0                  ; IF NULL == I
	jne Process_String_loop     ; Otherwise keep looping

	pop rdx                     ; Restore RDX
	pop rcx                     ; Restore RCX
	pop rbx                     ; Restore RBX
	pop rax                     ; Restore RAX
	ret


;; string_length function
;; Receives CHAR* in RAX
;; Returns INT in RAX
;; Uses RAX for CH, RBX for S and RCX for INDEX
string_length:
	push rbx                    ; Protect RBX
	push rcx                    ; Protect RCX
	mov rbx, rax                ; Set S
	mov rcx, 0                  ; INDEX = 0
string_length_loop:
	mov al, [rbx+rcx]           ; S[0]
	movzx rax, al               ; make it useful
	cmp rax, 0                  ; IF NULL == S[0]
	je string_length_done       ; Stop

	add rcx, 1                  ; INDEX = INDEX + 1
	jmp string_length_loop      ; Keep going

string_length_done:
	mov rax, rcx                ; RETURN INDEX
	pop rcx                     ; Restore RCX
	pop rbx                     ; Restore RBX
	ret


;; Eval_Immediates function
;; Receives List in RAX
;; Updates the list in place; does not modify registers
;; Uses RBX for I->TEXT[0], RCX for I->TEXT[1] and RDX for I
Eval_Immediates:
	push rax                    ; Protect RAX
	push rbx                    ; Protect RBX
	push rcx                    ; Protect RCX
	push rdx                    ; Protect RDX
	mov rdx, rax                ; I = HEAD
Eval_Immediates_Loop:
	;; Check for MACRO
	mov rax, [rdx+8]            ; I->TYPE
	cmp rax, 1                  ; IF MACRO == I->TYPE
	je Eval_Immediates_Next     ; Skip to next

	;; Check for NULL EXPRESSION
	mov rax, [rdx+24]           ; I->EXPRESSION
	cmp rax, 0                  ; IF NULL == I->EXPRESSION
	jne Eval_Immediates_Next    ; Skip to next

	;; Check if number
	mov rax, [rdx+16]           ; I->TEXT
	mov bl, [rax]               ; I->TEXT[0]
	movzx rbx, bl               ; Extend to use
	add rax, 1                  ; I->TEXT + 1
	mov cl, [rax]               ; I->TEXT[1]
	movzx rcx, cl               ; Extend to use
	call numerate_string        ; Convert string to INT
	cmp rax, 0                  ; IF 0 == numerate_string(I->TEXT + 1)
	jne Eval_Immediates_value   ; Has a value

	;; Last chance for Immediate
	cmp rcx, 48                 ; If '0' == I->TEXT[1]
	jne Eval_Immediates_Next    ; Skip to next

Eval_Immediates_value:
	call express_number         ; Convert value to hex string
	mov [rdx+24], rax           ; I->EXPRESSION = express_number(value, I-TEXT[0])

Eval_Immediates_Next:
	mov rdx, [rdx]              ; I = I->NEXT
	cmp rdx, 0                  ; IF NULL == I
	jne Eval_Immediates_Loop    ; Otherwise keep looping

	pop rdx                     ; Restore RDX
	pop rcx                     ; Restore RCX
	pop rbx                     ; Restore RBX
	pop rax                     ; Restore RAX
	ret


;; numerate_string function
;; Receives CHAR* in RAX
;; Returns value of CHAR* in RAX
;; Uses RAX for VALUE, RBX for S, RCX for CH and RSI for NEGATIVE?
numerate_string:
	push rbx                    ; Protect RBX
	push rcx                    ; Protect RCX
	push rdx                    ; Protect RDX
	push rsi                    ; Protect RSI
	mov rbx, rax                ; put S in correct place
	mov rax, 0                  ; Initialize to Zero
numerate_string_loop:
	mov cl, [rbx+1]             ; S[1]
	movzx rcx, cl               ; make it useful
	cmp rcx, 120                ; IF 'x' == S[1]
	je numerate_hex             ; Deal with hex input

	;; Assume decimal input
	mov rsi, 0                  ; Assume no negation
	mov cl, [rbx]               ; S[0]
	movzx rcx, cl               ; make it useful
	cmp rcx, 45                 ; IF '-' == S[0]
	jne numerate_decimal        ; Skip negation

	mov rsi, 1                  ; Set FLAG
	add rbx, 1                  ; S = S + 1

numerate_decimal:
	mov cl, [rbx]               ; S[0]
	movzx rcx, cl               ; make it useful
	cmp rcx, 0                  ; IF NULL == S[0]
	je numerate_decimal_done    ; We are done

	imul rax, 10                ; VALUE = VALUE * 10
	sub rcx, 48                 ; CH = CH - '0'
	cmp rcx, 9                  ; Check for illegal
	jg numerate_string_fail     ; If CH > '9'
	cmp rcx, 0                  ; Check for illegal
	jl numerate_string_fail     ; IF CH < 0
	add rax, rcx                ; VALUE = VALUE + CH
	add rbx, 1                  ; S = S + 1
	jmp numerate_decimal        ; Keep looping

numerate_decimal_done:
	cmp rsi, 1                  ; Check if need to negate
	jne numerate_string_done    ; Nope

	imul rax, -1                ; VALUE = VALUE * -1
	jmp numerate_string_done    ; Done

numerate_hex:
	add rbx, 2                  ; S = S + 2
numerate_hex_loop:
	mov cl, [rbx]               ; S[0]
	movzx rcx, cl               ; make it useful
	cmp rcx, 0                  ; IF NULL == S[0]
	je numerate_string_done     ; We are done

	shl rax, 4                  ; VALUE = VALUE << 4
	sub rcx, 48                 ; CH = CH - '0'
	cmp rcx, 10                 ; IF 10 >= CH
	jl numerate_hex_digit       ; NO
	sub rcx, 7                  ; Push A-F into range
numerate_hex_digit:
	cmp rcx, 15                 ; Check for illegal
	jg numerate_string_fail     ; If CH > 'F'
	cmp rcx, 0                  ; Check for illegal
	jl numerate_string_fail     ; IF CH < 0
	add rax, rcx                ; VALUE = VALUE + CH
	add rbx, 1                  ; S = S + 1
	jmp numerate_hex_loop       ; Keep looping

numerate_string_fail:
	mov rax, 0                  ; return ZERO

numerate_string_done:
	pop rsi                     ; Restore RSI
	pop rdx                     ; Restore RDX
	pop rcx                     ; Restore RCX
	pop rbx                     ; Restore RBX
	ret


;; express_number function
;; Receives INT in RAX and CHAR in RBX
;; Allocates a string and expresses the value in hex
;; Returns string in RAX
;; Uses RAX for VALUE, RBX for S and RCX for CH
express_number:
	push rbx                    ; Protect RBX
	push rcx                    ; Protect RCX
	push rdx                    ; Protect RDX
	mov rcx, rbx                ; Put CH in right place
	mov rbx, rax                ; Protect VALUE
	cmp rcx, 37                 ; IF '%' == CH
	jne express_number2         ; Otherwise try @

	mov rax, 9                  ; We need 9 bytes
	call malloc                 ; Get S pointer
	xchg rax, rbx               ; Put S and VALUE in place
	push rbx                    ; Protect S
	call hex32l                 ; Store 32bits
	jmp express_number_done     ; done

express_number2:
	cmp rcx, 64                 ; IF '@' == CH
	jne express_number1         ; Othrewise try !

	mov rax, 5                  ; We need 5 bytes
	call malloc                 ; Get S pointer
	xchg rax, rbx               ; Put S and VALUE in place
	push rbx                    ; Protect S
	call hex16l                 ; Store 16bits
	jmp express_number_done     ; done

express_number1:
	mov rax, 3                  ; We need 3 bytes
	call malloc                 ; Get S pointer
	xchg rax, rbx               ; Put S and VALUE in place
	push rbx                    ; Protect S
	call hex8                   ; Store 8bit

express_number_done:
	pop rax                     ; Restore S
	pop rdx                     ; Restore RDX
	pop rcx                     ; Restore RCX
	pop rbx                     ; Restore RBX
	ret


;; HEX to ascii routine
;; Receives INT in RAX and CHAR* in RBX
;; Stores ascii of INT in CHAR*
;; Returns only modifying RAX
hex64l:
	push rax                    ; Protect top 32
	call hex32l                  ; Store it
	pop rax                     ; do top 32
	shr rax, 32                 ; do bottom 32 first
hex32l:
	push rax                    ; Protect top 16
	call hex16l                  ; Store it
	pop rax                     ; do top 16
	shr rax, 16                 ; do bottom 16 first
hex16l:
	push rax                    ; Protect top byte
	call hex8                   ; Store it
	pop rax                     ; do high byte
	shr rax, 8                  ; do bottom byte first
hex8:
	push rax                    ; Protect bottom nibble
	shr rax, 4                  ; do high nibble first
	call hex4                   ; Store it
	pop rax                     ; do low nibble
hex4:
	and rax, 0xf                ; isolate nibble
	add al,'0'                  ; convert to ascii
	cmp al,'9'                  ; valid digit?
	jbe hex1                    ; yes
	add al,7                    ; use alpha range
hex1:
	mov [ebx], al               ; store result
	add ebx, 1                  ; next position
	ret


;; Preserve_Other function
;; Receives List in RAX
;; Updates the list in place; does not modify registers
;; Uses RAX for I, RBX for I->TEXT
Preserve_Other:
	push rax                    ; Protect RAX
	push rbx                    ; Protect RBX
	push rcx                    ; Protect RCX
	push rdx                    ; Protect RDX
Preserve_Other_Loop:
	mov rbx, [rax+24]           ; I->EXPRESSION
	cmp rbx, 0                  ; IF NULL == I->EXPRESSION
	jne Preserve_Other_Next     ; Otherwise next

	;; Needs preserving
	mov rbx, [rax+16]           ; I->TEXT
	mov [rax+24], rbx           ; I->EXPRESSION = I->TEXT

Preserve_Other_Next:
	mov rax, [rax]              ; I = I->NEXT
	cmp rax, 0                  ; IF NULL == I
	jne Preserve_Other_Loop     ; Otherwise keep looping

	pop rdx                     ; Restore RDX
	pop rcx                     ; Restore RCX
	pop rbx                     ; Restore RBX
	pop rax                     ; Restore RAX
	ret


;; Print_Hex function
;; Receives list in RAX
;; walks the list and prints the I->EXPRESSION for all nodes followed by newline
;; Uses RBX for I
Print_Hex:
	push rbx                    ; Protect RBX
	push rcx                    ; Protect RCX
	mov rbx, r13                ; I = Head
Print_Hex_Loop:
	mov rax, [rbx+8]            ; I->TYPE
	cmp rax, 1                  ; IF MACRO == I->TYPE
	je Print_Hex_Next           ; Skip

	mov rax, [rbx + 24]         ; Using EXPRESSION
	call File_Print             ; Print it
	mov rax, 10                 ; NEWLINE
	call fputc                  ; Append it

Print_Hex_Next:
	mov rbx, [rbx]              ; Iterate to next Token
	cmp rbx, 0                  ; Check for NULL
	jne Print_Hex_Loop          ; Otherwise keep looping

	pop rcx                     ; Restore RCX
	pop rbx                     ; Restore RBX
	ret


;; File_Print function
;; Receives CHAR* in RAX
;; calls fputc for every non-null char
File_Print:
	push rbx                    ; Protect RBX
	push rcx                    ; Protect RCX
	mov rbx, rax                ; Protect S
	cmp rax, 0                  ; Protect against nulls
	je File_Print_Done          ; Simply don't try to print them
File_Print_Loop:
	mov al, [rbx]               ; Read byte
	movzx rax, al               ; zero extend
	cmp rax, 0                  ; Check for NULL
	je File_Print_Done          ; Stop at NULL

	call fputc                  ; write it
	add rbx, 1                  ; S = S + 1
	jmp File_Print_Loop         ; Keep going

File_Print_Done:
	pop rcx                     ; Restore RCX
	pop rbx                     ; Restore RBX
	ret


;; fputc function
;; receives CHAR in RAX and FILE* in R14
;; writes char and returns
fputc:
	push rax                    ; We are writing rax
	lea rsi, [rsp]              ; Get stack address
	mov rdi, r14                ; Write to target file
	mov rax, 1                  ; the syscall number for write
	push rdx                    ; Protect RDX
	mov rdx, 1                  ; set the size of chars we want
	push r11                    ; Protect HEAD
	syscall                     ; call the Kernel
	pop r11                     ; Restore HEAD
	pop rdx                     ; Restore RDX
	pop rax                     ; Restore stack
	ret
