From 1374167b0f85d3408351b66b8ecfbabb0e5c747c Mon Sep 17 00:00:00 2001 From: Zorchenhimer Date: Fri, 26 Dec 2025 18:55:22 -0500 Subject: [PATCH] Initial commit It works, but it's kinda messy. --- .gitignore | 3 + Makefile | 8 ++ cmd/main.go | 77 ++++++++++++++++ config.go | 88 ++++++++++++++++++ go.mod | 7 ++ go.sum | 13 +++ lex.go | 257 ++++++++++++++++++++++++++++++++++++++++++++++++++++ parse.go | 246 +++++++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 699 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 cmd/main.go create mode 100644 config.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 lex.go create mode 100644 parse.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b109f15 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +bin/ + +*.cfg diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..68c0bcf --- /dev/null +++ b/Makefile @@ -0,0 +1,8 @@ + +all: bin/dasm2lbl + +bin/: + mkdir bin + +bin/dasm2lbl: **/*.go + go build -o $@ cmd/main.go diff --git a/cmd/main.go b/cmd/main.go new file mode 100644 index 0000000..45bcfe7 --- /dev/null +++ b/cmd/main.go @@ -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 +} diff --git a/config.go b/config.go new file mode 100644 index 0000000..809ed21 --- /dev/null +++ b/config.go @@ -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 +} + diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..9fe28a5 --- /dev/null +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..67f51ac --- /dev/null +++ b/go.sum @@ -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= diff --git a/lex.go b/lex.go new file mode 100644 index 0000000..361ae7a --- /dev/null +++ b/lex.go @@ -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') +} diff --git a/parse.go b/parse.go new file mode 100644 index 0000000..d107aa9 --- /dev/null +++ b/parse.go @@ -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") +// } +// } +//}