-
Notifications
You must be signed in to change notification settings - Fork 0
/
00_compiler.fth
282 lines (252 loc) · 9.1 KB
/
00_compiler.fth
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
-1 PARSE \ Define ' to make manual compilation easier
-1 PARSE \ ' DUP puts the XT of the word DUP on the stack. v useful for compilation
-1 PARSE \ Manually compiling : (') PARSE-NAME FIND-NAME DUP =0 IF -2 THROW THEN ;
-1 PARSE \ and : ' (') NAME>XT ;
2DROP 2DROP 2DROP 2DROP
CP @
3 C,
40 C,
39 C,
41 C,
0 CP +!
LATEST @ ,
LATEST !
(DOCOL) ,
PARSE-NAME PARSE-NAME FIND-NAME NAME>XT ,
PARSE-NAME FIND-NAME FIND-NAME NAME>XT ,
PARSE-NAME DUP FIND-NAME NAME>XT ,
PARSE-NAME =0 FIND-NAME NAME>XT ,
PARSE-NAME ?BRANCH FIND-NAME NAME>XT ,
CP @ 0 ,
PARSE-NAME LIT FIND-NAME NAME>XT ,
-2 ,
PARSE-NAME THROW FIND-NAME NAME>XT ,
CP @ SWAP !
PARSE-NAME EXIT FIND-NAME NAME>XT ,
CP @
1 C,
39 C,
2 CP +!
LATEST @ ,
LATEST !
(DOCOL) ,
(') (') NAME>XT ,
(') NAME>XT NAME>XT ,
(') EXIT NAME>XT ,
-1 PARSE \ Now I can just write "' DUP ," to compile DUP into a def, without this verbose mess.
-1 PARSE \ Real comments sound useful, adding those next. Comments use the same trick I'm doing manually here;
-1 PARSE \ parse input until you find a nonexistent character, then throw out the string you've parsed.
2DROP 2DROP 2DROP
CP @
1 C,
92 C,
2 CP +!
LATEST @ ,
LATEST !
(DOCOL) ,
' LIT , -1 , ' PARSE , ' 2DROP ,
' EXIT ,
LATEST @ +NAME>IMMEDIATE?
\ Now I can write comments like this!
\ I'll define a short helper word HERE to get the latest address of the stack
\ Heavily commenting it to make it clearer what's going on
CP @ \ hold onto the head of the dictionary for later
4 C, \ this word has a 4-character name. The word C, adds a single byte to the the dictionary.
72 C, 69 C, 82 C, 69 C, \ ascii "HERE"
3 CP +! \ Manually adding padding here so addresses are 4-byte aligned internally
LATEST @ , \ Link to the word before this in the dict. The word , adds a cell (4 bytes) to the dictionary.
LATEST ! \ Update the dictionary now that ENOUGH of this word is defined to not break anything
(DOCOL) , \ Mark this as a colon definition. (DOCOL) is a native word that starts running the body of a "colon definition""
\ The actual "body" of the definition begins now!
' CP , \ Compile the execution token (XT) of "CP" into the definition. At interpretation time, CP will get run.
' @ , \ Same thing for "@". "CP" pushes a variable address onto the stack, "@" reads the var at that address.
' EXIT , \ Exit goes at the end of every colon definition. It returns control to the caller.
\ And that's it! we've got "HERE".
\ define ALIGN to ensure the CP is aligned, so I don't haev to do it manually
HERE
5 C,
65 C, 76 C, 73 C, 71 C, 78 C, \ ascii "ALIGN"
2 CP +!
LATEST @ ,
LATEST !
(DOCOL) ,
' HERE ,
' ALIGNED ,
' CP ,
' ! ,
' EXIT ,
\ inline comments sound nice too, I'll add those next
HERE
1 C, \ This word has a 1-character name.
40 C, \ ascii "("
ALIGN
LATEST @ ,
LATEST !
(DOCOL) ,
' LIT ,
41 , \ ascii ")". The LIT word will return this value at interpretation time.
' PARSE , \ Read from input (this file) until we find that character.
' 2DROP , \ PARSE returns a string, but we don't need it so we can throw it out
' EXIT ,
LATEST @ +NAME>IMMEDIATE? \ mark the word as immediate too
\ I'm tired of looking up ASCII values
\ Defining HEADER to add a word to the dictionary, so I don't have to so often
HERE
6 C,
72 C, 69 C, 65 C, 68 C, 69 C, 82 C, \ HEADER ( c-addr u -- )
ALIGN
LATEST @ ,
LATEST !
(DOCOL) ,
' HERE , ' -ROT , \ Keep a pointer to the def's head on the stack
' DUP , ' C, , \ Save the length of the name in the dictionary
HERE \ This is the start of a loop. Pushing CP onto the stack to track where to jump back to later
' DUP , ' <>0 , \ If we're still parsing the word
' ?BRANCH , HERE 0 , \ start of a conditional, so we need a forward jump. Saving space for the address to jump to here
' SWAP , ' DUP , ' C@ , ' UPCHAR , ' C, , \ add another char to the defintion
' 1+ , ' SWAP , ' 1- , \ increment string addr, decrement length
' BRANCH , SWAP , \ Unconditionally branch back to the start of the loop
HERE SWAP ! \ Fill in the target of the forward jump, now that we've reached it
\ Looping/conditionals will be a lot easier once we've got a compiler to handle branching
' ALIGN , \ Make sure the dictionary head is aligned
' 2DROP , \ Clear the parsed name from the stack
' LATEST , ' @ , ' , , \ Compile the pointer to the previous word
' LATEST , ' ! , \ update that LATEST pointer to include our new word
' (DOVAR) , ' , , \ and default to the behavior of a variable
' EXIT ,
\ Now it's even less wordy to define words!
\ Add CREATE too, it parses a name and compiles a word for it.
PARSE-NAME CREATE HEADER
(DOCOL) LATEST @ NAME>XT !
' PARSE-NAME , ' HEADER ,
' EXIT ,
\ Add a helper to set the XT of the currently-defined word
( xt -- )
CREATE XT,
(DOCOL) LATEST @ NAME>XT !
' LATEST , ' @ , ' NAME>XT , ' ! ,
' EXIT ,
\ Support single-cell variables ( -- )
CREATE VARIABLE
(DOCOL) XT,
' CREATE , ' LIT , 0 , ' , , \ Just CREATE but also reserve a cell of memory
' EXIT ,
\ Support single-cell constants ( val -- )
CREATE CONSTANT
(DOCOL) XT,
' CREATE ,
' (DOCON) , ' XT, , \ Set the behavior of the new constant
' , , \ and just store the input param after it on the stack (as (DOCON) wants)
' EXIT ,
\ Enough manual compilation! time to build colon definitions.
\ Define a helper to set the IMMEDIATE flag on the last-defined word.
\ IMMEDIATE words have behavior during compilation-mode; non-IMMEDIATE words are just baked into the current def.
\ We need IMMEDIATE words to be able to shut the compiler off.
CREATE IMMEDIATE
(DOCOL) XT,
' LATEST , ' @ , ' +NAME>IMMEDIATE? ,
' EXIT ,
\ The word ] starts compilation.
CREATE ]
(DOCOL) XT,
' LIT , -1 , ' STATE , ' ! ,
' EXIT ,
\ The word [ stops compilation, and goes back to interpreter mode.
CREATE [
(DOCOL) XT,
' LIT , 0 , ' STATE , ' ! ,
' EXIT ,
IMMEDIATE \ THIS has to be immediate, otherwise the compiler runs forever!
\ The word HIDE hides the current definition from FIND-NAME
CREATE HIDE
(DOCOL) XT, ]
LATEST @ +NAME>HIDDEN?
EXIT [
\ The word REVEAL undoes HIDE
CREATE REVEAL
(DOCOL) XT, ]
LATEST @ -NAME>HIDDEN?
EXIT [
\ The word : starts a colon definition (hence the name)
CREATE :
(DOCOL) XT, ]
CREATE (DOCOL) XT, \ Start defining a colon definition
HIDE \ mark the def as hidden
] \ Switch to compilation mode
EXIT [
\ The word ; ends a colon definition and switches back to interpretation.
\ Just to be cheeky, let's use it while we define it
CREATE ;
(DOCOL) XT, ]
LIT EXIT , \ Add EXIT to the end of the current definition
REVEAL \ mark the def as no longer hidden
[ ' [ , \ switch to interpretation mode (both the def of ";" and the def being compiled)
; IMMEDIATE \ and call it to finish compiling it!
\ And we're done! We have colon words!
\ Add other compilation utilities
: ['] \ ['] DUP pushes the XT of dup onto the stack at runtime
' \ get the XT
LIT LIT , , \ and compile in a literal for it
; IMMEDIATE
\ [ 6 ] literal pushes 6 onto the stack at runtime
: LITERAL ( n -- )
['] LIT , ,
; IMMEDIATE
\ POSTPONE parses a word, and compiles its compilation semantics into the current word
: POSTPONE ( "ccc" -- )
(') DUP NAME>IMMEDIATE?
?BRANCH [ HERE 0 , ] \ if
NAME>XT , \ compile the XT into the def
BRANCH [ HERE 0 , SWAP HERE SWAP ! ] \ else
['] LIT , NAME>XT , ['] , , \ compile "compile the XT" into the def
[ HERE SWAP ! ] \ then
; IMMEDIATE
\ throw in recursion.
: RECURSE
LATEST @ NAME>XT ,
; IMMEDIATE
\ ">BODY" gives you the address of a word defined with CREATE
: >BODY ( xt -- addr ) CELL + ;
\ "DOES>" lets you customize the runtime behavior of words you CREATEd.
: DOES>
POSTPONE LIT
HERE 0 , \ leave a gap (and track the address) for the new callable we're compiling
POSTPONE XT, \ use that as the XT of whichever word was just created
POSTPONE EXIT \ compile-time word over, runtime word begins
\ now HERE is at the address of the runtime word, so we can fill in that gap
(DODOES) HERE 8 LSHIFT OR SWAP !
; IMMEDIATE
\ mark that we should use the host version of the most recent word WHILE bootstrapping,
\ but use our own definition afterwards
: HOST-DEFERRED
HERE LATEST @ ( latest old-latest )
\ copy the most recent definition name
DUP DUP NAME>U 1+ ALIGNED
[ HERE ] \ begin
SWAP DUP @ , \ copy 4 bytes to the new def
4 + SWAP 4 - \ increment pointer
DUP =0 \ any left?
?BRANCH [ , ] \ until
2DROP ( latest old-latest )
DUP , \ backword is our most recent definition
+NAME>HIDDEN? \ hide that definition from search
DUP NAME>STRING FIND-NAME \ find the OLD definition
OVER LATEST ! \ add this to the word-list
(DOCOL) , NAME>XT , \ and make this "deferred" word call that OLD definition
POSTPONE EXIT
+NAME>TRAMPOLINED?
;
\ Stop bootstrapping, use the passed-in XT as "main"
( main-xt -- )
: HOST-FINALIZE
LATEST @
[ HERE ] \ begin
DUP NAME>TRAMPOLINED?
?BRANCH [ HERE 0 , ] \ if
\ update this one-word colon word to call the word before it instead
DUP NAME>BACKWORD NAME>XT OVER NAME>XT 4 + !
[ HERE SWAP ! ] \ then
NAME>BACKWORD ?DUP =0
?BRANCH [ , ] \ until
EXECUTE
;