gastownhall/beads · error

unexpected token after expression

Error message

unexpected token after expression

What it means

After parsing the full additive expression, parseExpr (the entry point of the expression parser) checks that only the EOF token remains. If extra tokens follow a complete expression, the input is ambiguous/trailing garbage and parsing fails with this error. It guards EvaluateExpr against silently ignoring trailing input.

Source

Thrown at internal/formula/range.go:226

		return token{tokEOF, 0}
	}
	return p.tokens[p.pos]
}

func (p *exprParser) advance() {
	p.pos++
}

// parseExpr parses an expression using recursive descent.
// Handles operator precedence: + - < * / < ^
func parseExpr(tokens []token) (float64, error) {
	p := &exprParser{tokens: tokens}
	result, err := p.parseAddSub()
	if err != nil {
		return 0, err
	}
	if p.current().typ != tokEOF {
		return 0, fmt.Errorf("unexpected token after expression")
	}
	return result, nil
}

// parseAddSub handles + and - (lowest precedence)
func (p *exprParser) parseAddSub() (float64, error) {
	left, err := p.parseMulDiv()
	if err != nil {
		return 0, err
	}

	for {
		switch p.current().typ {
		case tokPlus:
			p.advance()
			right, err := p.parseMulDiv()
			if err != nil {
				return 0, err

View on GitHub (pinned to 71377f2769)

Solutions

  1. Fix the expression so there is an operator between every pair of operands
  2. Remove trailing garbage after the expression
  3. Test the expression with ValidateRange before evaluation

Example fix

// before
range 1..(10)5 { ... }
// after
range 1..(10)*5 { ... }
Defensive patterns

Strategy: validation

Validate before calling

balanced := 0
for _, r := range expr { if r=='(' {balanced++}; if r==')' {balanced--} }
// also ensure no two operands are separated only by whitespace:
o predatory := regexp.MustCompile(`\d\s+\d`)
if balanced != 0 || operandGap.MatchString(expr) { /* reject */ }

Try / catch

val, err := EvaluateExpr(expr, vars)
if err != nil && strings.Contains(err.Error(), "unexpected token after expression") {
    // report malformed expression; do not retry
}

Prevention

When it happens

Trigger: EvaluateExpr with an expression that has trailing tokens after a valid expression, e.g. '1 2', '3 + 4 5', or an unbalanced construct like '(1+2))' where the parser finishes before consuming everything.

Common situations: Missing operators between operands ('2 3' instead of '2*3'), duplicated expressions from templating bugs, or accidental concatenation when building expressions programmatically.

Understand the failure class

Related errors


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