diff --git a/README.md b/README.md index b565a2b..d66a341 100644 --- a/README.md +++ b/README.md @@ -365,9 +365,10 @@ the reference sits, including in a field the format renders. A `{calc()}` operand is held on its own terms too, so the same fence guards it: `{..cat.net} x 2 = {calc(net * 2, 2)}` names one field two ways and is a load -error, with no path token anywhere. What an operand's draw fixes is the value it -renders, so a reference reaching *through* it is caught as well, while a sibling -its format never renders is free — that sibling is no part of the value. +error, with no path token anywhere. A calc renders its operand whole, so what the +hold pins is that one field — another route conflicts only by naming it. Two +names that merely draw from one source are two draws, as `{word} {word}` is, so +two dice over one `{..die}` are fine. ```text token {p} renders a level that {p.first} reads a path into; name the fields you want instead diff --git a/builtins.go b/builtins.go index 943fcd3..4249a32 100644 --- a/builtins.go +++ b/builtins.go @@ -48,9 +48,7 @@ var builtins = map[string]builtin{ return func(s *session, _ string, _ []string) string { return strconv.Itoa(lo + s.IntN(span)) } }}, "float": {arity: 3, check: floatArgs, prep: func(a []string) callFn { - lo, _ := strconv.ParseFloat(a[0], 64) - hi, _ := strconv.ParseFloat(a[1], 64) - dp := atoi(a[2]) + lo, hi, dp := atof(a[0]), atof(a[1]), atoi(a[2]) return func(s *session, _ string, _ []string) string { return strconv.FormatFloat(lo+s.Float64()*(hi-lo), 'f', dp, 64) } @@ -104,6 +102,15 @@ func atoi(s string) int { return n } +// atof is atoi for a float arg, and reports an unvalidated one the same way. +func atof(s string) float64 { + f, err := strconv.ParseFloat(s, 64) + if err != nil { + panic(fmt.Sprintf("fakes: builtin arg %q reached prep unvalidated: %v", s, err)) + } + return f +} + func randBytes(r rng, n int) []byte { b := make([]byte, n) for i := range b { diff --git a/render.go b/render.go index e5b2952..ee60f9c 100644 --- a/render.go +++ b/render.go @@ -156,7 +156,8 @@ func expand(s *session, t *template) string { case 'f': b.WriteString(readField(s, t, held, o.arms[s.IntN(len(o.arms))])) case 'b': - // Read in the order calcVars fixed, which is what op.operands holds. + // Read before the call, so the value a calc computes is the value the + // format showed. calcVars fixed the order op.operands holds. var operands []string if len(o.operands) > 0 { operands = make([]string, len(o.operands))