From 129b850e953655e633dd07d035e23d533bcfdd2a Mon Sep 17 00:00:00 2001 From: Flipez Date: Mon, 31 Oct 2022 11:12:58 +0100 Subject: [PATCH 1/9] Add rescue to block Signed-off-by: Flipez --- ast/block.go | 8 ++++++++ evaluator/block.go | 8 +++++++- parser/block.go | 17 ++++++++++++++++- token/token.go | 3 +++ 4 files changed, 34 insertions(+), 2 deletions(-) diff --git a/ast/block.go b/ast/block.go index d2ea08d..f2c65b6 100644 --- a/ast/block.go +++ b/ast/block.go @@ -9,6 +9,7 @@ import ( type Block struct { Token token.Token // the { token Statements []Statement + Rescue *Block } func (bs *Block) TokenLiteral() string { return bs.Token.Literal } @@ -19,5 +20,12 @@ func (bs *Block) String() string { out.WriteString(s.String()) } + if bs.Rescue != nil { + out.WriteString("\nrescue\n") + for _, s := range bs.Rescue.Statements { + out.WriteString(s.String()) + } + } + return out.String() } diff --git a/evaluator/block.go b/evaluator/block.go index c142b92..0a604b6 100644 --- a/evaluator/block.go +++ b/evaluator/block.go @@ -13,7 +13,13 @@ func evalBlock(block *ast.Block, env *object.Environment) object.Object { if result != nil { rt := result.Type() - if rt == object.RETURN_VALUE_OBJ || rt == object.ERROR_OBJ || rt == object.BREAK_VALUE_OBJ || rt == object.NEXT_VALUE_OBJ { + if rt == object.ERROR_OBJ { + if block.Rescue != nil { + result = evalBlock(block.Rescue, env) + } + return result + } + if rt == object.RETURN_VALUE_OBJ || rt == object.BREAK_VALUE_OBJ || rt == object.NEXT_VALUE_OBJ { return result } } diff --git a/parser/block.go b/parser/block.go index 01d02c6..0631fe8 100644 --- a/parser/block.go +++ b/parser/block.go @@ -11,7 +11,7 @@ func (p *Parser) parseBlock() *ast.Block { p.nextToken() - for !p.curTokenIs(token.RBRACE) && !p.curTokenIs(token.EOF) && !p.curTokenIs(token.END) && !p.curTokenIs(token.ELSE) { + for !p.curTokenIs(token.RBRACE) && !p.curTokenIs(token.EOF) && !p.curTokenIs(token.END) && !p.curTokenIs(token.ELSE) && !p.curTokenIs(token.RESCUE) { stmt := p.parseStatement() if stmt != nil { block.Statements = append(block.Statements, stmt) @@ -20,5 +20,20 @@ func (p *Parser) parseBlock() *ast.Block { p.nextToken() } + if p.curTokenIs(token.RESCUE) { + block.Rescue = &ast.Block{Token: p.curToken} + p.nextToken() + block.Rescue.Statements = []ast.Statement{} + for !p.curTokenIs(token.RBRACE) && !p.curTokenIs(token.EOF) && !p.curTokenIs(token.END) && !p.curTokenIs(token.ELSE) { + stmt := p.parseStatement() + if stmt != nil { + block.Rescue.Statements = append(block.Rescue.Statements, stmt) + } + + p.nextToken() + } + + } + return block } diff --git a/token/token.go b/token/token.go index f72fc6c..1693304 100644 --- a/token/token.go +++ b/token/token.go @@ -75,6 +75,8 @@ const ( IMPORT = "IMPORT" NIL = "NIL" + + RESCUE = "RESCUE" ) var keywords = map[string]TokenType{ @@ -95,6 +97,7 @@ var keywords = map[string]TokenType{ "nil": NIL, "and": AND, "or": OR, + "rescue": RESCUE, } func LookupIdent(ident string) TokenType { From d1d6adfc6b978e27f0dfb593d35bacfd294939fa Mon Sep 17 00:00:00 2001 From: Flipez Date: Mon, 31 Oct 2022 11:19:46 +0100 Subject: [PATCH 2/9] Add ability to print error message Signed-off-by: Flipez --- evaluator/block.go | 1 + object/error.go | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/evaluator/block.go b/evaluator/block.go index 0a604b6..a4e1b42 100644 --- a/evaluator/block.go +++ b/evaluator/block.go @@ -15,6 +15,7 @@ func evalBlock(block *ast.Block, env *object.Environment) object.Object { rt := result.Type() if rt == object.ERROR_OBJ { if block.Rescue != nil { + env.Set("error", result) result = evalBlock(block.Rescue, env) } return result diff --git a/object/error.go b/object/error.go index c6cdd57..05b9a7f 100644 --- a/object/error.go +++ b/object/error.go @@ -19,3 +19,27 @@ func (e *Error) Inspect() string { return "ERROR: " + e.Message } func (e *Error) InvokeMethod(method string, env Environment, args ...Object) Object { return objectMethodLookup(e, method, env, args) } + +func init() { + objectMethods[ERROR_OBJ] = map[string]ObjectMethod{ + "msg": ObjectMethod{ + Layout: MethodLayout{ + Description: "Returns the error message", + Example: `» def () +puts(nope) +rescue +puts((rescued error: + error.msg())) +end +🚀 » test() +"rescued error:identifier not found: nope"`, + ReturnPattern: Args( + Arg(STRING_OBJ), + ), + }, + method: func(o Object, _ []Object, _ Environment) Object { + e := o.(*Error) + return NewString(e.Message) + }, + }, + } +} From 93e329c0b43b7cb62e68671d4f13b2b49612589c Mon Sep 17 00:00:00 2001 From: Flipez Date: Mon, 31 Oct 2022 12:03:03 +0100 Subject: [PATCH 3/9] Use rescue struct instead of block Signed-off-by: Flipez --- ast/block.go | 4 ++-- evaluator/block.go | 4 ++-- parser/block.go | 16 ++++------------ 3 files changed, 8 insertions(+), 16 deletions(-) diff --git a/ast/block.go b/ast/block.go index f2c65b6..474d1bf 100644 --- a/ast/block.go +++ b/ast/block.go @@ -9,7 +9,7 @@ import ( type Block struct { Token token.Token // the { token Statements []Statement - Rescue *Block + Rescue *Rescue } func (bs *Block) TokenLiteral() string { return bs.Token.Literal } @@ -22,7 +22,7 @@ func (bs *Block) String() string { if bs.Rescue != nil { out.WriteString("\nrescue\n") - for _, s := range bs.Rescue.Statements { + for _, s := range bs.Rescue.Block.Statements { out.WriteString(s.String()) } } diff --git a/evaluator/block.go b/evaluator/block.go index a4e1b42..5a43130 100644 --- a/evaluator/block.go +++ b/evaluator/block.go @@ -15,8 +15,8 @@ func evalBlock(block *ast.Block, env *object.Environment) object.Object { rt := result.Type() if rt == object.ERROR_OBJ { if block.Rescue != nil { - env.Set("error", result) - result = evalBlock(block.Rescue, env) + env.Set(block.Rescue.ErrorIdent.Literal, result) + result = evalBlock(block.Rescue.Block, env) } return result } diff --git a/parser/block.go b/parser/block.go index 0631fe8..7b8a317 100644 --- a/parser/block.go +++ b/parser/block.go @@ -21,18 +21,10 @@ func (p *Parser) parseBlock() *ast.Block { } if p.curTokenIs(token.RESCUE) { - block.Rescue = &ast.Block{Token: p.curToken} - p.nextToken() - block.Rescue.Statements = []ast.Statement{} - for !p.curTokenIs(token.RBRACE) && !p.curTokenIs(token.EOF) && !p.curTokenIs(token.END) && !p.curTokenIs(token.ELSE) { - stmt := p.parseStatement() - if stmt != nil { - block.Rescue.Statements = append(block.Rescue.Statements, stmt) - } - - p.nextToken() - } - + block.Rescue = &ast.Rescue{Token: p.curToken} + p.expectPeek(token.IDENT) + block.Rescue.ErrorIdent = p.curToken + block.Rescue.Block = p.parseBlock() } return block From 1f61139b24caee3e5b9833abaa3fdeef2bcab894 Mon Sep 17 00:00:00 2001 From: Flipez Date: Mon, 31 Oct 2022 12:04:56 +0100 Subject: [PATCH 4/9] Add rescue ast Signed-off-by: Flipez --- ast/rescue.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 ast/rescue.go diff --git a/ast/rescue.go b/ast/rescue.go new file mode 100644 index 0000000..b5e963b --- /dev/null +++ b/ast/rescue.go @@ -0,0 +1,16 @@ +package ast + +import ( + "github.com/flipez/rocket-lang/token" +) + +type Rescue struct { + Token token.Token // the { token + ErrorIdent token.Token + Block *Block +} + +func (r *Rescue) TokenLiteral() string { return r.Token.Literal } +func (r *Rescue) String() string { + return r.Block.String() +} From ecd1420737bdb23196b731e1e35e2cf56f0a729a Mon Sep 17 00:00:00 2001 From: Flipez Date: Mon, 31 Oct 2022 15:33:10 +0100 Subject: [PATCH 5/9] add blocks Signed-off-by: Flipez --- ast/begin.go | 23 +++++++++++++++++++++++ evaluator/evaluator.go | 16 +++++++++------- parser/begin.go | 12 ++++++++++++ parser/parser.go | 1 + token/token.go | 2 ++ 5 files changed, 47 insertions(+), 7 deletions(-) create mode 100644 ast/begin.go create mode 100644 parser/begin.go diff --git a/ast/begin.go b/ast/begin.go new file mode 100644 index 0000000..2274955 --- /dev/null +++ b/ast/begin.go @@ -0,0 +1,23 @@ +package ast + +import ( + "bytes" + + "github.com/flipez/rocket-lang/token" +) + +type Begin struct { + Token token.Token + Block *Block +} + +func (b *Begin) TokenLiteral() string { return b.Token.Literal } +func (b *Begin) String() string { + var out bytes.Buffer + + out.WriteString("begin\n") + out.WriteString(b.Block.String()) + out.WriteString("\nend") + + return out.String() +} diff --git a/evaluator/evaluator.go b/evaluator/evaluator.go index a4e75c1..9de8d4d 100644 --- a/evaluator/evaluator.go +++ b/evaluator/evaluator.go @@ -16,6 +16,8 @@ func Eval(node ast.Node, env *object.Environment) object.Object { return Eval(node.Expression, env) case *ast.Block: return evalBlock(node, env) + case *ast.Begin: + return evalBlock(node.Block, env) case *ast.Foreach: return evalForeach(node, env) case *ast.While: @@ -209,13 +211,13 @@ func evalBangOperatorExpression(right object.Object) object.Object { } func evalMinusPrefixOperatorExpression(right object.Object) object.Object { - switch val := right.(type) { - case *object.Integer: - return object.NewInteger(-val.Value) - case *object.Float: - return object.NewFloat(-val.Value) - } - return object.NewErrorFormat("unknown operator: -%s", right.Type()) + switch val := right.(type) { + case *object.Integer: + return object.NewInteger(-val.Value) + case *object.Float: + return object.NewFloat(-val.Value) + } + return object.NewErrorFormat("unknown operator: -%s", right.Type()) } func nativeBoolToBooleanObject(input bool) *object.Boolean { diff --git a/parser/begin.go b/parser/begin.go new file mode 100644 index 0000000..461234a --- /dev/null +++ b/parser/begin.go @@ -0,0 +1,12 @@ +package parser + +import ( + "github.com/flipez/rocket-lang/ast" +) + +func (p *Parser) parseBegin() ast.Expression { + expression := &ast.Begin{Token: p.curToken} + p.nextToken() + expression.Block = p.parseBlock() + return expression +} diff --git a/parser/parser.go b/parser/parser.go index 5fe0e71..cdc0234 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -89,6 +89,7 @@ func New(l *lexer.Lexer, imports map[string]struct{}) *Parser { p.registerPrefix(token.LBRACE, p.parseHash) p.registerPrefix(token.IMPORT, p.parseImport) p.registerPrefix(token.NIL, p.parseNil) + p.registerPrefix(token.BEGIN, p.parseBegin) p.infixParseFns = make(map[token.TokenType]infixParseFn) p.registerInfix(token.ASSIGN, p.parseAssignExpression) diff --git a/token/token.go b/token/token.go index 1693304..c557c0b 100644 --- a/token/token.go +++ b/token/token.go @@ -76,6 +76,7 @@ const ( NIL = "NIL" + BEGIN = "BEGIN" RESCUE = "RESCUE" ) @@ -97,6 +98,7 @@ var keywords = map[string]TokenType{ "nil": NIL, "and": AND, "or": OR, + "begin": BEGIN, "rescue": RESCUE, } From 9203f7ca5348483a007c47546c0f78f3bbb4370e Mon Sep 17 00:00:00 2001 From: Flipez Date: Mon, 31 Oct 2022 17:14:29 +0100 Subject: [PATCH 6/9] add tests Signed-off-by: Flipez --- evaluator/evaluator_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/evaluator/evaluator_test.go b/evaluator/evaluator_test.go index dc2999d..1a5505b 100644 --- a/evaluator/evaluator_test.go +++ b/evaluator/evaluator_test.go @@ -218,6 +218,9 @@ func TestErrorHandling(t *testing.T) { {"break(1.nope())", "undefined method `.nope()` for INTEGER"}, {"next(1.nope())", "undefined method `.nope()` for INTEGER"}, {"nil.nope()", "undefined method `.nope()` for NIL"}, + {"begin puts(nope) end", "identifier not found: nope"}, + {"begin puts(nope) rescue e e.nope() end", "undefined method `.nope()` for ERROR"}, + {"a = begin puts(nope) rescue e e.msg() end; a.nope()", "undefined method `.nope()` for STRING"}, } for _, tt := range tests { From 9b31a94a061907e52af6bdd1930515190a64aded Mon Sep 17 00:00:00 2001 From: Flipez Date: Mon, 31 Oct 2022 17:48:38 +0100 Subject: [PATCH 7/9] add more tests Signed-off-by: Flipez --- ast/ast_test.go | 4 ++++ ast/block.go | 3 ++- parser/begin.go | 1 - 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/ast/ast_test.go b/ast/ast_test.go index 986eabb..5a0c8d9 100644 --- a/ast/ast_test.go +++ b/ast/ast_test.go @@ -56,6 +56,10 @@ func TestString(t *testing.T) { "break", "break", }, + { + "begin\ntrue\nrescue e\nfalse\nend", + "begin\ntrue\nrescue e\nfalse\nend", + }, } for _, tt := range tests { diff --git a/ast/block.go b/ast/block.go index 474d1bf..7ae761f 100644 --- a/ast/block.go +++ b/ast/block.go @@ -21,7 +21,8 @@ func (bs *Block) String() string { } if bs.Rescue != nil { - out.WriteString("\nrescue\n") + out.WriteString("\nrescue ") + out.WriteString(bs.Rescue.ErrorIdent.Literal + "\n") for _, s := range bs.Rescue.Block.Statements { out.WriteString(s.String()) } diff --git a/parser/begin.go b/parser/begin.go index 461234a..9945bc4 100644 --- a/parser/begin.go +++ b/parser/begin.go @@ -6,7 +6,6 @@ import ( func (p *Parser) parseBegin() ast.Expression { expression := &ast.Begin{Token: p.curToken} - p.nextToken() expression.Block = p.parseBlock() return expression } From 1cb90c39a6f28953d3910c9bdbb1c4f88d6a5500 Mon Sep 17 00:00:00 2001 From: Flipez Date: Tue, 1 Nov 2022 02:34:42 +0100 Subject: [PATCH 8/9] Update error docs Signed-off-by: Flipez --- docs/docs/literals/error.md | 55 +++++++++++++++++++++++++++++++++++++ docs/generate.go | 38 ++++++++++++++++++++++++- object/error.go | 6 ++-- 3 files changed, 95 insertions(+), 4 deletions(-) diff --git a/docs/docs/literals/error.md b/docs/docs/literals/error.md index c179f95..e3edbbd 100644 --- a/docs/docs/literals/error.md +++ b/docs/docs/literals/error.md @@ -1,10 +1,65 @@ # Error +An Error is created by RocketLang if unallowed or invalid code is run. +An error does often replace the original return value of a function or identifier. +The documentation of those functions does indicate ERROR as a potential return value. +A program can rescue from errors within a block or alter it's behavior within other blocks like 'if' or 'def'. + +```js +def test() + puts(nope) +rescue e + puts("Got error: '" + e.msg() + "'") +end + +test() + +=> "Got error in if: 'identifier not found: error'" + +if (true) + nope() +rescue your_name + puts("Got error in if: '" + your_name.msg() + "'") +end + +=> "Got error in if: 'identifier not found: nope'" + +begin + puts(nope) +rescue e + puts("rescue") +end + +=> "rescue" + +``` + ## Literal Specific Methods +### msg() +> Returns `STRING` + +Returns the error message + +:::caution +Please note that performing `.msg()` on a ERROR object does result in a STRING object which then will no longer be treated as an error! +::: + + +```js +» def () +puts(nope) +rescue e +puts((rescued error: + e.msg())) +end +🚀 » test() +"rescued error:identifier not found: nope" +``` + + ## Generic Literal Methods diff --git a/docs/generate.go b/docs/generate.go index d00e288..4c5a90a 100644 --- a/docs/generate.go +++ b/docs/generate.go @@ -148,7 +148,43 @@ is_true = a != b;`, DefaultMethods: default_methods} create_doc("docs/templates/literal.md", "docs/docs/literals/boolean.md", tempData) - tempData = templateData{Title: "Error", LiteralMethods: error_methods, DefaultMethods: default_methods} + tempData = templateData{ + Title: "Error", + Description: `An Error is created by RocketLang if unallowed or invalid code is run. +An error does often replace the original return value of a function or identifier. +The documentation of those functions does indicate ERROR as a potential return value. + +A program can rescue from errors within a block or alter it's behavior within other blocks like 'if' or 'def'. +`, + Example: `def test() + puts(nope) +rescue e + puts("Got error: '" + e.msg() + "'") +end + +test() + +=> "Got error in if: 'identifier not found: error'" + +if (true) + nope() +rescue your_name + puts("Got error in if: '" + your_name.msg() + "'") +end + +=> "Got error in if: 'identifier not found: nope'" + +begin + puts(nope) +rescue e + puts("rescue") +end + +=> "rescue" +`, + LiteralMethods: error_methods, + DefaultMethods: default_methods, + } create_doc("docs/templates/literal.md", "docs/docs/literals/error.md", tempData) tempData = templateData{ diff --git a/object/error.go b/object/error.go index 05b9a7f..4ddc003 100644 --- a/object/error.go +++ b/object/error.go @@ -24,11 +24,11 @@ func init() { objectMethods[ERROR_OBJ] = map[string]ObjectMethod{ "msg": ObjectMethod{ Layout: MethodLayout{ - Description: "Returns the error message", + Description: "Returns the error message\n\n:::caution\nPlease note that performing `.msg()` on a ERROR object does result in a STRING object which then will no longer be treated as an error!\n:::", Example: `» def () puts(nope) -rescue -puts((rescued error: + error.msg())) +rescue e +puts((rescued error: + e.msg())) end 🚀 » test() "rescued error:identifier not found: nope"`, From 542d378b8b1690150bd78bcab4d0dc73cc0558c0 Mon Sep 17 00:00:00 2001 From: Flipez Date: Tue, 1 Nov 2022 11:34:58 +0100 Subject: [PATCH 9/9] Fix example Signed-off-by: Flipez --- object/error.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/object/error.go b/object/error.go index 4ddc003..8e06cbd 100644 --- a/object/error.go +++ b/object/error.go @@ -25,7 +25,7 @@ func init() { "msg": ObjectMethod{ Layout: MethodLayout{ Description: "Returns the error message\n\n:::caution\nPlease note that performing `.msg()` on a ERROR object does result in a STRING object which then will no longer be treated as an error!\n:::", - Example: `» def () + Example: `» def test() puts(nope) rescue e puts((rescued error: + e.msg()))