Working: labels, label references, macros, ops, error messaging, dry run, verbose
This commit is contained in:
parent
a71e8032ef
commit
17595fd409
|
@ -1 +1,4 @@
|
|||
bird-asm
|
||||
*.rom
|
||||
*.asm
|
||||
*.brd
|
||||
|
|
89
helpers.go
89
helpers.go
|
@ -4,6 +4,16 @@ import (
|
|||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
argsError = 1
|
||||
fileError = iota
|
||||
lexError
|
||||
compileError
|
||||
)
|
||||
|
||||
func fileExists(p string) (bool, error) {
|
||||
|
@ -16,20 +26,20 @@ func fileExists(p string) (bool, error) {
|
|||
func setIn() {
|
||||
args := flag.Args()
|
||||
if len(args) == 0 {
|
||||
fmt.Fprintf(os.Stderr, "no input file\n")
|
||||
os.Exit(1)
|
||||
fmt.Fprintf(os.Stderr, "No input file\n")
|
||||
os.Exit(argsError)
|
||||
} else if len(args) > 1 {
|
||||
fmt.Fprintf(os.Stderr, "too many input files (%d), needed 1\n", len(args))
|
||||
os.Exit(2)
|
||||
fmt.Fprintf(os.Stderr, "Too many input files (%d), needed 1\n", len(args))
|
||||
os.Exit(argsError)
|
||||
}
|
||||
in = args[0]
|
||||
in = absFilepath(args[0])
|
||||
exists, err := fileExists(in)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "could not open file\n\t%s", err.Error())
|
||||
os.Exit(3)
|
||||
fmt.Fprintf(os.Stderr, "Could not open file\n\t%s", err.Error())
|
||||
os.Exit(fileError)
|
||||
} else if !exists {
|
||||
fmt.Fprintf(os.Stderr, "%q does not exist\n", in)
|
||||
os.Exit(4)
|
||||
os.Exit(fileError)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -41,4 +51,67 @@ func setOut(o string) {
|
|||
}
|
||||
}
|
||||
|
||||
func isHexValue(v byte) bool {
|
||||
if (v >= 0x30 && v <= 0x39) || (v >= 0x41 && v <= 0x46) || (v >= 0x61 && v <= 0x66) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isHex(s string) bool {
|
||||
if len(s) != 2 {
|
||||
return false
|
||||
}
|
||||
for _, b := range []byte(s) {
|
||||
if !isHexValue(b) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func loadFile(p string) string {
|
||||
b, err := os.ReadFile(p)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, err.Error())
|
||||
os.Exit(fileError)
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func absFilepath(p string) string {
|
||||
if strings.HasPrefix(p, "~") {
|
||||
if p == "~" || strings.HasPrefix(p, "~/") {
|
||||
homedir, _ := os.UserHomeDir()
|
||||
if len(p) <= 2 {
|
||||
p = homedir
|
||||
} else if len(p) > 2 {
|
||||
p = filepath.Join(homedir, p[2:])
|
||||
}
|
||||
} else {
|
||||
i := strings.IndexRune(p, '/')
|
||||
var u string
|
||||
var remainder string
|
||||
if i < 0 {
|
||||
u = p[1:]
|
||||
remainder = ""
|
||||
} else {
|
||||
u = p[1:i]
|
||||
remainder = p[i:]
|
||||
}
|
||||
usr, err := user.Lookup(u)
|
||||
if err != nil {
|
||||
p = filepath.Join("/home", u, remainder)
|
||||
} else {
|
||||
p = filepath.Join(usr.HomeDir, remainder)
|
||||
}
|
||||
}
|
||||
} else if !strings.HasPrefix(p, "/") {
|
||||
wd, _ := os.Getwd()
|
||||
p = filepath.Join(wd, p)
|
||||
}
|
||||
|
||||
path, _ := filepath.Abs(p)
|
||||
return path
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,155 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
var lexLine uint16
|
||||
|
||||
|
||||
func eatMacro(r *strings.Reader) (string, []token) {
|
||||
startLine := lexLine
|
||||
name := eatText(r)
|
||||
data := make([]token, 0)
|
||||
for r.Len() > 0 {
|
||||
eatWhiteSpace(r)
|
||||
c, _, err := r.ReadRune()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
switch c {
|
||||
case '[', '{', ']', '}':
|
||||
// Allow these chars, but assign no meaning to them
|
||||
continue
|
||||
case '/':
|
||||
if c == '/' {
|
||||
eatSingleLineComment(r)
|
||||
} else if c == '*' {
|
||||
eatMultiLineComment(r)
|
||||
} else {
|
||||
fmt.Fprintf(os.Stderr, "Illegal char found following '/' on line %d,\nexpected '/' or '*'\n", lexLine)
|
||||
os.Exit(lexError)
|
||||
}
|
||||
case '\'':
|
||||
c, _, err := r.ReadRune()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Incomplete char literal on line %d\n", lexLine)
|
||||
os.Exit(lexError)
|
||||
}
|
||||
data = append(data, []token{token{"PSH", lexLine}, token{fmt.Sprintf("%02x", c), lexLine}}...)
|
||||
case '#':
|
||||
data = append(data, eatLiteral(r)...)
|
||||
case '%':
|
||||
fmt.Fprintf(os.Stderr, "Macro definition inside of macro definition starting on line %d\n", startLine)
|
||||
os.Exit(lexError)
|
||||
default:
|
||||
r.UnreadRune()
|
||||
dataWord := eatText(r)
|
||||
if dataWord == ";" {
|
||||
return name, data
|
||||
} else {
|
||||
if dataWord == name {
|
||||
fmt.Fprintf(os.Stderr, "Macro recursively referencing itself on line %d\n", lexLine)
|
||||
os.Exit(lexError)
|
||||
}
|
||||
data = append(data, token{dataWord, lexLine})
|
||||
}
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "Unclosed macro starting on line %d\n", startLine)
|
||||
os.Exit(lexError)
|
||||
return name, data
|
||||
}
|
||||
|
||||
func eatLiteral(r *strings.Reader) []token {
|
||||
num := eatText(r)
|
||||
for _, v := range []byte(num) {
|
||||
if !isHexValue(v) {
|
||||
fmt.Fprintf(os.Stderr, "Invalid literal '#' value on line %d\n", lexLine)
|
||||
os.Exit(lexError)
|
||||
}
|
||||
}
|
||||
if len(num) == 2 {
|
||||
return []token{token{"PSH", lexLine}, token{num, lexLine}}
|
||||
} else if len(num) == 4 {
|
||||
return []token{token{"PSH2", lexLine}, token{num[:2], lexLine}, token{num[2:], lexLine}}
|
||||
} else {
|
||||
fmt.Fprintf(os.Stderr, "Invalid PSH/# value on line %d\n", lexLine)
|
||||
os.Exit(lexError)
|
||||
}
|
||||
return []token{} // will never happen, but makes the compiler happy
|
||||
}
|
||||
|
||||
func eatText(r *strings.Reader) string {
|
||||
var buf strings.Builder
|
||||
|
||||
for r.Len() > 0 {
|
||||
c, _, err := r.ReadRune()
|
||||
if err != nil || unicode.IsSpace(c) || c == '[' || c == ']' || c == '{' || c == '}' {
|
||||
break
|
||||
}
|
||||
buf.WriteRune(c)
|
||||
}
|
||||
r.UnreadRune()
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
|
||||
func eatWhiteSpace(r *strings.Reader) {
|
||||
for r.Len() > 0 {
|
||||
c, _, err := r.ReadRune()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if c == '\n' {
|
||||
lexLine++
|
||||
}
|
||||
if !unicode.IsSpace(c) {
|
||||
r.UnreadRune()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func eatSingleLineComment(r *strings.Reader) {
|
||||
for r.Len() > 0 {
|
||||
c, _, err := r.ReadRune()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if c == '\n' {
|
||||
lexLine++
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func eatMultiLineComment(r *strings.Reader) {
|
||||
startLine := lexLine
|
||||
var asterisk bool
|
||||
for r.Len() > 0 {
|
||||
c, _, err := r.ReadRune()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
switch c {
|
||||
case '*':
|
||||
asterisk = true
|
||||
continue
|
||||
case '/':
|
||||
if asterisk {
|
||||
return
|
||||
}
|
||||
case '\n':
|
||||
lexLine++
|
||||
default:
|
||||
asterisk = false
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "Unclosed multiline comment starting on line %d\n", startLine)
|
||||
os.Exit(lexError)
|
||||
}
|
||||
|
29
main.go
29
main.go
|
@ -13,6 +13,7 @@ var (
|
|||
returnMask byte = 1 << 6
|
||||
verbose bool
|
||||
dryRun bool
|
||||
expansions uint16
|
||||
ops = map[string]uint8{
|
||||
"NOP": 0x00, "SET": 0x01, "GET": 0x02, "SEI": 0x03, "GEI": 0x04,
|
||||
"PSH": 0x07, "POP": 0x08, "OVR": 0x09, "SWP": 0x0A, "ROT": 0x0B, "DUP": 0x0C, "SSW": 0x0D,
|
||||
|
@ -20,7 +21,31 @@ var (
|
|||
"AND": 0x15, "BOR": 0x16, "XOR": 0x17, "SHR": 0x18, "SHL": 0x19,
|
||||
"JMP": 0x1C, "CAL": 0x1D, "JCD": 0x1E, "RET": 0x1F,
|
||||
"GCH": 0x23, "GST": 0x24, "PCH": 0x25, "PST": 0x26, "HLT": 0x27, "PHX": 0x28,
|
||||
"GRT": 0x2A, "LST": 0x2B, "GTE": 0x2C, "LTE": 0x2D, "EQL": 0x2E,
|
||||
"GRT": 0x2A, "LST": 0x2B, "GTE": 0x2C, "LTE": 0x2D, "EQL": 0x2E, "NEQ": 0x2F,
|
||||
|
||||
"NOPr": 0x40, "SETr": 0x41, "GETr": 0x42, "SEIr": 0x43, "GEIr": 0x44,
|
||||
"PSHr": 0x47, "POPr": 0x48, "OVRr": 0x49, "SWPr": 0x4A, "ROTr": 0x4B, "DUPr": 0x4C, "SSWr": 0x4D,
|
||||
"ADDr": 0x4E, "SUBr": 0x4F, "MULr": 0x50, "DIVr": 0x51, "INCr": 0x52, "DECr": 0x53,
|
||||
"ANDr": 0x55, "BORr": 0x56, "XORr": 0x57, "SHRr": 0x58, "SHLr": 0x59,
|
||||
"JMPr": 0x5C, "CALr": 0x5D, "JCDr": 0x5E, "RETr": 0x5F,
|
||||
"GCHr": 0x63, "GSTr": 0x64, "PCHr": 0x65, "PSTr": 0x66, "HLTr": 0x67, "PHXr": 0x68,
|
||||
"GRTr": 0x6A, "LSTr": 0x6B, "GTEr": 0x6C, "LTEr": 0x6D, "EQLr": 0x6E, "NEQr": 0x6F,
|
||||
|
||||
"NOP2": 0x80, "SET2": 0x81, "GET2": 0x82, "SEI2": 0x83, "GEI2": 0x84,
|
||||
"PSH2": 0x87, "POP2": 0x88, "OVR2": 0x89, "SWP2": 0x8A, "ROT2": 0x8B, "DUP2": 0x8C, "SSW2": 0x8D,
|
||||
"ADD2": 0x8E, "SUB2": 0x8F, "MUL2": 0x90, "DIV2": 0x91, "INC2": 0x92, "DEC2": 0x93,
|
||||
"AND2": 0x95, "BOR2": 0x96, "XOR2": 0x97, "SHR2": 0x98, "SHL2": 0x99,
|
||||
"JMP2": 0x9C, "CAL2": 0x9D, "JCD2": 0x9E, "RET2": 0x9F,
|
||||
"GCH2": 0xA3, "GST2": 0xA4, "PCH2": 0xA5, "PST2": 0xA6, "HLT2": 0xA7, "PHX2": 0xA8,
|
||||
"GRT2": 0xAA, "LST2": 0xAB, "GTE2": 0xAC, "LTE2": 0xAD, "EQL2": 0xAE, "NEQ2": 0xAF,
|
||||
|
||||
"NOP2r": 0xC0, "SET2r": 0xC1, "GET2r": 0xC2, "SEI2r": 0xC3, "GEI2r": 0xC4,
|
||||
"PSH2r": 0xC7, "POP2r": 0xC8, "OVR2r": 0xC9, "SWP2r": 0xCA, "ROT2r": 0xCB, "DUP2r": 0xCC, "SSW2r": 0xCD,
|
||||
"ADD2r": 0xCE, "SUB2r": 0xCF, "MUL2r": 0xD0, "DIV2r": 0xD1, "INC2r": 0xD2, "DEC2r": 0xD3,
|
||||
"AND2r": 0xD5, "BOR2r": 0xD6, "XOR2r": 0xD7, "SHR2r": 0xD8, "SHL2r": 0xD9,
|
||||
"JMP2r": 0xDC, "CAL2r": 0xDD, "JCD2r": 0xDE, "RET2r": 0xDF,
|
||||
"GCH2r": 0xE3, "GST2r": 0xE4, "PCH2r": 0xE5, "PST2r": 0xE6, "HLT2r": 0xE7, "PHX2r": 0xE8,
|
||||
"GRT2r": 0xEA, "LST2r": 0xEB, "GTE2r": 0xEC, "LTE2r": 0xED, "EQL2r": 0xEE, "NEQ2r": 0xEF,
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -39,4 +64,6 @@ func main() {
|
|||
|
||||
setIn()
|
||||
setOut(*outFile)
|
||||
program := newProg()
|
||||
program.Compile()
|
||||
}
|
||||
|
|
330
types.go
330
types.go
|
@ -3,102 +3,296 @@ package main
|
|||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
const (
|
||||
errTmplt string = "line %d: %s: \033[1m%s\033[0m"
|
||||
)
|
||||
|
||||
type token struct {
|
||||
val string
|
||||
line uint16
|
||||
}
|
||||
|
||||
type label struct {
|
||||
ref uint16
|
||||
count uint16
|
||||
parent string
|
||||
isSublabel bool
|
||||
}
|
||||
|
||||
func (l *label) Inc() {
|
||||
l.count++
|
||||
memContext bool
|
||||
}
|
||||
|
||||
type prog struct {
|
||||
labels map[string]label
|
||||
sublabels map[string]label
|
||||
macros map[string]string
|
||||
macros map[string][]token
|
||||
source string
|
||||
mp uint16
|
||||
cp uint16
|
||||
unknownWords []string
|
||||
tokens []token
|
||||
errors []string
|
||||
memContext bool
|
||||
labelCounts map[string]uint16
|
||||
}
|
||||
|
||||
func newProg() prog {
|
||||
return prog{
|
||||
make(map[string]label),
|
||||
make(map[string][]token),
|
||||
loadFile(in),
|
||||
0,
|
||||
make([]token, 0, 0),
|
||||
make([]string, 0, 5),
|
||||
false,
|
||||
make(map[string]uint16),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *prog) Compile() {
|
||||
// 1. Strip comments from source file
|
||||
p.stripComments()
|
||||
// 1. Tokenize the source, stripping comments
|
||||
p.Tokenize(p.source)
|
||||
|
||||
// 2. Expand macros
|
||||
p.expandMacros()
|
||||
|
||||
}
|
||||
|
||||
func (p *prog) stripComments() {
|
||||
if verbose {
|
||||
fmt.Fprint(os.Stderr, "Stripping comments\n")
|
||||
fmt.Fprintf(os.Stderr, "+ \033[1mExpanding macros\033[0m\n\tFound %d macros.\n", len(p.macros))
|
||||
}
|
||||
slc := regexp.MustCompile(`\/\/.*`)
|
||||
mlc := regexp.MustCompile(`(?s)\/\*.*\*\/`)
|
||||
p.source = slc.ReplaceAllString(p.source, "")
|
||||
p.source = mlc.ReplaceAllString(p.source, "")
|
||||
}
|
||||
|
||||
func (p *prog) expandMacros() {
|
||||
if verbose {
|
||||
fmt.Fprint(os.Stderr, "+ Expanding macros\n")
|
||||
}
|
||||
// Found all macro declarations
|
||||
re := regexp.MustCompile(`%(\w+)\s+([^;]*)\s+\;`)
|
||||
macrosFound := re.FindAllStringSubmatch(p.source, -1)
|
||||
|
||||
// If there were none, return
|
||||
if macrosFound == nil {
|
||||
if len(p.macros) == 0 {
|
||||
if verbose {
|
||||
fmt.Fprint(os.Stderr, "\t└ No macros were found\n")
|
||||
fmt.Fprint(os.Stderr, "\tSkipping.\n")
|
||||
}
|
||||
} else {
|
||||
p.tokens = p.expandMacros(p.tokens)
|
||||
if verbose {
|
||||
fmt.Fprintf(os.Stderr, "\tExpanded %d macro references.\n\tDone.\n", expansions)
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Build labels and other memory constructs
|
||||
p.tokens = p.buildLabels(p.tokens)
|
||||
if verbose {
|
||||
fmt.Fprintf(os.Stderr, "\t%d label definitions found\n\tDone.\n", len(p.labels))
|
||||
}
|
||||
|
||||
// 4. Add the end of defined memory to the beginning of the token stream
|
||||
// this will allow dynamic memory allocations without overwriting existing
|
||||
// data. It will also make the offset change for the
|
||||
heapStart := fmt.Sprintf("%04x", len(p.tokens)+2+int(p.mp))
|
||||
t := []token{token{heapStart[:2],0}, token{heapStart[2:],0}}
|
||||
t = append(t, p.tokens...)
|
||||
|
||||
// 5. Build the byte slice that is the final compiled file
|
||||
if verbose {
|
||||
fmt.Fprint(os.Stderr, "+ \033[1mGenerating bytes from token stream\033[0m\n")
|
||||
}
|
||||
b := make([]byte, 0, len(t))
|
||||
for i := 0; i < len(t); i++ {
|
||||
if isHex(t[i].val) {
|
||||
// Write a hex value
|
||||
n, err := strconv.ParseUint(t[i].val, 16, 8)
|
||||
if err != nil {
|
||||
p.errors = append(p.errors, fmt.Sprintf(errTmplt, t[i].line, "byte out or range", t[i].val))
|
||||
continue
|
||||
}
|
||||
b = append(b, byte(n))
|
||||
} else if v, ok := ops[t[i].val]; ok {
|
||||
// Write the op value
|
||||
b = append(b, v)
|
||||
} else if t[i].val[0] == ':' {
|
||||
// Fill in the label reference
|
||||
name := t[i].val[1:]
|
||||
if v, ok := p.labels[name]; ok {
|
||||
b = append(b, ops["PSH2"])
|
||||
i++
|
||||
// Make the reference apply to the memory area
|
||||
if v.memContext {
|
||||
v.ref+=uint16(len(t))
|
||||
}
|
||||
b = append(b, byte(v.ref >> 8))
|
||||
i++
|
||||
b = append(b, byte(v.ref & uint16(0xFF)))
|
||||
} else {
|
||||
p.errors = append(p.errors, fmt.Sprintf(errTmplt, t[i].line, "Undefined label", t[i].val))
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
p.errors = append(p.errors, fmt.Sprintf(errTmplt, t[i].line, "Unknown token", t[i].val))
|
||||
}
|
||||
}
|
||||
if verbose {
|
||||
fmt.Fprint(os.Stderr, "\tDone.\n+ \033[1mChecking for errors\033[0m\n")
|
||||
}
|
||||
|
||||
// 6. If there are errors, bail out now and show them
|
||||
if len(p.errors) > 0 {
|
||||
fmt.Fprintf(os.Stderr, "%s\n", strings.Join(p.errors, "\n"))
|
||||
os.Exit(compileError)
|
||||
}
|
||||
if verbose {
|
||||
fmt.Fprint(os.Stderr, "\tDone.\n")
|
||||
}
|
||||
if dryRun {
|
||||
return
|
||||
}
|
||||
|
||||
// Load the macros into our map
|
||||
for i := range macrosFound {
|
||||
p.macros[macrosFound[i][1]] = macrosFound[i][2]
|
||||
if verbose {
|
||||
fmt.Fprint(os.Stderr, "+ \033[1mWriting to file\033[0m\n")
|
||||
}
|
||||
|
||||
// Remove the declarations
|
||||
p.source = re.ReplaceAllString(p.source, "")
|
||||
|
||||
var twice bool
|
||||
Twice:
|
||||
// Expand the macros in the raw source
|
||||
for k, v := range p.macros {
|
||||
reM := regexp.MustCompile(`(?m)(\s|^)(?:`+k+`)(\s|$)`)
|
||||
p.source = reM.ReplaceAllString(p.source, `${1}`+v+`${2}`)
|
||||
}
|
||||
if !twice {
|
||||
twice = true
|
||||
goto Twice
|
||||
// 7. write it all to file. Done.
|
||||
err := os.WriteFile(out, b, 0644)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error writing to %s:\n %s", out, err.Error())
|
||||
return
|
||||
}
|
||||
if verbose {
|
||||
fmt.Fprintf(os.Stderr, "\t└ %d macros declarations were found and references were expanded\n", len(p.macros))
|
||||
fmt.Fprint(os.Stderr, "\tDone.\n\n")
|
||||
}
|
||||
fmt.Fprintf(os.Stdout, "Wrote %d bytes\n", len(b))
|
||||
}
|
||||
|
||||
func (p *prog) expandMacros(t []token) []token {
|
||||
tokens := make([]token, 0, len(t))
|
||||
for _, v := range t {
|
||||
if macroData, ok := p.macros[v.val]; ok {
|
||||
// Macro, expand it (recursively)
|
||||
expansions++
|
||||
tokens = append(tokens, p.expandMacros(macroData)...)
|
||||
} else {
|
||||
// Not a macro, just add it
|
||||
tokens = append(tokens, v)
|
||||
}
|
||||
}
|
||||
return tokens
|
||||
}
|
||||
|
||||
/* 1. Strip comments (using regex for dev speed at the moment)
|
||||
* 2. Do a pass for expanding macros
|
||||
* 3. Do a pass for finding labels
|
||||
* 4.
|
||||
*
|
||||
* :Label1 (refers to program counter)
|
||||
* @var1 (var is a pointer to memory)
|
||||
* +subvar (creates a subvar at current mem pointer)
|
||||
* $2 (pads the mem pointer to create sizes of vars)
|
||||
* %macro1 #2c pch .
|
||||
* Err should be thrown if an attempt to jump tp a var/subvar
|
||||
* is made.
|
||||
*/
|
||||
func (p *prog) buildLabels(t []token) []token {
|
||||
if verbose {
|
||||
fmt.Fprint(os.Stderr, "+ \033[1mBuilding label definitions\033[0m\n")
|
||||
}
|
||||
tokens := make([]token, 0, len(t))
|
||||
for _, l := range t {
|
||||
switch l.val[0] {
|
||||
case '@':
|
||||
// parse label definition
|
||||
if _, ok := p.labels[l.val[1:]]; ok {
|
||||
p.errors = append(p.errors, fmt.Sprintf(errTmplt, l.line, "Cannot redefine", l.val))
|
||||
continue
|
||||
}
|
||||
var la label
|
||||
// TODO
|
||||
// This is wrong. It cannot be known yet.
|
||||
// Need to add an update step for address (unless it is memContext true)
|
||||
if p.memContext {
|
||||
la.ref = p.mp
|
||||
la.memContext = true
|
||||
} else {
|
||||
la.ref = uint16(len(tokens))+2
|
||||
}
|
||||
p.labels[l.val[1:]] = la
|
||||
case '$':
|
||||
// pad memory
|
||||
if !p.memContext {
|
||||
p.errors = append(p.errors, fmt.Sprintf(errTmplt, l.line, "Memory padd in invalid context", "!program"))
|
||||
continue
|
||||
}
|
||||
num := l.val[1:]
|
||||
s, err := strconv.ParseUint(num, 16, 8)
|
||||
if err != nil {
|
||||
p.errors = append(p.errors, fmt.Sprintf(errTmplt, l.line, "Invalid pad value", l.val))
|
||||
continue
|
||||
}
|
||||
p.mp += uint16(s)
|
||||
case '!':
|
||||
// Memory context change
|
||||
if l.val[1:] == "memory" {
|
||||
p.memContext = true
|
||||
} else if l.val[1:] == "program" {
|
||||
p.memContext = false
|
||||
} else {
|
||||
p.errors = append(p.errors, fmt.Sprintf(errTmplt, l.line, "Undefined", l.val))
|
||||
continue
|
||||
}
|
||||
default:
|
||||
tokens = append(tokens, l)
|
||||
}
|
||||
}
|
||||
// TODO
|
||||
// Add verbose output
|
||||
return tokens
|
||||
}
|
||||
|
||||
func (p *prog) Tokenize(s string) {
|
||||
lexLine = 1
|
||||
reader := strings.NewReader(s)
|
||||
tokens := make([]token, 0)
|
||||
var currentString string
|
||||
if verbose {
|
||||
fmt.Fprint(os.Stderr, "+ \033[1mLexing input file\033[0m\n")
|
||||
}
|
||||
|
||||
TokenizationLoop:
|
||||
for reader.Len() > 0 {
|
||||
c, _, err := reader.ReadRune()
|
||||
if err != nil {
|
||||
break TokenizationLoop
|
||||
}
|
||||
|
||||
if unicode.IsSpace(c) {
|
||||
if c == '\n' {
|
||||
lexLine++
|
||||
}
|
||||
eatWhiteSpace(reader)
|
||||
continue
|
||||
}
|
||||
|
||||
switch c {
|
||||
case '[', '{', ']', '}':
|
||||
// Allow these chars, but assign no meaning to them
|
||||
continue
|
||||
case '/':
|
||||
c, _, err := reader.ReadRune()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Incomplete comment started on line %d\n", lexLine)
|
||||
os.Exit(lexError)
|
||||
}
|
||||
if c == '/' {
|
||||
eatSingleLineComment(reader)
|
||||
} else if c == '*' {
|
||||
eatMultiLineComment(reader)
|
||||
} else {
|
||||
fmt.Fprintf(os.Stderr, "Illegal char found following '/' on line %d,\nexpected '/' or '*'", lexLine)
|
||||
os.Exit(lexError)
|
||||
}
|
||||
case '\'':
|
||||
c, _, err := reader.ReadRune()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Incomplete char literal on line %d\n", lexLine)
|
||||
os.Exit(lexError)
|
||||
}
|
||||
tokens = append(tokens, []token{token{"PSH", lexLine}, token{fmt.Sprintf("%02x", c), lexLine}}...)
|
||||
case '#':
|
||||
tokens = append(tokens, eatLiteral(reader)...)
|
||||
case '%':
|
||||
name, data := eatMacro(reader)
|
||||
if _, ok := p.macros[name]; ok {
|
||||
fmt.Fprintf(os.Stderr, "Macro %q was re-declared on line %d", name, lexLine)
|
||||
os.Exit(lexError)
|
||||
}
|
||||
p.macros[name] = data
|
||||
case ':':
|
||||
reader.UnreadRune()
|
||||
currentString = eatText(reader)
|
||||
if currentString != "" {
|
||||
tokens = append(tokens, []token{token{currentString, lexLine},token{"NOP", lexLine}, token{"NOP", lexLine}}...)
|
||||
}
|
||||
default:
|
||||
reader.UnreadRune()
|
||||
currentString = eatText(reader)
|
||||
if currentString != "" {
|
||||
tokens = append(tokens, token{currentString, lexLine})
|
||||
}
|
||||
}
|
||||
}
|
||||
if verbose {
|
||||
fmt.Fprintf(os.Stderr, "\tDone.\n")
|
||||
}
|
||||
p.tokens = tokens
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue