; 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 ; 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 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=d9 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 ; store address D=:stack_end D=8D 8J=D J+=d8 ; 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 ; 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=d9 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=8D 8J=D J+=d8 ; 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 "mov rbp, rsp" J=d4 I=:mov_rbp_rsp D=d3 syscall x1 ; reset local variable table D=:local_variables 1D=0 C=:local_variables_end 8C=D ; go read the next line !:read_line :mov_rbp_rsp R=S :handle_label_definition ; 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 ; @TODO: handle argument ; emit "mov rsp, rbp" J=d4 I=:mov_rsp_rbp D=d3 syscall x1 ; emit "ret" J=d4 I=:ret D=d1 syscall x1 ; go read the next lines !: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 I=:ident_lookup_i I=8I call :ident= C=A ; move past terminator J+=d1 ?C!0:return_J C=:ident_lookup_sep C=8C J+=C C=1J ?C=0:return_0 !: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 ; read the space-terminated type from rsi, advance rsi, and set rax to the corresponding type number: ; 0 for non-pointer types ; 1 for pointer to char ; 2 for pointer to short ; 4 for pointer to int ; 8 for pointer to long :read_type C=1I D='* ?C=D:read_pointer_type ; it's not a pointer call :read_simple_type A=d0 return :read_pointer_type ; it's a pointer! I+=d1 !:read_simple_type ; returns 1 for char, 2 for short, 4 for int, 8 for long :read_simple_type R=I C=x20 I=R J=:"char" call :string= D=A ?D!0:return_1 I=R J=:"short" call :string= D=A ?D!0:return_2 I=R J=:"int" call :string= D=A ?D!0:return_4 I=R J=:"long" call :string= D=A ?D!0:return_8 !:bad_type :usage_error B=:usage_error_message call :general_error :usage_error_message str Please provide an input and an output file. xa x0 :input_file_error B=:input_file_error_message !:general_error :input_file_error_message str Couldn't open input file. xa x0 :output_file_error B=:output_file_error_message !:general_error :output_file_error_message str Couldn't open output file. xa x0 :bad_identifier B=:bad_identifier_error_message !:program_error :bad_identifier_error_message str Bad identifier. xa x0 :bad_label B=:bad_label_error_message !:program_error :bad_label_error_message str Bad label. xa x0 :bad_type B=:bad_type_error_message !:program_error :bad_type_error_message str Bad type. xa x0 :label_redefinition B=:label_redefinition_error_message !:program_error :label_redefinition_error_message str Label redefinition. xa x0 :global_redeclaration B=:global_redeclaration_error_message !:program_error :global_redeclaration_error_message str Global variable declared twice. xa x0 :local_redeclaration B=:local_redeclaration_error_message !:program_error :local_redeclaration_error_message str Local variable declared twice. xa x0 :general_error call :eputs J=d1 syscall x3c :program_error R=B B=:"Line" call :eputs D=:line_number D=8D B=D call :eputn B=:line_number_separator call :eputs B=R call :eputs J=d1 syscall x3c :"Line" str Line x20 x0 :line_number_separator str : x20 x0 :strlen I=B D=B :strlen_loop C=1I ?C=0:strlen_ret I+=d1 !:strlen_loop :strlen_ret I-=D A=I return ; check if strings in rdi and rsi are equal, up to terminator in rcx :string= D=1I A=1J ?D!A:return_0 ?D=C:return_1 I+=d1 J+=d1 !:string= ; check if strings in rdi and rsi are equal, up to the first non-identifier character :ident= D=1I B=D call :isident ; I ended ?A=0:ident=_I_end D=1J B=D call :isident ; J ended, but I didn't ?A=0:return_0 ; we haven't reached the end of either D=1I A=1J ?D!A:return_0 I+=d1 J+=d1 !:ident= :ident=_I_end D=1J B=D call :isident ; check if J also ended ?A=0:return_1 ; J didn't end !:return_0 :return_0 A=d0 return :return_1 A=d1 return :return_2 A=d2 return :return_3 A=d3 return :return_4 A=d4 return :return_5 A=d5 return :return_6 A=d6 return :return_7 A=d7 return :return_8 A=d8 return :return_J A=J return ; write the character in rbx to the file in rdi. :fputc C=B I=S I-=d1 1I=C D=d1 syscall x1 return ; write the string in rbx to stderr :eputs J=B call :strlen D=A I=J J=d2 syscall x1 return ; write rbx in decimal to stderr :eputn I=B J=S J-=d1 :eputn_loop D=d0 ; divide by 10 B=d10 A=I div ; quotient is new number I=A ; add remainder to string D+='0 1J=D J-=d1 ?I!0:eputn_loop D=S D-=J I=J J=d2 syscall x1 return ; copy rdx bytes from rsi to rdi. ; this copies from the left: if you're doing an overlapped copy, rsi should be greater than rdi :memcpy ?D=0:return_0 A=1I 1J=A I+=d1 J+=d1 D-=d1 !:memcpy ; copy from rdi to rsi, until byte cl is reached :memccpy D=1I 1J=D I+=d1 J+=d1 ?D!C:memccpy return :"char" str char x20 :"short" str short x20 :"int" str int x20 :"long" str long x20 :"global" str global x20 :"argument" str argument x20 :"local" str local x20 :"return" str return x20 :"function" str function xa ; put a 0 byte before the line (this is important for removing whitespace at the end of the line, ; specifically, we don't want this to be a space character) x0 :line reserve d1000 align :global_variables_end reserve d8 :static_memory_end reserve d8 :local_variables_end reserve d8 :stack_end reserve d8 :labels_end reserve d8 :line_number reserve d8 :global_variables reserve d50000 :local_variables reserve d20000 :labels reserve d200000 :ELF_header x7f x45 x4c x46 x02 x01 x01 reserve d9 x02 x00 x3e x00 x01 x00 x00 x00 x78 x00 x40 x00 x00 x00 x00 x00 x40 x00 x00 x00 x00 x00 x00 x00 reserve d12 x40 x00 x38 x00 x01 x00 x00 x00 x00 x00 x00 x00 x01 x00 x00 x00 x07 x00 x00 x00 x78 x00 x00 x00 x00 x00 x00 x00 x78 x00 x40 x00 x00 x00 x00 x00 reserve d8 x00 x00 x20 x00 x00 x00 x00 x00 x00 x00 x20 x00 x00 x00 x00 x00 x00 x10 x00 x00 x00 x00 x00 x00 ; NOTE: we shouldn't end the file with a reserve; we don't handle that properly