## Copyright (C) 2021 Andrius Štikonas
## 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/>.

## ELF Header

:ELF_base
7F 45 4C 46        ## e_ident[EI_MAG0-3] ELF's magic number

02                 ## e_ident[EI_CLASS] Indicating 64 bit
01                 ## e_ident[EI_DATA] Indicating little endianness
01                 ## e_ident[EI_VERSION] Indicating original elf

03                 ## e_ident[EI_OSABI] Set at 3 because FreeBSD is strict
00                 ## e_ident[EI_ABIVERSION] Set at 0 because none cares

00 00 00 00 00 00 00 ## e_ident[EI_PAD]
02 00              ## e_type Indicating Executable
F3 00              ## e_machine Indicating RISC-V
01 00 00 00        ## e_version Indicating original elf

&_start 00 00 00 00 ## e_entry Address of the entry point (Number of bytes this header is + Base Address)
%ELF_program_headers>ELF_base 00 00 00 00 ## e_phoff Address of program header table
00 00 00 00 00 00 00 00 ## e_shoff Address of section header table

00 00 00 00        ## e_flags
40 00              ## e_ehsize Indicating our 64 Byte header

38 00              ## e_phentsize size of a program header table
01 00              ## e_phnum number of entries in program table

00 00              ## e_shentsize size of a section header table
00 00              ## e_shnum number of entries in section table

00 00              ## e_shstrndx index of the section names

## Program Header
:ELF_program_headers
01 00 00 00             ## p_type
07 00 00 00             ## ph_flags: PF-X|PF-W|PF-R = 7
00 00 00 00 00 00 00 00 ## p_offset

&ELF_base 00 00 00 00 ## p_vaddr
&ELF_base 00 00 00 00 ## p_physaddr

%ELF_end>ELF_base 00 00 00 00 ## p_filesz
%ELF_end>ELF_base 00 00 00 00 ## p_memsz

01 00 00 00 00 00 00 00 ## Required alignment

:ELF_text
; Register use:
; s2: input fd
; s3: output fd
; s4: toggle
; s5: hold
; s6: ip
; s7: tempword
; s8: shiftregister

; Our main function
:_start

    03 36 01 01     # RD_A2 RS1_SP !16 LD               ; Input file name

    ; Initialize globals
    13 0A F0 FF     # RD_S4 !-1 ADDI                    ; Toggle
    93 0A 00 00     # RD_S5 ADDI                        ; Hold
    13 0B 00 00     # RD_S6 ADDI                        ; Instruction Pointer

    ; Open input file and store FD in s2
    93 08 80 03     # RD_A7 !56 ADDI                    ; sys_openat
    13 05 C0 F9     # RD_A0 !-100 ADDI                  ; AT_FDCWD
    93 05 06 00     # RD_A1 RS1_A2 MV                   ; file name
    13 06 00 00     # RD_A2 ADDI                        ; read only
    73 00 00 00     # ECALL                             ; syscall
    @Fail 63 40 05 00 # RS1_A0 @Fail BLTZ               ; Error opening file
    13 09 05 00     # RD_S2 RS1_A0 MV                   ; Save fd in for later

    ; Set default FD for output file to stdout
    93 09 10 00     # RD_S3 !1 ADDI

    ; If we only have 2 arguments, don't use the third (it's not set)
    93 02 20 00     # RD_T0 !2 ADDI
    03 35 01 00     # RD_A0 RS1_SP LD                   ; Get number of the args
    @Fail 63 40 55 00 # RS1_A0 RS2_T0 @Fail BLT         ; No input file provided
    @after_open 63 00 55 00 # RS1_A0 RS2_T0 @after_open BEQ ; No output file provided. Use stdout

    ; Open output file and store the FD in s3
    93 08 80 03     # RD_A7 !56 ADDI                    ; sys_openat
    13 05 C0 F9     # RD_A0 !-100 ADDI                  ; AT_FDCWD
    83 35 81 01     # RD_A1 RS1_SP !24 LD               ; Output file (argument 3)
    13 06 10 24     # RD_A2 !00001101 ADDI              ; decimal 577
    ; O_TRUNC   00001000
    ; O_CREAT   00000100
    ; O_WRONLY  00000001
    ; OCTAL!
    93 06 00 1C     # RD_A3 !00700 ADDI                 ; Set read, write, execute permission on user
    ; S_IRWXU  00700
    ; OCTAL!
    73 00 00 00     # ECALL                             ; syscall
    93 09 05 00     # RD_S3 RS1_A0 MV                   ; Save fd in for later

:after_open
    $First_pass EF 00 00 00 # RD_RA $First_pass JAL     ; First pass

    ; Rewind input file
    93 08 E0 03     # RD_A7 !62 ADDI                    ; sys_lseek
    13 05 09 00     # RD_A0 RS1_S2 MV                   ; Input file descriptor
    93 05 00 00     # RD_A1 MV                          ; Set offset to zero
    13 06 00 00     # RD_A2 MV                          ; Set whence to zero
    73 00 00 00     # ECALL                             ; syscall

    ; Initialize globals
    13 0A F0 FF     # RD_S4 !-1 ADDI                    ; Toggle
    93 0A 00 00     # RD_S5 ADDI                        ; Hold
    13 0B 00 00     # RD_S6 ADDI                        ; Instruction Pointer
    93 0B 00 00     # RD_S7 ADDI                        ; tempword
    13 0C 00 00     # RD_S8 ADDI                        ; Shift register

    $Second_pass EF 00 00 00 # RD_RA $Second_pass JAL   ; Now do the second pass

    $Done 6F 00 00 00 # $Done JAL                       ; We are done

; First pass loop to determine addresses of labels
:First_pass
    13 01 81 FF     # RD_SP RS1_SP !-8 ADDI             ; Allocate stack
    23 30 11 00     # RS1_SP RS2_RA SD                  ; protect ra

:First_pass_loop
    $Read_byte EF 00 00 00 # RD_RA $Read_byte JAL       ; Get another byte

    ; Deal with EOF
    13 03 C0 FF     # RD_T1 !-4 ADDI
    @First_pass_done 63 00 65 00 # RS1_A0 RS2_T1 @First_pass_done BEQ

    ; Check for :
    13 03 A0 03     # RD_T1 !0x3a ADDI
    @First_pass_0 63 10 65 00 # RS1_A0 RS2_T1 @First_pass_0 BNE
    $StoreLabel EF 00 00 00 # RD_RA $StoreLabel JAL     ; Store this label

:First_pass_0
    ; Check for !
    13 03 10 02     # RD_T1 !0x21 ADDI
    @Throwaway_token 63 00 65 00 # RS1_A0 RS2_T1 @Throwaway_token BEQ

    ; Check for @
    13 03 00 04     # RD_T1 !0x40 ADDI
    @Throwaway_token 63 00 65 00 # RS1_A0 RS2_T1 @Throwaway_token BEQ

    ; Check for $
    13 03 40 02     # RD_T1 !0x24 ADDI
    @Throwaway_token 63 00 65 00 # RS1_A0 RS2_T1 @Throwaway_token BEQ

    ; Check for ~
    13 03 E0 07     # RD_T1 !0x7e ADDI
    @Throwaway_token 63 00 65 00 # RS1_A0 RS2_T1 @Throwaway_token BEQ

    93 05 F0 FF     # RD_A1 !-1 ADDI                    ; write = false
    $DoByte EF 00 00 00 # RD_RA $DoByte JAL             ; Deal with everything else

    13 03 C0 FF     # RD_T1 !-4 ADDI                    ; Deal with EOF
    @First_pass_done 63 00 65 00 # RS1_A0 RS2_T1 @First_pass_done BEQ

    $First_pass_loop 6F 00 00 00 # $First_pass_loop JAL ; Keep looping

:Throwaway_token
    ; Deal with Pointer to label
    $Read_byte EF 00 00 00 # RD_RA $Read_byte JAL       ; Drop the char
    $First_pass_loop 6F 00 00 00 # $First_pass_loop JAL ; Loop again

:First_pass_done
    83 30 01 00     # RD_RA RS1_SP LD                   ; restore ra
    13 01 81 00     # RD_SP RS1_SP !8 ADDI              ; deallocate stack
    67 80 00 00     # RS1_RA JALR                       ; return

:Second_pass
    13 01 81 FF     # RD_SP RS1_SP !-8 ADDI             ; Allocate stack
    23 30 11 00     # RS1_SP RS2_RA SD                  ; protect ra

:Second_pass_loop
    $Read_byte EF 00 00 00 # RD_RA $Read_byte JAL       ; Read another byte

    ; Deal with EOF
    13 03 C0 FF     # RD_T1 !-4 ADDI                    ; Deal with EOF
    @Second_pass_done 63 00 65 00 # RS1_A0 RS2_T1 @Second_pass_done BEQ

    ; Drop the label
    13 03 A0 03     # RD_T1 !0x3a ADDI
    @Second_pass_0 63 10 65 00 # RS1_A0 RS2_T1 @Second_pass_0 BNE

    $Read_byte EF 00 00 00 # RD_RA $Read_byte JAL       ; Read the label
    $Second_pass_loop 6F 00 00 00 # $Second_pass_loop JAL ; Continue looping

:Second_pass_0
    ; Check for !
    13 03 10 02     # RD_T1 !0x21 ADDI
    @UpdateShiftRegister 63 00 65 00 # RS1_A0 RS2_T1 @UpdateShiftRegister BEQ

    ; Check for @
    13 03 00 04     # RD_T1 !0x40 ADDI
    @UpdateShiftRegister 63 00 65 00 # RS1_A0 RS2_T1 @UpdateShiftRegister BEQ

    ; Check for $
    13 03 40 02     # RD_T1 !0x24 ADDI
    @UpdateShiftRegister 63 00 65 00 # RS1_A0 RS2_T1 @UpdateShiftRegister BEQ

    ; Check for ~
    13 03 E0 07     # RD_T1 !0x7e ADDI
    @UpdateShiftRegister 63 00 65 00 # RS1_A0 RS2_T1 @UpdateShiftRegister BEQ

    ; Deal with everything else
    93 05 00 00     # RD_A1 MV                          ; write = true
    $DoByte EF 00 00 00 # RD_RA $DoByte JAL             ; Process our char

    # Deal with EOF
    13 03 C0 FF     # RD_T1 !-4 ADDI
    @Second_pass_done 63 00 65 00 # RS1_A0 RS2_T1 @Second_pass_done BEQ ; We are done

    $Second_pass_loop 6F 00 00 00 # $Second_pass_loop JAL ; continue looping

:UpdateShiftRegister
    93 05 05 00     # RD_A1 RS1_A0 MV                   ; Store label prefix
    $Get_table_target EF 00 00 00 # RD_RA $Get_table_target JAL ; Get target
    03 35 05 00     # RD_A0 RS1_A0 LD                   ; Dereference pointer
    33 05 65 41     # RD_A0 RS1_A0 RS2_S6 SUB           ; target - ip

    ; Check for !
    13 03 10 02     # RD_T1 !0x21 ADDI
    @UpdateShiftRegister_I 63 80 65 00 # RS1_A1 RS2_T1 @UpdateShiftRegister_I BEQ

    ; Check for @
    13 03 00 04     # RD_T1 !0x40 ADDI
    @UpdateShiftRegister_B 63 80 65 00 # RS1_A1 RS2_T1 @UpdateShiftRegister_B BEQ

    ; Check for $
    13 03 40 02     # RD_T1 !0x24 ADDI
    @UpdateShiftRegister_J 63 80 65 00 # RS1_A1 RS2_T1 @UpdateShiftRegister_J BEQ

    ; Check for ~
    13 03 E0 07     # RD_T1 !0x7e ADDI
    @UpdateShiftRegister_U 63 80 65 00 # RS1_A1 RS2_T1 @UpdateShiftRegister_U BEQ

    $Second_pass_loop 6F 00 00 00 # $Second_pass_loop JAL ; continue looping

:UpdateShiftRegister_I
    ; Corresponds to RISC-V I format
    13 05 45 00     # RD_A0 RS1_A0 !4 ADDI              ; add 4 due to this being 2nd part of AUIPC combo

    37 13 00 00     # RD_T1 ~0xfff LUI                  ; load higher bits
    1B 03 F3 FF     # RD_T1 RS1_T1 !0xfff ADDIW
    33 73 65 00     # RD_T1 RS1_A0 RS2_T1 AND           ; (value & 0xfff)
    93 1B 43 01     # RD_S7 RS1_T1 RS2_X20 SLLI         ; tempword = (value & 0xfff) << 20
    33 4C 7C 01     # RD_S8 RS1_S8 RS2_S7 XOR           ; shiftregister = shiftregister ^ tempword

    $Second_pass_loop 6F 00 00 00 # $Second_pass_loop JAL ; continue looping

:UpdateShiftRegister_B
    ; Corresponds to RISC-V B format

    ; tempword = ((value & 0x1e) << 7)            ; imm[4:1]
    ;          | ((value & 0x7e0) << (31 - 11))   ; imm[10:5]
    ;          | ((value & 0x800) >> 4)           ; imm[11]
    ;          | ((value & 0x1000) << (31 - 12))  ; imm[12]

    13 03 E0 01     # RD_T1 !0x1e ADDI
    33 73 65 00     # RD_T1 RS1_A0 RS2_T1 AND           ; value & 0x1e
    93 12 73 00     # RD_T0 RS1_T1 RS2_X7 SLLI          ; tempword = (value & 0x1e) << 7

    13 03 00 7E     # RD_T1 !0x7e0 ADDI
    33 73 65 00     # RD_T1 RS1_A0 RS2_T1 AND           ; value & 0x7e0
    13 13 43 01     # RD_T1 RS1_T1 RS2_X20 SLLI         ; (value & 0x7e0) << (31 - 11)
    B3 E2 62 00     # RD_T0 RS1_T0 RS2_T1 OR            ; logical or with the previous expression

    37 13 00 00     # RD_T1 ~0x800 LUI                  ; load higher bits
    1B 03 03 80     # RD_T1 RS1_T1 !0x800 ADDIW
    33 73 65 00     # RD_T1 RS1_A0 RS2_T1 AND           ; value & 0x800
    13 53 43 00     # RD_T1 RS1_T1 RS2_X4 SRLI          ; (value & 0x800) >> 4
    B3 E2 62 00     # RD_T0 RS1_T0 RS2_T1 OR            ; logical or with the previous expression

    37 13 00 00     # RD_T1 ~0x1000 LUI                 ; load higher bits
    33 73 65 00     # RD_T1 RS1_A0 RS2_T1 AND           ; value & 0x1000
    13 13 33 01     # RD_T1 RS1_T1 RS2_X19 SLLI         ; (value & 0x1000) << (31 - 12)
    B3 EB 62 00     # RD_S7 RS1_T0 RS2_T1 OR            ; logical or with the previous expression

    33 4C 7C 01     # RD_S8 RS1_S8 RS2_S7 XOR           ; shiftregister = shiftregister ^ tempword

    $Second_pass_loop 6F 00 00 00 # $Second_pass_loop JAL ; continue looping

:UpdateShiftRegister_J
    ; Corresponds to RISC-V J format

    ; tempword = ((value & 0x7fe) << (30 - 10))    ; imm[10:1]
    ;          | ((value & 0x800) << (20 - 11))    ; imm[11]
    ;          | ((value & 0xff000))               ; imm[19:12]
    ;          | ((value & 0x100000) << (31 - 20)) ; imm[20]

    13 03 E0 7F     # RD_T1 !0x7fe ADDI
    33 73 65 00     # RD_T1 RS1_A0 RS2_T1 AND           ; value & 0x7fe
    93 12 43 01     # RD_T0 RS1_T1 RS2_X20 SLLI         ; tempword = (value & 0x7fe) << 20

    37 13 00 00     # RD_T1 ~0x800 LUI                  ; load higher bits
    1B 03 03 80     # RD_T1 RS1_T1 !0x800 ADDIW
    33 73 65 00     # RD_T1 RS1_A0 RS2_T1 AND           ; value & 0x800
    13 13 93 00     # RD_T1 RS1_T1 RS2_X9 SLLI          ; (value & 0x800) << (20 - 11)
    B3 E2 62 00     # RD_T0 RS1_T0 RS2_T1 OR            ; logical or with the previous expression

    37 F3 0F 00     # RD_T1 ~0xff000 LUI                ; load higher bits
    33 73 65 00     # RD_T1 RS1_A0 RS2_T1 AND           ; value & 0xff000
    B3 E2 62 00     # RD_T0 RS1_T0 RS2_T1 OR            ; logical or with the previous expression

    37 03 10 00     # RD_T1 ~0x100000 LUI               ; load higher bits
    33 73 65 00     # RD_T1 RS1_A0 RS2_T1 AND           ; value & 0x100000
    13 13 B3 00     # RD_T1 RS1_T1 RS2_X11 SLLI         ; (value & 0x100000) << (31 - 20)
    B3 EB 62 00     # RD_S7 RS1_T0 RS2_T1 OR            ; logical or with the previous expression

    33 4C 7C 01     # RD_S8 RS1_S8 RS2_S7 XOR           ; shiftregister = shiftregister ^ tempword

    $Second_pass_loop 6F 00 00 00 # $Second_pass_loop JAL ; continue looping

:UpdateShiftRegister_U
    ; Corresponds to RISC-V U format
    ; if value is 0x800 or more we have to add 11-th bit (0x1000) to compensate for signed extension

    B7 12 00 00     # RD_T0 ~0x800 LUI                  ; load higher bits
    9B 82 02 80     # RD_T0 RS1_T0 !0x800 ADDIW
    37 13 00 00     # RD_T1 ~0xfff LUI                  ; load higher bits
    1B 03 F3 FF     # RD_T1 RS1_T1 !0xfff ADDIW

    ; We are outside 31-bit that ~ can normally load
    B7 03 10 00     # RD_T2 ~0x100000 LUI               ; load 0xfffff000
    9B 83 F3 FF     # RD_T2 RS1_T2 !-1 ADDIW            ; load 0xfffff000
    93 93 C3 00     # RD_T2 RS1_T2 RS2_X12 SLLI         ; load 0xfffff000
    33 73 65 00     # RD_T1 RS1_A0 RS2_T1 AND           ; value & 0xfff
    B3 7B 75 00     # RD_S7 RS1_A0 RS2_T2 AND           ; value & 0xfffff000
    @UpdateShiftRegister_U_small 63 40 53 00 # RS1_T1 RS2_T0 @UpdateShiftRegister_U_small BLT

    # Deal with sign extension: add 0x1000
    B7 12 00 00     # RD_T0 ~0x1000 LUI                 ; load higher bits
    BB 8B 72 01     # RD_S7 RS1_T0 RS2_S7 ADDW          ; (value & 0xfffff000) + 0x1000

:UpdateShiftRegister_U_small
    33 4C 7C 01     # RD_S8 RS1_S8 RS2_S7 XOR           ; shiftregister = shiftregister ^ tempword

    $Second_pass_loop 6F 00 00 00 # $Second_pass_loop JAL ; continue looping

:Second_pass_done
    83 30 01 00     # RD_RA RS1_SP LD                   ; restore ra
    13 01 81 00     # RD_SP RS1_SP !8 ADDI              ; deallocate stack
    67 80 00 00     # RS1_RA JALR                       ; return


; DoByte function
; Receives:
;   character in a0
;   bool write in a1
; Does not return anything
:DoByte
    13 01 81 FF     # RD_SP RS1_SP !-8 ADDI             ; Allocate stack
    23 30 11 00     # RS1_SP RS2_RA SD                  ; protect ra

    $hex EF 00 00 00 # RD_RA $hex JAL                   ; Process hex, store it in a6

    @DoByte_Done 63 40 08 00 # RS1_A6 @DoByte_Done BLTZ ; Deal with EOF and unrecognized characters

    @DoByte_NotToggle 63 10 0A 00 # RS1_S4 @DoByte_NotToggle BNEZ ; Check if toggle is set

    ; toggle = true
    @DoByte_1 63 90 05 00 # RS1_A1 @DoByte_1 BNEZ       ; check if we have to write

    ; write = true
    ; We calculate (hold * 16) + hex(c) ^ sr_nextb()
    ; First, calculate new shiftregister
    93 02 F0 0F     # RD_T0 !0xff ADDI
    B3 72 5C 00     # RD_T0 RS1_S8 RS2_T0 AND           ; sr_nextb = shiftregister & 0xff
    13 5C 8C 00     # RD_S8 RS1_S8 RS2_X8 SRLI          ; shiftregister >> 8

    B3 C2 02 01     # RD_T0 RS1_T0 RS2_A6 XOR           ; hex(c) ^ sr_nextb
    13 93 4A 00     # RD_T1 RS1_S5 RS2_X4 SLLI          ; hold << 4
    33 85 62 00     # RD_A0 RS1_T0 RS2_T1 ADD           ; (hold << 4) + hex(c) ^ sr_nextb()
    $fputc EF 00 00 00 # RD_RA $fputc JAL               ; print it
    @Fail 63 00 05 00 # RS1_A0 @Fail BEQZ               ; Fail if nothing was written

:DoByte_1
    13 0B 1B 00     # RD_S6 RS1_S6 !1 ADDI              ; Increment IP
    93 0A 00 00     # RD_S5 MV                          ; hold = 0
    $DoByte_FlipToggle 6F 00 00 00 # $DoByte_FlipToggle JAL ; return

:DoByte_NotToggle
    93 0A 08 00     # RD_S5 RS1_A6 MV                   ; hold = hex(c)

:DoByte_FlipToggle
    13 4A FA FF     # RD_S4 RS1_S4 NOT                  ; Flip the toggle

:DoByte_Done
    83 30 01 00     # RD_RA RS1_SP LD                   ; restore ra
    13 01 81 00     # RD_SP RS1_SP !8 ADDI              ; deallocate stack
    67 80 00 00     # RS1_RA JALR                       ; return

; Convert ASCII hex characters into binary representation, e.g. 'a' -> 0xA
; Receives:
;   character in a0
; Returns:
;   a6 with character's hex value.
:hex
    13 01 01 FF     # RD_SP RS1_SP !-16 ADDI            ; Allocate stack
    23 30 11 00     # RS1_SP RS2_RA SD                  ; protect ra
    23 34 B1 00     # RS1_SP RS2_A1 @8 SD               ; protect a1

    ; Deal with EOF
    13 03 C0 FF     # RD_T1 !-4 ADDI
    @hex_return 63 00 65 00 # RS1_A0 RS2_T1 @hex_return BEQ

    ; deal with line comments starting with #
    13 03 30 02     # RD_T1 !0x23 ADDI
    @ascii_comment 63 00 65 00 # RS1_A0 RS2_T1 @ascii_comment BEQ ; a0 eq to '#'

    ; deal with line comments starting with ;
    13 03 B0 03     # RD_T1 !0x3b ADDI
    @ascii_comment 63 00 65 00 # RS1_A0 RS2_T1 @ascii_comment BEQ  ; a0 eq to ';'

    ; deal all ascii less than 0
    13 03 00 03     # RD_T1 !0x30 ADDI
    @ascii_other 63 40 65 00 # RS1_A0 RS2_T1 @ascii_other BLT

    ; deal with 0-9
    13 03 A0 03     # RD_T1 !0x3a ADDI
    @ascii_num 63 40 65 00 # RS1_A0 RS2_T1 @ascii_num BLT

    ; deal with all ascii less than A
    13 03 10 04     # RD_T1 !0x41 ADDI
    @ascii_other 63 40 65 00 # RS1_A0 RS2_T1 @ascii_other BLT

    ; deal with A-F
    13 03 70 04     # RD_T1 !0x47 ADDI
    @ascii_high 63 40 65 00 # RS1_A0 RS2_T1 @ascii_high BLT

    ; deal with all ascii less than a
    13 03 10 06     # RD_T1 !0x61 ADDI
    @ascii_other 63 40 65 00 # RS1_A0 RS2_T1 @ascii_other BLT

    ; deal with a-f
    13 03 70 06     # RD_T1 !0x67 ADDI
    @ascii_low 63 40 65 00 # RS1_A0 RS2_T1 @ascii_low BLT

    ; The rest that remains needs to be ignored
    $ascii_other 6F 00 00 00 # $ascii_other JAL

:ascii_num
    13 03 00 03     # RD_T1 !0x30 ADDI                  ; '0' -> 0
    33 08 65 40     # RD_A6 RS1_A0 RS2_T1 SUB
    $hex_return 6F 00 00 00 # $hex_return JAL           ; return
:ascii_low
    13 03 70 05     # RD_T1 !0x57 ADDI                  ; 'a' -> 0xA
    33 08 65 40     # RD_A6 RS1_A0 RS2_T1 SUB
    $hex_return 6F 00 00 00 # $hex_return JAL           ; return
:ascii_high
    13 03 70 03     # RD_T1 !0x37 ADDI                  ; 'A' -> 0xA
    33 08 65 40     # RD_A6 RS1_A0 RS2_T1 SUB
    $hex_return 6F 00 00 00 # $hex_return JAL           ; return
:ascii_other
    13 08 F0 FF     # RD_A6 !-1 ADDI                    ; Return -1
    $hex_return 6F 00 00 00 # $hex_return JAL           ; return
:ascii_comment                        ; Read the comment until newline
    $Read_byte EF 00 00 00 # RD_RA $Read_byte JAL
    13 03 D0 00     # RD_T1 !0xd ADDI                   ; CR
    @ascii_comment_cr 63 00 65 00 # RS1_A0 RS2_T1 @ascii_comment_cr BEQ
    13 03 A0 00     # RD_T1 !0xa ADDI                   ; LF
    @ascii_comment 63 10 65 00 # RS1_A0 RS2_T1 @ascii_comment BNE  ; Keep reading comment
:ascii_comment_cr
    13 08 F0 FF     # RD_A6 !-1 ADDI                    ; Return -1
:hex_return
    83 30 01 00     # RD_RA RS1_SP LD                   ; restore ra
    83 35 81 00     # RD_A1 RS1_SP !8 LD                ; restore a1
    13 01 01 01     # RD_SP RS1_SP !16 ADDI             ; Deallocate stack
    67 80 00 00     # RS1_RA JALR                       ; return

; Read byte into a0
:Read_byte
    13 01 01 FF     # RD_SP RS1_SP !-16 ADDI            ; Allocate stack
    23 34 B1 00     # RS1_SP RS2_A1 @8 SD               ; protect a1

    93 08 F0 03     # RD_A7 !63 ADDI                    ; sys_read
    13 05 09 00     # RD_A0 RS1_S2 MV                   ; File descriptor
    93 05 01 00     # RD_A1 RS1_SP MV                   ; Get stack address for buffer
    13 00 00 00     # NOP                               ; no-op
    13 06 10 00     # RD_A2 !1 ADDI                     ; Size of what we want to read
    73 00 00 00     # ECALL                             ; syscall

    @Read_byte_1 63 00 05 00 # RS1_A0 @Read_byte_1 BEQZ ; Deal with EOF
    03 85 05 00     # RD_A0 RS1_A1 LB                   ; Dereference pointer

    $Read_byte_done 6F 00 00 00 # $Read_byte_done JAL   ; return

:Read_byte_1
    13 05 C0 FF     # RD_A0 !-4 ADDI                    ; Put EOF in a0
:Read_byte_done
    83 35 81 00     # RD_A1 RS1_SP !8 LD                ; restore a1
    13 01 01 01     # RD_SP RS1_SP !16 ADDI             ; Deallocate stack
    67 80 00 00     # RS1_RA JALR                       ; return

; Reads a byte and calculates table address
; Returns a pointer in a0
:Get_table_target
    13 01 81 FF     # RD_SP RS1_SP !-8 ADDI             ; Allocate stack
    23 30 11 00     # RS1_SP RS2_RA SD                  ; protect ra

    $Read_byte EF 00 00 00 # RD_RA $Read_byte JAL       ; Get single char label
    13 15 35 00     # RD_A0 RS1_A0 RS2_X3 SLLI          ; Each label in table takes 8 bytes to store
    97 02 00 00     # RD_T0 ~table AUIPC                ; Load address of table
    !table 93 82 02 00 # RD_T0 RS1_T0 !table ADDI       ; into register t0
    33 05 55 00     # RD_A0 RS1_A0 RS2_T0 ADD           ; Calculate offset

    83 30 01 00     # RD_RA RS1_SP LD                   ; restore ra
    13 01 81 00     # RD_SP RS1_SP !8 ADDI              ; deallocate stack
    67 80 00 00     # RS1_RA JALR                       ; return

:StoreLabel
    13 01 81 FF     # RD_SP RS1_SP !-8 ADDI             ; Allocate stack
    23 30 11 00     # RS1_SP RS2_RA SD                  ; protect ra

    $Get_table_target EF 00 00 00 # RD_RA $Get_table_target JAL
    23 30 65 01     # RS1_A0 RS2_S6 SD                  ; Store ip into table target

    83 30 01 00     # RD_RA RS1_SP LD                   ; restore ra
    13 01 81 00     # RD_SP RS1_SP !8 ADDI              ; deallocate stack
    67 80 00 00     # RS1_RA JALR                       ; return

; fputc function
; Receives CHAR in a0
; Writes and returns number of bytes written in a0
:fputc
    13 01 01 FE     # RD_SP RS1_SP !-32 ADDI            ; allocate stack
    23 30 A1 00     # RS1_SP RS2_A0 SD                  ; protect a0
    23 34 11 00     # RS1_SP RS2_RA @8 SD               ; protect ra
    23 38 B1 00     # RS1_SP RS2_A1 @16 SD              ; protect a1
    23 3C C1 00     # RS1_SP RS2_A2 @24 SD              ; protect a2

    93 08 00 04     # RD_A7 !64 ADDI                    ; sys_write
    13 85 09 00     # RD_A0 RS1_S3 MV                   ; write to output
    93 05 01 00     # RD_A1 RS1_SP MV                   ; Get stack address
    13 06 10 00     # RD_A2 !1 ADDI                     ; write 1 character
    73 00 00 00     # ECALL                             ; syscall

    83 30 81 00     # RD_RA RS1_SP !8 LD                ; restore ra
    83 35 01 01     # RD_A1 RS1_SP !16 LD               ; restore a1
    03 36 81 01     # RD_A2 RS1_SP !24 LD               ; restore a2
    13 01 01 02     # RD_SP RS1_SP !32 ADDI             ; Deallocate stack
    67 80 00 00     # RS1_RA JALR                       ; return

:Done
    ; Terminate program with 0 return code
    93 08 D0 05     # RD_A7 !93 ADDI                    ; sys_exit
    13 05 00 00     # RD_A0 MV                          ; Return code 0
    73 00 00 00     # ECALL                             ; exit(0)
:Fail
    ; Terminate program with 1 return code
    93 08 D0 05     # RD_A7 !93 ADDI                    ; sys_exit
    13 05 10 00     # RD_A0 !1 ADDI                     ; Return code 1
    73 00 00 00     # ECALL                             ; exit(1)
# PROGRAM END

:table
    00

:ELF_end
