1const functionMap = {
2 // Basic Arithmetic Operations, with checks for type as numbers
3 "add": (a, b) => typeof a === "number" && typeof b === "number" ? a + b : null,
84 "datebetween": (startDate, endDate, unit) => {
85 if (!startDate || !endDate || typeof unit !== "string") {
86 return new Response("Invalid arguments for datebetween function");
87 }
88 let difference = Math.abs(endDate - startDate);
160 // Conditional logic with checks
161 "if": (a, b, c) => {
162 console.log(`if function called with a: ${a}, b: ${b}, c: ${c}`);
163 const result = a ? b : c;
164 console.log(`if function result: ${result}`);
165 return result;
166 },
175};
176
177function tokenize(formula) {
178 const tokens = [];
179 const functionsRegex = new RegExp(`\\b(${Object.keys(functionMap).join("|")})\\b`, "g");
180 const tokenRegex = new RegExp(
181 `(\\[\\[date:\\d{4}-\\d{2}-\\d{2}\\]\\])|`
182 + `${functionsRegex.source}|`
183 + `("(?:[^"\\\\]|\\\\.)*")|` // Match double-quoted strings
184 + `(-?\\d+(?:\\.\\d+)?)|` // Match numbers (including negative and decimal numbers)
203}
204
205function parse(tokens) {
206 let position = 0;
207
208 function peek() {
209 return tokens[position];
210 }
211
212 function consume() {
213 position++;
214 }
215
216 function isNumber(token) {
217 return !isNaN(parseFloat(token)) && isFinite(token);
218 }
219
220 function isString(token) {
221 return token.startsWith("\"") && token.endsWith("\"");
222 }
223
224 function parsePrimaryExpr() {
225 const token = peek();
226 if (token === "Yes") {
239 consume();
240 return { type: "date", value: token.slice(7, -2) };
241 } else if (token && functionMap[token]) {
242 consume();
243 return { type: "function", name: token };
244 } else if (token.match(/^[a-zA-Z_]+$/)) {
245 consume();
256 }
257
258 function parseExpr() {
259 let expr = parsePrimaryExpr();
260 while (peek() === "(") {
283}
284
285function evaluate(ast) {
286 console.log(`Evaluating node: ${JSON.stringify(ast)}`);
287
288 if (typeof ast !== "object" || ast === null || typeof ast.type === "undefined") {
289 throw new Error("Invalid input to evaluate function: Input must be an AST object");
290 }
291
298 return new Date(ast.value);
299 case "call":
300 if (typeof functionMap[ast.name] !== "function") {
301 throw new Error(`Function '${ast.name}' not found in functionMap`);
302 }
303 const argsEvaluated = ast.args.map(arg => evaluate(arg));
304 const result = functionMap[ast.name](...argsEvaluated);
305 if (result === null || result === undefined) {
306 throw new Error(`Function '${ast.name}' returned null or undefined`);
307 }
308 return result;
321 }
322 } catch (error) {
323 console.error(`Error in evaluate function: ${error.message} for AST node ${JSON.stringify(ast)}`, error);
324 throw error;
325 }
326}
327
328export async function FormulaEndpoint(req) {
329 const url = new URL(req.url);
330 console.log(`Received request with URL: ${req.url}`);