javascript вложенные циклы, ожидающие ввода пользователя
Я построил интерпретатор C в C# некоторое время назад и теперь начал преобразовывать его в Javascript. Все шло хорошо, пока я не понял, что у js нет функции сна. Мой интерпретатор использует рекурсивный синтаксический анализатор и делает паузу для пользовательского ввода, в то время как в него вложено несколько функций (в C# я использовал waithandle во втором потоке). Я смотрел на setInterval и setTimeout, но они асинхронные / неблокирующие; Конечно, об оживленном ожидании не может быть и речи, и я посмотрел на реализацию timed_queue, которую нашел в SO, но не повезло. Я пробовал парсер как в главном окне, так и в веб-работнике. Я использую JQuery. У меня ограниченный опыт работы с JS, и я ищу идеи для реализации. Я мало знаю о стиле продолжения прохождения, или уступаю, и мне интересно, могут ли они держать ключ. Здесь немного вырезано из кода, чтобы показать некоторые элементы управления. Любые идеи, пожалуйста...
var STATE = {
START: "START",
RUN: "RUN", //take continuous steps at waitTime delay
STEP: "STEP", //take 1 step
PAUSE: "PAUSE",//wait for next step command
STOP: "STOP",
ERROR: "ERROR"
}
var state = state.STOP;
function parsing_process() //long process we may want to pause or wait in
{
while(token !== end_of_file)//
{
//do lots of stuff - much of it recursive
//the call to getNextToken will be encountered a lot in the recursion
getNextToken();
if (state === STATE.STOP)
break;
}
}
function getNextToken()
{
//retrieve next token from lexer array
if (token === end_of_line)
{
//tell the gui to highlight the current line
if (state === STATE.STOP)
return;
if (state === STATE.STEP)//wait for next step
{
//mimick wait for user input by using annoying alert
alert("click me to continue")
}
if (state === STATE.RUN) {
//a delay here - set by a slider in the window
//a busy wait haults processing of the window
}
}
}
Я получил это для работы в Firefox с помощью task.js
<html>
<head>
<title>task.js examples: sleep</title>
<script type="application/javascript" src="task.js"></script>
</head>
<body>
Only works in FIREFOX
<button onclick="step()">Step</button>
<button onclick="run()">Run</button>
<button onclick="stop()">Stop</button>
<pre style="border: solid 1px black; width: 300px; height: 200px;" id="out">
</pre>
<script type="application/javascript;version=1.8">
function start() {
process();
}
function step() {
if (state === STATE.STOP)
start();
state = STATE.STEP;
}
function run() {
if (state === STATE.STOP)
start();
state = STATE.RUN;
}
function stop() {
state = STATE.STOP;
}
var STATE = {
START: "START",
RUN: "RUN", //take continuous steps at sleepTime delay
STEP: "STEP", //take 1 step
PAUSE: "PAUSE",//wait for next step command
STOP: "STOP",
ERROR: "ERROR"
}
var state = STATE.STOP;
var sleepTime = 500;
function process() {
var { spawn, choose, sleep } = task;
var out = document.getElementById("out");
var i=0;
out.innerHTML = "i="+i;
var sp = spawn(function() {
while(state !== STATE.STOP)
{
i++;
out.innerHTML = "i="+i;
if (state === STATE.RUN)
{
yield sleep(sleepTime);
}
if (state === STATE.STEP)
state = STATE.PAUSE;
while (state===STATE.PAUSE)
{
yield;
}
}
});
}
</script>
</body>
</html>
Я был бы признателен, если бы кто-то, кто знал что-то об обещаниях, мог дать мне еще несколько подсказок. Мое приложение не потребительское, но было бы неплохо, если бы оно работало больше, чем Firefox
3 ответа
Как автор АКПП, я столкнулся с точно такой же проблемой, когда внедрял отладчик, который на лету приостанавливает и продолжает интерпретацию программы. В конце я решил использовать функции генератора из es6, но я хотел бы поделиться своим мыслительным процессом здесь.
Обычный способ - сначала скомпилировать целевой код в низкоуровневый безрекурсивный байт-код. Вы маркируете каждый оператор, а затем обрабатываете весь поток управления с помощью unconditional jump
а также conditional jump
, Затем вы запускаете интерпретатор байт-кода. Это хороший вариант, если вы не возражаете против всей этой работы по компиляции.
Другой способ - это рабочий процесс "сохранение стека вызовов / загрузка стека вызовов". Когда вам нужно приостановить интерпретацию, вы рекурсивно помещаете все аргументы и все локальные переменные в настраиваемый стек до самого конца. Когда вам нужно продолжить выполнение, вы рекурсивно загружаете все эти аргументы и локальные переменные. Ваш код будет преобразован из
AddExpression.prototype.visit = function(param) {
var leftVal = visit(this.left, param);
var rightVal = visit(this.right, param);
return leftVal + rightVal;
}
в
AddExpression.prototype.visit = function(param) {
if (needToStop) {
stack.push({
method: AddExpression.prototype.visit,
_this: this,
params: [param],
locals: {},
step: 0
});
return;
}
if (recoverFromStop && stack.top().step === 0) {
var thisCall = stack.pop();
if (stack.length > 0) {
var nextCall = stack.top();
nextCall.method.apply(nextCall._this, params);
}
}
var leftvalue = visit(this.left, param);
if (needToStop) {
stack.push({
method: AddExpression.prototype.visit,
_this: this,
params: [],
locals: {
leftvalue: leftvalue
},
step: 1
});
return;
}
if (recoverFromStop && stack.top().step === 1) {
var thisCall = stack.pop();
leftvalue = thisCall.locals.leftvalue;
if (stack.length > 0) {
var nextCall = stack.top();
nextCall.method.apply(nextCall._this, params);
}
}
var rightvalue = visit(this.right, param);
if (needToStop) {
stack.push({
method: AddExpression.prototype.visit,
_this: this,
params: [],
locals: {
leftvalue: leftvalue,
rightvalue: rightvalue
},
step: 2
});
return;
}
if (recoverFromStop && stack.top().step === 2) {
var thisCall = stack.pop();
leftvalue = thisCall.locals.leftvalue;
rightvalue = thisCall.locals.rightvalue;
if (stack.length > 0) {
var nextCall = stack.top();
nextCall.method.apply(nextCall._this, params);
}
}
return leftvalue + rightvalue;
};
Этот метод не меняет основную логику вашего интерпретатора, но вы можете сами убедиться в том, насколько безумным является код для простого синтаксиса A+B.
Наконец я решил использовать генераторы. Генераторы предназначены не для интерактивного изменения выполнения программы, а скорее для ленивой оценки. Но при некотором простом взломе мы можем использовать ленивую оценку наших операторов после получения команды "продолжить".
function interpret(mainNode, param) {
var step;
var gen = visit(mainNode);
do {
step = gen.next();
} while(!step.done);
return step.value;
}
function visit*(node, param) {
return (yield* node.visit(param));
}
AddExpression.prototype.visit = function*(param) {
var leftvalue = yield* visit(this.left, param);
var rightvalue = yield* visit(this.right, param);
return leftvalue + rightvalue;
}
Вот, function*
указывает на то, что мы хотим AddExpression.visit
функция, чтобы быть функцией генератора. yield*
с последующим visit
вызов средства visit
сама функция является рекурсивной функцией генератора.
На первый взгляд, это решение кажется идеальным, но оно страдает от значительного снижения производительности при использовании генераторов ( http://jsperf.com/generator-performance) и того, что оно от es6, и не многие браузеры поддерживают его.
В заключение, у вас есть три разных способа достижения прерываемого исполнения:
- скомпилировать в низкоуровневый код:
- Плюсы: обычная практика, отдельные проблемы, простота оптимизации и обслуживания
- Минусы: слишком много работы
- сохранить стек / загрузить стек:
- Плюсы: относительно быстро, сохраняет логику интерпретации
- Минусы: трудно поддерживать
- генератор:
- Плюсы: простота в обслуживании, отлично сохраняет логику интерпретации
- Минусы: медленный, нуждается в переносе с es6 на es5
Если вы запускаете свой скрипт в браузере и вам нужно дождаться ввода данных пользователем (событие щелчка, событие изменения поля и т. Д.) - тогда вы не можете использовать "while" и "приостановить" его, чтобы дождаться события браузера. Обработчик событий будет вызываться асинхронно, и к этому времени цикл while может даже закончить чтение списка токенов. Возможно, вам следует попробовать прочитать токен по токену и исходя из его значения - вызвать следующее действие.
Попробуйте этот пример: http://jsbin.com/puniquduqa/1/edit?js,console,output
Проделанная здесь работа https://github.com/felixhao28/JSCPP очень полезна, он использует генераторы и работает как в Chrome, так и в Firefox.