Skip to content

Commit

Permalink
Merge pull request #1648 from DanielXMoore/rest
Browse files Browse the repository at this point in the history
Non-end rest parameters support types
  • Loading branch information
edemaine authored Dec 18, 2024
2 parents a2a17da + efb62e7 commit bb241bb
Show file tree
Hide file tree
Showing 11 changed files with 467 additions and 202 deletions.
111 changes: 23 additions & 88 deletions source/parser.hera
Original file line number Diff line number Diff line change
Expand Up @@ -792,6 +792,7 @@ ArrowFunction
modifier: {
async: !!async.length,
},
parameters,
returnType,
},
parameters,
Expand Down Expand Up @@ -1811,10 +1812,12 @@ AfterReturnShorthand
Parameters
NonEmptyParameters
TypeParameters?:tp Loc:p ->
const parameters = []
return {
type: "Parameters",
children: [tp, {$loc: p.$loc, token: "()"}],
children: [tp, {$loc: p.$loc, token: "("}, parameters, ")"],
tp,
parameters,
names: [],
implicit: true,
}
Expand All @@ -1833,95 +1836,21 @@ ShortArrowParameters

ArrowParameters
ShortArrowParameters ->
const parameters = [ $1 ]
return {
type: "Parameters",
children: ["(", $1, ")"],
names: $1.names,
children: ["(", parameters, ")"],
parameters,
}
Parameters

NonEmptyParameters
TypeParameters?:tp OpenParen:open ParameterList:params ( __ CloseParen ):close ->
// Categorize arguments to put any ThisType in front, and split remaining
// arguments into before and after the rest parameter.
let tt, before = [], rest, after = [], errors = []
function append(p) {
(rest ? after : before).push(p)
}
for (const param of params) {
switch (param.type) {
case "ThisType":
if (tt) {
append({
type: "Error",
message: "Only one typed this parameter is allowed",
})
append(param)
} else {
tt = trimFirstSpace(param)
if (before.length || rest) { // moving ThisType to front
let delim = tt.children.at(-1)
if (Array.isArray(delim)) delim = delim.at(-1)
if (delim?.token !== ",") {
tt = {
...tt,
children: [...tt.children, ", "],
}
}
}
}
break
case "FunctionRestParameter":
if (rest) {
append({
type: "Error",
message: "Only one rest parameter is allowed",
})
append(param)
} else {
rest = param
}
break
default:
append(param)
}
}

const names = before.flatMap(p => p.names)
if (rest) {
const restIdentifier = rest.binding.ref || rest.binding
names.push(...rest.names || [])

let blockPrefix
if (after.length) {
blockPrefix = {
children: ["[", trimFirstSpace(after), "] = ", restIdentifier, ".splice(-", after.length.toString(), ")"],
names: after.flatMap(p => p.names)
}
}

return {
type: "Parameters",
children: [
tp,
open,
tt,
...before,
// Remove delimiter
{...rest, children: rest.children.slice(0, -1)},
close,
],
tp,
names,
blockPrefix,
}
}

TypeParameters?:tp OpenParen:open ParameterList:parameters ( __ CloseParen ):close ->
return {
type: "Parameters",
children: [tp, open, tt, ...before, close],
names,
children: [ tp, open, parameters, close ],
tp,
parameters,
}

ParameterList
Expand Down Expand Up @@ -1957,12 +1886,14 @@ Parameter
FunctionRestParameter
# BindingRestElement has a leading _?
# but also sometimes invokes __ via BindingIdentifier
!EOS BindingRestElement:id TypeSuffix? ParameterElementDelimiter ->
!EOS BindingRestElement:rest TypeSuffix?:typeSuffix ParameterElementDelimiter ->
return {
type: "FunctionRestParameter",
children: $0.slice(1),
names: id.names,
binding: id.binding,
rest,
names: rest.names,
binding: rest.binding,
typeSuffix,
}

# NOTE: Similar to BindingElement but appears in formal parameters list
Expand Down Expand Up @@ -2286,7 +2217,7 @@ BindingRestElement
_?:ws DotDotDot:dots ( BindingIdentifier / BindingPattern / EmptyBindingPattern ):binding BindingTypeSuffix?:typeSuffix ->
return {
type: "BindingRestElement",
children: [ws, [dots, binding]],
children: [ws, dots, binding],
dots,
binding,
typeSuffix,
Expand Down Expand Up @@ -2386,10 +2317,11 @@ FunctionExpression
[[], op, [], refB] // BinaryOpRHS
]])

const parameterList = [ [ refA, "," ], refB ]
const parameters = {
type: "Parameters",
children: ["(", refA, ",", refB, ")"],
names: [],
children: ["(", parameterList, ")"],
parameters: parameterList
}

const block = {
Expand Down Expand Up @@ -7954,17 +7886,19 @@ TypeIndex
# parse both (?!). For simplicity, we allow either in all cases.
# In some cases (e.g. let/const), we transpile them away later.
TypeSuffix
_? QuestionMark?:optional _? Colon MaybeNestedType:t -> {
_? QuestionMark?:optional _? Colon:colon MaybeNestedType:t -> {
type: "TypeSuffix",
ts: true,
optional,
t,
colon,
children: $0,
}
_? QuestionMark:optional _? -> {
type: "TypeSuffix",
ts: true,
optional,
colon: undefined,
children: $0,
}
# TypeScript has a special error for ! without : ("Declarations with definite
Expand All @@ -7977,6 +7911,7 @@ TypeSuffix
ts: true,
nonnull,
t,
colon,
children: [ $1, $2, colon, t ],
}

Expand Down
77 changes: 36 additions & 41 deletions source/parser/binding.civet
Original file line number Diff line number Diff line change
Expand Up @@ -23,26 +23,22 @@ import {
* see test/function.civet binding pattern
*/
function adjustAtBindings(statements: ASTNode, asThis = false): void
gatherRecursiveAll(statements, .type is "AtBindingProperty")
.forEach((binding) => {
const { ref } = binding

if (asThis) {
// Convert from @x to x: this.x keeping any whitespace or initializer to the right
const atBinding = binding.binding
atBinding.children.pop()
atBinding.type = undefined

binding.children.unshift(ref.id, ": this.", ref.base)
binding.type = "Property"
binding.ref = undefined
return
}
for each binding of gatherRecursiveAll statements, .type is "AtBindingProperty"
{ ref } := binding

if (ref.names[0] !== ref.base) {
binding.children.unshift(ref.base, ": ")
}
})
if asThis
// Convert from @x to x: this.x keeping any whitespace or initializer to the right
atBinding := binding.binding
atBinding.children.pop()
atBinding.type = undefined

binding.children.unshift(ref.id, ": this.", ref.base)
binding.type = "Property"
binding.ref = undefined
return

unless ref.names[0] is ref.base
binding.children.unshift(ref.base, ": ")

function adjustBindingElements(elements: ASTNodeObject[])
names := elements.flatMap .names or []
Expand Down Expand Up @@ -111,28 +107,27 @@ function gatherBindingCode(statements: ASTNode, opts?: { injectParamProps?: bool
splices: unknown[] := []

function insertRestSplices(s, p: unknown[], thisAssignments: ThisAssignments): void
gatherRecursiveAll(s, (n) => n.blockPrefix or (opts?.injectParamProps and n.accessModifier) or n.type is "AtBinding")
.forEach (n) =>
// Insert `this` assignments
if n.type is "AtBinding"
{ ref } := n as! AtBinding
{ id } := ref
thisAssignments.push([`this.${id} = `, ref])
return

if opts?.injectParamProps and n.type is "Parameter" and n.accessModifier
for each id of n.names
thisAssignments.push
type: "AssignmentExpression"
children: [`this.${id} = `, id]
js: true
return

{ blockPrefix } := n
p.push(blockPrefix)

// Search for any further nested splices, and at bindings
insertRestSplices(blockPrefix, p, thisAssignments)
for each n of gatherRecursiveAll s, (n) => n.blockPrefix or (opts?.injectParamProps and n.accessModifier) or n.type is "AtBinding"
// Insert `this` assignments
if n.type is "AtBinding"
{ ref } := n
{ id } := ref
thisAssignments.push([`this.${id} = `, ref])
continue

if opts?.injectParamProps and n.type is "Parameter" and n.accessModifier
for each id of n.names
thisAssignments.push
type: "AssignmentExpression"
children: [`this.${id} = `, id]
js: true
continue

{ blockPrefix } := n
p.push(blockPrefix)

// Search for any further nested splices, and at bindings
insertRestSplices(blockPrefix, p, thisAssignments)

insertRestSplices(statements, splices, thisAssignments)

Expand Down
9 changes: 3 additions & 6 deletions source/parser/declaration.civet
Original file line number Diff line number Diff line change
Expand Up @@ -107,11 +107,8 @@ function processAssignmentDeclaration(decl: ASTLeaf, pattern: Binding["pattern"]
}

function processDeclarations(statements: StatementTuple[]): void
// @ts-ignore
gatherRecursiveAll statements, .type is "Declaration"
// @ts-ignore
.forEach (statement: Declaration) =>
{ bindings } := statement as Declaration
for each declaration of gatherRecursiveAll statements, .type is "Declaration"
{ bindings } := declaration
bindings?.forEach (binding) =>
{ typeSuffix } := binding
if typeSuffix and typeSuffix.optional and typeSuffix.t
Expand All @@ -120,7 +117,7 @@ function processDeclarations(statements: StatementTuple[]): void

{ initializer } := binding
if initializer
prependStatementExpressionBlock initializer, statement
prependStatementExpressionBlock initializer, declaration

function prependStatementExpressionBlock(initializer: Initializer, statement: ASTNodeParent): ASTRef?
{expression: exp} .= initializer
Expand Down
Loading

0 comments on commit bb241bb

Please sign in to comment.