Make great documentation goal 1.2, and plan the README's reorganisation and the nested-template gaps #33
+3
-1
@@ -9,6 +9,8 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/larvit/fejkdata/internal/grammar"
|
||||
)
|
||||
|
||||
func TestBuiltinIDGenerators(t *testing.T) {
|
||||
@@ -454,7 +456,7 @@ func TestNoRefOperandsMatchesTheOperandGrammar(t *testing.T) {
|
||||
if b.operands == nil {
|
||||
continue
|
||||
}
|
||||
if takesRef := slices.ContainsFunc(b.operands([]string{"/x"}), 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)
|
||||
}
|
||||
}
|
||||
|
||||
+10
-15
@@ -6,6 +6,8 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/larvit/fejkdata/internal/grammar"
|
||||
)
|
||||
|
||||
// TestCalcArithmetic pins the operators, precedence, parentheses and unary minus
|
||||
@@ -99,26 +101,19 @@ func TestCalcReproducible(t *testing.T) {
|
||||
// call that is not a calc, and for one whose expression does not parse: none, either way.
|
||||
func TestParsedCallReadsOnlyAnOperandBuiltin(t *testing.T) {
|
||||
for _, format := range []string{"{luhn()}", "{calc()}", "{calc(1 +)}", "{calc(()}"} {
|
||||
if toks, err := parseFormat(format); err != nil || len(toks) != 1 || toks[0].names != nil {
|
||||
t.Errorf("parseFormat(%q) = %+v, %v, want one call reading no operand", format, toks, err)
|
||||
if toks, err := grammar.ParseFormat(format, builtinOperands); err != nil || len(toks) != 1 || toks[0].Names != nil {
|
||||
t.Errorf("ParseFormat(%q) = %+v, %v, want one call reading no operand", format, toks, err)
|
||||
}
|
||||
}
|
||||
if toks, err := parseFormat("{calc(net * qty)}"); err != nil || len(toks) != 1 || len(toks[0].names) != 2 {
|
||||
t.Errorf("parseFormat({calc(net * qty)}) = %+v, %v, want both operands", toks, err)
|
||||
if toks, err := grammar.ParseFormat("{calc(net * qty)}", builtinOperands); err != nil || len(toks) != 1 || len(toks[0].Names) != 2 {
|
||||
t.Errorf("ParseFormat({calc(net * qty)}) = %+v, %v, want both operands", toks, err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestCalcGuardsPanic pins the guards on calc's own invariants: checkCalc parsed
|
||||
// the expression before prep sees it, and indexVars places every name calcVars
|
||||
// read, so a break in either is reported rather than computed around.
|
||||
func TestCalcGuardsPanic(t *testing.T) {
|
||||
for name, call := range map[string]func(){
|
||||
"prep on an expression that does not parse": func() { calcPrep([]string{"1 +"}) },
|
||||
"an operand name never placed": func() { calcVar("n").eval(nil) },
|
||||
"indexVars on a name it did not read": func() { indexVars(calcVar("n"), map[string]int{}) },
|
||||
} {
|
||||
mustPanic(t, name, call)
|
||||
}
|
||||
// TestCalcPrepPanicsOnAnUncheckedExpression pins the guard on calc's own invariant:
|
||||
// checkCalc parsed the expression before prep sees it.
|
||||
func TestCalcPrepPanicsOnAnUncheckedExpression(t *testing.T) {
|
||||
mustPanic(t, "prep on an expression that does not parse", func() { calcPrep([]string{"1 +"}) })
|
||||
}
|
||||
|
||||
// --- one draw, one value: a calc operand reads the expansion's draw ---
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package fejkdata
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -131,18 +130,3 @@ func TestFormatCompileErrors(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestSplitArgsQuotesOutsideSelectors pins the arg grammar: a comma splits outside a
|
||||
// selector and outside a quoted layout, and a quote inside a selector is a name's text.
|
||||
func TestSplitArgsQuotesOutsideSelectors(t *testing.T) {
|
||||
for in, want := range map[string][]string{
|
||||
"a, b": {"a", "b"},
|
||||
"1990-01-01,1990-12-31,'January 2, 2006'": {"1990-01-01", "1990-12-31", "'January 2, 2006'"},
|
||||
"/geo.US.locality[O'Fallon].name, 2": {"/geo.US.locality[O'Fallon].name", "2"},
|
||||
"'[a,b]'": {"'[a,b]'"},
|
||||
} {
|
||||
if got := splitArgs(in); !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("splitArgs(%q) = %q, want %q", in, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -29,9 +29,10 @@ func TestPackageImports(t *testing.T) {
|
||||
}
|
||||
checked := 0
|
||||
allowed := map[string][]string{
|
||||
".": {"internal/drawstate"},
|
||||
".": {"internal/drawstate", "internal/grammar"},
|
||||
"cmd/fejkdata": {"."},
|
||||
"internal/drawstate": nil,
|
||||
"internal/grammar": nil,
|
||||
}
|
||||
seen := map[string]bool{}
|
||||
err = filepath.WalkDir(".", func(dir string, d fs.DirEntry, err error) error {
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
package grammar
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseCalcPlacesEachOperandAtItsFirstReading(t *testing.T) {
|
||||
n, err := ParseCalc("b * (a - b) / -2")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want := CalcBin{'/', CalcBin{'*', CalcVar{"b", 0}, CalcBin{'-', CalcVar{"a", 1}, CalcVar{"b", 0}}}, CalcNeg{CalcNum(2)}}
|
||||
if !reflect.DeepEqual(n, want) {
|
||||
t.Errorf("ParseCalc = %#v, want %#v", n, want)
|
||||
}
|
||||
if got := CalcVars(n); !reflect.DeepEqual(got, []string{"b", "a"}) {
|
||||
t.Errorf("CalcVars = %q, want [b a]", got)
|
||||
}
|
||||
if got := CalcText(n); got != "((b * (a - b)) / -2)" {
|
||||
t.Errorf("CalcText = %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseCalcRefusesAnUnfinishedExpression(t *testing.T) {
|
||||
for _, expr := range []string{"", " ", "1 +", "(1", "1 2", "a.b", "postal-code *"} {
|
||||
if _, err := ParseCalc(expr); err == nil {
|
||||
t.Errorf("ParseCalc(%q) = nil error, want one", expr)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package grammar
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestSplitArgsQuotesOutsideSelectors pins the arg grammar: a comma splits outside a
|
||||
// selector and outside a quoted layout, and a quote inside a selector is a name's text.
|
||||
func TestSplitArgsQuotesOutsideSelectors(t *testing.T) {
|
||||
for in, want := range map[string][]string{
|
||||
"a, b": {"a", "b"},
|
||||
"1990-01-01,1990-12-31,'January 2, 2006'": {"1990-01-01", "1990-12-31", "'January 2, 2006'"},
|
||||
"/geo.US.locality[O'Fallon].name, 2": {"/geo.US.locality[O'Fallon].name", "2"},
|
||||
"'[a,b]'": {"'[a,b]'"},
|
||||
} {
|
||||
if got := SplitArgs(in); !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("SplitArgs(%q) = %q, want %q", in, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseFormatReadsEachTokenKind(t *testing.T) {
|
||||
toks, err := ParseFormat("a{{{x|/y.z[a.b]}{/p as q}{f(1, n)}", func(fn string, args []string) []string {
|
||||
return args[1:]
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want := []Token{
|
||||
{Kind: LiteralRun, Lit: "a{"},
|
||||
{Kind: NameRead, Body: "x|/y.z[a.b]", Names: []string{"x", "/y.z[a.b]"}},
|
||||
{Kind: NameBind, Body: "/p as q", BoundRef: "/p", Bound: "q"},
|
||||
{Kind: BuiltinCall, Body: "f(1, n)", Fn: "f", Args: []string{"1", "n"}, Names: []string{"n"}},
|
||||
}
|
||||
if !reflect.DeepEqual(toks, want) {
|
||||
t.Errorf("ParseFormat = %+v, want %+v", toks, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseFormatRefusesUnbalancedBraces(t *testing.T) {
|
||||
for _, format := range []string{"{a", "a}", "{a{b}}", "{f(}"} {
|
||||
if _, err := ParseFormat(format, nil); err == nil {
|
||||
t.Errorf("ParseFormat(%q) = nil error, want one", format)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package grammar
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestCheckNameRefusesWhatAGrammarReserves(t *testing.T) {
|
||||
for _, name := range []string{"", "-", "a as b", "a.b", "a|b", "a(b", "a{b", "a}b", "a/b", "a[b", "a]b", `a"b`} {
|
||||
if CheckName(name) == nil {
|
||||
t.Errorf("CheckName(%q) = nil, want an error", name)
|
||||
}
|
||||
}
|
||||
for _, name := range []string{"a", "postal-code", "é", "a_b"} {
|
||||
if err := CheckName(name); err != nil {
|
||||
t.Errorf("CheckName(%q) = %v, want nil", name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package grammar
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSplitPathKeepsSelectorsAndStepsUpWhole(t *testing.T) {
|
||||
for path, want := range map[string][]string{
|
||||
"a": {"a"},
|
||||
"a.b": {"a", "b"},
|
||||
"misc.territory[SE].name": {"misc", "territory", "[SE]", "name"},
|
||||
"x[a.b]": {"x", "[a.b]"},
|
||||
"city[Oslo]..country": {"city", "[Oslo]", "..", "country"},
|
||||
"a..b": {"a", "..", "b"},
|
||||
} {
|
||||
segs, err := SplitPath(path)
|
||||
if err != nil || !reflect.DeepEqual(segs, want) {
|
||||
t.Errorf("SplitPath(%q) = %q, %v, want %q", path, segs, err, want)
|
||||
continue
|
||||
}
|
||||
if got := JoinSegments(segs); got != path {
|
||||
t.Errorf("JoinSegments(%q) = %q, want %q", segs, got, path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSplitPathRefusesAStraySelectorBracket(t *testing.T) {
|
||||
for _, path := range []string{"[a]", "a.[b]", "a[b", "a]", "a[]", "a[b]c", "a[b[c]]"} {
|
||||
if _, err := SplitPath(path); err == nil {
|
||||
t.Errorf("SplitPath(%q) = nil error, want one", path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNameSegmentsDropSelectorsAndStepsUp(t *testing.T) {
|
||||
if got := NameSegments([]string{"a", "[b]", "..", "c"}); !reflect.DeepEqual(got, []string{"a", "c"}) {
|
||||
t.Errorf("NameSegments = %q, want [a c]", got)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package grammar
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestRefShapeSplitsTheSigil(t *testing.T) {
|
||||
for ref, want := range map[string][2]string{
|
||||
"/a.b": {"/", "a.b"},
|
||||
".a": {".", "a"},
|
||||
"..a.b": {"..", "a.b"},
|
||||
} {
|
||||
sigil, rest, err := RefShape(ref)
|
||||
if err != nil || sigil != want[0] || rest != want[1] {
|
||||
t.Errorf("RefShape(%q) = %q, %q, %v, want %q", ref, sigil, rest, err, want)
|
||||
}
|
||||
}
|
||||
for _, ref := range []string{"/", "//a", ".../a", "/a..b.", "/a.[b]"} {
|
||||
if _, _, err := RefShape(ref); err == nil {
|
||||
t.Errorf("RefShape(%q) = nil error, want one", ref)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user