Make great documentation goal 1.2, and plan the README's reorganisation and the nested-template gaps #33
@@ -975,7 +975,7 @@ data.go the load path: the categories internal/datafiles hands over, com
|
|||||||
cmd/fejkdata/ the fejkdata CLI
|
cmd/fejkdata/ the fejkdata CLI
|
||||||
internal/builtinfunc/ the {name()} functions: their checks and draws, checksums, date and time layouts, transforms and calc arithmetic
|
internal/builtinfunc/ the {name()} functions: their checks and draws, checksums, date and time layouts, transforms and calc arithmetic
|
||||||
internal/datafiles/ the walk of a data tree: its folders, its category files and the rows files beside them
|
internal/datafiles/ the walk of a data tree: its folders, its category files and the rows files beside them
|
||||||
internal/datatype/ what a record column holds, which DataType aliases
|
internal/datatype/ the datatype a record column holds; the root's DataType is an alias of it
|
||||||
internal/drawstate/ the seeded randomness and the {seq()} counters a generator draws through
|
internal/drawstate/ the seeded randomness and the {seq()} counters a generator draws through
|
||||||
internal/grammar/ how the template language is written: format tokens, paths and selectors, reference sigils, names, calc syntax, and whether an argument is a template or a path
|
internal/grammar/ how the template language is written: format tokens, paths and selectors, reference sigils, names, calc syntax, and whether an argument is a template or a path
|
||||||
internal/invariant/ the one phrase every package panics with when an invariant breaks
|
internal/invariant/ the one phrase every package panics with when an invariant breaks
|
||||||
|
|||||||
@@ -11,9 +11,8 @@ import (
|
|||||||
"github.com/larvit/fejkdata/internal/invariant"
|
"github.com/larvit/fejkdata/internal/invariant"
|
||||||
)
|
)
|
||||||
|
|
||||||
// checkCalcFields refuses a calc operand a field holds that is never a number, and a division
|
// checkCalcFields runs checkOperands over the calc operands fields hold. An operand no field
|
||||||
// by a constant zero. An operand no field holds is left for a name to answer, and
|
// holds is left for a name, which checkCalcNames checks once names link.
|
||||||
// checkCalcNames checks it once names link.
|
|
||||||
func checkCalcFields(args []string, fields map[string]node) error {
|
func checkCalcFields(args []string, fields map[string]node) error {
|
||||||
return checkOperands(args[0], builtinfunc.ParsedCalc(args[0]), func(name string) []node {
|
return checkOperands(args[0], builtinfunc.ParsedCalc(args[0]), func(name string) []node {
|
||||||
if n, ok := fields[name]; ok {
|
if n, ok := fields[name]; ok {
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ func compileInto(place func(dir []string) *folder) func(datafiles.Category) erro
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
place(c.Dir).children[c.Name] = n
|
place(c.Folders).children[c.Name] = n
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -827,7 +827,7 @@ Valid while names are read only inside the category binding them.
|
|||||||
|
|
||||||
## comprehension floor: every dimension and the overall at 7.0 or above; item 5 scores the restructure, which ships unscored: every `{…}` drawing afresh, the moves into `internal/`, and items 101, 102 and 104 to 106
|
## comprehension floor: every dimension and the overall at 7.0 or above; item 5 scores the restructure, which ships unscored: every `{…}` drawing afresh, the moves into `internal/`, and items 101, 102 and 104 to 106
|
||||||
|
|
||||||
2026-10-04, Lilleman auf Larv. Serves goal 3. The nine-seat panels in `todo.md`'s git history, 2026-09-22 to 2026-09-30, held the score between 5.2 and 6.1, with Locality lowest at every seat. The hardest units were the draw fences, which the change making every `{…}` draw afresh deleted. The change making every `{…}` draw afresh, the moves of the draw state, the grammar, `DataType`, the value proof, the builtins and the reading of data files into `internal/`, and `todo.md` items 101, 102 and 104 to 106 delete or move what the earlier chunks of the below-floor round touched, so a scoring run before item 106 merges would score code about to go; the calc change accepting a name as an operand shipped unscored on 2026-10-05 for that reason. These are the restructure the maintainer approved, which `AGENTS.md`'s comprehension rule lets merge below the floor. No scoring run or panel runs until item 5, which runs the nine-seat panel and larv-review's scoring run. The scoring run writes this entry's first scores. Valid until `todo.md` item 5 runs.
|
2026-10-04, Lilleman auf Larv. Serves goal 3. The nine-seat panels in `todo.md`'s git history, 2026-09-22 to 2026-09-30, held the score between 5.2 and 6.1, with Locality lowest at every seat. The hardest units were the draw fences, which the change making every `{…}` draw afresh deleted. The restructure deletes or moves what the earlier chunks of the below-floor round touched. It is three things: the change making every `{…}` draw afresh; the moves of the draw state, the grammar, `DataType`, what a proof knows of a value, the builtins and the reading of data files into `internal/`; and `todo.md` items 101, 102 and 104 to 106. A scoring run before item 106 merges would score code about to go. The calc change accepting a name as an operand shipped unscored on 2026-10-05 for that reason. These are the restructure the maintainer approved, which `AGENTS.md`'s comprehension rule lets merge below the floor. No scoring run or panel runs until item 5, which runs the nine-seat panel and larv-review's scoring run. The scoring run writes this entry's first scores. Valid until `todo.md` item 5 runs.
|
||||||
|
|
||||||
## The template engine stays the root package until item 107; what reads no engine type sits in `internal/`, and items 101 and 102 move a table's rows there
|
## The template engine stays the root package until item 107; what reads no engine type sits in `internal/`, and items 101 and 102 move a table's rows there
|
||||||
|
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import (
|
|||||||
"github.com/larvit/fejkdata/internal/grammar"
|
"github.com/larvit/fejkdata/internal/grammar"
|
||||||
)
|
)
|
||||||
|
|
||||||
// checkFunc validates a call at compile time: a known builtin, its args, and for calc the
|
// checkFunc validates a call at compile time: a known builtin, its args, and, for calc, the
|
||||||
// operands fields holds.
|
// operands held in fields.
|
||||||
func checkFunc(tok grammar.Token, fields map[string]node) error {
|
func checkFunc(tok grammar.Token, fields map[string]node) error {
|
||||||
err := builtinfunc.Check(tok.Fn, tok.Args)
|
err := builtinfunc.Check(tok.Fn, tok.Args)
|
||||||
if err == nil && tok.Fn == builtinfunc.CalcName {
|
if err == nil && tok.Fn == builtinfunc.CalcName {
|
||||||
|
|||||||
@@ -17,12 +17,9 @@ import (
|
|||||||
// values of the operands it named, which the caller read for it.
|
// values of the operands it named, which the caller read for it.
|
||||||
type Call func(s *drawstate.State, emitted string, operands []string) string
|
type Call func(s *drawstate.State, emitted string, operands []string) string
|
||||||
|
|
||||||
// builtin is a format-string function invoked as {name(args)}. It receives the
|
// builtin is a format-string function invoked as {name(args)}. Its Call stays pure over its
|
||||||
// draw state, the output emitted so far in the current expansion (for derivations
|
// inputs, so seeded output is reproducible. arity is the exact arg count, or -1 for variadic,
|
||||||
// such as a checksum over preceding digits), and the values of the operands it named
|
// where checkArgs does all the validation.
|
||||||
// (calc and the transforms name them). All must stay pure over (draw state, emitted, operands) so
|
|
||||||
// seeded output is reproducible. arity is the exact arg count, or -1 for variadic (then
|
|
||||||
// checkArgs does all the validation).
|
|
||||||
type builtin struct {
|
type builtin struct {
|
||||||
arity int
|
arity int
|
||||||
// prep parses validated args once, at compile time, into the closure a render calls.
|
// prep parses validated args once, at compile time, into the closure a render calls.
|
||||||
@@ -36,15 +33,15 @@ type builtin struct {
|
|||||||
// proveNumber bounds the number a call's text reads as, token its body, and says which
|
// proveNumber bounds the number a call's text reads as, token its body, and says which
|
||||||
// datatypes that text is not; set it where every render reads as a finite number, which
|
// datatypes that text is not; set it where every render reads as a finite number, which
|
||||||
// makes the call a calc operand, and leave it nil otherwise.
|
// makes the call a calc operand, and leave it nil otherwise.
|
||||||
proveNumber func(token string, prints datatype.DataType, args []string) proven.Value
|
proveNumber func(token string, prints datatype.DataType, args []string) proven.Facts
|
||||||
// prints is the datatype a call's text is, handed to proveNumber: datatype.String where it
|
// prints is the datatype a call's text is, handed to proveNumber: datatype.String where it
|
||||||
// reads as a number no column should type, as digits' leading zeros; unset where
|
// reads as a number no column should type, as digits' leading zeros; unset where
|
||||||
// proveNumber is nil.
|
// proveNumber is nil.
|
||||||
prints datatype.DataType
|
prints datatype.DataType
|
||||||
}
|
}
|
||||||
|
|
||||||
// CalcName is the builtin whose operands the engine checks and proves, since only the engine
|
// CalcName is the builtin whose operands package fejkdata checks and proves, since only it
|
||||||
// holds their nodes.
|
// holds the nodes they read.
|
||||||
const CalcName = "calc"
|
const CalcName = "calc"
|
||||||
|
|
||||||
// rng is the randomness a builtin sample draws from, which *drawstate.State satisfies.
|
// rng is the randomness a builtin sample draws from, which *drawstate.State satisfies.
|
||||||
@@ -92,11 +89,12 @@ func IsTransform(name string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ProveNumber proves a call whose every render reads as a finite number, token its body;
|
// ProveNumber proves a call whose every render reads as a finite number, token its body;
|
||||||
// false for any other call. A calc's proof reads its operands, which the caller proves.
|
// false for any other call, calc included: proven.OfCalc proves a calc from its operands,
|
||||||
func ProveNumber(name, token string, args []string) (proven.Value, bool) {
|
// which only the caller holds.
|
||||||
|
func ProveNumber(name, token string, args []string) (proven.Facts, bool) {
|
||||||
b := builtins[name]
|
b := builtins[name]
|
||||||
if b.proveNumber == nil {
|
if b.proveNumber == nil {
|
||||||
return proven.Value{}, false
|
return proven.Facts{}, false
|
||||||
}
|
}
|
||||||
return b.proveNumber(token, b.prints, args), true
|
return b.proveNumber(token, b.prints, args), true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ func TestNoRefOperandsMatchesTheOperandGrammar(t *testing.T) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if takesRef := slices.ContainsFunc(b.operands([]string{"/x"}), grammar.IsRef); b.noRefOperands == takesRef {
|
if takesRef := slices.ContainsFunc(b.operands([]string{"/x"}), grammar.IsRef); b.noRefOperands == takesRef {
|
||||||
t.Errorf("%s: noRefOperands = %v, but its operands func reads a reference: %v", name, b.noRefOperands, takesRef)
|
t.Errorf("%s: noRefOperands = %v, yet a reference /x read as an operand: %v", name, b.noRefOperands, takesRef)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ var builtins = withTransforms(map[string]builtin{
|
|||||||
"ulid": {arity: 0, prep: sample(ulid)},
|
"ulid": {arity: 0, prep: sample(ulid)},
|
||||||
"nanoid": {arity: 1, checkArgs: posIntArg, prep: chars(nanoidAlphabet)},
|
"nanoid": {arity: 1, checkArgs: posIntArg, prep: chars(nanoidAlphabet)},
|
||||||
"hex": {arity: 1, checkArgs: posIntArg, prep: chars(hexDigits)},
|
"hex": {arity: 1, checkArgs: posIntArg, prep: chars(hexDigits)},
|
||||||
"digits": {arity: 1, checkArgs: posIntArg, prep: chars("0123456789"), prints: datatype.String, proveNumber: func(token string, prints datatype.DataType, a []string) proven.Value {
|
"digits": {arity: 1, checkArgs: posIntArg, prep: chars("0123456789"), prints: datatype.String, proveNumber: func(token string, prints datatype.DataType, a []string) proven.Facts {
|
||||||
return proven.Printing(token, prints, proven.Bounded(0, math.Pow(10, float64(atoi(a[0])))-1, true))
|
return proven.Printing(token, prints, proven.Bounded(0, math.Pow(10, float64(atoi(a[0])))-1, true))
|
||||||
}},
|
}},
|
||||||
"upper": {arity: 1, checkArgs: posIntArg, prep: chars("ABCDEFGHIJKLMNOPQRSTUVWXYZ")},
|
"upper": {arity: 1, checkArgs: posIntArg, prep: chars("ABCDEFGHIJKLMNOPQRSTUVWXYZ")},
|
||||||
@@ -49,7 +49,7 @@ var builtins = withTransforms(map[string]builtin{
|
|||||||
"int": {arity: 2, checkArgs: intRangeArgs, prep: func(a []string) Call {
|
"int": {arity: 2, checkArgs: intRangeArgs, prep: func(a []string) Call {
|
||||||
lo, span := atoi(a[0]), atoi(a[1])-atoi(a[0])+1
|
lo, span := atoi(a[0]), atoi(a[1])-atoi(a[0])+1
|
||||||
return func(s *drawstate.State, _ 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: datatype.Integer, proveNumber: func(token string, prints datatype.DataType, a []string) proven.Value {
|
}, prints: datatype.Integer, proveNumber: func(token string, prints datatype.DataType, a []string) proven.Facts {
|
||||||
return proven.Printing(token, prints, proven.Bounded(float64(atoi(a[0])), float64(atoi(a[1])), true))
|
return proven.Printing(token, prints, proven.Bounded(float64(atoi(a[0])), float64(atoi(a[1])), true))
|
||||||
}},
|
}},
|
||||||
"float": {arity: 3, checkArgs: floatArgs, prep: func(a []string) Call {
|
"float": {arity: 3, checkArgs: floatArgs, prep: func(a []string) Call {
|
||||||
@@ -57,7 +57,7 @@ var builtins = withTransforms(map[string]builtin{
|
|||||||
return func(s *drawstate.State, _ string, _ []string) string {
|
return func(s *drawstate.State, _ string, _ []string) string {
|
||||||
return formatFloat(lo+s.Float64()*(hi-lo), dp)
|
return formatFloat(lo+s.Float64()*(hi-lo), dp)
|
||||||
}
|
}
|
||||||
}, prints: datatype.Number, proveNumber: func(token string, _ datatype.DataType, a []string) proven.Value {
|
}, prints: datatype.Number, proveNumber: func(token string, _ datatype.DataType, a []string) proven.Facts {
|
||||||
return proven.PrintedNumber(token, proven.Bounded(atof(a[0]), atof(a[1]), false), atoi(a[2]))
|
return proven.PrintedNumber(token, proven.Bounded(atof(a[0]), atof(a[1]), false), atoi(a[2]))
|
||||||
}},
|
}},
|
||||||
"iban": {arity: 1, checkArgs: ibanArg, prep: func(a []string) Call {
|
"iban": {arity: 1, checkArgs: ibanArg, prep: func(a []string) Call {
|
||||||
@@ -78,7 +78,7 @@ var builtins = withTransforms(map[string]builtin{
|
|||||||
return func(s *drawstate.State, _ string, _ []string) string {
|
return func(s *drawstate.State, _ string, _ []string) string {
|
||||||
return strconv.FormatUint(s.Seq(key), 10)
|
return strconv.FormatUint(s.Seq(key), 10)
|
||||||
}
|
}
|
||||||
}, prints: datatype.Integer, proveNumber: func(token string, prints datatype.DataType, _ []string) proven.Value {
|
}, prints: datatype.Integer, proveNumber: func(token string, prints datatype.DataType, _ []string) proven.Facts {
|
||||||
return proven.Printing(token, prints, proven.Bounded(1, math.MaxInt64, true))
|
return proven.Printing(token, prints, proven.Bounded(1, math.MaxInt64, true))
|
||||||
}},
|
}},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -72,9 +72,7 @@ func ibanArg(a []string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// iban generates a structurally valid IBAN for cc: a numeric BBAN of the right
|
// iban generates an IBAN for cc: a numeric BBAN of the right length, then mod-97 check digits.
|
||||||
// length, then mod-97 check digits. Real bank/branch structure isn't modelled —
|
|
||||||
// the result passes length and checksum validation, which is what fake data needs.
|
|
||||||
func iban(r rng, cc string) string {
|
func iban(r rng, cc string) string {
|
||||||
bban := make([]byte, ibanLen[cc]-4)
|
bban := make([]byte, ibanLen[cc]-4)
|
||||||
for i := range bban {
|
for i := range bban {
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ func unwrapTransform(arg string) (leaf string, chain []func(string) string, err
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// transformArg proves a transform's chain.
|
// transformArg checks that each call nested in the arg is a transform taking one arg.
|
||||||
func transformArg(a []string) error {
|
func transformArg(a []string) error {
|
||||||
_, _, err := unwrapTransform(a[0])
|
_, _, err := unwrapTransform(a[0])
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -32,14 +32,14 @@ func FS(fsys fs.FS, base string) Source { return Source{fsys: fsys, base: base}
|
|||||||
// Category is one category file: the folders above it from the source's base, its name,
|
// Category is one category file: the folders above it from the source's base, its name,
|
||||||
// and its parsed JSON.
|
// and its parsed JSON.
|
||||||
type Category struct {
|
type Category struct {
|
||||||
Dir []string
|
Folders []string
|
||||||
Name string
|
Name string
|
||||||
JSON any
|
JSON any
|
||||||
rows *rowsFiles
|
rows *rowsFiles
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadRows reads a rows file beside the category. Reading a file is what names it, so no
|
// ReadRows reads a rows file beside the category and marks it as named; Walk refuses a rows
|
||||||
// file is named and left unread.
|
// file no category read.
|
||||||
func (c Category) ReadRows(name string) (string, error) { return c.rows.read(name) }
|
func (c Category) ReadRows(name string) (string, error) { return c.rows.read(name) }
|
||||||
|
|
||||||
func (s Source) labelled(p string) string {
|
func (s Source) labelled(p string) string {
|
||||||
@@ -87,7 +87,7 @@ func (s Source) walkDir(dir []string, compile func(Category) error) (bool, error
|
|||||||
rows := newRowsFiles(s, full, entries)
|
rows := newRowsFiles(s, full, entries)
|
||||||
handed := false
|
handed := false
|
||||||
for _, e := range entries {
|
for _, e := range entries {
|
||||||
if strings.HasPrefix(e.Name(), ".") { // hidden: a checkout or an editor's file, never data
|
if strings.HasPrefix(e.Name(), ".") {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
var holds bool
|
var holds bool
|
||||||
@@ -109,7 +109,7 @@ func (s Source) walkDir(dir []string, compile func(Category) error) (bool, error
|
|||||||
return handed, nil
|
return handed, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// walkFolder walks a subfolder, and refuses its name once it holds a category.
|
// walkFolder walks a subfolder, and refuses its name if it holds a category.
|
||||||
func (s Source) walkFolder(dir []string, compile func(Category) error) (bool, error) {
|
func (s Source) walkFolder(dir []string, compile func(Category) error) (bool, error) {
|
||||||
holds, err := s.walkDir(dir, compile)
|
holds, err := s.walkDir(dir, compile)
|
||||||
if err != nil || !holds {
|
if err != nil || !holds {
|
||||||
@@ -121,7 +121,7 @@ func (s Source) walkFolder(dir []string, compile func(Category) error) (bool, er
|
|||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load hands compile the one category file name in the folder dir.
|
// Load hands compile the category file name.json in the folder dir.
|
||||||
func (s Source) Load(dir []string, name string, compile func(Category) error) error {
|
func (s Source) Load(dir []string, name string, compile func(Category) error) error {
|
||||||
full := s.dirPath(dir)
|
full := s.dirPath(dir)
|
||||||
entries, err := fs.ReadDir(s.fsys, full)
|
entries, err := fs.ReadDir(s.fsys, full)
|
||||||
@@ -151,7 +151,7 @@ func (s Source) compileFile(dir []string, file string, rows *rowsFiles, compile
|
|||||||
if err := json.Unmarshal(b, &raw); err != nil {
|
if err := json.Unmarshal(b, &raw); err != nil {
|
||||||
return false, fmt.Errorf("%s: %w", s.labelled(full), err)
|
return false, fmt.Errorf("%s: %w", s.labelled(full), err)
|
||||||
}
|
}
|
||||||
if err := compile(Category{Dir: dir, Name: name, JSON: raw, rows: rows}); err != nil {
|
if err := compile(Category{Folders: dir, Name: name, JSON: raw, rows: rows}); err != nil {
|
||||||
return false, fmt.Errorf("%s: %w", s.labelled(full), err)
|
return false, fmt.Errorf("%s: %w", s.labelled(full), err)
|
||||||
}
|
}
|
||||||
return true, nil
|
return true, nil
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ func walk(t *testing.T, src Source) ([]handed, error) {
|
|||||||
t.Helper()
|
t.Helper()
|
||||||
var got []handed
|
var got []handed
|
||||||
err := src.Walk(func(c Category) error {
|
err := src.Walk(func(c Category) error {
|
||||||
got = append(got, handed{c.Dir, c.Name, c.JSON})
|
got = append(got, handed{c.Folders, c.Name, c.JSON})
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
return got, err
|
return got, err
|
||||||
@@ -134,7 +134,7 @@ func TestLoadHandsOneCategoryAndItsRows(t *testing.T) {
|
|||||||
var got handed
|
var got handed
|
||||||
var rows string
|
var rows string
|
||||||
err := src.Load([]string{"sub"}, "t", func(c Category) error {
|
err := src.Load([]string{"sub"}, "t", func(c Category) error {
|
||||||
got = handed{c.Dir, c.Name, c.JSON}
|
got = handed{c.Folders, c.Name, c.JSON}
|
||||||
var err error
|
var err error
|
||||||
rows, err = c.ReadRows("t.tsv")
|
rows, err = c.ReadRows("t.tsv")
|
||||||
return err
|
return err
|
||||||
|
|||||||
+22
-22
@@ -14,9 +14,9 @@ import (
|
|||||||
"github.com/larvit/fejkdata/internal/invariant"
|
"github.com/larvit/fejkdata/internal/invariant"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Value is what a proof knows of every render of a node: bounds on the number each
|
// Facts is what a proof knows of every render of a node: bounds on the number each
|
||||||
// reads as, and per datatype why some render's text is not one ("" when none).
|
// reads as, and per datatype why some render's text is not one ("" when none).
|
||||||
type Value struct {
|
type Facts struct {
|
||||||
Lo, Hi float64
|
Lo, Hi float64
|
||||||
NonZero float64 // every value is at least this far from zero; 0 when one can be zero
|
NonZero float64 // every value is at least this far from zero; 0 when one can be zero
|
||||||
Integral bool
|
Integral bool
|
||||||
@@ -33,7 +33,7 @@ const ShortestDecimals = -1
|
|||||||
const limit = 1e300
|
const limit = 1e300
|
||||||
|
|
||||||
// Or is what a proof knows of a render that is either v or w.
|
// Or is what a proof knows of a render that is either v or w.
|
||||||
func (v Value) Or(w Value) Value {
|
func (v Facts) Or(w Facts) Facts {
|
||||||
v.Lo, v.Hi, v.NonZero = min(v.Lo, w.Lo), max(v.Hi, w.Hi), min(v.NonZero, w.NonZero)
|
v.Lo, v.Hi, v.NonZero = min(v.Lo, w.Lo), max(v.Hi, w.Hi), min(v.NonZero, w.NonZero)
|
||||||
v.Integral, v.Nullable = v.Integral && w.Integral, v.Nullable || w.Nullable
|
v.Integral, v.Nullable = v.Integral && w.Integral, v.Nullable || w.Nullable
|
||||||
if v.NotOperand == "" {
|
if v.NotOperand == "" {
|
||||||
@@ -48,7 +48,7 @@ func (v Value) Or(w Value) Value {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// OfCalc bounds a calc expression from its operands, or says why it cannot.
|
// OfCalc bounds a calc expression from its operands, or says why it cannot.
|
||||||
func OfCalc(expr grammar.CalcNode, operand func(name string) Value) (Value, string) {
|
func OfCalc(expr grammar.CalcNode, operand func(name string) Facts) (Facts, string) {
|
||||||
v, doubt := bound(expr, operand)
|
v, doubt := bound(expr, operand)
|
||||||
if doubt == "" && !(magnitude(v) <= limit) {
|
if doubt == "" && !(magnitude(v) <= limit) {
|
||||||
doubt = grammar.CalcText(expr) + " is not proven within 1e300"
|
doubt = grammar.CalcText(expr) + " is not proven within 1e300"
|
||||||
@@ -56,7 +56,7 @@ func OfCalc(expr grammar.CalcNode, operand func(name string) Value) (Value, stri
|
|||||||
return v, doubt
|
return v, doubt
|
||||||
}
|
}
|
||||||
|
|
||||||
func bound(n grammar.CalcNode, operand func(name string) Value) (Value, string) {
|
func bound(n grammar.CalcNode, operand func(name string) Facts) (Facts, string) {
|
||||||
switch n := n.(type) {
|
switch n := n.(type) {
|
||||||
case grammar.CalcNum:
|
case grammar.CalcNum:
|
||||||
v := float64(n)
|
v := float64(n)
|
||||||
@@ -64,9 +64,9 @@ func bound(n grammar.CalcNode, operand func(name string) Value) (Value, string)
|
|||||||
case grammar.CalcVar:
|
case grammar.CalcVar:
|
||||||
v := operand(n.Name)
|
v := operand(n.Name)
|
||||||
if v.NotOperand != "" {
|
if v.NotOperand != "" {
|
||||||
return Value{}, fmt.Sprintf("operand %q: %s", n.Name, v.NotOperand)
|
return Facts{}, fmt.Sprintf("operand %q: %s", n.Name, v.NotOperand)
|
||||||
}
|
}
|
||||||
return Value{Lo: v.Lo, Hi: v.Hi, NonZero: v.NonZero, Integral: v.Integral}, ""
|
return Facts{Lo: v.Lo, Hi: v.Hi, NonZero: v.NonZero, Integral: v.Integral}, ""
|
||||||
case grammar.CalcNeg:
|
case grammar.CalcNeg:
|
||||||
v, doubt := bound(n.X, operand)
|
v, doubt := bound(n.X, operand)
|
||||||
v.Lo, v.Hi = -v.Hi, -v.Lo
|
v.Lo, v.Hi = -v.Hi, -v.Lo
|
||||||
@@ -86,8 +86,8 @@ func bound(n grammar.CalcNode, operand func(name string) Value) (Value, string)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// combine bounds one operation from the bounds of its sides.
|
// combine bounds one operation from the bounds of its sides.
|
||||||
func combine(n grammar.CalcBin, l, r Value) (Value, string) {
|
func combine(n grammar.CalcBin, l, r Facts) (Facts, string) {
|
||||||
var v Value
|
var v Facts
|
||||||
integral := l.Integral && r.Integral
|
integral := l.Integral && r.Integral
|
||||||
switch n.Operator {
|
switch n.Operator {
|
||||||
case '+':
|
case '+':
|
||||||
@@ -102,7 +102,7 @@ func combine(n grammar.CalcBin, l, r Value) (Value, string) {
|
|||||||
return v, fmt.Sprintf("divides by %s, which is not proven nonzero", grammar.CalcText(n.R))
|
return v, fmt.Sprintf("divides by %s, which is not proven nonzero", grammar.CalcText(n.R))
|
||||||
}
|
}
|
||||||
m := magnitude(l) / r.NonZero
|
m := magnitude(l) / r.NonZero
|
||||||
v = Value{Lo: -m, Hi: m, NonZero: l.NonZero / magnitude(r)}
|
v = Facts{Lo: -m, Hi: m, NonZero: l.NonZero / magnitude(r)}
|
||||||
}
|
}
|
||||||
if !(magnitude(v) <= limit) {
|
if !(magnitude(v) <= limit) {
|
||||||
return v, grammar.CalcText(n) + " is not proven within 1e300"
|
return v, grammar.CalcText(n) + " is not proven within 1e300"
|
||||||
@@ -111,8 +111,8 @@ func combine(n grammar.CalcBin, l, r Value) (Value, string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Bounded is a number in [lo, hi], its distance from zero read off the bounds.
|
// Bounded is a number in [lo, hi], its distance from zero read off the bounds.
|
||||||
func Bounded(lo, hi float64, integral bool) Value {
|
func Bounded(lo, hi float64, integral bool) Facts {
|
||||||
v := Value{Lo: lo, Hi: hi, Integral: integral}
|
v := Facts{Lo: lo, Hi: hi, Integral: integral}
|
||||||
switch {
|
switch {
|
||||||
case lo > 0:
|
case lo > 0:
|
||||||
v.NonZero = lo
|
v.NonZero = lo
|
||||||
@@ -122,10 +122,10 @@ func Bounded(lo, hi float64, integral bool) Value {
|
|||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
func magnitude(v Value) float64 { return math.Max(math.Abs(v.Lo), math.Abs(v.Hi)) }
|
func magnitude(v Facts) float64 { return math.Max(math.Abs(v.Lo), math.Abs(v.Hi)) }
|
||||||
|
|
||||||
// PrintedNumber is what a token printing v to dp decimals holds.
|
// PrintedNumber is what a token printing v to dp decimals holds.
|
||||||
func PrintedNumber(token string, v Value, dp int) Value {
|
func PrintedNumber(token string, v Facts, dp int) Facts {
|
||||||
if dp == ShortestDecimals {
|
if dp == ShortestDecimals {
|
||||||
if v.Integral { // a whole value prints with no point
|
if v.Integral { // a whole value prints with no point
|
||||||
return printedInteger(token, v)
|
return printedInteger(token, v)
|
||||||
@@ -133,14 +133,14 @@ func PrintedNumber(token string, v Value, dp int) Value {
|
|||||||
return Printing(token, datatype.Number, v)
|
return Printing(token, datatype.Number, v)
|
||||||
}
|
}
|
||||||
half, _ := strconv.ParseFloat("5e-"+strconv.Itoa(dp+1), 64)
|
half, _ := strconv.ParseFloat("5e-"+strconv.Itoa(dp+1), 64)
|
||||||
v = Value{Lo: v.Lo - half, Hi: v.Hi + half, NonZero: math.Max(0, v.NonZero-half), Integral: v.Integral || dp == 0}
|
v = Facts{Lo: v.Lo - half, Hi: v.Hi + half, NonZero: math.Max(0, v.NonZero-half), Integral: v.Integral || dp == 0}
|
||||||
if dp == 0 {
|
if dp == 0 {
|
||||||
return printedInteger(token, v)
|
return printedInteger(token, v)
|
||||||
}
|
}
|
||||||
return Printing(token, datatype.Number, v)
|
return Printing(token, datatype.Number, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
func printedInteger(token string, v Value) Value {
|
func printedInteger(token string, v Facts) Facts {
|
||||||
v = Printing(token, datatype.Integer, v)
|
v = Printing(token, datatype.Integer, v)
|
||||||
if !(magnitude(v) < math.MaxInt64) {
|
if !(magnitude(v) < math.MaxInt64) {
|
||||||
v.Not[datatype.Integer] = fmt.Sprintf("{%s} is not proven within int64", token)
|
v.Not[datatype.Integer] = fmt.Sprintf("{%s} is not proven within int64", token)
|
||||||
@@ -150,7 +150,7 @@ func printedInteger(token string, v Value) Value {
|
|||||||
|
|
||||||
// Printing is v for a token whose every render is text of datatype prints, with a reason
|
// Printing is v for a token whose every render is text of datatype prints, with a reason
|
||||||
// against each datatype that text is not.
|
// against each datatype that text is not.
|
||||||
func Printing(token string, prints datatype.DataType, v Value) Value {
|
func Printing(token string, prints datatype.DataType, v Facts) Facts {
|
||||||
for d := datatype.Integer; d < datatype.Count; d++ {
|
for d := datatype.Integer; d < datatype.Count; d++ {
|
||||||
if prints != d && !(prints == datatype.Integer && d == datatype.Number) {
|
if prints != d && !(prints == datatype.Integer && d == datatype.Number) {
|
||||||
v.Not[d] = fmt.Sprintf("{%s} prints %s, not %s", token, datatype.Noun(prints), datatype.Noun(d))
|
v.Not[d] = fmt.Sprintf("{%s} prints %s, not %s", token, datatype.Noun(prints), datatype.Noun(d))
|
||||||
@@ -160,8 +160,8 @@ func Printing(token string, prints datatype.DataType, v Value) Value {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Unproven is a render no datatype and no calc can take, for why.
|
// Unproven is a render no datatype and no calc can take, for why.
|
||||||
func Unproven(why string) Value {
|
func Unproven(why string) Facts {
|
||||||
v := Value{NotOperand: why}
|
v := Facts{NotOperand: why}
|
||||||
for d := datatype.Integer; d < datatype.Count; d++ {
|
for d := datatype.Integer; d < datatype.Count; d++ {
|
||||||
v.Not[d] = why
|
v.Not[d] = why
|
||||||
}
|
}
|
||||||
@@ -174,8 +174,8 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Literal proves fixed text: the number calc reads it as, and each datatype it is.
|
// Literal proves fixed text: the number calc reads it as, and each datatype it is.
|
||||||
func Literal(text string) Value {
|
func Literal(text string) Facts {
|
||||||
var v Value
|
var v Facts
|
||||||
if f, err := strconv.ParseFloat(strings.TrimSpace(text), 64); err != nil || math.IsNaN(f) || math.IsInf(f, 0) {
|
if f, err := strconv.ParseFloat(strings.TrimSpace(text), 64); err != nil || math.IsNaN(f) || math.IsInf(f, 0) {
|
||||||
v.NotOperand = fmt.Sprintf("%q is not a number", text)
|
v.NotOperand = fmt.Sprintf("%q is not a number", text)
|
||||||
} else {
|
} else {
|
||||||
@@ -196,7 +196,7 @@ func Literal(text string) Value {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// signedZero refuses a zero written with a sign as a typed value, naming it unsigned.
|
// signedZero refuses a zero written with a sign as a typed value, naming it unsigned.
|
||||||
func signedZero(text string, v Value) Value {
|
func signedZero(text string, v Facts) Facts {
|
||||||
mantissa, _, _ := strings.Cut(strings.ToLower(text), "e")
|
mantissa, _, _ := strings.Cut(strings.ToLower(text), "e")
|
||||||
if !strings.HasPrefix(text, "-") || v.NotOperand != "" || strings.Trim(mantissa, "-0.") != "" {
|
if !strings.HasPrefix(text, "-") || v.NotOperand != "" || strings.Trim(mantissa, "-0.") != "" {
|
||||||
return v
|
return v
|
||||||
|
|||||||
@@ -42,8 +42,8 @@ func TestOrKeepsTheBoundsOfBothAndTheFirstReason(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestOfCalcBoundsAnExpressionFromItsOperands(t *testing.T) {
|
func TestOfCalcBoundsAnExpressionFromItsOperands(t *testing.T) {
|
||||||
operand := func(name string) Value {
|
operand := func(name string) Facts {
|
||||||
return map[string]Value{"a": Bounded(1, 9, true), "b": Bounded(0, 9, true), "big": Bounded(0, 1e299, true), "word": Unproven("it is text")}[name]
|
return map[string]Facts{"a": Bounded(1, 9, true), "b": Bounded(0, 9, true), "big": Bounded(0, 1e299, true), "word": Unproven("it is text")}[name]
|
||||||
}
|
}
|
||||||
for expr, want := range map[string]string{
|
for expr, want := range map[string]string{
|
||||||
"a * 2 + 1": "",
|
"a * 2 + 1": "",
|
||||||
@@ -69,7 +69,7 @@ func TestOfCalcBoundsAnExpressionFromItsOperands(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
lone, _ := grammar.ParseCalc("x")
|
lone, _ := grammar.ParseCalc("x")
|
||||||
if _, doubt := OfCalc(lone.Expr, func(string) Value { return Bounded(0, 1e301, true) }); doubt != "x is not proven within 1e300" {
|
if _, doubt := OfCalc(lone.Expr, func(string) Facts { return Bounded(0, 1e301, true) }); doubt != "x is not proven within 1e300" {
|
||||||
t.Errorf("OfCalc(x) doubts %q, want a lone operand held to the limit", doubt)
|
t.Errorf("OfCalc(x) doubts %q, want a lone operand held to the limit", doubt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -310,7 +310,7 @@ var columnKinds = map[reflect.Kind]columnKind{
|
|||||||
|
|
||||||
// holds reports whether a field of this kind holds every value v proves. An integer prints
|
// holds reports whether a field of this kind holds every value v proves. An integer prints
|
||||||
// whole, so its bounds round inward first.
|
// whole, so its bounds round inward first.
|
||||||
func (k columnKind) holds(v proven.Value) bool {
|
func (k columnKind) holds(v proven.Facts) bool {
|
||||||
switch k.datatype {
|
switch k.datatype {
|
||||||
case DataTypeString, DataTypeBoolean:
|
case DataTypeString, DataTypeBoolean:
|
||||||
return true
|
return true
|
||||||
|
|||||||
+17
-17
@@ -12,8 +12,8 @@ import (
|
|||||||
|
|
||||||
// valueProof proves what typed columns and their calc operands hold, each node once per scope.
|
// valueProof proves what typed columns and their calc operands hold, each node once per scope.
|
||||||
type valueProof struct {
|
type valueProof struct {
|
||||||
memo map[node]proven.Value
|
memo map[node]proven.Facts
|
||||||
columnMemo map[node]proven.Value
|
columnMemo map[node]proven.Facts
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkDatatype rejects a typed column item some render of which is not text of its datatype,
|
// checkDatatype rejects a typed column item some render of which is not text of its datatype,
|
||||||
@@ -65,7 +65,7 @@ func (p *valueProof) checkField(label string, ft reflect.Type, column node) erro
|
|||||||
}
|
}
|
||||||
|
|
||||||
// proveColumnItem proves a column item: what it renders, or, when it is a column it reads, that column.
|
// proveColumnItem proves a column item: what it renders, or, when it is a column it reads, that column.
|
||||||
func (p *valueProof) proveColumnItem(t *template) proven.Value {
|
func (p *valueProof) proveColumnItem(t *template) proven.Facts {
|
||||||
if t.link.readsColumn == nil {
|
if t.link.readsColumn == nil {
|
||||||
return p.prove(t)
|
return p.prove(t)
|
||||||
}
|
}
|
||||||
@@ -74,15 +74,15 @@ func (p *valueProof) proveColumnItem(t *template) proven.Value {
|
|||||||
|
|
||||||
// proveColumn proves a column over what its items draw, a null item marking it null rather than
|
// proveColumn proves a column over what its items draw, a null item marking it null rather than
|
||||||
// rendering "".
|
// rendering "".
|
||||||
func (p *valueProof) proveColumn(n node) proven.Value {
|
func (p *valueProof) proveColumn(n node) proven.Facts {
|
||||||
if v, done := p.columnMemo[n]; done {
|
if v, done := p.columnMemo[n]; done {
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
if p.columnMemo == nil {
|
if p.columnMemo == nil {
|
||||||
p.columnMemo = map[node]proven.Value{}
|
p.columnMemo = map[node]proven.Facts{}
|
||||||
}
|
}
|
||||||
items, nullable := columnItems(n)
|
items, nullable := columnItems(n)
|
||||||
var v proven.Value
|
var v proven.Facts
|
||||||
for i, it := range items {
|
for i, it := range items {
|
||||||
if w := p.proveColumnItem(it); i == 0 {
|
if w := p.proveColumnItem(it); i == 0 {
|
||||||
v = w
|
v = w
|
||||||
@@ -95,14 +95,14 @@ func (p *valueProof) proveColumn(n node) proven.Value {
|
|||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *valueProof) prove(n node) proven.Value {
|
func (p *valueProof) prove(n node) proven.Facts {
|
||||||
if v, done := p.memo[n]; done {
|
if v, done := p.memo[n]; done {
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
if p.memo == nil {
|
if p.memo == nil {
|
||||||
p.memo = map[node]proven.Value{}
|
p.memo = map[node]proven.Facts{}
|
||||||
}
|
}
|
||||||
var v proven.Value
|
var v proven.Facts
|
||||||
switch n := n.(type) {
|
switch n := n.(type) {
|
||||||
case *choice:
|
case *choice:
|
||||||
v = p.proveUnion(n.items)
|
v = p.proveUnion(n.items)
|
||||||
@@ -124,10 +124,10 @@ func (p *valueProof) prove(n node) proven.Value {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// proveCells proves a table column over every cell it may render.
|
// proveCells proves a table column over every cell it may render.
|
||||||
func (p *valueProof) proveCells(c *tableColumn) proven.Value {
|
func (p *valueProof) proveCells(c *tableColumn) proven.Facts {
|
||||||
var v proven.Value
|
var v proven.Facts
|
||||||
for r := 0; r < c.t.rowCount(); r++ {
|
for r := 0; r < c.t.rowCount(); r++ {
|
||||||
var w proven.Value
|
var w proven.Facts
|
||||||
if cell := c.t.cellTemplate(r, c.i); cell != nil {
|
if cell := c.t.cellTemplate(r, c.i); cell != nil {
|
||||||
w = p.prove(cell)
|
w = p.prove(cell)
|
||||||
} else {
|
} else {
|
||||||
@@ -142,7 +142,7 @@ func (p *valueProof) proveCells(c *tableColumn) proven.Value {
|
|||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *valueProof) proveUnion(nodes []node) proven.Value {
|
func (p *valueProof) proveUnion(nodes []node) proven.Facts {
|
||||||
v := p.prove(nodes[0])
|
v := p.prove(nodes[0])
|
||||||
for _, n := range nodes[1:] {
|
for _, n := range nodes[1:] {
|
||||||
v = v.Or(p.prove(n))
|
v = v.Or(p.prove(n))
|
||||||
@@ -152,7 +152,7 @@ func (p *valueProof) proveUnion(nodes []node) proven.Value {
|
|||||||
|
|
||||||
// proveTemplate proves a template that renders one value: fixed text, or a format that is
|
// proveTemplate proves a template that renders one value: fixed text, or a format that is
|
||||||
// one token alone.
|
// one token alone.
|
||||||
func (p *valueProof) proveTemplate(t *template) proven.Value {
|
func (p *valueProof) proveTemplate(t *template) proven.Facts {
|
||||||
lit, fixed := t.fixedText()
|
lit, fixed := t.fixedText()
|
||||||
switch {
|
switch {
|
||||||
case t.repeat != 1:
|
case t.repeat != 1:
|
||||||
@@ -181,13 +181,13 @@ func (p *valueProof) proveTemplate(t *template) proven.Value {
|
|||||||
if v, numeric := builtinfunc.ProveNumber(name, body, args); numeric {
|
if v, numeric := builtinfunc.ProveNumber(name, body, args); numeric {
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
return proven.Printing(body, DataTypeString, proven.Value{NotOperand: fmt.Sprintf("{%s} prints text, not a number", body)})
|
return proven.Printing(body, DataTypeString, proven.Facts{NotOperand: fmt.Sprintf("{%s} prints text, not a number", body)})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *valueProof) proveCalc(o op) proven.Value {
|
func (p *valueProof) proveCalc(o op) proven.Facts {
|
||||||
body, args := o.Body, o.Args
|
body, args := o.Body, o.Args
|
||||||
nodes := operandNodes(o)
|
nodes := operandNodes(o)
|
||||||
v, doubt := proven.OfCalc(builtinfunc.ParsedCalc(args[0]).Expr, func(name string) proven.Value { return p.proveUnion(nodes(name)) })
|
v, doubt := proven.OfCalc(builtinfunc.ParsedCalc(args[0]).Expr, func(name string) proven.Facts { return p.proveUnion(nodes(name)) })
|
||||||
if doubt != "" {
|
if doubt != "" {
|
||||||
return proven.Unproven(fmt.Sprintf("{%s}: %s", body, doubt))
|
return proven.Unproven(fmt.Sprintf("{%s}: %s", body, doubt))
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user