Skip to content

Instantly share code, notes, and snippets.

@distransient
Forked from anonymous/callbackmap.js
Last active August 29, 2015 14:05
Show Gist options
  • Save distransient/88fa7607360b8859b020 to your computer and use it in GitHub Desktop.
Save distransient/88fa7607360b8859b020 to your computer and use it in GitHub Desktop.
#!/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