Hi! I've decided to treat myself to some yak shaving for a bit. First off, the "colon" and "word" terminology makes this seem more like a traditional Forth than it actually is. So I'm gonna rename some stuff... First up, I'm going to change the term "word" to "def" as in "definition". I was tempted to call them "functions", but I don't think these inlined chunks of code are actually functions. Visibly, the ":" (colon) will change to "def". Ten minutes later: $ ./meow5 def meow "Meow!" say ; meow Meow! By the way, my little test script has been invaluable for changes like this. I made a pass of renames and then made sure I hadn't broken anything each time: $ ./test.sh ............. Passed! Okay, that was trivial. Now I want to improve the def inspection experience by combining my "inspect" (gives def stats) and "dump" (dumps a def's machine code) into a single def because I always end up calling both of them anyway. And I also want to be able to call it like this: inspect foo instead of what I currently have to do: "foo" find inspect That turned out to also be trivial: def m "meow" print ; inspect m m: 43 bytes IMMEDIATE COMPILE 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 Now it's easy and handy to make a pretty good illustration of a pure inlining concatenative language: def ++ + + ; 10 10 10 ++ ps 30 inspect + +: 5 bytes IMMEDIATE COMPILE 58 5b 1 d8 50 inspect ++ ++: 10 bytes IMMEDIATE COMPILE 58 5b 1 d8 50 58 5b 1 d8 50 Clearly "++" contains two copies of "+". I'm also renaming "make_elf" to just "elf". Here's what's fun about being able to write out a program from any definition is that "compiling" from the command line can look like this: $ cat 5.meow def meow "Meow!\n" print ; def five-meows meow meow meow meow meow exit ; elf five-meows $ ./meow5 < 5.meow Wrote to "five-meows". $ ./five-meows Meow! Meow! Meow! Meow! Meow! Gosh, this is really fun! Something I discovered last night was that I don't have a couple really obvious defs: dup and pop! Well, *that* was easy: STARTDEF popstack pop eax ENDDEF popstack, 'pop', (IMMEDIATE | COMPILE) STARTDEF dupstack pop eax push eax push eax ENDDEF dupstack, 'dup', (IMMEDIATE | COMPILE) $ ./meow5 1 2 3 4 ps 1 2 3 4 pop ps 1 2 3 dup ps 1 2 3 3 Now for conditionals! I think the easiest one will be immediate-mode "if". I'm going to have it just check the value on the stack (0 for false, 1 for true) and if will always either continue for true or skip a single token of input for false. STARTDEF if pop eax test eax, eax ; zero? jnz .continue_for_true EAT_SPACES_CODE GET_TOKEN_CODE pop eax ; throw away token! .continue_for_true: ENDDEF if, '?', (IMMEDIATE) Could it really be that easy? def hi "Hello\n" print ; 0 ? hi 1 ? hi Hello Wow, yup. It really was that easy. A compiled "if" wasn't nearly as easy, but I still got it in a single evening. Here I'm making a silly conditional plus def called "foo" with a lot of debug printing turned on and comparing the compiled opcodes for the conditional jump followed by the inlined "+" def: $ ./build.sh && ./meow5 def foo ? + ; tail of def:0804a0db len of def:00000005 here:0804b1ac len of def:00000005 here:0804b1b5 inlining:0804a0db inspect + +: 5 bytes IMMEDIATE COMPILE 58 5b 1 d8 50 inspect foo foo: 14 bytes IMMEDIATE COMPILE 58 85 c0 f 85 5 0 0 0 58 5b 1 d8 50 1 1 1 foo ps 1 1 1 1 0 foo ps 1 1 2 Very cool! The key part of making this work was remembering that it needed to be set as a "RUNCOMP" def - so it would actually *execute* in compile mode rather than be compiled in as-is! Now for looping! The next thing is "loop?", which will be nearly identical except it'll insert an unconditional jump after the def that jumps back to perform the test again. loop? Which should be fairly painless to use, I hope. Hey, it works! I had some bugs, but nothing worth writing up here. Oh, and I'm now a HUGE fan of Evan Teran's EDB! Even though it's a GUI program, it has now completely replaced GDB for me with assembly language debugging. It automatically halts on the entry instruction and displays a disassembly - GDB makes this *brutally* hard if you don't have a C program with debugging symbols. EDB also shows graphical arrows to illustrate jumps, multiple memory display areas, registers, etc. Pretty intuitive to use. https://github.com/eteran/edb-debugger Oh, I also added comments! It's pretty much a copy of "eat_spaces" for input, but the name of the def is "#" to start the comment and it advances the input pointer until it gets to a newline character. It's a RUNCOMP def, so comments can also apepar in defs. So here's my first loop test program: ====================================================== $ cat loop.meow # A meow5 program to test looping # A chunk of code to be looped! def foo dup # duplicate the number on the stack printnum # print it " " print # and a space dec # reduce the one still on the stack # Another def that loops! def foo-loop loop? foo "\n" print # put a newline after calling loop ; # Try some loops! 5 foo-loop 10 foo-loop ====================================================== And let's run it: $ ./meow5 < loop.meow 5 4 3 2 1 10 9 8 7 6 5 4 3 2 1 Heck yeah! (Note that printnum is NOT position-independent code, so exporting an ELF with this particular example explodes.) Now I can do a loop version of the five meow printing program: ====================================================== $ cat five-loop.meow def meow "Meow!\n" print # decrement the stack each time or # we'll end up with an infinite loop! dec ; # Another def that loops five times and exits def five-meows-loop 5 loop? meow exit ; # Compile as an ELF executable elf five-meows-loop ====================================================== Let's try it! $ ./meow5