1 Hi! I've decided to treat myself to some yak shaving for
2 a bit. First off, the "colon" and "word" terminology
3 makes this seem more like a traditional Forth than it
4 actually is. So I'm gonna rename some stuff...
5
6 First up, I'm going to change the term "word" to "def"
7 as in "definition". I was tempted to call them
8 "functions", but I don't think these inlined chunks of
9 code are actually functions.
10
11 Visibly, the ":" (colon) will change to "def".
12
13 Ten minutes later:
14
15 $ ./meow5
16
17 def meow
18 "Meow!" say
19 ;
20
21 meow
22 Meow!
23
24 By the way, my little test script has been invaluable
25 for changes like this. I made a pass of renames and then
26 made sure I hadn't broken anything each time:
27
28 $ ./test.sh
29 .............
30 Passed!
31
32 Okay, that was trivial. Now I want to improve the def
33 inspection experience by combining my "inspect" (gives
34 def stats) and "dump" (dumps a def's machine code) into
35 a single def because I always end up calling both of
36 them anyway.
37
38 And I also want to be able to call it like this:
39
40 inspect foo
41
42 instead of what I currently have to do:
43
44 "foo" find inspect
45
46 That turned out to also be trivial:
47
48 def m "meow" print ;
49 inspect m
50 m: 43 bytes IMMEDIATE COMPILE
51 e8 5 0 0 0 6d 65 6f 77 0 58 50 50 58 b9 0 0 0 0 80 3c 8 0 74 3 41 eb f7 51 5a 59 bb 1 0 0 0 b8 4 0 0 0 cd 80
52
53
54 Now it's easy and handy to make a pretty good
55 illustration of a pure inlining concatenative language:
56
57 def ++ + + ;
58
59 10 10 10 ++ ps
60 30
61
62 inspect +
63 +: 5 bytes IMMEDIATE COMPILE
64 58 5b 1 d8 50
65
66 inspect ++
67 ++: 10 bytes IMMEDIATE COMPILE
68 58 5b 1 d8 50 58 5b 1 d8 50
69
70 Clearly "++" contains two copies of "+".
71
72 I'm also renaming "make_elf" to just "elf".
73
74 Here's what's fun about being able to write out a
75 program from any definition is that "compiling" from the
76 command line can look like this:
77
78 $ cat 5.meow
79 def meow
80 "Meow!\n" print
81 ;
82
83 def five-meows
84 meow
85 meow
86 meow
87 meow
88 meow
89 exit
90 ;
91
92 elf five-meows
93
94 $ ./meow5 < 5.meow
95 Wrote to "five-meows".
96
97 $ ./five-meows
98 Meow!
99 Meow!
100 Meow!
101 Meow!
102 Meow!
103
104 Gosh, this is really fun!
105
106 Something I discovered last night was that I don't have
107 a couple really obvious defs: dup and pop!
108
109 Well, *that* was easy:
110
111 STARTDEF popstack
112 pop eax
113 ENDDEF popstack, 'pop', (IMMEDIATE | COMPILE)
114
115 STARTDEF dupstack
116 pop eax
117 push eax
118 push eax
119 ENDDEF dupstack, 'dup', (IMMEDIATE | COMPILE)
120
121 $ ./meow5
122 1 2 3 4 ps
123 1 2 3 4
124 pop ps
125 1 2 3
126 dup ps
127 1 2 3 3
128
129 Now for conditionals!
130
131 I think the easiest one will be immediate-mode "if". I'm
132 going to have it just check the value on the stack (0
133 for false, 1 for true) and if will always either
134 continue for true or skip a single token of input for
135 false.
136
137 STARTDEF if
138 pop eax
139 test eax, eax ; zero?
140 jnz .continue_for_true
141 EAT_SPACES_CODE
142 GET_TOKEN_CODE
143 pop eax ; throw away token!
144 .continue_for_true:
145 ENDDEF if, '?', (IMMEDIATE)
146
147 Could it really be that easy?
148
149 def hi "Hello\n" print ;
150 0 ? hi
151 1 ? hi
152 Hello
153
154 Wow, yup. It really was that easy.
155
156 A compiled "if" wasn't nearly as easy, but I still got
157 it in a single evening. Here I'm making a silly
158 conditional plus def called "foo" with a lot of debug
159 printing turned on and comparing the compiled opcodes
160 for the conditional jump followed by the inlined "+"
161 def:
162
163 $ ./build.sh && ./meow5
164 def foo ? + ;
165 tail of def:0804a0db
166 len of def:00000005
167 here:0804b1ac
168 len of def:00000005
169 here:0804b1b5
170 inlining:0804a0db
171 inspect +
172 +: 5 bytes IMMEDIATE COMPILE
173 58 5b 1 d8 50
174 inspect foo
175 foo: 14 bytes IMMEDIATE COMPILE
176 58 85 c0 f 85 5 0 0 0 58 5b 1 d8 50
177 1 1 1 foo ps
178 1 1
179 1 1 0 foo ps
180 1 1 2
181
182 Very cool!
183
184 The key part of making this work was remembering that it
185 needed to be set as a "RUNCOMP" def - so it would
186 actually *execute* in compile mode rather than be
187 compiled in as-is!
188
189 Now for looping!
190
191 The next thing is "loop?", which will be nearly identical
192 except it'll insert an unconditional jump after the def
193 that jumps back to perform the test again.
194
195 loop? <def>
196
197 Which should be fairly painless to use, I hope.
198
199 Hey, it works! I had some bugs, but nothing worth
200 writing up here.
201
202 Oh, and I'm now a HUGE fan of Evan Teran's EDB! Even
203 though it's a GUI program, it has now completely
204 replaced GDB for me with assembly language debugging. It
205 automatically halts on the entry instruction and
206 displays a disassembly - GDB makes this *brutally* hard
207 if you don't have a C program with debugging symbols.
208 EDB also shows graphical arrows to illustrate jumps,
209 multiple memory display areas, registers, etc. Pretty
210 intuitive to use.
211
212 https://github.com/eteran/edb-debugger
213
214 Oh, I also added comments! It's pretty much a copy of
215 "eat_spaces" for input, but the name of the def is "#"
216 to start the comment and it advances the input pointer
217 until it gets to a newline character. It's a RUNCOMP
218 def, so comments can also apepar in defs.
219
220 So here's my first loop test program:
221
222 ======================================================
223
224 $ cat loop.meow
225 # A meow5 program to test looping
226
227 # A chunk of code to be looped!
228 def foo
229 dup # duplicate the number on the stack
230 printnum # print it
231 " " print # and a space
232 dec # reduce the one still on the stack
233
234 # Another def that loops!
235 def foo-loop
236 loop? foo
237 "\n" print # put a newline after calling loop
238 ;
239
240 # Try some loops!
241 5 foo-loop
242 10 foo-loop
243
244 ======================================================
245
246 And let's run it:
247
248 $ ./meow5 < loop.meow
249 5 4 3 2 1
250 10 9 8 7 6 5 4 3 2 1
251
252 Heck yeah!
253
254 (Note that printnum is NOT position-independent code, so
255 exporting an ELF with this particular example explodes.)
256
257 Now I can do a loop version of the five meow printing
258 program:
259
260
261 ======================================================
262
263 $ cat five-loop.meow
264 def meow
265 "Meow!\n" print
266
267 # decrement the stack each time or
268 # we'll end up with an infinite loop!
269 dec
270 ;
271
272 # Another def that loops five times and exits
273 def five-meows-loop
274 5 loop? meow
275 exit
276 ;
277
278 # Compile as an ELF executable
279 elf five-meows-loop
280
281 ======================================================
282
283 Let's try it!
284
285 $ ./meow5 <five-loop.meow
286 Wrote to "five-meows-loop".
287
288 $ ./five-meows-loop
289 Meow!
290 Meow!
291 Meow!
292 Meow!
293 Meow!
294
295 What's really cool about this is that the five-meows
296 program that has five copies of the 'meow' def is 317
297 bytes and this looping version is only 160 bytes!
298
299