summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--config.toml12
-rw-r--r--content/posts/now-2026-spring/index.md62
-rw-r--r--content/posts/now-2026-summer/index.md25
-rw-r--r--content/projects/atk16_v2.md488
-rw-r--r--content/projects/cookbook.md24
-rw-r--r--static/style.css16
7 files changed, 615 insertions, 13 deletions
diff --git a/.gitignore b/.gitignore
index ef1e8ed..045d821 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
public/
linklog.json
.envrc
+static/giallo.css
diff --git a/config.toml b/config.toml
index c78fa27..6a47a86 100644
--- a/config.toml
+++ b/config.toml
@@ -15,15 +15,17 @@ build_search_index = false
taxonomies = [{ name = "kind", feed = true }]
[markdown]
-# Whether to do syntax highlighting
-# Theme can be customised by setting the `highlight_theme` variable to a theme supported by Zola
-highlight_code = true
-highlight_theme = "css"
-error_on_missing_highlight = true
external_links_target_blank = true
bottom_footnotes = true
lazy_async_image = false
insert_anchor_links = "heading"
+[markdown.highlighting]
+# A valid theme is required, but not actually used. `style.css` overrides the highlighting classes (z-*).
+# Causes static/giallo.css to be generated.
+theme = "github-dark"
+style = "class"
+error_on_missing_language = true
+
[extra]
# Put all your custom variables here
diff --git a/content/posts/now-2026-spring/index.md b/content/posts/now-2026-spring/index.md
index e6a0811..e52a7cc 100644
--- a/content/posts/now-2026-spring/index.md
+++ b/content/posts/now-2026-spring/index.md
@@ -1,22 +1,30 @@
---
title: 🌱 Now, spring 2026
-date: 2026-01-01
-description: TODO
+date: 2026-04-12
+description: Planning the wedding, reworking my wardrobe. Home reno. Dog escaped.
kind: now
-draft: true
---
-Planning the wedding, figuring out my style.
+Planning the wedding, reworking my wardrobe. Home reno.
{{ fig(src="17670809516920.jpg", alt="Dogs playing in the dog park.") }}
+Jojo (the shiba inu) breached containment one night. We were going to take him out on a walk but the dog was nowhere to be seen. The backyard gate was slightly open. We tried finding him by driving around the neighborhood. Some people on social media had seen him walking around so we tried to track him, but we were always too late to the scene.
+
+Luckily, he walked back home and was waiting for us in the backyard.
+
+You're a smart boy, Jojo, but don't do that again. One of the most stressful evenings of my life.
+
{{ toc() }}
## TV & anime
+I was sick for a week so I had ample time to watch a bunch of stuff from my backlog.
+
**Started**
- Chiikawa. The episodes are so short that we watch a bunch at a time.
+- Jojo's Bizarre Adventure: Steel Ball Run aka part 7 (where are the episodes, Netflix?)
**Finished**
@@ -24,23 +32,44 @@ Planning the wedding, figuring out my style.
- The Witcher, seasons S2–S4
- Golden Boy
- Yuru Camp S1
+- Beyond the Boundary (series and Future movie)
+- Paprika (movie)
+- The Boy and the Heron (movie)
+
+## Manga
+
+**Started**
+
+- Jojo's Bizarre Adventure: Jojolion, aka part 8
+- Witch Hat Atelier (in Japanese)
+
+**Finished**
+
+- Berserk 1–43. RIP Kentaro Miura.
+- DU-RAN-KI
## Games
- Stray
-- Pizza Tower
+- Pizza Tower (dropped midway, didn't spark interest)
+- Castlevania: Symphony of the Night
+- Warhammer 40K: Space Marine II
+- Absolum
+
+I also won a game of Twilight Imperium (4th ed).
## Music
- Stopped using Spotify. I already pay for Youtube Premium + Music so I already have a streaming service that I can use. I also try to be more mindful of my music consumption and focus on supporting independent artists and creators. Mostly by buying digital albums or tapes from Bandcamp or distributors.
- Saw [Merzbow](https://merzbow.net/show/) live.
+- Went to a [Bongo's Bingo](https://www.bongosbingo.fi/).
I listen to a bunch of black metal and adjacent genres. There's a lot of white supremacists and fascists in those communities, especially in the Finnish scene (sadly). I figured I should use a couple of hours to go through some of the artists that I have listened to and see if they're sketchy or not.
I'm happy to find that most of the stuff I listen to is a-ok, and a great deal of artists are vocally against hate, or at least apolitical (whatever that means). However, some artists had slipped through the cracks.
**Mgła** collaborates with Nazis and the frontman has an earlier project with clearly anti-semitic titles and lyrics.
-**Drudkh** is Ukrainian ultra-nationalist. They have an album dedicated to a fascist figure who was a Nazi collaborator, and they have used Nazi imagery during their live shows.
+**Drudkh** has used Nazi imagery during their live shows.
I really enjoyed their music in 2025, but I won't be supporting them anymore.
@@ -48,14 +77,31 @@ I really enjoyed their music in 2025, but I won't be supporting them anymore.
<iframe style="border: 0; width: 400px; height: 472px;" src="https://bandcamp.com/EmbeddedPlayer/album=2777761805/size=large/bgcol=ffffff/linkcol=0687f5/artwork=small/transparent=true/" seamless><a href="https://agriculturemusic.bandcamp.com/album/the-spiritual-sound">The Spiritual Sound by Agriculture</a></iframe>
-(**Agriculture** is radically left-wing, by the way)
-
## This website
The new Zola-based implementation has been working well. There was a bunch of figuring-out to do at first, like how to structure the templates to get the [/now](/now) page working, or how to get the previous/next post links appended to each post, but it's been worth it. Zola compilation is way faster than my earlier custom Scheme-based SSG.
My earlier system had the major upside that it directly read Markdown from my Obsidian vault, which allowed me to edit posts on the go. Currently everything's laptop-driven, which is bit of a pain. I might set up something with file shares to allow editing posts from my phone. Deployments will still happen from the laptop.
+## Home improvements
+
+Quick-fire round:
+
+Woodworking:
+
+- new windowsill and shelf in the living room
+ - great for positioning the rear bluetooth speakers
+- shoe rack inside a cabinet in Marika's room
+- shoe rack inside a cabinet in the front door hallway
+
+Bought furniture:
+
+- new metal and glass cabinet in the kitchen
+- a bench for not-clean-but-not-dirty clothes, upstairs
+- a new towel rack for the bathroom
+
+The studio room is also progressing after a hiatus. I got the server/synth rack set up and the old studio desk disassembled. Excited about getting things moving in that room too.
+
## Previous update
- [❄️ Now, winter 2025](/posts/now-2025-winter)
diff --git a/content/posts/now-2026-summer/index.md b/content/posts/now-2026-summer/index.md
new file mode 100644
index 0000000..2ec84df
--- /dev/null
+++ b/content/posts/now-2026-summer/index.md
@@ -0,0 +1,25 @@
+---
+title: ☀️ Now, summer 2026
+date: 2026-06-01
+description: TODO
+kind: now
+draft: true
+---
+
+{{ toc() }}
+
+## Games
+
+- The Messenger
+
+## New laptop
+
+TODO
+
+## This website
+
+Obsidian + Syncthing
+
+## Previous update
+
+- [❄️ Now, spring 2026](/posts/now-2026-spring)
diff --git a/content/projects/atk16_v2.md b/content/projects/atk16_v2.md
new file mode 100644
index 0000000..093c9fd
--- /dev/null
+++ b/content/projects/atk16_v2.md
@@ -0,0 +1,488 @@
+---
+title: ATK16 v2
+weight: 0
+draft: true
+extra:
+ kind: project
+---
+
+## 2026-03-05 code example with inlining semantics
+
+- inlining probably hard to do in a single pass, would have to maintain some kind of compiler call stack
+- `( i n )` are stack assertions, checked at compile time when encountered
+
+```c
+: mmio/console/in ( -- addr ) #beef ; /* push the console input address */
+
+ns: my-program /* set namespace to my-program. the % symbol is replaced with this namespace */
+: %/count ( -- n ) #f ; /* define word that pushes the loop count */
+
+: % ( -- ) /* define main program */
+ %/count loop{ /* countdown from count..0, writing each value to console */
+ ifn'{ drop break } /* jump to } if stack top is nonzero */
+ /* note: ifn'{ is the nonconsuming variant of ifn{ */
+
+ ( i n )
+ ~mmio/console/in swap ( i addr n )
+ /* note: ~ inlines the callee body instead of emitting a jump */
+
+ ! /* write to the console port */
+
+ 1 - ( i-1 )
+ } ;
+
+my-program /* run the program */
+```
+
+- compile & run idea: one enter press runs compile, second in a row runs from last stop point
+ - how exactly does control return to the editor/REPL?
+- better idea: normal keypresses just go into a preallocated text area, and a CTRL+R (etc) run command starts parsing and running it
+ - note that nothing will jump back automatically, so emit a control stack pop and jump to return to the REPL
+ - read: get characters until whitespace or EOF
+ - execute: get matching word from dictionary and jump to it, error if not found
+ - print: some status information, dict size etc.
+ - loop
+- the compiler word (:) will consume more words from the input area
+
+## code example with immediate execution semantics, no inlining
+
+```c
+: mmio/console/in ( -- addr ) #beef ; /* push the console input address */
+
+ns: my-program /* set namespace to my-program. the % symbol is replaced with this namespace */
+: %/count ( -- n ) #f ; /* define word that pushes the loop count */
+
+: % ( -- ) /* define main program */
+ %/count loop{ /* countdown from count..0, writing each value to console */
+ ifn'{ drop break } /* jump to } if stack top is nonzero */
+ /* note: ifn'{ is the nonconsuming variant of ifn{ */
+
+ ( i )
+ ~{ mmio/console/in # } /* inside ~{ } is immediate mode, which makes all words act like immediate/compiler words */
+ /* # takes a word X from the compiler data stack and emits a LIT X instruction that pushes X */
+ over ( i addr n )
+
+ ! /* write to the console port */
+
+ 1 - ( i-1 )
+ } ;
+
+my-program /* run the program */
+```
+
+- This makes me think that a simple interpreter is probably necessary as part of the REPL
+- It doesn't have to support everything, maybe only word calls e.g. `my-program` and `:`, and maybe literals too e.g. `#abcd`
+ - The interpreter should NOT run compiler words
+
+The core REPL loop should then look like this:
+
+- Read next word
+- If it starts with a `#`, push value to stack, continue loop.
+- Find a matching word. If it exists and is not a compiler word, call it, continue loop.
+- Print error, continue loop.
+
+The core `:` loop should look like this:
+
+- Enable "compiler mode flag"
+- Parse name
+- Parse stack effect declaration, store in metadata field
+- Read next word
+- If the word starts with a sigil (not a-zA-Z0-9 basically, but with öäå), move parser pointer to second character, set WORD to `:/sigil/<symbol>`
+ - The sigil handlers mostly expand to a reader word, i.e. a word that consume from the input stream
+ - E.g. `#1000` -> `[ LIT 1000 ]`
+- Else, set WORD to the whole parsed word.
+- If WORD is a compiler word && immediate mode is on, print error, exit `:`.
+- If WORD is a compiler word || immediate mode is on, call it, continue.
+- Lookup the word as is and emit a call to it, continue.
+- Try interpreting the word as an hex value and emit it as is, continue.
+- Print error, exit `:`.
+
+Global variables
+
+```c
+:/mode /* 1 if compiling, 0 if interpreting */
+:/cword/offset /* current word to compile, offset into input stream */
+:/cword/length
+
+etc todo
+```
+
+- Still a bit unsure how the `ns:` and `%` syntax should look like.
+ - It currently feels like it would require a bunch of special processing in the `:` compiler to be supported
+
+```c
+: const ( -- a )
+ ~{ R/next R/interpret # } ; immediate
+
+: constants/abc #beef ;
+: my-program ( -- )
+ [ const constants/abc ] ( abc ) /* [ and ] are just for formatting, no effect */
+ #0010 + ( n )
+ drop
+;
+```
+
+- How should the stack analysis handle immediate mode execution? Just blindly trust the declared stack effect?
+- We can't really disable stack effect checking except on a whole-word basis
+ - Maybe require that each immediate block is followed by a stack assertion to get the checker back on track?
+ - Could be hard to use
+- Maybe just remove the `~{` and `}` immediate mode altogether and require a "wrapper compiler word" for metaprogramming
+ - Then we could just trust the stack effect of the compiler/immediate word
+ - Would be very simple:
+
+```c
+: count ( -- a ) #f # ; immediate
+: program ( -- )
+ count ( n )
+ drop ( )
+ ;
+```
+
+- Yeah, I think the above is good
+- Now I'm wondering about conditionals. If we just have `if{` and `}` (and possibly a `ifz{` variant), we must require that the fallthrough delta == the "branch taken" delta, which means that "branch taken" delta must be always be zero. This is very inconvenient. Maybe we could group conditionals something like this:
+
+```c
+#0 cond{
+ ?{ /* if zero */ }
+ 10 - ?{ /* if 16 (decimal) */ }
+}
+```
+
+- The semantics of the above idea:
+ - `cond{` starts a conditional context
+ - `}` ends it, patch all "jump to end" slots
+ - `?{` emit "jump if zero", add its slot to patch list
+ - `}` patch jump, emit "jump to end", add its slot to patch list
+ - stack effect checker ensures that join point after cond block is consistent
+
+```c
+#0
+ dup ?{ /* if zero */ },
+ dup 1 - ?{ /* if one */ },
+ dup 2 - ?{ /* if two */ },
+ else{ /* otherwise */ }
+```
+
+- Semantics of above:
+ - `?{` start branch, emit JNZ to next closing brace (`}`, or `},`), add slot to patch list
+ - `},` emit a JMP to join point (`}`), add its slot to patch list, close branch, patch JNZ
+ - `}` close branch, patch JNZ and JMPs
+- "else" can be simulated with a `drop #0 ?{`
+ - or maybe just a `else{` that is a NOP that adds a dummy patch list entry for `}` to work properly
+- should work with the stack checker without any tricks
+
+- We probably have to keep track of the number of patch slots since the `}` must patch multiple slots, and it cannot patch all of them because then it might patch those from an outer context (a loop etc.). Maybe in the loop context object?
+
+```c
+/* Control context struct */
+[ 16 context type ][ 16 patch slots in ctx ]
+
+: ctrl/type/word ( -- a ) #0 ; immediate
+: ctrl/type/?{ ( -- a ) #1 ; immediate
+: ctrl/type/loop{ ( -- a ) #2 ; immediate
+
+: }/word ( ps-count ctx-type -- )
+ /* todo */
+ ; immediate
+
+: } ( -- )
+ C> C> ( ps-count ctx-type )
+ dup ctrl/type/word - ( ps-count ctx-type pred? )
+ [ callz }/word ]
+ dup ctrl/type/?{ - ( ps-count ctx-type pred? )
+ [ callz }/{? ]
+ ; immediate
+```
+
+- Idea above: the control structures `?{ ... }` and `loop{ break next ... }` cannot be defined in terms of themselves
+ - => Need to use `callz` (call if zero) and `calln` (call if nonzero).
+
+- Radical idea: only one stack (no control stack):
+
+```c
+: foo ( a b r -- a+b r )
+ rot2 ( r a b )
+ + ( r a+b )
+ swap
+ ;
+
+
+: program ( -- n )
+ 1 2 foo ;
+```
+
+- Yea uhh very unwieldy
+
+- Idea about notation for both data and return stacks:
+
+```c
+: foo ( a -- n :: r -- )
+ c>d ( a b :: )
+ + ( a+b )
+ ;
+```
+
+- Syntax for the stack effect declaration
+
+```c
+/* no declaration */ => error, data stack effect required
+( ) => error, missing -- in data stack
+( -- ) => data: 0 in, 0 out; return: 0 in, 0 out
+( a b -- c ) => data: 2 in, 1 out; return: 0 in, 0 out
+( a b -- c :: ) => error, missing -- in return stack
+( a b -- c :: r -- g ) => data: 2 in, 1 out; return: 1 in, 1 out
+```
+
+- Syntax for the stack assertion:
+
+```c
+/* no declaration */ => no assertion done
+( ) => data: 0, return: 0
+( a b ) => data: 2, return: 0
+( a b :: ) => data: 2, return: 0
+( a b :: c ) => data: 2, return: 1
+( a b -- :: c ) => error, -- forbidden in assertion
+```
+
+- A dictionary entry format:
+
+```c
+[ 16 link ] /* pointer to start of previous entry, or null */
+[ 16 namelen ] /* length of name, in words */
+[ 16*namelen name ] /* name character data */
+[ 8 ninputs ][ 8 delta ] /* number of inputs on stack, (signed) delta = noutputs - ninputs */
+[ 16 meta ] /* flags etc */
+[ 16 doc ] /* docstring pointer, or null */
+[ 16 deflen ] /* length of definition, in words */
+[ 16*deflen def ] /* definition words */
+```
+
+- `meta` being a bitfield with bits:
+
+```
+[0] compiler word
+[1] program (not data)
+[2..15] unused
+```
+
+- Instruction set concepting
+ - Instructions with apostrophe are non-consuming variants
+ - E.g. `slip` has `( a b -- b a )`, but `slip'` has `( a b -- b a b )`
+ - The jump immediate variants are useful for patching, but they could theoretically be implemented as a `lit <imm>` + `jmp`/`jz`
+ - Instead of implementing jump variants for different ALU flags, maybe allow loading the ALU flags as a bitfield onto the stack (`lfg`)?
+ - Probably not necessary, you can compute overflow/carry/negative using jz and the available ALU ops
+ - The `slip` (top to second), `bury` (top to third), `pick` (second to top) and `dig` (third to top) are named like that for easier remembering
+ - I never remember which way `over` or `rot` work in Forth
+ - The instructions spanning two columns in the table are long format, i.e. two words or 32 bits in total.
+
+```toml
+stack manipulation alu control memory
+[ lit <imm> ] [ add ] [ add' ] [ jma ] [ jma' ] [ load ] [ load' ]
+[ dup ] [ nip ] [ sub ] [ sub' ] [ jza ] [ jza' ] [ store ] [ store' ]
+[ drop ] [ and ] [ and' ] [ jmr ] [ jmr' ]
+[ slip ] [ slip' ] [ or ] [ or' ] [ jzr ] [ jzr' ]
+[ bury ] [ bury' ] [ xor ] [ xor' ]
+[ pick ] [ pick' ] [ shl ] [ shl' ] [ lpc ]
+[ dig ] [ dig' ] [ shr ] [ shr' ] [ nop ]
+[ c>d ] [ c>d' ] [ sar ] [ sar' ] [ hlt ]
+[ d>c ] [ d>c' ]
+```
+
+- for good call ergonomics I think `lpc` should get `PC+2` and push it onto the return stack
+ - call would then be `lpc jmp`
+ - ret would be `c>d jmp`
+- since `lpc` and `jmp` operate on different stacks, we could have one-cycle `call` and `ret` instruction
+ - probably overengineering, it's fine to have two-cycle function prologue and epilogue
+
+- Simple if-else approach instead of the multi branch solution before
+
+```c
+#0
+ if{ true branch }
+ else{ false branch }
+```
+
+- Nah probably not. Let's go with the `?{ ... }` syntax designed above.
+- Thinking about inlining: to inline a `:` definition, the compiler would have to loop over the the compiled body of the definition and emit each word to HERE. This requires a `deflen` to exist. A problem arises with jumps: a word-internal, absolute jump would become invalid if the definition is moved. A relative jump would work still. Jumps to other `:` words would have to absolute, though.
+ - This could be solvable by compiling all control structures (conditional branches and loops) to relative jumps (need to add these to the ISA), and calls to absolute jumps.
+
+- Idea about a stack checker that only has assertions:
+ - The big issue is that the "return type" is now after the body
+
+```c
+: foo ( a b c )
+ + + ( a+b+c )
+ ;
+```
+
+```c
+: foo ( a b c :: r ) /* data: 3, control: 1 */
+ + + ( result :: r ) /* data: 1, control: 1 */
+ c>d ( result r :: ) /* data: 2, control: 0 */
+ d>c ( result :: r ) /* data: 1, control: 1 */
+ ;
+```
+
+- Conclusion! If we forbid:
+ - word-internal absolute addressing jumps, and
+ - word-external relative addressing jumps,
+- and require that words can only directly call already defined words (not self, forbidding recursion),
+- all words become location-independent and can be inlined freely by the compiler via simple `memcpy`
+- it is always beneficial (smaller current word length + fewer total executed instrs) to inline words with deflen<=2
+ - because call overhead is 2 in the caller (prologue)
+- it is mostly beneficial (fewer total executed instrs) to inline words with deflen<=4
+ - because call overhead is 2 in the callee too (epilogue)
+- longer words should be inlined based on some heuristics, e.g. callee max deflen <=8 and caller max deflen <=32 or something like that.
+ - probably could use profiling to find optimum
+
+- Recursion can be reconstructed by continuation passing from an other word: call word `inner` and pass `&inner` as an argument
+ - Actually direct self-recursion is also fine if we just use absolute addressing
+ - This would actually avoid the issue of inlining one copy of a recursive implementation to the outer function that still jumps to the inner one, wasting space
+
+- Another thing: if a word definition ends in a word call, we can do a tail call optimization.
+ - A normal word call at tail position: `[ lpc jma ] [ c>d jma ]`
+ - Optimized word call at tail position: `jma`
+- I.e. we don't have to return back to the caller just to immediately jump to its caller. We can skip the tail call site altogether.
+ - This works through any number of call stack layers
+
+- Thinking about loops: [Moore says](https://www.ultratechnology.com/moore4th.htm) that they prefer to use manual repetition or recursion instead of looping constructs
+- Maybe we could also manage with just a conditional and recursion?
+
+```c
+: con/out #beef ;
+: loop ( count )
+ dup con/out ( count count addr )
+ !
+ #1 - ( count-1 )
+ ?loop /* if non-zero, recurse. this marks `loop` as `noinline` */
+ ;
+: program ( )
+ #f loop ;
+```
+
+- Here, `?` is a sigil that jumps to the word following it using absolute addressing if stack top is non-zero.
+ - `?<word>` consumes its argument, making it a good fit for tail position calls
+ - The current word's header is already written to the dictionary at this point so no special handling needed for self recursion
+- Consider marking a word `noinline` if it refers to itself by name or uses `here` (which emits a literal instruction with the current compiler head address)
+ - A mistakenly inlined "non-inlinable" word would still work I guess, but it would be not very elegant
+ - I.e. the first iteration would be executed from the caller memory, but when the recursive jump is encountered, it jumps to the callee memory. They have the same instructions in both memory areas so it should work, but eh.
+
+- Let's try to imagine an "early return" scenario with fibonacci and see how this model works:
+
+```c
+: return ( :: r )
+ c>d jma ; /* If inlined, this should work like an early return */
+
+: fibo ( n )
+ dup ?return ( n )
+ dup #1 - ?return ( n )
+ #1 - dup ( n-1 n-1 )
+ fibo swap ( fibo_n-1 n-1 )
+ #1 - fibo ( fibo_n-1 fibo_n-2 )
+ + ( fibo_n )
+ ;
+```
+
+- This has multiple problems:
+ - How to notate the return value of the early return? The `return` word itself is not "polymorphic" in data stack height
+ - There are now multiple exit points, how to ensure that they all have the same stack height?
+- This makes me think that early return, at least implemented like this, is not the approach
+
+```c
+: fibo0 ( n :: r _ )
+ c>d drop c>d over ( n r n )
+ jz ( n ) ;
+: fibo1 ( n :: r )
+ #1 - ( n-1 )
+ c>d drop c>d over ( n-1 r n-1 )
+ jz ( n-1 ) ;
+: fibo ( n )
+ fibo0 ( n>0 )
+ fibo1 ( n>1 )
+ dup #1 - ( n n-1 )
+ over #2 - ( n-1 n-2 )
+ + fibo ( n )
+```
+
+- This could work, but it kind of circumvents the whole control stack thing by manually jumping to the caller's return address manually to implement early return
+ - I'm sure there's a better design
+
+- Maybe if the caller provides a "continuation":
+
+```c
+: fibo/0 ( k n )
+ dup rot ( n k n ) jz ;
+: fibo/1 ( k n )
+ #1 -
+ dup rot ( n-1 k n-1 ) jz ;
+: fibo ( n :: k )
+ dup c>d dup ( n n k k )
+ rot swap ( n k k n )
+ fibo/0 ( n k )
+ over ( n k n )
+ fibo/1 ( n )
+ dup #1 - fibo ( n fibon-1 )
+ swap #2 - fibo ( fibon-1 fibon-2 )
+ + ( fibon )
+ ;
+```
+
+- Pretty inconvenient still with all the shuffling on the data stack and the `jz`
+- Maybe if we push the continuation manually on the return stack?
+ - No, that isn't possible since the helper words still need to be able to return to the main word in case the condition does not hold
+- Maybe if we have a specific "early return" keyword `ret` that jumps to `k` on CS
+ - And a conditional variant `?ret` obviously that jumps if `n` on DS is zero
+
+```c
+: ?ret ( pred :: k )
+ c>d' swap jz ( :: k ) ;
+
+: fibo ( n :: k )
+ dup ( n n :: k )
+ ?ret ( n :: k ) /* return to k if tos == 0 */
+ dup #1 - ( n n-1 :: k )
+ ?ret ( n :: k ) /* return k to if tos == 1 */
+ #1 - dup #1 - ( n-1 n-2 :: k )
+ fibo swap fibo + ( fibo_n )
+ ;
+```
+
+- This is actually pretty good, but there's still bit of work with the control stack
+ - Now `?ret` would jump to the continuation where `k` would still be unconsumed on the CS
+- How about trying to do a switch-case kind of jump table?
+
+```c
+/* map 1 => 10, 2 => 20, 3 => 30 */
+: map ( n :: k )
+ dup #10 swap #1 - ( n 10 n-1 :: k )
+ ?ret ( n 10 :: k )
+ ...
+```
+
+- Hmm I don't know
+
+- Maybe a very explicit when-case-then-endwhen construct?
+ - This would be super simple to compile if we disallow nesting, but nesting could be done without issue
+
+```c
+/* map 1 => 10, 2 => 20, 3 => 30 */
+: map ( n )
+ when case dup #1 = then #10
+ case dup #2 = then #20
+ case dup #3 = then #30
+ else #40
+ endwhen drop ( m ) ;
+```
+
+- The idea is that we keep track of two numbers: a predicate delta and a branch delta. First they are undefined and the first case-then compound sets them. The deltas of the later cases must match the first ones, or otherwise error.
+
+Above `map` example hand-compiled:
+
+```c
+/* map ( n ) */
+ case0: dup [ lit 1 ] = [ jz :case1 ] [ lit 10 ] [ jmp :endwhen ]
+ case1: dup [ lit 2 ] = [ jz :case2 ] [ lit 20 ] [ jmp :endwhen ]
+ case2: dup [ lit 3 ] = [ jz :case3 ] [ lit 30 ] [ jmp :endwhen ]
+ case3: [ lit 30 ] [ jmp :endwhen ]
+ endwhen:
+```
diff --git a/content/projects/cookbook.md b/content/projects/cookbook.md
index cbd1617..dd8458c 100644
--- a/content/projects/cookbook.md
+++ b/content/projects/cookbook.md
@@ -109,3 +109,27 @@ Roast in oven red onion, garlic, cauliflower, pecan nuts. Add sage leaves and bl
## Moose meatballs
Prepare soy mince from dark soy crumble. Mix moose meat mince with salt, pepper, garlic powder, onion powder, soy mince, egg, dried mushroom powder. Form into balls. Fry in oven. Serve with mashed potatoes and roasted vegetables with lingonberry jam.
+
+## Chicken curry soup
+
+Sauté a bunch of shallots, garlic. Add dried thai chili (~4). Add bitesize chicken. Add curry paste, chicken stock, coconut milk, garlic powder, ginger, tomato purée. Add water to thin, rice noodles. Garnish with spring onion.
+
+## Hot mango salsa wraps
+
+Make a salsa from fresh mangos, habaneros (1 per mango), cilantro, red onion, salt. Fry Gold & Green veg protein (or chicken) seasoned with spice mix ("grillimauste"). Serve in heated corn & wheat tortillas with mayo and guacamole.
+
+## Salsa verde wraps
+
+Fry Gold & Green veg protein, season with cumin, garlic powder, chili powder, paprika, MSG, salt. Serve in heated corn & wheat tortillas with a good salsa verde (Pirkka Parhaat), red cabbage, cheese, guac.
+
+## Arrabbiata with veg meatballs
+
+Barilla Arrabbiata + Apetit veg meatballs (kasvispullat). Serve with pasta. Parmiggiano on top.
+
+## Spinach feta pie
+
+Prepare ready made savoury pie dough, preheat in oven. Thaw frozen spinach, mix with two eggs, pack of feta, 2dl cream, cheese (e.g. mozz + emmental), salt, pepper, garlic powder. Oven for 35min at 200C. Let rest.
+
+## Adobo Chipotle chicken thighs
+
+Salt (and marinade), then fry chicken thighs in oil to get a good crust. Add soy sauce, chicken stock. Boil for some time. Remove chicken, add chipotle peppers in adobo sauce, and crushed tomatoes. Reduce. Make into paste with a blender, serve over rice with some fresh veggies.
diff --git a/static/style.css b/static/style.css
index 758d523..f32dc54 100644
--- a/static/style.css
+++ b/static/style.css
@@ -363,6 +363,22 @@ p {
line-height: 1.5;
}
+/* Required by Giallo (Zola syntax highlighter) */
+.giallo-l {
+ display: inline-block;
+ min-height: 1lh;
+ width: 100%;
+}
+.giallo-ln {
+ display: inline-block;
+ user-select: none;
+ margin-right: 0.4em;
+ padding: 0.4em;
+ min-width: 3ch;
+ text-align: right;
+ opacity: 0.8;
+}
+
/* Highlighting with Dracula theme as the base */
.z-code {
color: #f8f8f2;