English | 简体中文
A simple JavaScript engine written in JavaScript, built for learning compiler principles and interpreter design.
- Lexer: Converts source code into a token stream
- Parser: Converts a token stream into an Abstract Syntax Tree (AST)
- Interpreter: Evaluates the AST and returns a result
- Supported language features:
- Variable declarations (
let,const,var) - Primitive data types (number, string, boolean, null, undefined)
- Arithmetic operators (
+,-,*,/,%) - Comparison operators (
==,!=,<,>,<=,>=) - Function declarations and calls
- Conditional statements (
if/else) - Loop statements (
for,while) - Array and object literals
- Member access (
obj.prop,obj[prop]) - Scope and closures
- Variable declarations (
├── src/
│ ├── lexer.js # Lexer
│ ├── parser.js # Parser
│ ├── ast.js # AST node definitions
│ ├── interpreter.js # Interpreter
│ └── index.js # Main engine file
├── test/
│ └── test-lexer-parser.js # Tests
├── demo/
│ └── demo.js # Demo script
└── package.json
npm run demonpm testimport { SimpleJSEngine } from './src/index.js';
const engine = new SimpleJSEngine();
// Execute JavaScript code
const result = engine.run(`
function fibonacci(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
fibonacci(10);
`);
console.log(result.result); // 55engine.run(code)— Execute JavaScript source codeengine.tokenize(code)— Perform lexical analysis onlyengine.parse(code)— Perform lexical + syntax analysisengine.printAST(code)— Print the AST structureengine.getASTJSON(code)— Get the AST as a JSON string
let x = 10;
let y = 20;
let result = x + y * 2; // 50function add(a, b) {
return a + b;
}
let sum = add(5, 3); // 8let score = 85;
if (score >= 80) {
console.log("Excellent");
} else {
console.log("Keep going");
}let sum = 0;
for (let i = 1; i <= 10; i = i + 1) { // ++ not supported yet
sum = sum + i;
}
console.log(sum); // 55let arr = [1, 2, 3, 4, 5];
let obj = {
name: "Alice",
age: 25
};
console.log(arr[0]); // 1
console.log(obj.name); // "Alice"function createCounter() {
let count = 0;
return function() {
count = count + 1;
return count;
};
}
let counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2Converts source code into a token stream:
"let x = 42;" → [LET, IDENTIFIER(x), ASSIGN, NUMBER(42), SEMICOLON]
Converts the token stream into an Abstract Syntax Tree:
[LET, IDENTIFIER(x), ASSIGN, NUMBER(42)] → VariableDeclaration AST Node
Walks the AST and executes each node:
VariableDeclaration → define variable x = 42 in the current environment
-
Compiler fundamentals
- How lexical analysis works
- Syntax analysis and AST construction
- Recursive-descent parsers
-
Interpreter design
- Tree-walking interpreters
- Environment and scope management
- Function calls and closure implementation
-
JavaScript language internals
- Hoisting and scope
- Functions as first-class citizens
- Dynamic type system
-
Add more language features
try/catchexception handling- Arrow functions
() => {} - Template literals
`Hello ${name}` - Destructuring assignment
-
Performance improvements
- Bytecode compilation
- Simple JIT compilation
- Garbage collection optimisations
-
Tooling
- Debugger support
- Source maps
- Profiler
-
Standard library
- More built-in objects (Array, Object, Math)
- Async support (Promise, async/await)
- Module system
- Crafting Interpreters — an excellent guide to interpreter design
- ECMA-262 — the JavaScript language specification
- Babel Parser — a production-grade JavaScript parser