diff --git a/src/build/core/CompileCall.go b/src/build/core/CompileCall.go index 079cf97..9c92950 100644 --- a/src/build/core/CompileCall.go +++ b/src/build/core/CompileCall.go @@ -2,6 +2,7 @@ package core import ( "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/errors" "git.akyoto.dev/cli/q/src/build/expression" ) @@ -11,9 +12,18 @@ import ( // After the function call, they must be restored in reverse order. func (f *Function) CompileCall(root *expression.Expression) error { funcName := root.Children[0].Token.Text() + isSyscall := funcName == "syscall" + + if !isSyscall { + _, exists := f.functions[funcName] + + if !exists { + return errors.New(&errors.UnknownFunction{Name: funcName}, f.File, root.Children[0].Token.Position) + } + } + parameters := root.Children[1:] registers := f.cpu.Input[:len(parameters)] - isSyscall := funcName == "syscall" if isSyscall { registers = f.cpu.Syscall[:len(parameters)] diff --git a/src/build/core/CompileIf.go b/src/build/core/CompileIf.go index bebfc10..de43bec 100644 --- a/src/build/core/CompileIf.go +++ b/src/build/core/CompileIf.go @@ -9,25 +9,15 @@ import ( // CompileIf compiles a branch instruction. func (f *Function) CompileIf(branch *ast.If) error { - condition := branch.Condition - tmp := f.cpu.MustFindFree(f.cpu.General) - err := f.ExpressionToRegister(condition.Children[0], tmp) + err := f.Evaluate(branch.Condition) if err != nil { return err } - f.cpu.Use(tmp) - err = f.Execute(condition.Token, tmp, condition.Children[1]) - - if err != nil { - return err - } - - f.cpu.Free(tmp) endLabel := fmt.Sprintf("%s_end_if_%d", f.Name, f.count.branch) - switch condition.Token.Text() { + switch branch.Condition.Token.Text() { case "==": f.assembler.Label(asm.JNE, endLabel) case "!=": diff --git a/src/build/core/Evaluate.go b/src/build/core/Evaluate.go new file mode 100644 index 0000000..5c3a60c --- /dev/null +++ b/src/build/core/Evaluate.go @@ -0,0 +1,41 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/build/ast" + "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/build/token" +) + +// Evaluate evaluates an expression. +func (f *Function) Evaluate(value *expression.Expression) error { + left := value.Children[0] + right := value.Children[1] + + if left.IsLeaf() && left.Token.Kind == token.Identifier { + variable := f.variables[left.Token.Text()] + register := variable.Register + defer f.useVariable(variable) + return f.Execute(value.Token, register, right) + } + + if ast.IsFunctionCall(left) && right.IsLeaf() { + err := f.CompileCall(left) + + if err != nil { + return err + } + + return f.Execute(value.Token, f.cpu.Output[0], right) + } + + tmp := f.cpu.MustFindFree(f.cpu.General) + err := f.ExpressionToRegister(left, tmp) + + if err != nil { + return err + } + + f.cpu.Use(tmp) + defer f.cpu.Free(tmp) + return f.Execute(value.Token, tmp, right) +} diff --git a/src/build/errors/UnknownFunction.go b/src/build/errors/UnknownFunction.go new file mode 100644 index 0000000..a2b7e4e --- /dev/null +++ b/src/build/errors/UnknownFunction.go @@ -0,0 +1,18 @@ +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/UnknownFunction.q b/tests/errors/UnknownFunction.q new file mode 100644 index 0000000..6b917e1 --- /dev/null +++ b/tests/errors/UnknownFunction.q @@ -0,0 +1,3 @@ +main() { + unknown() +} \ No newline at end of file diff --git a/tests/errors_test.go b/tests/errors_test.go index d1123bb..7782a67 100644 --- a/tests/errors_test.go +++ b/tests/errors_test.go @@ -33,6 +33,7 @@ var errs = []struct { {"MissingOperand.q", errors.MissingOperand}, {"MissingOperand2.q", errors.MissingOperand}, {"VariableAlreadyExists.q", &errors.VariableAlreadyExists{Name: "x"}}, + {"UnknownFunction.q", &errors.UnknownFunction{Name: "unknown"}}, {"UnknownIdentifier.q", &errors.UnknownIdentifier{Name: "x"}}, {"UnknownIdentifier2.q", &errors.UnknownIdentifier{Name: "x"}}, {"UnknownIdentifier3.q", &errors.UnknownIdentifier{Name: "f"}}, diff --git a/tests/programs/branch.q b/tests/programs/branch.q new file mode 100644 index 0000000..547fcfc --- /dev/null +++ b/tests/programs/branch.q @@ -0,0 +1,81 @@ +main() { + x := 0 + + if x != 0 { + exit(1) + } + + if x > 0 { + exit(1) + } + + if x < 0 { + exit(1) + } + + if 0 != x { + exit(1) + } + + if 0 > x { + exit(1) + } + + if 0 < x { + exit(1) + } + + if x >= 1 { + exit(1) + } + + if 1 <= x { + exit(1) + } + + if x == inc(x) { + exit(1) + } + + if x == dec(x) { + exit(1) + } + + if inc(0) == x { + exit(1) + } + + if dec(0) == x { + exit(1) + } + + if inc(x) == dec(x) { + exit(1) + } + + if x + 1 != inc(x) { + exit(1) + } + + if x + 1 != x + 1 { + exit(1) + } + + if x == 0 { + exit(0) + } + + exit(1) +} + +exit(x) { + syscall(60, x) +} + +inc(x) { + return x + 1 +} + +dec(x) { + return x - 1 +} \ No newline at end of file