jevstrudel.git / packages / core / evaluate.mjs
1/*
2evaluate.mjs - <short description TODO>
3Copyright (C) 2022 Strudel contributors - see <https://codeberg.org/uzu/strudel/src/branch/main/packages/core/evaluate.mjs>
4This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program.  If not, see <https://www.gnu.org/licenses/>.
5*/
6
7export const strudelScope = {};
8// Make strudelScope available globally so transpiled code can access it
9globalThis.strudelScope = strudelScope;
10
11// Track user-defined keys (from block-based eval) so we can clear them without removing strudel functions
12export const userDefinedKeys = new Set();
13globalThis.userDefinedKeys = userDefinedKeys;
14
15/**
16 * Clears all user-defined variables and functions from the scope.
17 * This removes variables created during block-based evaluation.
18 * @name clearScope
19 * @example
20 * // After defining variables in blocks:
21 * // let myVar = 5
22 * // function myFunc() { return 10; }
23 * clearScope() // removes myVar and myFunc from scope
24 */
25export const clearScope = () => {
26  for (const key of userDefinedKeys) {
27    delete strudelScope[key];
28    delete globalThis[key];
29  }
30  userDefinedKeys.clear();
31  // Return silence if available (for use in pattern expressions), otherwise undefined
32  return globalThis.silence;
33};
34// Make clearScope available globally
35globalThis.clearScope = clearScope;
36
37export const evalScope = async (...args) => {
38  const results = await Promise.allSettled(args);
39  const modules = results.filter((result) => result.status === 'fulfilled').map((r) => r.value);
40  results.forEach((result, i) => {
41    if (result.status === 'rejected') {
42      console.warn(`evalScope: module with index ${i} could not be loaded:`, result.reason);
43    }
44  });
45  // Object.assign(globalThis, ...modules);
46  // below is a fix for above commented out line
47  // same error as https://github.com/vitest-dev/vitest/issues/1807 when running this on astro server
48  modules.forEach((module) => {
49    Object.entries(module).forEach(([name, value]) => {
50      globalThis[name] = value;
51      strudelScope[name] = value;
52    });
53  });
54  return modules;
55};
56
57function safeEval(str, options = {}) {
58  const { wrapExpression = true, wrapAsync = true } = options;
59  if (wrapExpression) {
60    str = `{${str}}`;
61  }
62  if (wrapAsync) {
63    str = `(async ()=>${str})()`;
64  }
65  const body = `"use strict";return (${str})`;
66  return Function(body)();
67}
68
69export const evaluate = async (code, transpiler, transpilerOptions) => {
70  let meta = {};
71
72  if (transpiler) {
73    // transform syntactically correct js code to semantically usable code
74    const transpiled = transpiler(code, transpilerOptions);
75    code = transpiled.output;
76    meta = transpiled;
77  }
78  // if no transpiler is given, we expect a single instruction (!wrapExpression)
79  const options = { wrapExpression: !!transpiler };
80  let evaluated = await safeEval(code, options);
81  return { mode: 'javascript', pattern: evaluated, meta };
82};