(tokens: Token[])
| 22 | * precedence) parser. |
| 23 | */ |
| 24 | export function parseExpression(tokens: Token[]): ASTNode { |
| 25 | let pos = 0; |
| 26 | |
| 27 | const cur = (): Token => tokens[pos] ?? { t: T.EOF }; |
| 28 | const adv = (): Token => { |
| 29 | const tok = cur(); |
| 30 | pos++; |
| 31 | return tok; |
| 32 | }; |
| 33 | const eat = (kind: T): void => { |
| 34 | if (cur().t === kind) pos++; |
| 35 | }; |
| 36 | |
| 37 | // ── Infix precedence lookup ───────────────────────────────────────────── |
| 38 | function getInfixPrec(tok: Token): number { |
| 39 | switch (tok.t) { |
| 40 | case T.Question: |
| 41 | return PREC_TERNARY; |
| 42 | case T.Or: |
| 43 | return PREC_OR; |
| 44 | case T.And: |
| 45 | return PREC_AND; |
| 46 | case T.EqEq: |
| 47 | case T.NotEq: |
| 48 | return PREC_EQ; |
| 49 | case T.Greater: |
| 50 | case T.Less: |
| 51 | case T.GreaterEq: |
| 52 | case T.LessEq: |
| 53 | return PREC_CMP; |
| 54 | case T.Plus: |
| 55 | case T.Minus: |
| 56 | return PREC_ADD; |
| 57 | case T.Star: |
| 58 | case T.Slash: |
| 59 | case T.Percent: |
| 60 | return PREC_MUL; |
| 61 | case T.Dot: |
| 62 | case T.LBrack: |
| 63 | return PREC_MEMBER; |
| 64 | default: |
| 65 | return 0; |
| 66 | } |
| 67 | } |
| 68 | |
| 69 | // ── Main Pratt loop ───────────────────────────────────────────────────── |
| 70 | function parseExpr(minPrec: number = 0): ASTNode { |
| 71 | let left = parsePrefix(); |
| 72 | while (getInfixPrec(cur()) > minPrec) { |
| 73 | left = parseInfix(left); |
| 74 | } |
| 75 | return left; |
| 76 | } |
| 77 | |
| 78 | // ── Prefix / atoms ───────────────────────────────────────────────────── |
| 79 | function parsePrefix(): ASTNode { |
| 80 | const tok = cur(); |
| 81 |
no test coverage detected