Skip to content

Commit

Permalink
Merge pull request #142 from Flipez/add-rescue
Browse files Browse the repository at this point in the history
[object/error] Add ability to rescue errors and introduce `begin/rescue/end`
  • Loading branch information
Flipez authored Nov 1, 2022
2 parents 40839b4 + 542d378 commit 23b63fd
Show file tree
Hide file tree
Showing 14 changed files with 213 additions and 10 deletions.
4 changes: 4 additions & 0 deletions ast/ast_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
23 changes: 23 additions & 0 deletions ast/begin.go
Original file line number Diff line number Diff line change
@@ -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()
}
9 changes: 9 additions & 0 deletions ast/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
type Block struct {
Token token.Token // the { token
Statements []Statement
Rescue *Rescue
}

func (bs *Block) TokenLiteral() string { return bs.Token.Literal }
Expand All @@ -19,5 +20,13 @@ func (bs *Block) String() string {
out.WriteString(s.String())
}

if bs.Rescue != nil {
out.WriteString("\nrescue ")
out.WriteString(bs.Rescue.ErrorIdent.Literal + "\n")
for _, s := range bs.Rescue.Block.Statements {
out.WriteString(s.String())
}
}

return out.String()
}
16 changes: 16 additions & 0 deletions ast/rescue.go
Original file line number Diff line number Diff line change
@@ -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()
}
55 changes: 55 additions & 0 deletions docs/docs/literals/error.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down
38 changes: 37 additions & 1 deletion docs/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down
9 changes: 8 additions & 1 deletion evaluator/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,14 @@ 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 {
env.Set(block.Rescue.ErrorIdent.Literal, result)
result = evalBlock(block.Rescue.Block, env)
}
return result
}
if rt == object.RETURN_VALUE_OBJ || rt == object.BREAK_VALUE_OBJ || rt == object.NEXT_VALUE_OBJ {
return result
}
}
Expand Down
16 changes: 9 additions & 7 deletions evaluator/evaluator.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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 {
Expand Down
3 changes: 3 additions & 0 deletions evaluator/evaluator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
24 changes: 24 additions & 0 deletions object/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -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\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 test()
puts(nope)
rescue e
puts((rescued error: + e.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)
},
},
}
}
11 changes: 11 additions & 0 deletions parser/begin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package parser

import (
"github.com/flipez/rocket-lang/ast"
)

func (p *Parser) parseBegin() ast.Expression {
expression := &ast.Begin{Token: p.curToken}
expression.Block = p.parseBlock()
return expression
}
9 changes: 8 additions & 1 deletion parser/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -20,5 +20,12 @@ func (p *Parser) parseBlock() *ast.Block {
p.nextToken()
}

if p.curTokenIs(token.RESCUE) {
block.Rescue = &ast.Rescue{Token: p.curToken}
p.expectPeek(token.IDENT)
block.Rescue.ErrorIdent = p.curToken
block.Rescue.Block = p.parseBlock()
}

return block
}
1 change: 1 addition & 0 deletions parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
5 changes: 5 additions & 0 deletions token/token.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ const (
IMPORT = "IMPORT"

NIL = "NIL"

BEGIN = "BEGIN"
RESCUE = "RESCUE"
)

var keywords = map[string]TokenType{
Expand All @@ -95,6 +98,8 @@ var keywords = map[string]TokenType{
"nil": NIL,
"and": AND,
"or": OR,
"begin": BEGIN,
"rescue": RESCUE,
}

func LookupIdent(ident string) TokenType {
Expand Down

2 comments on commit 23b63fd

@vercel
Copy link

@vercel vercel bot commented on 23b63fd Nov 1, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vercel
Copy link

@vercel vercel bot commented on 23b63fd Nov 1, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.