1// Package markovdemo is a small on-chain demo of the Markov-chain text2// 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 to6// 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 a8// seed derived from runtime.ChainHeight() — so babbling is a pure, reproducible9// query that still varies block to block. It contains no Markov logic of its10// own — build/walk math all lives in the library.11package markovdemo1213import (14 "chain"15 "chain/runtime"16 "strconv"17 "strings"1819 "gno.land/p/moul/x/daily/markov/v0"20)2122// maxGen clamps Generate / Render so output stays bounded.23const maxGen = 2002425// mc is the single on-chain corpus, seeded at deploy time.26var mc = markov.New()2728func init() {29 // Two seed sentences with heavy two-word-prefix overlap, so a fresh deploy30 // 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}3637// seedFor derives a Generate seed from the current chain height so the babble38// changes block to block yet stays reproducible for anyone querying the same39// height.40func seedFor() uint64 {41 return uint64(runtime.ChainHeight())*2654435761 + 0x9e3779b97f4a7c1542}4344// Feed appends text to the on-chain corpus, extending the Markov chain. Any45// 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}5758// Generate babbles up to nWords of Markov text from the current corpus, seeded59// deterministically by the chain height. Read-only.60func Generate(nWords int) string {61 if nWords <= 0 {62 nWords = 2063 }64 if nWords > maxGen {65 nWords = maxGen66 }67 return strings.Join(mc.Generate(nWords, seedFor()), " ")68}6970// Stats returns (totalWords, prefixCount) for the current corpus.71func Stats() (int, int) {72 return mc.Stats()73}7475// parseN pulls a trailing positive integer out of a Render path such as76// "/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 085 }86 return n87}8889// Render shows a freshly generated sample plus corpus stats. The path may90// carry a word count, e.g. Render("/60").91func Render(path string) string {92 n := parseN(path)93 if n == 0 {94 n = 4095 }96 if n > maxGen {97 n = maxGen98 }99100 words, prefixes := mc.Stats()101102 var b strings.Builder103 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")110111 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")118119 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")123124 b.WriteString("## Some prefixes → suffixes\n\n")125 b.WriteString("| prefix | can be followed by |\n|---|---|\n")126 shown := 0127 mc.Iterate(func(k string, suffixes []string) bool {128 disp := k129 if disp == " " || disp == "" {130 disp = "_(start)_"131 }132 b.WriteString("| `" + disp + "` | " + strings.Join(suffixes, ", ") + " |\n")133 shown++134 return shown >= 12 // cap the table135 })136 b.WriteString("\n_Call `Feed(\"your text here\")` to teach it new words, then " +137 "reload for a new sample._\n")138139 return b.String()140}141Feed(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, text string) string
Generate(nWords int) string
Render(path string) string
Stats() (int, int)
Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.
vm/qrender output, sanitized (docs/render-security.md) and displayed in an empty-sandbox iframe — scripts, forms and popups cannot run. Links stay inert in-preview; right-click to open.