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};