Implemented multiple return values

This commit is contained in:
Eduard Urbach 2024-08-05 12:39:07 +02:00
parent d4020da6d9
commit 42f0367a94
Signed by: akyoto
GPG Key ID: C874F672B1AF20C0
15 changed files with 113 additions and 35 deletions

View File

@ -115,11 +115,11 @@ This is what generates expressions from tokens.
- [x] Loops - [x] Loops
- [x] Hexadecimal, octal and binary literals - [x] Hexadecimal, octal and binary literals
- [x] Escape sequences - [x] Escape sequences
- [x] Multiple return values
- [ ] Data structures - [ ] Data structures
- [ ] Type system - [ ] Type system
- [ ] Type operator: `|` (`User | Error`) - [ ] Type operator: `|` (`User | Error`)
- [ ] Error handling - [ ] Error handling
- [ ] Multiple return values
- [ ] Threading library - [ ] Threading library
- [ ] Self-hosted compiler - [ ] Self-hosted compiler

View File

@ -4,13 +4,14 @@ import sys
number(x) { number(x) {
length := 20 length := 20
buffer := mem.alloc(length) buffer := mem.alloc(length)
tmp := itoa(x, buffer, length) address, count := itoa(x, buffer, length)
sys.write(1, tmp, buffer + length - tmp) sys.write(1, address, count)
mem.free(buffer, length) mem.free(buffer, length)
} }
itoa(x, buffer, length) { itoa(x, buffer, length) {
tmp := buffer + length end := buffer + length
tmp := end
digit := 0 digit := 0
loop { loop {
@ -19,7 +20,7 @@ itoa(x, buffer, length) {
tmp[0] = '0' + digit tmp[0] = '0' + digit
if x == 0 { if x == 0 {
return tmp return tmp, end - tmp
} }
} }
} }

View File

@ -3,5 +3,5 @@ package x64
// Return transfers program control to a return address located on the top of the stack. // 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. // The address is usually placed on the stack by a Call instruction.
func Return(code []byte) []byte { func Return(code []byte) []byte {
return append(code, 0xc3) return append(code, 0xC3)
} }

View File

@ -29,8 +29,8 @@ func Count(body AST, buffer []byte, kind token.Kind, name string) uint8 {
count += Count(node.Body, buffer, kind, name) count += Count(node.Body, buffer, kind, name)
case *Return: case *Return:
if node.Value != nil { for _, value := range node.Values {
count += node.Value.Count(buffer, kind, name) count += value.Count(buffer, kind, name)
} }
case *Switch: case *Switch:

View File

@ -6,5 +6,5 @@ import (
// Return represents a return statement. // Return represents a return statement.
type Return struct { type Return struct {
Value *expression.Expression Values []*expression.Expression
} }

View File

@ -48,8 +48,8 @@ func parseKeyword(tokens token.List, source []byte, nodes AST) (Node, error) {
return &Return{}, nil return &Return{}, nil
} }
value := expression.Parse(tokens[1:]) values := expression.NewList(tokens[1:])
return &Return{Value: value}, nil return &Return{Values: values}, nil
case token.Switch: case token.Switch:
blockStart := tokens.IndexKind(token.BlockStart) blockStart := tokens.IndexKind(token.BlockStart)

View File

@ -31,7 +31,10 @@ func (f *Function) CompileASTNode(node ast.Node) error {
return f.CompileLoop(node) return f.CompileLoop(node)
case *ast.Return: case *ast.Return:
f.Fold(node.Value) for _, value := range node.Values {
f.Fold(value)
}
return f.CompileReturn(node) return f.CompileReturn(node)
case *ast.Switch: case *ast.Switch:

View File

@ -69,6 +69,7 @@ func (f *Function) CompileCall(root *expression.Expression) error {
return err return err
} }
// TODO: Save all return value registers of the function
f.SaveRegister(f.CPU.Output[0]) f.SaveRegister(f.CPU.Output[0])
// Push // Push

View File

@ -1,36 +1,50 @@
package core package core
import ( import (
"git.akyoto.dev/cli/q/src/build/asm"
"git.akyoto.dev/cli/q/src/build/ast" "git.akyoto.dev/cli/q/src/build/ast"
"git.akyoto.dev/cli/q/src/build/errors" "git.akyoto.dev/cli/q/src/build/errors"
"git.akyoto.dev/cli/q/src/build/scope" "git.akyoto.dev/cli/q/src/build/expression"
"git.akyoto.dev/cli/q/src/build/token"
) )
// CompileDefinition compiles a variable definition. // CompileDefinition compiles a variable definition.
func (f *Function) CompileDefinition(node *ast.Define) error { func (f *Function) CompileDefinition(node *ast.Define) error {
left := node.Expression.Children[0] left := node.Expression.Children[0]
right := node.Expression.Children[1] right := node.Expression.Children[1]
name := left.Token.Text(f.File.Bytes)
if f.IdentifierExists(name) { if left.IsLeaf() {
return errors.New(&errors.VariableAlreadyExists{Name: name}, f.File, left.Token.Position) variable, err := f.Define(left)
}
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)
}
register := f.NewRegister()
err := f.ExpressionToRegister(right, register)
f.AddVariable(&scope.Variable{
Name: name,
Register: register,
Alive: uses,
})
if err != nil {
return err return err
}
err = f.ExpressionToRegister(right, variable.Register)
f.AddVariable(variable)
return err
}
if !ast.IsFunctionCall(right) {
return errors.New(errors.NotImplemented, f.File, node.Expression.Token.Position)
}
count := 0
err := f.CompileCall(right)
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
})
} }

View File

@ -8,9 +8,9 @@ import (
func (f *Function) CompileReturn(node *ast.Return) error { func (f *Function) CompileReturn(node *ast.Return) error {
defer f.Return() defer f.Return()
if node.Value == nil { if len(node.Values) == 0 {
return nil return nil
} }
return f.ExpressionToRegister(node.Value, f.CPU.Output[0]) return f.ExpressionsToRegisters(node.Values, f.CPU.Output)
} }

31
src/build/core/Define.go Normal file
View File

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

View File

@ -11,8 +11,9 @@ import (
// ExpressionToRegister puts the result of an expression into the specified register. // ExpressionToRegister puts the result of an expression into the specified register.
func (f *Function) ExpressionToRegister(node *expression.Expression, register cpu.Register) error { func (f *Function) ExpressionToRegister(node *expression.Expression, register cpu.Register) error {
if node.IsFolded {
f.SaveRegister(register) f.SaveRegister(register)
if node.IsFolded {
f.RegisterNumber(asm.MOVE, register, node.Value) f.RegisterNumber(asm.MOVE, register, node.Value)
return nil return nil
} }
@ -25,7 +26,6 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp
err := f.CompileCall(node) err := f.CompileCall(node)
if register != f.CPU.Output[0] { if register != f.CPU.Output[0] {
f.SaveRegister(register)
f.RegisterRegister(asm.MOVE, register, f.CPU.Output[0]) 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) err = f.Execute(node.Token, register, right)
if register != final { if register != final {
f.SaveRegister(final)
f.RegisterRegister(asm.MOVE, final, register) f.RegisterRegister(asm.MOVE, final, register)
f.FreeRegister(register) f.FreeRegister(register)
} }

View File

@ -7,7 +7,7 @@ import (
// ExpressionsToRegisters moves multiple expressions into the specified registers. // ExpressionsToRegisters moves multiple expressions into the specified registers.
func (f *Function) ExpressionsToRegisters(expressions []*expression.Expression, registers []cpu.Register) error { 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]) err := f.ExpressionToRegister(expressions[i], registers[i])
if err != nil { if err != nil {

View File

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

View File

@ -24,6 +24,7 @@ var programs = []struct {
{"reassign", "", "", 0}, {"reassign", "", "", 0},
{"reuse", "", "", 0}, {"reuse", "", "", 0},
{"return", "", "", 0}, {"return", "", "", 0},
{"return-multi", "", "", 0},
{"math", "", "", 0}, {"math", "", "", 0},
{"precedence", "", "", 0}, {"precedence", "", "", 0},
{"op-assign", "", "", 0}, {"op-assign", "", "", 0},