fix(format): keep a macro-named label line whole

A label whose name is a macro expands into whatever the body is, so the
line's statement structure exists only after expansion; splitting the
label off changed clean input into a different statement sequence.  The
formatter now records the names #define'd above each line and renders
such a label line unsplit, and the crashing input joins the corpus.

Assisted-by: GLM 5.3
This commit is contained in:
petrbalvin committed 2026-10-07 13:54:42 +02:00
1 parent 715298ed23
commit f452b8a995
3 files changed
+53 -4

No files matched your search

+31 -4
View File
@@ -24,14 +24,16 @@ func Source(src string) string {
// index of the TEXT function it belongs to, so that mnemonic widths can be
// aligned per function.
type info struct {
kind int
mnemLen int
funcID int
kind int
mnemLen int
funcID int
macroName bool // the line's label names a macro defined above it
}
infos := make([]info, len(lines))
funcID := -1
maxWidth := map[int]int{} // funcID -> widest mnemonic
maxWidth := map[int]int{} // funcID -> widest mnemonic
macros := map[string]bool{} // names #define'd so far, in file order
for i, line := range lines {
inf := info{kind: kBlank, funcID: funcID}
if len(line) > 0 {
@@ -45,6 +47,10 @@ func Source(src string) string {
inf.kind = kComment
case line[0].Kind == token.Hash:
inf.kind = kPreproc
if len(line) >= 3 && line[1].Kind == token.Ident &&
line[1].Text == "define" && line[2].Kind == token.Ident {
macros[line[2].Text] = true
}
case line[0].Kind == token.Ident && isDirective(line[0].Text):
inf.kind = kDirective
if line[0].Text == "TEXT" {
@@ -56,6 +62,14 @@ func Source(src string) string {
}
case len(line) >= 2 && line[1].Kind == token.Colon:
inf.kind = kLabel
if macros[line[0].Text] {
// The label names a macro, so what the line becomes
// exists only after expansion; the line must not be
// restructured, and no mnemonic of it aligns.
inf.macroName = true
infos[i] = inf
continue
}
// Peel stacked labels exactly as the render pass does; the
// instruction after the last one is rendered at the
// function's alignment width, so its mnemonic counts here.
@@ -115,6 +129,19 @@ func Source(src string) string {
}
inBody = line[0].Text == "TEXT"
case kLabel:
if inf.macroName && len(line) > 2 {
// A label that names a macro expands into whatever the body
// is: after expansion the line may not be a label at all, so
// its statement structure exists only as one line. The label
// keeps the canonical colon and everything behind it renders
// as one unaligned run, unsplit.
out := line[0].Text + ":"
if rest := renderOps(line[2:]); rest != "" {
out += " " + rest
}
outs = append(outs, outLine{kind: kLabel, text: trimLineRight(out, last)})
continue
}
// Every label, and a trailing instruction, becomes its own
// output line: separate outLines keep the blank-line pass
// honest about what it is looking at.