add 04 to bootstrap, #line

This commit is contained in:
pommicket 2022-01-07 11:29:37 -05:00
parent 519069a89d
commit 479ff20704
5 changed files with 81 additions and 335 deletions

View file

@ -212,6 +212,9 @@ conditionally jump to the specified label. `{operator}` should be one of
- `return {rvalue}`
- `string {str}` - places a literal string in the code
- `byte {number}` - places a literal byte in the code
- `#line {line number} {filename}` / `#line {line number}` - set line number and optionally the filename for future errors (no code is outputted from this)
The `#line` directive (which also exists in C) seems a bit strange, but its use will be revealed soon.
Now let's get down into the weeds:

81
04/in03
View file

@ -16,6 +16,13 @@ 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
@ -30,7 +37,18 @@ A=d2
J=A
?J<0:output_file_error
!:second_pass_starting_point
:argv_file_names
: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)
@ -173,6 +191,13 @@ 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
@ -275,13 +300,18 @@ align
; 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
C=A
D=xff
?CaD:bad_byte
?RaD:bad_byte
; write byte
I=:byte
1I=C
1I=R
J=d4
D=d1
syscall x1
@ -289,6 +319,35 @@ align
: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 "
@ -2139,7 +2198,9 @@ align
:program_error
R=B
B=:"Line"
B=:filename
call :eputs
B=:":"
call :eputs
D=:line_number
@ -2155,9 +2216,8 @@ align
J=d1
syscall x3c
:"Line"
str Line
x20
:":"
str :
x0
:line_number_separator
@ -2346,6 +2406,9 @@ align
:"string"
str string
x20
:"#line"
str #line
x20
:"goto"
str goto
x20
@ -2408,6 +2471,8 @@ align
reserve d8
:line_number
reserve d8
:filename
reserve d80
:global_variables
reserve d50000
:local_variables

322
04a/in03
View file

@ -1,322 +0,0 @@
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

0
04a/in04 Normal file
View file

View file

@ -68,12 +68,12 @@ if [ "$(./out03)" != 'Hello, world!' ]; then
fi
cd ..
echo 'Processing stage 04a...'
cd 04a
echo 'Processing stage 04...'
cd 04
rm -f out*
make -s out04a
if [ "$(cat out04a)" != "$(printf '\n\nHello, world!')" ]; then
echo_red 'Stage 04a failed.'
make -s out04
if [ "$(./out04)" != 'Hello, world!' ]; then
echo_red 'Stage 04 failed.'
exit 1
fi
cd ..