; initialize global_variables_end C=:global_variables_end D=:global_variables 8C=D ; initialize static_memory_end C=:static_memory_end D=x500000 8C=D ; initialize labels_end C=:labels_end D=:labels 8C=D I=8S A=d3 ?I!A:usage_error ; open input file J=S ; argv[1] is at *(rsp+16) J+=d16 J=8J I=d0 syscall x2 J=A ?J<0:input_file_error ; open output file J=S ; argv[2] is at *(rsp+24) J+=d24 J=8J I=x241 D=x1ed syscall x2 J=A ?J<0:output_file_error :second_pass_starting_point ; write ELF header J=d4 I=:ELF_header D=x78 syscall x1 :read_line ; increment line number D=:line_number C=8D C+=d1 8D=C ; use rbp to store line pointer R=:line :read_line_loop ; read 1 byte into rbp J=d3 I=R D=d1 syscall x0 D=A ?D=0:eof ; check if the character was a newline: C=1R D=xa ?C=D:read_line_loop_end ; check if the character was a tab: D=x9 ; if so, don't increment rbp ?C=D:read_line_loop ; check if the character was a semicolon: D='; ; if so, it's a comment ?C=D:handle_comment R+=d1 !:read_line_loop :handle_comment ; read out rest of line from file J=d3 I=R D=d1 syscall x0 D=A ?D=0:eof C=1R D=xa ; if we didn't reach the end of the line, keep going ?C!D:handle_comment !:read_line_loop_end :read_line_loop_end ; remove whitespace (specifically, ' ' characters) at end of line I=R :remove_terminal_whitespace_loop I-=d1 C=1I D=x20 ?C!D:remove_terminal_whitespace_loop_end ; replace ' ' with a newline D=xa 1I=D !:remove_terminal_whitespace_loop :remove_terminal_whitespace_loop_end ; check if this is a blank line C=:line D=1C C=xa ?C=D:read_line C=': ?C=D:handle_label_definition I=:line J=:"global" C=x20 call :string= D=A ?D!0:handle_global I=:line J=:"local" C=x20 call :string= D=A ?D!0:handle_local I=:line J=:"argument" C=x20 call :string= D=A ?D!0:handle_local I=:line J=:"return" C=x20 call :string= D=A ?D!0:handle_return C=xa I=:line J=:"function" call :string= D=A ?D!0:handle_function !:read_line :eof C=:second_pass D=1C ?D!0:exit_success ; set 2nd pass to 1 1C=d1 ; seek both files back to start J=d3 I=d0 D=d0 syscall x8 J=d4 I=d0 D=d0 syscall x8 !:second_pass_starting_point :exit_success J=d0 syscall x3c align :local_start reserve d8 :handle_local R=I ; emit sub rsp, 8 J=d4 I=:sub_rsp_8 D=d7 syscall x1 I=R ; skip ' ' I+=d1 call :read_type R=A I+=d1 ; check if already defined C=:local_start 8C=I J=:local_variables D=d5 call :ident_lookup C=A ?C!0:local_redeclaration C=:local_start I=8C J=:local_variables_end J=8J call :ident_copy ; store type 1J=R J+=d1 ; increase stack_end, store it in J C=:stack_end D=4C D+=d8 4C=D 4J=D J+=d4 ; store null terminator 1J=0 ; update :stack_end D=:stack_end C=8D C+=d8 8D=C ; update :local_variables_end I=:local_variables_end 8I=J ; read the next line !:read_line :sub_rsp_8 x48 x81 xec x08 x00 x00 x00 align :global_start reserve d8 :handle_global ; ignore if this is the second pass C=:second_pass C=1C ?C!0:read_line ; skip ' ' I+=d1 call :read_type ; put type in R R=A ; skip ' ' after type I+=d1 ; check if already defined C=:global_start 8C=I J=:global_variables D=d5 call :ident_lookup C=A ?C!0:global_redeclaration C=:global_start I=8C J=:global_variables_end J=8J call :ident_copy ; store type 1J=R J+=d1 ; store address D=:static_memory_end D=4D 4J=D J+=d4 ; store null terminator 1J=0 ; update :static_memory_end D=:static_memory_end C=8D C+=d8 8D=C ; update :global_variables_end I=:global_variables_end 8I=J ; go read the next line !:read_line :handle_function ; emit prologue J=d4 I=:function_prologue D=d14 syscall x1 ; reset local variable table D=:local_variables 1D=0 C=:local_variables_end 8C=D ; reset stack_end D=:stack_end 8D=0 ; go read the next line !:read_line :function_prologue ; sub rsp, 8 x48 x81 xec x08 x00 x00 x00 ; mov [rsp], rbp x48 x89 x2c x24 ; mov rbp, rsp R=S ; total length: 7 + 4 + 3 = 14 bytes :function_epilogue ; mov rsp, rbp S=R ; mov rbp, [rsp] x48 x8b x2c x24 ; add rsp, 8 x48 x81 xc4 x08 x00 x00 x00 ; ret return ; total length = 15 bytes :handle_label_definition ; ignore if this is the second pass C=:second_pass C=1C ?C!0:read_line ; make sure label only has identifier characters I=:line I+=d1 :label_checking_loop C=1I D=xa ?C=D:label_checking_loop_end I+=d1 B=C call :isident D=A ?D!0:label_checking_loop !:bad_label :label_checking_loop_end I=:line I+=d1 J=:labels D=d4 call :ident_lookup C=A ?C!0:label_redefinition J=:labels_end J=8J I=:line I+=d1 call :ident_copy R=J J=d4 I=d0 D=d1 syscall x8 C=A C+=x400000 J=R 4J=C J+=d4 ; update labels_end C=:labels_end 8C=J ; read the next line !:read_line :handle_return I=:line ; "return " is 7 chars long I+=d7 call :set_rax_to_term J=d4 I=:function_epilogue D=d15 syscall x1 ; go read the next line !:read_line :mov_rsp_rbp S=R :ret return ; copy the newline-terminated identifier from rsi to rdi :ident_copy C=1I B=C call :isident D=A ?D=0:bad_identifier :ident_loop C=1I 1J=C I+=d1 J+=d1 D=xa ?C=D:ident_loop_end B=C call :isident D=A ?D=0:bad_identifier !:ident_loop :ident_loop_end return align :ident_lookup_i reserve d8 :ident_lookup_sep reserve d8 ; look up identifier rsi in list rdi with separation rdx between entries ; returns address of whatever's right after the identifier in the list, or 0 if not found :ident_lookup C=:ident_lookup_sep 8C=D C=:ident_lookup_i 8C=I :ident_lookup_loop ; check if reached the end of the table C=1J ?C=0:return_0 I=:ident_lookup_i I=8I call :ident= C=A ; move past terminator of identifier in table :ident_finish_loop D=1J J+=d1 A=xa ?D!A:ident_finish_loop ; check if this was it ?C!0:return_J ; nope. keep going C=:ident_lookup_sep C=8C J+=C !:ident_lookup_loop ; can the character in rbx appear in an identifier? :isident A='0 ?BA:return_1 A='_ ?B=A:return_1 !:return_0 ; set rax to the term in rsi :set_rax_to_term R=I C=1I D='' ?C=D:term_char C=1I D=d58 ?C