0

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

  • 1
    "*something like a REPL*" - why don't you use the existing [repl module](https://nodejs.org/api/repl.html)? – Bergi May 05 '21 at 02:09
  • @Bergi yes, looks good. but a few things about that: 1. I need it to support DOM (e.g. `document.createElement`). 2. it should be a sandbox. **BUT I will add that next to current REPL, as a new part of my extension**. thanks :) – Mohammad Mostafa Dastjerdi May 05 '21 at 05:22

1 Answers1

3

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]')
georg
  • 211,518
  • 52
  • 313
  • 390
  • 1
    your code also has a little problem. if I evaluate an undeclared variable the VM breaks and returns undefined for all variables. I fixed this by covering your code with a `try...catch` as follows => `while (1) { try { expr = yield eval(expr || ""); } catch (error) { expr = yield error; } }` – Mohammad Mostafa Dastjerdi Apr 20 '21 at 16:55
  • back in here with a little problem. I cannot use `let` or `const` in global scope -> `variableName is not defined` I get. but it is ok when doing the same inside a function and there is no problem with that. any fix? – Mohammad Mostafa Dastjerdi Apr 29 '21 at 11:58
  • @MMD: no idea. Asked a new question about that: https://stackoverflow.com/questions/67322922/context-preserving-eval – georg Apr 29 '21 at 18:12