From b003f7ce6dd76a910acfb48c1dfd106398bcda6d Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Tue, 29 Oct 2024 11:08:00 -0400 Subject: [PATCH] Placeholders lift through reverse slice operators Fixes #1496 --- source/parser/lib.civet | 9 +++++++-- source/parser/types.civet | 8 ++++++++ test/function-block-shorthand.civet | 22 ++++++++++++++++++++++ 3 files changed, 37 insertions(+), 2 deletions(-) diff --git a/source/parser/lib.civet b/source/parser/lib.civet index e61372fe..fa7da8f3 100644 --- a/source/parser/lib.civet +++ b/source/parser/lib.civet @@ -14,6 +14,7 @@ import type { ASTNodeParent ASTRef BlockStatement + Call CallExpression CaseBlock CatchBinding @@ -46,6 +47,7 @@ import { gatherRecursive gatherRecursiveAll gatherRecursiveWithinFunction + type Predicate } from ./traversal.civet import { @@ -663,6 +665,7 @@ function processCallMemberExpression(node: CallExpression | MemberExpression): A return makeNode {...node, children: [ type: "CallExpression" as const + implicit: true children: [ getHelperRef "rslice" makeNode { @@ -1459,7 +1462,9 @@ function processPlaceholders(statements: StatementTuple[]): void if exp.subtype is "." // Partial placeholder . lifts to the nearest call expression, // including the call itself and any surrounding unary operations. - { ancestor } = findAncestor exp, .type is "Call" + { ancestor } = findAncestor exp, + ($): $ is Call => $.type is "Call" and + not ($.parent as CallExpression?)?.implicit ancestor = ancestor?.parent while ancestor?.parent? and ancestor.parent.type is like "UnaryExpression", "NewExpression", "AwaitExpression", "ThrowStatement", "StatementExpression" ancestor = ancestor.parent @@ -1477,7 +1482,7 @@ function processPlaceholders(statements: StatementTuple[]): void if type is "IfStatement" liftedIfs.add ancestor (or) - type is "Call" + type is "Call" and not (ancestor.parent as CallExpression?)?.implicit // Block, except for if/else blocks when condition already lifted type is "BlockStatement" and not (ancestor.parent is like {type: "IfStatement"} and liftedIfs.has ancestor.parent as IfStatement) and diff --git a/source/parser/types.civet b/source/parser/types.civet index d004a2b2..84e14732 100644 --- a/source/parser/types.civet +++ b/source/parser/types.civet @@ -38,6 +38,7 @@ export type StatementNode = */ export type ExpressionNode = | ArrayExpression + | AwaitExpression | AssignmentExpression | BinaryOp | CallExpression @@ -240,6 +241,7 @@ export type MemberExpression export type CallExpression type: "CallExpression" + implicit?: boolean // manufactured by Civet to implement an operator? children: Children parent?: Parent @@ -261,6 +263,11 @@ export type Await parent?: Parent op?: ASTNode +export type AwaitExpression + type: "AwaitExpression" + children?: Children + parent?: Parent + export type NewExpression type: "NewExpression" children: Children @@ -925,6 +932,7 @@ export type ReturnTypeAnnotation = optional?: ASTNode t: TypeNode children: Children + parent?: Parent export type MethodModifier = get?: boolean diff --git a/test/function-block-shorthand.civet b/test/function-block-shorthand.civet index be49d2c4..ca3b44cd 100644 --- a/test/function-block-shorthand.civet +++ b/test/function-block-shorthand.civet @@ -124,6 +124,28 @@ describe "&. function block shorthand", -> x.map($ => $.slice(0, 2 + 1 || 1/0)) """ + testCase """ + reverse slice + --- + x.map &[..>=] + --- + var modulo: (a: number, b: number) => number = (a, b) => (a % b + b) % b; + type RSliceable = string | {length: number; slice(start: number, end: number): {reverse(): R}} + var rslice: >(a: T, start?: number, end?: number) => T extends string ? string : T extends RSliceable ? R : never = ((a, start, end) => { + const l = a.length + start = modulo(start ?? -1, l) + end = modulo((end ?? -1) + 1, l) + if (typeof a === 'string') { + let r = "" + for (let i = start; i >= end; --i) r += a[i] + return r as any + } else { + return a.slice(end, start + 1).reverse() + } + }); + x.map($ => rslice($, -1, 0 - 1)) + """ + testCase """ kitchen sink ---