diff --git a/ast/ast.go b/ast/ast.go index db755a01..fa88c929 100644 --- a/ast/ast.go +++ b/ast/ast.go @@ -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 diff --git a/eval/eval.go b/eval/eval.go index 1bb8b73a..a0d2fb44 100644 --- a/eval/eval.go +++ b/eval/eval.go @@ -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: @@ -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 @@ -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 diff --git a/eval/eval_api.go b/eval/eval_api.go index f07ebccb..ea0672a2 100644 --- a/eval/eval_api.go +++ b/eval/eval_api.go @@ -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" ) @@ -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 @@ -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 diff --git a/examples/for.gr b/examples/for.gr index a5283e0a..751a6c25 100644 --- a/examples/for.gr +++ b/examples/for.gr @@ -14,7 +14,8 @@ 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)} @@ -22,5 +23,5 @@ 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)} diff --git a/examples/pi2.gr b/examples/pi2.gr index fde73d73..c5009357 100644 --- a/examples/pi2.gr +++ b/examples/pi2.gr @@ -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) diff --git a/object/object.go b/object/object.go index 936cd94c..d6abf1f7 100644 --- a/object/object.go +++ b/object/object.go @@ -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 { diff --git a/parser/parser.go b/parser/parser.go index 31c0e620..035ce20a 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -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) @@ -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 diff --git a/token/token.go b/token/token.go index d63816f1..5fb50af6 100644 --- a/token/token.go +++ b/token/token.go @@ -128,6 +128,8 @@ const ( ELSE RETURN FOR + BREAK + CONTINUE // Macro magic. MACRO QUOTE diff --git a/token/type_string.go b/token/type_string.go index 0c5bb784..de0116a7 100644 --- a/token/type_string.go +++ b/token/type_string.go @@ -65,23 +65,25 @@ func _() { _ = x[ELSE-54] _ = x[RETURN-55] _ = x[FOR-56] - _ = x[MACRO-57] - _ = x[QUOTE-58] - _ = x[UNQUOTE-59] - _ = x[LEN-60] - _ = x[FIRST-61] - _ = x[REST-62] - _ = x[PRINT-63] - _ = x[PRINTLN-64] - _ = x[LOG-65] - _ = x[ERROR-66] - _ = x[endIdentityTokens-67] - _ = x[EOF-68] + _ = x[BREAK-57] + _ = x[CONTINUE-58] + _ = x[MACRO-59] + _ = x[QUOTE-60] + _ = x[UNQUOTE-61] + _ = x[LEN-62] + _ = x[FIRST-63] + _ = x[REST-64] + _ = x[PRINT-65] + _ = x[PRINTLN-66] + _ = x[LOG-67] + _ = x[ERROR-68] + _ = x[endIdentityTokens-69] + _ = x[EOF-70] } -const _Type_name = "ILLEGALEOLstartValueTokensIDENTINTFLOATSTRINGLINECOMMENTBLOCKCOMMENTendValueTokensstartSingleCharTokensASSIGNPLUSMINUSBANGASTERISKSLASHPERCENTLTGTBITANDBITORBITXORBITNOTCOMMASEMICOLONLPARENRPARENLBRACERBRACELBRACKETRBRACKETCOLONDOTendSingleCharTokensstartMultiCharTokensLTEQGTEQEQNOTEQINCRDECRDOTDOTORANDLEFTSHIFTRIGHTSHIFTLAMBDAendMultiCharTokensstartIdentityTokensFUNCTRUEFALSEIFELSERETURNFORMACROQUOTEUNQUOTELENFIRSTRESTPRINTPRINTLNLOGERRORendIdentityTokensEOF" +const _Type_name = "ILLEGALEOLstartValueTokensIDENTINTFLOATSTRINGLINECOMMENTBLOCKCOMMENTendValueTokensstartSingleCharTokensASSIGNPLUSMINUSBANGASTERISKSLASHPERCENTLTGTBITANDBITORBITXORBITNOTCOMMASEMICOLONLPARENRPARENLBRACERBRACELBRACKETRBRACKETCOLONDOTendSingleCharTokensstartMultiCharTokensLTEQGTEQEQNOTEQINCRDECRDOTDOTORANDLEFTSHIFTRIGHTSHIFTLAMBDAendMultiCharTokensstartIdentityTokensFUNCTRUEFALSEIFELSERETURNFORBREAKCONTINUEMACROQUOTEUNQUOTELENFIRSTRESTPRINTPRINTLNLOGERRORendIdentityTokensEOF" -var _Type_index = [...]uint16{0, 7, 10, 26, 31, 34, 39, 45, 56, 68, 82, 103, 109, 113, 118, 122, 130, 135, 142, 144, 146, 152, 157, 163, 169, 174, 183, 189, 195, 201, 207, 215, 223, 228, 231, 250, 270, 274, 278, 280, 285, 289, 293, 299, 301, 304, 313, 323, 329, 347, 366, 370, 374, 379, 381, 385, 391, 394, 399, 404, 411, 414, 419, 423, 428, 435, 438, 443, 460, 463} +var _Type_index = [...]uint16{0, 7, 10, 26, 31, 34, 39, 45, 56, 68, 82, 103, 109, 113, 118, 122, 130, 135, 142, 144, 146, 152, 157, 163, 169, 174, 183, 189, 195, 201, 207, 215, 223, 228, 231, 250, 270, 274, 278, 280, 285, 289, 293, 299, 301, 304, 313, 323, 329, 347, 366, 370, 374, 379, 381, 385, 391, 394, 399, 407, 412, 417, 424, 427, 432, 436, 441, 448, 451, 456, 473, 476} func (i Type) String() string { if i >= Type(len(_Type_index)-1) {