This commit is contained in:
parent
f7be86a3d9
commit
31c5ed614c
27 changed files with 548 additions and 61 deletions
|
@ -1,4 +1,29 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"git.urbach.dev/cli/q/src/cpu"
|
||||
"git.urbach.dev/cli/q/src/token"
|
||||
)
|
||||
|
||||
// Compile turns a function into machine code.
|
||||
func (f *Function) Compile() {}
|
||||
func (f *Function) Compile() {
|
||||
registerCount := 0
|
||||
|
||||
for _, input := range f.Input {
|
||||
f.Identifiers[input.Name] = f.AppendRegister(cpu.Register(registerCount))
|
||||
registerCount++
|
||||
|
||||
if input.TypeTokens[0].Kind == token.ArrayStart {
|
||||
f.Identifiers[input.Name+".length"] = f.AppendRegister(cpu.Register(registerCount))
|
||||
registerCount++
|
||||
}
|
||||
}
|
||||
|
||||
for instr := range f.Body.Instructions {
|
||||
f.Err = f.CompileInstruction(instr)
|
||||
|
||||
if f.Err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
27
src/core/CompileInstruction.go
Normal file
27
src/core/CompileInstruction.go
Normal file
|
@ -0,0 +1,27 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"git.urbach.dev/cli/q/src/expression"
|
||||
"git.urbach.dev/cli/q/src/token"
|
||||
)
|
||||
|
||||
func (f *Function) CompileInstruction(instr token.List) error {
|
||||
if instr[0].IsKeyword() {
|
||||
switch instr[0].Kind {
|
||||
case token.Return:
|
||||
return f.CompileReturn(instr)
|
||||
}
|
||||
}
|
||||
|
||||
expr := expression.Parse(instr)
|
||||
|
||||
if expr.Token.Kind == token.Define {
|
||||
name := expr.Children[0].String(f.File.Bytes)
|
||||
value, err := f.Evaluate(expr.Children[1])
|
||||
f.Identifiers[name] = value
|
||||
return err
|
||||
}
|
||||
|
||||
_, err := f.Evaluate(expr)
|
||||
return err
|
||||
}
|
24
src/core/CompileReturn.go
Normal file
24
src/core/CompileReturn.go
Normal file
|
@ -0,0 +1,24 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"git.urbach.dev/cli/q/src/expression"
|
||||
"git.urbach.dev/cli/q/src/ssa"
|
||||
"git.urbach.dev/cli/q/src/token"
|
||||
)
|
||||
|
||||
// CompileReturn compiles a return instruction.
|
||||
func (f *Function) CompileReturn(tokens token.List) error {
|
||||
expr := expression.Parse(tokens[1:])
|
||||
value, err := f.Evaluate(expr)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f.Append(ssa.Value{
|
||||
Type: ssa.Return,
|
||||
Args: []*ssa.Value{value},
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
84
src/core/Evaluate.go
Normal file
84
src/core/Evaluate.go
Normal file
|
@ -0,0 +1,84 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.urbach.dev/cli/q/src/errors"
|
||||
"git.urbach.dev/cli/q/src/expression"
|
||||
"git.urbach.dev/cli/q/src/ssa"
|
||||
"git.urbach.dev/cli/q/src/token"
|
||||
)
|
||||
|
||||
// Evaluate converts an expression to an SSA value.
|
||||
func (f *Function) Evaluate(expr *expression.Expression) (*ssa.Value, error) {
|
||||
if expr.IsLeaf() {
|
||||
switch expr.Token.Kind {
|
||||
case token.Identifier:
|
||||
name := expr.Token.String(f.File.Bytes)
|
||||
value, exists := f.Identifiers[name]
|
||||
|
||||
if !exists {
|
||||
return nil, errors.New(&UnknownIdentifier{Name: name}, f.File, expr.Token.Position)
|
||||
}
|
||||
|
||||
return value, nil
|
||||
|
||||
case token.Number:
|
||||
number, err := f.ToNumber(expr.Token)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return f.AppendInt(number), nil
|
||||
|
||||
case token.String:
|
||||
data := expr.Token.Bytes(f.File.Bytes)
|
||||
data = Unescape(data)
|
||||
return f.AppendBytes(data), nil
|
||||
}
|
||||
|
||||
return nil, errors.New(InvalidExpression, f.File, expr.Token.Position)
|
||||
}
|
||||
|
||||
switch expr.Token.Kind {
|
||||
case token.Call:
|
||||
children := expr.Children
|
||||
typ := ssa.Call
|
||||
|
||||
if children[0].Token.Kind == token.Identifier {
|
||||
funcName := children[0].String(f.File.Bytes)
|
||||
|
||||
if funcName == "len" {
|
||||
identifier := children[1].String(f.File.Bytes)
|
||||
return f.Identifiers[identifier+".length"], nil
|
||||
}
|
||||
|
||||
if funcName == "syscall" {
|
||||
children = children[1:]
|
||||
typ = ssa.Syscall
|
||||
}
|
||||
}
|
||||
|
||||
args := make([]*ssa.Value, len(children))
|
||||
|
||||
for i, child := range children {
|
||||
value, err := f.Evaluate(child)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
args[i] = value
|
||||
}
|
||||
|
||||
call := f.Append(ssa.Value{Type: typ, Args: args})
|
||||
return call, nil
|
||||
|
||||
case token.Dot:
|
||||
name := fmt.Sprintf("%s.%s", expr.Children[0].String(f.File.Bytes), expr.Children[1].String(f.File.Bytes))
|
||||
return f.AppendFunction(name), nil
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
|
@ -11,20 +11,26 @@ import (
|
|||
// Function is the smallest unit of code.
|
||||
type Function struct {
|
||||
ssa.Function
|
||||
Name string
|
||||
UniqueName string
|
||||
File *fs.File
|
||||
Input []*Parameter
|
||||
Output []*Parameter
|
||||
Body token.List
|
||||
Name string
|
||||
UniqueName string
|
||||
File *fs.File
|
||||
Input []*Parameter
|
||||
Output []*Parameter
|
||||
Body token.List
|
||||
Identifiers map[string]*ssa.Value
|
||||
Err error
|
||||
}
|
||||
|
||||
// NewFunction creates a new function.
|
||||
func NewFunction(name string, file *fs.File) *Function {
|
||||
return &Function{
|
||||
Name: name,
|
||||
File: file,
|
||||
UniqueName: fmt.Sprintf("%s.%s", file.Package, name),
|
||||
Name: name,
|
||||
File: file,
|
||||
UniqueName: fmt.Sprintf("%s.%s", file.Package, name),
|
||||
Identifiers: make(map[string]*ssa.Value),
|
||||
Function: ssa.Function{
|
||||
Blocks: []*ssa.Block{{}},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
63
src/core/ToNumber.go
Normal file
63
src/core/ToNumber.go
Normal file
|
@ -0,0 +1,63 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"git.urbach.dev/cli/q/src/errors"
|
||||
"git.urbach.dev/cli/q/src/fs"
|
||||
"git.urbach.dev/cli/q/src/token"
|
||||
)
|
||||
|
||||
// ToNumber tries to convert the token into a numeric value.
|
||||
func (f *Function) ToNumber(t token.Token) (int, error) {
|
||||
return ToNumber(t, f.File)
|
||||
}
|
||||
|
||||
// ToNumber tries to convert the token into a numeric value.
|
||||
func ToNumber(t token.Token, file *fs.File) (int, error) {
|
||||
switch t.Kind {
|
||||
case token.Number:
|
||||
var (
|
||||
digits = t.String(file.Bytes)
|
||||
number int64
|
||||
err error
|
||||
)
|
||||
|
||||
switch {
|
||||
case strings.HasPrefix(digits, "0x"):
|
||||
number, err = strconv.ParseInt(digits[2:], 16, 64)
|
||||
case strings.HasPrefix(digits, "0o"):
|
||||
number, err = strconv.ParseInt(digits[2:], 8, 64)
|
||||
case strings.HasPrefix(digits, "0b"):
|
||||
number, err = strconv.ParseInt(digits[2:], 2, 64)
|
||||
default:
|
||||
number, err = strconv.ParseInt(digits, 10, 64)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return 0, errors.New(InvalidNumber, file, t.Position)
|
||||
}
|
||||
|
||||
return int(number), nil
|
||||
|
||||
case token.Rune:
|
||||
r := t.Bytes(file.Bytes)
|
||||
r = Unescape(r)
|
||||
|
||||
if len(r) == 0 {
|
||||
return 0, errors.New(InvalidRune, file, t.Position+1)
|
||||
}
|
||||
|
||||
number, size := utf8.DecodeRune(r)
|
||||
|
||||
if len(r) > size {
|
||||
return 0, errors.New(InvalidRune, file, t.Position+1)
|
||||
}
|
||||
|
||||
return int(number), nil
|
||||
}
|
||||
|
||||
return 0, errors.New(InvalidNumber, file, t.Position)
|
||||
}
|
43
src/core/Unescape.go
Normal file
43
src/core/Unescape.go
Normal file
|
@ -0,0 +1,43 @@
|
|||
package core
|
||||
|
||||
import "bytes"
|
||||
|
||||
// Unescape replaces the escape sequences in the contents of a string token with the respective characters.
|
||||
func Unescape(data []byte) []byte {
|
||||
data = data[1 : len(data)-1]
|
||||
escape := bytes.IndexByte(data, '\\')
|
||||
|
||||
if escape == -1 {
|
||||
return data
|
||||
}
|
||||
|
||||
tmp := make([]byte, 0, len(data))
|
||||
|
||||
for {
|
||||
tmp = append(tmp, data[:escape]...)
|
||||
|
||||
switch data[escape+1] {
|
||||
case '0':
|
||||
tmp = append(tmp, '\000')
|
||||
case 't':
|
||||
tmp = append(tmp, '\t')
|
||||
case 'n':
|
||||
tmp = append(tmp, '\n')
|
||||
case 'r':
|
||||
tmp = append(tmp, '\r')
|
||||
case '"':
|
||||
tmp = append(tmp, '"')
|
||||
case '\'':
|
||||
tmp = append(tmp, '\'')
|
||||
case '\\':
|
||||
tmp = append(tmp, '\\')
|
||||
}
|
||||
|
||||
data = data[escape+2:]
|
||||
escape = bytes.IndexByte(data, '\\')
|
||||
|
||||
if escape == -1 {
|
||||
return tmp
|
||||
}
|
||||
}
|
||||
}
|
27
src/core/errors.go
Normal file
27
src/core/errors.go
Normal file
|
@ -0,0 +1,27 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.urbach.dev/cli/q/src/errors"
|
||||
)
|
||||
|
||||
var (
|
||||
InvalidExpression = errors.String("Invalid expression")
|
||||
InvalidNumber = errors.String("Invalid number")
|
||||
InvalidRune = errors.String("Invalid rune")
|
||||
)
|
||||
|
||||
// UnknownIdentifier represents unknown identifiers.
|
||||
type UnknownIdentifier struct {
|
||||
Name string
|
||||
CorrectName string
|
||||
}
|
||||
|
||||
func (err *UnknownIdentifier) Error() string {
|
||||
if err.CorrectName != "" {
|
||||
return fmt.Sprintf("Unknown identifier '%s', did you mean '%s'?", err.Name, err.CorrectName)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("Unknown identifier '%s'", err.Name)
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue