commit 085907af825d30ef637384cc660ead2ec1ffa89a
parent 0053a177b91ca6804dc08e416d9b319be703bef6
Author: bsandro <email@bsandro.tech>
Date: Mon, 4 Jul 2022 01:13:06 +0300
parsing and dumping a sample .ums (umx assembly) file
Diffstat:
6 files changed, 155 insertions(+), 9 deletions(-)
diff --git a/asm/instruction.go b/asm/instruction.go
@@ -2,6 +2,10 @@ package asm
import (
"bytes"
+ "errors"
+ "fmt"
+ "strconv"
+ "strings"
)
type Instruction struct {
@@ -10,6 +14,50 @@ type Instruction struct {
Value uint32
}
+func NewInstruction(input []string) (*Instruction, error) {
+ cnt := len(input)
+ if cnt == 0 {
+ return nil, errors.New("invalid input - array cannot be empty")
+ }
+ opcode := NewOpcode(strings.ToUpper(input[0]))
+ instr := Instruction{Opcode: opcode}
+ errmsg := fmt.Sprintf("invalid number of operands for opcode %s", input[0])
+
+ switch opcode {
+ case CMOV, ARRI, ARRA, ADD, MUL, DIV, NOTA:
+ if cnt != 4 {
+ return nil, errors.New(errmsg)
+ }
+ instr.RegA = NewRegister(input[1])
+ instr.RegB = NewRegister(input[2])
+ instr.RegC = NewRegister(input[3])
+ case HALT: // no operands
+ case ALLO, LOAD:
+ if cnt != 3 {
+ return nil, errors.New(errmsg)
+ }
+ instr.RegB = NewRegister(input[1])
+ instr.RegC = NewRegister(input[2])
+ case ABAN, OUTP, INP:
+ if cnt != 2 {
+ return nil, errors.New(errmsg)
+ }
+ instr.RegC = NewRegister(input[1])
+ case ORTH:
+ if cnt != 3 {
+ return nil, errors.New(errmsg)
+ }
+ val, err := strconv.ParseUint(input[2], 10, 32)
+ if err != nil {
+ return nil, err
+ }
+ instr.RegA = NewRegister(input[1])
+ instr.Value = uint32(val)
+ }
+
+ return &instr, nil
+}
+
func (i *Instruction) String() string {
var buf bytes.Buffer
buf.WriteString(i.Opcode.String())
@@ -20,14 +68,13 @@ func (i *Instruction) String() string {
dumpRegisters(&buf, i.RegA, i.RegB, i.RegC)
case HALT:
// no operands
- case ALLO:
+ case ALLO, LOAD:
dumpRegisters(&buf, i.RegB, i.RegC)
case ABAN, OUTP, INP:
dumpRegisters(&buf, i.RegC)
- case LOAD:
- dumpRegisters(&buf, i.RegB, i.RegC)
case ORTH:
dumpRegisters(&buf, i.RegA)
+ buf.WriteString(strconv.Itoa(int(i.Value)))
}
return buf.String()
diff --git a/asm/instruction_test.go b/asm/instruction_test.go
@@ -0,0 +1,21 @@
+package asm
+
+import "testing"
+
+func TestInstructionStringify(t *testing.T) {
+ input := []struct {
+ instr Instruction
+ str string
+ }{
+ {Instruction{Opcode: CMOV, RegA: 10, RegB: 20, RegC: 30}, "CMOV 10 20 30 "},
+ {Instruction{Opcode: OUTP, RegC: 54}, "OUTP 54 "},
+ {Instruction{Opcode: ORTH, RegA: 5, Value: 65535}, "ORTH 5 65535"},
+ {Instruction{Opcode: HALT}, "HALT "},
+ }
+
+ for _, tt := range input {
+ if tt.instr.String() != tt.str {
+ t.Fatalf(`invalid string for instruction %q, expected "%s", got "%s"`, tt.instr, tt.str, tt.instr.String())
+ }
+ }
+}
diff --git a/asm/opcode.go b/asm/opcode.go
@@ -1,5 +1,9 @@
package asm
+import (
+ "log"
+)
+
//go:generate go run golang.org/x/tools/cmd/stringer@latest -type=Opcode
type Opcode uint8
@@ -19,3 +23,39 @@ const (
LOAD
ORTH
)
+
+func NewOpcode(input string) Opcode {
+ switch {
+ case input == CMOV.String():
+ return CMOV
+ case input == ARRI.String():
+ return ARRI
+ case input == ARRA.String():
+ return ARRA
+ case input == ADD.String():
+ return ADD
+ case input == MUL.String():
+ return MUL
+ case input == DIV.String():
+ return DIV
+ case input == NOTA.String():
+ return NOTA
+ case input == HALT.String():
+ return HALT
+ case input == ALLO.String():
+ return ALLO
+ case input == ABAN.String():
+ return ABAN
+ case input == OUTP.String():
+ return OUTP
+ case input == INP.String():
+ return INP
+ case input == LOAD.String():
+ return LOAD
+ case input == ORTH.String():
+ return ORTH
+ default:
+ log.Fatalf("invalid opcode %s", input)
+ }
+ return CMOV // thanks Go
+}
diff --git a/asm/register.go b/asm/register.go
@@ -1,6 +1,7 @@
package asm
import (
+ "log"
"strconv"
)
@@ -9,3 +10,11 @@ type Register uint8
func (r Register) String() string {
return strconv.Itoa(int(r))
}
+
+func NewRegister(input string) Register {
+ val, err := strconv.ParseUint(input, 10, 8)
+ if err != nil {
+ log.Fatal(err)
+ }
+ return Register(uint8(val))
+}
diff --git a/main.go b/main.go
@@ -1,16 +1,38 @@
package main
import (
- "umx_asm/asm"
+ "bufio"
"fmt"
+ "os"
+ "regexp"
+ "strings"
+ "umx_asm/asm"
)
func main() {
+ re := regexp.MustCompile(`[a-zA-Z0-9]+`)
fmt.Println("umx_asm")
- instruction := &asm.Instruction{
- asm.CMOV,
- 10, 20, 30,
- 500,
+
+ file, err := os.Open("sample.ums")
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+ defer file.Close()
+
+ scanner := bufio.NewScanner(file)
+ for scanner.Scan() {
+ str, _, _ := strings.Cut(scanner.Text(), ";")
+ if len(str) > 0 {
+ ins, err := asm.NewInstruction(re.FindAllString(str, -1))
+ if err == nil {
+ fmt.Printf("%s\n", ins.String())
+ } else {
+ fmt.Printf("error: %q\n", err)
+ }
+ }
+ }
+ if err := scanner.Err(); err != nil {
+ fmt.Println(err)
}
- fmt.Println(instruction.String())
}
diff --git a/sample.ums b/sample.ums
@@ -0,0 +1,7 @@
+; sample file - comments are prepended with a semicolon
+; bsd licence, you know the drill
+
+cmov 1 2 3
+arri 1 8 15
+orth 5 493028
+halt