// 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" ) // maxFoldDepth bounds the nesting the folder follows, counting parenthesis // groups and unary signs alike. Real expressions nest a handful deep, and // beyond the bound the expression simply fails to fold, which lands it in // the ordinary operand paths. Without a bound, a hostile file parks the // folder millions of frames down the mutual recursion of foldAdd, foldMul // and foldFactor, and the goroutine stack overflows with a fatal error no // caller can recover from. const maxFoldDepth = 1000 // 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, 0) 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, depth int) (int64, []token.Token, bool) { v, rest, ok := foldMul(ts, depth) 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:], depth) 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, depth int) (int64, []token.Token, bool) { v, rest, ok := foldFactor(ts, depth) if !ok { return 0, ts, false } for len(rest) > 0 { switch rest[0].Kind { case token.Star: w, r2, ok := foldFactor(rest[1:], depth) if !ok { return v, rest, true } v *= w rest = r2 case token.Slash: w, r2, ok := foldFactor(rest[1:], depth) if !ok || w == 0 { return v, rest, true } v /= w rest = r2 case token.Ampersand: w, r2, ok := foldFactor(rest[1:], depth) if !ok { return v, rest, true } v &= w rest = r2 case token.LShift: w, r2, ok := foldFactor(rest[1:], depth) if !ok || w < 0 || w >= 64 { return v, rest, true } v <<= uint(w) rest = r2 case token.RShift: w, r2, ok := foldFactor(rest[1:], depth) 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. Each parenthesis group and each unary operator costs one // depth level, so nesting beyond maxFoldDepth fails here instead of growing // the stack without end. func foldFactor(ts []token.Token, depth int) (int64, []token.Token, bool) { if len(ts) == 0 || depth > maxFoldDepth { 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:], depth+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:], depth+1) if !ok { return 0, ts, false } return -v, rest, true case token.Plus: return foldFactor(ts[1:], depth+1) case token.Tilde: v, rest, ok := foldFactor(ts[1:], depth+1) if !ok { return 0, ts, false } return ^v, rest, true } return 0, ts, false }