-
-
Save distransient/88fa7607360b8859b020 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env node --harmony | |
// todo: only worry about flags if this is the main module | |
var argv = require('minimist')(process.argv.slice(2)) | |
var createClient = require('net-oce-protocol') | |
var duplex = require('duplexer') | |
var spawn = require('child_process').spawn | |
var path = require('path') | |
var inspect = require('util').inspect | |
var co = require('co') | |
var thunkify = require('thunkify') | |
exports = module.exports = {} | |
var T = exports.TOKENS = { | |
LPAREN: ['('] | |
, RPAREN: [')'] | |
, PIPE: ['->'] | |
, PLUS: ['+', '+'] | |
, MINUS: ['-', '-'] | |
, MULTIPLY: ['*', '⋅', '×'] | |
, DIVIDE: ['/', '÷'] | |
} | |
DEBUG = exports.debug = argv.debug || false | |
// change this when we need to send debug info to web client | |
CONSOLE = exports.console = console | |
// TODO: | |
// logical operators? | |
// | |
// maths: | |
// "cross" - cross product of two vectors | |
// "norm/normal" - euclidean normal of a vector | |
// vector add/subtract/multiply/divide | |
// | |
// csg and 3d functions | |
// each command takes current list of arguments and its return value is piped into the next | |
// operator in the command | |
var commands = exports.commands = { | |
add: function (argv, next) { next(apply(function (a, b) { return (a||0) + (b||0) }, 2, argv)) } | |
, div: function (argv, next) { next(apply(function (a, b) { return (a||0) / (b||1) }, 2, argv)) } | |
, divide: function (argv, next) { next(apply(function (a, b) { return (a||0) / (b||1) }, 2, argv)) } | |
, help: function (argv, next) { next(Object.keys(exports.commands).sort()) } | |
, len: function (argv, next) { next(argv.length||0) } | |
, ln: function (argv, next) { next(apply(Math.log, 1, argv)) } | |
, mod: function (argv, next) { next(apply(function (a, b) { return (a||0) % (b||1) }, 2, argv)) } | |
, modulo: function (argv, next) { next(apply(function (a, b) { return (a||0) % (b||1) }, 2, argv)) } | |
, modulus: function (argv, next) { next(apply(function (a, b) { return (a||0) % (b||1) }, 2, argv)) } | |
, mul: function (argv, next) { next(apply(function (a, b) { return (a||0) * (b||0) }, 2, argv)) } | |
, multiply: function (argv, next) { next(apply(function (a, b) { return (a||0) * (b||0) }, 2, argv)) } | |
// TODO: send this stuff back to user for printing | |
, print: function (argv, next) { CONSOLE.log(argv.join(' ')); next(argv) } | |
, sign: function (argv, next) { next(apply(function(a) { (a > 1) ? 1 : (a < 1) ? -1 : 0 }, 1, argv)) } | |
, sub: function (argv, next) { next(apply(function (a, b) { return (a||0) - (b||0) }, 2, argv)) } | |
, subtract: function (argv, next) { next(apply(function (a, b) { return (a||0) - (b||0) }, 2, argv)) } | |
} | |
// enumerate Math.* stuff and put it in commands | |
Object.getOwnPropertyNames(Math).map(function(op){ | |
commands[op] = (typeof Math[op] === 'function') | |
? function (argv, next) { next(apply(Math[op], Math[op].length, argv)) } | |
: function (argv, next) { next([].concat(Math[op], argv)) } | |
}) | |
// override some js Math stuff | |
commands.log = function (argv, next) { next(apply(function (a, b) { | |
return Math.log(a) / Math.log(b) | |
}, 2, argv)) } | |
commands.max = function (argv, next) { next(apply(Math.max, argv.length, argv)) } | |
commands.min = function (argv, next) { next(apply(Math.min, argv.length, argv)) } | |
exports.eval = function (string, callback) { execute(parse(string), callback) } | |
// apply argc of argv to function n, prepend result to remainder of argv | |
function apply (fn, argc, argv) { return [].concat( | |
fn.apply(null, argv.slice(0, argc)) | |
, argv.slice(argc) | |
)} | |
// todo: allow user to define variables somehow | |
// idea: 'let varname = [operators]' | |
// update: variables might not actually make sense! | |
// however, names would make sense | |
// ie: "myRectangles: range 0 5 -> rectangle output 40 29 1 1 1" | |
// (output of rectangle() is { guid }, so myRectangles would be [{ guid }]) | |
// following use, name would be operator: "myRectangles -> transform 0 39 2" | |
// the guid isn't actually mutated; myRectangles is a name and is immutable. | |
// the rectangle the guid POINTS to is mutated | |
// future idea: attach signals to commands, based on the involved operators | |
// so that the command will be re-ran when the signal changes | |
function parse(text) { | |
if (text === '') return; | |
if (DEBUG) CONSOLE.log('Parsing source text: "' + text + '"') | |
var nest = [[]] | |
// buffer tokens with spaces | |
for (var token in T) text = text.replace( | |
RegExp(T[token].join('|') | |
.replace('(','\\(') | |
.replace(')','\\)') | |
.replace('*', '\\*') | |
.replace('+', '\\+') | |
), ' ' + T[token] + ' ' | |
) | |
text = text.trim().split(/\s+/) | |
// this part assumes the array is of operators | |
text.map(function (op) { | |
if (op === T.LPAREN[0]) { | |
nest[nest.length - 1].push([]) | |
nest.push(nest[nest.length - 1][nest[nest.length - 1].length - 1]) | |
} else if (op === T.RPAREN[0]) { | |
nest.pop() | |
} else { | |
nest[nest.length - 1].push(op) | |
} | |
}) | |
// TODO: reorder operators here so that infix ops | |
// become prefix functions | |
// | |
// ie: 1 + 1 -> add 1 1 | |
if (DEBUG) CONSOLE.log('Source representation created:', inspect(nest[0])) | |
return nest.pop() | |
} | |
// TODO: tuple types for matrix/vector operations? | |
var execute = exports.execute = function (command, callback) { | |
if (command === undefined || command.map === undefined) return; | |
if (DEBUG) CONSOLE.log('Executing source representation:', command) | |
cbmap(command.reverse(), executor, [], function (output) { | |
if (DEBUG) CONSOLE.log('Source representation', command, 'returns value', inspect(output), '\n') | |
callback(output) | |
}) | |
} | |
function executor (operator, input, next) { | |
if (DEBUG) CONSOLE.log('Interpreting operator', inspect(operator), 'with input stack', inspect(input)) | |
if (Object.prototype.toString.call(operator) === '[object Array]') { | |
if (DEBUG) CONSOLE.log('\nRecursing...') | |
cbmap(operator.reverse(), executor, [], function (output) { next([].concat(output, input)) }) | |
} else if (typeof commands[operator] === 'function') { | |
if (DEBUG) CONSOLE.log('Function ', operator, 'found...') | |
commands[operator](input, next) | |
} else if (typeof operator === 'string' && !isNaN(operator)) { | |
if (DEBUG) CONSOLE.log('Prepending number', operator, 'to argument list...') | |
next([].concat(Number(operator), input)) | |
} else { | |
if (DEBUG) CONSOLE.log('Prepending string', inspect(operator), 'to argument list...') | |
next([].concat(operator, input)) | |
} | |
} | |
function cbmap (array, fn, input, callback) { | |
fn( | |
array[0] | |
, input||[] | |
, function (output) { | |
if (array.length > 0) cbmap(array.slice(1), fn, output, callback) | |
else { | |
if (DEBUG) CONSOLE.log('Returning value', inspect(output.slice(1)), '\n') | |
callback(output.slice(1)) | |
} | |
} | |
) | |
} | |
if (require.main === module) { | |
// TODO: add queue or something so that data is not lost on stdin | |
if (!argv.oce) return CONSOLE.log('usage: index.js --oce=/path/to/net-oce') | |
var child = spawn(path.resolve(process.cwd(), argv.oce), [], { stdio : 'pipe' }) | |
child.on('error', console.error) | |
child.stderr.on('error', console.error) | |
createClient(duplex(child.stdin, child.stdout), function(e, methods) { | |
// TODO: callbacks on these methods | |
Object.keys(methods).forEach(function (method) { | |
commands[method] = function (argv, next) { | |
next(apply(methods[method], methods[method].argc, argv)) | |
} | |
}) | |
process.stdout.write('> ') | |
process.stdin.setEncoding('utf8') | |
process.stdin.on('readable', function () { | |
(process.stdin.read()||'').trim() | |
.split('\n') | |
.map(parse) | |
.map(function (command) { | |
execute(command, function (result) { | |
process.stdout.write(result ? inspect(result) + '\n' : '') | |
process.stdout.write('> ') | |
}) | |
}) | |
child.kill(); | |
}) | |
}); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment