Let a struct field's Go type type a bare number or boolean in its tag, and offer only a datatype that holds a refused column's number
This commit is contained in:
@@ -5,12 +5,12 @@ import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/larvit/fejkdata/internal/datafiles"
|
||||
"github.com/larvit/fejkdata/internal/grammar"
|
||||
"github.com/larvit/fejkdata/internal/invariant"
|
||||
"github.com/larvit/fejkdata/internal/jsonvalue"
|
||||
"github.com/larvit/fejkdata/internal/proven"
|
||||
)
|
||||
|
||||
// node is a compiled element of the namespace tree: a folder, choice, null,
|
||||
@@ -91,8 +91,11 @@ func (t *template) startOf(name string) node {
|
||||
|
||||
// compile converts parsed JSON — a category or an inline template — into a node tree,
|
||||
// validating structure up front.
|
||||
func compile(v jsonvalue.Value) (node, error) {
|
||||
n, err := compileItem(v.Any(), atTop)
|
||||
func compile(v jsonvalue.Value) (node, error) { return compileFrom(v, atTop) }
|
||||
|
||||
// compileFrom is compile with the root at pos: atTop, or atGoTop for a struct's tags.
|
||||
func compileFrom(v jsonvalue.Value, pos position) (node, error) {
|
||||
n, err := compileItem(v.Any(), pos)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -115,11 +118,16 @@ func compileCategory(c datafiles.Category) (node, error) {
|
||||
type position int
|
||||
|
||||
const (
|
||||
inFormat position = iota // rendered by a format, so neither
|
||||
atTop // a category or an inline template, whose fields may be columns
|
||||
inColumn // a column, or a choice item standing in for one
|
||||
inFormat position = iota // rendered by a format, so neither
|
||||
atTop // a category or an inline template, whose fields may be columns
|
||||
inColumn // a column, or a choice item standing in for one
|
||||
atGoTop // a struct's tags, whose fields are columns a Go type types
|
||||
inGoColumn // a column a struct field's Go type types, or a choice item in one
|
||||
)
|
||||
|
||||
// column reports a position that is a record column.
|
||||
func (p position) column() bool { return p == inColumn || p == inGoColumn }
|
||||
|
||||
// compileItem compiles one node; a weight on it skews only a choice drawing it.
|
||||
func compileItem(v any, pos position) (node, error) {
|
||||
if _, err := weightOf(v); err != nil {
|
||||
@@ -137,13 +145,13 @@ func compileItem(v any, pos position) (node, error) {
|
||||
case map[string]any:
|
||||
return compileTemplate(v, pos)
|
||||
case nil:
|
||||
if pos != inColumn {
|
||||
if !pos.column() {
|
||||
return compileString("")
|
||||
}
|
||||
return &nullItem{}, nil
|
||||
case json.Number:
|
||||
if pos == inColumn {
|
||||
return nil, typedOrText(string(v), numberType(v))
|
||||
return nil, typedOrText(string(v), DataTypeInteger, DataTypeNumber)
|
||||
}
|
||||
return compileString(string(v))
|
||||
case bool:
|
||||
@@ -156,18 +164,15 @@ func compileItem(v any, pos position) (node, error) {
|
||||
}
|
||||
|
||||
// typedOrText refuses a bare JSON number or boolean in a record column, which could be the
|
||||
// text it spells or a value of datatype d, naming both spellings.
|
||||
func typedOrText(text string, d DataType) error {
|
||||
return fmt.Errorf(`%s in a record column could be text or a value of datatype %s; write %q for text, or {"format":%q,"datatype":%q}`, text, d, text, text, d)
|
||||
}
|
||||
|
||||
// numberType is the datatype a JSON number spells: an integer where it has no fraction or
|
||||
// exponent, else a number.
|
||||
func numberType(n json.Number) DataType {
|
||||
if strings.ContainsAny(string(n), ".eE") {
|
||||
return DataTypeNumber
|
||||
// text it spells or a typed value, naming the text and the first of ds that holds it.
|
||||
func typedOrText(text string, ds ...DataType) error {
|
||||
held := proven.Literal(text)
|
||||
for _, d := range ds {
|
||||
if held.Not[d] == "" {
|
||||
return fmt.Errorf(`%s in a record column could be text or a value of datatype %s; write %q for text, or {"format":%q,"datatype":%q}`, text, d, text, text, d)
|
||||
}
|
||||
}
|
||||
return DataTypeInteger
|
||||
return fmt.Errorf(`%s in a record column could be text or a typed value, though no datatype holds it; write %q for text`, text, text)
|
||||
}
|
||||
|
||||
// jsonKind names the kind of a JSON value an option cannot take, in the data format's own
|
||||
@@ -209,14 +214,18 @@ func (t *template) fixedText() (string, bool) {
|
||||
|
||||
func compileChoice(items []any, pos position) (node, error) {
|
||||
itemPos := inFormat
|
||||
if pos == inColumn {
|
||||
itemPos = inColumn
|
||||
if pos.column() {
|
||||
itemPos = pos
|
||||
}
|
||||
switch len(items) {
|
||||
case 0:
|
||||
return nil, fmt.Errorf("empty choice")
|
||||
case 1:
|
||||
if w, err := weightOf(items[0]); err == nil && w == 0 {
|
||||
w, err := weightOf(items[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if w == 0 {
|
||||
return nil, fmt.Errorf("every weight is 0, so the choice has nothing to draw")
|
||||
}
|
||||
return compileItem(items[0], pos)
|
||||
@@ -263,8 +272,8 @@ func compileTemplate(m map[string]any, pos position) (node, error) {
|
||||
return nil, err
|
||||
}
|
||||
fieldPos := inFormat
|
||||
if pos == atTop && o.repeat == 1 {
|
||||
fieldPos = inColumn
|
||||
if o.repeat == 1 {
|
||||
fieldPos = map[position]position{atTop: inColumn, atGoTop: inGoColumn}[pos]
|
||||
}
|
||||
fields, err := compileFields(m, fieldPos)
|
||||
if err != nil {
|
||||
@@ -274,7 +283,7 @@ func compileTemplate(m map[string]any, pos position) (node, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &template{format: o.format, tokens: toks, fields: fields, repeat: o.repeat, separator: o.separator, datatype: o.datatype, isRecord: fieldPos == inColumn, unbound: unbound}, nil
|
||||
return &template{format: o.format, tokens: toks, fields: fields, repeat: o.repeat, separator: o.separator, datatype: o.datatype, isRecord: fieldPos.column(), unbound: unbound}, nil
|
||||
}
|
||||
|
||||
// templateOptions is what a template object's option keys say.
|
||||
|
||||
Reference in New Issue
Block a user