diff --git a/assembler/assemble_test.go b/assembler/assemble_test.go new file mode 100644 index 0000000..30570f7 --- /dev/null +++ b/assembler/assemble_test.go @@ -0,0 +1,30 @@ +package assembler + +import "testing" + +import "github.com/firestuff/subcoding/vm" + +func TestAssembleExecute(t *testing.T) { + prog, err := AssembleString(` +functions: +- - [add, f0, 1] + - [call, +1] + - [ltu, f0, 3] + - [jmpt, -3] + +- - [add, g0, 1] +`) + if err != nil { + t.Fatal(err) + } + + state, err := vm.NewState(prog) + if err != nil { + t.Fatal(err) + } + + err = state.Execute() + if err != nil { + t.Fatal(err) + } +} diff --git a/assembler/assembler.go b/assembler/assembler.go new file mode 100644 index 0000000..3fc0b06 --- /dev/null +++ b/assembler/assembler.go @@ -0,0 +1,138 @@ +package assembler + +import "fmt" +import "strconv" +import "strings" + +import "github.com/firestuff/subcoding/vm" +import "github.com/pkg/errors" + +var opCodeByName = map[string]vm.OpCodeType{ + "add": vm.OpAdd, + "call": vm.OpCall, + "ltu": vm.OpLTU, + "jmpt": vm.OpJmpT, +} + +var operandsByOpCode = map[vm.OpCodeType]int{ + vm.OpAdd: 2, + vm.OpCall: 1, + vm.OpLTU: 2, + vm.OpJmpT: 1, +} + +func Assemble(src []byte) ([][]*vm.Instruction, error) { + prog, err := parse(src) + if err != nil { + return nil, err + } + + fncs := [][]*vm.Instruction{} + + for f, fnc := range prog.Functions { + instrs, err := assembleFunction(fnc) + if err != nil { + return nil, errors.Wrapf(err, "At function index %d\n", f) + } + + fncs = append(fncs, instrs) + } + + return fncs, nil +} + +func AssembleString(src string) ([][]*vm.Instruction, error) { + return Assemble([]byte(src)) +} + +func assembleFunction(fnc function) ([]*vm.Instruction, error) { + instrs := []*vm.Instruction{} + + for i, in := range fnc { + instr, err := assembleInstruction(in) + if err != nil { + return nil, errors.Wrapf(err, "At instruction index %d\n", i) + } + + instrs = append(instrs, instr) + } + + return instrs, nil +} + +func assembleInstruction(in instruction) (*vm.Instruction, error) { + if len(in) < 1 { + return nil, fmt.Errorf("Empty instruction") + } + + instr := &vm.Instruction{} + + opCode, found := opCodeByName[in[0]] + if !found { + return nil, fmt.Errorf("Invalid op name: %s\n", in[0]) + } + + instr.OpCode = opCode + + operands := in[1:] + if len(operands) != operandsByOpCode[instr.OpCode] { + return nil, fmt.Errorf("Incorrect number of operands: expected %d, found %d\n", operandsByOpCode[instr.OpCode], len(operands)) + } + + if len(operands) >= 1 { + op1, err := assembleOperand(operands[0]) + if err != nil { + return nil, errors.Wrapf(err, "In first operand") + } + + instr.Operand1 = op1 + } + + if len(operands) >= 2 { + op2, err := assembleOperand(operands[0]) + if err != nil { + return nil, errors.Wrapf(err, "In second operand") + } + + instr.Operand2 = op2 + } + + return instr, nil +} + +func assembleOperand(op string) (vm.Operand, error) { + ret := vm.Operand{} + numStr := "" + + switch { + case strings.HasPrefix(op, "f"): + ret.Type = vm.FunctionMemoryIndex + numStr = strings.TrimPrefix(op, "f") + + case strings.HasPrefix(op, "g"): + ret.Type = vm.GlobalMemoryIndex + numStr = strings.TrimPrefix(op, "g") + + default: + ret.Type = vm.Literal + numStr = op + } + + switch { + case strings.HasPrefix(numStr, "+") || strings.HasPrefix(numStr, "-"): + num, err := strconv.ParseInt(numStr, 10, 64) + if err != nil { + return ret, errors.Wrapf(err, "While parsing operand value %s", numStr) + } + ret.Value = uint64(num) + + default: + num, err := strconv.ParseUint(numStr, 10, 64) + if err != nil { + return ret, errors.Wrapf(err, "While parsing operand value %s", numStr) + } + ret.Value = num + } + + return ret, nil +} diff --git a/assembler/go.mod b/assembler/go.mod index 2e175da..50c97a3 100644 --- a/assembler/go.mod +++ b/assembler/go.mod @@ -2,4 +2,10 @@ module github.com/firestuff/subcoding/assembler go 1.17 -require gopkg.in/yaml.v2 v2.4.0 // indirect +require ( + github.com/firestuff/subcoding/vm v0.0.0-20211119013410-6de2e87342fa + github.com/pkg/errors v0.9.1 + gopkg.in/yaml.v2 v2.4.0 +) + +require github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 // indirect diff --git a/assembler/go.sum b/assembler/go.sum index 7534661..bc61fed 100644 --- a/assembler/go.sum +++ b/assembler/go.sum @@ -1,3 +1,10 @@ +github.com/firestuff/subcoding/vm v0.0.0-20211119013410-6de2e87342fa h1:xx9ht6+TpFa19jx954x5+4koxcyxQHRbxItLnzPv6ug= +github.com/firestuff/subcoding/vm v0.0.0-20211119013410-6de2e87342fa/go.mod h1:s02/Ta9wsxRR9W5Uom7LoQEu4bpFQfJdhaIHaXzN0IY= +github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 h1:EnfXoSqDfSNJv0VBNqY/88RNnhSGYkrHaO0mmFGbVsc= +github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40/go.mod h1:vy1vK6wD6j7xX6O6hXe621WabdtNkou2h7uRtTfRMyg= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/assembler/parse.go b/assembler/parse.go index ae938c0..098690e 100644 --- a/assembler/parse.go +++ b/assembler/parse.go @@ -2,24 +2,21 @@ package assembler import "gopkg.in/yaml.v2" -type Program struct { - Functions []*Function `yaml:"functions"` +type program struct { + Functions []function `yaml:"functions"` } -type Function []*Instruction -type Instruction []string +type function []instruction -func NewProgramFromBytes(in []byte) (*Program, error) { - prog := &Program{} +type instruction []string - err := yaml.UnmarshalStrict(in, &prog) +func parse(src []byte) (*program, error) { + prog := &program{} + + err := yaml.UnmarshalStrict(src, &prog) if err != nil { return nil, err } return prog, nil } - -func NewProgramFromString(in string) (*Program, error) { - return NewProgramFromBytes([]byte(in)) -} diff --git a/assembler/parse_test.go b/assembler/parse_test.go deleted file mode 100644 index 3d7eb00..0000000 --- a/assembler/parse_test.go +++ /dev/null @@ -1,20 +0,0 @@ -package assembler - -import "testing" - -func TestSimple(t *testing.T) { - prog, err := NewProgramFromString(` -functions: -- - [add, f0, 1] - - [call, +1] - - [ltu, f0, 3] - - [jmpt, -3] - -- - [add, g0, 1] -`) - if err != nil { - t.Fatal(err) - } - - t.Logf("%+v", prog) -}