Register a transform once, and derive the calls a typed column's error offers from builtin.number
This commit is contained in:
@@ -413,7 +413,7 @@ datatype cannot hold is a load error naming it:
|
|||||||
|
|
||||||
```text
|
```text
|
||||||
order.id: datatype integer: {digits(3)} prints text, not an integer
|
order.id: datatype integer: {digits(3)} prints text, not an integer
|
||||||
order.id: datatype integer: "1{digits(2)}" is not one value; write one literal or one {int()}, {float()}, {seq()} or {calc()}, or read one
|
order.id: datatype integer: "1{digits(2)}" is not one value; write one literal or one {float()}, {int()}, {seq()} or {calc()}, or read one
|
||||||
```
|
```
|
||||||
|
|
||||||
A column of one reference alone to another record's column — `"score": "{/src.score}"`
|
A column of one reference alone to another record's column — `"score": "{/src.score}"`
|
||||||
|
|||||||
+12
-15
@@ -22,7 +22,7 @@ const (
|
|||||||
// emitted so far in the current expansion (place them after their payload);
|
// emitted so far in the current expansion (place them after their payload);
|
||||||
// samples read only the rng. A time-based id (uuid v7, ulid) draws its timestamp
|
// samples read only the rng. A time-based id (uuid v7, ulid) draws its timestamp
|
||||||
// from the rng, not the wall clock, so seeded output stays reproducible.
|
// from the rng, not the wall clock, so seeded output stays reproducible.
|
||||||
var builtins = map[string]builtin{
|
var builtins = withTransforms(map[string]builtin{
|
||||||
"luhn": {arity: 0, prep: derive(func(e string) string { return string(rune('0' + luhnCheck(e))) })},
|
"luhn": {arity: 0, prep: derive(func(e string) string { return string(rune('0' + luhnCheck(e))) })},
|
||||||
"mod11": {arity: 0, prep: derive(mod11Check)},
|
"mod11": {arity: 0, prep: derive(mod11Check)},
|
||||||
"ean": {arity: 0, prep: derive(eanCheck)},
|
"ean": {arity: 0, prep: derive(eanCheck)},
|
||||||
@@ -30,8 +30,8 @@ var builtins = map[string]builtin{
|
|||||||
"ulid": {arity: 0, prep: sample(ulid)},
|
"ulid": {arity: 0, prep: sample(ulid)},
|
||||||
"nanoid": {arity: 1, check: posIntArg, prep: chars(nanoidAlphabet)},
|
"nanoid": {arity: 1, check: posIntArg, prep: chars(nanoidAlphabet)},
|
||||||
"hex": {arity: 1, check: posIntArg, prep: chars(hexDigits)},
|
"hex": {arity: 1, check: posIntArg, prep: chars(hexDigits)},
|
||||||
"digits": {arity: 1, check: posIntArg, prep: chars("0123456789"), number: func(token string, a []string) proven {
|
"digits": {arity: 1, check: posIntArg, prep: chars("0123456789"), prints: DataTypeString, number: func(token string, prints DataType, a []string) proven {
|
||||||
return printing(token, DataTypeString, bounded(0, math.Pow(10, float64(atoi(a[0])))-1, true))
|
return printing(token, prints, bounded(0, math.Pow(10, float64(atoi(a[0])))-1, true))
|
||||||
}},
|
}},
|
||||||
"upper": {arity: 1, check: posIntArg, prep: chars("ABCDEFGHIJKLMNOPQRSTUVWXYZ")},
|
"upper": {arity: 1, check: posIntArg, prep: chars("ABCDEFGHIJKLMNOPQRSTUVWXYZ")},
|
||||||
"lower": {arity: 1, check: posIntArg, prep: chars("abcdefghijklmnopqrstuvwxyz")},
|
"lower": {arity: 1, check: posIntArg, prep: chars("abcdefghijklmnopqrstuvwxyz")},
|
||||||
@@ -44,27 +44,24 @@ var builtins = map[string]builtin{
|
|||||||
"int": {arity: 2, check: intRangeArgs, prep: func(a []string) callFn {
|
"int": {arity: 2, check: intRangeArgs, prep: func(a []string) callFn {
|
||||||
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 *session, _ string, _ []string) string { return strconv.Itoa(lo + s.IntN(span)) }
|
return func(s *session, _ string, _ []string) string { return strconv.Itoa(lo + s.IntN(span)) }
|
||||||
}, number: func(token string, a []string) proven {
|
}, prints: DataTypeInteger, number: func(token string, prints DataType, a []string) proven {
|
||||||
return printing(token, DataTypeInteger, bounded(float64(atoi(a[0])), float64(atoi(a[1])), true))
|
return printing(token, prints, bounded(float64(atoi(a[0])), float64(atoi(a[1])), true))
|
||||||
}},
|
}},
|
||||||
"float": {arity: 3, check: floatArgs, prep: func(a []string) callFn {
|
"float": {arity: 3, check: floatArgs, prep: func(a []string) callFn {
|
||||||
lo, hi, dp := atof(a[0]), atof(a[1]), atoi(a[2])
|
lo, hi, dp := atof(a[0]), atof(a[1]), atoi(a[2])
|
||||||
return func(s *session, _ string, _ []string) string {
|
return func(s *session, _ string, _ []string) string {
|
||||||
return formatFloat(lo+s.Float64()*(hi-lo), dp)
|
return formatFloat(lo+s.Float64()*(hi-lo), dp)
|
||||||
}
|
}
|
||||||
}, number: func(token string, a []string) proven {
|
}, prints: DataTypeNumber, number: func(token string, _ DataType, a []string) proven {
|
||||||
return printedNumber(token, bounded(atof(a[0]), atof(a[1]), false), atoi(a[2]))
|
return printedNumber(token, bounded(atof(a[0]), atof(a[1]), false), atoi(a[2]))
|
||||||
}},
|
}},
|
||||||
"iban": {arity: 1, check: ibanArg, prep: func(a []string) callFn {
|
"iban": {arity: 1, check: ibanArg, prep: func(a []string) callFn {
|
||||||
cc := a[0]
|
cc := a[0]
|
||||||
return func(s *session, _ string, _ []string) string { return iban(s, cc) }
|
return func(s *session, _ string, _ []string) string { return iban(s, cc) }
|
||||||
}},
|
}},
|
||||||
"date": {arity: -1, check: dateArgs, prep: datePrep},
|
"date": {arity: -1, check: dateArgs, prep: datePrep},
|
||||||
"time": {arity: -1, check: timeArg, prep: timePrep},
|
"time": {arity: -1, check: timeArg, prep: timePrep},
|
||||||
"calc": {arity: -1, check: checkCalc, prep: calcPrep, operands: calcOperands},
|
"calc": {arity: -1, check: checkCalc, prep: calcPrep, operands: calcOperands},
|
||||||
"lowercase": {arity: 1, check: transformArg, prep: transformPrep(strings.ToLower), operands: transformOperand},
|
|
||||||
"uppercase": {arity: 1, check: transformArg, prep: transformPrep(strings.ToUpper), operands: transformOperand},
|
|
||||||
"ascii": {arity: 1, check: transformArg, prep: transformPrep(asciiFold), operands: transformOperand},
|
|
||||||
// seq is the one stateful builtin: a per-session counter from 1, advancing on
|
// seq is the one stateful builtin: a per-session counter from 1, advancing on
|
||||||
// each call. An optional name selects an independent counter; no name uses the
|
// each call. An optional name selects an independent counter; no name uses the
|
||||||
// default one. Deterministic by construction, so seeded output stays stable.
|
// default one. Deterministic by construction, so seeded output stays stable.
|
||||||
@@ -76,10 +73,10 @@ var builtins = map[string]builtin{
|
|||||||
return func(s *session, _ string, _ []string) string {
|
return func(s *session, _ string, _ []string) string {
|
||||||
return strconv.FormatUint(s.next(key), 10)
|
return strconv.FormatUint(s.next(key), 10)
|
||||||
}
|
}
|
||||||
}, number: func(token string, _ []string) proven {
|
}, prints: DataTypeInteger, number: func(token string, prints DataType, _ []string) proven {
|
||||||
return printing(token, DataTypeInteger, bounded(1, math.MaxInt64, true))
|
return printing(token, prints, bounded(1, math.MaxInt64, true))
|
||||||
}},
|
}},
|
||||||
}
|
})
|
||||||
|
|
||||||
// derive and sample are the two argument-free builtin shapes: a derivation reads
|
// derive and sample are the two argument-free builtin shapes: a derivation reads
|
||||||
// the output emitted so far, a sample reads only the rng. chars is the shape of a
|
// the output emitted so far, a sample reads only the rng. chars is the shape of a
|
||||||
|
|||||||
+3
-2
@@ -119,8 +119,9 @@ type builtin struct {
|
|||||||
// for a builtin that reads none.
|
// for a builtin that reads none.
|
||||||
operands func(args []string) []string
|
operands func(args []string) []string
|
||||||
// number proves what a call prints, token its body: the bounds of its number and the
|
// number proves what a call prints, token its body: the bounds of its number and the
|
||||||
// datatype of its text; nil for a builtin whose text is no number.
|
// datatype of its text, prints or one inside it; nil for a builtin whose text is no number.
|
||||||
number func(token string, args []string) proven
|
prints DataType
|
||||||
|
number func(token string, prints DataType, args []string) proven
|
||||||
}
|
}
|
||||||
|
|
||||||
// funcCall splits a "{token}" body shaped name(args) into its parts; ok is false
|
// funcCall splits a "{token}" body shaped name(args) into its parts; ok is false
|
||||||
|
|||||||
@@ -14,6 +14,13 @@ var transforms = map[string]func(string) string{
|
|||||||
"uppercase": strings.ToUpper,
|
"uppercase": strings.ToUpper,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func withTransforms(builtins map[string]builtin) map[string]builtin {
|
||||||
|
for name, fn := range transforms {
|
||||||
|
builtins[name] = builtin{arity: 1, check: transformArg, prep: transformPrep(fn), operands: transformOperand}
|
||||||
|
}
|
||||||
|
return builtins
|
||||||
|
}
|
||||||
|
|
||||||
// unwrapTransform peels nested transform calls off an operand arg, returning the
|
// unwrapTransform peels nested transform calls off an operand arg, returning the
|
||||||
// field it finally names and the transforms to apply, innermost last.
|
// field it finally names and the transforms to apply, innermost last.
|
||||||
func unwrapTransform(arg string) (leaf string, chain []func(string) string, err error) {
|
func unwrapTransform(arg string) (leaf string, chain []func(string) string, err error) {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
@@ -148,8 +149,8 @@ func (p *valueProof) template(t *template) proven {
|
|||||||
case t.fixed:
|
case t.fixed:
|
||||||
return literalValue(t.lit)
|
return literalValue(t.lit)
|
||||||
case len(t.ops) != 1:
|
case len(t.ops) != 1:
|
||||||
v := unproven(notOneValue(t.format, "{int()}, {float()}, {seq()} or {calc()}"))
|
v := unproven(notOneValue(t.format, numberCalls(false)))
|
||||||
v.notOperand = notOneValue(t.format, "{int()}, {float()}, {seq()}, {digits()} or {calc()}")
|
v.notOperand = notOneValue(t.format, numberCalls(true))
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
o := t.ops[0]
|
o := t.ops[0]
|
||||||
@@ -164,7 +165,7 @@ func (p *valueProof) template(t *template) proven {
|
|||||||
case name == "calc":
|
case name == "calc":
|
||||||
return p.calc(t, body, args)
|
return p.calc(t, body, args)
|
||||||
case builtins[name].number != nil:
|
case builtins[name].number != nil:
|
||||||
return builtins[name].number(body, args)
|
return builtins[name].number(body, builtins[name].prints, args)
|
||||||
case isTransform:
|
case isTransform:
|
||||||
return unproven(fmt.Sprintf("{%s} rewrites text rather than printing a value; write the values it would print", body))
|
return unproven(fmt.Sprintf("{%s} rewrites text rather than printing a value; write the values it would print", body))
|
||||||
}
|
}
|
||||||
@@ -287,6 +288,19 @@ func printing(token string, prints DataType, v proven) proven {
|
|||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// numberCalls lists the builtins printing a number, or with text also those whose text
|
||||||
|
// is one.
|
||||||
|
func numberCalls(text bool) string {
|
||||||
|
var calls []string
|
||||||
|
for name, b := range builtins {
|
||||||
|
if b.number != nil && (text || b.prints != DataTypeString) {
|
||||||
|
calls = append(calls, "{"+name+"()}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
slices.Sort(calls)
|
||||||
|
return strings.Join(calls, ", ") + " or {calc()}"
|
||||||
|
}
|
||||||
|
|
||||||
func notOneValue(format, calls string) string {
|
func notOneValue(format, calls string) string {
|
||||||
return fmt.Sprintf("%q is not one value; write one literal or one %s, or read one", format, calls)
|
return fmt.Sprintf("%q is not one value; write one literal or one %s, or read one", format, calls)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user