package text import ( "io" "jinx/pkg/lang/vm/code" "jinx/pkg/libs/source" "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) { builder := code.NewBuilder() 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) builder.AppendRaw(line) if len(line) > 0 { builder.AppendLine(cpl.src.Loc().Row - 1) } } if err := cpl.linkLabels(&builder); err != nil { return code.Code{}, err } return builder.Build(), nil } func (cpl *Compiler) compileLine() (code.Raw, error) { builder := code.NewBuilder() 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 } builder.AppendOp(op) // Find the value, of which there is at most one. if value != "" { val, err := cpl.compileValue(value) if err != nil { return nil, err } builder.AppendRaw(val) } return builder.Code(), 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) (code.Raw, error) { builder := code.NewBuilder() // 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. }) 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 nil, err } builder.AppendFloat(val) } else { val, err := strconv.ParseInt(str, 10, 64) if err != nil { return nil, err } builder.AppendInt(val) } return builder.Code(), nil } if str[0] == '"' { str = strings.Trim(str, "\"") builder.AppendString(str) return builder.Code(), nil } return nil, 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(builder *code.Builder) error { for _, ref := range cpl.labelReferences { pos, ok := cpl.labelPositions[ref.label] if !ok { return ErrUnkonwnLabel{ref.label} } builder.SetInt(ref.at, int64(pos)) } return nil }