Search code examples
javascriptgoogle-chrome-devtoolseval

JavaScript: do all evaluations in one vm


I'm creating a custom JavaScript console that I expect to work exactly like the console in dev tools. (or … something like a REPL) https://github.com/MohammadMD1383/js-interactive

I get user inputs one by one and evaluate them. eval(userInput)

the problem is with defining variables. I noticed that the eval function uses a new VM each time, so the declaration is in a separate VM than a call to the variable. so it causes the error someVarName is not defined

the sample of my code:

button.onclick = () => {
    evaluateMyExpression(textarea.value);
};

function evaluateMyExpression(code) {
    let result = eval(code);
    // do something else …
}

enter image description here


Solution

  • You can use a generator function and "feed" expressions to it as they come in. Since the generator retains its context, all variables from previous lines will be there when you evaluate the next one:

    function* VM(expr) {
        while (1) {
            try {
                expr = yield eval(expr || '')
            } catch(err) {
                expr = yield err
            }
        }
    }
    
    
    vm = VM()
    vm.next()
    
    function evaluate(line) {
        let result = vm.next(line).value
        if (result instanceof Error)
            console.log(line, 'ERROR', result.message)
        else
            console.log(line, 'result', result)
    }
    
    evaluate('var a = 55')
    evaluate('a')
    evaluate('var b = 5')
    evaluate('c = a + b')
    evaluate('foobar--')
    evaluate('c++')
    evaluate('[a, b, c]')

    As suggested in another thread, a "self-similar" eval will also preserve block-scoping variables:

    let _EVAL = e => eval(`_EVAL=${_EVAL}; undefined; ${e}`)
    
    function evaluate(line) {
        try {
          let result = _EVAL(line)
          console.log(line, '==>', result)
        } catch (err) {
            console.log(line, '==>', 'ERROR', err.message)
        }
    }
    
    evaluate('var a = 55')
    evaluate('a')
    evaluate('foobar--')
    evaluate('let x = 10')
    evaluate('const y = 20')
    evaluate('[a, x, y]')