Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

markovdemo.gno

4.55 Kb · 140 lines
  1// Package markovdemo is a small on-chain demo of the Markov-chain text
  2// generator provided by the [p/moul/x/daily/markov](/p/moul/x/daily/markov/v0)
  3// library.
  4//
  5// It holds a single package-level [markov.Chain] as the corpus and wires it to
  6// the chain: Feed appends words to that persistent chain (a crossing function,
  7// since it mutates on-chain state), while Generate and Render walk it with a
  8// seed derived from runtime.ChainHeight() — so babbling is a pure, reproducible
  9// query that still varies block to block. It contains no Markov logic of its
 10// own — build/walk math all lives in the library.
 11package markovdemo
 12
 13import (
 14	"chain"
 15	"chain/runtime"
 16	"strconv"
 17	"strings"
 18
 19	"gno.land/p/moul/x/daily/markov/v0"
 20)
 21
 22// maxGen clamps Generate / Render so output stays bounded.
 23const maxGen = 200
 24
 25// mc is the single on-chain corpus, seeded at deploy time.
 26var mc = markov.New()
 27
 28func init() {
 29	// Two seed sentences with heavy two-word-prefix overlap, so a fresh deploy
 30	// already babbles something recognizably chain-like.
 31	mc.Build("it was the best of times it was the worst of times " +
 32		"it was the age of wisdom it was the age of foolishness")
 33	mc.Build("i am a free man i am not a number " +
 34		"i am the master of my fate i am the captain of my soul")
 35}
 36
 37// seedFor derives a Generate seed from the current chain height so the babble
 38// changes block to block yet stays reproducible for anyone querying the same
 39// height.
 40func seedFor() uint64 {
 41	return uint64(runtime.ChainHeight())*2654435761 + 0x9e3779b97f4a7c15
 42}
 43
 44// Feed appends text to the on-chain corpus, extending the Markov chain. Any
 45// caller may enrich the corpus — this is the open-membership demo model.
 46func Feed(cur realm, text string) string {
 47	if strings.TrimSpace(text) == "" {
 48		panic("markov: empty text")
 49	}
 50	added := mc.Build(text)
 51	words, prefixes := mc.Stats()
 52	chain.Emit("Feed", "words", strconv.Itoa(added), "total", strconv.Itoa(words))
 53	return "fed " + strconv.Itoa(added) + " words; corpus now " +
 54		strconv.Itoa(words) + " words across " +
 55		strconv.Itoa(prefixes) + " prefixes"
 56}
 57
 58// Generate babbles up to nWords of Markov text from the current corpus, seeded
 59// deterministically by the chain height. Read-only.
 60func Generate(nWords int) string {
 61	if nWords <= 0 {
 62		nWords = 20
 63	}
 64	if nWords > maxGen {
 65		nWords = maxGen
 66	}
 67	return strings.Join(mc.Generate(nWords, seedFor()), " ")
 68}
 69
 70// Stats returns (totalWords, prefixCount) for the current corpus.
 71func Stats() (int, int) {
 72	return mc.Stats()
 73}
 74
 75// parseN pulls a trailing positive integer out of a Render path such as
 76// "/40" or "gen/40"; it returns 0 when there is none.
 77func parseN(path string) int {
 78	p := strings.Trim(path, "/")
 79	if i := strings.LastIndex(p, "/"); i >= 0 {
 80		p = p[i+1:]
 81	}
 82	n, err := strconv.Atoi(p)
 83	if err != nil || n < 0 {
 84		return 0
 85	}
 86	return n
 87}
 88
 89// Render shows a freshly generated sample plus corpus stats. The path may
 90// carry a word count, e.g. Render("/60").
 91func Render(path string) string {
 92	n := parseN(path)
 93	if n == 0 {
 94		n = 40
 95	}
 96	if n > maxGen {
 97		n = maxGen
 98	}
 99
100	words, prefixes := mc.Stats()
101
102	var b strings.Builder
103	b.WriteString("# Markov Babbler\n\n")
104	b.WriteString("A demo of the [`p/moul/x/daily/markov`](/p/moul/x/daily/markov/v0) " +
105		"library — an on-chain port of Go's classic *\"Generating arbitrary text: " +
106		"a Markov chain algorithm\"* codewalk. Every two-word **prefix** maps to " +
107		"the words that followed it; `Generate` walks that chain, picking suffixes " +
108		"with a PRNG seeded by the block height — deterministic, yet different each " +
109		"block.\n\n")
110
111	b.WriteString("## Fresh sample (" + strconv.Itoa(n) + " words, height " +
112		strconv.FormatInt(runtime.ChainHeight(), 10) + ")\n\n")
113	sample := strings.Join(mc.Generate(n, seedFor()), " ")
114	if sample == "" {
115		sample = "_(empty corpus)_"
116	}
117	b.WriteString("> " + sample + "\n\n")
118
119	b.WriteString("## Corpus\n\n")
120	b.WriteString("- **Words:** " + strconv.Itoa(words) + "\n")
121	b.WriteString("- **Distinct prefixes:** " + strconv.Itoa(prefixes) + "\n")
122	b.WriteString("- **Prefix length:** " + strconv.Itoa(markov.PrefixLen) + " words\n\n")
123
124	b.WriteString("## Some prefixes → suffixes\n\n")
125	b.WriteString("| prefix | can be followed by |\n|---|---|\n")
126	shown := 0
127	mc.Iterate(func(k string, suffixes []string) bool {
128		disp := k
129		if disp == " " || disp == "" {
130			disp = "_(start)_"
131		}
132		b.WriteString("| `" + disp + "` | " + strings.Join(suffixes, ", ") + " |\n")
133		shown++
134		return shown >= 12 // cap the table
135	})
136	b.WriteString("\n_Call `Feed(\"your text here\")` to teach it new words, then " +
137		"reload for a new sample._\n")
138
139	return b.String()
140}