diff --git a/source/parser.hera b/source/parser.hera index c3a72dc1..1c33b54a 100644 --- a/source/parser.hera +++ b/source/parser.hera @@ -1230,9 +1230,53 @@ ArrowParameters Parameters NonEmptyParameters - # NOTE: BindingElement -> ParameterElement - TypeParameters?:tp OpenParen:open ThisType?:tt ParameterElement*:pes FunctionRestParameter?:rest ParameterElement*:after ( __ CloseParen ):close -> - const names = pes.flatMap(p => p.names) + 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 = insertTrimmingSpace(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 || []) @@ -1251,7 +1295,7 @@ NonEmptyParameters tp, open, tt, - ...pes, + ...before, // Remove delimiter {...rest, children: rest.children.slice(0, -1)}, close, @@ -1264,29 +1308,63 @@ NonEmptyParameters return { type: "Parameters", - children: [tp, open, tt, ...pes, close], - names: pes.flatMap((p) => p.names), + children: [tp, open, tt, ...before, close], + names, tp, } +ParameterList + # Nested case: Allow for one line of parameters followed by a nested list + Parameter* NestedParameterList -> + return [...$1, ...$2] + # Otherwise, try parsing while ignore indentation + ( __ Parameter )* -> + return $1.map(([eos, p]) => ({ + ...p, + children: [eos, ...p.children], + })) + +NestedParameterList + PushIndent NestedParameter*:params PopIndent -> + if (!params.length) return $skip + return params + +NestedParameter + # Allow one or more parameters on one line + Nested:ws Parameter+:params -> + // Attach whitespace to first parameter + params = [...params] + params[0] = { + ...params[0], + children: [ws, ...params[0].children], + } + return params + +Parameter + ThisType + ParameterElement + FunctionRestParameter + # https://262.ecma-international.org/#prod-FunctionRestParameter FunctionRestParameter - __ BindingRestElement:id TypeSuffix? ParameterElementDelimiter -> + # BindingRestElement has a leading _? + # but also sometimes invokes __ via BindingIdentifier + !EOS BindingRestElement:id TypeSuffix? ParameterElementDelimiter -> return { type: "FunctionRestParameter", - children: $0, + children: $0.slice(1), names: id.names, binding: id.binding, } # NOTE: Similar to BindingElement but appears in formal parameters list ParameterElement - __ AccessModifier? ( BindingIdentifier / BindingPattern ) TypeSuffix? Initializer? ParameterElementDelimiter -> + _? AccessModifier?:accessModifier _? ( NWBindingIdentifier / BindingPattern ):binding TypeSuffix? Initializer? ParameterElementDelimiter -> return { type: "Parameter", children: $0, - names: $3.names, - accessModifier: $2, + names: binding.names, + accessModifier, } ParameterElementDelimiter @@ -6589,7 +6667,7 @@ TypeParameterDelimiter # TypeScript's this: T syntax in function parameters ThisType - ( This / AtThis ) Colon Type ParameterElementDelimiter -> { + _? ( This / AtThis ) Colon Type ParameterElementDelimiter -> { type: "ThisType", ts: true, children: $0 diff --git a/test/function.civet b/test/function.civet index fb882306..a2f3be45 100644 --- a/test/function.civet +++ b/test/function.civet @@ -634,6 +634,13 @@ describe "function", -> }) """ + throws """ + double rest parameter + --- + (a, ...b, ...c) -> + c + """ + testCase """ non-end rest parameter --- @@ -904,7 +911,6 @@ describe "function", -> ) { return null } """ - // TODO: wrong indentation of }s testCase """ indented with types --- @@ -918,10 +924,38 @@ describe "function", -> function f( t: { item: T - }, + }, u: { item: T - } + } + ){} + """ + + testCase """ + indented with assignments + --- + function f( + x = 0 + y = 1 + ) + --- + function f( + x = 0, + y = 1 + ){} + """ + + testCase """ + indented with assignments and types + --- + function f( + x : number = 0 + y : number = 1 + ) + --- + function f( + x : number = 0, + y : number = 1 ){} """ diff --git a/test/types/function.civet b/test/types/function.civet index ed8d28c5..5635cc6a 100644 --- a/test/types/function.civet +++ b/test/types/function.civet @@ -397,3 +397,19 @@ describe "[TS] function", -> --- (function (this: T) {}) """ + + testCase """ + move this type to front + --- + function (x: T, @: T,) {} + function (x: T, @: T) {} + --- + (function (this: T,x: T,) {}); + (function (this: T, x: T,) {}) + """ + + throws """ + double this type + --- + function (this: T, @: T) {} + """