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, и не многие браузеры поддерживают его.

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

  1. скомпилировать в низкоуровневый код:
    • Плюсы: обычная практика, отдельные проблемы, простота оптимизации и обслуживания
    • Минусы: слишком много работы
  2. сохранить стек / загрузить стек:
    • Плюсы: относительно быстро, сохраняет логику интерпретации
    • Минусы: трудно поддерживать
  3. генератор:
    • Плюсы: простота в обслуживании, отлично сохраняет логику интерпретации
    • Минусы: медленный, нуждается в переносе с es6 на es5

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

Попробуйте этот пример: http://jsbin.com/puniquduqa/1/edit?js,console,output

Проделанная здесь работа https://github.com/felixhao28/JSCPP очень полезна, он использует генераторы и работает как в Chrome, так и в Firefox.

Другие вопросы по тегам