about summary refs log tree commit diff
path: root/pkg/lang/vm/text/compiler.go
diff options
context:
space:
mode:
authorMel <einebeere@gmail.com>2022-05-18 01:37:09 +0200
committerMel <einebeere@gmail.com>2022-05-18 01:37:09 +0200
commit3abe18ffca484efc3553aa4ec9cb677eb19cdaf4 (patch)
tree15dfc5f349ca35ca150c88a3c1db4e43ab6c0320 /pkg/lang/vm/text/compiler.go
parentec5ee8647bcbf6ab073711c6892710776925c54d (diff)
downloadjinx-3abe18ffca484efc3553aa4ec9cb677eb19cdaf4.tar.zst
jinx-3abe18ffca484efc3553aa4ec9cb677eb19cdaf4.zip
Create compiler for Lang bytecode assembly
Diffstat (limited to 'pkg/lang/vm/text/compiler.go')
-rw-r--r--pkg/lang/vm/text/compiler.go202
1 files changed, 202 insertions, 0 deletions
diff --git a/pkg/lang/vm/text/compiler.go b/pkg/lang/vm/text/compiler.go
new file mode 100644
index 0000000..2732aea
--- /dev/null
+++ b/pkg/lang/vm/text/compiler.go
@@ -0,0 +1,202 @@
+package text
+
+import (
+	"encoding/binary"
+	"io"
+	"jinx/pkg/lang/vm/code"
+	"jinx/pkg/libs/source"
+	"math"
+	"strconv"
+	"strings"
+	"unicode"
+)
+
+type Compiler struct {
+	src     source.Walker
+	codePos int
+
+	labelPositions  map[string]int
+	labelReferences []labelReference
+}
+
+func NewCompiler(src io.Reader) *Compiler {
+	return &Compiler{
+		src:     *source.NewWalker(src),
+		codePos: 0,
+
+		labelPositions:  map[string]int{},
+		labelReferences: []labelReference{},
+	}
+}
+
+func (cpl *Compiler) Compile() (code.Code, error) {
+	res := []byte{}
+
+	for {
+		_, eof, err := cpl.src.Peek()
+		if err != nil {
+			return code.Code{}, err
+		}
+
+		if eof {
+			break
+		}
+
+		line, err := cpl.compileLine()
+		if err != nil {
+			return code.Code{}, err
+		}
+
+		cpl.codePos += len(line)
+		res = append(res, line...)
+	}
+
+	if err := cpl.linkLabels(res); err != nil {
+		return code.Code{}, err
+	}
+
+	return code.New(res), nil
+}
+
+func (cpl *Compiler) compileLine() ([]byte, error) {
+	res := []byte{}
+
+	start, value, err := cpl.splitLine()
+	if err != nil || start == "" {
+		return nil, err
+	}
+
+	// Ignore lines starting with a comment.
+	if start[0] == '#' {
+		return nil, nil
+	}
+
+	// Save the position of the label.
+	if start[0] == '@' {
+		label := strings.Trim(start, "@:")
+		if _, ok := cpl.labelPositions[label]; ok {
+			return nil, ErrDuplicateLabel{label}
+		}
+		cpl.labelPositions[label] = cpl.codePos
+		return nil, nil
+	}
+
+	// Find the operator.
+	op, err := cpl.compileOp(start)
+	if err != nil {
+		return nil, err
+	}
+
+	res = append(res, byte(op))
+
+	// Find the value, of which there is at most one.
+	if value != "" {
+		val, err := cpl.compileValue(value)
+		if err != nil {
+			return nil, err
+		}
+
+		res = append(res, val...)
+	}
+
+	return res, nil
+}
+
+func (cpl *Compiler) compileOp(str string) (code.Op, error) {
+	op, err := StringToOp(str)
+	if err != nil {
+		return 0, err
+	}
+
+	return op, nil
+}
+
+func (cpl *Compiler) compileValue(str string) ([]byte, error) {
+	res := make([]byte, 8)
+
+	// Save label reference.
+	if str[0] == '@' {
+		label := strings.Trim(str, "@:")
+		cpl.labelReferences = append(cpl.labelReferences, labelReference{
+			label: label,
+			at:    cpl.codePos + 1, // +1 to skip the opcode.
+		})
+		return res, 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
+			}
+
+			binary.LittleEndian.PutUint64(res, math.Float64bits(val))
+		} else {
+			val, err := strconv.ParseInt(str, 10, 64)
+			if err != nil {
+				return res, err
+			}
+
+			binary.LittleEndian.PutUint64(res, uint64(val))
+		}
+
+		return res, nil
+	}
+
+	if str[0] == '"' {
+		str = strings.Trim(str, "\"")
+		res = []byte(str)
+		res = append(res, 0)
+
+		return res, nil
+	}
+
+	return res, ErrInvalidValue{str}
+}
+
+func (cpl *Compiler) splitLine() (string, string, error) {
+	line := ""
+
+	for {
+		c, eof, err := cpl.src.Next()
+		if err != nil {
+			return "", "", err
+		}
+
+		if eof || c == '\n' {
+			break
+		}
+
+		line += string(c)
+	}
+
+	fields := strings.Fields(line)
+	if len(fields) == 0 {
+		return "", "", nil
+	}
+
+	start := fields[0]
+	if len(fields) > 1 {
+		return start, strings.Join(fields[1:], " "), nil
+	}
+
+	return start, "", nil
+}
+
+type labelReference struct {
+	label string
+	at    int
+}
+
+func (cpl *Compiler) linkLabels(code []byte) 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))
+	}
+	return nil
+}