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 }