保持上下文的评估 [英] Context-preserving eval
问题描述
我们正在构建一个小的REPL,用于评估(由 eval
)用户所输入的javascript表达式.由于整个过程都是事件驱动的,因此评估必须在单独的函数中进行,但是必须在调用之间保留上下文(即所有声明的变量和函数).我想出了以下解决方案:
We're building a small REPL that evaluates (with eval
) javascript expressions as they are being entered by the user. Since the whole thing is event-driven, evaluation must take place in a separate function, but the context (that is, all declared variables and functions) must be preserved between the calls. I came up with the following solution:
function* _EVAL(s) {
while (1) {
try {
s = yield eval(s)
} catch(err) {
s = yield err
}
}
}
let _eval = _EVAL()
_eval.next()
function evaluate(expr) {
let result = _eval.next(expr).value
if (result instanceof Error)
console.log(expr, 'ERROR:', result.message)
else
console.log(expr, '===>', result)
}
evaluate('var ten = 10')
evaluate('function cube(x) { return x ** 3 }')
evaluate('ten + cube(3)')
evaluate('console.log("SIDE EFFECT")')
evaluate('let twenty = 20')
evaluate('twenty + 40') // PROBLEM
如您所见,它与函数作用域变量( var
和 function
)配合使用,但对块范围变量( let
)无效).
As you can see it works fine with function-scoped variables (var
and function
), but fails on block scoped ones (let
).
我如何编写一个上下文保留的 eval
包装器,该包装器还保留块作用域变量?
How can I write a context-preserving eval
wrapper that would also preserve block-scoped variables?
代码在浏览器中运行,DOM和Workers完全可用.
The code runs in a browser, DOM and Workers are fully available.
应该提到的是,所需的功能必须正确处理副作用,也就是说,每一行代码,或者至少每个副作用,应该准确地执行一次.
It should be mentioned that the desired function must handle side effects properly, that is, each line of code, or, at least, each side effect, should be performed exactly once.
链接:
JavaScript:在一个虚拟机中进行所有评估 | https://vane.life/2016/04/03/eval-locally-with-persistent-context/
推荐答案
如果您运行 5,则本机JavaScript行为会返回
本质上就是检查 5
.让a = 2 twenty + 40
的值时程序最终语句中发生的事情.但是,一种快速的解决方法是同时收集结果,完整结果 fullResult
和该步骤的结果( stepResult
).有了这两种方法,一旦您的 evaluate()
函数成功完成,我们就可以检查 stepResult
是否等于 undefined
分配新变量值时发生.
It is native JavaScript behavior to return 5
if you run 5; let a = 2
which is essentially what is happening here in the final statement of your program when you check for the value of twenty + 40
. However, a quick workaround for this would be to gather both results, the full result fullResult
and the result of just that step (stepResult
). With both of these, once a success is met in your evaluate()
function, we can check to see if stepResult
is equal to undefined
which occurs when assigning a new variable value.
在这种情况下,我们使用该值 undefined
.否则,我们将使用 fullResult
的值,该值在您所提出的问题的每种情况下都适用:
If this is the case, we use that value undefined
. Otherwise, we use the value of fullResult
, which works in every case of the provided code in your question:
const pastEvals = [];
function* _EVAL(s) {
while (1) {
try {
s = yield eval(s)
} catch(err) {
s = yield err
}
}
}
let _eval = _EVAL()
_eval.next()
function evaluate(expr) {
pastEvals.push(expr)
const fullResult = _eval.next(pastEvals.join(';')).value
const stepResult = _eval.next(expr).value
if (fullResult instanceof Error)
console.log(expr, 'ERROR:', result.message)
else
console.log(expr, '===>', stepResult === undefined ? stepResult : fullResult);
}
evaluate('var ten = 10')
evaluate('function cube(x) { return x ** 3 }')
evaluate('ten + cube(3)')
evaluate('let twenty = 20')
evaluate('twenty + 40')
当尝试使用更高级的JS函数(例如 async
/ await
和 fetch
)时,您会遇到此问题,但应该这些简单的用例就可以正常工作.如果您需要构建适用于更高级用途的工具,则可能需要在幕后创建一个虚拟DOM,每次运行都创建并销毁一个新的虚拟DOM,还需要等到所有创建的承诺在迭代之间实现并完成后,因为这是与任何 fetch
相关的操作所必需的.
You will run into problems with this when trying to use more advanced JS functions such as async
/await
and fetch
, but it should work just fine for these simpler use-cases. If you need to build something that works for more advanced uses, you may need to create a virtual DOM behind the scenes, create and destroy a new virtual DOM with each run, and also wait until any created promises are fulfilled and completed between iterations, as that is what would be required for any fetch
-related operations.
这篇关于保持上下文的评估的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!