diff options
| author | Mel <einebeere@gmail.com> | 2022-05-18 01:37:09 +0200 |
|---|---|---|
| committer | Mel <einebeere@gmail.com> | 2022-05-18 01:37:09 +0200 |
| commit | 3abe18ffca484efc3553aa4ec9cb677eb19cdaf4 (patch) | |
| tree | 15dfc5f349ca35ca150c88a3c1db4e43ab6c0320 /pkg/lang/vm/text/compiler.go | |
| parent | ec5ee8647bcbf6ab073711c6892710776925c54d (diff) | |
| download | jinx-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.go | 202 |
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 +} |
