对于学校项目,我和同学正在javascript(节点)中编写特定于域的语言。该语言包含需要通过Websocket连接进行用户输入的语句。
当语句需要用户输入时,解释器必须停止执行,并等待事件。
通常会暂停线程并在继续线程之前等待接收用户输入,但我们不能这样做,因为node.js是单线程的,并且在不阻塞处理器的情况下不提供睡眠选项。
我们尝试了很多方法来解决它,但失败了。 : - (
这个问题的答案可能是如何建立一个可以解释的翻译的建议。
下面,我们将完成解释器的简化(有错误)
我们用这些节点构建一个抽象语法树
var Print = function( str ){
this.str = str;
}
var Block = function( stats ){
this.stats = stats;
}
var Delayed = function( stats ){
this.stats = stats;
}
var Loop = function( times, stats ){
this.times = times;
this.stats = stats;
}
树看起来像这样:
var ast = new Block([
new Delayed([
new Print("blah blah"),
new Delayed([])
]),
new Loop(3,[
new Delayed([
new Print("loop delayed")
])
])
]);
用于评估陈述的解释器。请注意,此代码不能正常工作。它永远不会停下来等待输入。
var Interpreter = function( ast ){
this.ast = ast;
}
Interpreter.prototype.run = function(){
this.handle( this.ast );
}
Interpreter.prototype.handleAll = function( stats ){
for( var i = 0; i < stats.length; i++ ){
this.handle(stats[i]);
}
}
Interpreter.prototype.handle = function( stat ){
var t = this;
/*-----------------------------------------------*
* Simple statement - no need for pause here *
*-----------------------------------------------*/
if( stat instanceof Print ){
sys.puts(stat.str);
}
/*-----------------------------------------------------*
* Delayed - this might contain more delayed stats *
*-----------------------------------------------------*/
else if( stat instanceof Delayed ){
sys.debug("waiting for user input");
// this represents a user input with a string
setTimeout(function(str){
sys.debug("done waiting");
sys.puts(str);
// this might contain delayed stats
t.handleAll(stat.stats);
}, 2000, "some string");
}
// ============================================
// = Block - this might contain delayed stats =
// ============================================
else if( stat instanceof Block ){
sys.debug("doing a block - before");
this.handleAll(stat.stats);
sys.debug("doing a block - after");
}
// ===========================================
// = Loop - this might contain delayed stats =
// ===========================================
else if( stat instanceof Loop ){
sys.debug("before loop");
for( var i = 0; i < stat.times; i++ ){
sys.debug("inside loop[" + i + "] - begin");
// this will maybe contain delayed stats
this.handleAll(stat.stats);
sys.debug("inside loop[" + i + "] - end");
}
sys.debug("after loop");
}
else {
throw "error.. statement not recognized"
}
}
解释器需要在遇到“延迟”语句时暂停,然后在延迟完成时继续。
上面的代码永远不会停顿。遇到“延迟”语句时,子语句会延迟,但执行“延迟”后的其他语句。
对于代码的非碎片版本,请{se http://pastie.org/1317023
答案 0 :(得分:1)
我认为Ivo的答案基本上是正确的,但我会尝试重新措辞并添加一些建议:
Delayed()应该是一个离开动作,而不是AST的内部节点 - 假设我得到了正确的语义:它应该阻塞直到收到数据,然后完成/终止。
< / LI>你需要模仿程序计数器和堆栈帧的概念。对于真正构造的动作(例如循环),堆栈帧需要包含循环变量的当前值以及语句序列中的当前位置。您不应该为该状态回收AST对象,因为同一个循环可能同时执行多次(假设有多个客户端)。
状态有“下一步”操作,执行一个步骤。首次调用时,延迟会立即返回一个代码,表明不需要进一步执行。第二次调用时,它什么都不做(表示操作已完成)。
答案 1 :(得分:0)
你的循环是完全错误的。而不是在这里使用循环:
for( var i = 0; i < stat.times; i++ ){
}
您需要修改完整的句柄功能:
// you might want to make it so that you can pass null to indicate blocking etc.
Interpreter.prototype.handle = function( stat ){
var that = this;
var wait = 0;
// in case of delayed, just set wait to the desired delay
// in case of a loop, well you either go recursive or use a stack based approach
// fake the loop
setTimeout(function(){that.handle();}, wait);
}
所以你需要通过回调“伪造”你的循环,看起来很棘手,但它确实不具备循环的所有优点(你需要我上面提到的堆栈/递归),但它也给出了你想要的所有其他东西。
对于WebSocket输入,也是异步,在数据事件中,您只需检查当前是否阻止,如果是,则将数据作为用户输入提供。
请记住,一次只运行一件事,所以如果你遍历你的程序,没有其他任何东西都有机会运行,即使你的WebSocket事件只会排队,然后在之后触发所有你的循环结束了。