diff --git a/core/cobra_root.go b/core/cobra_root.go index 4053398..24e0872 100644 --- a/core/cobra_root.go +++ b/core/cobra_root.go @@ -179,7 +179,7 @@ func registerInterpreterWithExit(interpreter *MainInterpreter) { } exiting = true codeToExitWith = code - interpreter.ExecuteDeferredStmts() + interpreter.ExecuteDeferredStmts(code) existing(codeToExitWith) } } diff --git a/core/gen_stmt.go b/core/gen_stmt.go index 1c1d507..ed7ce9b 100644 --- a/core/gen_stmt.go +++ b/core/gen_stmt.go @@ -327,6 +327,7 @@ func (e DeleteStmt) String() string { type DeferStmt struct { DeferToken Token + IsErrDefer bool DeferredStmt *Stmt DeferredBlock *Block } @@ -337,6 +338,7 @@ func (e DeferStmt) Accept(visitor StmtVisitor) { func (e DeferStmt) String() string { var parts []string parts = append(parts, fmt.Sprintf("DeferToken: %v", e.DeferToken)) + parts = append(parts, fmt.Sprintf("IsErrDefer: %v", e.IsErrDefer)) parts = append(parts, fmt.Sprintf("DeferredStmt: %v", e.DeferredStmt)) parts = append(parts, fmt.Sprintf("DeferredBlock: %v", e.DeferredBlock)) return fmt.Sprintf("DeferStmt(%s)", strings.Join(parts, ", ")) diff --git a/core/generators/ast.go b/core/generators/ast.go index f5ffe0b..6e3e82d 100644 --- a/core/generators/ast.go +++ b/core/generators/ast.go @@ -86,7 +86,7 @@ func main() { "BreakStmt : Token BreakToken", "ContinueStmt : Token ContinueToken", "DeleteStmt : Token DeleteToken, []VarPath Vars", - "DeferStmt : Token DeferToken, *Stmt DeferredStmt, *Block DeferredBlock", + "DeferStmt : Token DeferToken, bool IsErrDefer, *Stmt DeferredStmt, *Block DeferredBlock", }) defineAst(outputDir, "ArgStmt", "", []string{ diff --git a/core/interpreter.go b/core/interpreter.go index 6877ecb..a396c19 100644 --- a/core/interpreter.go +++ b/core/interpreter.go @@ -498,11 +498,15 @@ func (i *MainInterpreter) resolveStartEnd(sliceAccess SliceAccess, len int) (int } // todo currently these execute after an error is printed. Should they execute before? -func (i *MainInterpreter) ExecuteDeferredStmts() { +func (i *MainInterpreter) ExecuteDeferredStmts(errCode int) { // execute backwards (LIFO) for j := len(i.deferredStmts) - 1; j >= 0; j-- { deferredStmt := i.deferredStmts[j] + if deferredStmt.IsErrDefer && errCode == 0 { + continue + } + func() { defer func() { if r := recover(); r != nil { diff --git a/core/keywords.go b/core/keywords.go index c80847b..28c9fd3 100644 --- a/core/keywords.go +++ b/core/keywords.go @@ -18,13 +18,14 @@ var GLOBAL_KEYWORDS = map[string]TokenType{ "if": IF, "else": ELSE, //"resource": RESOURCE, - "del": DELETE, - "not": NOT, - "unsafe": UNSAFE, - "quiet": QUIET, - "fail": FAIL, - "recover": RECOVER, - "defer": DEFER, + "del": DELETE, + "not": NOT, + "unsafe": UNSAFE, + "quiet": QUIET, + "fail": FAIL, + "recover": RECOVER, + "defer": DEFER, + "errdefer": ERRDEFER, } var ARGS_BLOCK_KEYWORDS = map[string]TokenType{ diff --git a/core/parser.go b/core/parser.go index bebc5b0..3c4d87e 100644 --- a/core/parser.go +++ b/core/parser.go @@ -272,7 +272,7 @@ func (p *Parser) statement() Stmt { return p.deleteStmt() } - if p.peekKeyword(GLOBAL_KEYWORDS, DEFER) { + if p.peekKeyword(GLOBAL_KEYWORDS, DEFER) || p.peekKeyword(GLOBAL_KEYWORDS, ERRDEFER) { return p.deferStmt() } @@ -526,13 +526,21 @@ func (p *Parser) deleteStmt() Stmt { } func (p *Parser) deferStmt() Stmt { - deferToken := p.consumeKeyword(GLOBAL_KEYWORDS, DEFER) + var deferToken Token + isErrDefer := false + if p.matchKeyword(GLOBAL_KEYWORDS, ERRDEFER) { + deferToken = p.previous() + isErrDefer = true + } else { + deferToken = p.consumeKeyword(GLOBAL_KEYWORDS, DEFER) + } + if p.matchAny(COLON) { block := p.block() - return &DeferStmt{DeferToken: deferToken, DeferredStmt: nil, DeferredBlock: &block} + return &DeferStmt{DeferToken: deferToken, IsErrDefer: isErrDefer, DeferredStmt: nil, DeferredBlock: &block} } else { stmt := p.statement() - return &DeferStmt{DeferToken: deferToken, DeferredStmt: &stmt, DeferredBlock: nil} + return &DeferStmt{DeferToken: deferToken, IsErrDefer: isErrDefer, DeferredStmt: &stmt, DeferredBlock: nil} } } diff --git a/core/token_type.go b/core/token_type.go index c589bf7..43c3ac7 100644 --- a/core/token_type.go +++ b/core/token_type.go @@ -84,6 +84,7 @@ const ( FAIL TokenType = "FAIL" RECOVER TokenType = "RECOVER" DEFER TokenType = "DEFER" + ERRDEFER TokenType = "ERRDEFER" MAP TokenType = "MAP" // only in Args block diff --git a/docs-web/docs/defer.md b/docs-web/docs/defer.md new file mode 100644 index 0000000..7af232f --- /dev/null +++ b/docs-web/docs/defer.md @@ -0,0 +1,84 @@ +--- +title: Defer & Errdefer +--- + +- `defer` and `errdefer` run in LIFO order, each kind being part of the same one queue. +- If there are several defer statements, and one fails, further defer statements will still attempt to run. +- Rad's error code will become an error if the main script succeeded but a defer statement failed. +- errdefers will not get triggered if the main script succeeded but a `defer` or `errdefer` statement failed. + +## `defer` + +```rsl title="defer Example" +defer: + print(1) + print(2) +defer: + print(3) + print(4) +print("Hello!") +``` + +```title="defer Example Output" +Hello! +3 +4 +1 +2 +``` + +## `errdefer` + +```rsl title="errdefer Example 1" +defer: + print(1) + print(2) +errdefer: + print(3) + print(4) +defer: + print(5) + print(6) +errdefer: + print(7) + print(8) +print("Hello!") +exit(0) // successful script run +``` + +```title="errdefer Example 1 Output" +Hello! +5 +6 +1 +2 +``` + +```rsl title="errdefer Example 2" +defer: + print(1) + print(2) +errdefer: + print(3) + print(4) +defer: + print(5) + print(6) +errdefer: + print(7) + print(8) +print("Hello!") +exit(1) // perceived as error! +``` + +```title="errdefer Example 2 Output" +Hello! +7 +8 +5 +6 +3 +4 +1 +2 +``` \ No newline at end of file