{"record":{"id":"c93a6c22aaa9ce3d","repo":"gastownhall/beads","slug":"expected-closing-parenthesis","errorCode":null,"errorMessage":"expected closing parenthesis","messagePattern":"expected closing parenthesis","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/formula/range.go","lineNumber":338,"sourceCode":"\t}\n\treturn p.parsePrimary()\n}\n\n// parsePrimary handles numbers and parentheses\nfunc (p *exprParser) parsePrimary() (float64, error) {\n\tswitch p.current().typ {\n\tcase tokNumber:\n\t\tval := p.current().val\n\t\tp.advance()\n\t\treturn val, nil\n\tcase tokLParen:\n\t\tp.advance()\n\t\tval, err := p.parseAddSub()\n\t\tif err != nil {\n\t\t\treturn 0, err\n\t\t}\n\t\tif p.current().typ != tokRParen {\n\t\t\treturn 0, fmt.Errorf(\"expected closing parenthesis\")\n\t\t}\n\t\tp.advance()\n\t\treturn val, nil\n\tdefault:\n\t\treturn 0, fmt.Errorf(\"unexpected token in expression\")\n\t}\n}\n\n// ValidateRange validates a range expression without evaluating it.\n// Useful for syntax checking during formula validation.\nfunc ValidateRange(expr string) error {\n\texpr = strings.TrimSpace(expr)\n\tif expr == \"\" {\n\t\treturn fmt.Errorf(\"empty range expression\")\n\t}\n\n\tm := rangePattern.FindStringSubmatch(expr)\n\tif m == nil {","sourceCodeStart":320,"sourceCodeEnd":356,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/formula/range.go#L320-L356","documentation":"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.","triggerScenarios":"EvaluateExpr with an unclosed parenthesis, e.g. 'range 1..(2+3' — after parsing '2+3' the parser sees EOF instead of ')'.","commonSituations":"Hand-edited formulas losing a ')', template expansion dropping characters, nested parens miscounted in generated expressions.","solutions":["Balance the parentheses in the expression (add the missing ')')","Count opening/closing parens programmatically before evaluation","Syntax-check with ValidateRange first"],"exampleFix":"// before\nrange 1..(2+3 { ... }\n// after\nrange 1..(2+3) { ... }","handlingStrategy":"validation","validationCode":"func parensBalanced(s string) bool {\n    n := 0\n    for _, r := range s {\n        if r == '(' { n++ }\n        if r == ')' { n--; if n < 0 { return false } }\n    }\n    return n == 0\n}\n// call before EvaluateExpr","typeGuard":null,"tryCatchPattern":"val, err := EvaluateExpr(expr, vars)\nif err != nil && strings.Contains(err.Error(), \"expected closing parenthesis\") {\n    // surface a friendly 'unbalanced parentheses' message\n}","preventionTips":["Check parenthesis balance before evaluation","When generating expressions, emit parens as matched pairs","Run ValidateRange as a pre-flight check"],"tags":["formula","parser","syntax","parentheses"],"backgroundTag":"unbalanced-parentheses","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}