Skip to content

Commit

Permalink
Merge pull request #1566 from DanielXMoore/finally-block
Browse files Browse the repository at this point in the history
Lone `finally` block provides cleanup for rest of block
  • Loading branch information
edemaine authored Nov 3, 2024
2 parents b628945 + b3f95b6 commit 39fd84a
Show file tree
Hide file tree
Showing 6 changed files with 152 additions and 7 deletions.
17 changes: 17 additions & 0 deletions civet.dev/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -2103,6 +2103,23 @@ try
catch {message: /^EPIPE:/}
</Playground>
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)):
<Playground>
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
</Playground>
### Do Blocks
To put multiple lines in a scope and possibly an expression,
Expand Down
21 changes: 18 additions & 3 deletions source/parser.hera
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -4318,6 +4328,7 @@ Statement
return $1
SwitchStatement !ShouldExpressionize -> $1
TryStatement !ShouldExpressionize -> $1
FinallyClause

EmptyStatement

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down
4 changes: 2 additions & 2 deletions source/parser/block.civet
Original file line number Diff line number Diff line change
Expand Up @@ -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

/**
Expand Down
27 changes: 26 additions & 1 deletion source/parser/lib.civet
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import type {
ASTLeaf
ASTNode
ASTNodeObject
ASTNodeParent
ASTRef
BlockStatement
Call
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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
Expand Down
7 changes: 6 additions & 1 deletion source/parser/types.civet
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down
83 changes: 83 additions & 0 deletions test/try.civet
Original file line number Diff line number Diff line change
Expand Up @@ -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() }
"""

0 comments on commit 39fd84a

Please sign in to comment.