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
+85
View File
@@ -4,7 +4,10 @@
package parser
import (
"math"
"os"
"slices"
"strings"
"testing"
"sourcedock.dev/petrbalvin/gasm-devkit/ast"
@@ -315,4 +318,86 @@ func TestSignedZeroFrame(t *testing.T) {
if txt.Args == nil || !txt.Args.Imm.HasVal || txt.Args.Imm.Val != 24 {
t.Errorf("args = %+v, want -24", txt.Args)
}
// The <ABIInternal> marker belongs to the symbol: the pseudo-register is
// consumed, the marker is recorded, and neither leaks into the flags.
if txt.Name.Pseudo != "SB" {
t.Errorf("pseudo = %q, want SB", txt.Name.Pseudo)
}
if txt.Name.ABI != "ABIInternal" {
t.Errorf("ABI = %q, want ABIInternal", txt.Name.ABI)
}
if !strings.Contains(txt.Name.Raw, "<ABIInternal>") {
t.Errorf("Raw = %q, want it to contain <ABIInternal>", txt.Name.Raw)
}
if want := []string{"NOSPLIT"}; !slices.Equal(txt.Flags, want) {
t.Errorf("flags = %v, want %v", txt.Flags, want)
}
}
// TestPipedFlags covers TEXT and GLOBL flag lists joined by '|': the bars are
// their own token kind, skipped by the flag loop, and only the identifiers
// are collected as flags.
func TestPipedFlags(t *testing.T) {
file, errs := Parse("t.s", "TEXT \u00b7f(SB), NOSPLIT|NOFRAME|DUPOK, $0\n\tRET\n")
if len(errs) > 0 {
t.Fatalf("parse errors: %v", errs)
}
txt := file.Decls[0].(*ast.Text)
if want := []string{"NOSPLIT", "NOFRAME", "DUPOK"}; !slices.Equal(txt.Flags, want) {
t.Errorf("flags = %v, want %v", txt.Flags, want)
}
g, errs := Parse("t.s", "GLOBL \u00b7mask(SB), RODATA|NOPTR, $8\n")
if len(errs) > 0 {
t.Fatalf("parse errors: %v", errs)
}
gl := g.Decls[0].(*ast.Globl)
if want := []string{"RODATA", "NOPTR"}; !slices.Equal(gl.Flags, want) {
t.Errorf("flags = %v, want %v", gl.Flags, want)
}
}
// TestTextMissingSymbolKeepsDecl covers a TEXT with no symbol at all: the
// decl must stay in the tree with a non-nil placeholder name, because the
// linter and the LSP dereference Name on every parsed TEXT.
func TestTextMissingSymbolKeepsDecl(t *testing.T) {
file, errs := Parse("t.s", "// func f(a int) int\nTEXT $0\n\tMOVQ AX, BX\n")
if len(errs) == 0 {
t.Fatal("expected a diagnostic for the missing symbol")
}
if file == nil || len(file.Decls) != 1 {
t.Fatalf("file = %v, want the TEXT decl kept", file)
}
txt := file.Decls[0].(*ast.Text)
if txt.Name == nil {
t.Fatal("Name must never be nil: downstream tools dereference it")
}
if txt.Name.Name == "" {
t.Error("placeholder name is empty")
}
if txt.Frame == nil || !txt.Frame.Imm.HasVal || txt.Frame.Imm.Val != 0 {
t.Errorf("frame = %+v, want $0", txt.Frame)
}
if len(txt.Body) != 1 {
t.Errorf("body = %d statements, want 1", len(txt.Body))
}
}
// TestInt64MinimumImmediate covers $-0x8000000000000000: the digits overflow
// int64 when parsed directly, but the negated magnitude is exactly the int64
// minimum and must land in Val rather than the float fallback.
func TestInt64MinimumImmediate(t *testing.T) {
file, errs := Parse("t.s", "TEXT \u00b7f(SB), $0\n\tMOVQ $-0x8000000000000000, AX\n\tRET\n")
if len(errs) > 0 {
t.Fatalf("parse errors: %v", errs)
}
txt := file.Decls[0].(*ast.Text)
instr := txt.Body[0].(*ast.Instr)
imm := instr.Operands[0].Imm
if !imm.HasVal || imm.Val != math.MinInt64 {
t.Errorf("imm = %+v, want Val = %d with HasVal set", imm, math.MinInt64)
}
if imm.Float != "" {
t.Errorf("imm.Float = %q, want empty", imm.Float)
}
}