There is an assembler now

This commit is contained in:
Sasha Koshka 2022-10-27 11:49:20 -04:00
parent 5b2e13dcf4
commit 565d733e0f
5 changed files with 332 additions and 0 deletions

1
.gitignore vendored
View File

@ -1 +1,2 @@
/marcie/marcie
/masm/masm

View File

@ -7,6 +7,7 @@ import "git.tebibyte.media/sashakoshka/marcie"
func main () {
if len(os.Args) != 2 {
fmt.Println("specify file name")
os.Exit(1)
}
file, err := os.Open(os.Args[1])

BIN
masm/hellorld Normal file

Binary file not shown.

25
masm/hellorld.mas Normal file
View File

@ -0,0 +1,25 @@
counter, adr string
one, dec 1
string, chr h
chr e
chr l
chr l
chr o
chr r
chr l
chr d
hex a
hex 0
// if the current character is zero, halt
loopStart, loadi counter
skipcond 800
halt
// otherwise, output it, increment, and loop
output
load counter
add one
store counter
jump loopStart

305
masm/main.go Normal file
View File

@ -0,0 +1,305 @@
package main
import "os"
import "fmt"
import "bufio"
import "errors"
import "strings"
import "strconv"
import "git.tebibyte.media/sashakoshka/marcie"
type Statement struct {
label string
opcode marcie.Opcode
lineNumber int
isOperation bool
operand int16
reference string
wantAddress bool
}
const ORG = marcie.Opcode(0xFF)
var mnemonics = map[string] marcie.Opcode {
"jns": marcie.JNS,
"load": marcie.LOAD,
"store": marcie.STORE,
"add": marcie.ADD,
"subt": marcie.SUBT,
"input": marcie.INPUT,
"output": marcie.OUTPUT,
"halt": marcie.HALT,
"skipcond": marcie.SKIPCOND,
"jump": marcie.JUMP,
"clear": marcie.CLEAR,
"addi": marcie.ADDI,
"jumpi": marcie.JUMPI,
"loadi": marcie.LOADI,
"storei": marcie.STOREI,
"org": ORG,
}
var literalParsers = map[string] func (input string) (output int16, err error) {
"hex": parseHexLiteral,
"dec": parseDecLiteral,
"chr": parseCharLiteral,
}
func main () {
if len(os.Args) != 3 {
fmt.Println("specify input and output file names")
os.Exit(1)
}
inputFile, err := os.Open(os.Args[1])
if err != nil {
fmt.Println(err)
os.Exit(1)
}
inputScanner := bufio.NewScanner(inputFile)
inputScanner.Split(bufio.ScanLines)
origin := 0
originSet := false
operations := []Statement { }
symbols := []Statement { }
symbolTable := map[string] int { }
// parse
for lineNumber := 0; inputScanner.Scan(); lineNumber ++ {
tokens := strings.Fields(inputScanner.Text())
if len(tokens) == 0 { continue }
statement, err := parseStatement(tokens)
statement.lineNumber = lineNumber
if statement.opcode == ORG {
if originSet {
err = errors.New("origin may only be set once")
} else {
origin = int(statement.operand)
originSet = true
}
}
if statement.opcode != ORG {
if statement.isOperation {
operations = append(operations, statement)
} else {
symbols = append(symbols, statement)
}
}
if err != nil {
fmt.Println (
"file", os.Args[1],
"line", lineNumber + 1,
err)
os.Exit(1)
}
}
// get symbol addresses
placer := origin
statements := append(operations, symbols...)
for _, statement := range statements {
if statement.label != "" {
_, alreadyDefined := symbolTable[statement.label]
if alreadyDefined {
err = errors.New (
"symbol " + statement.label +
"defined more than once")
} else {
symbolTable[statement.label] = placer
}
}
placer ++
}
if inputScanner.Err() != nil {
fmt.Println(inputScanner.Err())
os.Exit(1)
}
outputFile, err := os.Create(os.Args[2])
if err != nil {
fmt.Println(err)
os.Exit(1)
}
// for _, statement := range statements {
// fmt.Println(statement)
// }
//
// for symbol, address := range symbolTable {
// fmt.Println(symbol, address)
// }
// write origin
buffer := make([]byte, 2)
buffer[0] = byte((uint16(origin) >> 8) & 0xFF)
buffer[1] = byte(uint16(origin) & 0xFF)
_, err = outputFile.Write(buffer)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
// zero pad
// TODO: possibly make the simulator insert the program into the memory
// at the location specified by the origin instead of doing this
for index := 0; index < origin; index ++ {
_, err = outputFile.Write([]byte { 0, 0 })
if err != nil {
fmt.Println(err)
os.Exit(1)
}
}
// resolve symbols and write to file
for index := 0; index < len(statements); index ++ {
statement := &statements[index]
if statement.reference != "" {
address, exists := symbolTable[statement.reference]
if !exists {
fmt.Println (
"file", os.Args[1],
"line", statement.lineNumber + 1,
"symbol", statement.reference,
"not found")
os.Exit(1)
}
statement.operand = int16(address)
}
buffer := make([]byte, 2)
if statement.isOperation {
opcode := uint16(statement.opcode)
operand := uint16(statement.operand)
buffer[0] = byte((opcode << 4) & 0xF0)
buffer[0] |= byte((operand >> 8) & 0x0F)
buffer[1] = byte(operand & 0xFF)
} else {
operand := uint16(statement.operand)
buffer[0] = byte((operand >> 8) & 0xFF)
buffer[1] = byte(operand & 0xFF)
}
_, err = outputFile.Write(buffer)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
}
}
func parseStatement (tokens []string) (statement Statement, err error) {
if tokens[0][0] == '/' {
// skip line if it is a comment
return
}
// get label if it exists
possibleLabel := tokens[0]
if possibleLabel[len(possibleLabel) - 1] == ',' {
statement.label = possibleLabel[:len(possibleLabel) - 1]
tokens = tokens[1:]
}
// get mnemonic if it exists
if len(tokens) < 1 {
err = errors.New("expected operand")
return
}
mnemonic := strings.ToLower(tokens[0])
opcode, exists := mnemonics[mnemonic]
if exists {
statement.opcode = opcode
statement.isOperation = true
tokens = tokens[1:]
} else if len(tokens) < 1 {
err = errors.New("unknown opcode " + mnemonic)
return
}
// if there is no operand, go on to the next line
if len(tokens) < 1 { return }
// get type signifier if it exists
signifier := strings.ToLower(tokens[0])
parseFunction, exists := literalParsers[signifier]
if exists {
tokens = tokens[1:]
} else if statement.isOperation {
if tokens[0][0] >= '0' && tokens[0][0] <= '9' {
// this must be a hex literal
parseFunction = parseHexLiteral
} else {
// this must be a variable name
statement.reference = tokens[0]
tokens = tokens[1:]
return
}
} else if signifier == "adr" {
// if we are parsing a variable then we can set it to the
// address of another variable
tokens = tokens[1:]
if len(tokens) < 1 {
err = errors.New("expected symbol name")
return
}
statement.reference = tokens[0]
tokens = tokens[1:]
return
} else {
// statement needs to have a type signifier or be an
// operation, if it is neither we assume a mistyped
// opcode
err = errors.New("unknown opcode " + mnemonic)
return
}
if len(tokens) < 1 {
err = errors.New("expected literal")
return
}
statement.operand, err = parseFunction(tokens[0])
return
}
func parseHexLiteral (input string) (output int16, err error) {
var intOutput int64
intOutput, err = strconv.ParseInt(input, 16, 16)
output = int16(intOutput)
return
}
func parseDecLiteral (input string) (output int16, err error) {
var intOutput int64
intOutput, err = strconv.ParseInt(input, 10, 16)
output = int16(intOutput)
return
}
func parseCharLiteral (input string) (output int16, err error) {
runes := []rune(input)
if len(runes) != 1 {
err = errors.New("excess data in char literal")
return
}
output = int16(runes[0])
return
}