maude: Phase 6 strategy language (19 tests, 178 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 57s

lib/maude/strategy.sx — first-class set-valued strategies: idle/fail/all/
rule/seq/alt/star/plus/bang/name combinators, named-strategy env. Same
rule set computes different things under different strategies; verified
with single-rule vs all vs seq-order vs alt vs star vs bang.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-07 15:26:52 +00:00
parent 858d35a68c
commit e2aca38a84
6 changed files with 395 additions and 8 deletions

View File

@@ -13,6 +13,7 @@ PRELOADS=(
lib/maude/conditional.sx
lib/maude/fire.sx
lib/maude/rewrite.sx
lib/maude/strategy.sx
)
SUITES=(
@@ -21,4 +22,5 @@ SUITES=(
"matching:lib/maude/tests/matching.sx:(mau-matching-tests-run!)"
"conditional:lib/maude/tests/conditional.sx:(mau-conditional-tests-run!)"
"rewrite:lib/maude/tests/rewrite.sx:(mau-rewrite-tests-run!)"
"strategy:lib/maude/tests/strategy.sx:(mau-strategy-tests-run!)"
)

View File

@@ -1,14 +1,15 @@
{
"lang": "maude",
"total_passed": 159,
"total_passed": 178,
"total_failed": 0,
"total": 159,
"total": 178,
"suites": [
{"name":"parse","passed":65,"failed":0,"total":65},
{"name":"reduce","passed":26,"failed":0,"total":26},
{"name":"matching","passed":28,"failed":0,"total":28},
{"name":"conditional","passed":19,"failed":0,"total":19},
{"name":"rewrite","passed":21,"failed":0,"total":21}
{"name":"rewrite","passed":21,"failed":0,"total":21},
{"name":"strategy","passed":19,"failed":0,"total":19}
],
"generated": "2026-06-07T15:22:28+00:00"
"generated": "2026-06-07T15:26:26+00:00"
}

View File

@@ -1,6 +1,6 @@
# maude scoreboard
**159 / 159 passing** (0 failure(s)).
**178 / 178 passing** (0 failure(s)).
| Suite | Passed | Total | Status |
|-------|--------|-------|--------|
@@ -9,3 +9,4 @@
| matching | 28 | 28 | ok |
| conditional | 19 | 19 | ok |
| rewrite | 21 | 21 | ok |
| strategy | 19 | 19 | ok |

217
lib/maude/strategy.sx Normal file
View File

@@ -0,0 +1,217 @@
;; lib/maude/strategy.sx — strategy language (Phase 6).
;;
;; A strategy controls HOW rules are applied. Strategies are first-class values
;; (tagged dicts) and SET-VALUED: applying a strategy to a term yields the set
;; (deduped by canonical form) of result terms. The same rule set under
;; different strategies computes different things — `;` sequences, `|` unions,
;; `*`/`+` iterate, `!` normalises.
;;
;; Constructors:
;; (mau/s-idle) identity (the term itself)
;; (mau/s-fail) empty set
;; (mau/s-all) apply any rule once, anywhere
;; (mau/s-rule LABEL) apply a named rule once, anywhere
;; (mau/s-seq A B) A ; B (apply B to every result of A)
;; (mau/s-alt A B) A | B (union of results)
;; (mau/s-star A) A * (reflexive-transitive closure)
;; (mau/s-plus A) A + (one or more)
;; (mau/s-bang A) A ! (normal forms: results where A can't apply)
;; (mau/s-name N) look up named strategy N in the env
;;
;; Run with (mau/srun M STRATS STRAT SRC): STRATS is a dict NAME -> strategy.
(define mau/s-idle (fn () {:s :idle}))
(define mau/s-fail (fn () {:s :fail}))
(define mau/s-all (fn () {:s :all}))
(define mau/s-rule (fn (label) {:label label :s :rule}))
(define mau/s-seq (fn (a b) {:a a :b b :s :seq}))
(define mau/s-alt (fn (a b) {:a a :b b :s :alt}))
(define mau/s-star (fn (a) {:a a :s :star}))
(define mau/s-plus (fn (a) {:a a :s :plus}))
(define mau/s-bang (fn (a) {:a a :s :bang}))
(define mau/s-name (fn (n) {:n n :s :name}))
(define
mau/rules-with-label
(fn (rules label) (filter (fn (r) (= (get r :label) label)) rules)))
(define
mau/dedup-loop
(fn
(theory ts seen acc)
(if
(empty? ts)
acc
(let
((c (mau/canon theory (first ts))))
(if
(mau/member? c seen)
(mau/dedup-loop theory (rest ts) seen acc)
(mau/dedup-loop
theory
(rest ts)
(cons c seen)
(mau/append2 acc (list (first ts)))))))))
(define
mau/dedup-canon
(fn (theory ts) (mau/dedup-loop theory ts (list) (list))))
;; ---- strategy interpreter ----
(define
mau/sapply
(fn
(ctx strat term)
(let
((k (get strat :s)) (theory (get ctx :theory)))
(cond
((= k "idle") (list term))
((= k "fail") (list))
((= k "all")
(mau/dedup-canon
theory
(mau/all-successors theory (get ctx :eqs) (get ctx :rules) term)))
((= k "rule")
(mau/dedup-canon
theory
(mau/all-successors
theory
(get ctx :eqs)
(mau/rules-with-label (get ctx :rules) (get strat :label))
term)))
((= k "seq")
(mau/dedup-canon
theory
(mau/concat-map
(fn (t) (mau/sapply ctx (get strat :b) t))
(mau/sapply ctx (get strat :a) term))))
((= k "alt")
(mau/dedup-canon
theory
(mau/append2
(mau/sapply ctx (get strat :a) term)
(mau/sapply ctx (get strat :b) term))))
((= k "star") (mau/sstar ctx (get strat :a) term))
((= k "plus")
(mau/dedup-canon
theory
(mau/concat-map
(fn (t) (mau/sstar ctx (get strat :a) t))
(mau/sapply ctx (get strat :a) term))))
((= k "bang")
(mau/dedup-canon theory (mau/sbang ctx (get strat :a) term)))
((= k "name")
(mau/sapply ctx (get (get ctx :strats) (get strat :n)) term))
(else (list))))))
;; reflexive-transitive closure: term plus everything reachable via A
(define
mau/sstar
(fn
(ctx a term)
(mau/sstar-loop
ctx
a
(list term)
(list (mau/canon (get ctx :theory) term))
(list term))))
(define
mau/sstar-loop
(fn
(ctx a frontier seen acc)
(if
(empty? frontier)
acc
(let
((newf (list))
(newseen seen)
(newacc acc)
(theory (get ctx :theory)))
(for-each
(fn
(t)
(for-each
(fn
(succ)
(let
((c (mau/canon theory succ)))
(when
(not (mau/member? c newseen))
(do
(set! newseen (cons c newseen))
(append! newf succ)
(append! newacc succ)))))
(mau/sapply ctx a t)))
frontier)
(mau/sstar-loop ctx a newf newseen newacc)))))
;; normal forms: terms reachable via A where A yields nothing more
(define
mau/sbang
(fn
(ctx a term)
(mau/sbang-loop
ctx
a
(list term)
(list (mau/canon (get ctx :theory) term))
(list))))
(define
mau/sbang-loop
(fn
(ctx a frontier seen acc)
(if
(empty? frontier)
acc
(let
((newf (list))
(newseen seen)
(newacc acc)
(theory (get ctx :theory)))
(for-each
(fn
(t)
(let
((succs (mau/sapply ctx a t)))
(if
(empty? succs)
(append! newacc t)
(for-each
(fn
(succ)
(let
((c (mau/canon theory succ)))
(when
(not (mau/member? c newseen))
(do
(set! newseen (cons c newseen))
(append! newf succ)))))
succs))))
frontier)
(mau/sbang-loop ctx a newf newseen newacc)))))
;; ---- public API ----
(define mau/make-sctx (fn (m strats) {:eqs (mau/module-eqs m) :theory (mau/build-theory m) :strats strats :rules (mau/module-rules m)}))
(define
mau/srun
(fn
(m strats strat src)
(let
((ctx (mau/make-sctx m strats)))
(let
((t0 (mau/cnormalize (get ctx :theory) (get ctx :eqs) (mau/parse-term-in m src) mau/reduce-fuel)))
(mau/dedup-canon (get ctx :theory) (mau/sapply ctx strat t0))))))
(define
mau/srun-canon
(fn
(m strats strat src)
(let
((theory (mau/build-theory m)))
(mau/sort-strings
(map (fn (t) (mau/canon theory t)) (mau/srun m strats strat src))))))

151
lib/maude/tests/strategy.sx Normal file
View File

@@ -0,0 +1,151 @@
;; lib/maude/tests/strategy.sx — Phase 6: strategy language.
(define mst-pass 0)
(define mst-fail 0)
(define mst-failures (list))
(define
mst-check!
(fn
(name got expected)
(if
(= got expected)
(set! mst-pass (+ mst-pass 1))
(do
(set! mst-fail (+ mst-fail 1))
(append!
mst-failures
(str name " expected: " expected " got: " got))))))
;; ---- a branching system; meaning depends on the strategy ----
(define
mst-mod
(mau/parse-module
"mod CHOICE is\n sort S .\n ops a b c x y : -> S .\n rl [r1] : a => b .\n rl [r2] : b => c .\n rl [toX] : a => x .\n rl [toY] : a => y .\nendm"))
(define mst-env {})
(dict-set! mst-env "twice" (mau/s-seq (mau/s-rule "r1") (mau/s-rule "r2")))
(dict-set! mst-env "anyplus" (mau/s-plus (mau/s-all)))
(dict-set! mst-env "norm" (mau/s-bang (mau/s-all)))
;; basic combinators
(mst-check!
"idle"
(mau/srun-canon mst-mod mst-env (mau/s-idle) "a")
(list "a"))
(mst-check! "fail" (mau/srun-canon mst-mod mst-env (mau/s-fail) "a") (list))
(mst-check!
"single-rule"
(mau/srun-canon mst-mod mst-env (mau/s-rule "r1") "a")
(list "b"))
(mst-check!
"single-rule-x"
(mau/srun-canon mst-mod mst-env (mau/s-rule "toX") "a")
(list "x"))
(mst-check!
"all"
(mau/srun-canon mst-mod mst-env (mau/s-all) "a")
(list "b" "x" "y"))
;; sequencing: order matters
(mst-check!
"seq-ok"
(mau/srun-canon
mst-mod
mst-env
(mau/s-seq (mau/s-rule "r1") (mau/s-rule "r2"))
"a")
(list "c"))
(mst-check!
"seq-fail"
(mau/srun-canon
mst-mod
mst-env
(mau/s-seq (mau/s-rule "r2") (mau/s-rule "r1"))
"a")
(list))
;; alternation: union
(mst-check!
"alt"
(mau/srun-canon
mst-mod
mst-env
(mau/s-alt (mau/s-rule "toX") (mau/s-rule "toY"))
"a")
(list "x" "y"))
(mst-check!
"alt-with-fail"
(mau/srun-canon
mst-mod
mst-env
(mau/s-alt (mau/s-rule "r2") (mau/s-rule "r1"))
"a")
(list "b"))
;; iteration
(mst-check!
"star"
(mau/srun-canon mst-mod mst-env (mau/s-star (mau/s-all)) "a")
(list "a" "b" "c" "x" "y"))
(mst-check!
"plus"
(mau/srun-canon mst-mod mst-env (mau/s-plus (mau/s-all)) "a")
(list "b" "c" "x" "y"))
(mst-check!
"bang-normal-forms"
(mau/srun-canon mst-mod mst-env (mau/s-bang (mau/s-all)) "a")
(list "c" "x" "y"))
(mst-check!
"star-from-b"
(mau/srun-canon mst-mod mst-env (mau/s-star (mau/s-all)) "b")
(list "b" "c"))
;; named strategies + strategy expressions as values
(mst-check!
"named-twice"
(mau/srun-canon mst-mod mst-env (mau/s-name "twice") "a")
(list "c"))
(mst-check!
"named-anyplus"
(mau/srun-canon mst-mod mst-env (mau/s-name "anyplus") "a")
(list "b" "c" "x" "y"))
(mst-check!
"named-norm"
(mau/srun-canon mst-mod mst-env (mau/s-name "norm") "a")
(list "c" "x" "y"))
;; nested composition: (r1 ; r2) | toX
(mst-check!
"nested"
(mau/srun-canon
mst-mod
mst-env
(mau/s-alt
(mau/s-seq (mau/s-rule "r1") (mau/s-rule "r2"))
(mau/s-rule "toX"))
"a")
(list "c" "x"))
;; ---- a 1-D walk: strategy chooses how far ----
(define
mst-walk
(mau/parse-module
"mod WALK is\n sort Pos .\n op 0 : -> Pos .\n op s_ : Pos -> Pos .\n op p : Pos -> Pos .\n var X : Pos .\n rl [step] : p(X) => p(s X) .\nendm"))
(mst-check!
"walk-one"
(mau/srun-canon mst-walk {} (mau/s-rule "step") "p(0)")
(list "p(s_(0))"))
(mst-check!
"walk-twice"
(mau/srun-canon
mst-walk
{}
(mau/s-seq (mau/s-rule "step") (mau/s-rule "step"))
"p(0)")
(list "p(s_(s_(0)))"))
(define mau-strategy-tests-run! (fn () {:failures mst-failures :total (+ mst-pass mst-fail) :passed mst-pass :failed mst-fail}))

View File

@@ -92,9 +92,9 @@ The novel substrate stress: equational matching. Pattern `X + Y` against `1 + 2
- [x] Tests: state-transition systems (puzzle solvers, protocol simulators).
### Phase 6 — Strategy language
- [ ] Compose strategies: sequential `;`, alternative `|`, iteration `*`, fixed-point.
- [ ] User-named strategies; strategy expressions as values.
- [ ] Tests: programs whose meaning depends on strategy choice.
- [x] Compose strategies: sequential `;`, alternative `|`, iteration `*`, fixed-point.
- [x] User-named strategies; strategy expressions as values.
- [x] Tests: programs whose meaning depends on strategy choice.
### Phase 7 — Reflection (META-LEVEL)
- [ ] Terms-as-data: `META-LEVEL` lets you encode/decode terms as Maude terms.
@@ -220,5 +220,20 @@ The novel substrate stress: equational matching. Pattern `X + Y` against `1 + 2
`.` can't be an op token (statement terminator). `mau/search` is the prime
Phase 7 reflection / Phase 8 extraction target alongside the matcher.
- **Phase 6 (strategy language) — DONE, 178/178 total.**
`lib/maude/strategy.sx`. Strategies are first-class tagged-dict VALUES and
set-valued: `mau/sapply ctx strat term` → deduped (by canon) list of results.
Combinators: `idle`/`fail`/`all`/`rule LABEL`/`seq`/`alt`/`star`/`plus`/`bang`
/`name`. `seq` = flatmap B over A's results; `alt` = union; `star` = reflexive-
transitive closure (BFS, canon-deduped); `plus` = A then star; `bang` =
normal forms (reachable terms where A yields nothing). Named strategies via a
NAME->strategy env dict passed to `mau/srun`/`mau/srun-canon`. Verified that
the same rule set computes different things under different strategies
(single rule vs all vs seq order vs alt vs star vs bang). Built on Phase 5
`mau/all-successors` (rule label filter = `mau/rules-with-label`).
- Note: `dict-set!` returns the value, not the dict — build a named-strategy
env by binding `(define env {})` then `(dict-set! env ...)`, pass `env`.
`srun-canon` sorts results so expected lists must be sorted.
## Blockers
_(none)_