Implemented multiple return values
This commit is contained in:
parent
d4020da6d9
commit
42f0367a94
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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:
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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:
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -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
31
src/build/core/Define.go
Normal 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
|
||||||
|
}
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
28
tests/programs/return-multi.q
Normal file
28
tests/programs/return-multi.q
Normal 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
|
||||||
|
}
|
@ -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},
|
||||||
|
Loading…
Reference in New Issue
Block a user