Simplified compilation of function calls

This commit is contained in:
Eduard Urbach 2025-03-02 21:36:23 +01:00
parent c3054369e3
commit ea233d789d
Signed by: akyoto
GPG Key ID: C874F672B1AF20C0
14 changed files with 114 additions and 97 deletions

View File

@ -11,6 +11,7 @@ import (
func Compile(constants <-chan *core.Constant, files <-chan *fs.File, functions <-chan *core.Function, structs <-chan *types.Struct, errs <-chan error) (Result, error) {
all := core.Environment{
Files: make([]*fs.File, 0, 8),
Extern: make(map[string]struct{}, 0),
Functions: make(map[string]*core.Function, 32),
Structs: make(map[string]*types.Struct, 8),
Constants: make(map[string]*core.Constant, 8),
@ -29,6 +30,10 @@ func Compile(constants <-chan *core.Constant, files <-chan *fs.File, functions <
function.All = &all
all.Functions[function.UniqueName] = function
if function.IsExtern() {
all.Extern[function.Package] = struct{}{}
}
case structure, ok := <-structs:
if !ok {
structs = nil

View File

@ -3,7 +3,9 @@ package core
import (
"git.urbach.dev/cli/q/src/asm"
"git.urbach.dev/cli/q/src/errors"
"git.urbach.dev/cli/q/src/eval"
"git.urbach.dev/cli/q/src/expression"
"git.urbach.dev/cli/q/src/token"
"git.urbach.dev/cli/q/src/types"
)
@ -12,17 +14,8 @@ import (
// Registers that are in use must be saved if they are modified by the function.
// After the function call, they must be restored in reverse order.
func (f *Function) CompileCall(root *expression.Expression) ([]types.Type, error) {
var (
pkg = f.Package
pkgNode *expression.Expression
name string
nameNode = root.Children[0]
fn *Function
exists bool
)
if nameNode.IsLeaf() {
name = nameNode.Token.Text(f.File.Bytes)
if root.Children[0].Token.Kind == token.Identifier {
name := root.Children[0].Token.Text(f.File.Bytes)
switch name {
case "len":
@ -37,63 +30,56 @@ func (f *Function) CompileCall(root *expression.Expression) ([]types.Type, error
case "store":
return nil, f.CompileMemoryStore(root)
}
} else {
pkgNode = nameNode.Children[0]
nameNode = nameNode.Children[1]
pkg = pkgNode.Token.Text(f.File.Bytes)
name = nameNode.Token.Text(f.File.Bytes)
}
if f.UniqueName == "core.init" && pkg == "main" && name == "main" {
f.Label(asm.CALL, "main.main")
return nil, nil
}
fn, exists = f.All.Functions[pkg+"."+name]
if !exists {
variable := f.VariableByName(name)
if variable != nil {
f.Register(asm.CALL, variable.Value.Register)
return nil, nil
}
return nil, errors.New(&errors.UnknownFunction{Name: name}, f.File, nameNode.Token.Position)
}
if pkg != f.File.Package && !fn.IsExtern() {
if f.File.Imports == nil {
return nil, errors.New(&errors.UnknownPackage{Name: pkg}, f.File, pkgNode.Token.Position)
}
imp, exists := f.File.Imports[pkg]
if !exists {
return nil, errors.New(&errors.UnknownPackage{Name: pkg}, f.File, pkgNode.Token.Position)
}
imp.Used = true
}
parameters := root.Children[1:]
if len(parameters) != len(fn.Input) {
return nil, errors.New(&errors.ParameterCountMismatch{Function: fn.Name, Count: len(parameters), ExpectedCount: len(fn.Input)}, f.File, nameNode.Token.End())
}
if fn.IsExtern() {
return f.CallExtern(fn, parameters)
}
registers := f.CPU.Input[:len(parameters)]
err := f.ExpressionsToRegisters(parameters, registers, fn.Input, true)
value, err := f.Evaluate(root.Children[0])
if err != nil {
return nil, err
}
f.CallSafe(fn, registers)
return fn.OutputTypes, nil
parameters := root.Children[1:]
registers := f.CPU.Input[:len(parameters)]
switch value := value.(type) {
case *eval.Label:
fn, exists := f.All.Functions[value.Label]
if !exists {
if value.Label == "main.main" && f.UniqueName == "core.init" {
f.Label(asm.CALL, "main.main")
return nil, nil
}
return nil, errors.New(&errors.UnknownIdentifier{Name: value.Label}, f.File, root.Children[0].Token.Position)
}
if len(parameters) != len(fn.Input) {
return nil, errors.New(&errors.ParameterCountMismatch{Function: fn.Name, Count: len(parameters), ExpectedCount: len(fn.Input)}, f.File, root.Children[0].Token.End())
}
if fn.IsExtern() {
return f.CallExtern(fn, parameters)
}
err := f.ExpressionsToRegisters(parameters, registers, fn.Input, true)
if err != nil {
return nil, err
}
f.CallSafe(fn, registers)
return fn.OutputTypes, nil
case *eval.Register:
f.Register(asm.CALL, value.Register)
case *eval.Memory:
tmp := f.NewRegister()
f.MemoryRegister(asm.LOAD, value.Memory, tmp)
f.Register(asm.CALL, tmp)
f.FreeRegister(tmp)
}
return nil, nil
}

View File

@ -8,6 +8,7 @@ import (
// Environment holds information about the entire build.
type Environment struct {
Constants map[string]*Constant
Extern map[string]struct{}
Functions map[string]*Function
Structs map[string]*types.Struct
Files []*fs.File

View File

@ -59,19 +59,24 @@ func (f *Function) EvaluateDot(expr *expression.Expression) (eval.Value, error)
return value, nil
}
uniqueName := fmt.Sprintf("%s.%s", leftText, rightText)
function, exists := f.All.Functions[uniqueName]
if leftText != "main" {
imp, exists := f.File.Imports[leftText]
if exists {
f.File.Imports[leftText].Used = true
if exists {
imp.Used = true
} else {
_, exists := f.All.Extern[leftText]
value := &eval.Label{
Typ: types.AnyPointer,
Label: function.UniqueName,
if !exists {
return nil, errors.New(&errors.UnknownPackage{Name: leftText}, f.File, left.Token.Position)
}
}
return value, nil
}
return nil, errors.New(&errors.UnknownIdentifier{Name: uniqueName}, f.File, left.Token.Position)
value := &eval.Label{
Typ: types.AnyPointer,
Label: fmt.Sprintf("%s.%s", leftText, rightText),
}
return value, nil
}

View File

@ -1,18 +0,0 @@
package errors
import "fmt"
// UnknownFunction represents unknown function errors.
type UnknownFunction struct {
Name string
CorrectName string
}
// Error generates the string representation.
func (err *UnknownFunction) Error() string {
if err.CorrectName != "" {
return fmt.Sprintf("Unknown function '%s', did you mean '%s'?", err.Name, err.CorrectName)
}
return fmt.Sprintf("Unknown function '%s'", err.Name)
}

View File

@ -0,0 +1,3 @@
main() {
loop
}

View File

@ -1,3 +0,0 @@
main() {
x := 1 + f(x)
}

View File

@ -0,0 +1,3 @@
main() {
x := 1 + unknown(x)
}

View File

@ -39,6 +39,7 @@ var errs = []struct {
{"MissingBlockEnd.q", errors.MissingBlockEnd},
{"MissingBlockEnd2.q", errors.MissingBlockEnd},
{"MissingBlockStart.q", errors.MissingBlockStart},
{"MissingBlockStart2.q", errors.MissingBlockStart},
{"MissingExpression.q", errors.MissingExpression},
{"MissingGroupEnd.q", errors.MissingGroupEnd},
{"MissingGroupStart.q", errors.MissingGroupStart},
@ -52,11 +53,11 @@ var errs = []struct {
{"ReturnCountMismatch.q", &errors.ReturnCountMismatch{Count: 1, ExpectedCount: 0}},
{"TypeMismatch.q", &errors.TypeMismatch{Expected: "*any", Encountered: "int", ParameterName: "p"}},
{"TypeMismatch2.q", &errors.TypeMismatch{Expected: "[]any", Encountered: "int", ParameterName: "array"}},
{"UnknownFunction.q", &errors.UnknownFunction{Name: "unknown"}},
{"UnknownFunction2.q", &errors.UnknownFunction{Name: "f"}},
{"UnknownIdentifier.q", &errors.UnknownIdentifier{Name: "x"}},
{"UnknownIdentifier2.q", &errors.UnknownIdentifier{Name: "x"}},
{"UnknownIdentifier3.q", &errors.UnknownIdentifier{Name: "x"}},
{"UnknownIdentifier4.q", &errors.UnknownIdentifier{Name: "unknown"}},
{"UnknownIdentifier5.q", &errors.UnknownIdentifier{Name: "unknown"}},
{"UnknownPackage.q", &errors.UnknownPackage{Name: "sys"}},
{"UnknownType.q", &errors.UnknownType{Name: "unknown"}},
{"UnknownType2.q", &errors.UnknownType{Name: "unknown"}},

View File

@ -0,0 +1,11 @@
import core
struct Struct {
func *any
}
main() {
s := new(Struct)
s.func = core.exit
s.func()
}

View File

@ -0,0 +1,21 @@
import sys
main() {
x := 0
loop {
for 0..10 {
x += 1
}
assert x == 10
loop {
x -= 1
if x == 0 {
sys.exit(0)
}
}
}
}

View File

@ -58,8 +58,9 @@ var programs = []struct {
{"branch-save", 0},
{"jump-near", 0},
{"switch", 0},
{"loop-infinite", 0},
{"loop", 0},
{"loop-lifetime", 0},
{"loop-in-loop", 0},
{"for", 0},
{"memory-free", 0},
{"out-of-memory", 0},
@ -69,6 +70,7 @@ var programs = []struct {
{"len", 0},
{"cast", 0},
{"function-pointer", 0},
{"function-pointer-field", 0},
}
func TestPrograms(t *testing.T) {