jester
Advanced tools
Comparing version 0.2.2 to 0.2.3
module.exports = { | ||
createContext : require('./lib/context').createContext, | ||
createVM : require('./lib/vm').createVM | ||
createVM : require('./lib/vm').createVM, | ||
constants : require('./lib/constants'), | ||
opcodeMeta : require('./lib/vm').opcodeMeta | ||
}; |
@@ -7,3 +7,4 @@ "use strict"; | ||
[ 'DEF', | ||
[ 'MODULE', | ||
'DEF', | ||
'IF', | ||
@@ -18,6 +19,8 @@ 'WHILE', | ||
'IDENT', | ||
'GLOBAL_IDENT', | ||
'CALL', | ||
'BIN_OP', | ||
'UN_OP', | ||
'SPAWN' | ||
'SPAWN', | ||
'EVAL' | ||
].forEach(function(ast) { | ||
@@ -28,2 +31,2 @@ var astNodeId = nextAstNode++; | ||
module.exports = AST_NODES; | ||
module.exports = AST_NODES; |
"use strict"; | ||
var T = require('./tokens').tokens, | ||
A = require('./ast_nodes'), | ||
ops = require('./opcodes'), | ||
makeFunction = require('./types').makeFunction, | ||
makeColor = require('./types').makeColor; | ||
var T = require('./tokens').tokens, | ||
A = require('./ast_nodes'), | ||
ops = require('./opcodes'), | ||
makeFunction = require('./types').makeFunction, | ||
makeColor = require('./types').makeColor; | ||
function assert(condition, message) { | ||
if (!condition) throw (message || "assertion failed"); | ||
if (!condition) throw (message || "assertion failed"); | ||
} | ||
@@ -26,296 +26,349 @@ | ||
function Compiler() { | ||
this._fn = null; | ||
this._fn = null; | ||
} | ||
Compiler.prototype = { | ||
emit: function(opcode, line) { | ||
this._fn.code.push(opcode); | ||
this._fn.sourceMap.push(line || 0); | ||
}, | ||
compileFnDef: function(ast) { | ||
var oldFn = this._fn; | ||
var newFn = makeFunction(); | ||
newFn.name = ast.name; | ||
this._fn = newFn; | ||
var params = ast.parameters; | ||
for (var i = 0; i < params.length; ++i) { | ||
newFn.slotForLocal(params[i]); | ||
newFn.minArgs++; | ||
newFn.maxArgs++; | ||
} | ||
this.compileFunctionBody(ast.body); | ||
this._env[ast.name] = newFn; | ||
this._fn = oldFn; | ||
return newFn; | ||
}, | ||
compileAssign: function(ast) { | ||
var slot = this._fn.slotForLocal(ast.left.name); | ||
this.compileExpression(ast.right); | ||
this.emit(ops.SETL | (slot << 8), ast.line); | ||
}, | ||
compileIf: function(ast) { | ||
var ix = 0, | ||
firstAbs = null, | ||
lastAbs = null, | ||
clauses = ast.clauses; | ||
while (ix < clauses.length) { | ||
var clause = clauses[ix]; | ||
if (typeof clause.condition !== 'undefined') { | ||
this.compileExpression(clause.condition); | ||
var failJump = this._fn.code.length; | ||
this.emit(ops.JMPF); | ||
this.compileStatements(clause.body); | ||
if (firstAbs === null) { | ||
firstAbs = this._fn.code.length; | ||
lastAbs = this._fn.code.length; | ||
this.emit(0); | ||
} else { | ||
var tmp = this._fn.code.length; | ||
this.emit(lastAbs); // hack - stash pointer to last jump point so we can backtrack | ||
lastAbs = tmp; | ||
} | ||
this._fn.code[failJump] = ops.JMPF | ((this._fn.code.length - failJump - 1) << 8); | ||
} else { | ||
this.compileStatements(clause.body); | ||
} | ||
ix++; | ||
} | ||
var jmpOp = ops.JMPA | (this._fn.code.length << 8), | ||
currAbs = lastAbs; | ||
do { | ||
var tmp = this._fn.code[currAbs]; | ||
this._fn.code[currAbs] = jmpOp; | ||
if (currAbs == firstAbs) { | ||
break; | ||
} else { | ||
currAbs = tmp; | ||
} | ||
} while (true); | ||
}, | ||
compileWhile: function(ast) { | ||
var loopStart = this._fn.code.length; | ||
this.compileExpression(ast.condition); | ||
var failJump = this._fn.code.length; | ||
this.emit(ops.JMPF); | ||
this.compileStatements(ast.body); | ||
this.emit(ops.JMPA | (loopStart << 8)); | ||
this._fn.code[failJump] = ops.JMPF | ((this._fn.code.length - failJump - 1) << 8); | ||
emit: function(opcode, line) { | ||
this._fn.code.push(opcode); | ||
this._fn.sourceMap.push(line || 0); | ||
}, | ||
compileFnDef: function(ast) { | ||
var oldFn = this._fn; | ||
var newFn = makeFunction(); | ||
newFn.name = ast.name; | ||
this._fn = newFn; | ||
var params = ast.parameters; | ||
for (var i = 0; i < params.length; ++i) { | ||
newFn.slotForLocal(params[i]); | ||
newFn.minArgs++; | ||
newFn.maxArgs++; | ||
} | ||
this.compileFunctionBody(ast.body); | ||
this._env[ast.name] = newFn; | ||
this._fn = oldFn; | ||
return newFn; | ||
}, | ||
compileAssign: function(ast) { | ||
}, | ||
compileLoop: function(ast) { | ||
var loopStart = this._fn.code.length; | ||
this.compileStatements(ast.body); | ||
this.emit(ops.YIELD, ast.line); | ||
this.emit(ops.JMPA | (loopStart << 8)); | ||
}, | ||
compileReturn: function(ast) { | ||
if (typeof ast.returnValue !== 'undefined') { | ||
this.compileExpression(ast.returnValue); | ||
} else { | ||
this.emit(ops.PUSHF); // TODO: should probably push undefined/null or whatever | ||
} | ||
this.emit(ops.RET, ast.line); | ||
}, | ||
compileYield: function(ast) { | ||
this.emit(ops.YIELD, ast.line); | ||
}, | ||
compileCall: function(ast) { | ||
var args = ast.args; | ||
if (args.length > 255) { | ||
throw "compile error - max args per function call (255) exceeded"; | ||
} | ||
for (var i = 0; i < args.length; ++i) { | ||
this.compileExpression(args[i]); | ||
} | ||
this.emit(ops.CALL | (args.length << 8) | (this._fn.slotForFunctionCall(ast.fn.name) << 16), ast.line); | ||
}, | ||
if (ast.left.type === A.IDENT) { | ||
compileSpawn: function(ast) { | ||
var slot = this._fn.slotForLocal(ast.left.name); | ||
this.compileExpression(ast.right); | ||
this.emit(ops.SETL | (slot << 8), ast.line); | ||
} else if (ast.left.type === A.GLOBAL_IDENT) { | ||
var args = ast.args; | ||
if (args.length > 255) { | ||
throw "compile error - max args per spawn (255) exceeded"; | ||
} | ||
var slot = this._fn.slotForName(ast.left.name); | ||
this.compileExpression(ast.right); | ||
this.emit(ops.SETG | (slot << 16), ast.line); | ||
for (var i = 0; i < args.length; ++i) { | ||
this.compileExpression(args[i]); | ||
} | ||
} else { | ||
this.emit(ops.SPAWN | (args.length << 8) | (this._fn.slotForFunctionCall(ast.fn.name) << 16), ast.line); | ||
throw "compile error: invalid left hand in assignment"; | ||
}, | ||
compileLogicalAnd: function(ast) { | ||
this.compileExpression(ast.left); | ||
var bailJump = this._fn.code.length; | ||
this.emit(0); | ||
this.compileExpression(ast.right); | ||
this._fn.code[bailJump] = ops.JMPF_OP | ((this._fn.code.length - bailJump - 1) << 8); | ||
} | ||
}, | ||
compileLogicalOr: function(ast) { | ||
this.compileExpression(ast.left); | ||
var bailJump = this._fn.code.length; | ||
this.emit(0); | ||
this.compileExpression(ast.right); | ||
this._fn.code[bailJump] = ops.JMPT_OP | ((this._fn.code.length - bailJump - 1) << 8); | ||
}, | ||
compileExpression: function(ast) { | ||
if (ast === true) { | ||
this.emit(ops.PUSHT, ast.line); | ||
} else if (ast === false) { | ||
this.emit(ops.PUSHF, ast.line); | ||
} else if (typeof ast == 'number' || typeof ast == 'string') { | ||
this.emit(ops.PUSHC | (this._fn.slotForConstant(ast) << 8), ast.line); | ||
} else { | ||
switch (ast.type) { | ||
case A.COLOR: | ||
var color = makeColor(ast.r, ast.g, ast.b, ast.a); | ||
this.emit(ops.PUSHC | (this._fn.slotForConstant(color) << 8), ast.line); | ||
break; | ||
case A.ASSIGN: | ||
this.compileAssign(ast); | ||
break; | ||
case A.TRACE: | ||
this.emit(ops.TRACE, ast.line); | ||
break; | ||
case A.IDENT: | ||
this.emit(ops.PUSHL | (this._fn.slotForLocal(ast.name) << 8), ast.line); | ||
break; | ||
case A.CALL: | ||
this.compileCall(ast); | ||
break; | ||
case A.BIN_OP: | ||
if (ast.op === T.LAND) { | ||
this.compileLogicalAnd(ast); | ||
} else if (ast.op === T.LOR) { | ||
this.compileLogicalOr(ast); | ||
} else if (ast.op in BIN_OPS) { | ||
this.compileExpression(ast.left); | ||
this.compileExpression(ast.right); | ||
this.emit(BIN_OPS[ast.op], ast.line); | ||
} else { | ||
throw "unknown binary operator!"; | ||
} | ||
break; | ||
case A.SPAWN: | ||
this.compileSpawn(ast); | ||
break; | ||
default: | ||
throw "unknown/unimplemented AST node type: " + ast.type; | ||
} | ||
} | ||
}, | ||
compileStatement: function(ast) { | ||
if (ast.type) { | ||
switch (ast.type) { | ||
case A.DEF: | ||
this.compileFnDef(ast); | ||
break; | ||
case A.IF: | ||
this.compileIf(ast); | ||
break; | ||
case A.WHILE: | ||
this.compileWhile(ast); | ||
break; | ||
case A.LOOP: | ||
this.compileLoop(ast); | ||
break; | ||
case A.RETURN: | ||
this.compileReturn(ast); | ||
break; | ||
case A.YIELD: | ||
this.compileYield(ast); | ||
break; | ||
default: | ||
this.compileExpression(ast); | ||
this.emit(ops.SETZ); | ||
break; | ||
} | ||
} else { | ||
this.compileExpression(ast); | ||
this.emit(ops.SETZ); | ||
} | ||
}, | ||
compileStatements: function(statements) { | ||
for (var i = 0; i < statements.length; ++i) { | ||
this.compileStatement(statements[i]); | ||
} | ||
}, | ||
compileFunctionBody: function(statements) { | ||
this.compileStatements(statements); | ||
this.emit(ops.PUSHZ); | ||
this.emit(ops.RET); | ||
}, | ||
compile: function(ast) { | ||
this._env = {}; | ||
this._fn = makeFunction(); | ||
this.compileFunctionBody(ast); | ||
return { | ||
topLevelFn : this._fn, | ||
symbols : this._env | ||
}; | ||
} | ||
}, | ||
compileIf: function(ast) { | ||
var ix = 0, | ||
firstAbs = null, | ||
lastAbs = null, | ||
clauses = ast.clauses; | ||
while (ix < clauses.length) { | ||
var clause = clauses[ix]; | ||
if (typeof clause.condition !== 'undefined') { | ||
this.compileExpression(clause.condition); | ||
var failJump = this._fn.code.length; | ||
this.emit(ops.JMPF); | ||
this.compileStatements(clause.body); | ||
if (firstAbs === null) { | ||
firstAbs = this._fn.code.length; | ||
lastAbs = this._fn.code.length; | ||
this.emit(0); | ||
} else { | ||
var tmp = this._fn.code.length; | ||
this.emit(lastAbs); // hack - stash pointer to last jump point so we can backtrack | ||
lastAbs = tmp; | ||
} | ||
this._fn.code[failJump] = ops.JMPF | ((this._fn.code.length - failJump - 1) << 8); | ||
} else { | ||
this.compileStatements(clause.body); | ||
} | ||
ix++; | ||
} | ||
var jmpOp = ops.JMPA | (this._fn.code.length << 8), | ||
currAbs = lastAbs; | ||
do { | ||
var tmp = this._fn.code[currAbs]; | ||
this._fn.code[currAbs] = jmpOp; | ||
if (currAbs == firstAbs) { | ||
break; | ||
} else { | ||
currAbs = tmp; | ||
} | ||
} while (true); | ||
}, | ||
compileWhile: function(ast) { | ||
var loopStart = this._fn.code.length; | ||
this.compileExpression(ast.condition); | ||
var failJump = this._fn.code.length; | ||
this.emit(ops.JMPF); | ||
this.compileStatements(ast.body); | ||
this.emit(ops.JMPA | (loopStart << 8)); | ||
this._fn.code[failJump] = ops.JMPF | ((this._fn.code.length - failJump - 1) << 8); | ||
}, | ||
compileLoop: function(ast) { | ||
var loopStart = this._fn.code.length; | ||
this.compileStatements(ast.body); | ||
this.emit(ops.YIELD, ast.line); | ||
this.emit(ops.JMPA | (loopStart << 8)); | ||
}, | ||
compileReturn: function(ast) { | ||
if (typeof ast.returnValue !== 'undefined') { | ||
this.compileExpression(ast.returnValue); | ||
} else { | ||
this.emit(ops.PUSHF); // TODO: should probably push undefined/null or whatever | ||
} | ||
this.emit(ops.RET, ast.line); | ||
}, | ||
compileYield: function(ast) { | ||
this.emit(ops.YIELD, ast.line); | ||
}, | ||
compileCall: function(ast) { | ||
var args = ast.args; | ||
if (args.length > 255) { | ||
throw "compile error - max args per function call (255) exceeded"; | ||
} | ||
for (var i = 0; i < args.length; ++i) { | ||
this.compileExpression(args[i]); | ||
} | ||
this.emit(ops.CALL | (args.length << 8) | (this._fn.slotForFunctionCall(ast.fn.name) << 16), ast.line); | ||
}, | ||
compileSpawn: function(ast) { | ||
var args = ast.args; | ||
if (args.length > 255) { | ||
throw "compile error - max args per spawn (255) exceeded"; | ||
} | ||
for (var i = 0; i < args.length; ++i) { | ||
this.compileExpression(args[i]); | ||
} | ||
this.emit(ops.SPAWN | (args.length << 8) | (this._fn.slotForFunctionCall(ast.fn.name) << 16), ast.line); | ||
}, | ||
compileEval: function(ast) { | ||
this.compileExpression(ast.code); | ||
this.emit(ops.EVAL, ast.line); | ||
}, | ||
compileLogicalAnd: function(ast) { | ||
this.compileExpression(ast.left); | ||
var bailJump = this._fn.code.length; | ||
this.emit(0); | ||
this.compileExpression(ast.right); | ||
this._fn.code[bailJump] = ops.JMPF_OP | ((this._fn.code.length - bailJump - 1) << 8); | ||
}, | ||
compileLogicalOr: function(ast) { | ||
this.compileExpression(ast.left); | ||
var bailJump = this._fn.code.length; | ||
this.emit(0); | ||
this.compileExpression(ast.right); | ||
this._fn.code[bailJump] = ops.JMPT_OP | ((this._fn.code.length - bailJump - 1) << 8); | ||
}, | ||
compileExpression: function(ast) { | ||
// FIXME: ast.line will be undefined for true and false. Is this intentional? | ||
if (ast === true) { | ||
this.emit(ops.PUSHT, ast.line); | ||
} else if (ast === false) { | ||
this.emit(ops.PUSHF, ast.line); | ||
} else if (typeof ast == 'number' || typeof ast == 'string') { | ||
this.emit(ops.PUSHC | (this._fn.slotForConstant(ast) << 8), ast.line); | ||
} else { | ||
switch (ast.type) { | ||
case A.COLOR: | ||
var color = makeColor(ast.r, ast.g, ast.b, ast.a); | ||
this.emit(ops.PUSHC | (this._fn.slotForConstant(color) << 8), ast.line); | ||
break; | ||
case A.ASSIGN: | ||
this.compileAssign(ast); | ||
break; | ||
case A.TRACE: | ||
this.emit(ops.TRACE, ast.line); | ||
break; | ||
case A.IDENT: | ||
this.emit(ops.PUSHL | (this._fn.slotForLocal(ast.name) << 8), ast.line); | ||
break; | ||
case A.GLOBAL_IDENT: | ||
this.emit(ops.PUSHG | (this._fn.slotForName(ast.name) << 16), ast.line); | ||
break; | ||
case A.CALL: | ||
this.compileCall(ast); | ||
break; | ||
case A.BIN_OP: | ||
if (ast.op === T.LAND) { | ||
this.compileLogicalAnd(ast); | ||
} else if (ast.op === T.LOR) { | ||
this.compileLogicalOr(ast); | ||
} else if (ast.op in BIN_OPS) { | ||
this.compileExpression(ast.left); | ||
this.compileExpression(ast.right); | ||
this.emit(BIN_OPS[ast.op], ast.line); | ||
} else { | ||
throw "unknown binary operator!"; | ||
} | ||
break; | ||
case A.SPAWN: | ||
this.compileSpawn(ast); | ||
break; | ||
case A.EVAL: | ||
this.compileEval(ast); | ||
break; | ||
case A.NULL: | ||
this.emit(ops.PUSHN, ast.line); | ||
break; | ||
default: | ||
throw "unknown/unimplemented AST node type: " + ast.type; | ||
} | ||
} | ||
}, | ||
compileStatement: function(ast) { | ||
if (ast.type) { | ||
switch (ast.type) { | ||
case A.DEF: | ||
this.compileFnDef(ast); | ||
break; | ||
case A.IF: | ||
this.compileIf(ast); | ||
break; | ||
case A.WHILE: | ||
this.compileWhile(ast); | ||
break; | ||
case A.LOOP: | ||
this.compileLoop(ast); | ||
break; | ||
case A.RETURN: | ||
this.compileReturn(ast); | ||
break; | ||
case A.YIELD: | ||
this.compileYield(ast); | ||
break; | ||
default: | ||
this.compileExpression(ast); | ||
this.emit(ops.SETZ); | ||
break; | ||
} | ||
} else { | ||
this.compileExpression(ast); | ||
this.emit(ops.SETZ); | ||
} | ||
}, | ||
compileStatements: function(statements) { | ||
for (var i = 0; i < statements.length; ++i) { | ||
this.compileStatement(statements[i]); | ||
} | ||
}, | ||
compileFunctionBody: function(statements) { | ||
this.compileStatements(statements); | ||
this.emit(ops.PUSHZ); | ||
this.emit(ops.RET); | ||
}, | ||
compile: function(ast) { | ||
this._env = {}; | ||
this._fn = makeFunction(); | ||
this.compileFunctionBody(ast); | ||
return { | ||
topLevelFn : this._fn, | ||
symbols : this._env | ||
}; | ||
}, | ||
compileForEval: function(ast, fn) { | ||
this._env = {}; | ||
this._fn = fn; | ||
var code = fn.code; | ||
this._fn.code = []; | ||
this.compileStatements(ast); | ||
this.emit(ops.PUSHZ); | ||
var retVal = { | ||
code : this._fn.code, | ||
symbols : this._env | ||
}; | ||
this._fn.code = code; | ||
return retVal; | ||
} | ||
}; | ||
exports.createCompiler = function() { | ||
return new Compiler(); | ||
} | ||
return new Compiler(); | ||
} |
"use strict"; | ||
var createVM = require('./vm').createVM, | ||
createLexer = require('./lexer').createLexer, | ||
createParser = require('./parser').createParser, | ||
createCompiler = require('./compiler').createCompiler, | ||
taskStates = require('./task_states'), | ||
types = require('./types'); | ||
var createVM = require('./vm').createVM, | ||
createLexer = require('./lexer').createLexer, | ||
createParser = require('./parser').createParser, | ||
createCompiler = require('./compiler').createCompiler, | ||
taskStates = require('./task_states'), | ||
types = require('./types'), | ||
readline = require('readline'); | ||
function createContext(vm) { | ||
vm = vm || createVM(); | ||
// | ||
// Ghetto stdlib | ||
vm.trace = function(vm, task, frame) { | ||
console.log("tracin'", task, frame); | ||
console.log(frame.dirtyLocals()); | ||
} | ||
vm.env['random'] = function() { | ||
return Math.floor(Math.random() * 1000); | ||
} | ||
vm.env['delay'] = function(args, task, env, vm) { | ||
if (task.state === taskStates.RESUMED) { | ||
task.state = taskStates.RUNNABLE; | ||
return null; | ||
} | ||
task.state = taskStates.BLOCKED; | ||
setTimeout(function() { vm.resumeTask(task); }, args[0]); | ||
}; | ||
vm.env['print'] = function(args) { | ||
var val = args[0]; | ||
if (typeof val !== 'object') { | ||
console.log(args[0]); | ||
} else { | ||
switch (val.__type__) { | ||
case types.T_FN: | ||
console.log("<Function>"); | ||
break; | ||
case types.T_TASK: | ||
console.log("<Task id=" + val.id + ">"); | ||
break; | ||
default: | ||
console.log("<Unknown>"); | ||
} | ||
} | ||
}; | ||
function start() { | ||
vm.start(); | ||
} | ||
function run(source, filename) { | ||
try { | ||
var lexer = createLexer(source), | ||
parser = createParser(lexer), | ||
ast = parser.parseTopLevel(), | ||
compiler = createCompiler(), | ||
result = compiler.compile(ast); | ||
vm = vm || createVM(); | ||
// | ||
// Ghetto stdlib | ||
vm.trace = function(vm, task, frame) { | ||
console.log("tracin'", task, frame); | ||
console.log(frame.dirtyLocals()); | ||
} | ||
vm.env['random'] = function() { | ||
return Math.floor(Math.random() * 1000); | ||
} | ||
vm.env['delay'] = function(args, task, env, vm) { | ||
vm.merge(result.symbols); | ||
vm.spawn(result.topLevelFn); | ||
} catch (e) { | ||
console.log(e); | ||
} | ||
} | ||
return { | ||
start : start, | ||
run : run | ||
}; | ||
if (task.state === taskStates.RESUMED) { | ||
task.state = taskStates.RUNNABLE; | ||
return null; | ||
} | ||
task.state = taskStates.BLOCKED; | ||
setTimeout(function() { | ||
if (task.state !== taskStates.DEAD) { | ||
vm.resumeTask(task); | ||
} | ||
}, args[0]); | ||
}; | ||
vm.env['print'] = function(args) { | ||
var val = args[0]; | ||
if (typeof val !== 'object') { | ||
console.log(args[0]); | ||
} else { | ||
switch (val.__type__) { | ||
case types.T_FN: | ||
console.log("<Function>"); | ||
break; | ||
case types.T_TASK: | ||
console.log("<Task id=" + val.id + ">"); | ||
break; | ||
default: | ||
console.log("<Unknown>"); | ||
} | ||
} | ||
}; | ||
vm.env['prompt'] = function(args) { | ||
var prompt = args[0] || "> "; | ||
process.stdout.write(prompt); | ||
}; | ||
vm.env['input'] = function(args, task, env, vm) { | ||
var frame = task.frames[task.fp]; | ||
if (task.state == taskStates.RESUMED) { | ||
task.state = taskStates.RUNNABLE; | ||
return frame.z; | ||
} | ||
task.state = taskStates.BLOCKED; | ||
var rl = readline.createInterface({ | ||
input: process.stdin, | ||
output: process.stdout | ||
}); | ||
frame.z = null; | ||
rl.once("line", function(input) { | ||
frame.z = input; | ||
rl.close(); | ||
}).once("close", function() { | ||
vm.resumeTask(task); | ||
}); | ||
}; | ||
function start() { | ||
vm.start(); | ||
} | ||
function spawn(fun) { | ||
return vm.spawn(fun); | ||
} | ||
function compile(source, filename) { | ||
try { | ||
var lexer = createLexer(source), | ||
parser = createParser(lexer), | ||
ast = parser.parseTopLevel(), | ||
compiler = createCompiler(), | ||
result = compiler.compile(ast); | ||
vm.merge(result.symbols); | ||
return result.topLevelFn; | ||
} catch (e) { | ||
console.log(e); | ||
return null; | ||
} | ||
} | ||
function run(source, filename) { | ||
var fun = compile(source, filename); | ||
if (!fun) | ||
return null; | ||
return vm.spawn(fun); | ||
} | ||
function killTask(task) { | ||
vm.killTask(task); | ||
} | ||
return { | ||
compile : compile, | ||
start : start, | ||
run : run, | ||
spawn : spawn, | ||
killTask : killTask, | ||
env : vm.env | ||
}; | ||
} | ||
exports.createContext = createContext; | ||
exports.createContext = createContext; |
@@ -15,3 +15,5 @@ "use strict"; | ||
'false' : T.FALSE, | ||
'spawn' : T.SPAWN | ||
'null' : T.NULL, | ||
'spawn' : T.SPAWN, | ||
'eval' : T.EVAL | ||
}; | ||
@@ -188,2 +190,15 @@ | ||
break; | ||
case '$': | ||
if (more() && ident_start_p(src[p+1])) { | ||
start = ++p; | ||
while (more() && ident_rest_p(src[p+1])) | ||
adv(); | ||
text = src.substring(start, p + 1); | ||
tok = T.GLOBAL_IDENT; | ||
} else { | ||
error = "expected: global variable name"; | ||
tok = T.ERROR; | ||
} | ||
break; | ||
case '\r': | ||
@@ -315,2 +330,2 @@ if (more() && src[p+1] === '\n') { | ||
}; | ||
}; |
@@ -12,13 +12,14 @@ "use strict"; | ||
var EXP_START_TOKENS = {}; | ||
EXP_START_TOKENS[T.BANG] = true; | ||
EXP_START_TOKENS[T.TILDE] = true; | ||
EXP_START_TOKENS[T.TRUE] = true; | ||
EXP_START_TOKENS[T.FALSE] = true; | ||
EXP_START_TOKENS[T.INTEGER] = true; | ||
EXP_START_TOKENS[T.HEX] = true; | ||
EXP_START_TOKENS[T.FLOAT] = true; | ||
EXP_START_TOKENS[T.STRING] = true; | ||
EXP_START_TOKENS[T.TRACE] = true; | ||
EXP_START_TOKENS[T.IDENT] = true; | ||
EXP_START_TOKENS[T.COLOR] = true; | ||
EXP_START_TOKENS[T.BANG] = true; | ||
EXP_START_TOKENS[T.TILDE] = true; | ||
EXP_START_TOKENS[T.TRUE] = true; | ||
EXP_START_TOKENS[T.FALSE] = true; | ||
EXP_START_TOKENS[T.INTEGER] = true; | ||
EXP_START_TOKENS[T.HEX] = true; | ||
EXP_START_TOKENS[T.FLOAT] = true; | ||
EXP_START_TOKENS[T.STRING] = true; | ||
EXP_START_TOKENS[T.TRACE] = true; | ||
EXP_START_TOKENS[T.IDENT] = true; | ||
EXP_START_TOKENS[T.GLOBAL_IDENT] = true; | ||
EXP_START_TOKENS[T.COLOR] = true; | ||
@@ -228,3 +229,3 @@ function ParseError(message, line, column, expectedToken, actualToken) { | ||
} | ||
function parseStatements() { | ||
@@ -372,5 +373,23 @@ var statements = []; | ||
} | ||
curr.exp = parseCall(); | ||
curr.exp = parseEval(); | ||
return root; | ||
} else { | ||
return parseEval(); | ||
} | ||
} | ||
function parseEval() { | ||
if (at(T.EVAL)) { | ||
var line = lexer.line(); | ||
var node = { type: A.EVAL, line: line, code: undefined }; | ||
next(); | ||
if (atBlockTerminator() || atStatementTerminator()) { | ||
error("expected: expression"); | ||
} else { | ||
node.code = parseExpression(); | ||
} | ||
return node; | ||
} else { | ||
return parseCall(); | ||
@@ -413,2 +432,5 @@ } | ||
next(); | ||
} else if (at(T.NULL)) { | ||
exp = { type: A.NULL, line: line }; | ||
next(); | ||
} else if (at(T.INTEGER)) { | ||
@@ -438,2 +460,5 @@ exp = parseInt(text(), 10); | ||
next(); | ||
} else if (at(T.GLOBAL_IDENT)) { | ||
exp = { type: A.GLOBAL_IDENT, line: line, name: text() }; | ||
next(); | ||
} else if (at(T.SPAWN)) { | ||
@@ -469,2 +494,2 @@ next(); | ||
}; | ||
} | ||
} |
@@ -35,2 +35,3 @@ "use strict"; | ||
'FALSE', // false | ||
'NULL', // null | ||
'IF', // if | ||
@@ -44,4 +45,6 @@ 'ELSE', // else | ||
'SPAWN', // spawn | ||
'EVAL', // eval | ||
'IDENT', | ||
'GLOBAL_IDENT', | ||
'INTEGER', | ||
@@ -64,2 +67,2 @@ 'HEX', | ||
exports.tokens = TOKENS; | ||
exports.names = TOKEN_NAMES; | ||
exports.names = TOKEN_NAMES; |
@@ -23,3 +23,3 @@ "use strict"; | ||
this.fnNames = []; | ||
this.fnCache = []; | ||
this.names = []; | ||
}; | ||
@@ -58,4 +58,13 @@ | ||
this.fnNames.push(name); | ||
this.fnCache.push(null); | ||
return this.fnNames.length - 1; | ||
}, | ||
slotForName: function(name) { | ||
for (var i = 0; i < this.names.length; ++i) { | ||
if (name === this.names[i]) { | ||
return i; | ||
} | ||
} | ||
this.names.push(name); | ||
return this.names.length - 1; | ||
} | ||
@@ -78,2 +87,10 @@ }; | ||
Task.prototype.stop = function() { | ||
} | ||
Task.prototype.start = function() { | ||
} | ||
exports.makeFunction = function() { | ||
@@ -80,0 +97,0 @@ return new Fn(); |
@@ -105,7 +105,10 @@ "use strict"; | ||
OP_PUSHL = t('PUSHL', 'Push local', [31, 8, 'local']), | ||
OP_PUSHG = t('PUSHG', 'Push global', [31, 16, 'name']), | ||
OP_PUSHT = t('PUSHT', 'Push true'), | ||
OP_PUSHF = t('PUSHF', 'Push false'), | ||
OP_PUSHN = t('PUSHN', 'Push null'), | ||
OP_PUSHZ = t('PUSHZ', 'Push last evaluated value'), | ||
OP_SETZ = t('SETZ', 'Set last evaluated value'), | ||
OP_SETL = t('SETL', 'Set local', [31, 8, 'local']), | ||
OP_SETG = t('SETG', 'Set global', [31, 16, 'name']), | ||
OP_CALL = t('CALL', 'Call function', [31, 16, 'fn', 15, 8, 'nargs']), | ||
@@ -133,2 +136,3 @@ OP_SPAWN = t('SPAWN', 'Spawn new thread', [31, 16, 'fn', 15, 8, 'nargs']), | ||
OP_YIELD = t('YIELD', 'Yield'), | ||
OP_EVAL = t('EVAL', 'Evaluate code'), | ||
OP_EXIT = t('EXIT', 'Exit task'); | ||
@@ -157,3 +161,4 @@ | ||
blocked: new TaskList(), | ||
env: {} | ||
env: {}, | ||
globals: {} | ||
}; | ||
@@ -163,3 +168,3 @@ | ||
var env = vm.env; | ||
var env = vm.env, globals = vm.globals; | ||
@@ -191,6 +196,10 @@ function runtimeError(task, message) { | ||
fn = frame.fn, | ||
code = fn.code; | ||
code = fn.code, | ||
fetch = function () { return code[frame.ip++]; }, | ||
next_op = fetch; | ||
for (;;) { | ||
var op = code[frame.ip++]; | ||
var op = next_op(); | ||
// console.log(opcodeMeta[op & 0xFF].name); | ||
@@ -207,2 +216,6 @@ switch (op & 0x000000FF) { | ||
break; | ||
case OP_PUSHG: | ||
var gname = fn.names[op >> 16]; | ||
task.stack[frame.sp++] = (gname in globals) ? globals[gname] : null; | ||
break; | ||
case OP_PUSHT: | ||
@@ -214,2 +227,5 @@ task.stack[frame.sp++] = true; | ||
break; | ||
case OP_PUSHN: | ||
task.stack[frame.sp++] = null; | ||
break; | ||
case OP_PUSHZ: | ||
@@ -230,2 +246,6 @@ task.stack[frame.sp++] = frame.z; | ||
break; | ||
case OP_SETG: | ||
// Again, stack is not popped | ||
globals[fn.names[op >> 16]] = task.stack[frame.sp-1]; | ||
break; | ||
case OP_CALL: | ||
@@ -315,2 +335,36 @@ | ||
break; | ||
case OP_EVAL: | ||
var value = task.stack[frame.sp-1], | ||
eval_ip = 0, | ||
eval_code = []; | ||
try { | ||
var lexer = require('./lexer').createLexer(value), | ||
parser = require('./parser').createParser(lexer), | ||
ast = parser.parseTopLevel(), | ||
compiler = require('./compiler').createCompiler(), | ||
result = compiler.compileForEval(ast, frame.fn); | ||
vm.merge(result.symbols); | ||
eval_code = result.code; | ||
} catch (e) { | ||
return runtimeError(task, "Could not evaluate code '" + value + "': " + e); | ||
} | ||
next_op = function () { | ||
if (eval_ip < eval_code.length) { | ||
return eval_code[eval_ip++]; | ||
} else { | ||
next_op = fetch; | ||
return next_op(); | ||
} | ||
} | ||
break; | ||
case OP_POP: | ||
@@ -481,3 +535,3 @@ --frame.sp; | ||
setTimeout(resume, 0); | ||
return task; | ||
@@ -546,2 +600,11 @@ } | ||
} | ||
function killTask(task) { | ||
if (task.state === TASK_RUNNABLE || task.state === TASK_RESUMED) { | ||
tasklist_remove(vm.runnable, task); | ||
} else if (task.state === TASK_BLOCKED) { | ||
tasklist_remove(vm.blocked, task); | ||
} | ||
task.state = TASK_DEAD; | ||
} | ||
@@ -581,2 +644,3 @@ function gcTask(task) { | ||
vm.resumeTask = resumeTask; | ||
vm.killTask = killTask; | ||
vm.merge = merge; | ||
@@ -589,2 +653,2 @@ | ||
exports.opcodes = opcodes; | ||
exports.opcodeMeta = opcodeMeta; | ||
exports.opcodeMeta = opcodeMeta; |
{ | ||
"name": "jester", | ||
"version": "0.2.2", | ||
"version": "0.2.3", | ||
"description": "Simple language designed for teaching", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
96482
37
2605
3
2