From 42f0367a94d709beb23b93e3cd7104f78adb6da4 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 5 Aug 2024 12:39:07 +0200 Subject: [PATCH] Implemented multiple return values --- README.md | 2 +- lib/log/number.q | 9 +++-- src/build/arch/x64/Return.go | 2 +- src/build/ast/Count.go | 4 +- src/build/ast/Return.go | 2 +- src/build/ast/parseKeyword.go | 4 +- src/build/core/CompileASTNode.go | 5 ++- src/build/core/CompileCall.go | 1 + src/build/core/CompileDefinition.go | 48 +++++++++++++++--------- src/build/core/CompileReturn.go | 4 +- src/build/core/Define.go | 31 +++++++++++++++ src/build/core/ExpressionToRegister.go | 5 +-- src/build/core/ExpressionsToRegisters.go | 2 +- tests/programs/return-multi.q | 28 ++++++++++++++ tests/programs_test.go | 1 + 15 files changed, 113 insertions(+), 35 deletions(-) create mode 100644 src/build/core/Define.go create mode 100644 tests/programs/return-multi.q diff --git a/README.md b/README.md index 8ba0c76..d48bd05 100644 --- a/README.md +++ b/README.md @@ -115,11 +115,11 @@ This is what generates expressions from tokens. - [x] Loops - [x] Hexadecimal, octal and binary literals - [x] Escape sequences +- [x] Multiple return values - [ ] Data structures - [ ] Type system - [ ] Type operator: `|` (`User | Error`) - [ ] Error handling -- [ ] Multiple return values - [ ] Threading library - [ ] Self-hosted compiler diff --git a/lib/log/number.q b/lib/log/number.q index 01471dc..86be427 100644 --- a/lib/log/number.q +++ b/lib/log/number.q @@ -4,13 +4,14 @@ import sys number(x) { length := 20 buffer := mem.alloc(length) - tmp := itoa(x, buffer, length) - sys.write(1, tmp, buffer + length - tmp) + address, count := itoa(x, buffer, length) + sys.write(1, address, count) mem.free(buffer, length) } itoa(x, buffer, length) { - tmp := buffer + length + end := buffer + length + tmp := end digit := 0 loop { @@ -19,7 +20,7 @@ itoa(x, buffer, length) { tmp[0] = '0' + digit if x == 0 { - return tmp + return tmp, end - tmp } } } \ No newline at end of file diff --git a/src/build/arch/x64/Return.go b/src/build/arch/x64/Return.go index dc185da..a64d39f 100644 --- a/src/build/arch/x64/Return.go +++ b/src/build/arch/x64/Return.go @@ -3,5 +3,5 @@ package x64 // Return transfers program control to a return address located on the top of the stack. // The address is usually placed on the stack by a Call instruction. func Return(code []byte) []byte { - return append(code, 0xc3) + return append(code, 0xC3) } diff --git a/src/build/ast/Count.go b/src/build/ast/Count.go index 192a36f..2c61203 100644 --- a/src/build/ast/Count.go +++ b/src/build/ast/Count.go @@ -29,8 +29,8 @@ func Count(body AST, buffer []byte, kind token.Kind, name string) uint8 { count += Count(node.Body, buffer, kind, name) case *Return: - if node.Value != nil { - count += node.Value.Count(buffer, kind, name) + for _, value := range node.Values { + count += value.Count(buffer, kind, name) } case *Switch: diff --git a/src/build/ast/Return.go b/src/build/ast/Return.go index 37574e2..6c8aa2d 100644 --- a/src/build/ast/Return.go +++ b/src/build/ast/Return.go @@ -6,5 +6,5 @@ import ( // Return represents a return statement. type Return struct { - Value *expression.Expression + Values []*expression.Expression } diff --git a/src/build/ast/parseKeyword.go b/src/build/ast/parseKeyword.go index 0b6a423..f4a948a 100644 --- a/src/build/ast/parseKeyword.go +++ b/src/build/ast/parseKeyword.go @@ -48,8 +48,8 @@ func parseKeyword(tokens token.List, source []byte, nodes AST) (Node, error) { return &Return{}, nil } - value := expression.Parse(tokens[1:]) - return &Return{Value: value}, nil + values := expression.NewList(tokens[1:]) + return &Return{Values: values}, nil case token.Switch: blockStart := tokens.IndexKind(token.BlockStart) diff --git a/src/build/core/CompileASTNode.go b/src/build/core/CompileASTNode.go index e3bccaf..7277ccc 100644 --- a/src/build/core/CompileASTNode.go +++ b/src/build/core/CompileASTNode.go @@ -31,7 +31,10 @@ func (f *Function) CompileASTNode(node ast.Node) error { return f.CompileLoop(node) case *ast.Return: - f.Fold(node.Value) + for _, value := range node.Values { + f.Fold(value) + } + return f.CompileReturn(node) case *ast.Switch: diff --git a/src/build/core/CompileCall.go b/src/build/core/CompileCall.go index 68e915a..c04f842 100644 --- a/src/build/core/CompileCall.go +++ b/src/build/core/CompileCall.go @@ -69,6 +69,7 @@ func (f *Function) CompileCall(root *expression.Expression) error { return err } + // TODO: Save all return value registers of the function f.SaveRegister(f.CPU.Output[0]) // Push diff --git a/src/build/core/CompileDefinition.go b/src/build/core/CompileDefinition.go index 75241ba..689f020 100644 --- a/src/build/core/CompileDefinition.go +++ b/src/build/core/CompileDefinition.go @@ -1,36 +1,50 @@ package core import ( + "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/ast" "git.akyoto.dev/cli/q/src/build/errors" - "git.akyoto.dev/cli/q/src/build/scope" - "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/build/expression" ) // CompileDefinition compiles a variable definition. func (f *Function) CompileDefinition(node *ast.Define) error { left := node.Expression.Children[0] right := node.Expression.Children[1] - name := left.Token.Text(f.File.Bytes) - if f.IdentifierExists(name) { - return errors.New(&errors.VariableAlreadyExists{Name: name}, f.File, left.Token.Position) + if left.IsLeaf() { + variable, err := f.Define(left) + + if err != nil { + return err + } + + err = f.ExpressionToRegister(right, variable.Register) + f.AddVariable(variable) + return err } - uses := token.Count(f.Body, f.File.Bytes, token.Identifier, name) - 1 - - if uses == 0 { - return errors.New(&errors.UnusedVariable{Name: name}, f.File, left.Token.Position) + if !ast.IsFunctionCall(right) { + return errors.New(errors.NotImplemented, f.File, node.Expression.Token.Position) } - register := f.NewRegister() - err := f.ExpressionToRegister(right, register) + count := 0 + err := f.CompileCall(right) - f.AddVariable(&scope.Variable{ - Name: name, - Register: register, - Alive: uses, + if err != nil { + return err + } + + return left.EachLeaf(func(leaf *expression.Expression) error { + variable, err := f.Define(leaf) + + if err != nil { + return err + } + + f.RegisterRegister(asm.MOVE, variable.Register, f.CPU.Output[count]) + f.AddVariable(variable) + count++ + return nil }) - - return err } diff --git a/src/build/core/CompileReturn.go b/src/build/core/CompileReturn.go index 26f1cab..ca59d56 100644 --- a/src/build/core/CompileReturn.go +++ b/src/build/core/CompileReturn.go @@ -8,9 +8,9 @@ import ( func (f *Function) CompileReturn(node *ast.Return) error { defer f.Return() - if node.Value == nil { + if len(node.Values) == 0 { return nil } - return f.ExpressionToRegister(node.Value, f.CPU.Output[0]) + return f.ExpressionsToRegisters(node.Values, f.CPU.Output) } diff --git a/src/build/core/Define.go b/src/build/core/Define.go new file mode 100644 index 0000000..0867f1b --- /dev/null +++ b/src/build/core/Define.go @@ -0,0 +1,31 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/build/errors" + "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/build/scope" + "git.akyoto.dev/cli/q/src/build/token" +) + +// Define defines a new variable. +func (f *Function) Define(leaf *expression.Expression) (*scope.Variable, error) { + name := leaf.Token.Text(f.File.Bytes) + + if f.IdentifierExists(name) { + return nil, errors.New(&errors.VariableAlreadyExists{Name: name}, f.File, leaf.Token.Position) + } + + uses := token.Count(f.Body, f.File.Bytes, token.Identifier, name) - 1 + + if uses == 0 { + return nil, errors.New(&errors.UnusedVariable{Name: name}, f.File, leaf.Token.Position) + } + + variable := &scope.Variable{ + Name: name, + Register: f.NewRegister(), + Alive: uses, + } + + return variable, nil +} diff --git a/src/build/core/ExpressionToRegister.go b/src/build/core/ExpressionToRegister.go index e115758..29a084c 100644 --- a/src/build/core/ExpressionToRegister.go +++ b/src/build/core/ExpressionToRegister.go @@ -11,8 +11,9 @@ import ( // ExpressionToRegister puts the result of an expression into the specified register. func (f *Function) ExpressionToRegister(node *expression.Expression, register cpu.Register) error { + f.SaveRegister(register) + if node.IsFolded { - f.SaveRegister(register) f.RegisterNumber(asm.MOVE, register, node.Value) return nil } @@ -25,7 +26,6 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp err := f.CompileCall(node) if register != f.CPU.Output[0] { - f.SaveRegister(register) f.RegisterRegister(asm.MOVE, register, f.CPU.Output[0]) } @@ -70,7 +70,6 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp err = f.Execute(node.Token, register, right) if register != final { - f.SaveRegister(final) f.RegisterRegister(asm.MOVE, final, register) f.FreeRegister(register) } diff --git a/src/build/core/ExpressionsToRegisters.go b/src/build/core/ExpressionsToRegisters.go index 64d6816..f3a09fe 100644 --- a/src/build/core/ExpressionsToRegisters.go +++ b/src/build/core/ExpressionsToRegisters.go @@ -7,7 +7,7 @@ import ( // ExpressionsToRegisters moves multiple expressions into the specified registers. func (f *Function) ExpressionsToRegisters(expressions []*expression.Expression, registers []cpu.Register) error { - for i := len(registers) - 1; i >= 0; i-- { + for i := len(expressions) - 1; i >= 0; i-- { err := f.ExpressionToRegister(expressions[i], registers[i]) if err != nil { diff --git a/tests/programs/return-multi.q b/tests/programs/return-multi.q new file mode 100644 index 0000000..47e72be --- /dev/null +++ b/tests/programs/return-multi.q @@ -0,0 +1,28 @@ +main() { + a, b := reverse2(1, 2) + assert a == 2 + assert b == 1 + + c, d, e := reverse3(1, 2, 3) + assert c == 3 + assert d == 2 + assert e == 1 + + f, g, h, i := mix4(1, 2, 3, 4) + assert f == 4 + 1 + assert g == 3 + 2 + assert h == 2 + 3 + assert i == 1 + 4 +} + +reverse2(a, b) { + return b, a +} + +reverse3(a, b, c) { + return c, b, a +} + +mix4(a, b, c, d) { + return d + a, c + b, b + c, a + d +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index c381573..39c5677 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -24,6 +24,7 @@ var programs = []struct { {"reassign", "", "", 0}, {"reuse", "", "", 0}, {"return", "", "", 0}, + {"return-multi", "", "", 0}, {"math", "", "", 0}, {"precedence", "", "", 0}, {"op-assign", "", "", 0},