umx_asm

UMX virtual machine assembly compiler
git clone git://bsandro.tech/umx_asm
Log | Files | Refs

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:
Masm/instruction.go | 53++++++++++++++++++++++++++++++++++++++++++++++++++---
Aasm/instruction_test.go | 21+++++++++++++++++++++
Masm/opcode.go | 40++++++++++++++++++++++++++++++++++++++++
Masm/register.go | 9+++++++++
Mmain.go | 34++++++++++++++++++++++++++++------
Asample.ums | 7+++++++
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