From b3f95b601a7de0365b50e2c157896c64554cc72a Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Sun, 3 Nov 2024 12:34:21 -0500 Subject: [PATCH] Lone `finally` block provides cleanup for rest of block --- civet.dev/reference.md | 17 ++++++++ source/parser.hera | 21 ++++++++-- source/parser/block.civet | 4 +- source/parser/lib.civet | 27 ++++++++++++- source/parser/types.civet | 7 +++- test/try.civet | 83 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 152 insertions(+), 7 deletions(-) diff --git a/civet.dev/reference.md b/civet.dev/reference.md index 13aa3afd..04460aeb 100644 --- a/civet.dev/reference.md +++ b/civet.dev/reference.md @@ -2103,6 +2103,23 @@ try catch {message: /^EPIPE:/} +Finally, you can specify a `finally` block without a `try` block, +and it automatically wraps the rest of the block (similar to +`defer` in +[Zig](https://ziglang.org/documentation/master/#defer) and +[Swift](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/statements/#Defer-Statement)): + + +depth .= 0 +function recurse(node) + depth++ + finally depth-- + console.log depth, "entering", node + finally console.log depth, "exiting", node + return unless node? + recurse child for child of node + + ### Do Blocks To put multiple lines in a scope and possibly an expression, diff --git a/source/parser.hera b/source/parser.hera index 25932f79..a9fa9c07 100644 --- a/source/parser.hera +++ b/source/parser.hera @@ -76,7 +76,17 @@ let filename; // filename currently being parsed let initialConfig; // input for parser config let config; // current parser config after directives let sync; // synchronous mode: as much as possible without await -export const state = {}; // parser state +export const state = { // parser state + // These get (re)set in Reset, but are included here for type inference + forbidClassImplicitCall: [false], + forbidIndentedApplication: [false], + forbidBracedApplication: [false], + forbidTrailingMemberProperty: [false], + forbidNestedBinaryOp: [false], + forbidNewlineBinaryOp: [false], + forbidPipeline: [false], + JSXTagStack: [undefined], +} export const getState = () => state; export const getConfig = () => config; @@ -4318,6 +4328,7 @@ Statement return $1 SwitchStatement !ShouldExpressionize -> $1 TryStatement !ShouldExpressionize -> $1 + FinallyClause EmptyStatement @@ -5025,7 +5036,7 @@ IgnoreColon # https://262.ecma-international.org/#prod-TryStatement TryStatement - Try !":" NoPostfixBracedOrEmptyBlock CatchClause* ElseClause? FinallyClause? -> + Try !":" NoPostfixBracedOrEmptyBlock CatchClause* ElseClause? WFinallyClause? -> return processTryBlock($0) # https://262.ecma-international.org/#prod-Catch @@ -5055,9 +5066,13 @@ CatchBinding children: [ ws, open, parameter, close ] } +WFinallyClause + ( Nested / _ ) FinallyClause -> + return prepend($1, $2) + # https://262.ecma-international.org/#prod-Finally FinallyClause - ( Nested / _ ) Finally ( BracedThenClause / BracedOrEmptyBlock ):block -> + Finally ( BracedThenClause / BracedOrEmptyBlock ):block -> return { type: "FinallyClause", children: $0, diff --git a/source/parser/block.civet b/source/parser/block.civet index 5a9cbf31..76f98bab 100644 --- a/source/parser/block.civet +++ b/source/parser/block.civet @@ -170,8 +170,8 @@ function insertHoistDec(block: BlockStatement, node: ASTNode | StatementTuple, d function processBlocks(statements: StatementTuple[]): void insertSemicolon(statements) - gatherRecursive statements, .type is "BlockStatement" - .forEach ({ expressions }) => + + for each { expressions } of gatherRecursive statements, .type is "BlockStatement" processBlocks expressions /** diff --git a/source/parser/lib.civet b/source/parser/lib.civet index e355d164..45c9445e 100644 --- a/source/parser/lib.civet +++ b/source/parser/lib.civet @@ -11,7 +11,6 @@ import type { ASTLeaf ASTNode ASTNodeObject - ASTNodeParent ASTRef BlockStatement Call @@ -499,6 +498,7 @@ function processTryBlock($0: [ASTLeaf, undefined, BlockStatement, CatchClause[], blocks := [b] blocks.push c.block if c + // Omitting finally block here, to avoid implicit return from finally block return { type: "TryStatement" @@ -1334,6 +1334,30 @@ function processNegativeIndexAccess(statements: StatementTuple[]): void ".length" ] +function processFinallyClauses(statements: StatementTuple[]): void + for each let f of gatherRecursiveAll statements, ($): $ is FinallyClause => ( + $.type is "FinallyClause" and $.parent?.type is not "TryStatement" + ) + unless { block, index } := blockContainingStatement f + throw new Error "finally clause must be inside try statement or block" + indent := block.expressions[index][0] + expressions := block.expressions[>index] + t: BlockStatement := makeNode { + type: "BlockStatement" + expressions + children: [ "{", expressions, "}" ] + bare: false + } + f = prepend(" ", f) as FinallyClause + tuple: StatementTuple := [indent, + makeNode + type: "TryStatement" + blocks: [ t ] // omit f to avoid implicit return + children: [ "try ", t, f ] + parent: block + ] + block.expressions[>=index] = [tuple] + function processProgram(root: BlockStatement): void state := getState() config := getConfig() @@ -1370,6 +1394,7 @@ function processProgram(root: BlockStatement): void processStatementExpressions(statements) processPatternMatching(statements) processIterationExpressions(statements) + processFinallyClauses(statements) // Hoist hoistDec attributes to actual declarations. // NOTE: This should come after iteration expressions get processed diff --git a/source/parser/types.civet b/source/parser/types.civet index 312aaba9..0e49b6e7 100644 --- a/source/parser/types.civet +++ b/source/parser/types.civet @@ -639,6 +639,8 @@ export type TryStatement type: "TryStatement" children: Children parent?: Parent + // `blocks` should list all blocks that need implicit return: + // main and `catch` blocks, but not `finally `block blocks: BlockStatement[] export type CatchClause @@ -676,7 +678,10 @@ export type CatchPattern export type FinallyClause type: "FinallyClause" - children: Children & [ Whitespace | ASTString, FinallyToken, BlockStatement ] + children: Children & ( + | [ FinallyToken, BlockStatement ] + | [ Whitespace | ASTString, FinallyToken, BlockStatement ] + ) parent?: Parent block: BlockStatement diff --git a/test/try.civet b/test/try.civet index d2faf37e..1e0c494e 100644 --- a/test/try.civet +++ b/test/try.civet @@ -375,3 +375,86 @@ describe "try", -> --- ParseErrors: unknown:3:10 Only one catch clause allowed unless using pattern matching """ + + describe "lone finally blocks", -> + testCase """ + basic + --- + init() + finally cleanup() + process() + process() + --- + init() + try { + process() + process()} finally { cleanup() } + """ + + testCase """ + indented block + --- + init() + finally + cleanup1() + cleanup2() + process() + process() + --- + init() + try { + process() + process()} finally { + cleanup1() + cleanup2() + } + """ + + testCase """ + multiple blocks + --- + init() + finally cleanup() + loop + start() + finally stop() + process() + --- + init() + try { + while(true) { + start() + try { + process()} finally { stop() } + }} finally { cleanup() } + """ + + testCase """ + multiple consecutive finally + --- + init() + finally cleanup1() + finally cleanup2() + process() + --- + init() + try { + try { + process()} finally { cleanup2() }} finally { cleanup1() } + """ + + testCase """ + multiple nonconsecutive finally + --- + init1() + finally cleanup1() + init2() + finally cleanup2() + process() + --- + init1() + try { + init2() + try { + process()} finally { cleanup2() }} finally { cleanup1() } + """