Skip to content

Commit 75bb697

Browse files
authored
Merge pull request #1466 from DanielXMoore/repl-function-hoist
`repl` directive hoists function and class declarations too, fix one-line declarations in `if`
2 parents 1ff18b7 + bad2a74 commit 75bb697

File tree

10 files changed

+310
-56
lines changed

10 files changed

+310
-56
lines changed

source/parser.hera

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -977,10 +977,12 @@ ClassDeclaration
977977
ClassExpression
978978
Decorators?:decorators ( Abstract __ )?:abstract Class !":" ClassBinding?:binding ClassHeritage?:heritage ClassBody:body ->
979979
return {
980+
type: "ClassExpression",
980981
decorators,
981982
abstract,
982983
binding,
983984
id: binding?.[0],
985+
name: binding?.[0].name,
984986
heritage,
985987
body,
986988
children: $0,
@@ -2508,7 +2510,7 @@ Block
25082510

25092511
ThenClause
25102512
# NOTE: !EOS prevents capturing a following unindented Statement
2511-
_?:ws !EOS Statement:s ->
2513+
_?:ws !EOS DeclarationOrStatement:s ->
25122514
const expressions = [[ws, s]]
25132515
return {
25142516
type: "BlockStatement",
@@ -5717,6 +5719,7 @@ VariableStatement
57175719
...$3,
57185720
names: $3.names,
57195721
children: [$1, ...$2, ...$3.children],
5722+
decl: "var",
57205723
}
57215724

57225725
# https://262.ecma-international.org/#prod-VariableDeclarationList

source/parser/function.civet

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ function processReturnValue(func: FunctionNode)
206206

207207
return true
208208

209-
function patternAsValue(pattern)
209+
function patternAsValue(pattern): ASTNode
210210
switch (pattern.type) {
211211
case "ArrayBindingPattern": {
212212
const children = [...pattern.children]
@@ -371,36 +371,45 @@ function insertReturn(node: ASTNode, outerNode: ASTNode = node): void
371371
return if isExit exp
372372

373373
outer := exp
374-
{type} .= exp
375-
if type is "LabelledStatement"
374+
if exp.type is "LabelledStatement"
376375
exp = exp.statement
377-
{type} = exp
378376

379-
switch type
377+
switch exp.type
380378
when "BreakStatement", "ContinueStatement", "DebuggerStatement", "EmptyStatement", "ReturnStatement", "ThrowStatement"
381379
return
382380
when "Declaration"
383381
value := if exp.bindings?.#
384382
[" ", patternAsValue(exp.bindings.-1.pattern)]
385383
else
386-
[]
387-
exp.children.push ["", {
384+
[] as ASTNode
385+
parent := outer.parent as BlockStatement?
386+
index := findChildIndex parent?.expressions, outer
387+
assert.notEqual index, -1, "Could not find declaration in parent"
388+
parent!.expressions.splice index+1, 0, ["", {
388389
type: "ReturnStatement"
390+
expression: value
389391
children: [
390-
";return",
391-
...value
392+
";" unless parent!.expressions[index][2] is ";"
393+
"return"
394+
value
392395
]
393396
parent: exp
394397
}]
398+
braceBlock parent!
395399
return
396400
when "FunctionExpression"
397401
// Add return after function declaration if it has an id to not interfere with hoisting
398402
if exp.id
399-
exp.children.push ["",
403+
parent := outer.parent as BlockStatement?
404+
index := findChildIndex parent?.expressions, outer
405+
assert.notEqual index, -1, "Could not find function declaration in parent"
406+
parent!.expressions.splice index+1, 0, ["",
400407
type: "ReturnStatement"
408+
expression: exp.id
401409
children: [";return ", exp.id]
402410
parent: exp
403411
]
412+
braceBlock parent!
404413
return
405414
/* c8 ignore next 3 */
406415
// This is currently never hit because anonymous FunctionExpressions are already wrapped in parens by this point

source/parser/lib.civet

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import type {
2525
ExpressionNode
2626
FinallyClause
2727
ForStatement
28+
FunctionExpression
2829
IfStatement
2930
IterationStatement
3031
MemberExpression
@@ -1251,13 +1252,7 @@ function processProgram(root: BlockStatement): void
12511252
adjustAtBindings(statements)
12521253

12531254
// REPL wants all top-level variables hoisted to outermost scope
1254-
if config.repl
1255-
topBlock :=
1256-
gatherRecursive(rootIIFE!, .type is 'BlockStatement')[0]
1257-
for each [, statement] of topBlock.expressions
1258-
if statement is like {type: 'Declaration'}
1259-
statement.children.shift() // remove const/let/var
1260-
root.expressions.unshift ['', `var ${statement.names.join ','};`]
1255+
processRepl root, rootIIFE if config.repl
12611256

12621257
// Run synchronous versions of async steps in case we're in sync mode
12631258
if getSync()
@@ -1268,8 +1263,34 @@ async function processProgramAsync(root: BlockStatement): Promise<void>
12681263
{ expressions: statements } := root
12691264
await processComptime(statements)
12701265

1266+
function processRepl(root: BlockStatement, rootIIFE: ASTNode): void
1267+
topBlock :=
1268+
gatherRecursive(rootIIFE!, .type is "BlockStatement")[0]
1269+
i .= 0
1270+
// Hoist top-level declarations and all var declarations
1271+
for each decl of gatherRecursiveWithinFunction topBlock, .type is "Declaration"
1272+
if decl.parent is topBlock or decl.decl is "var"
1273+
decl.children.shift() // remove const/let/var
1274+
root.expressions.splice i++, 0, ["", `var ${decl.names.join ','};`]
1275+
// Hoist all function declarations, and hoist values when top-level
1276+
for each func of gatherRecursive topBlock, .type is "FunctionExpression"
1277+
if func.name and func.parent?.type is "BlockStatement" // declaration
1278+
if func.parent is topBlock
1279+
// Top-level => hoist to beginning
1280+
replaceNode func, undefined
1281+
root.expressions.splice i++, 0, ["", func]
1282+
func.parent = root
1283+
else
1284+
func.children.unshift func.name, "="
1285+
root.expressions.splice i++, 0, ["", `var ${func.name};`]
1286+
// Hoist top-level class declarations (like `let`)
1287+
for each classExp of gatherRecursiveWithinFunction topBlock, .type is "ClassExpression"
1288+
if classExp.name and classExp.parent is topBlock or classExp.parent is like {type: "ReturnStatement", parent: ^topBlock}
1289+
classExp.children.unshift classExp.name, "="
1290+
root.expressions.splice i++, 0, ["", `var ${classExp.name};`]
1291+
12711292
function populateRefs(statements: ASTNode): void {
1272-
const refNodes = gatherRecursive(statements, ({ type }) => type is "Ref")
1293+
const refNodes = gatherRecursive(statements, .type is "Ref")
12731294

12741295
if (refNodes.length) {
12751296
// Find all ids within nested scopes

source/parser/traversal.civet

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ function gatherRecursiveWithinFunction<T extends ASTNodeObject>(node: ASTNode, p
1919
* which is useful for working with e.g. the `expressions` property.
2020
* Returns -1 if `child` cannot be found.
2121
*/
22-
function findChildIndex(parent: ASTNodeObject | Children, child: ASTNode)
22+
function findChildIndex(parent: (ASTNodeObject | Children)?, child: ASTNode)
2323
return -1 unless parent?
2424
children := Array.isArray(parent) ? parent : (parent as {children?: Children}).children
2525
return -1 unless children?

source/parser/types.civet

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ export type ExpressionNode =
4141
| AssignmentExpression
4242
| BinaryOp
4343
| CallExpression
44+
| ClassExpression
4445
| ComptimeExpression
4546
| Existence
4647
| FunctionNode
@@ -370,29 +371,34 @@ export type CaseBlock
370371
type: "CaseBlock"
371372
clauses: (PatternClause | CaseClause | WhenClause | DefaultClause)[]
372373
children: Children
374+
parent?: Parent
373375

374376
export type PatternClause
375377
type: "PatternClause"
376378
children: Children
379+
parent?: Parent
377380
patterns: PatternExpression[]
378381
block: BlockStatement
379382

380383
export type CaseClause
381384
type: "CaseClause"
382385
children: Children
386+
parent?: Parent
383387
cases: ASTNode[]
384388
block: BlockStatement
385389

386390
export type WhenClause
387391
type: "WhenClause"
388392
children: Children
393+
parent?: Parent
389394
cases: ASTNode[]
390395
break: ASTNode
391396
block: BlockStatement
392397

393398
export type DefaultClause
394399
type: "DefaultClause"
395400
children: Children
401+
parent?: Parent
396402
block: BlockStatement
397403

398404
export type EmptyStatement
@@ -456,6 +462,7 @@ export type AtBinding =
456462
type: "AtBinding"
457463
ref: ASTRef
458464
children: Children & [ASTRef]
465+
parent?: Parent
459466

460467
export type BlockStatement =
461468
type: "BlockStatement"
@@ -498,6 +505,7 @@ export type DeclarationStatement =
498505
export type Binding =
499506
type: "Binding"
500507
children: Children
508+
parent?: Parent
501509
names: string[]
502510
pattern: BindingIdentifier | BindingPattern
503511
typeSuffix: TypeSuffix?
@@ -638,6 +646,7 @@ export type Placeholder =
638646
export type PinPattern =
639647
type: "PinPattern"
640648
children: Children
649+
parent?: Parent
641650
expression: ExpressionNode
642651

643652
// _?, __
@@ -701,6 +710,7 @@ export type ObjectBindingPatternContent =
701710
export type ObjectBindingPattern =
702711
type: "ObjectBindingPattern",
703712
children: Children & [Whitespace, ASTLeaf, ObjectBindingPatternContent, WSNode, ASTLeaf]
713+
parent?: Parent
704714
names: string[]
705715
properties: ObjectBindingPatternContent
706716
typeSuffix?: TypeSuffix?
@@ -747,11 +757,12 @@ export type Argument
747757
expression: ASTNode
748758
spread: ASTNode?
749759

750-
export type FunctionExpression =
760+
export type FunctionExpression
751761
type: "FunctionExpression"
752762
children: Children
753763
parent?: Parent
754764
name: string
765+
id: Identifier
755766
async: ASTNode[]
756767
generator: ASTNode[]
757768
signature: FunctionSignature
@@ -805,6 +816,7 @@ export type FunctionSignature =
805816
children: Children
806817
parent?: Parent
807818
name: string
819+
id: Identifier
808820
optional: unknown
809821
modifier: MethodModifier
810822
returnType: ReturnTypeAnnotation?
@@ -817,6 +829,7 @@ export type TypeSuffix =
817829
nonnull?: ASTNode
818830
t?: ASTNode
819831
children: Children
832+
parent?: Parent
820833

821834
export type ReturnTypeAnnotation =
822835
type: "ReturnTypeAnnotation"
@@ -857,8 +870,19 @@ export type TypeParameters = unknown
857870

858871
export type FunctionNode = FunctionExpression | ArrowFunction | MethodDefinition
859872

873+
export type ClassExpression
874+
type: "ClassExpression"
875+
children: Children
876+
parent?: Parent
877+
name: string
878+
id: Identifier
879+
heritage: ASTNode
880+
body: ClassBody
881+
882+
export type ClassBody = BlockStatement & { subtype: "ClassBody" }
883+
860884
export type Literal =
861-
type: "Literal",
885+
type: "Literal"
862886
subtype?: "NumericLiteral" | "StringLiteral"
863887
children: Children & [ LiteralContentNode ]
864888
parent?: Parent

source/parser/util.civet

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ assert := {
2424
equal(a: unknown, b: unknown, msg: string): void
2525
/* c8 ignore next */
2626
throw new Error(`Assertion failed [${msg}]: ${a} !== ${b}`) if a !== b
27+
notEqual(a: unknown, b: unknown, msg: string): void
28+
/* c8 ignore next */
29+
throw new Error(`Assertion failed [${msg}]: ${a} === ${b}`) if a === b
2730
}
2831

2932
/**

test/function.civet

Lines changed: 41 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -93,20 +93,6 @@ describe "function", ->
9393
const x =function() { return a }
9494
"""
9595

96-
testCase """
97-
const declaration as implicit return
98-
---
99-
->
100-
fn2 := () ->
101-
x
102-
---
103-
(function() {
104-
const fn2 = function() {
105-
return x
106-
};return fn2
107-
})
108-
"""
109-
11096
testCase """
11197
async const declaration
11298
---
@@ -555,25 +541,6 @@ describe "function", ->
555541
}]
556542
"""
557543

558-
testCase """
559-
nested implicit return fat arrow
560-
---
561-
function g() {
562-
function f() {
563-
y((node) =>
564-
x)
565-
}
566-
}
567-
---
568-
function g() {
569-
function f() {
570-
return y((node) => {
571-
return x
572-
})
573-
};return f
574-
}
575-
"""
576-
577544
testCase """
578545
fat interprets single arg without parens as function application
579546
---
@@ -1112,6 +1079,20 @@ describe "function", ->
11121079
(function() { $2.implicit = $1.generated; $2; })
11131080
"""
11141081

1082+
testCase """
1083+
const declaration
1084+
---
1085+
->
1086+
fn2 := () ->
1087+
x
1088+
---
1089+
(function() {
1090+
const fn2 = function() {
1091+
return x
1092+
};return fn2
1093+
})
1094+
"""
1095+
11151096
testCase """
11161097
renamed declaration
11171098
---
@@ -1134,6 +1115,33 @@ describe "function", ->
11341115
})
11351116
"""
11361117

1118+
testCase """
1119+
nested implicit return fat arrow
1120+
---
1121+
function g() {
1122+
function f() {
1123+
y((node) =>
1124+
x)
1125+
}
1126+
}
1127+
---
1128+
function g() {
1129+
function f() {
1130+
return y((node) => {
1131+
return x
1132+
})
1133+
};return f
1134+
}
1135+
"""
1136+
1137+
testCase """
1138+
one-line function adds braces
1139+
---
1140+
(x) => if (x) function f() { x }
1141+
---
1142+
(x) => { if (x) { function f() { return x };return f};return }
1143+
"""
1144+
11371145
testCase """
11381146
inline multi-statement longhand
11391147
---

0 commit comments

Comments
 (0)