M .gitignore => .gitignore +2 -1
@@ 1,3 1,4 @@
*.o
*.img
-/website
+*.tar.gz
+/*/*.html
M 01-duskcc/01-buckleup.md => 01-duskcc/01-buckleup.md +7 -7
@@ 50,7 50,7 @@ an amd64 machine:
$ objdump -d buckleup
...
- 0000000000001129 <foo>:
+ 0000000000001129 <foo>:
1129: 55 push %rbp
112a: 48 89 e5 mov %rsp,%rbp
112d: 89 7d fc mov %edi,-0x4(%rbp)
@@ 60,12 60,12 @@ an amd64 machine:
1139: 01 d0 add %edx,%eax
113b: 5d pop %rbp
113c: c3 ret
- 000000000000113d <main>:
+ 000000000000113d <main>:
113d: 55 push %rbp
113e: 48 89 e5 mov %rsp,%rbp
1141: be 0c 00 00 00 mov $0xc,%esi
1146: bf 2a 00 00 00 mov $0x2a,%edi
- 114b: e8 d9 ff ff ff call 1129 <foo>
+ 114b: e8 d9 ff ff ff call 1129 <foo>
1150: 5d pop %rbp
1151: c3 ret
...
@@ 159,13 159,13 @@ With everything cleared up, let's look at our new ELF dump:
buckleup: file format elf64-x86-64
Disassembly of section .text:
- 0000000000401000 <foo>:
+ 0000000000401000 <foo>:
401000: 01 c3 add %eax,%ebx
401002: c3 ret
- 0000000000401003 <_start>:
+ 0000000000401003 <_start>:
401003: b8 2a 00 00 00 mov $0x2a,%eax
401008: bb 0c 00 00 00 mov $0xc,%ebx
- 40100d: e8 ee ff ff ff call 401000 <foo>
+ 40100d: e8 ee ff ff ff call 401000 <foo>
401012: b8 01 00 00 00 mov $0x1,%eax
401017: cd 80 int $0x80
@@ 212,7 212,7 @@ we'll talk later.
[collapseos]: http://collapseos.org
[duskos]: http://duskos.org
-[srctgz]: 01-buckleup.tar.gz
+[srctgz]: https://tumbleforth.hardcoded.net/01-duskcc/01-buckleup.tar.gz
[elf]: https://en.wikipedia.org/wiki/Executable_and_Linkable_Format
[ccall]: https://en.wikipedia.org/wiki/X86_calling_conventions#System_V_AMD64_ABI
[nasm]: https://www.nasm.us/
M 01-duskcc/02-baremetal.md => 01-duskcc/02-baremetal.md +1 -1
@@ 241,7 241,7 @@ world of Forth, which OSdev doesn’t cover.
[^16]: Although I’d be happy if it did!
-[srctgz]: 02-baremetal.tar.gz
+[srctgz]: https://tumbleforth.hardcoded.net/01-duskcc/02-baremetal.tar.gz
[prev]: 01-buckleup.html
[nextup]: 03-onesector.html
[qemu]: https://www.qemu.org/
M 01-duskcc/03-onesector.md => 01-duskcc/03-onesector.md +2 -2
@@ 63,7 63,7 @@ with this article is available in [a tarball][srctgz] which you should use in
order to follow along.*
We have a source? We have a destination? Then we know what to do, let’s write
-it. Oh wait, [wrote it for you already][srctgz]. If you run this (with `make
+it. Oh wait, [I wrote it for you already][srctgz]. If you run this (with `make
run`), you’ll have a result similar to the previous “Hello World!”, but this
time, the code that’s executed comes from the second sector of the disk.
@@ 147,7 147,7 @@ number would need to be 0xaa55 to have the intended effect.
[^6]: You think that’s complicated? Just wait until you try to get in protected
mode!
-[srctgz]: 03-onesector.tar.gz
+[srctgz]: https://tumbleforth.hardcoded.net/01-duskcc/03-onesector.tar.gz
[prev]: 02-baremetal.html
[nextup]: 04-wordsshell.html
[sectorforth]: https://github.com/cesarblum/sectorforth
M 01-duskcc/04-wordsshell.md => 01-duskcc/04-wordsshell.md +1 -1
@@ 161,7 161,7 @@ prefer that order. More on this later.
[^6]: With the contents having been loaded from the bootloader.
[^7]: "jb” means “jump if dest operand is below source operand”.
-[srctgz]: 04-wordsshell.tar.gz
+[srctgz]: https://tumbleforth.hardcoded.net/01-duskcc/04-wordsshell.tar.gz
[prev]: 03-onesector.html
[nextup]: 05-dolookup.html
[basic]: https://en.wikipedia.org/wiki/BASIC_interpreter
M 01-duskcc/05-dolookup.md => 01-duskcc/05-dolookup.md +5 -5
@@ 46,10 46,10 @@ so you might as well get used to it.
## Writing a dictionary
All Forths start with a system dictionary which is often assembled in their
-predefined structure directly in the code. If you look at the [I’ve written for
-you][srctgz], you’ll see that this is what I’ve done too, starting at the `“;
-Dictionary”` comment. Let’s look at the first entry (`hellomsg` isn’t part of
-the dictionary entry):
+predefined structure directly in the code. If you look at the [implementation
+I’ve written for you][srctgz], you’ll see that this is what I’ve done too,
+starting at the `“; Dictionary”` comment. Let’s look at the first entry
+(`hellomsg` isn’t part of the dictionary entry):
db 'hello'
hello: dw 0
@@ 190,7 190,7 @@ centered around colors!
word “user” really doesn’t convey how intensely the creativity of the person at
the keyboard is being solicited. You’re like Tank in the Matrix!
-[srctgz]: 05-dolookup.tar.gz
+[srctgz]: https://tumbleforth.hardcoded.net/01-duskcc/05-dolookup.tar.gz
[prev]: 04-wordsshell.html
[nextup]: 06-taletwostacks.html
[ll]: https://en.wikipedia.org/wiki/Linked_list
M 01-duskcc/06-taletwostacks.md => 01-duskcc/06-taletwostacks.md +1 -1
@@ 280,7 280,7 @@ personal convention.
used.
[^11]: In Forth speak, the “!” symbol means “store”.
-[srctgz]: 06-taletwostacks.tar.gz
+[srctgz]: https://tumbleforth.hardcoded.net/01-duskcc/06-taletwostacks.tar.gz
[prev]: 05-dolookup.html
[nextup]: 07-babywalk.html
[ccall]: https://en.wikipedia.org/wiki/X86_calling_conventions
M 01-duskcc/07-babywalk.md => 01-duskcc/07-babywalk.md +1 -1
@@ 183,7 183,7 @@ unfinished, and thus broken. With no `ret` to stop the call chain, calling the
broken word will result in executing uninitialized memory, which means
“fireworks!”. Our baby Forth isn’t mistake-friendly!
-[srctgz]: 07-babywalk.tar.gz
+[srctgz]: https://tumbleforth.hardcoded.net/01-duskcc/07-babywalk.tar.gz
[prev]: 06-taletwostacks.html
[nextup]: 08-immediate.html
[brad]: https://www.bradrodriguez.com/papers/moving1.htm
M 01-duskcc/08-immediate.md => 01-duskcc/08-immediate.md +7 -3
@@ 209,6 209,10 @@ for your mercy, and let’s discover the brave new world of Dusk OS!
*[Next: Buy this story arc from the home page!](/)*
+or, if you're reading this from the purchased bundle:
+
+*[Next: From Dusk Till C][nextup]*
+
[^1]: Remember, you’re Tank! You ain’t some cog in the machine, machines bend
to your will!
[^2]: For example, the string literal.
@@ 221,9 225,9 @@ to your will!
[^9]: and I’f be very happy if you were! Let me know if you’re stuck at some
point.
-[srctgz]: 07-babywalk.tar.gz
-[prev]: 06-taletwostacks.html
-[nextup]: 08-immediate.html
+[srctgz]: https://tumbleforth.hardcoded.net/01-duskcc/08-immediate.tar.gz
+[prev]: 07-babywalk.html
+[nextup]: 09-dusktillc.html
[starting]: https://www.forth.com/starting-forth/
[gforth]: https://gforth.org/
[jonesforth]: https://github.com/nornagon/jonesforth
A 01-duskcc/09-dusktillc.md => 01-duskcc/09-dusktillc.md +331 -0
@@ 0,0 1,331 @@
+# [Tumble Forth](/): The Unbearable Immediateness of Compiling
+
+We now know the [secret ingredients that make Forths alive][prev], but we won’t
+go all the way to a full Forth because it would be much less interesting than
+the first magical moments of life. Moreover, I already have one for you: [Dusk
+OS][dusk].
+
+Dusk OS is a 32-bit STC Forth that broadly follows Starting Forth’s
+conventions, with a few caveats:
+
+1. Lowercase words.
+2. Filesystem-based rather than Block-based.
+3. There is no `HEX/DEC` mode. Hexadecimal literals are prefixed with `$`, like
+in `$1234abcd`. There is also support for character literals like `‘X’`.
+Printing a literal in hexadecimal is done with the `.x` word.
+4. No support for decimals.
+5. `DO..LOOP` changes to `for2..next` because Dusk has [fancy support for
+iterators][iter].
+6. `VARIABLE` changes to `value` with what Dusk calls [“to” semantics][usage].
+7. The [text editor][ged] is completely different.
+
+You can of course read [Dusk’s documentation][duskdoc] which will tell you all
+you need to know, but you don’t have to. Your knowledge from having read
+Starting Forth will be enough, I’ll fill you in as needed.
+
+## Dawn of a new world
+
+With all this ruckus about building a Forth and then discovering a whole new
+one, where were we again? Ah yes, we want to compile this:
+
+ int foo(int a, int b) {
+ return a + b;
+ }
+
+ int main() {
+ return foo(42, 12);
+ }
+
+Compiling this, alright, but what to? Under UNIX, this compiled into an ELF
+executable that would be called and yield the result through its program return
+code. Under a Forth, we would rather want to compile this to a simple word.
+
+The `main` function is a crutch that only UNIX needs. What we’d actually want
+to do with our compiled word is this:
+
+ 42 12 foo . \ prints 54
+
+This means we can ignore the `main` function. So, just like that, half our work
+is done!
+
+Of course, we could compile this with Dusk’s existing C compiler, but we
+wouldn’t be learning much. The rest of this story arc will be devoted to write
+the code necessary to minimally compile the `foo` function’s source code into
+an executable word under i386 Dusk.
+
+We will first build an assembler for the few instructions we need, then we’ll
+build ourselves a C tokenizer, and finally write the code that parses these
+incoming tokens and generate the corresponding i386 code through the
+assembler.
+
+## Pitching your tent at Dusk
+
+Let’s prepare our stuff. First, make sure that you can run the i386 version of
+Dusk. Running `make pcrun` should result in QEMU launching with a Dusk OS
+prompt.
+
+For this story arc, we’ll make the filesystem root our workspace. We’ll write
+our work in files at the root filesystem and then load them in Dusk. Let’s test
+that this work by creating a file called `myasm.fs` in the `fs/` subfolder in
+Dusk’s source code, with this content:
+
+ ." Hello Dusk!\n"
+
+Then, run `make pcrun`[^1] again and at prompt, type `”f<< myasm.fs”`. You get
+the message? Good! We’re ready to build our barebone i386
+assembler.
+
+## Assembler scope
+
+As mentioned above, we’ll go for a
+minimal solution for the presented problem, which means a minimal assembler. To
+determine the scope of that assembler, let’s try to see what `foo` would look
+like in NASM. You should already have an idea since it’s functionally
+equivalent to the `+` word we’ve already implemented in our baby Forth, with
+these differences:
+
+1. We’re in 32-bit mode, so register names have a `E` (for “extended”) prefix.
+2. All 16-bit references are upgraded to 32-bit. Whatever was 2 bytes in our
+baby Forth is now 4 bytes.
+3. Dusk’s register assigned to `PS` is `ESI` rather than `BP`.
+4. Dusk keeps `PS` Top Of Stack element in the `EAX` register.
+
+The last point needs a special mention. Brad Rodriguez, in the same excellent
+[“Moving Forth”][brad] article I’ve already linked previously, speaks of the
+subject in the section named “Top-Of-Stack in Register”. Dusk OS does this. Not
+only for performance reasons, but also because of the [HAL][hal], which is a
+vast subject we’ll not talk about now.
+
+This peculiarity changes the logic for our word a lot. What
+was:
+
+1. Pop `PS` in a register.
+2. Add register to `PS` top.
+3. Write result to `PS` top.
+4. Return.
+
+Becomes:
+
+1. `PS` top is already in `EAX`.
+2. Add `[ESI]` to `EAX.`
+3. Shrink `ESI` stack by 4 (that is, add 4). Result in `EAX`, which is still
+our `PS` top.
+4. Return.
+
+In NASM, this would look like:
+
+ BITS 32
+ add eax, [esi]
+ add esi, 4
+ ret
+
+This would mean that to compile `foo`, our assembler would only need two
+instructions, `add` and `ret`. Should be easy!
+
+## i386 encoding
+
+Alright, let’s get started. First, we need a reference. [“Intel 80386
+Programmer’s Reference Manual”][i386ref] will do. It’s a big document, but I’ll
+walk you to the important parts, so there’s no need to read it beyond the pages
+I specifically mention, but you have to read those pages to follow this
+article. You might not understand the totality of what you read there, but
+that’s fine, my goal is to explain these pages to you.
+
+Should we begin with the easy instruction? Let’s encode `ret`! The reference
+PDF has a Table of Contents with each instruction listed there. Through this
+TOC, you’ll easily find `ret` at p. 378. We see on that page that there are
+multiple forms of this instruction, but we will not bother with the fancy
+versions and use only the regular one, the “near” version. The listing on that
+page tells us that this particular instruction has a simple opcode, `C3`, with
+no parameter. Too easy! We can thus add our first word in `myasm.fs`:
+
+ : ret, $c3 c, ;
+
+Why `”ret,”` instead of `”ret”`? To indicate that the effect of this word is to
+“write” to “here”. It’s just better form, Forth wise.
+
+That assembler can now create its first word:
+
+ f<< myasm.fs
+ code nothing ret,
+
+You can now call `nothing` and see that it does nothing. To be sure, you could
+try inspecting your stacks before and after with the `.S` utility word.
+
+Starting Forth doesn’t talk about `code`, but it’s a frequent word in Forths
+that support assemblers. `“code xxx”` is the equivalent to `“: xxx”` except
+that it doesn’t go in compilation mode. This means that words we call
+afterwards are interpreted. The result of the snippet above is thus to create
+an empty `“nothing”` dictionary entry, followed by `C3`. You can verify this
+with `“‘ nothing dump”`.
+
+## i386 modr/m
+
+So, that’s if for `ret`. Does this mean that half the job is done? Obi-Wan
+would answer “yes, from a certain *point of view*”. But you know better than to
+take the words of ghosts at face value right?
+
+`add` is a much more complex instruction (p. 261) and we need to cover two of
+its forms:
+
+1. “direct register + indirect register”, that is, reference the 4 bytes
+(`dword` in x86-speak) where `ESI` points to and combine it to register `EAX.`
+2. “direct register + immediate”, that is, reference the 4 bytes constant
+encoded in the instruction itself and combine it to register `ESI`.
+
+How should we encode them? When you look at the i386 reference, it’s hard to
+understand what you read and find the encoding you’re supposed to use. You
+easily see the “EAX, imm32” form, which should fit one of the forms if it
+targeted the right register, but the rest of the forms are gibberish. You can
+guess that “r32” means a reference to a 32-bit register, but what’s “r/m32”?
+How do we handle indirect references? The answer is that these addressing forms
+are encoded in x86’s famous “modr/m” encoding (p. 241).
+
+As we’ve already seen before, the x86 family has a wide range of addressing
+modes for its instructions. Those addressing modes are encoded in a second byte
+following the main instruction opcode. Except for the “shortcut forms” of some
+instructions (such at the “EAX, imm32” one we see for `add`), most instruction
+forms involving a register or a memory location will have the modr/m byte.
+There’s at most one per instruction, and it has this bit
+structure:
+
+1. `b7:6` mod
+2. `b5:3` reg
+3. `b2:0` r/m (register or memory)
+
+The naming is confusing, but to Intel’s defense, I don’t see what other names
+I’d use. Those fields are too flexible for a more specific naming scheme.
+
+The `mod` field selects one of the 4 general addressing modes[^2] documented at
+p. 244:
+
+* `00 [reg]`
+* `01 [reg + 8b offset]`
+* `02 [reg + 32b offset]`
+* `03 reg`
+
+In other words, whenever the modr/m byte fits the `C0` mask, it means that both
+operands are “direct”, otherwise, one of the operands is “indirect”.
+
+The next two fields select the operands for the instructions using register IDs
+also documented in p. 244[^3]. The `reg` field is generally used for the
+"direct register" operand and is not affected by `mod`. In the `“add eax,
+[esi]”` instance, this field would contain a reference to `EAX`, thus 0. This
+field is **not** always the destination. For example, in `“add [esi], eax”`,
+the `reg` field would **also** contain `EAX`. It’s the instruction opcode that
+determines operands order (`01` vs `03`)[^4][^5].
+
+The last field, `r/m`, is the ID of the register that will be affected by
+`mod`. For example, under a mod `00`, we would set `r/m` to `ESI` (6) to
+express `[ESI]`.
+
+With this knowledge, we can encode `“add eax, [esi]”` in our head. First, we
+need to select the right `add` form, which is the “r32, r/m32” one. This means
+a `03` instruction opcode. Then, we need a modr/m byte with `mod=00 reg=EAX
+r/m=ESI`, which means `06`. This gives us a final encoded instruction: `03 06`.
+Is this what NASM gives us? Great, we agree!
+
+## Assembler API
+
+First of all, let’s have useful constants:
+
+ 0 const eax
+ 1 const ecx
+ 2 const edx
+ 3 const ebx
+ 4 const esp
+ 5 const ebp
+ 6 const esi
+ 7 const edi
+
+We’ll use these constants as arguments to our API. Now, the tricky part is to
+elegantly express the “use `EAX` as a direct destination and use `ESI` as an
+indirect source`”` command to the `add` instruction. Dusk’s i386 assembler has
+such an API, but the scope of this subject is too wide for this story arc.
+We’ll defer this to another story arc and go with a brutally simple, albeit
+ugly, API: specifying the form in the word name.
+
+For example, for the form we need, we will add a word named `“addr[], ( dst src
+-- )”` which means “write the `add` instruction in its “r32, r/m32” form with
+`mod=00 dst=r32 and src=r/m32`”. With what we already know of i386 encoding,
+the implementation of this word is trivial:
+
+ : addr[], ( dst src -- ) $03 c, swap 3 lshift or c, ;
+
+This allows us to create a new word:
+
+ code foo eax esi addr[], ret,
+ 42 12 foo . \ prints 54
+
+So, that’s it? Success? From a certain point of view, yes, but this word has a
+problem: it leaks an element to `PS`. Instead of the signature `“a b -- n”`
+that we want, it has the signature `“a b -- a n”`. To drop the `a`, we need to
+“nip” it from the stack with `“add esi, 4”`.
+
+The instruction form that allows this is the “r/m32, imm32”[^6] form. This one
+also has a modr/m byte, but also a 32-bit immediate that will follow it. The
+modr/m byte in this case is special: it only has one operand. In these cases,
+only the `mod` and `r/m` fields are used, `reg` stays empty. **However**, to
+pack as many instructions as possible in as few bytes as possible, i386
+encoding scheme use these 3 precious bits to expand the tight 8-bit base opcode
+space. Therefore, for the “r/m32, imm32” form, `add` shares the `81` opcode
+with other instructions such as `sub, adc, `etc. So, you can’t put *anything*
+in the `reg` field, you have to put what Intel tells you to put. In the
+documentation, it’s the number after the slash. In our case `/0`.
+
+Wrapping all this up, this allows us to add our new word:
+
+ : addri, ( reg imm -- ) $81 c, swap $c0 or c, , ;
+
+You guessed what the `”$c0 or”` was for? To select `mod=03`! With this new
+word, we can complete our `foo` word:
+
+ code foo
+ eax esi addr[],
+ esi 4 addri,
+ ret,
+ 42 12 foo . \ prints 54
+
+You can now see, with `.S`, that we don’t leak to `PS` anymore! This completes
+our extremely minimal i386 assembler. We’ll be able to use it in our C
+compiler, when comes the time of generating the code.
+
+*The source code for our toy assembler can be [downloaded here][srctgz]*
+
+## Up next
+
+In the next article, we’ll set aside our shiny new assembler for a while as we
+tackle the first part of a C compiler by building a tokenizer.
+
+[^1]: PC build takes a little while. This is because I insist on using Dusk’s
+tools to build the destination FAT12 filesystem rather than POSIX ones, and
+those tools have to run through Dusk’s POSIX VM which is pretty slow. Every
+time you make a change to the code, this build process will have to repeat.
+Sorry about that, there isn’t much to do about it. Dusk is designed to work
+from within itself (which means not rebuilding it all the time), but I don’t
+want to force you to learn to use Dusk’s text editor, which is a bit rough
+around the edges. You can speed up the build process a little bit by deleting
+the “fs/doc” directory from your source tree.
+[^2]: I ignore special cases for simplicity reasons and because we won’t use
+them in this story arc.
+[^3]: EAX=0 ECX=1, etc.
+[^4]: It’s also this opcode that determines that the operation is a 32-bit one.
+If we wanted to do the same operation with the same operands in 8-bit, we’d
+change the opcode to `00` or `02`. What about 16-bit? It’s complicated and out
+of scope of this article.
+[^5]: It’s a bit confusing, but one realization about i386 will help you grok
+the logic. In i386, it’s impossible to have an instruction with two “mod”
+operands. For example, `“add [esi], [eax]”` is impossible. Therefore, one of
+the operands is always “direct”. `reg` is the one.
+[^6]: Let’s ignore the imm8 optimization for now.
+
+[srctgz]: https://tumbleforth.hardcoded.net/01-duskcc/09-dusktillc.tar.gz
+[prev]: 08-immediate.html
+[dusk]: http://duskos.org/
+[iter]: https://git.sr.ht/~vdupras/duskos/tree/master/item/fs/doc/iter.txt
+[usage]: https://git.sr.ht/~vdupras/duskos/tree/master/item/fs/doc/usage.txt
+[ged]: https://git.sr.ht/~vdupras/duskos/tree/master/item/fs/doc/text/ged.txt
+[duskdoc]: https://git.sr.ht/~vdupras/duskos/tree/master/item/fs/doc/index.txt
+[hal]: https://git.sr.ht/~vdupras/duskos/tree/master/item/fs/doc/hal.txt
+[brad]: https://www.bradrodriguez.com/papers/moving1.htm
+[i386ref]: https://css.csail.mit.edu/6.858/2014/readings/i386.pdf
A 01-duskcc/main.css => 01-duskcc/main.css +20 -0
@@ 0,0 1,20 @@
+body {
+ font-family: Arial, Helvetica, sans-serif;
+ font-size: 17px;
+ background-color: white;
+ max-width: 800px;
+ margin: 0 auto 0 auto;
+ padding: 0 20px;
+ text-align: justify;
+}
+
+table {
+ border-collapse: collapse;
+}
+table td, table th {
+ border: 1px solid black;
+}
+
+blockquote {
+ font-style: italic;
+}
M Makefile => Makefile +15 -15
@@ 7,32 7,32 @@ ARTICLES_WITH_TGZ = \
01-duskcc/05-dolookup \
01-duskcc/06-taletwostacks \
01-duskcc/07-babywalk \
- 01-duskcc/08-immediate
+ 01-duskcc/08-immediate \
+ 01-duskcc/09-dusktillc
ARTICLES = \
$(ARTICLES_WITH_TGZ)
-OUTDIR = website
-OUTHTML = $(addprefix $(OUTDIR)/, $(addsuffix .html, $(ARTICLES)))
-OUTTGZ = $(addprefix $(OUTDIR)/, $(addsuffix .tar.gz, $(ARTICLES_WITH_TGZ)))
-ALLCSS = $(OUTDIR)/main.css $(addprefix $(OUTDIR)/, $(addsuffix /main.css, $(STORY_ARCS)))
+BUNDLEDIR = bundles
+OUTHTML = $(addsuffix .html, $(ARTICLES))
+OUTTGZ = $(addsuffix .tar.gz, $(ARTICLES_WITH_TGZ))
+STORIESTGZ = $(addprefix $(BUNDLEDIR)/, $(addsuffix .tar.gz, $(STORY_ARCS)))
.PHONY: all
-all: $(OUTHTML) $(OUTTGZ) $(ALLCSS) $(OUTDIR)/index.html
+all: $(OUTHTML) $(OUTTGZ)
-$(OUTDIR)/index.html: index.html
- cp $< $@
+.PHONY: bundles
+bundles: $(OUTHTML) $(STORIESTGZ)
-$(ALLCSS): main.css
- mkdir -p `dirname $@`
- cp main.css $@
+$(STORIESTGZ): $(BUNDLEDIR)/%.tar.gz: %
+ mkdir -p $(BUNDLEDIR)
+ tar zcf $@ --exclude "*.tar.gz" $<
-$(OUTHTML): $(OUTDIR)/%.html: %.md
- mkdir -p `dirname $@`
+$(OUTHTML): %.html: %.md
markdown_py -x footnotes $< | cat header.html - footer.html > $@
-$(OUTTGZ): $(OUTDIR)/%.tar.gz: %
+$(OUTTGZ): %.tar.gz: %
tar czf $@ $<
.PHONY: clean
clean:
- rm -rf website
+ rm -f $(OUTHTML) $(OUTTGZ) $(STORIESTGZ)
M index.html => index.html +14 -0
@@ 57,6 57,20 @@ before you receive the bundle.</p>
My <a href="01-duskcc/01-buckleup.html">“pilot” story arc</a> is on the subject of Dusk OS’ C compiler.
</p>
+<p>Table of Contents</p>
+<ol>
+ <li><a href="01-duskcc/01-buckleup.html">Buckle up, Dorothy</a></li>
+ <li><a href="01-duskcc/02-baremetal.html">Liberation through bare metal</a></li>
+ <li><a href="01-duskcc/03-onesector.html">One sector to rule them all</a></li>
+ <li><a href="01-duskcc/04-wordsshell.html">Words in the shell</a></li>
+ <li><a href="01-duskcc/05-dolookup.html">Do Look Up</a></li>
+ <li><a href="01-duskcc/06-taletwostacks.html">A tale of two stacks</a></li>
+ <li><a href="01-duskcc/07-babywalk.html">Baby's first steps</a></li>
+ <li><a href="01-duskcc/08-immediate.html">The Unbearable Immediateness of Compiling</a></li>
+ <li>From Dusk Till C</li>
+ <li>... to write ...</li>
+</ol>
+
<script async
src="https://js.stripe.com/v3/buy-button.js">
</script>