Page MenuHomePhorge

bunny.js
No OneTemporary

Size
7 KB
Referenced Files
None
Subscribers
None

bunny.js

const Opcode = {
CONST: 0,
LOAD: 1,
ADD: 2,
CALL: 3,
RETURN: 4,
EXTERN: 5,
HALT: 6
};
const TokenType = {
EOF: 0,
FN: 1,
RETURN: 2,
IDENT: 3,
NUMBER: 4,
LPAREN: 5,
RPAREN: 6,
LBRACE: 7,
RBRACE: 8,
COMMA: 9,
SEMICOLON: 10,
PLUS: 11,
DOT: 12
};
class Lexer {
constructor(src) {
this.src = src;
this.pos = 0;
this.current = this.nextToken();
}
skipWhitespace() {
while (this.pos < this.src.length && /\s/.test(this.src[this.pos])) {
this.pos++;
}
}
nextToken() {
this.skipWhitespace();
if (this.pos >= this.src.length) {
return { type: TokenType.EOF };
}
let c = this.src[this.pos];
if (/[a-zA-Z]/.test(c)) {
let start = this.pos;
while (this.pos < this.src.length && /[a-zA-Z0-9_]/.test(this.src[this.pos])) {
this.pos++;
}
let value = this.src.slice(start, this.pos);
if (value === 'fn') return { type: TokenType.FN, value };
if (value === 'return') return { type: TokenType.RETURN, value };
return { type: TokenType.IDENT, value };
}
if (/[0-9]/.test(c)) {
let start = this.pos;
while (this.pos < this.src.length && /[0-9]/.test(this.src[this.pos])) {
this.pos++;
}
return { type: TokenType.NUMBER, numValue: parseInt(this.src.slice(start, this.pos)) };
}
this.pos++;
const charMap = {
'(': TokenType.LPAREN,
')': TokenType.RPAREN,
'{': TokenType.LBRACE,
'}': TokenType.RBRACE,
',': TokenType.COMMA,
';': TokenType.SEMICOLON,
'+': TokenType.PLUS,
'.': TokenType.DOT
};
return { type: charMap[c] || TokenType.EOF };
}
advance() {
this.current = this.nextToken();
}
}
class VM {
constructor() {
this.code = [];
this.entryPoint = 0;
this.stack = [];
this.callStack = [];
this.functions = [];
this.externs = [
{
name: 'std.io.println',
fn: vm => {
console.log(vm.pop());
vm.push(0);
},
params: 1
},
{
name: 'bunny.squeak',
fn: vm => {
console.log('squeak');
vm.push(0);
},
params: 0
}
];
this.output = [];
}
emit(op, operand = 0) {
this.code.push({ op, operand });
}
push(v) {
this.stack.push(v);
}
pop() {
return this.stack.pop();
}
findParam(func, name) {
if (!func) return -1;
let idx = func.paramNames.indexOf(name);
return idx !== -1 ? func.params - 1 - idx : -1;
}
findFunction(name) {
return this.functions.findIndex(f => f.name === name);
}
findExtern(name) {
return this.externs.findIndex(e => e.name === name);
}
run() {
let pc = this.entryPoint;
let fp = 0;
this.output = [];
const origLog = console.log;
console.log = (...args) => this.output.push(args.join(' '));
while (pc < this.code.length) {
const instr = this.code[pc];
switch (instr.op) {
case Opcode.CONST:
this.push(instr.operand);
pc++;
break;
case Opcode.LOAD:
this.push(this.stack[fp + instr.operand]);
pc++;
break;
case Opcode.ADD: {
let b = this.pop(),
a = this.pop();
this.push(a + b);
pc++;
break;
}
case Opcode.CALL: {
let func = this.functions[instr.operand];
this.callStack.push(pc + 1, fp);
fp = this.stack.length - func.params;
pc = func.addr;
break;
}
case Opcode.RETURN: {
let ret = this.pop();
this.stack.length = fp;
fp = this.callStack.pop();
pc = this.callStack.pop();
this.push(ret);
break;
}
case Opcode.EXTERN:
this.externs[instr.operand].fn(this);
pc++;
break;
case Opcode.HALT:
console.log = origLog;
return;
}
}
console.log = origLog;
}
printBytecode() {
const names = ['CONST', 'LOAD', 'ADD', 'CALL', 'RETURN', 'EXTERN', 'HALT'];
let out = 'bytecode:\n';
this.code.forEach((instr, i) => {
out += `${String(i).padStart(3)}: ${names[instr.op].padEnd(8)} ${instr.operand}\n`;
});
return out;
}
}
function parseQualifiedName(lex) {
if (lex.current.type !== TokenType.IDENT) return null;
let name = lex.current.value;
lex.advance();
while (lex.current.type === TokenType.DOT) {
lex.advance();
if (lex.current.type !== TokenType.IDENT) break;
name += '.' + lex.current.value;
lex.advance();
}
return name;
}
function parseExpr(lex, vm, currentFunc) {
if (lex.current.type === TokenType.NUMBER) {
vm.emit(Opcode.CONST, lex.current.numValue);
lex.advance();
} else if (lex.current.type === TokenType.IDENT) {
let name = parseQualifiedName(lex);
if (lex.current.type === TokenType.LPAREN) {
parseCall(lex, vm, currentFunc, name);
} else {
let offset = vm.findParam(currentFunc, name);
vm.emit(Opcode.LOAD, offset);
}
}
if (lex.current.type === TokenType.PLUS) {
lex.advance();
parseExpr(lex, vm, currentFunc);
vm.emit(Opcode.ADD);
}
}
function parseCall(lex, vm, currentFunc, name) {
lex.advance(); // skip (
if (lex.current.type !== TokenType.RPAREN) {
parseExpr(lex, vm, currentFunc);
while (lex.current.type === TokenType.COMMA) {
lex.advance();
parseExpr(lex, vm, currentFunc);
}
}
lex.advance(); // skip )
let externId = vm.findExtern(name);
if (externId !== -1) {
vm.emit(Opcode.EXTERN, externId);
} else {
vm.emit(Opcode.CALL, vm.findFunction(name));
}
}
function parseFunction(lex, vm) {
lex.advance(); // skip 'fn'
let funcName = lex.current.value;
lex.advance();
lex.advance(); // skip (
let paramNames = [];
if (lex.current.type === TokenType.IDENT) {
paramNames.push(lex.current.value);
lex.advance();
while (lex.current.type === TokenType.COMMA) {
lex.advance();
paramNames.push(lex.current.value);
lex.advance();
}
}
lex.advance(); // skip )
lex.advance(); // skip {
let func = {
name: funcName,
addr: vm.code.length,
params: paramNames.length,
paramNames
};
vm.functions.push(func);
while (lex.current.type !== TokenType.RBRACE) {
if (lex.current.type === TokenType.RETURN) {
lex.advance();
parseExpr(lex, vm, func);
vm.emit(Opcode.RETURN);
lex.advance(); // skip ;
}
}
lex.advance(); // skip }
}
function parseProgram(lex, vm) {
while (lex.current.type === TokenType.FN) {
parseFunction(lex, vm);
}
vm.entryPoint = vm.code.length;
while (lex.current.type !== TokenType.EOF) {
if (lex.current.type === TokenType.IDENT) {
let name = parseQualifiedName(lex);
if (lex.current.type === TokenType.LPAREN) {
parseCall(lex, vm, null, name);
if (lex.current.type === TokenType.SEMICOLON) lex.advance();
}
} else {
lex.advance();
}
}
vm.emit(Opcode.HALT);
}
function compile(source) {
const vm = new VM();
const lex = new Lexer(source);
parseProgram(lex, vm);
return vm;
}
const source = `
fn add(a, b) {
return a + b;
}
bunny.squeak();
std.io.println(add(5, 10));
`.trim();
console.log('Source:\n');
console.log(source);
console.log('\n' + '='.repeat(40) + '\n');
const vm = compile(source);
console.log(vm.printBytecode());
console.log('Output:');
vm.run();
vm.output.forEach(line => console.log(line));

File Metadata

Mime Type
text/plain
Expires
Sun, May 3, 9:25 AM (1 d, 21 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
518032
Default Alt Text
bunny.js (7 KB)

Event Timeline