feat: gasm-devkit 0.1.0 — GAsm lexer, parser, linter, formatter, LSP and amd64 assembler
Assisted-by: Qwen 3.8 Max Preview
This commit is contained in:
+162
@@ -0,0 +1,162 @@
|
||||
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Package ast defines the abstract syntax tree of a GAsm source file. The
|
||||
// tree is produced by the parser and consumed by the formatter, linter and
|
||||
// language server. Operand classification that depends on the target
|
||||
// architecture (is this bare name a register or a label?) is deliberately left
|
||||
// to the arch package; the AST records syntax only.
|
||||
package ast
|
||||
|
||||
import "sourcedock.dev/petrbalvin/gasm-devkit/token"
|
||||
|
||||
// File is the parsed representation of one .s source file.
|
||||
type File struct {
|
||||
Path string
|
||||
Decls []Decl
|
||||
Orphans []Stmt // labels/instructions seen before any TEXT directive
|
||||
// Macros holds the names introduced by #define directives in this file.
|
||||
// The linter uses it to avoid flagging macro invocations as unknown
|
||||
// instructions (macro expansion itself is out of scope — see the docs).
|
||||
Macros map[string]bool
|
||||
}
|
||||
|
||||
// Decl is a top-level declaration.
|
||||
type Decl interface {
|
||||
declNode()
|
||||
// Pos returns the position of the declaration's first token.
|
||||
Pos() token.Position
|
||||
}
|
||||
|
||||
// Stmt is a statement inside a TEXT body.
|
||||
type Stmt interface {
|
||||
stmtNode()
|
||||
Pos() token.Position
|
||||
}
|
||||
|
||||
// Include is a #include "header" line.
|
||||
type Include struct {
|
||||
Hash token.Token
|
||||
Name token.Token // the directive name, usually "include"
|
||||
Header token.Token // the string literal, quotes included
|
||||
}
|
||||
|
||||
func (*Include) declNode() {}
|
||||
func (d *Include) Pos() token.Position { return d.Hash.Pos }
|
||||
|
||||
// Preproc is any other preprocessor line (#define, #undef, …) captured loosely.
|
||||
type Preproc struct {
|
||||
Hash token.Token
|
||||
Raw string // verbatim text after the '#'
|
||||
}
|
||||
|
||||
func (*Preproc) declNode() {}
|
||||
func (d *Preproc) Pos() token.Position { return d.Hash.Pos }
|
||||
|
||||
// Text is a TEXT function definition and its body.
|
||||
type Text struct {
|
||||
Keyword token.Token // the TEXT token
|
||||
Name *Symbol // ·funcName(SB)
|
||||
Flags []string // NOSPLIT, DUPOK, …
|
||||
Frame *Operand // $0
|
||||
Args *Operand // the -65 part; nil when absent
|
||||
Body []Stmt
|
||||
Doc string // preceding comment block (typically the Go signature)
|
||||
}
|
||||
|
||||
func (*Text) declNode() {}
|
||||
func (d *Text) Pos() token.Position { return d.Keyword.Pos }
|
||||
|
||||
// Globl is a GLOBL symbol declaration.
|
||||
type Globl struct {
|
||||
Keyword token.Token
|
||||
Name *Symbol
|
||||
Flags []string
|
||||
Size *Operand
|
||||
}
|
||||
|
||||
func (*Globl) declNode() {}
|
||||
func (d *Globl) Pos() token.Position { return d.Keyword.Pos }
|
||||
|
||||
// Data is a DATA symbol initialiser.
|
||||
type Data struct {
|
||||
Keyword token.Token
|
||||
Name *Symbol // includes any +offset
|
||||
Width int // the /width suffix; 0 when absent
|
||||
Value *Operand
|
||||
}
|
||||
|
||||
func (*Data) declNode() {}
|
||||
func (d *Data) Pos() token.Position { return d.Keyword.Pos }
|
||||
|
||||
// Label is a local label definition such as vec1:.
|
||||
type Label struct {
|
||||
Name token.Token
|
||||
Colon token.Token
|
||||
}
|
||||
|
||||
func (*Label) stmtNode() {}
|
||||
func (s *Label) Pos() token.Position { return s.Name.Pos }
|
||||
|
||||
// Instr is a single machine instruction with zero or more operands.
|
||||
type Instr struct {
|
||||
Mnemonic token.Token
|
||||
Operands []*Operand
|
||||
Comment string // trailing comment text, without the leading //
|
||||
}
|
||||
|
||||
func (*Instr) stmtNode() {}
|
||||
func (s *Instr) Pos() token.Position { return s.Mnemonic.Pos }
|
||||
|
||||
// Symbol is a symbol reference: ·name(SB), name<>(SB), name+8(FP), …
|
||||
type Symbol struct {
|
||||
Raw string // verbatim text
|
||||
Pkg string // package prefix before the middle dot ("" = current package)
|
||||
Name string // identifier without the middle dot or <>
|
||||
Static bool // the <> marker is present
|
||||
Pseudo string // FP, SP, SB or PC ("" for a bare name)
|
||||
Offset int64
|
||||
HasOff bool
|
||||
Pos token.Position
|
||||
}
|
||||
|
||||
// OpKind classifies an operand syntactically.
|
||||
type OpKind int
|
||||
|
||||
// Operand kinds.
|
||||
const (
|
||||
OpInvalid OpKind = iota
|
||||
OpImmediate // $value
|
||||
OpAddr // register, memory reference, symbol or label
|
||||
)
|
||||
|
||||
// Operand is one instruction operand.
|
||||
type Operand struct {
|
||||
Raw string
|
||||
Kind OpKind
|
||||
Imm Immediate
|
||||
Addr Address
|
||||
Pos token.Position
|
||||
}
|
||||
|
||||
// Immediate is a $ value.
|
||||
type Immediate struct {
|
||||
Neg bool
|
||||
Val int64
|
||||
HasVal bool // a simple integer immediate was parsed
|
||||
Float string // non-empty for a floating-point immediate
|
||||
Str string // non-empty for a string/rune immediate
|
||||
Sym *Symbol // non-nil for $sym(…)
|
||||
}
|
||||
|
||||
// Address is a non-immediate operand: a register, a memory reference, a symbol
|
||||
// reference or a label. Fields are populated best-effort from the syntax.
|
||||
type Address struct {
|
||||
Sym *Symbol // name reference (bare ident, or name+off(pseudo))
|
||||
Base string // base register, from (base)
|
||||
Index string // index register, from (index*scale)
|
||||
Scale int // index scale; 0 when absent
|
||||
Offset int64 // leading displacement, from off(base)
|
||||
HasOff bool // a leading displacement is present
|
||||
Shift string // verbatim arm64 shift suffix, e.g. "<<2"
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package ast
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"sourcedock.dev/petrbalvin/gasm-devkit/token"
|
||||
)
|
||||
|
||||
func pos(line, col int) token.Position { return token.Position{Line: line, Column: col} }
|
||||
|
||||
func TestDeclPositions(t *testing.T) {
|
||||
hash := token.Token{Kind: token.Hash, Pos: pos(1, 1)}
|
||||
inc := &Include{Hash: hash}
|
||||
if inc.Pos() != hash.Pos {
|
||||
t.Errorf("Include.Pos() = %v, want %v", inc.Pos(), hash.Pos)
|
||||
}
|
||||
|
||||
pre := &Preproc{Hash: hash}
|
||||
if pre.Pos() != hash.Pos {
|
||||
t.Errorf("Preproc.Pos() = %v", pre.Pos())
|
||||
}
|
||||
|
||||
kw := token.Token{Kind: token.Ident, Text: "TEXT", Pos: pos(5, 1)}
|
||||
text := &Text{Keyword: kw}
|
||||
if text.Pos() != kw.Pos {
|
||||
t.Errorf("Text.Pos() = %v", text.Pos())
|
||||
}
|
||||
|
||||
gkw := token.Token{Kind: token.Ident, Text: "GLOBL", Pos: pos(7, 1)}
|
||||
if (&Globl{Keyword: gkw}).Pos() != gkw.Pos {
|
||||
t.Error("Globl.Pos()")
|
||||
}
|
||||
|
||||
dkw := token.Token{Kind: token.Ident, Text: "DATA", Pos: pos(8, 1)}
|
||||
if (&Data{Keyword: dkw}).Pos() != dkw.Pos {
|
||||
t.Error("Data.Pos()")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStmtPositions(t *testing.T) {
|
||||
name := token.Token{Kind: token.Ident, Text: "loop", Pos: pos(3, 1)}
|
||||
if (&Label{Name: name}).Pos() != name.Pos {
|
||||
t.Error("Label.Pos()")
|
||||
}
|
||||
mnem := token.Token{Kind: token.Ident, Text: "RET", Pos: pos(4, 2)}
|
||||
if (&Instr{Mnemonic: mnem}).Pos() != mnem.Pos {
|
||||
t.Error("Instr.Pos()")
|
||||
}
|
||||
}
|
||||
|
||||
// TestInterfaces confirms the node types satisfy their interfaces, so callers
|
||||
// can range over Decls and Stmts.
|
||||
func TestInterfaces(t *testing.T) {
|
||||
var decls []Decl = []Decl{&Include{}, &Preproc{}, &Text{}, &Globl{}, &Data{}}
|
||||
if len(decls) != 5 {
|
||||
t.Fatal("decl interface set")
|
||||
}
|
||||
var stmts []Stmt = []Stmt{&Label{}, &Instr{}}
|
||||
if len(stmts) != 2 {
|
||||
t.Fatal("stmt interface set")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user