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
; initialize :definitions_end
J=:definitions_end
D=:definitions
8J=D

:read_line
; 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
	R+=d1
	!:read_line_loop
:read_line_loop_end

; check if line = "#define " up to a terminator of ' '.
C=x20
I=:#define
J=:line
call :string=
D=A
?D!0:handle_#define

; handle a normal line
; R will store a pointer to the current character
R=:line
:process_line_loop
	C=1R
	B=C
	call :isident
	?A!0:process_ident
		; if *R is not an identifier character, just output it to the file.
		J=d4
		B=C
		call :fputc
		; check if we reached the end of the line
		C=1R
		D=xa
		?C=D:read_line
		; increment R, keep looping
		R+=d1
		!:process_line_loop
	:process_ident
		; if *R is an ident char. look up this identifier in :definitions.
		; use C to keep pointer to definition
		C=:definitions
		:lookup_loop
			D=1C
			; check if we reached end of definition table
			?D=0:lookup_loop_end
			; check if this entry matches our identifier
			I=R
			J=C
			call :ident=
			?A!0:perform_substitution
			; if not, skip over this entry
			:skip_definition_loop
				D=1C
				I=xa
				C+=d1
				?I!D:skip_definition_loop
			!:lookup_loop
		:lookup_loop_end
		; this identifier doesn't match anything in the definitions table; just write it.
		; first, figure out how long it is
		J=R
		:length_loop
			C=1J
			B=C
			call :isident
			?A=0:length_loop_end
			J+=d1
			!:length_loop
		:length_loop_end
		; now write it.
		I=R
		R=J
		J-=I
		D=J
		J=d4
		syscall x1
		; keep looping
		!:process_line_loop

:perform_substitution
	; right now, I is a pointer to the end of the identifier in :line,
	; and J is a pointer to the end of the identifier in :definitions.
	
	; advance :line pointer for next loop iteration
	R=I
	
	J+=d1
	; J now points to the definition. let's write it
	I=J
	:definition_end_loop
		C=1I
		D=xa
		?C=D:definition_end_loop_end
		I+=d1
		!:definition_end_loop
	:definition_end_loop_end
	D=I
	D-=J
	I=J
	J=d4
	syscall x1
	; process the rest of this line
	!:process_line_loop
	
:eof
	J=d0
	syscall x3c

; can the character in rbx appear in an identifier?
:isident
	A='0
	?B<A:return_0
	; note: 58 = '9' + 1
	A=d58
	?B<A:return_1
	A='A
	?B<A:return_0
	; note: 91 = 'z' + 1
	A=d91
	?B<A:return_1
	A='z
	?B>A:return_0
	; 96 = 'a' - 1
	A=d96
	?B>A:return_1
	A='_
	?B=A:return_1
	!:return_0
	

:handle_#define
	J=:definitions_end
	J=8J
	; start copy from after "#define"
	I=:line
	I+=d8
	
	:#define_copy_loop
		D=1I
		1J=D
		I+=d1
		J+=d1
		A=xa
		?D=A:#define_copy_loop_end
		!:#define_copy_loop
	:#define_copy_loop_end
	
	; update end of definitions
	D=:definitions_end
	8D=J
	; emit newline so we don't screw up line numbers
	J=d4
	I=:newline
	D=d1
	syscall x1
	
	!:read_line

:newline
	xa
	

:usage_error
	B=:usage_error_message
	call :error
	
:usage_error_message
	str Please provide an input and an output file.
	xa
	x0

:input_file_error
	B=:input_file_error_message
	!:error

:input_file_error_message
	str Couldn't open input file.
	xa
	x0

:output_file_error
	B=:output_file_error_message
	!:error

:output_file_error_message
	str Couldn't open output file.
	xa
	x0

:error
	J=B
	call :strlen
	D=A
	I=J
	J=d2
	syscall x1
	J=d1
	syscall x3c
	
:strlen
	I=B
	D=B
	:strlen_loop
	C=1I
	?C=0:strlen_ret
	I+=d1
	!:strlen_loop
	:strlen_ret
	I-=D
	A=I
	return

:#define
	str #define
	x20
	x0

; 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

; write the character in rbx to the file in rdi.
:fputc
	C=B
	I=S
	I-=d1
	1I=C
	D=d1
	syscall x1
	return
	

align
:definitions_end
	reserve d8
:line
	reserve d1000
:definitions
	reserve d200000
	
; we shouldn't end the file with a reserve; we don't handle that properly	
x00