// Copyright (c) 2026 Petr BalvĂ­n (https://petrbalvin.org) // SPDX-License-Identifier: BSD-3-Clause // Constant-expression folding for operands. The toolchain's assembler // evaluates arithmetic in every operand position, and macro-heavy GOROOT // sources lean on it: parameterised bodies carry offsets like // ((index*4)+0)(base), immediates like $(32-shift) and masks like // $~63 or $(1<<0|1<<9). Substituting the parameters textually therefore // leaves constant arithmetic behind, and the parser folds it here, keeping // the operand AST identical to what the same literals written out would // produce. Anything that is not a closed integer expression fails to fold // and falls through to the ordinary operand paths. package parser import ( "sourcedock.dev/petrbalvin/gasm-sdk/token" ) // foldExpr evaluates the constant integer expression at the head of ts and // returns its value together with the unconsumed tokens. ok is false when // the tokens do not form an expression, which is the callers' signal to use // the ordinary parsing paths. func foldExpr(ts []token.Token) (val int64, rest []token.Token, ok bool) { v, rest, ok := foldAdd(ts) if !ok { return 0, ts, false } return v, rest, true } // foldAdd parses addition-level expressions: +, - and | bind loosest, the // Plan 9 convention that makes x<<1|3 read as (x<<1)|3. func foldAdd(ts []token.Token) (int64, []token.Token, bool) { v, rest, ok := foldMul(ts) if !ok { return 0, ts, false } for len(rest) > 0 { kind := rest[0].Kind if kind != token.Plus && kind != token.Minus && kind != token.Pipe { return v, rest, true } w, r2, ok := foldMul(rest[1:]) if !ok { return v, rest, true } switch kind { case token.Plus: v += w case token.Minus: v -= w case token.Pipe: v |= w } rest = r2 } return v, rest, true } // foldMul parses multiplication-level expressions: *, / and the bit // operators &, << and >>. func foldMul(ts []token.Token) (int64, []token.Token, bool) { v, rest, ok := foldFactor(ts) if !ok { return 0, ts, false } for len(rest) > 0 { switch rest[0].Kind { case token.Star: w, r2, ok := foldFactor(rest[1:]) if !ok { return v, rest, true } v *= w rest = r2 case token.Slash: w, r2, ok := foldFactor(rest[1:]) if !ok || w == 0 { return v, rest, true } v /= w rest = r2 case token.Ampersand: w, r2, ok := foldFactor(rest[1:]) if !ok { return v, rest, true } v &= w rest = r2 case token.LShift: w, r2, ok := foldFactor(rest[1:]) if !ok || w < 0 || w >= 64 { return v, rest, true } v <<= uint(w) rest = r2 case token.RShift: w, r2, ok := foldFactor(rest[1:]) if !ok || w < 0 || w >= 64 { return v, rest, true } v >>= uint(w) rest = r2 default: return v, rest, true } } return v, rest, true } // foldFactor parses a number, a parenthesised expression, or a unary sign // or complement. func foldFactor(ts []token.Token) (int64, []token.Token, bool) { if len(ts) == 0 { return 0, ts, false } switch ts[0].Kind { case token.Number: v, ok := tryInt(ts[0].Text) if !ok { return 0, ts, false } return v, ts[1:], true case token.LParen: v, rest, ok := foldAdd(ts[1:]) if !ok || len(rest) == 0 || rest[0].Kind != token.RParen { return 0, ts, false } return v, rest[1:], true case token.Minus: v, rest, ok := foldFactor(ts[1:]) if !ok { return 0, ts, false } return -v, rest, true case token.Plus: return foldFactor(ts[1:]) case token.Tilde: v, rest, ok := foldFactor(ts[1:]) if !ok { return 0, ts, false } return ^v, rest, true } return 0, ts, false }