Интерпретатор, взаимодействующий с пользователем в неблокирующей однопоточной среде (хитрый) - PullRequest
0 голосов
/ 22 ноября 2010

Для школьного проекта я и одноклассник пишем предметно-ориентированный язык в 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"
  }
}

Интерпретатор должен останавливаться, когдаОператор "Delayed" встречается, а затем продолжается, когда задержка завершена.

Приведенный выше код никогда не останавливается. Когда встречается оператор "Delayed", подсостояния задерживаются, но другое состояниепосле задержки «Задержка».

Для не фрагментированной версии кода, se http://pastie.org/1317023

Ответы [ 2 ]

1 голос
/ 22 ноября 2010

Я думаю, что ответ Иво в основном правильный, но я попытаюсь перефразировать его и добавить несколько предложений:

  1. Задержка () должна быть действием выхода, а не внутренним узломAST - при условии, что я правильно понял предполагаемую семантику: он должен блокироваться до получения данных, а затем завершаться / завершаться.

  2. Вам как-то нужно эмулировать понятие счетчика программы,и из стека кадра.Для действительно сконструированных действий (таких как цикл) кадр стека должен содержать текущее значение переменной цикла и текущую позицию в последовательности операторов.Не следует повторно использовать объекты AST для этого состояния, поскольку один и тот же цикл может выполняться одновременно несколько раз (при условии, что у него несколько клиентов).

  3. В состоянии выполняется операция «next», выполняющаяодин шаг исполнения.Задержка при первом вызове немедленно возвращается с кодом, указывающим, что дальнейшее выполнение нежелательно.При вызове во второй раз он ничего не делает (указывая, что действие завершено).

0 голосов
/ 22 ноября 2010

Ты в петле просто не прав.Вместо того, чтобы использовать цикл здесь:

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 будут просто поставлены в очередь и затем вызовут все после , когда ваш цикл закончился.

...