{"record":{"id":"ab37da2ef70bde35","repo":"gastownhall/beads","slug":"unexpected-token-after-expression","errorCode":null,"errorMessage":"unexpected token after expression","messagePattern":"unexpected token after expression","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/formula/range.go","lineNumber":226,"sourceCode":"\t\treturn token{tokEOF, 0}\n\t}\n\treturn p.tokens[p.pos]\n}\n\nfunc (p *exprParser) advance() {\n\tp.pos++\n}\n\n// parseExpr parses an expression using recursive descent.\n// Handles operator precedence: + - < * / < ^\nfunc parseExpr(tokens []token) (float64, error) {\n\tp := &exprParser{tokens: tokens}\n\tresult, err := p.parseAddSub()\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tif p.current().typ != tokEOF {\n\t\treturn 0, fmt.Errorf(\"unexpected token after expression\")\n\t}\n\treturn result, nil\n}\n\n// parseAddSub handles + and - (lowest precedence)\nfunc (p *exprParser) parseAddSub() (float64, error) {\n\tleft, err := p.parseMulDiv()\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\tfor {\n\t\tswitch p.current().typ {\n\t\tcase tokPlus:\n\t\t\tp.advance()\n\t\t\tright, err := p.parseMulDiv()\n\t\t\tif err != nil {\n\t\t\t\treturn 0, err","sourceCodeStart":208,"sourceCodeEnd":244,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/formula/range.go#L208-L244","documentation":"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.","triggerScenarios":"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.","commonSituations":"Missing operators between operands ('2 3' instead of '2*3'), duplicated expressions from templating bugs, or accidental concatenation when building expressions programmatically.","solutions":["Fix the expression so there is an operator between every pair of operands","Remove trailing garbage after the expression","Test the expression with ValidateRange before evaluation"],"exampleFix":"// before\nrange 1..(10)5 { ... }\n// after\nrange 1..(10)*5 { ... }","handlingStrategy":"validation","validationCode":"balanced := 0\nfor _, r := range expr { if r=='(' {balanced++}; if r==')' {balanced--} }\n// also ensure no two operands are separated only by whitespace:\no predatory := regexp.MustCompile(`\\d\\s+\\d`)\nif balanced != 0 || operandGap.MatchString(expr) { /* reject */ }","typeGuard":null,"tryCatchPattern":"val, err := EvaluateExpr(expr, vars)\nif err != nil && strings.Contains(err.Error(), \"unexpected token after expression\") {\n    // report malformed expression; do not retry\n}","preventionTips":["Insert explicit operators when concatenating expression fragments","Validate ranges with ValidateRange before EvaluateExpr","Avoid templating that can emit two adjacent values without an operator"],"tags":["formula","parser","syntax","parsing"],"backgroundTag":"unexpected-trailing-tokens","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}