gastownhall/beads · error

expected closing parenthesis

Error message

expected closing parenthesis

What it means

parsePrimary handles parenthesized sub-expressions: after parsing the inner expression it requires a ')' token. If the next token is not a closing parenthesis, the parentheses are unbalanced or malformed and this error is thrown.

Source

Thrown at internal/formula/range.go:338

	}
	return p.parsePrimary()
}

// parsePrimary handles numbers and parentheses
func (p *exprParser) parsePrimary() (float64, error) {
	switch p.current().typ {
	case tokNumber:
		val := p.current().val
		p.advance()
		return val, nil
	case tokLParen:
		p.advance()
		val, err := p.parseAddSub()
		if err != nil {
			return 0, err
		}
		if p.current().typ != tokRParen {
			return 0, fmt.Errorf("expected closing parenthesis")
		}
		p.advance()
		return val, nil
	default:
		return 0, fmt.Errorf("unexpected token in expression")
	}
}

// ValidateRange validates a range expression without evaluating it.
// Useful for syntax checking during formula validation.
func ValidateRange(expr string) error {
	expr = strings.TrimSpace(expr)
	if expr == "" {
		return fmt.Errorf("empty range expression")
	}

	m := rangePattern.FindStringSubmatch(expr)
	if m == nil {

View on GitHub (pinned to 71377f2769)

Solutions

  1. Balance the parentheses in the expression (add the missing ')')
  2. Count opening/closing parens programmatically before evaluation
  3. Syntax-check with ValidateRange first

Example fix

// before
range 1..(2+3 { ... }
// after
range 1..(2+3) { ... }
Defensive patterns

Strategy: validation

Validate before calling

func parensBalanced(s string) bool {
    n := 0
    for _, r := range s {
        if r == '(' { n++ }
        if r == ')' { n--; if n < 0 { return false } }
    }
    return n == 0
}
// call before EvaluateExpr

Try / catch

val, err := EvaluateExpr(expr, vars)
if err != nil && strings.Contains(err.Error(), "expected closing parenthesis") {
    // surface a friendly 'unbalanced parentheses' message
}

Prevention

When it happens

Trigger: EvaluateExpr with an unclosed parenthesis, e.g. 'range 1..(2+3' — after parsing '2+3' the parser sees EOF instead of ')'.

Common situations: Hand-edited formulas losing a ')', template expansion dropping characters, nested parens miscounted in generated expressions.

Related errors


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