diff --git a/builtins.go b/builtins.go index 4819592..2bf910d 100644 --- a/builtins.go +++ b/builtins.go @@ -7,6 +7,8 @@ import ( "math" "strconv" "strings" + + "github.com/larvit/fejkdata/internal/drawstate" ) // maxLen caps sample output lengths (hex, nanoid, base64, digits, upper, lower) @@ -40,19 +42,19 @@ var builtins = withTransforms(map[string]builtin{ "lower": {arity: 1, checkArgs: posIntArg, prep: chars("abcdefghijklmnopqrstuvwxyz")}, "base64": {arity: 1, checkArgs: posIntArg, prep: func(a []string) callFn { n := atoi(a[0]) - return func(s *generatorState, _ string, _ []string) string { + return func(s *drawstate.State, _ string, _ []string) string { return base64.StdEncoding.EncodeToString(randBytes(s, n)) } }}, "int": {arity: 2, checkArgs: intRangeArgs, prep: func(a []string) callFn { lo, span := atoi(a[0]), atoi(a[1])-atoi(a[0])+1 - return func(s *generatorState, _ string, _ []string) string { return strconv.Itoa(lo + s.IntN(span)) } + return func(s *drawstate.State, _ string, _ []string) string { return strconv.Itoa(lo + s.IntN(span)) } }, prints: DataTypeInteger, proveNumber: func(token string, prints DataType, a []string) proven { return printing(token, prints, bounded(float64(atoi(a[0])), float64(atoi(a[1])), true)) }}, "float": {arity: 3, checkArgs: floatArgs, prep: func(a []string) callFn { lo, hi, dp := atof(a[0]), atof(a[1]), atoi(a[2]) - return func(s *generatorState, _ string, _ []string) string { + return func(s *drawstate.State, _ string, _ []string) string { return formatFloat(lo+s.Float64()*(hi-lo), dp) } }, prints: DataTypeNumber, proveNumber: func(token string, _ DataType, a []string) proven { @@ -60,7 +62,7 @@ var builtins = withTransforms(map[string]builtin{ }}, "iban": {arity: 1, checkArgs: ibanArg, prep: func(a []string) callFn { cc := a[0] - return func(s *generatorState, _ string, _ []string) string { return iban(s, cc) } + return func(s *drawstate.State, _ string, _ []string) string { return iban(s, cc) } }}, "date": {arity: -1, checkArgs: dateArgs, prep: datePrep}, "time": {arity: -1, checkArgs: timeArg, prep: timePrep}, @@ -73,8 +75,8 @@ var builtins = withTransforms(map[string]builtin{ if len(a) == 1 { key = a[0] } - return func(s *generatorState, _ string, _ []string) string { - return strconv.FormatUint(s.next(key), 10) + return func(s *drawstate.State, _ string, _ []string) string { + return strconv.FormatUint(s.Next(key), 10) } }, prints: DataTypeInteger, proveNumber: func(token string, prints DataType, _ []string) proven { return printing(token, prints, bounded(1, math.MaxInt64, true)) @@ -86,18 +88,18 @@ var builtins = withTransforms(map[string]builtin{ // sample of n characters drawn from an alphabet. func derive(f func(emitted string) string) func([]string) callFn { return func([]string) callFn { - return func(_ *generatorState, emitted string, _ []string) string { return f(emitted) } + return func(_ *drawstate.State, emitted string, _ []string) string { return f(emitted) } } } func sample(f func(rng) string) func([]string) callFn { return func([]string) callFn { - return func(s *generatorState, _ string, _ []string) string { return f(s) } + return func(s *drawstate.State, _ string, _ []string) string { return f(s) } } } func chars(alphabet string) func([]string) callFn { return func(a []string) callFn { n := atoi(a[0]) - return func(s *generatorState, _ string, _ []string) string { return randChars(s, n, alphabet) } + return func(s *drawstate.State, _ string, _ []string) string { return randChars(s, n, alphabet) } } } diff --git a/calc.go b/calc.go index 5473ab8..5205611 100644 --- a/calc.go +++ b/calc.go @@ -7,6 +7,8 @@ import ( "strconv" "strings" "unicode" + + "github.com/larvit/fejkdata/internal/drawstate" ) // calcNode is a parsed expression node. It evaluates over the operand values expand @@ -235,7 +237,7 @@ func calcPrep(args []string) callFn { } placed := indexVars(expr, at) dp := calcDecimals(args) - return func(_ *generatorState, _ string, operands []string) string { + return func(_ *drawstate.State, _ string, operands []string) string { return formatFloat(placed.eval(operands), dp) } } diff --git a/fejkdata.go b/fejkdata.go index 7ca6300..99526bb 100644 --- a/fejkdata.go +++ b/fejkdata.go @@ -6,11 +6,12 @@ import ( "errors" "fmt" "io/fs" - "math/rand/v2" "os" "reflect" "sort" "sync" + + "github.com/larvit/fejkdata/internal/drawstate" ) // MaxRepeat caps a repeat, and caps the renders nested repeats multiply to along @@ -26,27 +27,15 @@ var ErrNoData = errors.New("no data: WithoutShippedData needs at least one WithD // It is safe for concurrent use; a seeded sequence is reproducible only when drawn // from one goroutine. type Generator struct { - // mu guards rand, records, structs and root, which gains a shipped category on the + // mu guards draws, records, structs and root, which gains a shipped category on the // first call reaching it. mu sync.Mutex - rand *generatorState + draws *drawstate.State root folder // the categories as the node a path walks from, owned here so a walk allocates none records map[node]recordShape structs map[reflect.Type]structResult } -// generatorState is one generator's draw state, kept across its renders: the seeded rng plus -// the {seq()} counters. -type generatorState struct { - *rand.Rand - counters map[string]uint64 -} - -func (s *generatorState) next(key string) uint64 { - s.counters[key]++ - return s.counters[key] -} - type config struct { seed uint64 seeded bool @@ -97,11 +86,11 @@ func New(opts ...Option) (*Generator, error) { if err != nil { return nil, fmt.Errorf("fejkdata: %w", err) } - rng, err := newRand(c.seed, c.seeded) + seed, err := c.drawSeed() if err != nil { return nil, fmt.Errorf("fejkdata: %w", err) } - return &Generator{rand: rng, root: root}, nil + return &Generator{draws: drawstate.New(seed), root: root}, nil } // load is the tree New starts from. @@ -231,18 +220,16 @@ func join(prefix, name string) string { // randomBytes seeds an unseeded generator. var randomBytes = crand.Read -func newRand(seed uint64, seeded bool) (*generatorState, error) { - var r *rand.Rand - if seeded { - r = rand.New(rand.NewPCG(seed, seed^0x9e3779b97f4a7c15)) - } else { - var b [16]byte - if _, err := randomBytes(b[:]); err != nil { - return nil, fmt.Errorf("seeding from crypto/rand: %w", err) - } - r = rand.New(rand.NewPCG(binary.LittleEndian.Uint64(b[:8]), binary.LittleEndian.Uint64(b[8:]))) +// drawSeed is the seed WithSeed gave, or one read from the system's entropy. +func (c config) drawSeed() (uint64, error) { + if c.seeded { + return c.seed, nil } - return &generatorState{Rand: r, counters: map[string]uint64{}}, nil + var b [8]byte + if _, err := randomBytes(b[:]); err != nil { + return 0, fmt.Errorf("seeding from crypto/rand: %w", err) + } + return binary.LittleEndian.Uint64(b[:]), nil } func internalError(format string, a ...any) string { diff --git a/format.go b/format.go index 7f6914f..9fd33d2 100644 --- a/format.go +++ b/format.go @@ -3,6 +3,8 @@ package fejkdata import ( "fmt" "strings" + + "github.com/larvit/fejkdata/internal/drawstate" ) // scanUnit is one unit of a scanned format string: a literal rune or the body of a @@ -119,7 +121,7 @@ func parseFormat(format string) ([]formatToken, error) { } // builtin is a format-string function invoked as {name(args)}. It receives the -// generatorState, the output emitted so far in the current expansion (for derivations +// draw state, the output emitted so far in the current expansion (for derivations // such as a checksum over preceding digits), and the values of the operands it named // (calc and the transforms name them). All must stay pure over (rng, emitted, args) so // seeded output is reproducible; seq advances per-generator counter state, which is @@ -461,7 +463,7 @@ func checkSegments(a arm) error { // callFn is a builtin prepared for one call site: its args already parsed. It reads the // output emitted so far in the current expansion (a derivation's payload) and the // values of the operands it named, which expand read for it. -type callFn func(s *generatorState, emitted string, operands []string) string +type callFn func(s *drawstate.State, emitted string, operands []string) string // op is one compiled unit of a format string: a literal run, a name read, // or a builtin already prepared with its args. diff --git a/inline.go b/inline.go index 4392e2a..42dd50f 100644 --- a/inline.go +++ b/inline.go @@ -20,7 +20,7 @@ type Template struct { func (t *Template) Fake() string { t.g.mu.Lock() defer t.g.mu.Unlock() - return renderOnce(t.g.rand, t.n) + return renderOnce(t.g.draws, t.n) } // NewTemplate compiles an inline template — a format string or a JSON value — and diff --git a/internal/drawstate/drawstate.go b/internal/drawstate/drawstate.go new file mode 100644 index 0000000..74cc539 --- /dev/null +++ b/internal/drawstate/drawstate.go @@ -0,0 +1,22 @@ +// Package drawstate holds what a generator draws through: the seeded randomness and the +// {seq()} counters. +package drawstate + +import "math/rand/v2" + +// State is one generator's draw state, kept across its renders. +type State struct { + *rand.Rand + counters map[string]uint64 +} + +// New builds the state a seed reproduces. +func New(seed uint64) *State { + return &State{Rand: rand.New(rand.NewPCG(seed, seed^0x9e3779b97f4a7c15)), counters: map[string]uint64{}} +} + +// Next advances the counter key names, the one {seq()} or {seq(key)} reads, and returns it. +func (s *State) Next(key string) uint64 { + s.counters[key]++ + return s.counters[key] +} diff --git a/layout.go b/layout.go index cb1a61c..511c551 100644 --- a/layout.go +++ b/layout.go @@ -4,6 +4,8 @@ import ( "fmt" "strings" "time" + + "github.com/larvit/fejkdata/internal/drawstate" ) const dayLayout = "2006-01-02" @@ -135,13 +137,13 @@ func datePrep(a []string) callFn { panic(internalError("builtin arg %q reached prep unvalidated: %v", a[1], err)) } layout, start, span := layoutOf(a[2]), from.Unix(), int(to.Unix()-from.Unix())+86400 - return func(s *generatorState, _ string, _ []string) string { + return func(s *drawstate.State, _ string, _ []string) string { return time.Unix(start+int64(s.IntN(span)), 0).UTC().Format(layout) } } func timePrep(a []string) callFn { layout := layoutOf(a[0]) - return func(s *generatorState, _ string, _ []string) string { + return func(s *drawstate.State, _ string, _ []string) string { return time.Unix(int64(s.IntN(86400)), 0).UTC().Format(layout) } } diff --git a/path.go b/path.go index 700875b..ff16816 100644 --- a/path.go +++ b/path.go @@ -3,6 +3,8 @@ package fejkdata import ( "fmt" "strings" + + "github.com/larvit/fejkdata/internal/drawstate" ) // splitPath splits a dotted path into segments, a selector — [key or name] after a @@ -430,7 +432,7 @@ func probePath(n node, tail []string, steps []pathStep) ([]pathStep, error) { // For a read through a name, memo keeps the variant drawn at each of levels, so paths sharing a // prefix share it. It returns the leaf and the pins its row is in: a step down after a step up draws // afresh, into pins of its own, which memo shares with every path stepping down there. -func drawSteps(s *generatorState, n node, steps []pathStep, pins *pinSet, memo *drawMemo, levels []string) (node, *pinSet) { +func drawSteps(s *drawstate.State, n node, steps []pathStep, pins *pinSet, memo *drawMemo, levels []string) (node, *pinSet) { var climbed *table for _, st := range steps { if c, ok := n.(*choice); ok { @@ -458,7 +460,7 @@ func drawSteps(s *generatorState, n node, steps []pathStep, pins *pinSet, memo * return n, pins } -func (t *table) drawStep(s *generatorState, st pathStep, pins *pinSet) node { +func (t *table) drawStep(s *drawstate.State, st pathStep, pins *pinSet) node { switch st.kind { case stepSelect: if err := pins.pinRow(t, st.row); err != nil { @@ -480,7 +482,7 @@ func (t *table) drawStep(s *generatorState, st pathStep, pins *pinSet) node { panic(internalError("drawStep has no case for step kind %d", st.kind)) } -func drawVariant(s *generatorState, c *choice, memo *drawMemo, levels []string, at int) node { +func drawVariant(s *drawstate.State, c *choice, memo *drawMemo, levels []string, at int) node { if memo == nil { return resolveChoice(s, c) } diff --git a/pick.go b/pick.go index e844400..7f2fa14 100644 --- a/pick.go +++ b/pick.go @@ -1,5 +1,7 @@ package fejkdata +import "github.com/larvit/fejkdata/internal/drawstate" + // namedPick is one draw of a name: the variant drawn at each level a read of it addresses, the // value each read there produced, keyed by the path from the name, and the table rows they // pinned. @@ -135,7 +137,7 @@ func (sc renderScope) frameOf(scope *nameScope) *pickFrame { return nil } -func readName(s *generatorState, sc renderScope, a arm) readValue { +func readName(s *drawstate.State, sc renderScope, a arm) readValue { f := sc.frameOf(a.named.scope) if f == nil { panic(internalError("name %q is read where no frame of its scope renders", a.named.name)) @@ -155,7 +157,7 @@ func readName(s *generatorState, sc renderScope, a arm) readValue { } // drawRowOf draws the row a render of t reads: inside the pick's rows where t renders as part of one. -func (sc renderScope) drawRowOf(s *generatorState, t *table) int { +func (sc renderScope) drawRowOf(s *drawstate.State, t *table) int { if sc.pick != nil { return t.drawIn(s, &sc.pick.pins) } @@ -170,7 +172,7 @@ func (sc renderScope) keeps(a arm) bool { // readUnder reads a of t, which renders as part of the pick sc.pick at sc.pickKey, where a read of // the name addresses it: once per pick, by its path from the name. -func readUnder(s *generatorState, t *template, sc renderScope, a arm) readValue { +func readUnder(s *drawstate.State, t *template, sc renderScope, a arm) readValue { p, key := sc.pick, underKey(sc.pickKey, a.path) if r, done := p.memo.value[key]; done { return r @@ -194,7 +196,7 @@ func underKey(prefix, path string) string { // draw draws the path key names under p, its variant at key kept too, returning the leaf and the // pins its row is in. -func (p *namedPick) draw(s *generatorState, head node, steps []pathStep, levels []string, key string) (node, *pinSet) { +func (p *namedPick) draw(s *drawstate.State, head node, steps []pathStep, levels []string, key string) (node, *pinSet) { leaf, pins := drawSteps(s, head, steps, &p.pins, &p.memo, levels) if c, isChoice := leaf.(*choice); isChoice { leaf = p.memo.variantOf(s, c, key) @@ -203,7 +205,7 @@ func (p *namedPick) draw(s *generatorState, head node, steps []pathStep, levels } // renderAt renders leaf, drawn at key into pins, as part of p, and keeps what it rendered. -func (p *namedPick) renderAt(s *generatorState, leaf node, pins *pinSet, key string, sc renderScope) readValue { +func (p *namedPick) renderAt(s *drawstate.State, leaf node, pins *pinSet, key string, sc renderScope) readValue { sc = sc.at(leaf, pins) sc.pick, sc.pickKey = p, key r := renderLeaf(s, leaf, sc) @@ -215,7 +217,7 @@ func (p *namedPick) renderAt(s *generatorState, leaf node, pins *pinSet, key str } // variantOf is the variant of c drawn at level, drawn now where none was. -func (m *drawMemo) variantOf(s *generatorState, c *choice, level string) node { +func (m *drawMemo) variantOf(s *drawstate.State, c *choice, level string) node { n, drew := m.variant[level] if !drew { n = resolveChoice(s, c) diff --git a/record.go b/record.go index 41ad086..fe99890 100644 --- a/record.go +++ b/record.go @@ -7,6 +7,8 @@ import ( "strings" "unicode" "unicode/utf8" + + "github.com/larvit/fejkdata/internal/drawstate" ) // Column is one rendered column of a record. Value is the rendered text, which a @@ -133,7 +135,7 @@ func (f *Generator) FakeRecord(path string) (*Record, error) { var frames frameStack sc := renderScope{frames: &frames} if t, isTable := n.(*table); isTable { - if n, sc.row, err = tableRecord(f.rand, t, tail); err != nil { + if n, sc.row, err = tableRecord(f.draws, t, tail); err != nil { return nil, fmt.Errorf("fejkdata: %s: %w", path, err) } } else if len(tail) > 0 { @@ -147,12 +149,12 @@ func (f *Generator) FakeRecord(path string) (*Record, error) { if shape.err != nil { return nil, fmt.Errorf("fejkdata: %s %w", path, shape.err) } - return renderRecord(f.rand, shape.template, shape.columns, sc), nil + return renderRecord(f.draws, shape.template, shape.columns, sc), nil } // tableRecord walks a path's tail from a table to the table whose row is the record, and that // row, selected or drawn. -func tableRecord(s *generatorState, t *table, tail []string) (node, renderedRow, error) { +func tableRecord(s *drawstate.State, t *table, tail []string) (node, renderedRow, error) { n, pins, err := descend(s, t, tail) if err != nil { return nil, renderedRow{}, err @@ -204,7 +206,7 @@ func (t *RecordTemplate) Fake() *Record { t.g.mu.Lock() defer t.g.mu.Unlock() var frames frameStack - return renderRecord(t.g.rand, t.template, t.columns, renderScope{frames: &frames}) + return renderRecord(t.g.draws, t.template, t.columns, renderScope{frames: &frames}) } // NewRecordTemplate compiles an inline record — a JSON object with a format and @@ -260,7 +262,7 @@ func recordOf(n node) (*template, []Column, error) { // renderRecord draws each column once, in the name order recordOf fixed, as one render, so the // columns read one pick of each name; a table's columns read sc's row. -func renderRecord(s *generatorState, t *template, columns []Column, sc renderScope) *Record { +func renderRecord(s *drawstate.State, t *template, columns []Column, sc renderScope) *Record { if mark := sc.renderFrame(t); mark >= 0 { defer sc.frames.pop(mark) } diff --git a/render.go b/render.go index 607e94a..3cdf593 100644 --- a/render.go +++ b/render.go @@ -3,10 +3,12 @@ package fejkdata import ( "fmt" "strings" + + "github.com/larvit/fejkdata/internal/drawstate" ) // rng is the randomness a builtin sample draws from; *rand.Rand satisfies it. The -// render path takes the concrete *generatorState instead, which keeps the draws of the walk +// render path takes the concrete *drawstate.State instead, which keeps the draws of the walk // drawing through it off the heap. type rng interface { IntN(n int) int @@ -25,7 +27,7 @@ func (f *Generator) Fake(path string) (string, error) { return "", fmt.Errorf("fejkdata: %w", err) } f.loadShippedAt(segments) - n, pins, err := descend(f.rand, &f.root, segments) + n, pins, err := descend(f.draws, &f.root, segments) if err != nil { return "", fmt.Errorf("fejkdata: %s: %w", path, err) } @@ -34,11 +36,11 @@ func (f *Generator) Fake(path string) (string, error) { } var frames frameStack sc, _ := renderScope{frames: &frames}.at(n, &pins).enter(n, nil) - return render(f.rand, n, sc), nil + return render(f.draws, n, sc), nil } // descend walks a caller's path to the node it names, returning the rows its leaf renders in. -func descend(s *generatorState, root node, segments []string) (node, pinSet, error) { +func descend(s *drawstate.State, root node, segments []string) (node, pinSet, error) { // docs/decisions.md#a-path-is-walked-once-without-drawing-before-it-is-walked-for-real var buf [16]pathStep steps, err := probePath(root, segments, buf[:0]) @@ -51,7 +53,7 @@ func descend(s *generatorState, root node, segments []string) (node, pinSet, err } // renderOnce renders n as one render, over frames of its own. -func renderOnce(s *generatorState, n node) string { +func renderOnce(s *drawstate.State, n node) string { var frames frameStack return render(s, n, renderScope{frames: &frames}) } @@ -59,7 +61,7 @@ func renderOnce(s *generatorState, n node) string { // render evaluates a compiled node to a string. compile validates every node up // front, so rendering a compiled tree cannot fail. // A child this switch renders is one renderEdges must list too, or the fences miss it. -func render(s *generatorState, n node, sc renderScope) string { +func render(s *drawstate.State, n node, sc renderScope) string { switch n := n.(type) { case *choice: return render(s, pick(s, n), sc) @@ -94,7 +96,7 @@ func render(s *generatorState, n node, sc renderScope) string { // renderRepeat renders each iteration of t inside the name scopes rendering t, where a name bound // outside t keeps its pick, and a name t binds picks again. -func renderRepeat(s *generatorState, t *template, sc renderScope) string { +func renderRepeat(s *drawstate.State, t *template, sc renderScope) string { var b strings.Builder b.Grow(t.repeat * (t.compiled.grow + len(t.separator))) for i := 0; i < t.repeat; i++ { @@ -114,14 +116,14 @@ func renderRepeat(s *generatorState, t *template, sc renderScope) string { // pick selects one item. Uniform choices are O(1); weighted choices are an // O(log n) search over precomputed cumulative weights. -func pick(s *generatorState, c *choice) node { +func pick(s *drawstate.State, c *choice) node { if c.cum == nil { return c.items[s.IntN(len(c.items))] } return c.items[pickCum(s, c.cum)] } -func expand(s *generatorState, t *template, sc renderScope) string { +func expand(s *drawstate.State, t *template, sc renderScope) string { var b strings.Builder b.Grow(t.compiled.grow) for i := range t.compiled.ops { @@ -148,7 +150,7 @@ func expand(s *generatorState, t *template, sc renderScope) string { // readField renders one arm of a token. A read of a name, or of a level a read of the name // rendering addresses, is kept in that name's pick; every other read draws afresh, so {word} // {word} draws twice and {p.a} {p.b} reads two draws of p. -func readField(s *generatorState, t *template, sc renderScope, a arm) readValue { +func readField(s *drawstate.State, t *template, sc renderScope, a arm) readValue { switch { case a.kind == namedRead: return readName(s, sc, a) @@ -170,7 +172,7 @@ func readField(s *generatorState, t *template, sc renderScope, a arm) readValue // renderLeaf draws and renders what a read lands on: null on a null item, or on a column that only // reads one reference or name whose read drew null. -func renderLeaf(s *generatorState, n node, sc renderScope) readValue { +func renderLeaf(s *drawstate.State, n node, sc renderScope) readValue { n = resolveChoice(s, n) switch leaf := n.(type) { case *nullItem: @@ -185,7 +187,7 @@ func renderLeaf(s *generatorState, n node, sc renderScope) readValue { // resolveChoice resolves a choice to one variant. Nested choices unwrap too: a draw is one value, // not another set to pick from. -func resolveChoice(s *generatorState, n node) node { +func resolveChoice(s *drawstate.State, n node) node { for c, ok := n.(*choice); ok; c, ok = n.(*choice) { n = pick(s, c) } diff --git a/struct.go b/struct.go index 5af4d22..7023e15 100644 --- a/struct.go +++ b/struct.go @@ -8,6 +8,8 @@ import ( "slices" "strconv" "strings" + + "github.com/larvit/fejkdata/internal/drawstate" ) // FakeStruct fills the struct v points to. Each exported field tagged `fake:"…"` is a @@ -27,7 +29,7 @@ func (f *Generator) FakeStruct(v any) error { if err != nil { return fmt.Errorf("fejkdata: %w", err) } - shape.fill(f.rand, p.Elem()) + shape.fill(f.draws, p.Elem()) return nil } @@ -316,7 +318,7 @@ func (k columnKind) holds(v proven) bool { } // fill draws the record into v's tagged fields, then each nested struct as a record of its own. -func (s *structShape) fill(state *generatorState, v reflect.Value) { +func (s *structShape) fill(state *drawstate.State, v reflect.Value) { if s.record != nil { var frames frameStack for i, c := range renderRecord(state, s.record, s.columns, renderScope{frames: &frames}).columns { diff --git a/table.go b/table.go index 5e27ae3..553a580 100644 --- a/table.go +++ b/table.go @@ -7,6 +7,8 @@ import ( "strconv" "strings" "sync" + + "github.com/larvit/fejkdata/internal/drawstate" ) // table is a category whose rows come from a TSV beside it: the header names the @@ -470,20 +472,20 @@ func (t *table) builtLookup() *rowLookup { return &t.lookup } -func (t *table) drawRow(s *generatorState) int { +func (t *table) drawRow(s *drawstate.State) int { if t.cum == nil { return s.IntN(t.rowCount()) } return pickCum(s, t.cum) } -func pickCum(s *generatorState, cum []float64) int { +func pickCum(s *drawstate.State, cum []float64) int { x := s.Float64() * cum[len(cum)-1] return min(sort.Search(len(cum), func(i int) bool { return cum[i] > x }), len(cum)-1) // x can round up to the total } // drawUnder picks a row among those linked to parent row pr. -func (t *table) drawUnder(s *generatorState, pr int) int { +func (t *table) drawUnder(s *drawstate.State, pr int) int { lookup := t.builtLookup() k := t.parentT.cell(pr, t.parentT.keyIndex) rows := lookup.rowsByParent[k] @@ -496,7 +498,7 @@ func (t *table) drawUnder(s *generatorState, pr int) int { // drawIn is the row of t a path or a named pick reads: the one pinned in p, else one drawn inside the nearest // pinned ancestor — its parent drawn inside that first where the ancestor is further up — or // over the whole table, and pinned with its ancestors. -func (t *table) drawIn(s *generatorState, p *pinSet) int { +func (t *table) drawIn(s *drawstate.State, p *pinSet) int { if r, ok := p.pinned(t); ok { return r } diff --git a/transform.go b/transform.go index 17d7f4e..005323e 100644 --- a/transform.go +++ b/transform.go @@ -4,6 +4,8 @@ import ( "fmt" "strings" "unicode" + + "github.com/larvit/fejkdata/internal/drawstate" ) // transforms are the builtins that rewrite one operand's value; they nest, so @@ -65,7 +67,7 @@ func transformPrep(outer func(string) string) func([]string) callFn { if err != nil { panic(internalError("transform arg %q reached prep unvalidated: %v", a[0], err)) } - return func(_ *generatorState, _ string, operands []string) string { + return func(_ *drawstate.State, _ string, operands []string) string { v := operands[0] for i := len(chain) - 1; i >= 0; i-- { v = chain[i](v)