Initial commit

It works, but it's kinda messy.
This commit is contained in:
Zorchenhimer 2025-12-26 18:55:22 -05:00
commit 1374167b0f
Signed by: Zorchenhimer
GPG Key ID: 70A1AB767AAB9C20
8 changed files with 699 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
bin/
*.cfg

8
Makefile Normal file
View File

@ -0,0 +1,8 @@
all: bin/dasm2lbl
bin/:
mkdir bin
bin/dasm2lbl: **/*.go
go build -o $@ cmd/main.go

77
cmd/main.go Normal file
View File

@ -0,0 +1,77 @@
package main
import (
"os"
"fmt"
"github.com/alexflint/go-arg"
"git.zorchenhimer.com/Zorchenhimer/dasm-labels"
)
type Arguments struct {
Input string `arg:"positional,required"`
Output string `arg:"positional"`
RamStart int `arg:"--ram-start" default:"0x5000"`
RomStart int `arg:"--rom-start" default:"0xE000"`
}
func main() {
args := &Arguments{}
arg.MustParse(args)
err := run(args)
if err != nil {
fmt.Fprintln(os.Stdout, err)
os.Exit(1)
}
}
func run(args *Arguments) error {
input, err := os.ReadFile(args.Input)
if err != nil {
return err
}
l, ch := dasmlbl.NewLexer(string(input))
go l.Run()
parser := dasmlbl.NewParser(ch)
config, err := parser.Run()
if err != nil {
fmt.Println(err)
}
var output *os.File
if args.Output == "" {
output = os.Stdout
} else {
output, err = os.Create(args.Output)
if err != nil {
return err
}
defer output.Close()
}
for _, lbl := range config.Labels {
//fmt.Println(lbl)
fmt.Fprintln(output, lbl.Mlb(args.RamStart, args.RomStart))
}
for _, rng := range config.Ranges {
if rng.Name == "" {
continue
}
fmt.Fprintln(output, rng.Mlb(args.RamStart, args.RomStart))
}
//go func(items chan dasmlbl.LexItem) {
// for {
// fmt.Println(<-items)
// }
//}(ch)
//l.Run()
return nil
}

88
config.go Normal file
View File

@ -0,0 +1,88 @@
package dasmlbl
import (
"fmt"
)
type Config struct {
Cpu string
InputName string
OutputName string
HexOffset int
InputOffset int
InputSize int
StartAddr int
Comments int
LabelBreak int
PageLength int
CommentColumn int
ArgumentColumn int
MnemonicColumn int
TextColumn int
NewlineAfterJMP bool
NewlineAfterRTS bool
Labels []Label
Ranges []Range
Segments []Segment
}
type Label struct {
Name string
Address int
Comment string
Size int
ParamSize int
}
func (l Label) Mlb(startRam, startRom int) string {
addr := l.Address
memType := "NesMemory"
if addr >= startRom {
addr -= startRom
memType = "NesPrgRom"
} else if addr >= startRam {
addr -= startRam
memType = "NesWorkRam"
}
addrStr := fmt.Sprintf("%04X", addr)
if l.Size > 1 {
addrStr = fmt.Sprintf("%04X-%04X", l.Address, l.Address+l.Size-1)
}
return fmt.Sprintf("%s:%s:%s:%s", memType, addrStr, l.Name, l.Comment)
//addr := fmt.Sprintf("%04X", l.Address)
//if l.Size > 1 {
// addr = fmt.Sprintf("%04X-%04X", l.Address, l.Address+l.Size-1)
//}
//return fmt.Sprintf("NesMemory:%s:%s:%s", addr, l.Name, l.Comment)
}
type Range struct {
Name string
Comment string
Start int
End int
Type string
Unit int
AddrMode string
}
func (r Range) Mlb(startRam, startRom int) string {
addr := fmt.Sprintf("%04X", r.Start)
if r.End - r.Start > 1 {
addr = fmt.Sprintf("%04X-%04X", r.Start, r.End)
}
return fmt.Sprintf("NesMemory:%s:%s:%s", addr, r.Name, r.Comment)
}
type Segment struct {
Name string
Start int
End int
}

7
go.mod Normal file
View File

@ -0,0 +1,7 @@
module git.zorchenhimer.com/Zorchenhimer/dasm-labels
go 1.25.5
require github.com/alexflint/go-arg v1.6.1
require github.com/alexflint/go-scalar v1.2.0 // indirect

13
go.sum Normal file
View File

@ -0,0 +1,13 @@
github.com/alexflint/go-arg v1.6.1 h1:uZogJ6VDBjcuosydKgvYYRhh9sRCusjOvoOLZopBlnA=
github.com/alexflint/go-arg v1.6.1/go.mod h1:nQ0LFYftLJ6njcaee0sU+G0iS2+2XJQfA8I062D0LGc=
github.com/alexflint/go-scalar v1.2.0 h1:WR7JPKkeNpnYIOfHRa7ivM21aWAdHD0gEWHCx+WQBRw=
github.com/alexflint/go-scalar v1.2.0/go.mod h1:LoFvNMqS1CPrMVltza4LvnGKhaSpc3oyLEBUZVhhS2o=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

257
lex.go Normal file
View File

@ -0,0 +1,257 @@
package dasmlbl
import (
"unicode"
"unicode/utf8"
"strings"
"fmt"
)
var (
eof LexItem = LexItem{lex_EOF, "EOF"}
unicode_EOF rune = '\uFFFC'
)
type stateFn func(l *Lexer) stateFn
type Lexer struct {
input string
start int
pos int
width int
items chan LexItem
}
func NewLexer(input string) (*Lexer, chan LexItem) {
l := &Lexer{
input: input,
items: make(chan LexItem),
}
//fmt.Println("new lex")
return l, l.items
}
func (l *Lexer) Run() {
//fmt.Println("lex run()")
for state := lexStateStart; state != nil; {
state = state(l)
}
close(l.items)
}
func (l *Lexer) emit(t lexItemType) {
itm := LexItem{t, l.input[l.start:l.pos]}
//fmt.Println(">", itm)
l.items <- itm
l.start = l.pos
}
func (l *Lexer) next() rune {
if l.pos >= len(l.input) {
l.width = 0
return unicode_EOF
}
r, size := utf8.DecodeRuneInString(l.input[l.pos:])
if size == 0 {
panic(fmt.Sprintf("zero width at %d", l.pos))
}
l.width = size
l.pos += l.width
return r
}
func (l *Lexer) backup() {
l.pos -= l.width
}
func (l *Lexer) ignore() {
l.start = l.pos
}
func (l *Lexer) accept(valid string) bool {
if strings.IndexRune(valid, l.next()) >= 0 {
return true
}
l.backup()
return false
}
func (l *Lexer) acceptRun(valid string) {
for {
r := l.next()
idx := strings.IndexRune(valid, r)
if idx < 0 {
break
}
}
l.backup()
}
type lexItemType int
const (
lex_EOF lexItemType = iota
lex_Ident
lex_OpenBracket
lex_CloseBracket
lex_Semicolon
lex_String
lex_Number
lex_Pound
lex_Space
lex_Error
)
func (lit lexItemType) String() string {
switch lit {
case lex_Ident:
return "lex_Ident"
case lex_OpenBracket:
return "lex_OpenBracket"
case lex_CloseBracket:
return "lex_CloseBracket"
case lex_Semicolon:
return "lex_Semicolon"
case lex_String:
return "lex_String"
case lex_Number:
return "lex_Number"
case lex_Pound:
return "lex_Pound"
case lex_Space:
return "lex_Space"
case lex_EOF:
return "lex_EOF"
case lex_Error:
return "lex_Error"
}
return "lex_UNKNOWN"
}
type LexItem struct {
typ lexItemType
val string
}
func (itm LexItem) String() string {
return fmt.Sprintf("%s: %q", itm.typ, itm.val)
}
func lexComment(l *Lexer) stateFn {
for {
r := l.next()
if r == '\n' {
l.backup()
l.emit(lex_String)
break
}
if r == unicode_EOF {
return nil
}
}
return lexStateStart
}
func lexIdent(l *Lexer) stateFn {
l.acceptRun("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
l.emit(lex_Ident)
return lexStateStart
}
func lexNumber(l *Lexer) stateFn {
l.accept("-+")
l.accept("0x")
l.accept("$%")
l.acceptRun("0123456789ABCDEFabcdef")
l.emit(lex_Number)
return lexStateStart
}
func lexString(l *Lexer) stateFn {
for {
r := l.next()
if r == unicode_EOF {
l.items <- LexItem{lex_Error, "EOF before string end"}
return nil
}
if r == '"' {
break
}
if r == '\n' {
l.items <- LexItem{lex_Error, fmt.Sprintf("newline in string; pos: %d", l.pos)}
return nil
}
}
l.backup()
l.emit(lex_String)
l.pos++
l.ignore()
return lexStateStart
}
// consume until the first non-space
func lexStateStart(l *Lexer) stateFn {
for {
r := l.next()
if r == unicode_EOF {
break
}
if unicode.IsSpace(r) {
l.ignore()
continue
}
switch r {
case '{':
l.emit(lex_OpenBracket)
continue
case '}':
l.emit(lex_CloseBracket)
continue
case ';':
l.emit(lex_Semicolon)
continue
case '#':
l.emit(lex_Pound)
return lexComment
case '"':
//l.backup()
l.ignore()
return lexString
}
if r == '$' || r == '%' || r == '+' || r == '-' || ('0' <= r && r <= '9') {
l.backup()
return lexNumber
}
if unicode.IsLetter(r) {
l.backup()
return lexIdent
}
}
// eof
if l.pos > l.start {
l.emit(lex_Space)
}
l.emit(lex_EOF)
return nil
}
func isAlphaNumeric(r rune) bool {
if !isAlpha(r) {
return false
}
return '0' <= r && r <= '9'
}
func isAlpha(r rune) bool {
return ('A' <= r && r <= 'Z') || ('a' <= r && r <= 'Z')
}

246
parse.go Normal file
View File

@ -0,0 +1,246 @@
package dasmlbl
import (
"strings"
"strconv"
"fmt"
)
type Parser struct {
config *Config
items chan LexItem
}
func NewParser(items chan LexItem) *Parser {
return &Parser{
config: &Config{},
items: items,
}
}
func (p *Parser) next() LexItem {
select {
case itm := <- p.items:
//fmt.Println(itm)
return itm
}
return LexItem{lex_EOF, "EOF"}
}
func (p *Parser) Run() (*Config, error) {
for {
itm := p.next()
//fmt.Println("<", itm)
if itm.typ == lex_EOF {
break
}
if itm.typ == lex_Pound {
itm = p.next()
continue
}
if itm.typ != lex_Ident {
continue
//return nil, fmt.Errorf("expected ident, got %s", itm)
}
//if strings.ToLower(itm.val) != "label" {
// continue
//}
var err error
switch strings.ToLower(itm.val) {
case "label":
err = p.parseLabel()
case "range":
err = p.parseRange()
}
if err != nil {
return p.config, err
}
}
return p.config, nil
}
func (p *Parser) parseRange() error {
itm := p.next()
if itm.typ != lex_OpenBracket {
return fmt.Errorf("missing open bracket")
}
rng := Range{}
for {
itm = p.next()
if itm.typ == lex_CloseBracket {
break
}
if itm.typ != lex_Ident {
return fmt.Errorf("missing ident")
}
switch strings.ToLower(itm.val) {
case "name", "comment":
typ := itm.val
itm = p.next()
if itm.typ != lex_String {
return fmt.Errorf("%s requires string: %s", typ, itm)
//return fmt.Errorf("name requires string")
}
switch typ {
case "name":
rng.Name = itm.val
case "comment":
rng.Comment = itm.val
case "type":
rng.Type = itm.val
case "addrmode":
rng.AddrMode = itm.val
}
case "type", "addrmode":
p.next()
// just consume it
case "start", "end", "unit":
typ := itm.val
itm = p.next()
if itm.typ != lex_Number {
return fmt.Errorf("%s: %s", strings.ToLower(typ+" requires number"), itm)
}
str := itm.val
if strings.HasPrefix(str, "$") {
str = "0x"+strings.TrimPrefix(str, "$")
} else if strings.HasPrefix(str, "%") {
str = "0b"+strings.TrimPrefix(str, "%")
}
val, err := strconv.ParseInt(str, 0, 32)
if err != nil {
return err
}
switch typ {
case "start":
rng.Start = int(val)
case "end":
rng.End = int(val)
case "unit":
rng.Unit = int(val)
}
}
itm = p.next()
if itm.typ != lex_Semicolon {
return fmt.Errorf("missing semicolon")
}
}
p.config.Ranges = append(p.config.Ranges, rng)
return nil
}
func (p *Parser) parseLabel() error {
itm := p.next()
if itm.typ != lex_OpenBracket {
return fmt.Errorf("missing open bracket")
}
lbl := Label{}
for {
itm = p.next()
if itm.typ == lex_CloseBracket {
break
}
if itm.typ != lex_Ident {
return fmt.Errorf("missing ident")
}
//fmt.Println("]", itm)
switch strings.ToLower(itm.val) {
case "name":
itm = p.next()
if itm.typ != lex_String {
return fmt.Errorf("name requires string: %s", itm)
}
lbl.Name = itm.val
case "addr", "size", "paramsize":
typ := strings.ToLower(itm.val)
itm = p.next()
if itm.typ != lex_Number {
return fmt.Errorf("%s: %s", strings.ToLower(typ+" requires number"), itm)
}
str := itm.val
if strings.HasPrefix(str, "$") {
str = "0x"+strings.TrimPrefix(str, "$")
} else if strings.HasPrefix(str, "%") {
str = "0b"+strings.TrimPrefix(str, "%")
}
val, err := strconv.ParseInt(str, 0, 32)
if err != nil {
return err
}
switch typ {
case "addr":
lbl.Address = int(val)
case "size":
lbl.Size = int(val)
case "paramsize":
lbl.ParamSize = int(val)
}
case "comment":
itm = p.next()
if itm.typ != lex_String {
return fmt.Errorf("comment requires string")
}
lbl.Comment = itm.val
}
itm = p.next()
if itm.typ != lex_Semicolon {
return fmt.Errorf("missing semicolon")
}
}
p.config.Labels = append(p.config.Labels, lbl)
return nil
}
//func (p *Parser) parseIdent(itm LexItem) error {
// switch strings.ToLower(itm.val) {
// case "global":
// select {
// case itm = <- p.items:
// if itm.typ != lex_OpenBracket {
// return fmt.Errorf("expected { after global, got %q", itm)
// }
// return p.parseGlobal()
// default:
// return fmt.Errorf("early EOF")
// }
// }
//
// return nil
//}
//func (p *Parser) parseGlobal() error {
// for {
// select {
// case itm := <- p.items:
// default:
// return fmt.Errorf("early EOF")
// }
// }
//}