Skip to content

Commit

Permalink
feat: impl builtin functions
Browse files Browse the repository at this point in the history
  • Loading branch information
iyxan23 committed Sep 16, 2024
1 parent 8103ee0 commit 838a193
Show file tree
Hide file tree
Showing 2 changed files with 227 additions and 4 deletions.
225 changes: 225 additions & 0 deletions src/sheet/functions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
// Default built-in functions

import { z } from "zod";
import { callLambda, createTemplaterFunction } from "./templater-function";
import { createTemplaterNoArgsFunction } from "./templater-function";
import { success } from "./expression/result";

// == array

// .rest() is not possible yet
// const array = createTemplaterFunction(z.tuple([]).rest(z.any()), (...rest) =>
// success([...rest]),
// );
const arrayEmpty = createTemplaterNoArgsFunction(() => []);
const arrayAppend = createTemplaterFunction(
z.tuple([z.any().array(), z.any()]),
(arr, item) => success([...arr, item]),
);
const length = createTemplaterFunction(z.tuple([z.array(z.any())]), (a) =>
success(a.length),
);
const keys = createTemplaterFunction(z.tuple([z.any()]), (obj) =>
success(Object.keys(obj)),
);
const values = createTemplaterFunction(z.tuple([z.any()]), (obj) =>
success(Object.values(obj)),
);
const entries = createTemplaterFunction(z.tuple([z.any()]), (obj) =>
success(Object.entries(obj)),
);

// == composable functions

const flatten = createTemplaterFunction(
z.tuple([z.array(z.array(z.any()))]),
(arr) => success(arr.flat()),
);

// usage:
//
// [reduce [:array] item acc { [...] } 0]
// ------- ---- --- --------- -
// array | | | L initial value
// | | lambda
// item local variable name | with item
// | & acc
// acc local variable name -+
const reduce = createTemplaterFunction(
z.tuple([
z.array(z.any()),
z.string(),
z.string(),
z.function(),
z.any().optional(),
]),
(arr, itemName, accName, fn, init) => {
let result = init;
const issues = [];
const callFn = callLambda(fn);

for (const item of arr) {
const callResult = callFn({
variables: {
[itemName]: item,
[accName]: result,
},
});

if (callResult.status === "failed") return callResult;
issues.push(...callResult.issues);

result = callResult.result;
}

return success(result, issues);
},
);

// usage:
//
// [map [:array] item { [...] }]
// -------- ---- --------
// array | L lambda with item,
// | returns mapped item
// item local variable name
const map = createTemplaterFunction(
z.tuple([z.array(z.any()), z.string(), z.function()]),
(arr, idxName, fn) => {
const resultArr = [];
const issues = [];
const callFn = callLambda(fn);

for (let i = 0; i < arr.length; i++) {
const result = callFn({
variables: {
[idxName]: i,
},
});

if (result.status === "failed") return result;

issues.push(...result.issues);
resultArr.push(result.result);
}

return success(resultArr, issues);
},
);
const unique = createTemplaterFunction(z.tuple([z.array(z.any())]), (a) =>
success([...new Set(a)]),
);

// usage:
//
// [filter [:array] item { [...] }]
// -------- ---- --------
// array | L lambda with item,
// | returns boolean
// item local variable name
const filter = createTemplaterFunction(
z.tuple([z.array(z.any()), z.string(), z.function()]),
(arr, idxName, fn) => {
const resultArr = [];
const issues = [];
const callFn = callLambda(fn);

for (const item of arr) {
const result = callFn({
variables: {
[idxName]: item,
},
});

if (result.status === "failed") return result;

issues.push(...result.issues);
if (result.result) resultArr.push(item);
}

return success(resultArr, issues);
},
);

// == arithmetic

const add = createTemplaterFunction(
z.tuple([z.coerce.number(), z.coerce.number()]),
(a, b) => success(a + b),
);
const subtract = createTemplaterFunction(
z.tuple([z.coerce.number(), z.coerce.number()]),
(a, b) => success(a - b),
);
const multiply = createTemplaterFunction(
z.tuple([z.coerce.number(), z.coerce.number()]),
(a, b) => success(a * b),
);
const divide = createTemplaterFunction(
z.tuple([z.coerce.number(), z.coerce.number()]),
(a, b) => success(a / b),
);
const round = createTemplaterFunction(
z.tuple([z.coerce.number(), z.coerce.number().optional()]),
(num, round) =>
success(
round ? Math.round(num * (round * 10)) / (round * 10) : Math.round(num),
),
);

// == string

const concat = createTemplaterFunction(
z.tuple([z.coerce.string(), z.coerce.string()]),
(a, b) => success(a + b),
);
const join = createTemplaterFunction(
z.tuple([z.array(z.string()), z.string().optional()]),
(arr, sep = "") => success(arr.join(sep)),
);
const jsonStringify = createTemplaterFunction(z.tuple([z.any()]), (a) =>
success(JSON.stringify(a)),
);
const jsonParse = createTemplaterFunction(z.tuple([z.string()]), (a) =>
success(JSON.parse(a)),
);

// == conditionals

const if_ = createTemplaterFunction(
z.tuple([z.coerce.boolean(), z.any(), z.any()]),
(condition, ifTrue, ifFalse) => (condition ? ifTrue : ifFalse),
);
const ifUndefined = createTemplaterFunction(
z.tuple([z.any(), z.string()]),
(a, b) => (a === undefined ? success(b) : success(a)),
);

export const builtinFunctions = {
arrayEmpty,
arrayAppend,
flatten,
reduce,
map,
unique,
filter,

length,
keys,
values,
entries,

add,
subtract,
multiply,
divide,
round,

concat,
join,
jsonStringify,
jsonParse,

if: if_,
ifUndefined,
};
6 changes: 2 additions & 4 deletions src/sheet/sheet-templater.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
import { resultSymbol, success } from "./expression/result";
import { Result } from "./expression/result";
import deepmerge from "deepmerge";
import { builtinFunctions } from "./functions";

export interface TemplatableCell {
getTextContent(): string;
Expand Down Expand Up @@ -56,10 +57,7 @@ export class SheetTemplater<SheetT extends TemplatableCell> {
private sheet: Sheet<SheetT>;

private functions: Record<string, TemplaterFunction<any>> = {
helloWorld: createTemplaterNoArgsFunction(() => "hello world"),
testLambda: createTemplaterFunction(z.tuple([z.function()]), (a) =>
success(a()),
),
...builtinFunctions,
};

constructor(
Expand Down

0 comments on commit 838a193

Please sign in to comment.