Load each spelling that only restated a shorter one, as that shorter form

This commit is contained in:
2026-10-07 22:32:18 +02:00
committed by Lillem4n
parent 91b65e3ded
commit e63efe327f
19 changed files with 101 additions and 366 deletions
+7 -75
View File
@@ -4,9 +4,6 @@ import (
"encoding/json"
"fmt"
"math"
"slices"
"sort"
"strings"
"github.com/larvit/fejkdata/internal/datafiles"
"github.com/larvit/fejkdata/internal/grammar"
@@ -99,70 +96,16 @@ func compile(v any) (node, error) {
}
// compileCategory compiles a data file's value, which may be a table over a rows
// file beside it, and refuses a choice that is a table written as templates.
// file beside it.
func compileCategory(c datafiles.Category) (node, error) {
if m, ok := c.JSON.(map[string]any); ok {
if _, isTable := m["rows"]; isTable {
return compileTable(m, c.Folders, c.Name, c.ReadRows)
}
}
if items, ok := c.JSON.([]any); ok {
if err := checkNotRows(items, c.Name); err != nil {
return nil, err
}
}
return compile(c.JSON)
}
// checkNotRows refuses a choice of templates sharing one format and one set of
// string fields: each item is a row, and the rows file is the spelling for that.
// docs/decisions.md#the-choice-of-rows-fence-guards-a-data-files-root-and-requires-string-fields
func checkNotRows(items []any, name string) error {
var format string
var fields []string
weighted := false
for i, raw := range items {
f, keys, w, isRow := rowShape(raw)
if !isRow || i > 0 && (f != format || !slices.Equal(keys, fields)) {
return nil
}
format, fields, weighted = f, keys, weighted || w
}
if len(items) < 2 || len(fields) == 0 {
return nil
}
weight := ""
if weighted {
weight = `, a weight column, and "weight" naming it`
}
return fmt.Errorf("a choice of %d templates with one format and the fields %v is a table; write the rows in %s.tsv with the header %q%s, and the category as {\"format\": %q, \"rows\": \"%s.tsv\"}",
len(items), fields, name, strings.Join(fields, "\t"), weight, format, name)
}
// rowShape reads a choice item as a row: a template whose every field is a string,
// with its format, its sorted field names, and whether it carries a weight.
func rowShape(raw any) (format string, fields []string, weighted, isRow bool) {
m, ok := raw.(map[string]any)
if !ok {
return "", nil, false, false
}
format, _ = m["format"].(string)
for k, v := range m {
if k == "weight" {
weighted = true
continue
}
if _, isString := v.(string); !isString || isOption(k) && k != "format" {
return "", nil, false, false
}
if k != "format" {
fields = append(fields, k)
}
}
sort.Strings(fields)
return format, fields, weighted, true
}
// position is where a JSON value sits, which decides whether it may carry a datatype or
// be null.
type position int
@@ -199,7 +142,7 @@ func compileItem(v any, pos position) (node, error) {
return compileTemplate(v, pos)
case nil:
if pos != inColumn {
return nil, fmt.Errorf(`null is a record column's value; here it only renders "", so write ""`)
return compileString("")
}
return &nullItem{}, nil
default:
@@ -247,7 +190,10 @@ func compileChoice(items []any, pos position) (node, error) {
return nil, fmt.Errorf("empty choice")
}
if len(items) == 1 {
return nil, fmt.Errorf("a one-item choice is its item; write the item")
if _, err := weightOf(items[0]); err != nil {
return nil, err
}
return compileItem(items[0], pos)
}
if err := checkNoRepeatedItem(items); err != nil {
return nil, err
@@ -326,9 +272,6 @@ func compileTemplate(m map[string]any, pos position) (node, error) {
if err != nil {
return nil, err
}
if len(fields) == 0 && o.repeat == 1 && !o.weighted && o.datatype == DataTypeString {
return nil, fmt.Errorf("an object holding only a format is a string; write %q", o.format)
}
toks, unbound, err := parseChecked(o.format, fields)
if err != nil {
return nil, err
@@ -342,7 +285,6 @@ type templateOptions struct {
format string
repeat int
separator string
weighted bool
}
func readOptions(m map[string]any, pos position) (templateOptions, error) {
@@ -367,11 +309,7 @@ func readOptions(m map[string]any, pos position) (templateOptions, error) {
if repeat == 1 {
return o, fmt.Errorf("separator joins repeated renders, so it has no effect without a repeat above 1")
}
if o.separator == "" {
return o, fmt.Errorf("separator \"\" is the default, so it has no effect; drop it")
}
}
_, o.weighted = m["weight"]
return o, nil
}
@@ -394,7 +332,7 @@ func compileFields(m map[string]any, pos position) (map[string]node, error) {
}
// repeatOf reads a template's "repeat" (default 1): how many times its format
// is rendered and concatenated. A present one must be an integer above 1.
// is rendered and concatenated. A present one must be a positive integer.
func repeatOf(m map[string]any) (int, error) {
rv, ok := m["repeat"]
if !ok {
@@ -407,9 +345,6 @@ func repeatOf(m map[string]any) (int, error) {
if math.IsNaN(r) || math.IsInf(r, 0) || r < 1 || r != math.Trunc(r) {
return 0, fmt.Errorf("repeat must be a positive integer, got %v", rv)
}
if r == 1 {
return 0, fmt.Errorf("repeat 1 is the default, so it has no effect; drop it")
}
if r > MaxRepeat { // caps the renders one repeat asks for; repeatCheck bounds what nested ones multiply to
return 0, fmt.Errorf("repeat %v exceeds the maximum %d", rv, MaxRepeat)
}
@@ -437,9 +372,6 @@ func weightOf(raw any) (float64, error) {
if w == 0 {
return 0, fmt.Errorf("weight 0 means the item is never drawn; remove the item instead")
}
if w == 1 {
return 0, fmt.Errorf("weight 1 is the default, so it has no effect; drop it")
}
return w, nil
}