diff --git a/src/compiler/Compile.go b/src/compiler/Compile.go index f62943c..ae3c437 100644 --- a/src/compiler/Compile.go +++ b/src/compiler/Compile.go @@ -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 diff --git a/src/core/CompileCall.go b/src/core/CompileCall.go index fec090a..7e47f48 100644 --- a/src/core/CompileCall.go +++ b/src/core/CompileCall.go @@ -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 } diff --git a/src/core/Environment.go b/src/core/Environment.go index e2a1354..c27828a 100644 --- a/src/core/Environment.go +++ b/src/core/Environment.go @@ -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 diff --git a/src/core/EvaluateDot.go b/src/core/EvaluateDot.go index 944ea02..69f078c 100644 --- a/src/core/EvaluateDot.go +++ b/src/core/EvaluateDot.go @@ -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 } diff --git a/src/errors/UnknownFunction.go b/src/errors/UnknownFunction.go deleted file mode 100644 index a2b7e4e..0000000 --- a/src/errors/UnknownFunction.go +++ /dev/null @@ -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) -} diff --git a/tests/errors/MissingBlockStart2.q b/tests/errors/MissingBlockStart2.q new file mode 100644 index 0000000..7e12b77 --- /dev/null +++ b/tests/errors/MissingBlockStart2.q @@ -0,0 +1,3 @@ +main() { + loop +} \ No newline at end of file diff --git a/tests/errors/UnknownFunction2.q b/tests/errors/UnknownFunction2.q deleted file mode 100644 index c31b354..0000000 --- a/tests/errors/UnknownFunction2.q +++ /dev/null @@ -1,3 +0,0 @@ -main() { - x := 1 + f(x) -} \ No newline at end of file diff --git a/tests/errors/UnknownFunction.q b/tests/errors/UnknownIdentifier4.q similarity index 100% rename from tests/errors/UnknownFunction.q rename to tests/errors/UnknownIdentifier4.q diff --git a/tests/errors/UnknownIdentifier5.q b/tests/errors/UnknownIdentifier5.q new file mode 100644 index 0000000..5d38d8e --- /dev/null +++ b/tests/errors/UnknownIdentifier5.q @@ -0,0 +1,3 @@ +main() { + x := 1 + unknown(x) +} \ No newline at end of file diff --git a/tests/errors_test.go b/tests/errors_test.go index 95a5424..9403bfc 100644 --- a/tests/errors_test.go +++ b/tests/errors_test.go @@ -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"}}, diff --git a/tests/programs/function-pointer-field.q b/tests/programs/function-pointer-field.q new file mode 100644 index 0000000..89894b9 --- /dev/null +++ b/tests/programs/function-pointer-field.q @@ -0,0 +1,11 @@ +import core + +struct Struct { + func *any +} + +main() { + s := new(Struct) + s.func = core.exit + s.func() +} \ No newline at end of file diff --git a/tests/programs/loop-in-loop.q b/tests/programs/loop-in-loop.q new file mode 100644 index 0000000..969ace6 --- /dev/null +++ b/tests/programs/loop-in-loop.q @@ -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) + } + } + } +} \ No newline at end of file diff --git a/tests/programs/loop-infinite.q b/tests/programs/loop.q similarity index 100% rename from tests/programs/loop-infinite.q rename to tests/programs/loop.q diff --git a/tests/programs_test.go b/tests/programs_test.go index 369c4af..a1e76ce 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -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) {