felixhao28 / JSCPP

A simple C++ interpreter written in JavaScript
https://felixhao28.github.io/JSCPP/
MIT License
863 stars 71 forks source link

JSCPP

This is a simple C++ interpreter written in JavaScript.

Try it out on github.io!

Travis Build Status npm version Dependency Status devDependency Status

Purpose of the project

As far as I know, every public online C++ excuting environment requires backend servers to compile and run the produced executable. A portable and lightweight interpreter that can be run in browsers can be a fine substitute for those who do not intend to pay for such services.

I also want to make a strict interpreter. The reason being C++ has too many undefined and platform-dependent behaviors and popular C++ compilers tend to be an "over-caring mother" who tries to ignore or even justify the undocumented usages. The abuse of them should be avoided as much as possible IMO. For example, I do not want my students to take it as guaranteed that sizeof int produces 4, because on Arduino Uno, an int is a 2-byte value.

Currently, it is mainly for educational uses for a MOOC course I am running (and fun).

Prerequisites

How to use

Installation

npm install JSCPP

or (to use lastest cutting-edge version or to contribute)

git clone https://github.com/felixhao28/JSCPP.git
cd JSCPP
npm install .

Or you can download the minified single JS file directly from here:

https://raw.githubusercontent.com/felixhao28/JSCPP/gh-pages/dist/JSCPP.es5.min.js

With NodeJS

var JSCPP = require("JSCPP");
var code =    "#include <iostream>"
            + "using namespace std;"
            + "int main() {"
            + "    int a;"
            + "    cin >> a;"
            + "    cout << a << endl;"
            + "    return 0;"
            + "}"
;
var input = "4321";
var exitcode = JSCPP.run(code, input);
console.info("program exited with code " + exitcode);

See demo/example.coffee for example.

Main API: JSCPP.run(code, input, config):

Using debugger

As of 2.0.0, there is a simple but functional real debugger available.

A list of debugger API:

var JSCPP = require("JSCPP")
var mydebugger = JSCPP.run(code, input, { debug: true });
// continue to the next interpreting operation
var done = mydebugger.next();
// if you have an active breakpoint condition, you can just continue
var done = mydebugger.continue();
// by default, debugger pauses at every new line, but you can change it
mydebugger.setStopConditions({
    isStatement: true
    positionChanged: true
    lineChanged: false
});
// so that debugger only stops at a statement of a new position
// or you can add your own condition, i.e. stops at line 10
mydebugger.setCondition("line10", function (prevNode, nextNode) {
    if (nextNode.sLine === 10) {
        // disable itself so that it only triggers once on line 10
        mydebugger.disableCondition("line10");
        return true;
    } else {
        return false;
    }
});
// then enable it
mydebugger.enableCondition("line10");
// we need to explicitly use "false" because exit code can be 0
if (done !== false) {
    console.log("program exited with code " + done.v);
}
// the AST node to be executed next
var s = mydebugger.nextNode();
// sometimes a breakpoint can be set without a statement to be executed next,
// i.e. entering a function call.
while ((s = mydebugger.nextNode()) == null) {
    mydebugger.next();
}
// the content of the statement to be executed next
var nextLine = mydebugger.nextLine();
// it is essentially same as
nextLine = mydebugger.getSource().slice(s.sOffset, s.eOffset).trim()

console.log("from " + s.sLine + ":" + s.sColumn + "(" + s.sOffset + ")");
console.log("to " + s.eLine + ":" + s.eColumn + "(" + s.eOffset + ")");
console.log("==> " + nextLine);
// examine the internal registry for a type
mydebugger.type("int");
// examine the value of variable "a"
mydebugger.variable("a");
// or list all local variables
mydebugger.variable();

A full interactive example is available in demo/debug.coffee. Use node -harmony demo/debug A+B -debug to debug "A+B" test.

With a browser

There should be a newest version of JSCPP.js or JSCPP.es5.js in dist ready for you. If not, use npm run build to generate one.

Then you can add it to your html. The exported global name for this package is "JSCPP".

<script src="https://github.com/felixhao28/JSCPP/raw/master/JSCPP.es5.min.js"></script>
<script type="text/javascript">
    var code =  "#include <iostream>"+
                "using namespace std;"+
                "int main() {"+
                "    int a;"+
                "    cin >> a;"+
                "    cout << a << endl;"+
                "    return 0;"+
                "}"
    ;
    var input = "4321";
    var output = "";
    var config = {
        stdio: {
            write: function(s) {
                output += s;
            }
        },
        unsigned_overflow: "error" // can be "error"(default), "warn" or "ignore"
    };
    var exitCode = JSCPP.run(code, input, config);
    alert(output + "\nprogram exited with code " + exitCode);
</script>

If you do not provide a customized write method for stdio configuration, console output will not be correctly shown. See demo/demo.html for example.

Running in WebWorker

There are two Helper classes to make JSCPP easier to run in WebWorkers. One is JSCPP.WebWorkerHelper in an old callback style and JSCPP.AsyncWebWorkerHelper in a modern Promise/async-await style.

<script src="https://github.com/felixhao28/JSCPP/raw/master/JSCPP.es5.min.js"></script>
<script type="text/javascript">
    var helper = new JSCPP.WebWorkerHelper("./JSCPP.es5.min.js"); // it is a class
    var output = "";
    helper.run(`#include <iostream>
        using namespace std;
        int main() {
        int a;
        cin >> a;
        a += 7;
        cout << a*10 << endl;
        return 0;
    }`, "5", {
        stdio: {
            write: function(s) {
                output += s;
            }
        }
    }, function (err, returnCode) {
        if (err) {
            alert("An error occurred: " + (err.message || err));
        } else {
            alert("Program exited with code " + returnCode);
        }
    });

    helper.worker.terminate(); // directly control the Worker instance
</script>
<script src="https://github.com/felixhao28/JSCPP/raw/master/JSCPP.es5.min.js"></script>
<script type="text/javascript">
    async function asyncWrapper() {
        var helper = new JSCPP.AsyncWebWorkerHelper("./JSCPP.es5.min.js"); // it is a class
        var output = "";
        try {
            var returnCode = await helper.run(`#include <iostream>
                using namespace std;
                int main() {
                int a;
                cin >> a;
                a += 7;
                cout << a*10 << endl;
                return 0;
            }`, "5", {
                stdio: {
                    write: function(s) {
                        output += s;
                    }
                }
            });
            alert("Program exited with code " + returnCode);
        } catch (err) {
            alert("An error occurred: " + (err.message || err));
        }
        helper.worker.terminate(); // directly control the Worker instance
    }
    asyncWrapper();
</script>

The helper classes are implemented in src/index.js, and a test page is available in dist/index.html.

Run tests

npm run test

Q&A

Which features are implemented?

Which notable features are not implemented yet?

How is the performance?

If you want to run C++ programs effciently, compile your C++ code to LLVM-bitcode and then use Emscripten.

Which libraries are supported?

See current progress in includes folder.

Bug report? Feedback?

Post it on Issues.

Changelog