Skip to content

Commit

Permalink
Adding break and continue for for loops. (#197)
Browse files Browse the repository at this point in the history
* Add break and continue

* tweak the example for brievety
  • Loading branch information
ldemailly authored Aug 29, 2024
1 parent 66aa34d commit ddc01b0
Show file tree
Hide file tree
Showing 9 changed files with 84 additions and 28 deletions.
5 changes: 5 additions & 0 deletions ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,11 @@ func (b Base) PrettyPrint(ps *PrintState) *PrintState {
return ps.Print(b.Literal())
}

// Break or continue statement.
type ControlExpression struct {
Base
}

type ReturnStatement struct {
Base
ReturnValue Node
Expand Down
49 changes: 41 additions & 8 deletions eval/eval.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,12 +220,14 @@ func (s *State) evalInternal(node any) object.Object { //nolint:funlen,gocyclo,g
case *ast.StringLiteral:
return object.String{Value: node.Literal()}

case *ast.ControlExpression:
return object.ReturnValue{Value: object.NULL, ControlType: node.Type()}
case *ast.ReturnStatement:
if node.ReturnValue == nil {
return object.ReturnValue{Value: object.NULL}
return object.ReturnValue{Value: object.NULL, ControlType: token.RETURN}
}
val := s.evalInternal(node.ReturnValue)
return object.ReturnValue{Value: val}
return object.ReturnValue{Value: val, ControlType: token.RETURN}
case *ast.Builtin:
return s.evalBuiltin(node)
case *ast.FunctionLiteral:
Expand Down Expand Up @@ -694,9 +696,24 @@ func (s *State) evalForInteger(fe *ast.ForExpression, start *object.Integer, end
if name != "" {
s.env.Set(name, object.Integer{Value: int64(i)})
}
lastEval = s.evalInternal(fe.Body)
if rt := lastEval.Type(); rt == object.RETURN || rt == object.ERROR {
return lastEval
nextEval := s.evalInternal(fe.Body)
switch nextEval.Type() {
case object.ERROR:
return nextEval
case object.RETURN:
r := nextEval.(object.ReturnValue)
switch r.ControlType {
case token.BREAK:
return lastEval
case token.CONTINUE:
continue
case token.RETURN:
return r
default:
return s.Errorf("for loop unexpected control type %s", r.ControlType.String())
}
default:
lastEval = nextEval
}
}
return lastEval
Expand Down Expand Up @@ -750,9 +767,25 @@ func (s *State) evalForList(fe *ast.ForExpression, list object.Object, name stri
return s.NewError("for list element is nil")
}
s.env.Set(name, v)
lastEval = s.evalInternal(fe.Body)
if rt := lastEval.Type(); rt == object.RETURN || rt == object.ERROR {
return lastEval
// Copy pasta from evalForInteger. hard to share control flow.
nextEval := s.evalInternal(fe.Body)
switch nextEval.Type() {
case object.ERROR:
return nextEval
case object.RETURN:
r := nextEval.(object.ReturnValue)
switch r.ControlType {
case token.BREAK:
return lastEval
case token.CONTINUE:
continue
case token.RETURN:
return r
default:
return s.Errorf("for loop unexpected control type %s", r.ControlType.String())
}
default:
lastEval = nextEval
}
}
return lastEval
Expand Down
6 changes: 5 additions & 1 deletion eval/eval_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"grol.io/grol/lexer"
"grol.io/grol/object"
"grol.io/grol/parser"
"grol.io/grol/token"
"grol.io/grol/trie"
)

Expand All @@ -18,7 +19,7 @@ import (
// runtime: goroutine stack exceeds 1000000000-byte limit
// fatal error: stack overflow. Was 250k but adding a log
// in Error() makes it go over that (somehow).
const DefaultMaxDepth = 195_000
const DefaultMaxDepth = 150_000

type State struct {
Out io.Writer
Expand Down Expand Up @@ -114,6 +115,9 @@ func (s *State) Eval(node any) object.Object {
s.depth--
// unwrap return values only at the top.
if returnValue, ok := result.(object.ReturnValue); ok {
if returnValue.ControlType != token.RETURN {
return s.Errorf("unexpected control type %v outside of for loops", returnValue.ControlType)
}
return returnValue.Value
}
return result
Expand Down
7 changes: 4 additions & 3 deletions examples/for.gr
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,14 @@

for 3 {println("this will be printed 3 times")}

for i = -2:5 {println("i is",i)}
// Also demonstrates break, loop ends when i > 3 instead of continuing to 36
for i = -2:37 {println("i is",i); if i >= 3 {break}}

// like first() iterating over maps produces key-value pairs (maps with keys `key` and `value`)
for kv = {"c":3,"a":1,"b":2} {println("key",kv.key,"value",kv.value)}

// iterating over arrays
for v = [2,-5,7] {println("v",v)}

// For strings iterations is on runes
for c = "ab😀" {println("rune",c)}
// For strings iterations is on runes - demonstrate continue (skip the "b")
for c = "ab😀" {if c=="b" {continue} println("rune",c)}
2 changes: 1 addition & 1 deletion examples/pi2.gr
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ f = func(i, n, prod) {
}
self(i+1, n, prod*(1-1./(2*i)))
}
n = 194_990 // close to 195_000 default limit.
n = 149_990 // close to 150_000 default limit.
f(1, n, 1)
3 changes: 2 additions & 1 deletion object/object.go
Original file line number Diff line number Diff line change
Expand Up @@ -507,7 +507,8 @@ func (e Error) Inspect() string {
}

type ReturnValue struct {
Value Object
Value Object
ControlType token.Type
}

func (rv ReturnValue) JSON(w io.Writer) error {
Expand Down
8 changes: 8 additions & 0 deletions parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ func New(l *lexer.Lexer) *Parser {
p.registerPrefix(token.LPAREN, p.parseGroupedExpression)
p.registerPrefix(token.IF, p.parseIfExpression)
p.registerPrefix(token.FOR, p.parseForExpression)
p.registerPrefix(token.BREAK, p.parseControlExpression)
p.registerPrefix(token.CONTINUE, p.parseControlExpression)
p.registerPrefix(token.FUNC, p.parseFunctionLiteral)
p.registerPrefix(token.STRING, p.parseStringLiteral)
p.registerPrefix(token.LEN, p.parseBuiltin)
Expand Down Expand Up @@ -500,6 +502,12 @@ func (p *Parser) parseInfixExpression(left ast.Node) ast.Node {
return expression
}

func (p *Parser) parseControlExpression() ast.Node {
expression := &ast.ControlExpression{}
expression.Token = p.curToken
return expression
}

func (p *Parser) parseForExpression() ast.Node {
expression := &ast.ForExpression{}
expression.Token = p.curToken
Expand Down
2 changes: 2 additions & 0 deletions token/token.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@ const (
ELSE
RETURN
FOR
BREAK
CONTINUE
// Macro magic.
MACRO
QUOTE
Expand Down
30 changes: 16 additions & 14 deletions token/type_string.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit ddc01b0

Please sign in to comment.