2015-06-07 15 views
6

Ho costruito un interprete C in C# qualche tempo fa e ora ho iniziato a convertirlo in Javascript. Tutto andava bene fino a quando ho capito che js non ha la funzione sleep. Il mio interprete usa un parser ricorsivo e si interrompe per l'input dell'utente mentre è annidato parecchie funzioni in profondità (in C# ho usato waithandle in un secondo thread). Ho guardato setInterval e setTimeout ma sono asincroni/non bloccanti; ovviamente un busywait è fuori discussione e ho visto un'implementazione di timed_queue che ho trovato su SO ma senza fortuna. Ho provato il parser sia nella finestra principale che in un webworker. Sto usando jQuery. Ho un'esperienza limitata con js e sto cercando idee da perseguire. Conosco poco sulla continuazione che passa stile, o rendimento e mi chiedo se potrebbero tenere la chiave. Qui è un po 'tagliato dal codice per mostrare alcuni dei controlscript. Tutte le idee per favore ...javascript loop anonimi in attesa dell'input dell'utente

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 
     } 
    } 
} 

che ho ottenuto questo per lavorare in Firefox utilizzando 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> 

sarei grato se qualcuno che sapeva qualcosa di promesse potrebbe darmi qualche più indizi. La mia applicazione non è di tipo consumer, ma sarebbe bella se funzionasse più di Firefox

+1

In che ambiente * stai correndo? L'assenza di una funzione 'sleep' è principalmente una cosa * ambientale *, non una cosa del linguaggio. –

+0

Dovresti usare le promesse. – SLaks

+0

Puoi spiegare come potrebbe essere utilizzato, ad esempio, qual è lo scopo? – Roberto

risposta

1

Come autore di JSCPP, ho affrontato lo stesso identico problema quando stavo implementando un debugger che si interrompe e continua l'interpretazione del programma al volo. Alla fine decido di utilizzare le funzioni del generatore da es6 ma vorrei condividere il mio processo di pensiero qui.

Il modo comune consiste nel compilare prima il codice di destinazione in un codice byte privo di ricorsività di basso livello. Contrassegni ogni dichiarazione e gestisci tutto il flusso di controllo con unconditional jump e conditional jump. Quindi esegui un interprete di codice byte in cima a quello. Questa è una buona opzione se non ti dispiace che tutto questo lavoro di compilazione sia fatto.

L'altro modo è un flusso di lavoro "salva stack chiamate/stack chiamate". Quando è necessario mettere in pausa l'interpretazione, spingere ricorsivamente tutti gli argomenti e tutte le variabili locali in uno stack personalizzato fino in fondo. Quando è necessario continuare l'esecuzione, si caricano in modo ricorsivo tutti questi argomenti e variabili locali. Il tuo codice sarà trasformata da

AddExpression.prototype.visit = function(param) { 
    var leftVal = visit(this.left, param); 
    var rightVal = visit(this.right, param); 
    return leftVal + rightVal; 
} 

a

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; 
}; 

Questo metodo non altera la logica principale del vostro interprete, ma si può vedere di persona come pazzo il codice è per un semplice A + sintassi B .

Infine, decido di utilizzare i generatori. I generatori non sono pensati per alterare interattivamente l'esecuzione del programma, ma piuttosto per scopi di valutazione lazy. Ma con qualche semplice hacking, possiamo usare pigri-valutare le nostre dichiarazioni dopo aver ricevuto il comando "continua".

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; 
} 

Qui, function* indica vogliamo che la funzione di AddExpression.visit sia la funzione del generatore. yield* seguito da visit mezzo di chiamata visit la funzione stessa è una funzione generatore ricorsivo.

Questa soluzione sembra perfetta a prima vista ma soffre di un enorme calo delle prestazioni utilizzando i generatori (http://jsperf.com/generator-performance) e che proviene da es6 e non molti browser lo supportano.

Per concludere, ci sono tre diversi modi di ottenere l'esecuzione interrompibile:

  1. compilare in codice di basso livello:
    • Pro: una pratica comune, a parte delle preoccupazioni, facile da ottimizzare e mantenere
    • Contro: troppo lavoro
  2. salvare pila/carico stack:
    • Pro: relativamente veloce, conserva l'interpretazione logica
    • Contro: difficile mantenere
  3. generatore:
    • Pro: di facile manutenzione, mantiene perfettamente la logica dell'interpretazione
    • Contro: lento, necessita di es6 per es5 transpiling
2

Se stai eseguendo il tuo script in un browser e devi aspettare l'input dell'utente (clicca evento, evento di cambio campo, ecc.) - quindi non è possibile utilizzare "while" e "pause" per attendere l'evento del browser. Il gestore di eventi verrà invocato in modo asincrono e, a quel punto, il ciclo "while" potrebbe persino terminare la lettura di un elenco di token. Probabilmente dovresti provare a leggere token per token e basandoti sul suo valore - chiama l'azione successiva.

Prova questo esempio: http://jsbin.com/puniquduqa/1/edit?js,console,output