Implemented compilation to SSA form
All checks were successful
/ test (push) Successful in 31s

This commit is contained in:
Eduard Urbach 2025-06-23 00:17:05 +02:00
parent f7be86a3d9
commit 31c5ed614c
Signed by: akyoto
GPG key ID: 49226B848C78F6C8
27 changed files with 548 additions and 61 deletions

View file

@ -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
}
}
}

View 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
View 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
View 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
}

View file

@ -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
View 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
View 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
View 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)
}