mcopy/app/lib/mscript/index.js

405 lines
9.4 KiB
JavaScript
Raw Normal View History

'use strict';
2016-04-12 04:06:23 +00:00
/** @module lib/mscript */
const BLACK = '0,0,0';
const CMD = [
2016-04-12 04:06:23 +00:00
'CF',
'PF',
'BF',
'CB',
'PB',
'BB'
2016-04-12 04:06:23 +00:00
];
const ALTS = {
2016-04-12 04:06:23 +00:00
'CF' : ['CAMERA FORWARD', 'CAM FORWARD'],
'PF' : ['PROJECTOR FORWARD', 'PROJ FORWARD'],
'BF' : ['BLACK FORWARD', 'BLACK', 'BLANK FORWARD', 'BLANK'],
2016-04-12 04:06:23 +00:00
'CB' : ['CAMERA BACKWARD', 'CAM BACKWARD', 'CAMERA BACK', 'CAM BACK'],
'PB' : ['PROJECTOR FORWARD', 'PROJ FORWARD', 'PROJECTOR BACK', 'PROJ BACK'],
'BB' : ['BLACK BACKWARD', 'BLACK BACK', 'BLANK BACK'],
'L ' : ['LIGHT', 'COLOR', 'LAMP'],
'F ' : ['FADE']
2016-04-12 04:06:23 +00:00
};
class Mscript {
constructor () {
this.output = {};
2016-04-12 04:06:23 +00:00
}
/**
* Clear the state of the script
*/
clear () {
this.cam = 0;
this.proj = 0;
this.color = '';
this.loops = [];
this.rec = -1;
this.two = '';
this.arr = [];
this.light = [];
this.target = 0; //move to target using CAM # or PROJ #
this.dist = 0;
this.output = {};
}
/**
* Main function, accepts multi-line string, parses into lines
* and interprets the instructions from the text. Returns an array
* of steps to be fed into the mcopy.
*/
interpret (text, callback) {
this.clear()
if (typeof text === 'undefined') {
return this.fail('No input');
}
//split string into lines, each containing a command
let lines = text.split('\n');
for (let line of lines) {
line = line.replace(/\t+/g, ""); //strip tabs
line = line.trim(); //remove excess whitespace before and after command
line = line.toUpperCase();
this.two = line.substring(0, 2);
if (CMD.indexOf(this.two) !== -1) {
this.basic_cmd(line);
} else if (line.substring(0, 4) === 'LOOP') {
this.new_loop(line);
} else if (line.substring(0, 2) === 'L ') {
this.light_state(line);
} else if (line.substring(0, 2) === 'F ') {
//fade
} else if (line.substring(0, 3) === 'END') {
this.end_loop(line);
} else if (line.substring(0, 3) === 'CAM') { //directly go to that frame (black?)
this.move_cam(line);
} else if (line.substring(0, 4) === 'PROJ') { //directly go to that frame
this.move_proj(line);
} else if (line.substring(0, 3) === 'SET') { //set that state
this.set_state(line);
} else if (line.substring(0, 1) === '#' || line.substring(0, 2) === '//') {
//comments
//ignore while parsing
2016-04-12 04:06:23 +00:00
}
}
this.output.success = true;
this.output.arr = this.arr; //all instructions
this.output.light = this.light; //all light instructions
this.output.cam = this.cam;
this.output.proj = this.proj;
if (typeof callback !== 'undefined') {
//should only be invoked by running mscript.tests()
callback(this.output);
}
}
/**
* Apply a basic two character command
*/
basic_cmd (line) {
if (this.loops.length > 0) {
//hold generated arr in state loop array
this.loops[this.rec].arr
.push.apply(this.loops[this.rec].arr,
this.str_to_arr(line,
this.two));
this.loops[this.rec].light
.push.apply(this.loops[this.rec].light,
this.light_to_arr(line,
this.two));
} else {
this.arr.push.apply(this.arr, this.str_to_arr(line, this.two));
this.light.push.apply(this.light, this.light_to_arr(line, this.two))
}
}
/**
* Start a new loop
*/
new_loop (line) {
this.rec++;
this.loops[this.rec] = {
arr : [],
light : [],
cam : 0,
proj : 0,
cmd : line + ''
};
}
/**
* Close the most recent loop
*/
end_loop (line) {
for (let x = 0; x < this.loop_count(this.loops[this.rec].cmd); x++) {
if (this.rec === 0) {
this.arr.push.apply(this.arr, this.loops[this.rec].arr);
this.light.push.apply(this.light, this.loops[this.rec].light);
} else if (this.rec >= 1) {
this.loops[this.rec - 1].arr
.push.apply(this.loops[this.rec - 1].arr,
this.loops[this.rec].arr);
this.loops[this.rec - 1].light
.push.apply(this.loops[this.rec - 1].light,
this.loops[this.rec].light);
}
}
this.update('END', this.loop_count(this.loops[this.rec].cmd));
delete this.loops[this.rec];
this.rec--;
}
/**
* Move camera to explicitly-defined frame
*/
move_cam (line) {
this.target = parseInt(line.split('CAM ')[1]);
if (this.loops.length > 0) {
if (this.target > this.cam) {
this.dist = this.target - this.cam;
for (let x = 0; x < this.dist; x++) {
this.loops[this.rec].arr.push('BF');
this.loops[this.rec].light.push(BLACK);
this.update('BF');
}
} else {
this.dist = this.cam - this.target;
for (let x = 0; x < this.dist; x++) {
this.loops[this.rec].arr.push('BB');
this.loops[this.rec].light.push(BLACK);
this.update('BB');
}
}
} else {
if (target > this.cam) {
this.dist = this.target - this.cam;
for (let x = 0; x < this.dist; x++) {
this.arr.push('BF');
this.light.push(BLACK);
this.update('BF');
}
} else {
this.dist = this.cam - this.target;
for (let x = 0; x < this.dist; x++) {
this.arr.push('BB');
this.light.push(BLACK);
this.update('BB');
}
}
}
}
/**
* Move projector to explicitly-defined frame
*/
move_proj (line) {
this.target = parseInt(line.split('PROJ ')[1]);
if (this.loops.length > 0) {
if (this.target > this.proj) {
this.dist = this.target - this.proj;
for (let x = 0; x < this.dist; x++) {
this.loops[this.rec].arr.push('PF');
this.loops[this.rec].light.push('');
this.update('PF');
}
} else {
this.dist = this.proj - this.target;
for (let x = 0; x < this.dist; x++) {
this.loops[this.rec].arr.push('PB');
this.loops[this.rec].light.push('');
this.update('PB');
}
}
} else {
if (this.target > this.proj) {
this.dist = this.target - this.proj;
for (let x = 0; x < this.dist; x++) {
this.arr.push('PF');
this.light.push('');
this.update('PF');
}
} else {
this.dist = this.proj - this.target;
for (let x = 0; x < this.dist; x++) {
this.arr.push('PB');
this.light.push('');
this.update('PB');
}
}
}
}
/**
* Set the state of either the cam or projector
*/
set_state (line) {
if (line.substring(0, 7) === 'SET CAM') {
this.cam = parseInt(line.split('SET CAM')[1]);
} else if (line.substring(0, 8) === 'SET PROJ') {
this.proj = parseInt(line.split('SET PROJ')[1]);
}
}
/**
* Return the last loop
*/
last_loop () {
return this.loops[this.loops.length - 1];
2016-04-12 04:06:23 +00:00
}
/**
* Return the second-last loop
*/
parent_loop () {
return this.loops[this.loops.length - 2];
2016-04-12 04:06:23 +00:00
}
/**
* Extract the loop count integer from a LOOP cmd
*/
loop_count (str) {
return parseInt(str.split(' ')[1]);
2016-04-12 04:06:23 +00:00
}
/**
* Extract the fade length integer from a FADE cmd
*/
fade_count (str) {
return parseInt(str.split(' ')[1]);
2016-04-12 04:06:23 +00:00
}
/**
* Increase the state of a specific object, such as the camera/projector,
* by the value defined in val
*/
update (cmd, val = 1) {
if (cmd === 'END') {
//I don't understand this loop
for (let i = 0; i < val; i++) {
if (this.rec === 0) {
this.cam += this.loops[this.rec].cam;
this.proj += this.loops[this.rec].proj;
} else if (this.rec >= 1) {
this.loops[this.rec - 1].cam += this.loops[this.rec].cam;
this.loops[this.rec - 1].proj += this.loops[this.rec].proj;
}
}
} else if (cmd === 'CF') {
if (this.loops.length < 1) {
this.cam += val;
} else {
this.loops[this.rec].cam += val;
}
} else if (cmd === 'CB') {
if (this.loops.length < 1) {
this.cam -= val;
} else {
this.loops[this.rec].cam--;
}
} else if (cmd === 'PF') {
if (this.loops.length < 1) {
this.proj += val;
} else {
this.loops[this.rec].proj += val;
}
} else if (cmd === 'PB') {
if (this.loops.length < 1) {
this.proj -= val;
} else {
this.loops[this.rec].proj--;
}
} else if (cmd === 'BF') {
if (this.loops.length < 1) {
this.cam += val;
} else {
this.loops[this.rec].cam += val;
}
} else if (cmd === 'BB') {
if (this.loops.length < 1) {
this.cam -= val;
} else {
this.loops[this.rec].cam -= val;
}
} else if (cmd === 'L ') {
2016-04-12 04:06:23 +00:00
}
2016-04-12 04:06:23 +00:00
}
/**
* Split string on command, extract any integers from string
*/
str_to_arr (str, cmd) {
const cnt = str.split(cmd);
let c = parseInt(cnt[1]);
let arr = [];
if (cnt[1] === '') {
c = 1;
} else {
c = parseInt(cnt[1]);
}
arr = new Array(c).fill(cmd);
this.update(cmd, c);
return arr;
2016-04-12 04:06:23 +00:00
}
/**
* Split a string on a command to extract data for light array
*/
light_to_arr (str, cmd) {
const cnt = str.split(cmd);
let c = parseInt(cnt[1]);
let arr = [];
if (cnt[1] === '') {
c = 1;
} else {
c = parseInt(cnt[1]);
}
for (var i = 0; i < c; i++) {
if (cmd === 'CF'
|| cmd === 'CB') {
arr.push(this.color);
} else if (cmd === 'BF'
|| cmd === 'BB') {
arr.push(BLACK);
} else {
arr.push('');
}
}
return arr;
2016-04-12 04:06:23 +00:00
}
/**
* Split a string to extract an rgb color value
*/
light_state (str) {
//add parsers for other color spaces
const color = str.replace('L ', '').trim();
this.color = color;
2016-04-12 04:06:23 +00:00
}
/**
* Throw an error with specific message
*/
fail (msg) {
throw new Error(msg);
2016-04-12 04:06:23 +00:00
}
}
module.exports = Mscript;
2016-04-12 04:06:23 +00:00
/*
CAM # - go to camera frame #
PROJ # - go to projector frame #
SET CAM # - sets camera count to #
SET PROJ # - sets projector count to #
LOOP # - begin loop, can nest recursively, # times
END LOOP - (or END) closes loop
L #RGB - sets light to rgb value
FADE
2016-04-12 04:06:23 +00:00
CF - Camera forwards
PF - Projector forwards
BF - Black forwards
CB - Camera backwards
PB - Projector backwards
BB - Black backwards
*/