Skip to content

Commit 0f1cfff

Browse files
committed
generating site
1 parent 49d2d35 commit 0f1cfff

File tree

1 file changed

+317
-8
lines changed

1 file changed

+317
-8
lines changed

content/garden/december-adventure.md

Lines changed: 317 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
title = "december adventure 2025"
33
author = ["Nilay Kumar"]
44
date = 2025-12-08T00:00:00-05:00
5-
lastmod = 2025-12-14T02:07:32-05:00
5+
lastmod = 2025-12-21T17:46:28-05:00
66
tags = ["december-adventure", "code", "japanese", "chinese", "calligraphy", "photography"]
77
draft = false
88
progress = "in-progress"
@@ -17,13 +17,13 @@ I've taken the liberty of retroactively logging the days I missed.
1717

1818
<div class="ox-hugo-table calendar-table">
1919

20-
||||||||
21-
|------------------|------------------|--------------------|--------------------|-------------------|-------------------|-------------------|
22-
| | [01](#december-1) | [02](#december-2-3) | [03](#december-2-3) | [04](#december-4) | [05](#december-5) | [06](#december-6) |
23-
| [07](#december-7) | [08](#december-8) | [09](#december-9) | [10](#december-10) | [11](#december-11) | [12](#december-12) | [13](#december-13) |
24-
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
25-
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
26-
| 28 | 29 | 30 | 31 | | | |
20+
| | | | | |||
21+
|----------------------|----------------------|----------------------|----------------------|----------------------|-------------------|-------------------|
22+
| | [01](#december-1) | [02](#december-2-3) | [03](#december-2-3) | [04](#december-4) | [05](#december-5) | [06](#december-6) |
23+
| [07](#december-7) | [08](#december-8) | [09](#december-9) | [10](#december-10) | [11](#december-11) | [12](#december-12) | [13](#december-13) |
24+
| [14](#december-14-18) | [15](#december-14-18) | [16](#december-14-18) | [17](#december-14-18) | [18](#december-14-18) | [19](#december-19) | [20](#december-20) |
25+
| [21](#december-21) | 22 | 23 | 24 | 25 | 26 | 27 |
26+
| 28 | 29 | 30 | 31 | | | |
2727

2828
</div>
2929

@@ -558,6 +558,315 @@ In additional to traversal there's a query API, but I'll scope that out later.
558558
I'll leave this here for now. Tomorrow I'll see if I have the time to put
559559
together an example static site and get started on the actual site generation.
560560
561+
562+
## december 14-18 {#december-14-18}
563+
564+
Bedridden due to the flu. No adventuring was attempted.
565+
566+
567+
## december 19 {#december-19}
568+
569+
Had a bit of energy by the time evening rolled around, so I decided to learn a
570+
bit more `uxntal`, with the short-term goal of improving my keffiyeh-drawing
571+
pattern to be less hard-coded. I started pretty basic, just trying to learn a
572+
little bit about defining subroutines and branching. I wrote a bit of code to
573+
print the byte or short that's at the top of the working stack.
574+
575+
```uxntal
576+
@print-short ( a* -- a* )
577+
DUP2 SWP print-byte POP print-byte POP
578+
JMP2r
579+
580+
@print-byte ( a -- a )
581+
DUP #f0 AND #04 SFT print-nibble
582+
DUP #0f AND print-nibble
583+
JMP2r
584+
585+
@print-nibble ( a -- )
586+
DUP #0a LTH ?{ !print-hex-digit } !print-digit
587+
JMP2r
588+
589+
@print-digit ( a -- )
590+
#30 ADD .Console/write DEO
591+
JMP2r
592+
593+
@print-hex-digit ( a -- )
594+
#57 ADD .Console/write DEO
595+
JMP2r
596+
```
597+
598+
As far as I understand, the `.Console/write` port only supports printing ascii
599+
characters, so the task was to grab the byte (the case of the short is clearly
600+
reduced to that of the byte in `print-short`), break it up into the two nibbles,
601+
figure out the corresponding ascii values, and printing those.
602+
603+
Now that I'm looking back at the code, it looks absolutely trivial, but I
604+
learned a lot writing it. I'm starting to get comfortable with the working stack
605+
(still don't really know much about the return stack) and I now understand the
606+
`?{ }` construction. In `print-nibble` we use it to branch based on whether the
607+
nibble is 0-9 or a-f.
608+
609+
To make sure the code worked, I set up some test data in memory and looped
610+
through it, printing the bytes encountered:
611+
612+
```uxntal
613+
;test-data
614+
@byte-loop
615+
DUP2 ;test-data SUB2 #00ff GTH2 ?&end
616+
DUP2 LDA print-byte POP #20 .Console/write DEO
617+
INC2 !byte-loop
618+
&end
619+
#0a .Console/write DEOk DEO
620+
```
621+
622+
There's `ff` bytes worth of test data, so the first line of the loop jumps breaks
623+
us out of the loop if we've passed the last relevant memory address. The next
624+
line does the printing, and the last line moves us to the next memory address
625+
before looping. There's a similar chunk of code that moves through the data 2
626+
bytes at a time and uses `print-short`.
627+
628+
I used the sublabel `&end` twice (once in the byte-printing test and once in the
629+
short-printing test), and was curious how things work under the hood. And
630+
indeed, if you look at the `.rom.sym` file generated by the assembler (say with
631+
`cat` or `hexdump -C`), you'll find separate symbols `byte-loop/end` and
632+
`short-loop/end`, so the sublabels are effectively namespaced by the parent label.
633+
634+
Looking around at the `uxntal` documentation I later realized that a slick code
635+
snippet to do this is already provided on the [software page](https://wiki.xxiivv.com/site/uxntal_software.html). It's quite clever
636+
and I haven't yet understood how it works. I'll return to this later (see day
637+
21).
638+
639+
640+
## december 20 {#december-20}
641+
642+
In the process of learning the
643+
basics of `uxntal`, I've been writing code in `emacs` with no syntax highlighting or
644+
other conveniences. I thought today might be a good time to try to change that.
645+
Let's write an `emacs` major mode for `uxntal` using `tree-sitter`!
646+
647+
This is a bit of an experiment in hubris, given that I don't know much about
648+
`emacs`, `emacs-lisp`, `uxntal`, or `tree-sitter`. But I've found a great [article](https://www.masteringemacs.org/article/lets-write-a-treesitter-major-mode) on how
649+
to do this by Mickey Petersen on exactly this topic. In case it's useful to
650+
anyone else, the code will be [here](https://git.sr.ht/~nilaykumar/uxntal-ts-mode). Here's what it looks like so far:
651+
652+
{{< figure src="images/december-adventure-2025/uxntal-ts-major-mode.jpg" alt="screenshot of some dark-mode themed, syntax highlighted uxntal code" >}}
653+
654+
I won't go through all the code in detail,
655+
just some important aspects worth highlighting -- definitely read Mickey's
656+
article if you're interesting in writing your own `tree-sitter` major mode. What
657+
follows is more of a log than a walkthrough.
658+
659+
We start by defining a new major mode derived from `prog-mode`, which is the
660+
generalized "major mode for editing programming language source code".
661+
662+
```emacs-lisp
663+
(define-derived-mode uxntal-ts-mode prog-mode "uxntal[ts]"
664+
"Tree-sitter major mode for editing uxntal code."
665+
(setq-local font-lock-defaults nil)
666+
(when (treesit-ready-p 'uxntal)
667+
(treesit-parser-create 'uxntal)
668+
(uxntal-ts-setup)))
669+
```
670+
671+
Here we set `font-lock-defaults` to `nil`, which I think turns off the default font-locking
672+
system (a somewhat dated regex-based system for syntax highlighting). We're
673+
going to be using `tree-sitter` do that. Next, we make sure that we have the
674+
`uxntal` grammar available, and then create a parser and install it into the
675+
buffer with `treesit-parser-create`. Finally, we'll do a bunch of setup in a
676+
separate function.
677+
678+
In particular, we're going to tell `emacs` which piece of code to highlight in
679+
what color. As far as I can tell, the colors -- or rather, faces -- available to
680+
use are listed in the manual [here](https://www.gnu.org/software/emacs/manual/html_node/elisp/Faces-for-Font-Lock.html). For example, let's start by displaying
681+
comments using `font-lock-comment-face`. To do this, we need to add a new feature
682+
to `treesit-font-lock-feature-list`, and then define it by telling `tree-sitter`
683+
which pieces of code to apply which face to.
684+
685+
```emacs-lisp
686+
(setq-local treesit-font-lock-feature-list '((comment)))
687+
688+
(defvar uxntal-ts-font-lock-rules
689+
'(
690+
:language uxntal
691+
:override t
692+
:feature comment
693+
((comment) @font-lock-comment-face)))
694+
695+
(setq-local treesit-font-lock-settings
696+
(apply #'treesit-font-lock-rules uxntal-ts-font-lock-rules))
697+
```
698+
699+
The key point to focus on here is the query `((comment) @font-lock-comment-face)`.
700+
This is telling tree-sitter that every comment found in the syntax tree should
701+
be decorated with the `font-lock-comment-face`.
702+
703+
Let's take a moment to understand how `tree-sitter` views `uxntal` code. Using `M-x
704+
treesit-explore-mode`, we can see that the code
705+
706+
```uxntal
707+
@print-short ( a* -- a* )
708+
DUP2 SWP print-byte POP print-byte POP
709+
JMP2r
710+
```
711+
712+
has a syntax tree that looks like:
713+
714+
```nil
715+
(subroutine
716+
(label @ (identifier))
717+
(comment)
718+
(opcode DUP2)
719+
(opcode SWP)
720+
(identifier)
721+
(opcode POP)
722+
(identifier)
723+
(opcode POP)
724+
(opcode JMP2r))
725+
```
726+
727+
Pretty easy to understand. So far we've got the comment highlighted. Next let's
728+
highlight the word/subroutine/label `print-short` we're defining. I don't know if
729+
it'd be fine just to write a query for label, but out of an abundance of
730+
caution let's highlight occurrences of label that are children of
731+
subroutine. To this, we can use
732+
733+
```emacs-lisp
734+
:language uxntal
735+
:override t
736+
:feature label
737+
((subroutine (label) @font-lock-function-name-face))
738+
```
739+
740+
Note that if the query had been `((subroutine (label))
741+
@font-lock-function-name-face)` instead, any subroutine with a label child would
742+
be highlighted. Since we don't want to highlight the code of the whole
743+
subroutine, we make sure that we place our face name right after the element to
744+
be highlighted.
745+
746+
Similarly, we can highlight sublabels with:
747+
748+
```emacs-lisp
749+
:language uxntal
750+
:override t
751+
:feature sublabel
752+
((rune (rune_char "&") @font-lock-variable-name-face (identifier) @font-lock-variable-name-face))
753+
```
754+
755+
Note how we single out specifically only runes with `&`, and make sure to
756+
highlight both the rune's character and the identifier itself. We do _not_
757+
highlight the full rune element, though, because that will lead to expressions
758+
like `?&end` being fully highlighted. I'd prefer just `&end` to be highlighted, with
759+
the rune `?` left alone.
760+
761+
As one last example, we can highlight square brackets with the comment face
762+
since they are purely aesthetic:
763+
764+
```emacs-lisp
765+
:language uxntal
766+
:override t
767+
:feature sqbrackets
768+
((brackets [ "[" "]" ] @font-lock-comment-face))
769+
```
770+
771+
Note that in the syntax above we can specify the language every time we're
772+
describing a new feature -- this comes in handy in the case where you're dealing
773+
with multiple languages in a single buffer. The `override t` acts sort of as a
774+
cascading behavior -- features defined later can override those defined earlier.
775+
This could be useful, for instance, if you wanted to single out comments that
776+
comments that are defined on the same line as a label.
777+
778+
One final note here: currently my feature list looks like:
779+
780+
```emacs-lisp
781+
(setq-local treesit-font-lock-feature-list
782+
'((comment opcode hex_literal raw_ascii label sublabel relpad abspad sqbrackets)))
783+
```
784+
785+
We could instead write this as
786+
787+
```emacs-lisp
788+
(setq-local treesit-font-lock-feature-list
789+
'((comment opcode hex_literal raw_ascii)
790+
(label)
791+
(sublabel)
792+
(relpad)
793+
(abspad)
794+
(sqbrackets)
795+
))
796+
```
797+
798+
where we've rearranged how many sublists our features are split into. There's a
799+
variable `treesit-font-lock-level` that controls how far down this list
800+
`tree-sitter` will go when actually executing features. I wasted a lot of time
801+
because I had arranged each feature into its own sublist and then was very
802+
confused when all but the first 4 of my features weren't activating. For now
803+
I've thrown everything in a flat list. Later on, when I know a bit more about
804+
`uxntal`, I'll think about how to group them more carefully. The documentation
805+
tells us:
806+
807+
> Major modes categorize their fontification features into levels,
808+
> from 1 which is the absolute minimum, to 4 that yields the maximum
809+
> fontifications.
810+
>
811+
> Level 1 usually contains only comments and definitions.
812+
> Level 2 usually adds keywords, strings, data types, etc.
813+
> Level 3 usually represents full-blown fontifications, including
814+
> assignments, constants, numbers and literals, etc.
815+
> Level 4 adds everything else that can be fontified: delimiters,
816+
> operators, brackets, punctuation, all functions, properties,
817+
> variables, etc.
818+
819+
Another thing I found slightly confusing at first was the syntax tree query
820+
system. The [manual](https://www.gnu.org/software/emacs/manual/html_node/elisp/Pattern-Matching.html) was kinda helpful, though I wish there were more concrete
821+
examples. Mostly I wish `treesit-explore-mode` allowed for running queries
822+
directly. Instead, I had to play around with `treesit-query-capture` and
823+
`treesit-node-on` manually, which was a bit painful. Still, the flexibility,
824+
experimentability, and level of documentation in `emacs` is super impressive. The
825+
fact that I could get syntax highlighting working as a complete beginner is a
826+
testament to `emacs` strengths (a lot of `M-x helpful...` commands were run).
827+
828+
With this small success under my belt, I'm a bit more confident about
829+
implementing other features that a `uxntal` major mode might have, so I hope to
830+
come back to this soon.
831+
832+
833+
## december 21 {#december-21}
834+
835+
Let's return to the `uxntal` code snippet for printing hex that we were looking at
836+
on day 19:
837+
838+
```uxntal
839+
@<phex> ( short* -: )
840+
SWP /b
841+
&b ( byte -: )
842+
DUP #04 SFT /c
843+
&c ( byte -: )
844+
#0f AND DUP #09 GTH #27 MUL ADD [ LIT "0 ] ADD #18 DEO
845+
JMP2r
846+
```
847+
848+
Two key things to understand here:
849+
850+
1. instead of branching we can just use a piecewise formula to determine the
851+
ascii character of a nibble. Easy enough
852+
2. the subroutine/sublabel calls `/b` and `/c` are crucial here. What happens is
853+
that when we jump to `/b`, we've pushed the address of the call location to the
854+
return stack, so by the time we print out the first nibble (out of 4 total to
855+
be printed), we have on the return stack the address of the calls to `/b` and
856+
`/c`. The `JMP2r` call pops the `/c` and has us then go print the second nibble.
857+
Since the top of the return stack is now `/b`, the `JMP2r` after printing the
858+
second nibble takes us back to (just after) the `/b` call, and we thus repeat.
859+
We end up printing two more nibbles, but this time of the next byte (as the
860+
working stack has changed).
861+
862+
Very very clever: thanks to `d_m` on `irc` for helping me understand some of this. I
863+
was confused at first because I couldn't see how the print (`#18 DEO`) was getting
864+
called 4 times, which would be necessary for printing the nibbles of a short.
865+
What I hadn't understood is that the jumps to the sublabels here do more than
866+
just move the program counter -- they also modify the working stack. In other
867+
words, `JMP2r` is not a simple `return`-from-`@subroutine`, the way I had been
868+
conceptualizing it.
869+
561870
---
562871

563872
Down here I'm collecting the little project ideas that tend to pop into

0 commit comments

Comments
 (0)