Skip to content

Commit

Permalink
chore: move function helpers into another file
Browse files Browse the repository at this point in the history
  • Loading branch information
iyxan23 committed Sep 16, 2024
1 parent a413250 commit 8103ee0
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 103 deletions.
7 changes: 2 additions & 5 deletions src/sheet/expression/evaluate.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import { z } from "zod";
import {
callLambda,
createTemplaterFunction,
createTemplaterNoArgsFunction,
} from "../sheet-templater";
import { callLambda, createTemplaterFunction } from "../templater-function";
import { createTemplaterNoArgsFunction } from "../templater-function";
import { evaluateExpression, Issue } from "./evaluate";
import { Expression } from "./parser";
import { success } from "./result";
Expand Down
5 changes: 2 additions & 3 deletions src/sheet/sheet-templater.test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { z } from "zod";
import { Sheet } from "./sheet";
import {
callLambda,
createTemplaterFunction,
createTemplaterNoArgsFunction,
SheetTemplater,
TemplatableCell,
} from "./sheet-templater";
import { callLambda, createTemplaterFunction } from "./templater-function";
import { createTemplaterNoArgsFunction } from "./templater-function";
import { success } from "./expression/result";

class SimpleCell implements TemplatableCell {
Expand Down
95 changes: 0 additions & 95 deletions src/sheet/sheet-templater.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import { type ExpressionCell, parseExpressionCell } from "./expression/parser";
import { Block, extractHoistsAndBlocks } from "./expression/extractor";
import { Sheet } from "./sheet";
import { z } from "zod";
import {
evaluateExpression,
Issue,
LambdaFunction,
TemplaterFunction,
} from "./expression/evaluate";
import { resultSymbol, success } from "./expression/result";
Expand All @@ -20,99 +18,6 @@ export interface TemplatableCell {

export type Indexable2DArray<T> = Record<number, Record<number, T>>;

export function createTemplaterNoArgsFunction<R>(
call: () => R,
): TemplaterFunction<R> {
return {
call: () => success(call()),
};
}

/**
* Please note that the result of a lambda call is a form of `Result<any>`,
* which has the following structure:
*
* ```
* export type Result<T> =
* | { status: "success"; result: T; issues: Issue[]; sym: ResultSymbol }
* | { status: "failed"; issues: Issue[]; error: Issue; sym: ResultSymbol };
* ```
*/
export function callLambda(
f: Function,
): (opts: {
variables?: Record<string, any>;
customVariableResolver?: (variableName: string) => any;
}) => Result<any> {
return (opts) =>
f(
(vName: string) =>
opts.customVariableResolver?.(vName) ?? opts.variables?.[vName],
);
}

type MapFunctionToLambda<T> = T extends (...args: any[]) => infer R
? LambdaFunction<R>
: T;
type MapFunctionsToLambdas<T> = { [K in keyof T]: MapFunctionToLambda<T[K]> };

/**
* ## Calling a lambda
* To call a lambda, use `z.function()` as arg, but call
* `callLambda(theFunc)()` to call the lambda.
*
* It's possible specify local variables that will only be defined within the
* lambda by passing a `Record<string, any>` on the field `variables` to the
* function returned by `callLambda`, as such:
*
* ```
* callLambda(theFunc)({ variables: { index: 0 } })
* ```
*
* The variable `index` will be defined within the lambda.
*
* It's also possible to provide variables through a function instead of
* passing a Record, use the `customVariableResolver`:
*
* ```
* callLambda(theFunc)({
* customVariableResolver:
* (vName) => vName === "myVar" ? "hello" : undefined
* })
* ```
*
* ## Return
*
* The caller of this function should return a `Result<any>` value, which can
* represent a successful or failed execution. It's also possible to include
* issues in the result, which will be handled by the caller at the upmost
* level.
*
* If you're working with lambdas, it's highly recommended to collect the
* `issues` returned by a lambda call, and accumulate them into a list, where
* it will be included in this function's `Result<any>` return value.
*
* See `success(...)`, and `failure(...)` to easily create `Result<any>` objects.
*/
export function createTemplaterFunction<T extends z.ZodTuple, R>(
schema: T,
call: (...args: MapFunctionsToLambdas<z.infer<T>>) => Result<R>,
): TemplaterFunction<R> {
return {
call: (funcName, ...args: any) => {
const result = schema.safeParse(args);

if (!result.success) {
throw new Error(
`invalid arguments when evaluating function \`${funcName}\`: ${result.error}`,
);
}

return call(...(result.data as MapFunctionsToLambdas<z.infer<T>>));
},
};
}

type SheetShiftListener = (
shift:
| {
Expand Down
96 changes: 96 additions & 0 deletions src/sheet/templater-function.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { z } from "zod";
import { LambdaFunction, TemplaterFunction } from "./expression/evaluate";
import { Result, success } from "./expression/result";

export function createTemplaterNoArgsFunction<R>(
call: () => R,
): TemplaterFunction<R> {
return {
call: () => success(call()),
};
}

/**
* Please note that the result of a lambda call is a form of `Result<any>`,
* which has the following structure:
*
* ```
* export type Result<T> =
* | { status: "success"; result: T; issues: Issue[]; sym: ResultSymbol }
* | { status: "failed"; issues: Issue[]; error: Issue; sym: ResultSymbol };
* ```
*/
export function callLambda(
f: Function,
): (opts: {
variables?: Record<string, any>;
customVariableResolver?: (variableName: string) => any;
}) => Result<any> {
return (opts) =>
f(
(vName: string) =>
opts.customVariableResolver?.(vName) ?? opts.variables?.[vName],
);
}

type MapFunctionToLambda<T> = T extends (...args: any[]) => infer R
? LambdaFunction<R>
: T;
type MapFunctionsToLambdas<T> = { [K in keyof T]: MapFunctionToLambda<T[K]> };

/**
* ## Calling a lambda
* To call a lambda, use `z.function()` as arg, but call
* `callLambda(theFunc)()` to call the lambda.
*
* It's possible specify local variables that will only be defined within the
* lambda by passing a `Record<string, any>` on the field `variables` to the
* function returned by `callLambda`, as such:
*
* ```
* callLambda(theFunc)({ variables: { index: 0 } })
* ```
*
* The variable `index` will be defined within the lambda.
*
* It's also possible to provide variables through a function instead of
* passing a Record, use the `customVariableResolver`:
*
* ```
* callLambda(theFunc)({
* customVariableResolver:
* (vName) => vName === "myVar" ? "hello" : undefined
* })
* ```
*
* ## Return
*
* The caller of this function should return a `Result<any>` value, which can
* represent a successful or failed execution. It's also possible to include
* issues in the result, which will be handled by the caller at the upmost
* level.
*
* If you're working with lambdas, it's highly recommended to collect the
* `issues` returned by a lambda call, and accumulate them into a list, where
* it will be included in this function's `Result<any>` return value.
*
* See `success(...)`, and `failure(...)` to easily create `Result<any>` objects.
*/
export function createTemplaterFunction<T extends z.ZodTuple, R>(
schema: T,
call: (...args: MapFunctionsToLambdas<z.infer<T>>) => Result<R>,
): TemplaterFunction<R> {
return {
call: (funcName, ...args: any) => {
const result = schema.safeParse(args);

if (!result.success) {
throw new Error(
`invalid arguments when evaluating function \`${funcName}\`: ${result.error}`,
);
}

return call(...(result.data as MapFunctionsToLambdas<z.infer<T>>));
},
};
}

0 comments on commit 8103ee0

Please sign in to comment.