Skip to content

Commit 4b6abef

Browse files
committed
added return expression
1 parent 2f2fcef commit 4b6abef

File tree

8 files changed

+135
-46
lines changed

8 files changed

+135
-46
lines changed

README.md

Lines changed: 69 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -459,30 +459,6 @@ sayHello(person)
459459

460460
The Rune language is a simple, dynamic scripting language. The following chapter describes the syntax and features of the Rune language, including how to define and call functions, use variables, control flow with `if` and `while` statements, data types, arrays, tables and more.
461461

462-
## Defining and Calling Functions
463-
464-
### Defining Functions
465-
466-
Functions in Rune are defined by assigning a function to a name.
467-
468-
Example:
469-
470-
```js
471-
greet = func() {
472-
print("Greetings")
473-
}
474-
```
475-
476-
### Calling Functions
477-
478-
Functions are called using the name followed by parentheses and optional arguments.
479-
480-
Example:
481-
482-
```js
483-
greet()
484-
```
485-
486462
## Defining and Using Variables
487463

488464
### Defining Variables
@@ -629,6 +605,75 @@ if false {
629605

630606
```
631607

608+
## Defining and Calling Functions
609+
610+
### Defining Functions
611+
612+
Functions in Rune are defined by assigning a function to a name.
613+
614+
Example:
615+
616+
```js
617+
greet = func() {
618+
print("Greetings")
619+
}
620+
```
621+
622+
### Calling Functions
623+
624+
Functions are called using the name followed by parentheses and optional arguments.
625+
626+
Example:
627+
628+
```js
629+
greet()
630+
```
631+
632+
## Return
633+
The last expression of a function will be returned, so the return keyword is optional.
634+
635+
However, you can use `return` to early exit a function.
636+
637+
>**Differences:** In Rune, you have to explicitly bind the return value with `<` to `return`.
638+
639+
The example below does not return 42, because 42 is not part of the return expression even though it is on the same line. It will return `false` because no return value has been given to `return`.
640+
```js
641+
main = fun() {
642+
println("Hello, World!")
643+
return 42
644+
println("After return")
645+
}
646+
val = main()
647+
println(val)
648+
```
649+
output:
650+
```
651+
Hello, World!
652+
false
653+
```
654+
655+
To explicitly provide a return value to `return`, you have to bind it to `return` with `<`.
656+
```js
657+
main = fun() {
658+
println("Hello, World!")
659+
return < 42
660+
println("After return")
661+
662+
}
663+
val = main()
664+
println(val)
665+
```
666+
667+
output:
668+
```
669+
Hello, World!
670+
42
671+
```
672+
673+
>**Note:** using `return` to return a value from a function is not ideomatic in Rune. Because the last expression in a function will be returned, `return` with a value is rarely needed.
674+
675+
>**Nice to know:** `return` skips all further expression in the scope where it was used. So using it in the top level will quit further execution of the script, basically acting as a program exit.
676+
632677
## Arrays
633678
In Rune you can define an array by binding it to a name:
634679
```js

ast.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const (
1414
If ExprType = "if"
1515
Prog ExprType = "prog"
1616
Call ExprType = "call"
17+
Return ExprType = "return"
1718
While ExprType = "while"
1819
Array ExprType = "array"
1920
Table ExprType = "table"

editor/vscode/rune/rune-1.0.0.vsix

5 Bytes
Binary file not shown.

editor/vscode/rune/syntaxes/rune.tmLanguage.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
"patterns": [
4242
{
4343
"name": "keyword.control.rune",
44-
"match": "\\b(if|then|elif|else|while|import)\\b"
44+
"match": "\\b(if|then|elif|else|while|import|return)\\b"
4545
},
4646
{
4747
"name": "constant.language.rune",

evaluator.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ func NewEvaluator() *Evaluator {
1717
return e
1818
}
1919

20+
type ReturnValue struct {
21+
Value interface{}
22+
}
23+
2024
func (e *Evaluator) evaluate(exp *Expr, env *Environment) interface{} {
2125
if exp == nil {
2226
Error(exp, "Null expression error, this is a bug and should never happen!. Please file a bug!")
@@ -50,6 +54,7 @@ func (e *Evaluator) evaluate(exp *Expr, env *Environment) interface{} {
5054
Error(exp, "Variable %v is not an array or table", exp.Value)
5155
}
5256
}
57+
5358
return value
5459

5560
case Assign:
@@ -174,7 +179,11 @@ func (e *Evaluator) evaluate(exp *Expr, env *Environment) interface{} {
174179
case Prog:
175180
var val interface{} = false
176181
for _, ex := range exp.Prog {
177-
val = e.evaluate(ex, env)
182+
result := e.evaluate(ex, env)
183+
if ret, ok := result.(ReturnValue); ok {
184+
return ret.Value
185+
}
186+
val = result
178187
}
179188
return val
180189

@@ -205,6 +214,9 @@ func (e *Evaluator) evaluate(exp *Expr, env *Environment) interface{} {
205214
}
206215
return ret
207216

217+
case Return:
218+
return ReturnValue{Value: e.evaluate(exp.Right, env)}
219+
208220
case Import:
209221
path := e.evaluate(exp.Left, env).(string) + ".rune"
210222
if _, alreadyImported := e.importedPaths[path]; alreadyImported {

example/test.rune

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,14 @@
11
# Hello World
22
main = fun() {
3-
print("Hello, World!")
4-
}
5-
6-
# Factorial
7-
factorial = fun(n) {
8-
if n == 0 then 1
9-
n * factorial(n - 1)
10-
}
11-
println(factorial(5)) # Outputs 120
3+
println("Hello, World!")
4+
return
5+
println("After return")
126

13-
# Fibonacci
14-
fibonacci = fun(n) {
15-
if n <= 1 then n
16-
fibonacci(n - 1) + fibonacci(n - 2)
177
}
18-
print(fibonacci(10)) # Outputs 55
19-
exit()
8+
val = main()
9+
println(val)
2010

11+
return 42
2112
myvar = "12"
2213

2314
mybool = not 13 # false because 13 is truthy

parser.go

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -393,9 +393,7 @@ func (p *Parser) parseNotExpr() *Expr {
393393
tok := p.input.Peek()
394394
p.skipKw("not")
395395
expr := p.parseExpression()
396-
// if expr.Type != Bool && expr.Type != Binary && expr.Type != Assign && expr.Type != Call && expr.Type != Var {
397-
// p.unexpected(tok)
398-
// }
396+
399397
return &Expr{
400398
Type: Unary,
401399
Operator: "not",
@@ -406,6 +404,26 @@ func (p *Parser) parseNotExpr() *Expr {
406404
}
407405
}
408406

407+
func (p *Parser) parseReturnExpr() *Expr {
408+
tok := p.input.Peek()
409+
p.skipKw("return")
410+
var expr *Expr
411+
if p.isOp("<") != nil {
412+
p.input.Next()
413+
expr = p.parseExpression()
414+
} else {
415+
// Inject a false expression if the return has no argument
416+
expr = FALSE
417+
}
418+
return &Expr{
419+
Type: Return,
420+
Right: expr,
421+
File: tok.File,
422+
Line: tok.Line,
423+
Col: tok.Col,
424+
}
425+
}
426+
409427
func (p *Parser) parseAtom() *Expr {
410428
var expr *Expr
411429
if p.isPunc("(") != nil {
@@ -431,6 +449,8 @@ func (p *Parser) parseAtom() *Expr {
431449
expr = p.parseImport()
432450
} else if p.isKw("not") != nil {
433451
expr = p.parseNotExpr()
452+
} else if p.isKw("return") != nil {
453+
expr = p.parseReturnExpr()
434454

435455
} else {
436456
tok := p.input.Next()

tokenstream.go

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,13 @@ type Token struct {
1818
type TokenStream struct {
1919
input *InputStream
2020
current *Token
21+
last *Token
2122
keywords map[string]bool
2223
}
2324

2425
func NewTokenStream(input *InputStream) *TokenStream {
2526
keywords := map[string]bool{
26-
"if": true, "then": true, "elif": true, "else": true, "while": true, "fun": true,
27+
"if": true, "then": true, "elif": true, "else": true, "while": true, "fun": true, "return": true,
2728
"true": true, "false": true, "array": true, "table": true, "import": true, "not": true,
2829
}
2930
return &TokenStream{input: input, keywords: keywords}
@@ -117,10 +118,12 @@ func (ts *TokenStream) skipComment() {
117118

118119
func (ts *TokenStream) readNext() *Token {
119120
ts.readWhile(ts.isWhitespace)
121+
120122
if ts.input.Eof() {
121123
return nil
122124
}
123125
ch := ts.input.Peek()
126+
124127
switch {
125128
case ch == '#':
126129
ts.skipComment()
@@ -151,12 +154,17 @@ func (ts *TokenStream) Peek() *Token {
151154
return ts.current
152155
}
153156

157+
func (ts *TokenStream) Last() *Token {
158+
return ts.last
159+
}
160+
154161
func (ts *TokenStream) Next() *Token {
155162
tok := ts.current
156163
ts.current = nil
157164
if tok == nil {
158-
return ts.readNext()
165+
tok = ts.readNext()
159166
}
167+
ts.last = ts.copyToken(tok)
160168
return tok
161169
}
162170

@@ -167,3 +175,15 @@ func (ts *TokenStream) Eof() bool {
167175
func (ts *TokenStream) Error(tok *Token, msg string) {
168176
ts.input.Error(tok, msg)
169177
}
178+
179+
func (ts *TokenStream) copyToken(tok *Token) *Token {
180+
t := &Token{
181+
Type: tok.Type,
182+
Value: tok.Value,
183+
File: tok.File,
184+
Line: tok.Line,
185+
Col: tok.Col,
186+
Length: tok.Length,
187+
}
188+
return t
189+
}

0 commit comments

Comments
 (0)