- Notifications
You must be signed in to change notification settings - Fork76
A simple C++ interpreter written in JavaScript
License
felixhao28/JSCPP
Folders and files
| Name | Name | Last commit message | Last commit date | |
|---|---|---|---|---|
Repository files navigation
This is a simple C++ interpreter written in JavaScript.
Try it outon github.io!
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 thatsizeof int produces4, because on Arduino Uno, anint is a 2-byte value.
Currently, it is mainly for educational uses for a MOOC course I am running (and fun).
- NodeJS version >= 0.11
- A modern browser
Installation
npm install JSCPPor (to use lastest cutting-edge version or to contribute)
git clone https://github.com/felixhao28/JSCPP.gitcd JSCPPnpm 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
varJSCPP=require("JSCPP");varcode="#include <iostream>"+"using namespace std;"+"int main() {"+" int a;"+" cin >> a;"+" cout << a << endl;"+" return 0;"+"}";varinput="4321";varexitcode=JSCPP.run(code,input);console.info("program exited with code "+exitcode);
Seedemo/example.coffee for example.
Main API:JSCPP.run(code, input, config):
- code:string The C++ source code to be interpreted.
- input:string The text to be sent into standard input (can be overriden with
config.stdio). - config:<optional> JSCPPConfig The configuration object. All configuration items have default value. So you only need to set the ones you want different from the defaults.
specifiers:<optional> string[]
- Allowed specifiers. By default all specifiers are allowed.
- Default:
["const", "inline", "_stdcall", "extern", "static", "auto", "register"]
charTypes:<optional> string[]
- Allowed char types. By default all char types are allowed.
- Default:
["char", "signed char", "unsigned char", "wchar_t", "unsigned wchar_t", "char16_t", "unsigned char16_t", "char32_t", "unsigned char32_t"]
intTypes:<optional> string[]
- Allowed integer types. By default all integer types are allowed.
- Default:
["short", "short int", "signed short", "signed short int", "unsigned short", "unsigned short int", "int", "signed int", "unsigned", "unsigned int", "long", "long int", "long int", "signed long", "signed long int", "unsigned long", "unsigned long int", "long long", "long long int", "long long int", "signed long long", "signed long long int", "unsigned long long", "unsigned long long int", "bool"]
limits:<optional> {[type: string]: { max: number, min: number, bytes: number}}
- The minimal and the maximum value on number types. You can just set a subset of all the types, and the unset types will use the default limits.
- Default:
{"char":{max:0x7f,min:0x00,bytes:1},"signed char":{max:0x7f,min:-0x80,bytes:1},"unsigned char":{max:0xff,min:0x00,bytes:1},"wchar_t":{max:0x7fffffff,min:-0x80000000,bytes:4},"unsigned wchar_t":{max:0xffffffff,min:0x00000000,bytes:4},"char16_t":{max:0x7fff,min:-0x8000,bytes:4},"unsigned char16_t":{max:0xffff,min:0x0000,bytes:4},"char32_t":{max:0x7fffffff,min:-0x80000000,bytes:4},"unsigned char32_t":{max:0xffffffff,min:0x00000000,bytes:4},"short":{max:0x7fff,min:-0x8000,bytes:2},"unsigned short":{max:0xffff,min:0x0000,bytes:2},"int":{max:0x7fffffff,min:-0x80000000,bytes:4},"unsigned":{max:0xffffffff,min:0x00000000,bytes:4},"long":{max:0x7fffffff,min:-0x80000000,bytes:4},"unsigned long":{max:0xffffffff,min:0x00000000,bytes:4},"long long":{max:0x7fffffffffffffff,min:-0x8000000000000000,bytes:8},"unsigned long long":{max:0xffffffffffffffff,min:0x0000000000000000,bytes:8},"float":{max:3.40282346638529e+038,min:-3.40282346638529e+038,bytes:4},"double":{max:1.79769313486232e+308,min:-1.79769313486232e+308,bytes:8},"pointer":{max:undefined,min:undefined,bytes:4},"bool":{max:1,min:0,bytes:1}}
includes:<optional> { [fileName: string]: IncludeModule }
- Define additional include files. This is extremely useful if you are defining new types, variables or functions to be used in the C++ source code.
IncludeModuleis an object that has aload(rt: CRuntime): voidmember function. For example,will register a global function equivalent to the following,before interpreting the source code:{"myheader.h":{load:function(rt){rt.regFunc(function(rt,_this,x,y){varfirstValue=x.v;varsecondValue=y.v;varreturnType=x.t;returnrt.val(returnType,firstValue+secondValue);},"global","myfunction",[rt.intTypeLiteral,rt.intTypeLiteral],rt.intTypeLiteral);}}}
so that user C++ code like this can be interpreted:// C++ codeintmyfunction(int x,int y) {return x + y;}
For more examples on writing a custom// C++ code#include"myheader.h"intmain() {returnmyfunction(1,2);// will return 3}
IncludeModule, including how to properly use types, values and variables, please take a look at the files insidesrc/includes. For custom classes (experimental), please take a look atsrc/includes/dummy_class_foo.ts andtest/class_basics.cpp.
loadedLibraries:<optional> string[]
- loadedLibraries keeps track of loaded headers. It can also be used to skip loading certain headers if given initial value.
stdio:<optional if in NodeJS> string[]
{ drain?: () => string; write: (s: string) => void; }- This controls the behavior of standard input/output. This isrequired if you are running JSCPP on webpages, since the default behavior of writing to standard output stream is to print to the console, which is invisible to end users.
- drain:<optional> () => string
- Executed whenever the standard input buffer needs new content. The returned string will be concatenated to the existing buffer. If
drainis set,drainwill be favored overinput. This is useful if the standard input is extremely large or is not immediately available at the start but only available later during the interpretation, for example, debugging. You don't normally need to setdrain.
- Executed whenever the standard input buffer needs new content. The returned string will be concatenated to the existing buffer. If
- write:(s: string) => void
- Write the string
sto standard output stream. By default it is implemeted as(s) => process.stdout.write(s);. You need to override this if you want to capture the console output and do something with it.
- Write the string
unsigned_overflow:<optional> "error" (default) | "warn" | "ignore"
- Overflowing an unsigned type is an undefined behavior. This configuration controls what to do if a such overflow happens.
- "error": immediately throw an exception.
- "warn": print a warning to standard error stream.
- "ignore": ignore the overflow and carry on interpreting.
- Overflowing an unsigned type is an undefined behavior. This configuration controls what to do if a such overflow happens.
maxTimeout:<optional> number
- If set, JSCPP will throw an exception if the milliseconds since the beginnig of execution exceeds
maxTimeout. This is not used in debug mode.
- If set, JSCPP will throw an exception if the milliseconds since the beginnig of execution exceeds
debug:<optional> boolean
- If
false(default), JSCPP will run normally and the return value ofJSCPP.runwill be the exit code of the C++ program. - If
true, JSCPP will enter debug mode, break on the first AST node and an debugger instance will be immediately returned instead. Please refer to the "Usingdebugger" part of this document for further details.
- If
Usingdebugger
As of 2.0.0, there is a simple but functional real debugger available.
A list of debugger API:
- methods
- debugger.next(): one step further
- debugger.continue(): continue until breakpoint
- debugger.nextNode(): the AST node to be executed
- sLine
- sColumn
- sOffset
- eLine
- eColumn
- eOffset
- debugger.nextLine()
- debugger.type(typeName)
- debugger.variable()
- debugger.variable(variableName)
- properties
- src: preprocessed source
- prevNode: previous AST node
- done
- conditions
- stopConditions
- rt: the internal runtime instance
- gen: the internal generator
varJSCPP=require("JSCPP")varmydebugger=JSCPP.run(code,input,{debug:true});// continue to the next interpreting operationvardone=mydebugger.next();// if you have an active breakpoint condition, you can just continuevardone=mydebugger.continue();// by default, debugger pauses at every new line, but you can change itmydebugger.setStopConditions({isStatement:truepositionChanged:truelineChanged: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 10mydebugger.setCondition("line10",function(prevNode,nextNode){if(nextNode.sLine===10){// disable itself so that it only triggers once on line 10mydebugger.disableCondition("line10");returntrue;}else{returnfalse;}});// then enable itmydebugger.enableCondition("line10");// we need to explicitly use "false" because exit code can be 0if(done!==false){console.log("program exited with code "+done.v);}// the AST node to be executed nextvars=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 nextvarnextLine=mydebugger.nextLine();// it is essentially same asnextLine=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 typemydebugger.type("int");// examine the value of variable "a"mydebugger.variable("a");// or list all local variablesmydebugger.variable();
A full interactive example is available indemo/debug.coffee. Usenode -harmony demo/debug A+B -debug to debug "A+B" test.
There should be a newest version ofJSCPP.js orJSCPP.es5.js indist ready for you. If not, usenpm run build to generate one.
Then you can add it to your html. The exported global name for this package is "JSCPP".
<scriptsrc="JSCPP.es5.min.js"></script><scripttype="text/javascript">varcode="#include <iostream>"+"using namespace std;"+"int main() {"+" int a;"+" cin >> a;"+" cout << a << endl;"+" return 0;"+"}";varinput="4321";varoutput="";varconfig={stdio:{write:function(s){output+=s;}},unsigned_overflow:"error"// can be "error"(default), "warn" or "ignore"};varexitCode=JSCPP.run(code,input,config);alert(output+"\nprogram exited with code "+exitCode);</script>
If you do not provide a customizedwrite method forstdio configuration, console output will not be correctly shown. Seedemo/demo.html for example.
There are two Helper classes to make JSCPP easier to run in WebWorkers. One isJSCPP.WebWorkerHelper in an old callback style andJSCPP.AsyncWebWorkerHelper in a modern Promise/async-await style.
<scriptsrc="JSCPP.es5.min.js"></script><scripttype="text/javascript">varhelper=newJSCPP.WebWorkerHelper("./JSCPP.es5.min.js");// it is a classvaroutput="";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>
<scriptsrc="JSCPP.es5.min.js"></script><scripttype="text/javascript">asyncfunctionasyncWrapper(){varhelper=newJSCPP.AsyncWebWorkerHelper("./JSCPP.es5.min.js");// it is a classvaroutput="";try{varreturnCode=awaithelper.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 insrc/index.js, and a test page is available indist/index.html.
npm run test- (Most) operators
- Primitive types
- Variables
- Arrays
Multidimensional array with initializers.
- Pointers
- If...else control flow
- Switch...case control flow
Declarations inside switch block.
- For loop
- While loop
- Do...while loop
- Functions
- Variable scopes
- Preprocessor directives
- Macro
- Include
- Goto statements
- Object-oriented features
- Namespaces
- Multiple files support
If you want to run C++ programs effciently, compile your C++ code toLLVM-bitcode and then useEmscripten.
See current progress inincludes folder.
- iostream (only cin and cout and endl)
- cmath
- cctype
- cstring
- cstdio (partial)
- cstdlib (partial)
Post it onIssues.
v2.0.10
- New
- Added experimental support for passing a function pointer as parameter.
- Fix
- Fixed
strcmpcomparing two identical strings.
- Fixed
- New
v2.0.9 (2021.1.27)
- New
- WebWorker support (check
dist/index.htmlfor examples).
- WebWorker support (check
- Fix
- Fixed an error message
- New
v2.0.7
- New
- Support some basic class usages (see
test\class_basics.cpp). - Uses special uninitialized numeric and poitner value.
- Adds an "unsigned_overflow" option to treat unsigned numeric overflow as "error"(default), "warn" or "ignore". Overflow on signed types will always throw an error.
- Support some basic class usages (see
- Fix
- Fixed some string-related methods.
- Fixed
powin<cmath>, thanks toClemenard. - Fixed multi-dimensional array declaration with initializers, thanks torodrigorgs.
- New
v2.0.6 (9.29)
- New
- Implemented following methods from cstdlib, thanks tovictorrseloy:
int getchar(void)char *gets(char *str)int putchar(int char)int puts(const char *str)int scanf(const char *format, ...)int sscanf(const char *str, const char *format, ...)
- Implemented following methods from cstdlib, thanks tovictorrseloy:
- Fix
- Fixed printf function
- Dev
- Update dependencies to latest
- New
v2.0.5 (4.6)
- New
- ctime library
- Fix
- Supports functions returning pointer type
- New
v2.0.4 (12.10)
- New
- Function default arguments support
- Fix
- cin.get now works as expected
- Dev
- Update dev dependencies
- New
v2.0.3 (10.15)
- New
- Function pointer support
- typedef support
- Dev
- Testing now uses
test/test.coffeedirectly - Update PEG.js from 0.8.0 to 0.9.0
- Please use NodeJS v4.0.0+
- Testing now uses
- New
v2.0.2 (7.31)
- New
- Wide char support (русский язык)
- Fix
- Truncating negative number to positive
- Error message when overflow
- Error when using function prototypes
- Dev
- test.json -> test.yaml
- Transpile to es5 properly (dist/JSCPP.es5.js)
- Minified version (dist/JSCPP.es5.min.js)
- Online site uses minified es5 version now
- A few dependencies have been updated
- Added linux helper for running and debugging (bin/run & bin/debug)
- New
v2.0.1 (6.24)
- Fix
- Debugger variable scope issue
- Readme example
- An issue on Chrome Canary
- Integer type promotion issue
- Many small fixes
- Fix
v2.0.0 (4.11)
- New
- Real debugger!
- Change
- API: Now
JSCPP.runis all you need - Runtime: The project uses es6, please use V8 with harmony flag
- Deprecated: Removed old legacy profiling-replay debugging
- Misc: Many other small changes
- API: Now
- Dev
- Major refactoring on interpreter using es6
- New
v1.1.1 (4.3)
- Split debugger from example
- (dev-side) Grunt only watches newer
- Fix debug prev command
v1.1.0 (4.2)
- Fixed array initialization with 0 issue
- Added support for reading string with cin
- Member function should not be registered globally
- Added new tests
- Basic debugging support
v1.0.3 (4.1)
- (dev-side) Fix dev-dependency on coffee-script.
- (dev-side) Grunt watches.
- (dev-side) Port to coffeescript
- (dev-side) Refactoring
- (dev-side) Reworked testing, now all tests are defined in
test.json - Fixed a bug related to a.push(b).concat(c) in syntax parser (#1).
- Added new tests
v1.0.2 (3.31)
- New examples.
- Update README.
- Renamed runtime and interpreter to start with upper case.
- (dev-side) Grunt.
v1.0.1 (3.31)
- This release is a mistake.
v1.0.0 (2015.3.31)
- Formal release of this project.
About
A simple C++ interpreter written in JavaScript
Resources
License
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Releases
Packages0
Uh oh!
There was an error while loading.Please reload this page.
Contributors8
Uh oh!
There was an error while loading.Please reload this page.