diff --git a/posts/2022-03-14-digitle-in-maude.md b/posts/2022-03-14-digitle-in-maude.md
new file mode 100644
index 0000000..3661133
--- /dev/null
+++ b/posts/2022-03-14-digitle-in-maude.md
@@ -0,0 +1,350 @@
+---
+title: digitle in maude
+date: 2022-03-14
+tags: [maude, computer]
+...
+
+so you know [digitle] right. it's the countdown numbers round.
+
+[digitle]: https://c.eev.ee/digitle
+
+1. you have six numbers to start with, each picked from 1–10 or 25, 50, 75, 100;
+ and a target.
+2. you can add, subtract, multiply, or divide any of the numbers you currently
+ have.
+3. you can only use each number once, and the intermediates have to also all be
+ positive integers. (it is ok to have some numbers left over at the end.)
+
+you probably have not heard of [maude] though. but it's cool. it lets you define
+rewrite systems with a few equational extras like associativity and
+commutativity. in other words it's essentially a domain specific language for
+solving puzzles like this. it also lets you prove stuff like termination of your
+system, but we are not going to do any of that today.
+
+[maude]: https://maude.cs.illinois.edu/w/index.php/The_Maude_System
+
+**i just think maude is neat and this is a nice little example to show it off.**
+that's all this is really.
+
+---
+
+```maude
+mod DIGITLE is
+ protecting NAT .
+ vars M N : Nat .
+```
+
+the file starts with a module declaration. everything has to live inside some
+module, but this program isn't big enough to be worth splitting up so it's all
+going into one. since digitle involves numbers, i'm also importing the `NAT`
+module, and saying that any time `M` or `N` show up in equations or rewrite
+rules they stand for single numbers.
+
+
+
+the first thing i need is a ~~type~~ sort for the pool of numbers we currently
+have. it's a _multiset_: the number of copies of each number matters, but the
+order they're in doesn't. the most convenient way to express that is like this.
+
+```maude
+sort Pool .
+subsort Nat < Pool .
+op nil : -> Pool .
+op __ : Pool Pool -> Pool [assoc comm id: nil] .
+var Ps : Pool .
+```
+
+first i just declare `Pool` as a sort, without saying anything about what it
+looks like. next i say that any `Nat` is also a `Pool` (containing one copy of
+just that number), by making `Nat` a subsort of `Pool`.
+
+in the third and fourth lines, i say that `nil` is a pool (with no numbers), and
+two `Pool`s written next to each other are also a `Pool`. so far, we've defined
+a binary tree. by making `__` associative, we're saying that the exact
+bracketing of the appends doesn't matter, which flattens it to a list. by saying
+it's commutative, we're saying the order _also_ doesn't matter.
+
+the last attribute, `id: nil`, says that `Ps nil = nil Ps = Ps`. it also allows
+a pattern like `M N Ps` to match against `1 2`, setting `Ps = nil`.
+
+it feels a bit weird at times to be specifying constructors of a tree and then
+telling the language to flatten them after the fact, coming from languages where
+you try to design your datatypes to only have one representation per value. but
+the benefit of writing it like this instead of something like
+`op cons : Nat Pool -> Pool` is that maude knows what `assoc`, `comm`, and `id`
+mean, so later when i have some equations and rewrite rules that match on the
+beginning of a list, it knows that it can actually pick those numbers from
+_anywhere_, without needing extra rules to shuffle the list around manually.
+
+
+
+from here, we *could* just define the rewrite rules and be done. something like
+this would work:
+
+```maude
+rl [add] : M N => M + N .
+rl [sub] : M N => sd(M, N) .
+rl [mul] : M N => M * N .
+crl [div] : M N => M quo N if M rem N = 0 .
+```
+
+
+
+the first three rules just pick two numbers from our pool (any two, because of
+`comm`; they don't have to be next to each other), and replace them with the
+result of applying them to one of our operations. the last one is slightly more
+complicated, because we can only divide evenly. `_quo_` ignores the remainder
+altogether, so we need a _conditional rule_ which only fires if the remainder
+actually is zero.
+
+save what we have so far, plus a terminating `endm` for the `mod`, in a file
+`digitle.maude`, and feed it into the repl:
+
+```
+$ maude digitle
+ \||||||||||||||||||/
+ --- Welcome to Maude ---
+ /||||||||||||||||||\
+ Maude 3.2.1 built: Mar 13 2022 18:56:15
+ Copyright 1997-2022 SRI International
+ Mon Mar 14 02:28:22 2022
+```
+
+let's use [random puzzle ALO7b9UK](https://c.eev.ee/digitle/#seed=ALO7b9UK)
+as an example. we have to get to **793** starting from
+**75, 4, 7, 9, 8, 2**. so starting from that list, we want to search for a
+sequence of rewrites that leads to a pool containing 793. one solution is all
+we need.
+
+```
+Maude> search [1] (75 4 7 9 8 2) =>* (793 Ps) .
+search [1] in DIGITLE : 75 4 7 9 2 8 =>* Ps 793 .
+
+Solution 1 (state 9577)
+states: 9578 rewrites: 273357 in 128ms cpu (128ms real) (2135601 rewrites/second)
+Ps --> 7
+```
+
+ok, so there _is_ at least one solution, which leaves 7 unused (because it is
+in the assignment for `Ps`). we can use the state label `9577` to replay the
+sequence that reaches it, and see which steps it took.
+
+```
+Maude> show path 9577 .
+state 0, Pool: 2 4 7 8 9 75
+===[ rl N M Ps => Ps N + M [label add] . ]===>
+state 4, Pool: 4 7 8 11 75
+===[ rl N M Ps => Ps N * M [label mul] . ]===>
+state 146, Pool: 7 11 32 75
+===[ rl N M Ps => Ps N * M [label mul] . ]===>
+state 1733, Pool: 7 32 825
+===[ rl N M Ps => Ps sd(N, M) [label sub] . ]===>
+state 9577, Pool: 7 793
+```
+
+squinting at the available numbers each time, along with the rule labels, we can
+just about make out what it did to get to a solution:
+
+:::twocol
+- 2 + 9 = **11**
+- 4 × 8 = **32**
+- 11 × 75 = **825**
+- 825 − 32 = **793** :star::star:
+:::
+
+ok that's cool. but reading the output is a bit annoying. let's instead keep
+track of what we did so that it's just printed legibly at the end of `search`.
+thanks to maude's flexible expression syntax, we can make this look like pretty
+much whatever we want.
+
+```maude
+sort Op .
+ops + - × ÷ : -> Op .
+
+sort Steps .
+
+--- empty list
+op nil : -> Steps .
+
+--- a single step, like "3 + 4 → 7"
+op ___→_ : Nat Op Nat Nat -> Steps [prec 10] .
+
+--- sequence of steps
+op _,_ : Steps Steps -> Steps [assoc id: nil prec 20] .
+
+var Ss : Steps .
+```
+
+
+
+so solution traces are written like
+`2 + 9 → 11,4 × 8 → 32,11 × 75 → 825,825 - 32 → 793`
+and that is also how maude will print them.
+
+the given state at any point is now going to be the available pool of numbers,
+_plus_ the steps taken so far. this is just a pair, along with an abbreviation
+`\{Ps}` to make the `search` command look a little nicer.
+
+```maude
+sort State .
+op _&_ : Pool Steps -> State .
+
+op {_} : Pool -> State .
+eq {Ps} = Ps & nil .
+
+var S : State .
+```
+
+one last thing before the expanded rewrite rules is a predicate to say what we
+are looking for. this isn't strictly necessary, you could just continue to
+pattern match in the `search` command like last time; i just think this looks a
+bit nicer. but a state "has" a number `N` if `N` occurs anywhere in the pool.
+maude supports repeating pattern variables so this is nice and short.
+
+```maude
+op _has_ : State Nat -> Bool .
+eq (N Ps & Ss) has N = true .
+eq S has N = false [otherwise] .
+```
+
+
+
+
+the new rewrite rules (delete the other ones) have the same behaviour for the
+number pool, and also append the current step to the trace. since we don't need
+`show path` any more i removed the labels.
+
+```maude
+rl M N Ps & Ss => Ps (M + N) & Ss, (M + N → (M + N)) .
+rl M N Ps & Ss => Ps sd(M, N) & Ss, (M - N → sd(M,N)) .
+rl M N Ps & Ss => Ps (M * N) & Ss, (M × N → (M * N)) .
+crl M N Ps & Ss => Ps (M quo N) & Ss, (M ÷ N → (M quo N)) if M rem N = 0 .
+```
+
+now load the updated file into maude and run the search command. i'm using
+`s.t.` (short for `such that`) to identify solutions by a boolean expression,
+the `_has_` function above.
+
+```
+$ maude -no-banner digitle
+Maude> search [1] {75 4 7 9 8 2} =>* S s.t. S has 793 .
+search [1] in DIGITLE : {75 4 7 9 2 8} =>* S such that S has 793 = true .
+
+Solution 1 (state 335734)
+states: 335735 rewrites: 1114984 in 832ms cpu (830ms real) (1340125 rewrites/second)
+S --> (7 793) & (9 + 2 → 11),(8 × 4 → 32),(75 × 11 → 825),(825 - 32 → 793)
+```
+
+and now we have our solution nicely printed!
+
+there is one small thing that is still bothering me. if we are trying `M + N`,
+then trying `N + M` later is just wasting time. so what if we make all the rules
+conditional so that the first argument is never smaller than the second.
+
+```maude
+crl M N Ps & Ss => Ps (M + N) & Ss, (M + N → (M + N)) if M >= N .
+crl M N Ps & Ss => Ps sd(M, N) & Ss, (M - N → sd(M,N)) if M > N .
+crl M N Ps & Ss => Ps (M * N) & Ss, (M × N → (M * N)) if M >= N .
+crl M N Ps & Ss => Ps (M quo N) & Ss, (M ÷ N → (M quo N))
+ if M >= N /\ M rem N = 0 .
+```
+
+and look, it's five times faster now it's not repeating itself.
+
+```
+Maude> search [1] {75 4 7 9 8 2} =>* S s.t. S has 793 .
+search [1] in DIGITLE : {75 4 7 9 2 8} =>* S such that S has 793 = true .
+
+Solution 1 (state 46714)
+states: 46715 rewrites: 270529 in 160ms cpu (158ms real) (1690806 rewrites/second)
+S --> (7 793) & (9 + 2 → 11),(8 × 4 → 32),(75 × 11 → 825),(825 - 32 → 793)
+```
+
+---
+
+here's the full file. the `format` directives i didn't discuss: the one in `_,_`
+puts a space after the comma, and the rest do some colours & indenting to make
+the solution a little bit prettier. it also contains a predicate `_has!_` for
+Hard Mode, that requires _all_ the numbers get used up; and `_almost has_`,
+which finds any almost-solution within 10 of the target, and uses language
+features that maybe i will discuss if i ever write a second post about maude.
+
+```maude
+mod DIGITLE is
+ protecting NAT .
+ var M N : Nat .
+
+ sort Pool .
+ subsort Nat < Pool .
+ op nil : -> Pool .
+ op __ : Pool Pool -> Pool [assoc comm id: nil] .
+ var Ps : Pool .
+
+ sort Op .
+ op + : -> Op [format (r o)] .
+ op - : -> Op [format (g o)] .
+ op × : -> Op [format (y o)] .
+ op ÷ : -> Op [format (b o)] .
+
+ sort Steps .
+ op nil : -> Steps .
+ op ___→_ : Nat Op Nat Nat -> Steps [prec 20 format (d d d d ! o)] .
+ op _,_ : Steps Steps -> Steps [assoc id: nil prec 10 format (d d s d)] .
+ var Ss : Steps .
+
+ sort State .
+ op _&_ : Pool Steps -> State [format (d d n++i --)] .
+ op {_} : Pool -> State .
+ eq {Ps} = Ps & nil .
+ var S : State .
+
+ op _has_ : State Nat -> Bool .
+ eq (N Ps & Ss) has N = true .
+ eq S has N = false [otherwise] .
+
+ op _has!_ : State Nat -> Bool .
+ eq (N & Ss) has! N = true .
+ eq S has! N = false [otherwise] .
+
+ op _almost has_ : State Nat -> [Bool] .
+ ceq (M Ps & Ss) almost has N = true if sd(M,N) <= 10 .
+
+ crl M N Ps & Ss => Ps (M + N) & Ss, (M + N → (M + N)) if M >= N .
+ crl M N Ps & Ss => Ps sd(M, N) & Ss, (M - N → sd(M,N)) if M > N .
+ crl M N Ps & Ss => Ps (M * N) & Ss, (M × N → (M * N)) if M >= N .
+ crl M N Ps & Ss => Ps (M quo N) & Ss, (M ÷ N → (M quo N))
+ if M >= N /\ M rem N = 0 .
+endm
+```
diff --git a/posts/test.md b/posts/test.md
deleted file mode 100644
index 85c584d..0000000
--- a/posts/test.md
+++ /dev/null
@@ -1,16 +0,0 @@
----
-title: test
-tags: [a, b]
-date: 2021-07-25
-conlang: laantas
-...
-
-# hello
-
-im gecs
-
-:::example
-`{#kášńḿł | size=100 ; stroke=4}`
-:::
-
-$$\mathbb{wow},\, \mathcal{MATH}$$
diff --git a/style/page.css b/style/page.css
index 48ee047..8be0f5f 100644
--- a/style/page.css
+++ b/style/page.css
@@ -88,6 +88,10 @@ hr {
height: 0;
}
+pre + hr {
+ margin-top: 1.5em;
+}
+
.meta {
display: flex;
column-gap: 1em;
@@ -168,8 +172,11 @@ code {
}
pre {
- width: min-content;
- margin: auto;
+ width: min-content;
+ margin: 0.5em auto;
+ padding: 0.5em 0.8em;
+ border: 1px solid var(--root-col);
+ background: hsla(0deg, 0%, 100%, 40%);
}
@@ -378,6 +385,14 @@ blockquote {
}
+aside {
+ font-size: 90%;
+ margin: 0.5em 3em;
+ border-left: 2px solid var(--root-col);
+ padding-left: 1em;
+}
+
+
/* syntax highlighting for maude
* [todo] does this also look ok for other langs */
.maude .pp { color: hsl(343deg, 100%, 40%); font-weight: 500; }