package commands import ( "strings" "testing" "c65gm/internal/compiler" "c65gm/internal/preproc" ) // setupPokeVars adds all needed symbols to a symbol table for POKE/POKEW tests. func setupPokeVars(st *compiler.SymbolTable) { st.AddConst("vic2", "", compiler.KindWord, 0xd000, preproc.Line{Filename: "test.c65", LineNo: 1}) st.AddVar("addrvar", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 2}) st.AddVar("valvar", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 3}) st.AddVar("wvalvar", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 4}) st.AddVar("waddrvar", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 5}) st.AddAbsolute("zpptr", "", compiler.KindWord, 0x80, preproc.Line{Filename: "test.c65", LineNo: 6}) st.AddVar("offsvar", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 7}) st.AddAbsolute("byaddr", "", compiler.KindByte, 0x40, preproc.Line{Filename: "test.c65", LineNo: 8}) } func newCtx() *compiler.CompilerContext { pragma := preproc.NewPragma() ctx := compiler.NewCompilerContext(pragma) setupPokeVars(ctx.SymbolTable) return ctx } func newLine(text string, pragma *preproc.Pragma) preproc.Line { return preproc.Line{ Text: text, Kind: preproc.Source, PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(), } } // ============================================================================= // POKE comma-spacing tests (constant address + literal value — Case 4) // ============================================================================= func TestPokeCommaSpacing(t *testing.T) { expectedAsm := []string{ "\tlda #5", "\tsta 53280", } tests := []struct { name string line string }{ {"space before and after comma", "POKE $d020 , 5"}, {"no space before or after comma", "POKE $d020,5"}, {"no space before comma, space after", "POKE $d020, 5"}, {"space before comma, no space after", "POKE $d020 ,5"}, {"no space comma, expression address", "POKE $d020+0, 5"}, {"WITH keyword", "POKE $d020 WITH 5"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ctx := newCtx() cmd := &PokeCommand{} line := newLine(tt.line, ctx.Pragma) if err := cmd.Interpret(line, ctx); err != nil { t.Fatalf("Interpret() error = %v", err) } asm, err := cmd.Generate(ctx) if err != nil { t.Fatalf("Generate() error = %v", err) } if !equalAsm(asm, expectedAsm) { t.Errorf("Generate() mismatch\ngot:\n%s\nwant:\n%s", strings.Join(asm, "\n"), strings.Join(expectedAsm, "\n")) } }) } } // ============================================================================= // POKEW comma-spacing tests (constant address + literal value — Case 4) // ============================================================================= func TestPokeWCommaSpacing(t *testing.T) { expectedAsm := []string{ "\tlda #$65", "\tsta 53280", "\tlda #$00", "\tsta 53281", } tests := []struct { name string line string }{ {"space before and after comma", "POKEW $d020 , 101"}, {"no space before or after comma", "POKEW $d020,101"}, {"no space before comma, space after", "POKEW $d020, 101"}, {"space before comma, no space after", "POKEW $d020 ,101"}, {"WITH keyword", "POKEW $d020 WITH 101"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ctx := newCtx() cmd := &PokeWCommand{} line := newLine(tt.line, ctx.Pragma) if err := cmd.Interpret(line, ctx); err != nil { t.Fatalf("Interpret() error = %v", err) } asm, err := cmd.Generate(ctx) if err != nil { t.Fatalf("Generate() error = %v", err) } if !equalAsm(asm, expectedAsm) { t.Errorf("Generate() mismatch\ngot:\n%s\nwant:\n%s", strings.Join(asm, "\n"), strings.Join(expectedAsm, "\n")) } }) } } // ============================================================================= // POKE Case 4 — Direct addressing (expression/constant address) // ============================================================================= func TestPokeDirectAddr(t *testing.T) { tests := []struct { name string line string wantAsm []string }{ { name: "expression constant + expression value, no-space comma", line: "POKE vic2+15,5+2", wantAsm: []string{ "\tlda #7", "\tsta 53263", }, }, { name: "variable value, no-space comma", line: "POKE $d020,valvar", wantAsm: []string{ "\tlda valvar", "\tsta 53280", }, }, { name: "variable value, space before comma", line: "POKE $d020 ,valvar", wantAsm: []string{ "\tlda valvar", "\tsta 53280", }, }, { name: "variable value, space after comma", line: "POKE $d020, valvar", wantAsm: []string{ "\tlda valvar", "\tsta 53280", }, }, { name: "expression address + var value, no-space comma", line: "POKE vic2+15,valvar", wantAsm: []string{ "\tlda valvar", "\tsta 53263", }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ctx := newCtx() cmd := &PokeCommand{} line := newLine(tt.line, ctx.Pragma) if err := cmd.Interpret(line, ctx); err != nil { t.Fatalf("Interpret() error = %v", err) } asm, err := cmd.Generate(ctx) if err != nil { t.Fatalf("Generate() error = %v", err) } if !equalAsm(asm, tt.wantAsm) { t.Errorf("Generate() mismatch\ngot:\n%s\nwant:\n%s", strings.Join(asm, "\n"), strings.Join(tt.wantAsm, "\n")) } }) } } // ============================================================================= // POKE Case 1 — ZP pointer (indexed indirect addressing) // ============================================================================= func TestPokeZPPointer(t *testing.T) { tests := []struct { name string line string wantAsm []string }{ { name: "no offset, literal value, no-space comma", line: "POKE zpptr,10", wantAsm: []string{ "\tldy #0", "\tlda #10", "\tsta (zpptr),y", }, }, { name: "no offset, var value, no-space comma", line: "POKE zpptr,valvar", wantAsm: []string{ "\tldy #0", "\tlda valvar", "\tsta (zpptr),y", }, }, { name: "literal offset, literal value, no-space comma", line: "POKE zpptr[5],10", wantAsm: []string{ "\tldy #5", "\tlda #10", "\tsta (zpptr),y", }, }, { name: "literal offset, var value, no-space comma", line: "POKE zpptr[5],valvar", wantAsm: []string{ "\tldy #5", "\tlda valvar", "\tsta (zpptr),y", }, }, { name: "var offset, literal value, no-space comma", line: "POKE zpptr[offsvar],10", wantAsm: []string{ "\tldy offsvar", "\tlda #10", "\tsta (zpptr),y", }, }, { name: "var offset, var value, no-space comma", line: "POKE zpptr[offsvar],valvar", wantAsm: []string{ "\tldy offsvar", "\tlda valvar", "\tsta (zpptr),y", }, }, { name: "literal offset, var value, spaced comma", line: "POKE zpptr[5] , valvar", wantAsm: []string{ "\tldy #5", "\tlda valvar", "\tsta (zpptr),y", }, }, { name: "word variable value (uses low byte), no-space comma", line: "POKE zpptr[5],wvalvar", wantAsm: []string{ "\tldy #5", "\tlda wvalvar", "\tsta (zpptr),y", }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ctx := newCtx() cmd := &PokeCommand{} line := newLine(tt.line, ctx.Pragma) if err := cmd.Interpret(line, ctx); err != nil { t.Fatalf("Interpret() error = %v", err) } asm, err := cmd.Generate(ctx) if err != nil { t.Fatalf("Generate() error = %v", err) } if !equalAsm(asm, tt.wantAsm) { t.Errorf("Generate() mismatch\ngot:\n%s\nwant:\n%s", strings.Join(asm, "\n"), strings.Join(tt.wantAsm, "\n")) } }) } } // ============================================================================= // POKE Case 2 — Byte variable address (self-modifying code) // ============================================================================= func TestPokeSMByteAddr(t *testing.T) { tests := []struct { name string line string wantAsm []string }{ { name: "literal value, no-space comma", line: "POKE addrvar,10", wantAsm: []string{ "\tlda addrvar", "\tsta _L1+1", "\tlda #10", "_L1", "\tsta $ff", }, }, { name: "var value, no-space comma", line: "POKE addrvar,valvar", wantAsm: []string{ "\tlda addrvar", "\tsta _L1+1", "\tlda valvar", "_L1", "\tsta $ff", }, }, { name: "word var value (uses low byte), no-space comma", line: "POKE addrvar,wvalvar", wantAsm: []string{ "\tlda addrvar", "\tsta _L1+1", "\tlda wvalvar", "_L1", "\tsta $ff", }, }, { name: "literal value, spaced comma", line: "POKE addrvar , 10", wantAsm: []string{ "\tlda addrvar", "\tsta _L1+1", "\tlda #10", "_L1", "\tsta $ff", }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ctx := newCtx() cmd := &PokeCommand{} line := newLine(tt.line, ctx.Pragma) if err := cmd.Interpret(line, ctx); err != nil { t.Fatalf("Interpret() error = %v", err) } asm, err := cmd.Generate(ctx) if err != nil { t.Fatalf("Generate() error = %v", err) } if !equalAsm(asm, tt.wantAsm) { t.Errorf("Generate() mismatch\ngot:\n%s\nwant:\n%s", strings.Join(asm, "\n"), strings.Join(tt.wantAsm, "\n")) } }) } } // ============================================================================= // POKE Case 3 — Word variable address (self-modifying code) // ============================================================================= func TestPokeSMWordAddr(t *testing.T) { tests := []struct { name string line string wantAsm []string }{ { name: "literal value, no-space comma", line: "POKE waddrvar,10", wantAsm: []string{ "\tlda waddrvar", "\tsta _L1+1", "\tlda waddrvar+1", "\tsta _L1+2", "\tlda #10", "_L1", "\tsta $ffff", }, }, { name: "var value, no-space comma", line: "POKE waddrvar,valvar", wantAsm: []string{ "\tlda waddrvar", "\tsta _L1+1", "\tlda waddrvar+1", "\tsta _L1+2", "\tlda valvar", "_L1", "\tsta $ffff", }, }, { name: "word var value (uses low byte), no-space comma", line: "POKE waddrvar,wvalvar", wantAsm: []string{ "\tlda waddrvar", "\tsta _L1+1", "\tlda waddrvar+1", "\tsta _L1+2", "\tlda wvalvar", "_L1", "\tsta $ffff", }, }, { name: "literal value, spaced comma", line: "POKE waddrvar , 10", wantAsm: []string{ "\tlda waddrvar", "\tsta _L1+1", "\tlda waddrvar+1", "\tsta _L1+2", "\tlda #10", "_L1", "\tsta $ffff", }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ctx := newCtx() cmd := &PokeCommand{} line := newLine(tt.line, ctx.Pragma) if err := cmd.Interpret(line, ctx); err != nil { t.Fatalf("Interpret() error = %v", err) } asm, err := cmd.Generate(ctx) if err != nil { t.Fatalf("Generate() error = %v", err) } if !equalAsm(asm, tt.wantAsm) { t.Errorf("Generate() mismatch\ngot:\n%s\nwant:\n%s", strings.Join(asm, "\n"), strings.Join(tt.wantAsm, "\n")) } }) } } // ============================================================================= // POKEW Case 4 — Direct addressing // ============================================================================= func TestPokeWDirectAddr(t *testing.T) { tests := []struct { name string line string wantAsm []string }{ { name: "literal value, no-space comma", line: "POKEW $d020,101", wantAsm: []string{ "\tlda #$65", "\tsta 53280", "\tlda #$00", "\tsta 53281", }, }, { name: "literal value >255, no-space comma", line: "POKEW $d020,$1234", wantAsm: []string{ "\tlda #$34", "\tsta 53280", "\tlda #$12", "\tsta 53281", }, }, { name: "word var value, no-space comma", line: "POKEW $d020,wvalvar", wantAsm: []string{ "\tlda wvalvar", "\tsta 53280", "\tlda wvalvar+1", "\tsta 53281", }, }, { name: "word var value, spaced comma", line: "POKEW $d020 , wvalvar", wantAsm: []string{ "\tlda wvalvar", "\tsta 53280", "\tlda wvalvar+1", "\tsta 53281", }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ctx := newCtx() cmd := &PokeWCommand{} line := newLine(tt.line, ctx.Pragma) if err := cmd.Interpret(line, ctx); err != nil { t.Fatalf("Interpret() error = %v", err) } asm, err := cmd.Generate(ctx) if err != nil { t.Fatalf("Generate() error = %v", err) } if !equalAsm(asm, tt.wantAsm) { t.Errorf("Generate() mismatch\ngot:\n%s\nwant:\n%s", strings.Join(asm, "\n"), strings.Join(tt.wantAsm, "\n")) } }) } } // ============================================================================= // POKEW Case 1 — ZP pointer (indexed indirect, two stores) // ============================================================================= func TestPokeWZPPointer(t *testing.T) { tests := []struct { name string line string wantAsm []string }{ { name: "no offset, literal value, no-space comma", line: "POKEW zpptr,101", wantAsm: []string{ "\tldy #0", "\tlda #$65", "\tsta (zpptr),y", "\tiny", "\tlda #$00", "\tsta (zpptr),y", }, }, { name: "no offset, word var value, no-space comma", line: "POKEW zpptr,wvalvar", wantAsm: []string{ "\tldy #0", "\tlda wvalvar", "\tsta (zpptr),y", "\tiny", "\tlda wvalvar+1", "\tsta (zpptr),y", }, }, { name: "literal offset, literal value, no-space comma", line: "POKEW zpptr[5],101", wantAsm: []string{ "\tldy #5", "\tlda #$65", "\tsta (zpptr),y", "\tiny", "\tlda #$00", "\tsta (zpptr),y", }, }, { name: "literal offset, word var value, no-space comma", line: "POKEW zpptr[5],wvalvar", wantAsm: []string{ "\tldy #5", "\tlda wvalvar", "\tsta (zpptr),y", "\tiny", "\tlda wvalvar+1", "\tsta (zpptr),y", }, }, { name: "literal offset, literal value, spaced comma", line: "POKEW zpptr[5] , 101", wantAsm: []string{ "\tldy #5", "\tlda #$65", "\tsta (zpptr),y", "\tiny", "\tlda #$00", "\tsta (zpptr),y", }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ctx := newCtx() cmd := &PokeWCommand{} line := newLine(tt.line, ctx.Pragma) if err := cmd.Interpret(line, ctx); err != nil { t.Fatalf("Interpret() error = %v", err) } asm, err := cmd.Generate(ctx) if err != nil { t.Fatalf("Generate() error = %v", err) } if !equalAsm(asm, tt.wantAsm) { t.Errorf("Generate() mismatch\ngot:\n%s\nwant:\n%s", strings.Join(asm, "\n"), strings.Join(tt.wantAsm, "\n")) } }) } } // ============================================================================= // POKEW Case 2 — Byte variable address (zero-page indexed) // ============================================================================= func TestPokeWByteAddr(t *testing.T) { tests := []struct { name string line string wantAsm []string }{ { name: "literal value, no-space comma", line: "POKEW byaddr,101", wantAsm: []string{ "\tldx byaddr", "\tlda #$65", "\tsta $00,x", "\tinx", "\tlda #$00", "\tsta $00,x", }, }, { name: "word var value, no-space comma", line: "POKEW byaddr,wvalvar", wantAsm: []string{ "\tldx byaddr", "\tlda wvalvar", "\tsta $00,x", "\tinx", "\tlda wvalvar+1", "\tsta $00,x", }, }, { name: "literal value, spaced comma", line: "POKEW byaddr , 101", wantAsm: []string{ "\tldx byaddr", "\tlda #$65", "\tsta $00,x", "\tinx", "\tlda #$00", "\tsta $00,x", }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ctx := newCtx() cmd := &PokeWCommand{} line := newLine(tt.line, ctx.Pragma) if err := cmd.Interpret(line, ctx); err != nil { t.Fatalf("Interpret() error = %v", err) } asm, err := cmd.Generate(ctx) if err != nil { t.Fatalf("Generate() error = %v", err) } if !equalAsm(asm, tt.wantAsm) { t.Errorf("Generate() mismatch\ngot:\n%s\nwant:\n%s", strings.Join(asm, "\n"), strings.Join(tt.wantAsm, "\n")) } }) } } // ============================================================================= // POKEW Case 3 — Word variable address (self-modifying code) // ============================================================================= func TestPokeWSMWordAddr(t *testing.T) { tests := []struct { name string line string wantAsm []string }{ { name: "literal value, no-space comma", line: "POKEW waddrvar,101", wantAsm: []string{ "\tlda waddrvar", "\tsta _L1+1", "\tsta _L2+1", "\tlda waddrvar+1", "\tsta _L1+2", "\tsta _L2+2", "\tlda #$65", "_L1", "\tsta $ffff", "\tinc _L2+1", "\tbne _L2", "\tinc _L2+2", "_L2", "\tlda #$00", "\tsta $ffff", }, }, { name: "word var value, no-space comma", line: "POKEW waddrvar,wvalvar", wantAsm: []string{ "\tlda waddrvar", "\tsta _L1+1", "\tsta _L2+1", "\tlda waddrvar+1", "\tsta _L1+2", "\tsta _L2+2", "\tlda wvalvar", "_L1", "\tsta $ffff", "\tinc _L2+1", "\tbne _L2", "\tinc _L2+2", "_L2", "\tlda wvalvar+1", "\tsta $ffff", }, }, { name: "literal value, spaced comma", line: "POKEW waddrvar , 101", wantAsm: []string{ "\tlda waddrvar", "\tsta _L1+1", "\tsta _L2+1", "\tlda waddrvar+1", "\tsta _L1+2", "\tsta _L2+2", "\tlda #$65", "_L1", "\tsta $ffff", "\tinc _L2+1", "\tbne _L2", "\tinc _L2+2", "_L2", "\tlda #$00", "\tsta $ffff", }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ctx := newCtx() cmd := &PokeWCommand{} line := newLine(tt.line, ctx.Pragma) if err := cmd.Interpret(line, ctx); err != nil { t.Fatalf("Interpret() error = %v", err) } asm, err := cmd.Generate(ctx) if err != nil { t.Fatalf("Generate() error = %v", err) } if !equalAsm(asm, tt.wantAsm) { t.Errorf("Generate() mismatch\ngot:\n%s\nwant:\n%s", strings.Join(asm, "\n"), strings.Join(tt.wantAsm, "\n")) } }) } } // ============================================================================= // Error cases // ============================================================================= func TestPokeErrors(t *testing.T) { tests := []struct { name string line string wantErr string }{ { name: "offset on non-ZP word pointer", line: "POKE waddrvar[5],10", wantErr: "POKE: offset", }, { name: "value out of byte range", line: "POKE $d020,256", wantErr: "out of byte range", }, { name: "POKEW with byte variable as value", line: "POKEW $d020,valvar", wantErr: "cannot use byte variable", }, { name: "POKEW self-referential (zp pointer == value)", line: "POKEW zpptr,zpptr", wantErr: "writing pointer", }, { name: "invalid separator", line: "POKE $d020 WRONG 5", wantErr: "must be 'WITH' or ','", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ctx := newCtx() if strings.HasPrefix(tt.line, "POKEW") { cmd := &PokeWCommand{} line := newLine(tt.line, ctx.Pragma) err := cmd.Interpret(line, ctx) if err == nil { t.Fatal("Interpret() expected error but got nil") } if !strings.Contains(err.Error(), tt.wantErr) { t.Errorf("Interpret() error = %q, want containing %q", err.Error(), tt.wantErr) } return } cmd := &PokeCommand{} line := newLine(tt.line, ctx.Pragma) err := cmd.Interpret(line, ctx) if err == nil { t.Fatal("Interpret() expected error but got nil") } if !strings.Contains(err.Error(), tt.wantErr) { t.Errorf("Interpret() error = %q, want containing %q", err.Error(), tt.wantErr) } }) } } func TestPokePragmaImmutable(t *testing.T) { t.Run("SM byte addr with USE_IMMUTABLE_CODE", func(t *testing.T) { pragma := preproc.NewPragma() pragma.AddPragma("_P_USE_IMMUTABLE_CODE", "1") ctx := compiler.NewCompilerContext(pragma) ctx.SymbolTable.AddVar("addrvar", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1}) cmd := &PokeCommand{} line := preproc.Line{ Text: "POKE addrvar,10", Kind: preproc.Source, PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(), } if err := cmd.Interpret(line, ctx); err != nil { t.Fatalf("Interpret() error = %v", err) } _, err := cmd.Generate(ctx) if err == nil { t.Fatal("Generate() expected error with USE_IMMUTABLE_CODE") } if !strings.Contains(err.Error(), "USE_IMMUTABLE_CODE") { t.Errorf("Generate() error = %q, want containing USE_IMMUTABLE_CODE", err.Error()) } }) t.Run("SM word addr with USE_IMMUTABLE_CODE", func(t *testing.T) { pragma := preproc.NewPragma() pragma.AddPragma("_P_USE_IMMUTABLE_CODE", "1") ctx := compiler.NewCompilerContext(pragma) ctx.SymbolTable.AddVar("waddrvar", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1}) cmd := &PokeCommand{} line := preproc.Line{ Text: "POKE waddrvar,10", Kind: preproc.Source, PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(), } if err := cmd.Interpret(line, ctx); err != nil { t.Fatalf("Interpret() error = %v", err) } _, err := cmd.Generate(ctx) if err == nil { t.Fatal("Generate() expected error with USE_IMMUTABLE_CODE") } if !strings.Contains(err.Error(), "USE_IMMUTABLE_CODE") { t.Errorf("Generate() error = %q, want containing USE_IMMUTABLE_CODE", err.Error()) } }) t.Run("ZP pointer is allowed with USE_IMMUTABLE_CODE", func(t *testing.T) { pragma := preproc.NewPragma() pragma.AddPragma("_P_USE_IMMUTABLE_CODE", "1") ctx := compiler.NewCompilerContext(pragma) ctx.SymbolTable.AddAbsolute("zpptr", "", compiler.KindWord, 0x80, preproc.Line{Filename: "test.c65", LineNo: 1}) cmd := &PokeCommand{} line := preproc.Line{ Text: "POKE zpptr,10", Kind: preproc.Source, PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(), } if err := cmd.Interpret(line, ctx); err != nil { t.Fatalf("Interpret() error = %v", err) } asm, err := cmd.Generate(ctx) if err != nil { t.Fatalf("Generate() error = %v (ZP pointers should be allowed)", err) } expected := []string{ "\tldy #0", "\tlda #10", "\tsta (zpptr),y", } if !equalAsm(asm, expected) { t.Errorf("Generate() mismatch\ngot:\n%s\nwant:\n%s", strings.Join(asm, "\n"), strings.Join(expected, "\n")) } }) t.Run("direct addr is allowed with USE_IMMUTABLE_CODE", func(t *testing.T) { pragma := preproc.NewPragma() pragma.AddPragma("_P_USE_IMMUTABLE_CODE", "1") ctx := compiler.NewCompilerContext(pragma) cmd := &PokeCommand{} line := preproc.Line{ Text: "POKE $d020,5", Kind: preproc.Source, PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(), } if err := cmd.Interpret(line, ctx); err != nil { t.Fatalf("Interpret() error = %v", err) } asm, err := cmd.Generate(ctx) if err != nil { t.Fatalf("Generate() error = %v (direct addr should be allowed)", err) } expected := []string{ "\tlda #5", "\tsta 53280", } if !equalAsm(asm, expected) { t.Errorf("Generate() mismatch\ngot:\n%s\nwant:\n%s", strings.Join(asm, "\n"), strings.Join(expected, "\n")) } }) }