gastownhall/beads · error

invalid number %q

Error message

invalid number %q

What it means

tokenize() scans a numeric literal in a range expression and hands the slice to strconv.ParseFloat. If the slice cannot be parsed as a float64 (e.g. multiple dots like '1.2.3'), the tokenizer fails with this error. It is the low-level lexical validation of number literals in range start/end expressions.

Source

Thrown at internal/formula/range.go:149

	for i < len(expr) {
		ch := expr[i]

		// Skip whitespace
		if unicode.IsSpace(rune(ch)) {
			i++
			continue
		}

		// Number
		if unicode.IsDigit(rune(ch)) {
			j := i
			for j < len(expr) && (unicode.IsDigit(rune(expr[j])) || expr[j] == '.') {
				j++
			}
			val, err := strconv.ParseFloat(expr[i:j], 64)
			if err != nil {
				return nil, fmt.Errorf("invalid number %q", expr[i:j])
			}
			tokens = append(tokens, token{tokNumber, val})
			i = j
			continue
		}

		// Operators
		switch ch {
		case '+':
			tokens = append(tokens, token{tokPlus, 0})
		case '-':
			// Could be unary minus or subtraction
			// If previous token is not a number or right paren, it's unary
			if len(tokens) == 0 || (tokens[len(tokens)-1].typ != tokNumber && tokens[len(tokens)-1].typ != tokRParen) {
				// Unary minus: parse the number with the minus
				j := i + 1
				for j < len(expr) && (unicode.IsDigit(rune(expr[j])) || expr[j] == '.') {
					j++

View on GitHub (pinned to 71377f2769)

Solutions

  1. Fix the numeric literal so it has at most one decimal point (e.g. '1.2..10')
  2. Run ValidateRange on the expression before evaluation to get the same diagnostic early
  3. If the value comes from a variable, check the substituted value is a plain number

Example fix

// before
range 1.2.3..10 { ... }
// after
range 1.23..10 { ... }
Defensive patterns

Strategy: validation

Validate before calling

re := /^[0-9]+(\.[0-9]+)?$/
for _, part := range strings.Split(expr, "..") {
    // after variable substitution, each numeric literal should match
    _ = re
}
ValidateRange(expr) // run before EvaluateExpr to surface this early

Try / catch

tokens, err := tokenize(expr)
if err != nil && strings.Contains(err.Error(), "invalid number") {
    // fall back to a default range or report a user-facing config error
}

Prevention

When it happens

Trigger: Calling EvaluateExpr or ValidateRange with an expression containing a malformed numeric literal that the digit/dot scan captures, such as '1.2.3..10' — the scan grabs '1.2.3' (digits and dots) and ParseFloat rejects it.

Common situations: Typos in loop specs like '..' inside a number, pasted expressions with thousands separators ('1,000' won't scan here but '1.0.0' will), or template placeholders that expand into malformed numbers.

Related errors


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/8bbf561eb7f55bea. Report an issue: GitHub.