; initialize global_variables_end C=:global_variables_end D=:global_variables 8C=D ; initialize static_memory_end C=:static_memory_end ; 0x100000 = 1MB for code D=x500000 8C=D ; initialize labels_end C=:labels_end D=:labels 8C=D I=8S A=d2 ?I>A:argv_file_names ; use default input/output filenames ; set :filename appropriately J=:filename I=:input_filename C=d0 call :memccpy ; open input file J=:input_filename I=d0 syscall x2 J=A ?J<0:input_file_error ; open output file J=:output_filename I=x241 D=x1ed syscall x2 J=A ?J<0:output_file_error !:second_pass_starting_point :argv_file_names ; get filenames from command-line arguments ; set :filename appropriately I=S ; argv[1] is at *(rsp+16) I+=d16 I=8I J=:filename C=d0 call :memccpy ; 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, space 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 ; arguments are treated the same as local variables 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 I=:line J=:"byte" C=x20 call :string= D=A ?D!0:handle_byte I=:line J=:"string" C=x20 call :string= D=A ?D!0:handle_string I=:line J=:"#line" C=x20 call :string= D=A ?D!0:handle_#line I=:line J=:"goto" C=x20 call :string= D=A ?D!0:handle_goto I=:line J=:"if" C=x20 call :string= D=A ?D!0:handle_if I=:line J=:"function" call :string= D=A ?D!0:handle_function ; set delimiter to newline C=xa I=:line J=:"return\n" call :string= D=A ?D!0:handle_return ; check if this is an assignment I=:line :assignment_check_loop C=1I D=xa ?C=D:assignment_check_loop_end D='= ?C=D:handle_assignment I+=d1 !:assignment_check_loop :assignment_check_loop_end ; check if this is a function call (where we discard the return value) I=:line ; (check for an opening bracket not preceded by a space) :call_check_loop C=1I D=x20 ?C=D:call_check_loop_end D=xa ?C=D:call_check_loop_end D='( ?C=D:handle_call I+=d1 !:call_check_loop :call_check_loop_end !:bad_statement !:read_line :eof C=:second_pass D=1C ?D!0:exit_success ; set 2nd pass to 1 1C=d1 ; make sure output file is large enough for static memory ; we'll use the ftruncate syscall to set the size of the file J=d4 I=:static_memory_end I=8I I-=x400000 syscall x4d ; seek both files back to start J=d3 I=d0 D=d0 syscall x8 J=d4 I=d0 D=d0 syscall x8 ; set line number to 0 C=:line_number 8C=0 !:second_pass_starting_point :exit_success J=d0 syscall x3c align :local_variable_name reserve d8 :handle_byte I=:line ; 5 = length of "byte " I+=d5 call :read_number ; store away number in rbp R=A ; make sure number is immediately followed by newline C=1I D=xa ?C!D:bad_number ; make sure byte is 0-255 D=xff ?RaD:bad_byte ; write byte I=:byte 1I=R J=d4 D=d1 syscall x1 !:read_line :byte reserve d1 :handle_#line I=:line ; 6 = length of "#line " I+=d6 call :read_number ; store line number D=A C=:line_number ; subtract one so that we specify the number of the *next* line D-=d1 8C=D ; check character after line number C=1I D=xa ; if it's a newline, we're done ?C=D:read_line ; otherwise, it'd better be a space D=x20 ?C!D:bad_statement ; set the filename I+=d1 J=:filename C=xa call :memccpy ; we want a null-terminated, not newline-terminated filename J-=d1 1J=0 !:read_line :handle_string I=:line ; 7 = length of "string " I+=d7 J=I ; find end of string :string_loop C=1J D=xa ?C=D:string_loop_end J+=d1 !:string_loop :string_loop_end ; get length of string D=J D-=I ; output fd J=d4 syscall x1 !:read_line :handle_call J=I ; just use the rvalue function call code C=:rvalue D=:line 8C=D I=:line call :rvalue_function_call !:read_line :handle_local ; skip ' ' I+=d1 ; store away pointer to variable name C=:local_variable_name 8C=I ; check if already defined J=:local_variables call :ident_lookup C=A ?C!0:local_redeclaration C=:local_variable_name I=8C J=:local_variables_end J=8J call :ident_copy ; 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 :local_variables_end I=:local_variables_end 8I=J ; set rsp appropriately C=:rbp_offset J=d0 J-=D 4C=J J=d4 I=:lea_rsp_[rbp_offset] D=d7 syscall x1 ; read the next line !:read_line :lea_rsp_[rbp_offset] x48 x8d xa5 :rbp_offset reserve d4 align :global_start reserve d8 :global_variable_name reserve d8 :global_variable_size reserve d8 :handle_global ; ignore if this is the second pass C=:second_pass C=1C ?C!0:read_line ; skip ' ' I+=d1 C=1I D='9 ?C>D:global_default_size ; read specific size of global call :read_number D=A C=:global_variable_size 8C=D ; check and skip space after number C=1I D=x20 ?C!D:bad_number I+=d1 !:global_cont :global_default_size ; default size = 8 C=:global_variable_size D=d8 8C=D :global_cont ; store away pointer to variable name C=:global_variable_name 8C=I ; check if already defined J=:global_variables call :ident_lookup C=A ?C!0:global_redeclaration C=:global_variable_name I=8C J=:global_variables_end J=8J call :ident_copy ; store address D=:static_memory_end C=4D 4J=C J+=d4 ; increase static_memory_end by size D=:global_variable_size D=8D C+=D D=:static_memory_end 4D=C ; store null terminator 1J=0 ; update :global_variables_end I=:global_variables_end 8I=J ; go read the next line !:read_line :handle_function I=:line ; length of "function " I+=d9 ; make function name a label call :add_label ; 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 4D=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 I=:line I+=d1 call :add_label !:read_line align :label_name reserve d8 ; add the label in rsi to the label list (with the current pc address) :add_label ; ignore if this is the second pass C=:second_pass C=1C ?C!0:return_0 C=:label_name 8C=I ; make sure label only has identifier characters :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 C=:label_name I=8C J=:labels call :ident_lookup C=A ?C!0:label_redefinition J=:labels_end J=8J C=:label_name I=8C call :ident_copy R=J ; figure out where in the file we are (using lseek) J=d4 I=d0 D=d1 syscall x8 C=A C+=x400000 J=R ; store address 4J=C J+=d4 ; update labels_end C=:labels_end 8C=J return :handle_goto J=d4 I=:jmp_prefix D=d1 syscall x1 I=:line ; 5 = length of "goto " I+=d5 call :emit_label_jump_address !:read_line :jmp_prefix xe9 :handle_if I=:line I+=d3 ; skip term 1 call :go_to_space I+=d1 ; skip operator call :go_to_space I+=d1 ; put second operand in rsi call :set_rax_to_term call :set_rsi_to_rax I=:line ; length of "if " I+=d3 ; put first operand in rax call :set_rax_to_term ; put second operand in rbx call :set_rbx_to_rsi ; emit cmp rax, rbx J=d4 I=:cmp_rax_rbx D=d3 syscall x1 I=:line I+=d3 call :go_to_space I+=d1 R=I C=x20 I=R J=:"==" call :string= I=A ?I!0:write_je I=R J=:"!=" call :string= I=A ?I!0:write_jne I=R J=:">" call :string= I=A ?I!0:write_jg I=R J=:"<" call :string= I=A ?I!0:write_jl I=R J=:">=" call :string= I=A ?I!0:write_jge I=R J=:"<=" call :string= I=A ?I!0:write_jle I=R J=:"]" call :string= I=A ?I!0:write_ja I=R J=:"[" call :string= I=A ?I!0:write_jb I=R J=:"]=" call :string= I=A ?I!0:write_jae I=R J=:"[=" call :string= I=A ?I!0:write_jbe !:bad_jump :write_je J=d4 I=:je_prefix D=d2 syscall x1 !:if_continue :write_jne J=d4 I=:jne_prefix D=d2 syscall x1 !:if_continue :write_jl J=d4 I=:jl_prefix D=d2 syscall x1 !:if_continue :write_jg J=d4 I=:jg_prefix D=d2 syscall x1 !:if_continue :write_jle J=d4 I=:jle_prefix D=d2 syscall x1 !:if_continue :write_jge J=d4 I=:jge_prefix D=d2 syscall x1 !:if_continue :write_jb J=d4 I=:jb_prefix D=d2 syscall x1 !:if_continue :write_ja J=d4 I=:ja_prefix D=d2 syscall x1 !:if_continue :write_jbe J=d4 I=:jbe_prefix D=d2 syscall x1 !:if_continue :write_jae J=d4 I=:jae_prefix D=d2 syscall x1 !:if_continue :if_continue I=:line I+=d3 ; skip term 1 call :go_to_space I+=d1 ; skip operator call :go_to_space I+=d1 ; skip term 2 call :go_to_space I+=d1 J=:"goto" C=x20 call :string= C=A ; make sure word after term 2 is "goto" ?C=0:bad_jump I+=d1 call :emit_label_jump_address !:read_line :je_prefix x0f x84 :jne_prefix x0f x85 :jl_prefix x0f x8c :jg_prefix x0f x8f :jle_prefix x0f x8e :jge_prefix x0f x8d :jb_prefix x0f x82 :ja_prefix x0f x87 :jbe_prefix x0f x86 :jae_prefix x0f x83 :cmp_rax_rbx x48 x39 xd8 align :reladdr reserve d4 ; emit relative address (for jumping) of label in rsi :emit_label_jump_address ; address doesn't matter for first pass C=:second_pass C=1C ?C=0:jump_ignore_address ; look up label; store address in rbp J=:labels call :ident_lookup C=A ?C=0:bad_label R=4C :jump_ignore_address ; first, figure out current address J=d4 I=d0 D=d1 syscall x8 C=A ; add an additional 4 because the relative address is 4 bytes long C+=x400004 ; compute relative address D=d0 D-=C D+=R ; store in :reladdr C=:reladdr 4C=D ; output J=d4 I=:reladdr D=d4 syscall x1 return align :assignment_type reserve d8 :handle_assignment I-=d1 C=:assignment_type 8C=I I+=d2 C=1I D=x20 ; check for space after = ?C!D:bad_assignment I+=d1 ; set rdi to right-hand side of assignment call :set_rax_to_rvalue call :set_rdi_to_rax J=:assignment_type J=8J C=1J ; put newline after lvalue to make parsing easier D=xa 1J=D D=x20 ?C=D:handle_assignment_cont J-=d1 D=xa 1J=D :handle_assignment_cont D=x20 ?C=D:handle_plain_assignment D='+ ?C=D:handle_+= D='- ?C=D:handle_-= D='* ?C=D:handle_*= D='/ ?C=D:handle_/= D='% ?C=D:handle_%= D='& ?C=D:handle_&= D='| ?C=D:handle_|= D='^ ?C=D:handle_^= D='< ?C=D:handle_<= D='> ?C=D:handle_>= !:bad_assignment :handle_plain_assignment I=:line call :set_lvalue_to_rax !:read_line :handle_+= I=:line call :set_rax_to_rvalue call :set_rbx_to_rdi call :emit_add_rax_rbx I=:line call :set_lvalue_to_rax !:read_line :handle_-= I=:line call :set_rax_to_rvalue call :set_rbx_to_rdi call :emit_sub_rax_rbx I=:line call :set_lvalue_to_rax !:read_line :handle_*= I=:line call :set_rax_to_rvalue call :set_rbx_to_rdi call :emit_imul_rbx I=:line call :set_lvalue_to_rax !:read_line :handle_/= I=:line call :set_rax_to_rvalue call :set_rbx_to_rdi call :emit_cqo_idiv_rbx I=:line call :set_lvalue_to_rax !:read_line :handle_%= I=:line call :set_rax_to_rvalue call :set_rbx_to_rdi call :emit_cqo_idiv_rbx call :set_rax_to_rdx I=:line call :set_lvalue_to_rax !:read_line :handle_&= I=:line call :set_rax_to_rvalue call :set_rbx_to_rdi call :emit_and_rax_rbx I=:line call :set_lvalue_to_rax !:read_line :handle_|= I=:line call :set_rax_to_rvalue call :set_rbx_to_rdi call :emit_or_rax_rbx I=:line call :set_lvalue_to_rax !:read_line :handle_^= I=:line call :set_rax_to_rvalue call :set_rbx_to_rdi call :emit_xor_rax_rbx I=:line call :set_lvalue_to_rax !:read_line :handle_<= I=:line call :set_rax_to_rvalue call :set_rcx_to_rdi call :emit_shl_rax_cl I=:line call :set_lvalue_to_rax !:read_line :handle_>= I=:line call :set_rax_to_rvalue call :set_rcx_to_rdi call :emit_shr_rax_cl I=:line call :set_lvalue_to_rax !:read_line align :lvalue reserve d8 ; set the lvalue in rsi to :set_lvalue_to_rax C=:lvalue 8C=I ; first, store away value in R=I call :set_rdi_to_rax I=R C=:lvalue I=8C C=1I D='* ?C=D:lvalue_deref ; not a dereference; just a variable C=:lvalue I=8C call :set_rax_to_address_of_variable call :set_rbx_to_rax call :set_rax_to_rdi call :set_[rbx]_to_rax return :lvalue_deref C=:lvalue I=8C I+=d2 call :set_rax_to_address_of_variable call :set_rbx_to_rax call :set_rax_to_[rbx] call :set_rbx_to_rax call :set_rax_to_rdi C=:lvalue I=8C I+=d1 C=1I D='1 ?C=D:lvalue_deref1 D='2 ?C=D:lvalue_deref2 D='4 ?C=D:lvalue_deref4 D='8 ?C=D:lvalue_deref8 !:bad_assignment :lvalue_deref1 !:set_[rbx]_to_al :lvalue_deref2 !:set_[rbx]_to_ax :lvalue_deref4 !:set_[rbx]_to_eax :lvalue_deref8 !:set_[rbx]_to_rax :handle_return I=:line ; skip "return" I+=d6 C=1I D=xa ?C=D:no_return_value ; skip ' ' after return I+=d1 call :set_rax_to_rvalue :no_return_value 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 ; look up identifier rsi in list rdi ; returns address of whatever's right after the identifier in the list, or 0 if not found :ident_lookup 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 ; skip over address: J+=d4 !: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 to the term in rsi :set_rax_to_term R=I C=1I D='' ?C=D:term_number D='. ?C=D:term_label D='* ?C=D:term_dereference D='& ?C=D:term_addressof D='~ ?C=D:term_bitwise_not D=d58 ?C to the variable in rsi :set_rax_to_variable ; variable call :set_rax_to_address_of_variable call :set_rbx_to_rax call :set_rax_to_[rbx] return :term_label C=:second_pass C=1C ; skip looking up label on first pass; just use whatever's in rsi ?C=0:set_rax_to_immediate ; move past . I+=d1 J=:labels call :ident_lookup C=A ?C=0:bad_label ; set rax to label value I=4C !:set_rax_to_immediate align :rvalue reserve d8 ; set to the rvalue in rsi :set_rax_to_rvalue ; store pointer to rvalue C=:rvalue 8C=I J=I :rvalue_loop C=1J D='( ?C=D:rvalue_function_call D=x20 ?C=D:rvalue_binary_op D=xa ; no space or opening bracket; this must be a term ?C=D:set_rax_to_term J+=d1 !:rvalue_loop align :rvalue_function_arg reserve d8 :rvalue_function_arg_offset reserve d4 :rvalue_function_call I=J I+=d1 C=1I D=') ?C=D:function_call_no_arguments C=:rvalue_function_arg_offset ; set arg offset to -16 (to skip over stack space for return address and rbp) D=xfffffffffffffff0 4C=D :rvalue_function_loop C=:rvalue_function_arg 8C=I ; set to argument call :set_rax_to_term ; set <[rsp-arg_offset]> to rax ; first, output prefix J=d4 I=:mov_[rsp_offset]_rax_prefix D=d4 syscall x1 ; now decrement offset, and output it I=:rvalue_function_arg_offset C=4I C-=d8 4I=C J=d4 D=d4 syscall x1 C=:rvalue_function_arg I=8C ; skip over argument :rvalue_function_arg_loop C=1I D=', ?C=D:rvalue_function_next_arg D=') ?C=D:rvalue_function_loop_end D=xa ; no closing bracket ?C=D:bad_call I+=d1 !:rvalue_function_arg_loop :rvalue_function_next_arg ; skip comma I+=d1 C=1I D=x20 ; make sure there's a space after the comma ?C!D:bad_call ; skip space I+=d1 ; handle the next argument !:rvalue_function_loop :rvalue_function_loop_end :function_call_no_arguments I+=d1 C=1I D=xa ; make sure there's nothing after the closing bracket ?C!D:bad_term C=:second_pass C=1C ?C=0:ignore_function_address ; look up function name I=:rvalue I=8I J=:labels call :ident_lookup C=A ?C=0:bad_function ; read address I=4C :ignore_function_address call :set_rax_to_immediate ; write call rax J=d4 I=:call_rax D=d2 syscall x1 ; we're done! return :mov_[rsp_offset]_rax_prefix x48 x89 x84 x24 :call_rax xff xd0 :binary_op reserve d1 :rvalue_binary_op ; move past ' ' J+=d1 ; store binary op D=1J C=:binary_op 1C=D ; make sure space follows operator J+=d1 C=1J D=x20 ?C!D:bad_term ; set rsi to second operand J+=d1 I=J call :set_rax_to_term call :set_rsi_to_rax ; now set rax to first operand I=:rvalue I=8I call :set_rax_to_term ; and combine C=:binary_op C=1C D='+ ?C=D:rvalue_add D='- ?C=D:rvalue_sub D='* ?C=D:rvalue_mul D='/ ?C=D:rvalue_div D='% ?C=D:rvalue_rem D='& ?C=D:rvalue_and D='| ?C=D:rvalue_or D='^ ?C=D:rvalue_xor D='< ?C=D:rvalue_shl D='> ?C=D:rvalue_shr !:bad_term :rvalue_add call :set_rbx_to_rsi !:emit_add_rax_rbx :rvalue_sub call :set_rbx_to_rsi !:emit_sub_rax_rbx :rvalue_mul call :set_rbx_to_rsi !:emit_imul_rbx :rvalue_div call :set_rbx_to_rsi !:emit_cqo_idiv_rbx :rvalue_rem call :set_rbx_to_rsi call :emit_cqo_idiv_rbx call :set_rax_to_rdx return :rvalue_and call :set_rbx_to_rsi !:emit_and_rax_rbx :rvalue_or call :set_rbx_to_rsi !:emit_or_rax_rbx :rvalue_xor call :set_rbx_to_rsi !:emit_xor_rax_rbx :rvalue_shl call :set_rcx_to_rsi !:emit_shl_rax_cl :rvalue_shr call :set_rcx_to_rsi !:emit_shr_rax_cl ; set to address of variable in rsi :set_rax_to_address_of_variable J=:local_variables R=I call :ident_lookup C=A ?C=0:try_global ; it's a local variable ; read the offset from D=4C ; put negated offset in rbp R=d0 R-=D ; lea rax, [rbp+ J=d4 I=:lea_rax_rbp_offset_prefix D=d3 syscall x1 ; offset] J=d4 I=:imm64 4I=R D=d4 syscall x1 return :try_global I=R J=:global_variables call :ident_lookup C=A ?C=0:bad_variable ; it's a global variable ; get its address C=4C ; put address in rax I=C !:set_rax_to_immediate :number_is_negative reserve d1 :term_number call :read_number I=A !:set_rax_to_immediate :term_bitwise_not I+=d1 call :set_rax_to_term J=d4 I=:not_rax D=d3 syscall x1 return :not_rax x48 xf7 xd0 :term_dereference_size reserve d1 :term_dereference I+=d1 D=1I C=:term_dereference_size 1C=D I+=d1 call :set_rax_to_variable call :set_rbx_to_rax call :zero_rax C=:term_dereference_size C=1C D='1 ?C=D:set_al_to_[rbx] D='2 ?C=D:set_ax_to_[rbx] D='4 ?C=D:set_eax_to_[rbx] D='8 ?C=D:set_rax_to_[rbx] !:bad_term :term_addressof I+=d1 !:set_rax_to_address_of_variable ; set rax to the number in the string at rsi :read_number C=1I D='' ?C=D:read_char D='- ; set rdx to 0 if number is positive, 1 if negative ?C=D:read_number_negative D=d0 !:read_number_cont :read_number_negative D=d1 I+=d1 :read_number_cont ; store away negativity C=:number_is_negative 1C=D ; check if number starts with 0-9 C=1I D='9 ?C>D:bad_number D='0 ?CD:hex_number_loop_end ; one of the digits a-f D=xffffffffffffffa9 !:hex_number_digit :hex_number_0123456789 D=xffffffffffffffd0 :hex_number_digit C+=D ; shift left by 4 R<=d4 ; add digit R+=C I+=d1 !:hex_number_loop :hex_number_loop_end !:read_number_output :read_number_output ; first, make sure number is followed by space/newline/appropriate punctuation C=1I D=x20 ?C=D:read_number_valid D=', ?C=D:read_number_valid D=') ?C=D:read_number_valid D=xa ?C=D:read_number_valid !:bad_number :read_number_valid ; we now have the *unsigned* number in rbp. take the sign into consideration C=:number_is_negative D=1C ?D=0:number_not_negative ; R = -R C=R R=d0 R-=C :number_not_negative ; finally, return A=R return ; set to the immediate in rsi. :set_rax_to_immediate C=:imm64 8C=I ; write prefix J=d4 D=d2 I=:mov_rax_imm64_prefix syscall x1 ; write immediate J=d4 D=d8 I=:imm64 syscall x1 return :zero_rax J=d4 I=:xor_eax_eax D=d2 syscall x1 return :xor_eax_eax x31 xc0 :set_rbx_to_rax J=d4 I=:mov_rbx_rax D=d3 syscall x1 return :mov_rbx_rax B=A :set_rbx_to_rsi J=d4 I=:mov_rbx_rsi D=d3 syscall x1 return :mov_rbx_rsi B=I :set_rbx_to_rdi J=d4 I=:mov_rbx_rdi D=d3 syscall x1 return :mov_rbx_rdi B=J :set_rcx_to_rsi J=d4 I=:mov_rcx_rsi D=d3 syscall x1 return :mov_rcx_rsi C=I :set_rcx_to_rdi J=d4 I=:mov_rcx_rdi D=d3 syscall x1 return :mov_rcx_rdi C=J :set_rax_to_rdx J=d4 I=:mov_rax_rdx D=d3 syscall x1 return :mov_rax_rdx A=D :set_rax_to_rdi J=d4 I=:mov_rax_rdi D=d3 syscall x1 return :mov_rax_rdi A=J :set_rsi_to_rax J=d4 I=:mov_rsi_rax D=d3 syscall x1 return :mov_rsi_rax I=A :set_rdi_to_rax J=d4 I=:mov_rdi_rax D=d3 syscall x1 return :mov_rdi_rax J=A :set_rax_to_[rbx] J=d4 I=:mov_rax_[rbx] D=d3 syscall x1 return :mov_rax_[rbx] x48 x8b x03 :set_eax_to_[rbx] J=d4 I=:mov_eax_[rbx] D=d2 syscall x1 return :mov_eax_[rbx] x8b x03 :set_ax_to_[rbx] J=d4 I=:mov_ax_[rbx] D=d3 syscall x1 return :mov_ax_[rbx] x66 x8b x03 :set_al_to_[rbx] J=d4 I=:mov_al_[rbx] D=d2 syscall x1 return :mov_al_[rbx] x8a x03 :set_[rbx]_to_rax J=d4 I=:mov_[rbx]_rax D=d3 syscall x1 return :mov_[rbx]_rax x48 x89 x03 :set_[rbx]_to_eax J=d4 I=:mov_[rbx]_eax D=d2 syscall x1 return :mov_[rbx]_eax x89 x03 :set_[rbx]_to_ax J=d4 I=:mov_[rbx]_ax D=d3 syscall x1 return :mov_[rbx]_ax x66 x89 x03 :set_[rbx]_to_al J=d4 I=:mov_[rbx]_al D=d2 syscall x1 return :mov_[rbx]_al x88 x03 :mov_rax_imm64_prefix x48 xb8 :emit_add_rax_rbx J=d4 I=:add_rax_rbx D=d3 syscall x1 return :add_rax_rbx x48 x01 xd8 :emit_sub_rax_rbx J=d4 I=:sub_rax_rbx D=d3 syscall x1 return :sub_rax_rbx x48 x29 xd8 :emit_and_rax_rbx J=d4 I=:and_rax_rbx D=d3 syscall x1 return :and_rax_rbx x48 x21 xd8 :emit_or_rax_rbx J=d4 I=:or_rax_rbx D=d3 syscall x1 return :or_rax_rbx x48 x09 xd8 :emit_xor_rax_rbx J=d4 I=:xor_rax_rbx D=d3 syscall x1 return :xor_rax_rbx x48 x31 xd8 :emit_shl_rax_cl J=d4 I=:shl_rax_cl D=d3 syscall x1 return :shl_rax_cl x48 xd3 xe0 :emit_shr_rax_cl J=d4 I=:shr_rax_cl D=d3 syscall x1 return :shr_rax_cl x48 xd3 xe8 :emit_imul_rbx J=d4 I=:imul_rbx D=d3 syscall x1 return :imul_rbx x48 xf7 xeb :emit_cqo_idiv_rbx J=d4 I=:cqo_idiv_rbx D=d5 syscall x1 return :cqo_idiv_rbx x48 x99 x48 xf7 xfb align :imm64 reserve d8 ; prefix for lea rax, [rbp+IMM32] :lea_rax_rbp_offset_prefix x48 x8d x85 :input_filename str in04 x0 :output_filename str out04 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_variable B=:bad_variable_error_message !:program_error :bad_variable_error_message str No such variable. xa x0 :bad_function B=:bad_function_error_message !:program_error :bad_function_error_message str No such function. xa x0 :bad_byte B=:bad_byte_error_message !:program_error :bad_byte_error_message str Byte not in range 0-255. xa x0 :bad_number B=:bad_number_error_message !:program_error :bad_number_error_message str Bad number. xa x0 :bad_assignment B=:bad_assignment_error_message !:program_error :bad_assignment_error_message str Bad assignment. xa x0 :bad_term B=:bad_term_error_message !:program_error :bad_term_error_message str Bad term. xa x0 :bad_statement B=:bad_statement_error_message !:program_error :bad_statement_error_message str Bad statement. xa x0 :bad_jump B=:bad_jump_error_message !:program_error :bad_jump_error_message str Bad jump. xa x0 :bad_call B=:bad_call_error_message !:program_error :bad_call_error_message str Bad function call. 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=:filename call :eputs B=:":" 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 :":" str : 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 J+=d1 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 ; advance rsi to the next space or newline character :go_to_space C=1I D=xa ?C=D:return_0 D=x20 ?C=D:return_0 I+=d1 !:go_to_space :"global" str global x20 :"argument" str argument x20 :"local" str local x20 :"return" str return x20 :"return\n" str return xa :"byte" str byte x20 :"string" str string x20 :"#line" str #line x20 :"goto" str goto x20 :"if" str if x20 :"function" str function x20 :"==" str == x20 :"!=" str != x20 :">" str > x20 :"<" str < x20 :"<=" str <= x20 :">=" str >= x20 :"[" str [ x20 :"]" str ] x20 :"[=" str [= x20 :"]=" str ]= x20 :zero x0 ; 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 :filename reserve d80 :global_variables reserve d50000 :local_variables reserve d20000 :labels reserve d200000 :second_pass reserve d1 :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