You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
@@ -558,6 +558,315 @@ In additional to traversal there's a query API, but I'll scope that out later.
558
558
I'll leave this here for now. Tomorrow I'll see if I have the time to put
559
559
together an example static site and get started on the actual site generation.
560
560
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".
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`
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
+
561
870
---
562
871
563
872
Down here I'm collecting the little project ideas that tend to pop into
0 commit comments