Created
September 4, 2019 20:02
-
-
Save hediet/0cf69ea00d54727d592e0ae8ac42b98e to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { hotClass, registerUpdateReconciler } from "@hediet/node-reload"; | |
import { ts, Node, SyntaxKind, TypeGuards, Identifier } from "ts-morph"; | |
registerUpdateReconciler(module); | |
import { registerAll } from "C:\\Users\\Henning\\Desktop\\playground\\vscode-debug-visualizer\\data-extraction"; | |
registerAll(); | |
@hotClass(module) | |
export class PatternMatchMain { | |
foo(node: Node) { | |
/**/ | |
/*try { | |
const pat = Pattern.node(SyntaxKind.CallExpression, { | |
expression: Pattern.identifier("formatMessage"), | |
arguments: Pattern.list( | |
[ | |
Pattern.node(SyntaxKind.ObjectLiteralExpression, { | |
properties: Pattern.list([ | |
Pattern.node(SyntaxKind.PropertyAssignment, { | |
name: Pattern.identifier("id"), | |
initializer: Pattern.any<Node>().named( | |
"bla" | |
) | |
}) | |
]) | |
}) | |
], | |
{ | |
rest: { | |
pattern: Pattern.any<Node[]>().named("rest"), | |
mode: "suffix" | |
} | |
} | |
).or(Pattern.any<Node>().named("invalid")) | |
}); | |
const mat = pat.match(node); | |
if (mat) { | |
debugger; | |
const nodes = mat.getCapturedValues("rest"); | |
} | |
} catch (e) { | |
debugger; | |
}*/ | |
} | |
} | |
export abstract class Match<TRoot, TVars> { | |
constructor(public readonly matchedValue: TRoot) {} | |
visualize(): unknown { | |
if (!TypeGuards.isNode(this.matchedValue)) { | |
return; | |
} | |
const obj = { matchedValue: this.matchedValue.compilerNode } as any; | |
for (const [key, items] of Object.entries(this.getCaptures())) { | |
const result = new Array<unknown>(); | |
for (const item of items as unknown[]) { | |
const arr = Array.isArray(item) ? item : [item]; | |
for (const i of arr) { | |
if (TypeGuards.isNode(i)) { | |
result.push(i.compilerNode); | |
} | |
} | |
} | |
obj[key] = result; | |
} | |
return obj; | |
} | |
public has<TVarName extends keyof TVars>(name: TVarName): boolean { | |
return this.getAll(name).length > 0; | |
} | |
public getSingle<TVarName extends keyof TVars>( | |
name: TVarName | |
): TVars[TVarName] { | |
const all = this.getAll(name); | |
if (all.length !== 1) { | |
throw new Error( | |
`Expected "${name}" to match exactly once, but matched ${all.length} times.` | |
); | |
} | |
return all[0]; | |
} | |
public getAll<TVarName extends keyof TVars>( | |
name: TVarName | |
): TVars[TVarName][] { | |
const arr = this.getCaptures()[name]; | |
if (!arr) { | |
return []; | |
} | |
return arr; | |
} | |
public getCaptures(): { [TKey in keyof TVars]: TVars[TKey][] } { | |
const result: Record<string, unknown[]> = {}; | |
this._collectCaptures(result); | |
return result as any; | |
} | |
public abstract _collectCaptures(captures: Record<string, unknown[]>): void; | |
} | |
class MatchImpl<TRoot, TVars> extends Match<TRoot, TVars> { | |
private readonly matches = new Array<Match<any, TVars>>(); | |
private readonly vars = new Array<{ name: string; value: unknown }>(); | |
public _collectCaptures(captures: Record<string, unknown[]>): void { | |
for (const v of this.vars) { | |
let list = captures[v.name]; | |
if (!list) { | |
list = []; | |
captures[v.name] = list; | |
} | |
list.push(v.value); | |
} | |
for (const m of this.matches) { | |
m._collectCaptures(captures); | |
} | |
} | |
addSubMatch(m: Match<any, TVars>): void { | |
this.matches.push(m); | |
} | |
addVar(name: string, value: unknown): void { | |
this.vars.push({ name, value }); | |
} | |
} | |
type Vars = Record<never, unknown>; | |
type UnionToIntersection<U> = (U extends any | |
? (k: U) => void | |
: never) extends ((k: infer I) => void) | |
? I | |
: never; | |
export abstract class Pattern< | |
TValue, | |
TOut = TValue, | |
TVars extends Vars = Vars | |
> { | |
public static node<TProps extends Record<string, Pattern<Node, any, any>>>( | |
kind: SyntaxKind, | |
properties: TProps | |
): Pattern<Node, Node, UnionToIntersection<TProps[keyof TProps]["TVars"]>> { | |
return new NodePattern(kind, properties) as any; | |
} | |
public static identifier(value?: string): Pattern<Node> { | |
const val = value; | |
return val | |
? new PredicatePattern( | |
n => TypeGuards.isIdentifier(n) && n.getText() === val | |
) | |
: new PredicatePattern(n => TypeGuards.isIdentifier(n)); | |
} | |
public static any<T>(): Pattern<T, T> { | |
return new PredicatePattern(n => true); | |
} | |
public static ofType<T>(type: { new (): T } | Function): Pattern<T, T> { | |
return new PredicatePattern(n => n instanceof type); | |
} | |
public static list<TVars extends Vars, TVarsRest extends Vars>( | |
patterns: Pattern<Node, any, TVars>[], | |
options?: { | |
rest?: { | |
pattern: Pattern<Node[], unknown, TVarsRest>; | |
mode: "suffix"; | |
}; | |
} | |
): Pattern<unknown, Node[], TVars & TVarsRest> { | |
return new FnPattern(node => { | |
if (!Array.isArray(node)) { | |
return false; | |
} | |
const c = node as Node[]; | |
if (options && options.rest) { | |
if (c.length < patterns.length) { | |
return false; | |
} | |
} else { | |
if (c.length !== patterns.length) { | |
return false; | |
} | |
} | |
const match = new MatchImpl<Node[], TVars & TVarsRest>(node); | |
for (let idx = 0; idx < patterns.length; idx++) { | |
const node = c[idx]; | |
const pattern = patterns[idx]; | |
const m = pattern.match(node); | |
if (!m) { | |
return false; | |
} | |
match.addSubMatch(m as any); | |
} | |
if (options && options.rest) { | |
const m = options.rest.pattern.match(c.slice(patterns.length)); | |
if (!m) { | |
return false; | |
} | |
match.addSubMatch(m as any); | |
} | |
return match; | |
}); | |
} | |
public get TVars(): TVars { | |
throw new Error(); | |
} | |
abstract match(value: TValue): Match<TOut, TVars> | false; | |
public or<TValue2, TOut2, TVars2 extends Vars>( | |
other: Pattern<TValue2, TOut2, TVars2> | |
): Pattern<TValue & TValue2, TOut | TOut2, TVars & TVars2> { | |
return new FnPattern(value => { | |
const m = this.match(value); | |
if (m) { | |
return m; | |
} | |
return other.match(value) as any; | |
}); | |
} | |
public named<TName extends string>( | |
name: TName | |
): Pattern<TValue, TOut, { [TKey in TName]: TOut } & TVars> { | |
return new FnPattern(value => { | |
const m = this.match(value); | |
if (!m) { | |
return false; | |
} | |
const m2 = new MatchImpl<TOut, any>(m.matchedValue); | |
m2.addSubMatch(m); | |
m2.addVar(name, value); | |
return m2 as any; | |
}); | |
} | |
} | |
class PredicatePattern<T> extends Pattern<T> { | |
constructor(public readonly test: (value: T) => boolean) { | |
super(); | |
} | |
match(value: T): Match<T, {}> | false { | |
const result = this.test(value); | |
if (result) { | |
return new MatchImpl(value); | |
} | |
return false; | |
} | |
} | |
class FnPattern<T, TOut, TVars extends Vars> extends Pattern<T, TOut, TVars> { | |
constructor( | |
public readonly matchFn: (value: T) => Match<TOut, TVars> | false | |
) { | |
super(); | |
} | |
match(value: T): Match<TOut, TVars> | false { | |
return this.matchFn(value); | |
} | |
} | |
class NodePattern<TVars extends Vars> extends Pattern<unknown, Node, TVars> { | |
constructor( | |
public readonly kind: ts.SyntaxKind, | |
public readonly properties: Record<string, Pattern<any, TVars>> | |
) { | |
super(); | |
} | |
match(value: unknown): Match<Node, TVars> | false { | |
if (!TypeGuards.isNode(value)) { | |
return false; | |
} | |
if (value.getKind() !== this.kind) { | |
return false; | |
} | |
const m = new MatchImpl<Node, TVars>(value); | |
for (const [key, pattern] of Object.entries(this.properties)) { | |
const prop = value.getNodeProperty(key as any); | |
// TODO | |
if (!prop) { | |
return false; | |
} | |
const match = pattern.match(prop); | |
if (!match) { | |
return false; | |
} | |
m.addSubMatch(match as any); | |
} | |
return m; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment