Files
gasm-sdk/lexer/lexer_test.go
T

156 lines
4.4 KiB
Go
Raw Permalink Normal View History

// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
// SPDX-License-Identifier: BSD-3-Clause
package lexer
import (
"testing"
"sourcedock.dev/petrbalvin/gasm-devkit/token"
)
// kinds tokenizes src and returns the kind sequence, dropping Newline/EOF.
func kinds(src string) []token.Kind {
var out []token.Kind
for _, t := range Tokenize(src) {
if t.Kind == token.Newline || t.Kind == token.EOF {
continue
}
out = append(out, t.Kind)
}
return out
}
// texts tokenizes src and returns the literal text of each significant token.
func texts(src string) []string {
var out []string
for _, t := range Tokenize(src) {
if t.Kind == token.Newline || t.Kind == token.EOF {
continue
}
out = append(out, t.Text)
}
return out
}
func eq[T comparable](t *testing.T, got, want []T) {
t.Helper()
if len(got) != len(want) {
t.Fatalf("length mismatch:\n got %v\n want %v", got, want)
}
for i := range got {
if got[i] != want[i] {
t.Fatalf("index %d:\n got %v\n want %v", i, got, want)
}
}
}
func TestTextDirective(t *testing.T) {
eq(t, texts("TEXT ·analyzeO1RangeAVX2(SB), NOSPLIT, $0-65"),
[]string{"TEXT", "·analyzeO1RangeAVX2", "(", "SB", ")", ",", "NOSPLIT", ",", "$", "0", "-", "65"})
}
func TestDataAndGlobl(t *testing.T) {
eq(t, texts("GLOBL ·idx16(SB), RODATA, $64"),
[]string{"GLOBL", "·idx16", "(", "SB", ")", ",", "RODATA", ",", "$", "64"})
eq(t, texts("DATA ·idx16+0(SB)/4, $1"),
[]string{"DATA", "·idx16", "+", "0", "(", "SB", ")", "/", "4", ",", "$", "1"})
}
func TestStaticSymbol(t *testing.T) {
// mask24<> is a file-local symbol; <> must lex as two angle tokens.
eq(t, texts("GLOBL mask24<>(SB), RODATA, $16"),
[]string{"GLOBL", "mask24", "<", ">", "(", "SB", ")", ",", "RODATA", ",", "$", "16"})
}
func TestNegativeImmediate(t *testing.T) {
eq(t, texts("ANDQ $-8, R10"),
[]string{"ANDQ", "$", "-", "8", ",", "R10"})
}
func TestHexImmediate(t *testing.T) {
eq(t, texts("DATA mask24<>+0(SB)/4, $0x80020100"),
[]string{"DATA", "mask24", "<", ">", "+", "0", "(", "SB", ")", "/", "4", ",", "$", "0x80020100"})
}
func TestMemoryAddressing(t *testing.T) {
eq(t, texts("LEAQ (SI)(BX*4), R9"),
[]string{"LEAQ", "(", "SI", ")", "(", "BX", "*", "4", ")", ",", "R9"})
eq(t, texts("VMOVDQU32 Z0, 4(SI)(AX*1)"),
[]string{"VMOVDQU32", "Z0", ",", "4", "(", "SI", ")", "(", "AX", "*", "1", ")"})
}
func TestLabelAndComment(t *testing.T) {
eq(t, kinds("vec1:\n\tJMP vec1 // loop"),
[]token.Kind{token.Ident, token.Colon, token.Ident, token.Ident, token.Comment})
}
func TestAVX512Mnemonics(t *testing.T) {
eq(t, texts("VFMADD231PD Z14, Z12, Z10"),
[]string{"VFMADD231PD", "Z14", ",", "Z12", ",", "Z10"})
eq(t, texts("KTESTW K1, K1"),
[]string{"KTESTW", "K1", ",", "K1"})
}
func TestArm64Shifts(t *testing.T) {
eq(t, texts("ADD R0<<2, R1, R2"),
[]string{"ADD", "R0", "<<", "2", ",", "R1", ",", "R2"})
eq(t, texts("MOVD R3->4, R5"),
[]string{"MOVD", "R3", "->", "4", ",", "R5"})
}
func TestInclude(t *testing.T) {
eq(t, texts(`#include "textflag.h"`),
[]string{"#", "include", `"textflag.h"`})
}
func TestPositions(t *testing.T) {
toks := Tokenize("MOVQ AX, BX\nRET")
// Find RET and check it landed on line 2.
var ret token.Token
for _, tok := range toks {
if tok.Text == "RET" {
ret = tok
}
}
if ret.Pos.Line != 2 || ret.Pos.Column != 1 {
t.Fatalf("RET position = %v, want 2:1", ret.Pos)
}
}
func TestIllegalNeverPanics(t *testing.T) {
// A stray backtick and NUL-ish garbage must not crash the scanner.
toks := Tokenize("MOVQ ` , \x01 AX")
if len(toks) == 0 {
t.Fatal("expected tokens")
}
}
func TestBlockComment(t *testing.T) {
eq(t, kinds("MOVQ /* inline */ AX"),
[]token.Kind{token.Ident, token.Comment, token.Ident})
// An unterminated block comment is tolerated.
toks := Tokenize("MOVQ /* never closed")
if toks[len(toks)-2].Kind != token.Comment {
t.Errorf("expected a comment token, got %v", toks)
}
}
func TestRuneLiteral(t *testing.T) {
eq(t, texts("MOVL $'a', AX"),
[]string{"MOVL", "$", "'a'", ",", "AX"})
}
func TestFloatAndBases(t *testing.T) {
eq(t, texts("$1.5"), []string{"$", "1.5"})
eq(t, texts("$0b1010"), []string{"$", "0b1010"})
eq(t, texts("$0o755"), []string{"$", "0o755"})
eq(t, texts("$1e3"), []string{"$", "1e3"})
}
func TestOperatorVariants(t *testing.T) {
eq(t, texts("R0>>2"), []string{"R0", ">>", "2"})
eq(t, texts("@>"), []string{"@", ">"})
eq(t, texts("a/b"), []string{"a", "/", "b"})
}