Skip to content
This repository has been archived by the owner on Dec 20, 2018. It is now read-only.

Commit

Permalink
Add support for functions to return multiple values
Browse files Browse the repository at this point in the history
  • Loading branch information
haifenghuang committed Apr 24, 2018
1 parent e40bab0 commit 9813291
Show file tree
Hide file tree
Showing 7 changed files with 175 additions and 58 deletions.
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1846,6 +1846,28 @@ if ret[1] != "" {
}
```

Starting from ver5.0, Monkey support multiple return values using 'let'.
The returned values are wrapped as a tuple.

```swift
fn testReturn(a, b, c, d=40) {
return a, b, c, d
}

let (x, y, c, d) = testReturn(10, 20, 30)
// let x, y, c, d = testReturn(10, 20, 30) same as above

printf("x=%v, y=%v, c=%v, d=%v\n", x, y, c, d)
//Result: x=10, y=20, c=30, d=40
```

Note:You must use `let` to support multiple return values, below statement will issue a compile error.

```swift
(x, y, c, d) = testReturn(10, 20, 30) // no 'let', compile error
x, y, c, d = testReturn(10, 20, 30) // no 'let', compile error
```

### Pipe Operator

The pipe operator, inspired by [Elixir](https://elixir-lang.org/).
Expand Down
22 changes: 22 additions & 0 deletions README_cn.md
Original file line number Diff line number Diff line change
Expand Up @@ -1817,6 +1817,28 @@ if ret[1] != "" {
}
```

从版本5.0开始,Monkey可以使用let语句支持函数返回多个值
返回的多个值被包装为一个元祖(tuple)

```swift
fn testReturn(a, b, c, d=40) {
return a, b, c, d
}

let (x, y, c, d) = testReturn(10, 20, 30)
// let x, y, c, d = testReturn(10, 20, 30) same as above

printf("x=%v, y=%v, c=%v, d=%v\n", x, y, c, d)
//Result: x=10, y=20, c=30, d=40
```

注:必须使用`let`才能够支持支持函数的多个返回值,下面的语句无法通过编译

```swift
(x, y, c, d) = testReturn(10, 20, 30) // no 'let', compile error
x, y, c, d = testReturn(10, 20, 30) // no 'let', compile error
```

### Pipe操作符

`pipe`操作符来自[Elixir](https://elixir-lang.org/).
Expand Down
19 changes: 14 additions & 5 deletions src/monkey/ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -1207,17 +1207,20 @@ func (ds *DeferStmt) String() string {
///////////////////////////////////////////////////////////
type ReturnStatement struct {
Token token.Token
ReturnValue Expression
ReturnValue Expression //for old campatibility
ReturnValues []Expression
}

func (rs *ReturnStatement) Pos() token.Position {
return rs.Token.Pos
}

func (rs *ReturnStatement) End() token.Position {
if rs.ReturnValue != nil {
return rs.ReturnValue.End()
aLen := len(rs.ReturnValues)
if aLen > 0 {
return rs.ReturnValues[aLen-1].End()
}

return token.Position{Line: rs.Token.Pos.Line, Col: rs.Token.Pos.Col + len(rs.Token.Literal)}
}

Expand All @@ -1229,9 +1232,15 @@ func (rs *ReturnStatement) String() string {

out.WriteString(rs.TokenLiteral() + " ")

if rs.ReturnValue != nil {
out.WriteString(rs.ReturnValue.String())
// if rs.ReturnValue != nil {
// out.WriteString(rs.ReturnValue.String())
// }

values := []string{}
for _, value := range rs.ReturnValues {
values = append(values, value.String())
}
out.WriteString(strings.Join(values, ", "))

out.WriteString(";")

Expand Down
113 changes: 69 additions & 44 deletions src/monkey/eval/eval.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,9 @@ func Eval(node ast.Node, scope *Scope) (val Object) {
case *ast.LetStatement:
return evalLetStatement(node, scope)
case *ast.ReturnStatement:
return evalReturnStatment(node, scope)
return evalReturnStatement(node, scope)
case *ast.DeferStmt:
return evalDeferStatment(node, scope)
return evalDeferStatement(node, scope)
case *ast.FunctionStatement:
return evalFunctionStatement(node, scope)
case *ast.Boolean:
Expand Down Expand Up @@ -189,7 +189,7 @@ func Eval(node ast.Node, scope *Scope) (val Object) {
case *ast.TernaryExpression:
return evalTernaryExpression(node, scope)
case *ast.SpawnStmt:
return evalSpawnStatment(node, scope)
return evalSpawnStatement(node, scope)
case *ast.YieldExpression:
return NIL
case *ast.NilLiteral:
Expand Down Expand Up @@ -276,8 +276,6 @@ func evalIncludeStatement(i *ast.IncludeStatement, scope *Scope) Object {
}

func evalLetStatement(l *ast.LetStatement, scope *Scope) (val Object) {
valuesLen := len(l.Values)

if l.DestructingFlag {
v := Eval(l.Values[0], scope)
valType := v.Type()
Expand All @@ -301,7 +299,7 @@ func evalLetStatement(l *ast.LetStatement, scope *Scope) (val Object) {

case ARRAY_OBJ:
arr := v.(*Array)
valuesLen = len(arr.Members)
valuesLen := len(arr.Members)
for idx, item := range l.Names {
if idx >= valuesLen { //There are more Values than Names
val = NIL
Expand All @@ -318,7 +316,7 @@ func evalLetStatement(l *ast.LetStatement, scope *Scope) (val Object) {

case TUPLE_OBJ:
tup := v.(*Tuple)
valuesLen = len(tup.Members)
valuesLen := len(tup.Members)
for idx, item := range l.Names {
if idx >= valuesLen { //There are more Values than Names
val = NIL
Expand All @@ -340,12 +338,35 @@ func evalLetStatement(l *ast.LetStatement, scope *Scope) (val Object) {
return
}

values := []Object{}
valuesLen := 0
for _, value := range l.Values {
val := Eval(value, scope)
if val.Type() == TUPLE_OBJ {
tupleObj := val.(*Tuple)
if tupleObj.IsMulti {
valuesLen += len(tupleObj.Members)
for _, tupleItem := range tupleObj.Members {
values = append(values, tupleItem)
}
} else {
valuesLen += 1
values = append(values, tupleObj)
}

} else {
valuesLen += 1
values = append(values, val)
}
}

for idx, item := range l.Names {
if idx >= valuesLen { //There are more Values than Names
val = NIL
scope.Set(item.String(), val)
} else {
val = Eval(l.Values[idx], scope)
//val = Eval(l.Values[idx], scope)
val = values[idx];
if val.Type() != ERROR_OBJ {
scope.Set(item.String(), val)
} else {
Expand Down Expand Up @@ -896,6 +917,28 @@ func evalAssignExpression(a *ast.AssignExpression, scope *Scope) (val Object) {
return evalClassIndexerAssignExpression(a, aObj, nodeType, val, scope)
}
}
// case *ast.TupleLiteral:
// if val.Type() != TUPLE_OBJ {
// panic(NewError(a.Pos().Sline(), GENERICERROR, "The right part must be a tuple"))
// }
//
// tupleObj := val.(*Tuple)
// valuesLen := len(tupleObj.Members)
//
// for idx, item := range nodeType.Members {
// if idx >= valuesLen { //There are more Values than Names
// val = NIL
// scope.Set(item.String(), val)
// } else {
// val = tupleObj.Members[idx];
// if val.Type() != ERROR_OBJ {
// scope.Set(item.String(), val)
// } else {
// return
// }
// }
// }
// return
}

if a.Token.Literal == "=" {
Expand Down Expand Up @@ -938,15 +981,22 @@ func evalAssignExpression(a *ast.AssignExpression, scope *Scope) (val Object) {
panic(NewError(a.Pos().Sline(), INFIXOP, left.Type(), a.Token.Literal, val.Type()))
}

func evalReturnStatment(r *ast.ReturnStatement, scope *Scope) Object {
if value := Eval(r.ReturnValue, scope); value != nil {
return &ReturnValue{Value: value}
func evalReturnStatement(r *ast.ReturnStatement, scope *Scope) Object {
ret := &ReturnValue{Values: []Object{}}
for _, value := range r.ReturnValues {
ret.Values = append(ret.Values, Eval(value, scope))
}

// for old campatibility
if len(ret.Values) > 0 {
ret.Value = ret.Values[0]
return ret
}

return NIL
}

func evalDeferStatment(d *ast.DeferStmt, scope *Scope) Object {
func evalDeferStatement(d *ast.DeferStmt, scope *Scope) Object {
frame := scope.CurrentFrame()
if frame == nil {
panic(NewError(d.Pos().Sline(), DEFERERROR))
Expand Down Expand Up @@ -3355,17 +3405,21 @@ func evalFunctionCall(call *ast.CallExpression, scope *Scope) Object {
f.Scope.Set("@_", NewInteger(int64(len(f.Literal.Parameters))))
}

extendedScope := extendFunctionScope(f, newScope, args)
oldInstance := currentInstance
currentInstance = f.Instance

r := Eval(f.Literal.Body, extendedScope)
r := Eval(f.Literal.Body, newScope)
currentInstance = oldInstance
if r.Type() == ERROR_OBJ {
return r
}

if obj, ok := r.(*ReturnValue); ok {
// if function returns multiple-values
// returns a tuple instead.
if len(obj.Values) > 1 {
return &Tuple{Members: obj.Values, IsMulti: true}
}
return obj.Value
}
return r
Expand Down Expand Up @@ -4145,7 +4199,7 @@ func evalTernaryExpression(te *ast.TernaryExpression, scope *Scope) Object {
}
}

func evalSpawnStatment(s *ast.SpawnStmt, scope *Scope) Object {
func evalSpawnStatement(s *ast.SpawnStmt, scope *Scope) Object {
newSpawnScope := NewScope(scope)

switch callExp := s.Call.(type) {
Expand Down Expand Up @@ -4467,8 +4521,6 @@ func evalFunctionDirect(fn Object, args []Object, scope *Scope) Object {
}

//newScope.DebugPrint(" ") //debug
// extendedScope := extendFunctionScope(fn, fn.Scope, args)

results := Eval(fn.Literal.Body, newScope)
if results.Type() == RETURN_VALUE_OBJ {
return results.(*ReturnValue).Value
Expand All @@ -4483,33 +4535,6 @@ func evalFunctionDirect(fn Object, args []Object, scope *Scope) Object {
panic(NewError("", GENERICERROR, fn.Type() + " is not a function"))
}

func extendFunctionScope(fn *Function, outer *Scope, args []Object) *Scope {
innerScope := NewScope(outer)

for i, param := range fn.Literal.Parameters {
innerScope.Set(param.(*ast.Identifier).Value, args[i])
}

// The "args" variable hold all extra parameters beyond those defined
// by the function at runtime. "args[0]" is the first EXTRA parameter
// after those that were defined have been bound.

// Although the elements of the args variable could be reassigned,
// I'm trying to discourage it by at least making the variable itself
// a constant. Trying to indicate "please don't mess with it". Mainly
// this is so the variable isn't overwritten accidentally.
if len(args) > len(fn.Literal.Parameters) {
innerScope.Set("args", &Array{Members: args[len(fn.Literal.Parameters):]})
} else {
// The idea is for functions to call "len(args)" to check for
// anything extra. "len(nil)" returns 0.
innerScope.Set("args", &Array{})
}

return innerScope
}


// Convert a Object to an ast.Expression.
func obj2Expression(obj Object) ast.Expression {
switch value := obj.(type) {
Expand Down
20 changes: 18 additions & 2 deletions src/monkey/eval/object.go
Original file line number Diff line number Diff line change
Expand Up @@ -235,9 +235,25 @@ func (f *Function) CallMethod(line string, scope *Scope, method string, args ...
panic(NewError(line, NOMETHODERROR, method, f.Type()))
}

type ReturnValue struct{ Value Object }
type ReturnValue struct {
Value Object // for old campatibility
Values []Object
}

func (rv *ReturnValue) Inspect() string {
//return rv.Value.Inspect()

var out bytes.Buffer
values := []string{}
for _, v := range rv.Values {
values = append(values, v.Inspect())
}

out.WriteString(strings.Join(values, ", "))

return out.String()
}

func (rv *ReturnValue) Inspect() string { return rv.Value.Inspect() }
func (rv *ReturnValue) Type() ObjectType { return RETURN_VALUE_OBJ }
func (rv *ReturnValue) CallMethod(line string, scope *Scope, method string, args ...Object) Object {
panic(NewError(line, NOMETHODERROR, method, rv.Type()))
Expand Down
3 changes: 3 additions & 0 deletions src/monkey/eval/tuple.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ import (
*/

type Tuple struct {
// Used in function return values.
// if function returns multiple values, it's true
IsMulti bool
Members []Object
}

Expand Down
Loading

0 comments on commit 9813291

Please sign in to comment.