147 lines
3.6 KiB
Go
147 lines
3.6 KiB
Go
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (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-devkit/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
|
|
}
|