/**
* Roboliq: Automation for liquid-handling robots
* @copyright 2017, ETH Zurich, Ellis Whitehead
* @license GPL-3.0
*/
/**
* A collection of helper utilities for our Evoware compiler.
* @module
*/
import _ from 'lodash';
import fs from 'fs';
import iconv from 'iconv-lite';
/**
* Encode an integer as an ASCII character.
* Evoware uses this to generate a string representing a list of wells or sites.
* @param {number} n - integer to encode as a character
* @return {string} a single-character string that represents the number
*/
export function encode(n) {
return String.fromCharCode("0".charCodeAt(0) + n);
}
/**
* Decode a character to an integer.
*/
export function decode(c) {
return c.charCodeAt(0) - "0".charCodeAt(0);
}
/**
* Convert the number to hex. Number should be between 0 and 15.
* @param {integer} n - number between 0 and 15
* @return {char}
*/
export function hex(n) {
return n.toString(16).toUpperCase()[0];
}
/**
* Takes an encoding of indexes on a 2D surface (as found in the file Carrier.cfg)
* and
* @param {string} encoded - an encoded list of indexes
* @return {array} tuple of [rows on surface, columns on surface, selected indexes on surface]
*/
export function parseEncodedIndexes(encoded) {
// HACK: for some reason, there is this strange sequence "�" that shows
// up in some places. It appears to simply indicate 7 bits, e.g. "0"+127, e.g. '¯'
encoded = encoded.replace(/�/g, String.fromCharCode(48+127));
const col_n = decode(encoded.charAt(1));
const row_n = decode(encoded.charAt(3));
const s = encoded.substring(4);
//console.log({col_n, row_n, s})
const indexes = _.flatMap(s, (c, c_i) => {
const n = decode(c);
//const bit_l = (0 to 7).flatMap(bit_i => if ((n & (1 << bit_i)) > 0) Some(bit_i) else None)
const bit_l = _.filter(_.times(7, bit_i => ((n & (1 << bit_i)) > 0) ? bit_i : undefined), x => !_.isUndefined(x));
//console.log({c, c_i, n, bit_l});
return bit_l.map(bit_i => c_i * 7 + bit_i);
});
return [col_n, row_n, indexes];
}
/**
* Split an evoware carrier line into its components.
* The first component is an integer that identifies the type of line.
* The remaining components are returned as a list of string.
*
* @param {string} line - a text line from Evoware's carrier file
* @return {array} Returns a pair [kind, items], where kind is an integer
* identifying the type of line, and items is a string array of the remaining
* components of the line.
*/
export function splitSemicolons(line) {
const l = line.split(";");
const kind = parseInt(l[0]);
return [kind, _.tail(l)];
}
/**
* A class to handle Evoware's semicolon-based file format.
* @class module:evoware/EvowareUtils.EvowareSemicolonFile
* @param {string} filename - path to semicolon file
* @param {number} skip - number of lines to initially skip at the top of the file
*/
export class EvowareSemicolonFile {
constructor(filename, skip) {
const raw = fs.readFileSync(filename);
const filedata = iconv.decode(raw, "ISO-8859-1");
this.lines = filedata.split("\n");
//console.log("lines:\n"+lines)
//console.log(lines.length);
this.lineIndex = skip;
}
/**
* Get the next line
* @return {string} next line in semicolon file
*/
next() {
if (this.lineIndex >= this.lines.length)
return undefined;
const line = this.lines[this.lineIndex];
this.lineIndex++;
return line;
}
/**
* Get the next line in the file and split it on semicolons.
* @return {array} array of strings resulting from splitting the line at semicolons.
*/
nextSplit() {
const line = this.next();
if (_.isUndefined(line))
return undefined;
const result = splitSemicolons(line);
return result;
}
/**
* Whether there are any more lines in the file.
* @return {boolean} true if there are more lines to read
*/
hasNext() {
return (this.lineIndex < this.lines.length);
}
/**
* Return a line that is `skip` lines ahead of the last line read.
* @param {number} skip - number of lines to skip over
* @return {string} line in file
*/
peekAhead(skip) {
const i = this.lineIndex + skip;
if (i >= this.lines.length)
return undefined;
const line = this.lines[i];
return line;
}
/**
* Skip `n` lines ahead
* @param {number} n - number of lines to skip
*/
skip(n) {
this.lineIndex += n;
}
/**
* Get the next `n` lines from the file.
* @param {number} n - number of lines to read.
* @return {array} array of strings read.
*/
take(n) {
const l = this.lines.slice(this.lineIndex, this.lineIndex + n);
this.lineIndex += n;
return l;
}
}