我正在制作一款小游戏,而对于其中的一部分,我想要一种非常简单的自定义编程语言。如果用户输入代码,例如variable "helloWorld" = 5
,"解释器"将变量更改为var并将引号删除为普通JavaScript。
我该如何运行该代码?我已经阅读了eval()
,但我也读过它很慢而且不应该使用。我已经研究了使用词法分析器,解析器和标记器创建编程语言,但我并不打算创建那些深入的东西。
任何有关方向的帮助都会很棒。
答案 0 :(得分:8)
我假设你不需要“如何编写代码?”的帮助,但是如何执行用户脚本。
eval
:eval
没问题。将脚本包裹在IIFE中!像这样包装脚本:
(function(){
// user script goes here. This will cause it to be in it's own scope!
})();
Javascript具有函数范围,因此这将保护全局空间不被用户变量和函数填充。用户仍然可能恶意影响全局变量,如下所示:
(function(){Array.isArray = function() { return 2;};})()
Array.isArray([]);
// returns 2
#!/bin/env node
// Be careful running this. You don't want to melt your cpu. Try 100,000 first.
console.time("no-eval");
for (var i = 0; i < 10000000; i++) { Math.sqrt(i); }
console.timeEnd("no-eval");
console.time("big-eval");
eval("for (var i = 0; i < 10000000; i++) { Math.sqrt(i); }");
console.timeEnd("big-eval");
console.time("evil-eval");
for (var i = 0; i < 10000000; i++) { eval("Math.sqrt(i);"); }
console.timeEnd("evil-eval");
输出:
no-eval: 272ms
big-eval: 294ms
evil-eval: 1945ms
正如你所看到的,'big-eval'有点慢。您可能会执行big-eval,一次运行用户脚本的所有行。 'evil-eval'慢得多,因为js引擎运行了10,000,000次eval! :)
答案 1 :(得分:2)
这一切都取决于你真正想用你的语言做什么。我猜你真的不希望用户运行在应用程序所在的同一个全局空间中运行的任意javascript。
因此,根据您真正想做的事情,您不一定需要使用eval()
。例如,让我们接受你的陈述:
variable "helloWorld" = 5
假设您解析了它并将其解析为一个包含数组中四个项的语句对象:
var statement = ["variable", "helloWorld", "=", 5];
好的,你可以从数组中的第一项看到用户正在声明一个变量。现在,您可能不希望用户的变量进入全局命名空间(另一个原因是不使用eval()
。因此,您为用户的变量创建了一个对象:
var userVars = {};
现在,当您处理上述声明时,您会看到它是对您刚刚转换为的用户变量的赋值:
userVars[statement[1]] = statement[3];
现在,您在名为userVars
的变量中有一个对象,其中包含名为"helloWorld"
的属性,其值为5
。用户的变量都不在全局命名空间中,或者可能影响您自己的程序操作。
现在,显然如果你想进入详细的表达式和逻辑流程,那么事情变得比这更困难,但我希望你看到你可能不仅仅想要将eval()
用于全局命名空间。 / p>
另一个&#34;安全&#34;执行任意用户javascript的方法是将其放在由主网页不同的域托管的iframe中。您可以将JS发送到您的服务器并将其传递给另一个服务器(可能在同一个盒子上)然后提供iframe,或者您可以使用window.postMessage()
将JS文本传递给其他合作的iframe和在那里执行。
由于iframe托管在其他域上,因此它与您自己的网页和您自己的网络应用服务器隔离开来。这基本上是jsFiddle的工作方式,允许用户运行任意javascript,同时保护自己的应用程序免受许多攻击。
或者,在将window.postMesage()
用于孤立的iframe的过程中,您甚至可以在其他iframe中使用eval()
。如果用户JS的观点是与您的游戏互动,那么您必须弄清楚如何允许两个iframe彼此交谈,但安全地这样做。可以使用IE8或更新版本支持的window.postMessage()
来完成。
孤立的iframe的美妙之处在于它拥有自己独立的全局变量集,并且除了通过window.postMessage()
公开的whateve界面外,根本无法解决你的游戏问题。< / p>
答案 2 :(得分:0)
你的语法可能会得到一些改进,但这个想法是这样的......
首先,你创建函数:
function variable(name, value){
window[name] = value;
}
下一步是解析用户输入(这是语法改进可能发挥作用的地方)。在你的例子中,你可以完全剥离引号,然后先将字符串除以'=',然后将空格分开。你得到3个代表(分别)的元素: 1)功能名称 2)变量名称 3)变量值
然后,你调用这样的函数:
window[functionName](variableName, variableValie);
答案 3 :(得分:0)
如果您需要JavaScript的完整表现力,那么坚持使用原始JavaScript,因为它已经为更多人所熟悉。使用原始JavaScript有两种可能性(没有限制解析器):
如果您希望允许用户放入您的应用程序的JavaScript能够与您的网站拥有完全的交互式权限,那么类似于允许您网站的特权加载项(请记住这一点很棒)风险:用户可能不知道他们在做什么,或者更糟糕的是,可能是从某些恶意来源粘贴的,完全访问将包括允许访问您可能用于存储会话信息或密码的cookie,然后继续进行亲自前往eval()
。
如果您希望获得JavaScript的完整表达能力,但又不希望用户代码干扰您的应用程序(并且可以自由地在单独的网站上托管代码,而无需访问您网站的Cookie ),然后您可以使用postMessage
基于eval()
沙盒的类似内容:http://www.html5rocks.com/en/tutorials/security/sandboxed-iframes/#safely-sandboxing-eval
但是,如果您不需要JavaScript的全部表现力,并且只是希望用户拨打您自己的自定义API或其他内容,我强烈建议您考虑使用Blockly(demo/tutorial ),因为它是一种图形编码机制,应该比手动创建一些自定义语法更加用户友好,并且更不容易出错。