feat(parser): split plain statements on semicolons in the raw parse
Test / test (push) Failing after 2m30s
Test / test (push) Failing after 2m30s
Assisted-by: GLM 5.3 Flash
This commit is contained in:
@@ -182,6 +182,19 @@ func TestNulIsIllegal(t *testing.T) {
|
|||||||
eq(t, texts("MOVQ \x00 AX"), []string{"MOVQ", "\x00", "AX"})
|
eq(t, texts("MOVQ \x00 AX"), []string{"MOVQ", "\x00", "AX"})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDivisionSlashInIdentifiers(t *testing.T) {
|
||||||
|
// U+2215 DIVISION SLASH is an identifier character, the way the
|
||||||
|
// toolchain's tokenizer treats it: the package path of a symbol is
|
||||||
|
// written with it (internal∕runtime∕atomic·Xchg) and must lex as one
|
||||||
|
// name. The ordinary slash (U+002F) stays punctuation.
|
||||||
|
eq(t, texts("CALL internal∕runtime∕atomic·Xchg(SB)"),
|
||||||
|
[]string{"CALL", "internal∕runtime∕atomic·Xchg", "(", "SB", ")"})
|
||||||
|
eq(t, texts("MOVQ sync∕atomic·Align(SB), AX"),
|
||||||
|
[]string{"MOVQ", "sync∕atomic·Align", "(", "SB", ")", ",", "AX"})
|
||||||
|
// It may also begin a name, like any letter of the toolchain's rule.
|
||||||
|
eq(t, kinds("∕x"), []token.Kind{token.Ident})
|
||||||
|
}
|
||||||
|
|
||||||
// TestOffsetsAroundInvalidByte pins Position.Offset against the original
|
// TestOffsetsAroundInvalidByte pins Position.Offset against the original
|
||||||
// bytes: an invalid UTF-8 byte decodes to RuneError but advances the offset
|
// bytes: an invalid UTF-8 byte decodes to RuneError but advances the offset
|
||||||
// table by exactly one byte, so every later position stays a true byte
|
// table by exactly one byte, so every later position stays a true byte
|
||||||
|
|||||||
@@ -401,3 +401,99 @@ func TestInt64MinimumImmediate(t *testing.T) {
|
|||||||
t.Errorf("imm.Float = %q, want empty", imm.Float)
|
t.Errorf("imm.Float = %q, want empty", imm.Float)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestDivisionSlashPackagePath covers the runtime's package-path spelling:
|
||||||
|
// U+2215 DIVISION SLASH separates the elements of an import path inside a
|
||||||
|
// symbol (internal∕runtime∕atomic·Xchg), and the middle dot still separates
|
||||||
|
// the package from the name. The whole spelling must reach the symbol, not
|
||||||
|
// stop at the first slash.
|
||||||
|
func TestDivisionSlashPackagePath(t *testing.T) {
|
||||||
|
file, errs := Parse("t.s", "TEXT \u00b7f(SB), $0\n\tCALL internal∕runtime∕atomic·Xchg(SB)\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)
|
||||||
|
sym := instr.Operands[0].Addr.Sym
|
||||||
|
if sym == nil {
|
||||||
|
t.Fatal("operand carries no symbol")
|
||||||
|
}
|
||||||
|
if sym.Pkg != "internal∕runtime∕atomic" {
|
||||||
|
t.Errorf("pkg = %q, want internal∕runtime∕atomic", sym.Pkg)
|
||||||
|
}
|
||||||
|
if sym.Name != "Xchg" {
|
||||||
|
t.Errorf("name = %q, want Xchg", sym.Name)
|
||||||
|
}
|
||||||
|
if sym.Raw != "internal∕runtime∕atomic·Xchg(SB)" {
|
||||||
|
t.Errorf("raw = %q", sym.Raw)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSemicolonStatements covers the plain parse path: ';' separates
|
||||||
|
// statements on one line exactly as it does inside macro expansion, and a
|
||||||
|
// ';' inside a comment is comment text.
|
||||||
|
func TestSemicolonStatements(t *testing.T) {
|
||||||
|
file, errs := Parse("t.s", "TEXT \u00b7f(SB), $0\n\tROLQ $3, DI; ROLQ $13, DI\n\tMOVQ AX, BX // note; still comment\n\tRET\n")
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("parse errors: %v", errs)
|
||||||
|
}
|
||||||
|
txt := file.Decls[0].(*ast.Text)
|
||||||
|
if len(txt.Body) != 4 {
|
||||||
|
t.Fatalf("body = %d statements, want 4", len(txt.Body))
|
||||||
|
}
|
||||||
|
first := txt.Body[0].(*ast.Instr)
|
||||||
|
if first.Mnemonic.Text != "ROLQ" || len(first.Operands) != 2 {
|
||||||
|
t.Errorf("first statement = %+v, want ROLQ with two operands", first.Mnemonic)
|
||||||
|
}
|
||||||
|
second := txt.Body[1].(*ast.Instr)
|
||||||
|
if second.Mnemonic.Text != "ROLQ" || len(second.Operands) != 2 {
|
||||||
|
t.Errorf("second statement = %s, want ROLQ with two operands", second.Mnemonic.Text)
|
||||||
|
}
|
||||||
|
// The trailing comment belongs to the second MOVQ, semicolon included.
|
||||||
|
third := txt.Body[2].(*ast.Instr)
|
||||||
|
if third.Mnemonic.Text != "MOVQ" || third.Comment != "note; still comment" {
|
||||||
|
t.Errorf("third = %s, comment %q", third.Mnemonic.Text, third.Comment)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSemicolonAfterLabel covers a label sharing its line with two
|
||||||
|
// statements.
|
||||||
|
func TestSemicolonAfterLabel(t *testing.T) {
|
||||||
|
file, errs := Parse("t.s", "TEXT \u00b7f(SB), $0\nloop: NOP; NOP\n\tRET\n")
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("parse errors: %v", errs)
|
||||||
|
}
|
||||||
|
txt := file.Decls[0].(*ast.Text)
|
||||||
|
if len(txt.Body) != 4 {
|
||||||
|
t.Fatalf("body = %d statements, want 4 (label, two instructions, RET)", len(txt.Body))
|
||||||
|
}
|
||||||
|
if _, ok := txt.Body[0].(*ast.Label); !ok {
|
||||||
|
t.Errorf("first statement = %T, want *ast.Label", txt.Body[0])
|
||||||
|
}
|
||||||
|
for i, want := range []string{"NOP", "NOP", "RET"} {
|
||||||
|
in, ok := txt.Body[i+1].(*ast.Instr)
|
||||||
|
if !ok || in.Mnemonic.Text != want {
|
||||||
|
t.Errorf("statement %d = %v, want %s", i+1, txt.Body[i+1], want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestParseEqualsZeroOptions pins the contract that ParseWithOptions with
|
||||||
|
// the zero Options reproduces Parse, here for the semicolon split.
|
||||||
|
func TestParseEqualsZeroOptions(t *testing.T) {
|
||||||
|
src := "TEXT \u00b7f(SB), $0\n\tNOP; NOP\n\tRET\n"
|
||||||
|
a, errsA := Parse("t.s", src)
|
||||||
|
b, errsB := ParseWithOptions("t.s", src, Options{})
|
||||||
|
if len(errsA) > 0 || len(errsB) > 0 {
|
||||||
|
t.Fatalf("errors: %v / %v", errsA, errsB)
|
||||||
|
}
|
||||||
|
ta, tb := texts(a), texts(b)
|
||||||
|
if len(ta) != len(tb) {
|
||||||
|
t.Fatalf("decl counts differ: %d vs %d", len(ta), len(tb))
|
||||||
|
}
|
||||||
|
for i := range ta {
|
||||||
|
if len(ta[i].Body) != len(tb[i].Body) {
|
||||||
|
t.Fatalf("TEXT %d: body lengths differ: %d vs %d", i, len(ta[i].Body), len(tb[i].Body))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+1
-1
@@ -47,7 +47,7 @@ func ParseWithOptions(path, src string, opts Options) (*ast.File, []error) {
|
|||||||
lines = pp.fileLines(path, tokens, token.Position{})
|
lines = pp.fileLines(path, tokens, token.Position{})
|
||||||
errs = pp.errs
|
errs = pp.errs
|
||||||
} else {
|
} else {
|
||||||
lines = splitLines(tokens)
|
lines = statementLines(tokens)
|
||||||
}
|
}
|
||||||
p := &state{path: path}
|
p := &state{path: path}
|
||||||
p.parse(lines)
|
p.parse(lines)
|
||||||
|
|||||||
@@ -462,7 +462,9 @@ TEXT ·f(SB), NOSPLIT, $0
|
|||||||
|
|
||||||
func TestParseUnchangedWithoutExpand(t *testing.T) {
|
func TestParseUnchangedWithoutExpand(t *testing.T) {
|
||||||
// Without Expand the preprocessor must not exist: a macro invocation
|
// Without Expand the preprocessor must not exist: a macro invocation
|
||||||
// stays an unexpanded instruction line and ';' keeps the old parse.
|
// stays an unexpanded instruction line. The ';' statement separator is
|
||||||
|
// not part of the preprocessor: the plain parse path splits on it the
|
||||||
|
// same way the expansion path does, so both spellings agree.
|
||||||
f, errs := Parse("t_amd64.s", `
|
f, errs := Parse("t_amd64.s", `
|
||||||
#define TWICE ADDQ AX, AX
|
#define TWICE ADDQ AX, AX
|
||||||
TEXT ·f(SB), NOSPLIT, $0
|
TEXT ·f(SB), NOSPLIT, $0
|
||||||
@@ -480,7 +482,7 @@ TEXT ·f(SB), NOSPLIT, $0
|
|||||||
mnemonics = append(mnemonics, in.Mnemonic.Text)
|
mnemonics = append(mnemonics, in.Mnemonic.Text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if strings.Join(mnemonics, " ") != "TWICE BYTE RET" {
|
if strings.Join(mnemonics, " ") != "TWICE BYTE BYTE RET" {
|
||||||
t.Errorf("non-expanding parse changed: %v", mnemonics)
|
t.Errorf("non-expanding parse changed: %v", mnemonics)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user