fix(parser): parse ABI markers and keep TEXT decls usable on errors

Assisted-by: GLM 5.3
This commit is contained in:
2026-09-19 23:48:47 +02:00
parent ac1c05c793
commit 7aefe6a42d
3 changed files with 128 additions and 18 deletions
+41 -17
View File
@@ -9,6 +9,7 @@ package parser
import (
"fmt"
"math"
"strconv"
"strings"
@@ -75,7 +76,6 @@ func splitLines(tokens []token.Token) [][]token.Token {
func (p *state) parse(lines [][]token.Token) {
p.file = &ast.File{Path: p.path, Macros: map[string]bool{}}
for _, line := range lines {
line = trimSpace(line)
if len(line) == 0 {
// Blank line: a comment block ends here only if it was not
// directly preceding a declaration; keep pending doc intact
@@ -87,10 +87,6 @@ func (p *state) parse(lines [][]token.Token) {
}
}
// trimSpace is a no-op placeholder kept for symmetry; the lexer already drops
// horizontal whitespace, but this documents the intent.
func trimSpace(line []token.Token) []token.Token { return line }
func (p *state) parseLine(line []token.Token) {
first := line[0]
@@ -196,6 +192,10 @@ func (p *state) parseText(line []token.Token) {
sym, n := parseSymbolPrefix(rest)
if sym == nil {
p.errorf(line[0].Pos, "TEXT missing a symbol name")
// Keep the decl in the tree with a placeholder name: the linter and
// LSP dereference Name on every parsed TEXT, so the file must stay
// usable alongside its errors.
sym = &ast.Symbol{Pos: line[0].Pos, Raw: "?", Name: "?"}
}
text.Name = sym
rest = rest[n:]
@@ -206,7 +206,7 @@ func (p *state) parseText(line []token.Token) {
if rest[0].Kind == token.Ident {
text.Flags = append(text.Flags, rest[0].Text)
}
// Commas, '|' (Illegal) and anything else between flags is skipped.
// Commas, '|' and anything else between flags is skipped.
rest = rest[1:]
}
@@ -307,7 +307,9 @@ var pseudoRegs = map[string]bool{"FP": true, "SP": true, "SB": true, "PC": true}
// parseSymbolPrefix parses a leading symbol reference from g and returns it
// together with the number of tokens consumed. It returns (nil, 0) when no
// symbol is present.
// symbol is present. Raw is the verbatim spelling, with the bracket group,
// offset and pseudo-register glued to the name the way the assembler writes
// the reference.
func parseSymbolPrefix(g []token.Token) (*ast.Symbol, int) {
if len(g) == 0 || g[0].Kind != token.Ident {
return nil, 0
@@ -317,27 +319,44 @@ func parseSymbolPrefix(g []token.Token) (*ast.Symbol, int) {
setName(g[0].Text, sym)
i++
if i+1 < len(g) && g[i].Kind == token.LAngle && g[i+1].Kind == token.RAngle {
sym.Static = true
i += 2
var raw strings.Builder
raw.WriteString(g[0].Text)
// An optional bracket group after the name: <> marks a file-static symbol
// and <NAME> selects the ABI of the reference. The ABI form is the
// standard runtime spelling (TEXT ·foo<ABIInternal>(SB)) and must be
// consumed here, or it leaks into the directive's flag list.
if i < len(g) && g[i].Kind == token.LAngle {
switch {
case i+1 < len(g) && g[i+1].Kind == token.RAngle:
sym.Static = true
raw.WriteString("<>")
i += 2
case i+2 < len(g) && g[i+1].Kind == token.Ident && g[i+2].Kind == token.RAngle:
sym.ABI = g[i+1].Text
raw.WriteString("<" + sym.ABI + ">")
i += 3
}
}
if i < len(g) && (g[i].Kind == token.Plus || g[i].Kind == token.Minus) {
neg := g[i].Kind == token.Minus
raw.WriteString(g[i].Text)
i++
if i < len(g) && g[i].Kind == token.Number {
sym.Offset, sym.HasOff = parseInt(g[i].Text), true
if neg {
sym.Offset = -sym.Offset
}
raw.WriteString(g[i].Text)
i++
}
}
if i+2 < len(g) && g[i].Kind == token.LParen && g[i+1].Kind == token.Ident &&
pseudoRegs[g[i+1].Text] && g[i+2].Kind == token.RParen {
sym.Pseudo = g[i+1].Text
raw.WriteString("(" + sym.Pseudo + ")")
i += 3
}
sym.Raw = joinRaw(g[:i])
sym.Raw = raw.String()
return sym, i
}
@@ -384,9 +403,8 @@ func parseImmediate(g []token.Token) ast.Immediate {
}
// $sym(…) form.
if findPseudoParen(g) >= 0 || (g[0].Kind == token.Ident) {
if sym, n := parseSymbolPrefix(g); sym != nil && (sym.Pseudo != "" || sym.Static) {
if sym, n := parseSymbolPrefix(g); n > 0 && (sym.Pseudo != "" || sym.Static) {
imm.Sym = sym
_ = n
return imm
}
}
@@ -402,10 +420,16 @@ func parseImmediate(g []token.Token) ast.Immediate {
if v, ok := tryInt(text); ok {
imm.Val = v
imm.HasVal = true
} else if u, err := strconv.ParseUint(text, 0, 64); err == nil && !imm.Neg {
// Unsigned 64-bit literals (DATA mask<>+8(SB)/8, $0x8000…)
// overflow int64; keep the bit pattern.
imm.Val = int64(u)
} else if u, err := strconv.ParseUint(text, 0, 64); err == nil && (!imm.Neg || u == 1<<63) {
// Unsigned 64-bit literals (DATA mask<>+8(SB)/8, $0x8000…) can
// overflow int64; keep the bit pattern. A negated magnitude of
// exactly 1<<63 is the int64 minimum: ParseInt rejects it, but
// the value is representable, so Val carries it exactly.
if imm.Neg {
imm.Val = math.MinInt64
} else {
imm.Val = int64(u)
}
imm.HasVal = true
} else {
imm.Float = text