commit
1374167b0f
|
|
@ -0,0 +1,3 @@
|
|||
bin/
|
||||
|
||||
*.cfg
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
|
||||
all: bin/dasm2lbl
|
||||
|
||||
bin/:
|
||||
mkdir bin
|
||||
|
||||
bin/dasm2lbl: **/*.go
|
||||
go build -o $@ 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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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=
|
||||
|
|
@ -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')
|
||||
}
|
||||
|
|
@ -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")
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
Loading…
Reference in New Issue