From 144f49d64e3fc11d334900e90d30c8620ca2991b Mon Sep 17 00:00:00 2001 From: Mel Date: Tue, 7 Jun 2022 09:36:27 +0000 Subject: Add builder for VM bytecode --- pkg/lang/vm/code/builder.go | 60 ++++++++++++++ pkg/lang/vm/code/code.go | 8 +- pkg/lang/vm/text/compiler.go | 55 ++++++------- pkg/lang/vm/text/compiler_test.go | 166 +++++++++++++++++++------------------- 4 files changed, 173 insertions(+), 116 deletions(-) create mode 100644 pkg/lang/vm/code/builder.go (limited to 'pkg') diff --git a/pkg/lang/vm/code/builder.go b/pkg/lang/vm/code/builder.go new file mode 100644 index 0000000..3aa6b6b --- /dev/null +++ b/pkg/lang/vm/code/builder.go @@ -0,0 +1,60 @@ +package code + +import ( + "encoding/binary" + "math" +) + +type Builder struct { + code []byte + debugInfo DebugInfo +} + +func NewBuilder() Builder { + return Builder{ + code: make([]byte, 0, 64), + debugInfo: NewDebugInfo("unknown file"), + } +} + +func (b *Builder) AppendOp(op Op) { + b.code = append(b.code, byte(op)) +} + +func (b *Builder) AppendInt(x int64) { + b.code = append(b.code, make([]byte, 8)...) + binary.LittleEndian.PutUint64(b.code[len(b.code)-8:], uint64(x)) +} + +func (b *Builder) AppendFloat(x float64) { + b.AppendInt(int64(math.Float64bits(x))) +} + +func (b *Builder) AppendString(s string) { + b.code = append(b.code, []byte(s)...) + b.code = append(b.code, 0) +} + +func (b *Builder) AppendRaw(raw Raw) { + b.code = append(b.code, raw...) +} + +func (b *Builder) AppendLine(line int) { + b.debugInfo.AppendLine(len(b.code)-1, line) +} + +func (b *Builder) SetInt(at int, x int64) { + binary.LittleEndian.PutUint64(b.code[at:], uint64(x)) +} + +func (b *Builder) Code() Raw { + return Raw(b.code) +} + +func (b *Builder) Len() int { + return len(b.code) +} + +func (b *Builder) Build() Code { + return New(b.code, b.debugInfo) +} diff --git a/pkg/lang/vm/code/code.go b/pkg/lang/vm/code/code.go index 38bbcf5..94fd612 100644 --- a/pkg/lang/vm/code/code.go +++ b/pkg/lang/vm/code/code.go @@ -7,12 +7,14 @@ import ( "strings" ) +type Raw []byte + type Code struct { - code []byte + code Raw debugInfo DebugInfo } -func New(code []byte, info DebugInfo) Code { +func New(code Raw, info DebugInfo) Code { return Code{ code: code, debugInfo: info, @@ -23,7 +25,7 @@ func (c *Code) Len() int { return len(c.code) } -func (c *Code) Code() []byte { +func (c *Code) Code() Raw { return c.code } diff --git a/pkg/lang/vm/text/compiler.go b/pkg/lang/vm/text/compiler.go index 1480171..68889eb 100644 --- a/pkg/lang/vm/text/compiler.go +++ b/pkg/lang/vm/text/compiler.go @@ -1,11 +1,9 @@ package text import ( - "encoding/binary" "io" "jinx/pkg/lang/vm/code" "jinx/pkg/libs/source" - "math" "strconv" "strings" "unicode" @@ -30,8 +28,7 @@ func NewCompiler(src io.Reader) *Compiler { } func (cpl *Compiler) Compile() (code.Code, error) { - res := []byte{} - info := code.NewDebugInfo("unknown file") + builder := code.NewBuilder() for { _, eof, err := cpl.src.Peek() @@ -50,22 +47,22 @@ func (cpl *Compiler) Compile() (code.Code, error) { cpl.codePos += len(line) - if line != nil { - info.AppendLine(cpl.codePos-1, cpl.src.Loc().Row-1) - } + builder.AppendRaw(line) - res = append(res, line...) + if len(line) > 0 { + builder.AppendLine(cpl.src.Loc().Row - 1) + } } - if err := cpl.linkLabels(res); err != nil { + if err := cpl.linkLabels(&builder); err != nil { return code.Code{}, err } - return code.New(res, info), nil + return builder.Build(), nil } -func (cpl *Compiler) compileLine() ([]byte, error) { - res := []byte{} +func (cpl *Compiler) compileLine() (code.Raw, error) { + builder := code.NewBuilder() start, value, err := cpl.splitLine() if err != nil || start == "" { @@ -93,7 +90,7 @@ func (cpl *Compiler) compileLine() ([]byte, error) { return nil, err } - res = append(res, byte(op)) + builder.AppendOp(op) // Find the value, of which there is at most one. if value != "" { @@ -102,10 +99,10 @@ func (cpl *Compiler) compileLine() ([]byte, error) { return nil, err } - res = append(res, val...) + builder.AppendRaw(val) } - return res, nil + return builder.Code(), nil } func (cpl *Compiler) compileOp(str string) (code.Op, error) { @@ -117,8 +114,8 @@ func (cpl *Compiler) compileOp(str string) (code.Op, error) { return op, nil } -func (cpl *Compiler) compileValue(str string) ([]byte, error) { - res := make([]byte, 8) +func (cpl *Compiler) compileValue(str string) (code.Raw, error) { + builder := code.NewBuilder() // Save label reference. if str[0] == '@' { @@ -127,38 +124,38 @@ func (cpl *Compiler) compileValue(str string) ([]byte, error) { label: label, at: cpl.codePos + 1, // +1 to skip the opcode. }) - return res, nil + builder.AppendInt(0) + return builder.Code(), nil } if unicode.IsDigit(rune(str[0])) || str[0] == '-' { if strings.Contains(str, ".") { val, err := strconv.ParseFloat(str, 64) if err != nil { - return res, err + return nil, err } - binary.LittleEndian.PutUint64(res, math.Float64bits(val)) + builder.AppendFloat(val) } else { val, err := strconv.ParseInt(str, 10, 64) if err != nil { - return res, err + return nil, err } - binary.LittleEndian.PutUint64(res, uint64(val)) + builder.AppendInt(val) } - return res, nil + return builder.Code(), nil } if str[0] == '"' { str = strings.Trim(str, "\"") - res = []byte(str) - res = append(res, 0) + builder.AppendString(str) - return res, nil + return builder.Code(), nil } - return res, ErrInvalidValue{str} + return nil, ErrInvalidValue{str} } func (cpl *Compiler) splitLine() (string, string, error) { @@ -195,14 +192,14 @@ type labelReference struct { at int } -func (cpl *Compiler) linkLabels(code []byte) error { +func (cpl *Compiler) linkLabels(builder *code.Builder) error { for _, ref := range cpl.labelReferences { pos, ok := cpl.labelPositions[ref.label] if !ok { return ErrUnkonwnLabel{ref.label} } - binary.LittleEndian.PutUint64(code[ref.at:], uint64(pos)) + builder.SetInt(ref.at, int64(pos)) } return nil } diff --git a/pkg/lang/vm/text/compiler_test.go b/pkg/lang/vm/text/compiler_test.go index 56b886d..c9e3fa1 100644 --- a/pkg/lang/vm/text/compiler_test.go +++ b/pkg/lang/vm/text/compiler_test.go @@ -1,10 +1,8 @@ package text_test import ( - "encoding/binary" "jinx/pkg/lang/vm/code" "jinx/pkg/lang/vm/text" - "math" "strings" "testing" @@ -22,13 +20,13 @@ func TestSimple(t *testing.T) { res, err := c.Compile() require.NoError(t, err) - parts := [][]byte{ - opBin(code.OpAdd), - opBin(code.OpSub), - opBin(code.OpRet), - } + exp := code.NewBuilder() - require.Equal(t, joinSlices(parts), res.Code()) + exp.AppendOp(code.OpAdd) + exp.AppendOp(code.OpSub) + exp.AppendOp(code.OpRet) + + require.Equal(t, exp.Code(), res.Code()) } func TestInt(t *testing.T) { @@ -43,16 +41,16 @@ func TestInt(t *testing.T) { res, err := c.Compile() require.NoError(t, err) - parts := [][]byte{ - opBin(code.OpPushInt), - uintBin(1), - opBin(code.OpPushInt), - uintBin(2), - opBin(code.OpAdd), - opBin(code.OpRet), - } + exp := code.NewBuilder() + + exp.AppendOp(code.OpPushInt) + exp.AppendInt(1) + exp.AppendOp(code.OpPushInt) + exp.AppendInt(2) + exp.AppendOp(code.OpAdd) + exp.AppendOp(code.OpRet) - require.Equal(t, joinSlices(parts), res.Code()) + require.Equal(t, exp.Code(), res.Code()) } func TestFloat(t *testing.T) { @@ -65,14 +63,14 @@ func TestFloat(t *testing.T) { res, err := c.Compile() require.NoError(t, err) - parts := [][]byte{ - opBin(code.OpPushFloat), - floatBin(3.1415), - opBin(code.OpPushFloat), - floatBin(-2.71828), - } + exp := code.NewBuilder() + + exp.AppendOp(code.OpPushFloat) + exp.AppendFloat(3.1415) + exp.AppendOp(code.OpPushFloat) + exp.AppendFloat(-2.71828) - require.Equal(t, joinSlices(parts), res.Code()) + require.Equal(t, exp.Code(), res.Code()) } func TestString(t *testing.T) { @@ -86,15 +84,15 @@ func TestString(t *testing.T) { res, err := c.Compile() require.NoError(t, err) - parts := [][]byte{ - opBin(code.OpPushString), - stringBin("Hello, "), - opBin(code.OpPushString), - stringBin("world!"), - opBin(code.OpAdd), - } + exp := code.NewBuilder() - require.Equal(t, joinSlices(parts), res.Code()) + exp.AppendOp(code.OpPushString) + exp.AppendString("Hello, ") + exp.AppendOp(code.OpPushString) + exp.AppendString("world!") + exp.AppendOp(code.OpAdd) + + require.Equal(t, exp.Code(), res.Code()) } func TestLabels(t *testing.T) { @@ -114,19 +112,19 @@ func TestLabels(t *testing.T) { res, err := c.Compile() require.NoError(t, err) - parts := [][]byte{ - opBin(code.OpNop), - opBin(code.OpNop), - opBin(code.OpNop), - opBin(code.OpJmp), - uintBin(0), - opBin(code.OpJmp), - uintBin(1), - opBin(code.OpJmp), - uintBin(2), - } - - require.Equal(t, joinSlices(parts), res.Code()) + exp := code.NewBuilder() + + exp.AppendOp(code.OpNop) + exp.AppendOp(code.OpNop) + exp.AppendOp(code.OpNop) + exp.AppendOp(code.OpJmp) + exp.AppendInt(0) + exp.AppendOp(code.OpJmp) + exp.AppendInt(1) + exp.AppendOp(code.OpJmp) + exp.AppendInt(2) + + require.Equal(t, exp.Code(), res.Code()) } func TestDebugInfo(t *testing.T) { @@ -146,46 +144,46 @@ func TestDebugInfo(t *testing.T) { res, err := c.Compile() require.NoError(t, err) - expected := code.NewDebugInfo("unknown file") - - expected.AppendLine(8, 1) - expected.AppendLine(17, 2) - expected.AppendLine(18, 4) - expected.AppendLine(19, 5) - expected.AppendLine(20, 8) - expected.AppendLine(21, 9) + exp := code.NewDebugInfo("unknown file") - require.Equal(t, expected, *res.Debug()) -} + exp.AppendLine(8, 1) + exp.AppendLine(17, 2) + exp.AppendLine(18, 4) + exp.AppendLine(19, 5) + exp.AppendLine(20, 8) + exp.AppendLine(21, 9) -func opBin(op code.Op) []byte { - return []byte{byte(op)} + require.Equal(t, exp, *res.Debug()) } -func uintBin(x uint64) []byte { - res := make([]byte, 8) - binary.LittleEndian.PutUint64(res, x) - return res -} - -func floatBin(x float64) []byte { - res := make([]byte, 8) - binary.LittleEndian.PutUint64(res, math.Float64bits(x)) - return res -} - -func stringBin(x string) []byte { - res := []byte(x) - res = append(res, 0) - return res -} - -func joinSlices[T any](slices [][]T) []T { - res := []T{} - - for _, slice := range slices { - res = append(res, slice...) - } - - return res -} +// func opBin(op code.Op) []byte { +// return []byte{byte(op)} +// } + +// func uintBin(x uint64) []byte { +// res := make([]byte, 8) +// binary.LittleEndian.PutUint64(res, x) +// return res +// } + +// func floatBin(x float64) []byte { +// res := make([]byte, 8) +// binary.LittleEndian.PutUint64(res, math.Float64bits(x)) +// return res +// } + +// func stringBin(x string) []byte { +// res := []byte(x) +// res = append(res, 0) +// return res +// } + +// func joinSlices[T any](slices [][]T) []T { +// res := []T{} + +// for _, slice := range slices { +// res = append(res, slice...) +// } + +// return res +// } -- cgit 1.4.1