Skip to content

Commit

Permalink
refactor: matcher and usage example
Browse files Browse the repository at this point in the history
  • Loading branch information
verytactical committed Oct 30, 2024
1 parent fb9b2e1 commit 4ecf064
Show file tree
Hide file tree
Showing 2 changed files with 123 additions and 45 deletions.
53 changes: 23 additions & 30 deletions src/interpreter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ import {
} from "./types/types";
import { sha256_sync } from "@ton/crypto";
import { enabledMasterchain } from "./config/features";
import { match } from "./utils/tricks";

// TVM integers are signed 257-bit integers
const minTvmInt: bigint = -(2n ** 256n);
Expand Down Expand Up @@ -765,36 +766,28 @@ export class Interpreter {
);
}

public interpretExpression(ast: AstExpression): Value {
switch (ast.kind) {
case "id":
return this.interpretName(ast);
case "method_call":
return this.interpretMethodCall(ast);
case "init_of":
return this.interpretInitOf(ast);
case "null":
return this.interpretNull(ast);
case "boolean":
return this.interpretBoolean(ast);
case "number":
return this.interpretNumber(ast);
case "string":
return this.interpretString(ast);
case "op_unary":
return this.interpretUnaryOp(ast);
case "op_binary":
return this.interpretBinaryOp(ast);
case "conditional":
return this.interpretConditional(ast);
case "struct_instance":
return this.interpretStructInstance(ast);
case "field_access":
return this.interpretFieldAccess(ast);
case "static_call":
return this.interpretStaticCall(ast);
}
}
public interpretExpression = (ast: AstExpression): Value =>
match(ast)
.on({ kind: "id" })((ast) => this.interpretName(ast))
.on({ kind: "method_call" })((ast) => this.interpretMethodCall(ast))
.on({ kind: "init_of" })((ast) => this.interpretInitOf(ast))
.on({ kind: "null" })((ast) => this.interpretNull(ast))
.on({ kind: "boolean" })((ast) => this.interpretBoolean(ast))
.on({ kind: "number" })((ast) => this.interpretNumber(ast))
.on({ kind: "string" })((ast) => this.interpretString(ast))
.on({ kind: "op_unary" })((ast) => this.interpretUnaryOp(ast))
.on({ kind: "op_binary" })((ast) => this.interpretBinaryOp(ast))
.on({ kind: "conditional" })((ast) =>
this.interpretConditional(ast),
)
.on({ kind: "struct_instance" })((ast) =>
this.interpretStructInstance(ast),
)
.on({ kind: "field_access" })((ast) =>
this.interpretFieldAccess(ast),
)
.on({ kind: "static_call" })((ast) => this.interpretStaticCall(ast))
.end();

public interpretName(ast: AstId): Value {
if (hasStaticConstant(this.context, idText(ast))) {
Expand Down
115 changes: 100 additions & 15 deletions src/utils/tricks.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { throwInternalCompilerError } from "../errors";

/**
Expand Down Expand Up @@ -44,19 +45,103 @@ type Handlers<I, O> = Unwrap<Intersect<Inputs<I>>> & Outputs<O>;
*/
export const makeVisitor =
<I>() =>
<O>(handlers: Handlers<I, O>) => {
return (input: Extract<I, { kind: string }>): O[keyof O] => {
const handler = (
handlers as Record<string, (input: I) => O[keyof O]>
)[input.kind];

// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (handler) {
return handler(input);
} else {
throwInternalCompilerError(
`Reached impossible case: ${input.kind}`,
);
}
};
<O>(handlers: Handlers<I, O>) =>
(input: Extract<I, { kind: string }>): O[keyof O] => {
const handler = (handlers as Record<string, (input: I) => O[keyof O]>)[
input.kind
];

// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (handler) {
return handler(input);
} else {
throwInternalCompilerError(
`Reached impossible case: ${input.kind}`,
);
}
};

type Extend<T extends any[], H> = H extends infer A ? [...T, A] : never;
type Flat<TS extends any[], R extends any[] = []> = TS extends [
infer H,
...infer T,
]
? Flat<T, Extend<R, H>>
: R;

declare const NoSuchCase: unique symbol;
interface NoSuchCaseBug<L> extends Array<never> {
[NoSuchCase]: L;
}
type On<V, I extends any[], O> = {
on: <const DI extends any[]>(
...key: I extends Flat<DI> ? DI : NoSuchCaseBug<DI>
) => <const DO>(
handler: (...args: Extract<I, Flat<DI>>) => DO,
) => MV<V, Exclude<I, Flat<DI>>, O | DO>;
};

declare const CasesAreNotExhaustive: unique symbol;
interface NonExhaustiveBug<L> {
[CasesAreNotExhaustive]: L;
}
type End<I, O> = [I] extends [never]
? EndInternal<O>
: {
end: NonExhaustiveBug<I>;
};
type MV<V, I extends any[], O> = End<I, O> & On<V, I, O>;

type OnInternal<V, I extends any[], O> = {
on: <const DI extends any[]>(
...key: DI
) => <const DO>(
handler: (...args: Extract<I, Flat<DI>>) => DO,
) => MVInternal<V, Exclude<I, Flat<DI>>, O | DO>;
};
type EndInternal<O> = {
end: () => O;
};
type MVInternal<V, I extends any[], O> = EndInternal<O> & OnInternal<V, I, O>;

const deepMatch = (a: unknown, b: unknown): boolean => {
if (
a === b &&
["number", "string", "boolean", "bigint"].includes(typeof a) &&
typeof a === typeof b
) {
return true;
}
if (a === null || b === null) {
return a === b;
}
if (typeof a === "object" && typeof b === "object") {
if (Array.isArray(a) && Array.isArray(b) && a.length === b.length) {
return a.every((a, i) => deepMatch(a, b[i]));
} else {
return Object.entries(b).every(([k, b]) =>
deepMatch(k in a ? (a as any)[k] : undefined, b),
);
}
}
return false;
};

export const match = <I extends any[]>(...args: I): MV<I, Flat<I>, never> => {
const rec = <V, I extends any[], O>(end: () => O): MVInternal<V, I, O> => ({
end,
on:
<const DI extends any[]>(...match: DI) =>
<const DO>(handler: (...args: Extract<I, Flat<DI>>) => DO) =>
rec<V, Exclude<I, Flat<DI>>, O | DO>(() =>
deepMatch(args, match)
? handler(
...(args as unknown as Extract<I, Flat<DI, []>>),
)
: end(),
),
});
return rec<I, Flat<I>, never>(() => {
throw new Error("Not exhaustive");
}) as MV<I, Flat<I>, never>;
};

0 comments on commit 4ecf064

Please sign in to comment.