Commit 72b71cd2 authored by Anton Klarén's avatar Anton Klarén
Browse files

Added tests and refactored/cleanup array decoding

parent dd72ad92
......@@ -35,122 +35,7 @@ btn.onclick = function() {
decodeFromUrl(urlInp.value);
};
decodeFromUrl('http://fair-2014-4.lab1.cs.lth.se:8080/logdata/clamping/experiments/e12c4b31-5b32-41c0-b6a3-a5ad045aa997/data');
var referenceObject = {
log_message: { //struct
sequence: 10, //int
message: 'Test', //string
data: { //struct
f_value: 1.6, //float
d_value: 1000000.0, //double
l_value: 145, //long
s_value: 10, //short
b_value: 0xA, //byte
theStack: { //struct
level: 2, //int
name: 'Robot.Move.toPos(10)', //string
fatal: true //bool
}
},
xyz: [1,2,3], //int[3]
vect: [8,7,6,5,4,3], //int[_]
fix_mat: [[1,2,3,4],[2,3,4,5],[3,4,5,6]], //int[3][_]
dyn_mat: [[1,2],[3,4]], //int[_][2]
fix_mul: [[1,2,3,4],[2,3,4,5],[3,4,5,6]], //int[_, 4]
dyn_mul: [[1,2],[3,4]], //int[_, 2]
fix_mulmat: [[[1,2,3],[4,5,6]]], //int[1, _][_]
fix_matmul: [[[1,2,3],[4,5,6]]], //int[_][2, _]
dyn_mulmat: [[[1,2,3],[4,5,6]]], //int[_, _][3]
dyn_matmul: [[[1,2,3],[4,5,6]]], //int[1][_, 3]
points: [
{x: 4.6, y: 2.3},
{x: 7.1, y: 1.0},
{x: 6.2, y: 5.9}
] //struct { float x; float y; } points[_]
}
};
function testObject(obj) {
console.log(JSON.stringify(obj));
console.log(compareObjects(referenceObject, obj));
}
function compareObjects(o, p) {
var i,
keysO = Object.keys(o).sort(),
keysP = Object.keys(p).sort();
if (keysO.length !== keysP.length) {
return false;//not the same nr of keys
}
if (keysO.join('') !== keysP.join('')) {
return false;//different keys
}
for (i=0;i<keysO.length;++i) {
var x = o[keysO[i]],
y = p[keysO[i]];
if (x instanceof Array) {
if (!(y instanceof Array))
return false;
//if (compareObjects(o[keysO[i]], p[keysO[i]] === false) return false
//would work, too, and perhaps is a better fit, still, this is easy, too
if (p[keysO[i]].sort().join('') !== o[keysO[i]].sort().join(''))
return false;
}
else if (o[keysO[i]] instanceof Date) {
if (!(p[keysO[i]] instanceof Date))
return false;
if ((''+o[keysO[i]]) !== (''+p[keysO[i]]))
return false;
}
else if (o[keysO[i]] instanceof Function) {
if (!(p[keysO[i]] instanceof Function))
return false;
//ignore functions, or check them regardless?
}
if (x instanceof Object) {
if (!(y instanceof Object))
return false;
if (x === o) {//self reference?
if (y !== p)
return false;
}
else if (compareObjects(x, y) === false) {
return false;//WARNING: does not deal with circular refs other than ^^
}
}
else if(Number(x) === x) { //number
if((x|0) === x) { //int
if(x!==y) {
return false;
}
}
else {
//float point, allow deviation
var diff = Math.abs(x-y);
if(diff > 1e-6) {
return false;
}
}
}
else if (x !== y) {//change !== to != for loose comparison
console.log('missmatch ' + x +':'+y);
return false;//not the same value
}
console.log('match ' + x +':'+y);
}
return true;
}
function isInt(n){
return Number(n)===n && n%1===0;
}
</script>
<script src='tester.js'></script>
</body>
</html>
......@@ -115,7 +115,7 @@ LabComm.Buffer.prototype.getString = function() {
/**
* Reads a boolean (8-bits) from the buffer.
* @return {Boolean} A boolean
* @return {boolean} A boolean
*/
LabComm.Buffer.prototype.getBoolean = function() {
return Boolean(this.data_.getUint8(this.offset_++));
......@@ -235,13 +235,13 @@ LabComm.Decoder = function(callback) {
* @param {string} type The string representation of the type
* @param {string} parser The parsing function to be called when decoding current field
* @param {number} level The current nesting level
* @param {string=} opt_arrayVar The array variable to put the result in
* @param {string=} opt_parentArray The array variable to put the result in
* @return {string} The JSON part of the final structure
* @private
*/
LabComm.Decoder.prototype.buildDecoder_ = function(name, json, fb, type, parser, level, opt_arrayVar) {
if(opt_arrayVar) {
fb.push(this.indent_(level) + opt_arrayVar + ' = ' + parser + '; //'+name+'\n');
LabComm.Decoder.prototype.buildDecoder_ = function(name, json, fb, type, parser, level, opt_parentArray) {
if(opt_parentArray) {
fb.push(this.indent_(level) + opt_parentArray + ' = ' + parser + '; //'+name+'\n');
return type;
}
......@@ -251,10 +251,22 @@ LabComm.Decoder.prototype.buildDecoder_ = function(name, json, fb, type, parser,
return '"'+name+'":'+'"'+type+'"';
};
/**
* Creates indentation
* @param {number} level The indentation level to match
* @return {string} A string with the given level of indentation
* @private
*/
LabComm.Decoder.prototype.indent_ = function(level) {
return new Array(level + 1).join('\t');
};
/**
* Splits the variable name based on array literal
* e.g. int[3] => {type: "int", dim: "[3]"}
* @param {string} str The string representation of an array type
* @return {{type: string, dim: string}} An object consisting of the split string
*/
LabComm.Decoder.prototype.exctractArray_ = function(str) {
var index = str.indexOf('[');
if(index > 0) {
......@@ -263,113 +275,102 @@ LabComm.Decoder.prototype.exctractArray_ = function(str) {
return {type: str, dim: ''};
}
/**
* Decodes a signature recursively
* @param {string} name The name of the current field
* @param {Array<string>} json The final json representation of the fields
* @param {Array<string>} fb The final function body of the decoder
* @param {number} level The current nestling level
* @param {string=} opt_arrayVar The array variable to put the result in
* @param {string=} opt_parentArray The array variable to put the result in
* @return {string} The JSON part of the sub structure
* @throws {string} If decoding could not be performed
* @private
*/
LabComm.Decoder.prototype.decodeSignature_ = function(name, json, fb, level, opt_arrayVar) {
LabComm.Decoder.prototype.decodeSignature_ = function(name, json, fb, level, opt_parentArray) {
var type = this.data.getVarint();
switch(type) {
case LabComm.Constants.ARRAY:
var arrayVariable = 'a'+this.uid_++;
var func = [];
func.push('"'+name+'":[');
if(!opt_arrayVar) { //only add first array to final result
if(!opt_parentArray) {
//only add top-most array to final result
json.push('"'+name+'":'+arrayVariable);
}
//read number of indecies
var indecies = this.data.getVarint();
if(indecies === 0) {
throw "Array cant have 0 dimensions";
}
//get dimensions of the indecies and build for loop header
var dims = new Array(indecies);
var dimStr = new Array(indecies);
var dimVars = new Array(indecies);
for(var i = 0; i < indecies; i++) {
dims[i] = this.data.getVarint();
}
//console.log(name + '[' + dims + ']');
var dimStr = [];
if(indecies == 1) {
var dim = dims[0];
var dim = this.data.getVarint();
dims[i] = dim;
dimStr[i] = (dim === 0)? '_' : dim; // 0 means dynamic array
if(dim === 0) {
dimStr.push('_');
var arrayDimVar = 'd' + this.uid_++;
fb.push(this.indent_(level) + 'var ' + arrayDimVar + ' = data.getVarint();\n');
dim = arrayDimVar;
} else dimStr.push(dim);
fb.push(this.indent_(level) + 'var ' + arrayVariable + ' = new Array(' + dim + ');\n');
var indexVar = 'i' + this.uid_++;
fb.push(this.indent_(level) + 'for(var '+indexVar+' = 0; '+indexVar+' < ' + dim + '; '+indexVar+'++) {\n')
var arrayType = this.decodeSignature_(name, json, fb, level+1, arrayVariable+'['+indexVar+']');
fb.push(this.indent_(level) + '}\n'); // array for loop
if(opt_arrayVar) {
fb.push(this.indent_(level) + opt_arrayVar+' = '+arrayVariable+';\n');
}
dimVars[i] = dim;
}
else {
var dimVars = new Array(indecies);
for(var i = 0; i < indecies; i++) {
var dim = dims[i];
if(dim === 0) {
var arrayDimVar = 'd' + this.uid_++;
fb.push(this.indent_(level) + 'var ' + arrayDimVar + ' = data.getVarint();\n');
dim = arrayDimVar;
dimStr.push('_');
} else dimStr.push(dim);
dimVars[i] = dim;
}
var endFor = [];
var nextVar, indexVar, prevVar, firstVar = arrayVariable;
for(var i = 0; i < indecies; i++) {
indexVar = 'i' + this.uid_++;
fb.push(this.indent_(level+i) + 'var ' + arrayVariable + ' = new Array(' + dimVars[i] + ');\n');
fb.push(this.indent_(level+i) + 'for(var '+indexVar+' = 0; '+indexVar+' < ' + dimVars[i] + '; '+indexVar+'++) {\n')
var nextVar = 'a' + this.uid_++;
endFor.unshift(
this.indent_(level+i+1) + '}\n' +
this.indent_(level+i+1) + arrayVariable + '[' + indexVar + '] = ' + nextVar + ';\n' +
this.indent_(level+i) + '}\n'
);
prevVar = arrayVariable;
arrayVariable = nextVar;
}
var arrayType = this.decodeSignature_(name, json, fb, level+indecies, prevVar+'['+indexVar+']');
endFor.shift(); // remove last
fb.push(endFor.join(''));
if(opt_arrayVar) {
fb.push(this.indent_(level) + opt_arrayVar+' = '+firstVar+';\n');
}
//create for loop(s)
var endFor = [],
nextVar, indexVar, prevVar,
currentVar = arrayVariable;
for(var i = 0; i < indecies; i++) {
indexVar = 'i' + this.uid_++;
fb.push(this.indent_(level+i) + 'var ' + currentVar + ' = new Array(' + dimVars[i] + ');\n');
fb.push(this.indent_(level+i) + 'for(var '+indexVar+' = 0; '+indexVar+' < ' + dimVars[i] + '; '+indexVar+'++) {\n')
var nextVar = 'a' + this.uid_++;
endFor.unshift(
this.indent_(level+i+1) + '}\n' +
this.indent_(level+i+1) + arrayVariable + '[' + indexVar + '] = ' + nextVar + ';\n' +
this.indent_(level+i) + '}\n'
);
prevVar = currentVar;
currentVar = nextVar;
}
endFor.shift(); // remove last
//propagate decoding
var arrayType = this.decodeSignature_(name, json, fb, level+indecies, prevVar+'['+indexVar+']');
//if we only have one index we need to explicitly end the foor loop
if(indecies == 1) {
endFor.unshift(this.indent_(level) + '}\n');
}
if(opt_arrayVar) {
//create for loop footer
fb.push(endFor.join(''));
if(opt_parentArray) {
fb.push(this.indent_(level) + opt_parentArray+' = '+arrayVariable+';\n');
}
//if we have a parent array just return the type
if(opt_parentArray) {
return arrayType + '['+dimStr.join(', ')+']';
}
var arrayParts = this.exctractArray_(arrayType);
if(arrayParts.type.charAt(0)=='{') {
//we need to handle structs seperatly since they contains JSON object
if(arrayParts.type.charAt(0)=='{') {
return '"'+name+'['+dimStr.join(', ')+']'+arrayParts.dim+'":'+arrayParts.type;
}
return '"'+name+'['+dimStr.join(', ')+']'+arrayParts.dim+'":'+'"'+arrayParts.type+'"';
break;
case LabComm.Constants.STRUCT:
var func = [];
var subJson = [];
var structVar = 's' + this.uid_++;
if(opt_arrayVar) {
func.push('{');
if(opt_parentArray) {
func.push('{'); //we get the name from the parent array
}
else {
func.push('"'+name+'":{');
......@@ -386,8 +387,8 @@ LabComm.Decoder.prototype.decodeSignature_ = function(name, json, fb, level, opt
}
func.push("}");
if(opt_arrayVar) {
fb.push(this.indent_(level) + opt_arrayVar + ' = {' + subJson.join('') + '};\n');
if(opt_parentArray) {
fb.push(this.indent_(level) + opt_parentArray + ' = {' + subJson.join('') + '};\n');
}
else {
fb.push(this.indent_(level) + 'var ' + structVar + ' = {' + subJson.join('') + '};\n');
......@@ -395,21 +396,21 @@ LabComm.Decoder.prototype.decodeSignature_ = function(name, json, fb, level, opt
}
return func.join('');
case LabComm.Constants.BOOLEAN:
return this.buildDecoder_(name, json, fb, "bool", "data.getBoolean()", level, opt_arrayVar);
return this.buildDecoder_(name, json, fb, "bool", "data.getBoolean()", level, opt_parentArray);
case LabComm.Constants.BYTE:
return this.buildDecoder_(name, json, fb, "byte", "data.getByte()", level, opt_arrayVar);
return this.buildDecoder_(name, json, fb, "byte", "data.getByte()", level, opt_parentArray);
case LabComm.Constants.SHORT:
return this.buildDecoder_(name, json, fb, "short", "data.getShort()", level, opt_arrayVar);
return this.buildDecoder_(name, json, fb, "short", "data.getShort()", level, opt_parentArray);
case LabComm.Constants.INT:
return this.buildDecoder_(name, json, fb, "int", "data.getInt()", level, opt_arrayVar);
return this.buildDecoder_(name, json, fb, "int", "data.getInt()", level, opt_parentArray);
case LabComm.Constants.LONG:
return this.buildDecoder_(name, json, fb, "long", "data.getLong()", level, opt_arrayVar);
return this.buildDecoder_(name, json, fb, "long", "data.getLong()", level, opt_parentArray);
case LabComm.Constants.FLOAT:
return this.buildDecoder_(name, json, fb, "float", "data.getFloat()", level, opt_arrayVar);
return this.buildDecoder_(name, json, fb, "float", "data.getFloat()", level, opt_parentArray);
case LabComm.Constants.DOUBLE:
return this.buildDecoder_(name, json, fb, "double", "data.getDouble()", level, opt_arrayVar);
return this.buildDecoder_(name, json, fb, "double", "data.getDouble()", level, opt_parentArray);
case LabComm.Constants.STRING:
return this.buildDecoder_(name, json, fb, "string", "data.getString()", level, opt_arrayVar);
return this.buildDecoder_(name, json, fb, "string", "data.getString()", level, opt_parentArray);
}
throw "Unknown type found in signature: 0x" + type.toString(16);
};
......@@ -425,7 +426,7 @@ LabComm.Decoder.prototype.decodePackage = function(buffer) {
var tag = this.data.getVarint();
var len = this.data.getVarint();
console.log('0x' + tag.toString(16) + ':' + len);
//console.log('0x' + tag.toString(16) + ':' + len);
switch(tag) {
case LabComm.Constants.VERSION:
......@@ -441,10 +442,7 @@ LabComm.Decoder.prototype.decodePackage = function(buffer) {
var json = [],
fb = [];
var tmp = this.decodeSignature_(name, json, fb, 1);
console.log(tmp);
var struct = JSON.parse('{' + tmp + "}");
console.log(struct);
var struct = JSON.parse('{' + this.decodeSignature_(name, json, fb, 1) + "}");
var func = fb.join('') + '\n\treturn {' + json.join('') + '};';
......@@ -470,7 +468,7 @@ LabComm.Decoder.prototype.decodePackage = function(buffer) {
this.callback_(decl, sample);
break;
break;
}
}
};
......
'use strict';
(function() {
function decodeFromUrl(url) {
var oReq = new XMLHttpRequest();
oReq.open("GET", url, true);
oReq.responseType = "arraybuffer";
oReq.onload = function (oEvent) {
var arrayBuffer = oReq.response;
if (arrayBuffer) {
var buff = new LabComm.Buffer(new DataView(arrayBuffer));
window.decoder = new LabComm.Decoder(function(decl, data) {
testObject(decl, data);
});
window.decoder.decodePackage(buff);
}
};
oReq.send(null);
}
decodeFromUrl('http://fair-2014-4.lab1.cs.lth.se:8080/logdata/clamping/experiments/e12c4b31-5b32-41c0-b6a3-a5ad045aa997/data');
var referenceStruct ={
"log_message":{
"sequence":"int",
"message":"string",
"data":{
"f_value":"float",
"d_value":"double",
"l_value":"long",
"s_value":"short",
"b_value":"byte",
"theStack":{
"level":"int",
"name":"string",
"fatal":"bool"
}
},
"xyz[3]":"int",
"vect[_]":"int",
"fix_mat[3][_]":"int",
"dyn_mat[_][2]":"int",
"fix_mul[_, 4]":"int",
"dyn_mul[_, 2]":"int",
"fix_mulmat[1, _][_]":"int",
"fix_matmul[_][2, _]":"int",
"dyn_mulmat[_, _][3]":"int",
"dyn_matmul[1][_, 3]":"int",
"points[_]":{
"x":"float",
"y":"float"
}
}
};
var referenceObject = {
log_message: { //struct
sequence: 10, //int
message: 'Test', //string
data: { //struct
f_value: 1.6, //float
d_value: 1000000.0, //double
l_value: 145, //long
s_value: 10, //short
b_value: 0xA, //byte
theStack: { //struct
level: 2, //int
name: 'Robot.Move.toPos(10)', //string
fatal: true //bool
}
},
xyz: [1,2,3], //int[3]
vect: [8,7,6,5,4,3], //int[_]
fix_mat: [[1,2,3,4],[2,3,4,5],[3,4,5,6]], //int[3][_]
dyn_mat: [[1,2],[3,4]], //int[_][2]
fix_mul: [[1,2,3,4],[2,3,4,5],[3,4,5,6]], //int[_, 4]
dyn_mul: [[1,2],[3,4]], //int[_, 2]
fix_mulmat: [[[1,2,3],[4,5,6]]], //int[1, _][_]
fix_matmul: [[[1,2,3],[4,5,6]]], //int[_][2, _]
dyn_mulmat: [[[1,2,3],[4,5,6]]], //int[_, _][3]
dyn_matmul: [[[1,2,3],[4,5,6]]], //int[1][_, 3]
points: [
{x: 4.6, y: 2.3},
{x: 7.1, y: 1.0},
{x: 6.2, y: 5.9}
] //struct { float x; float y; } points[_]
}
};
function testObject(decl, obj) {
var i = 0;
console.log('-----Running tests-----');
testCase('Decode Sample Decalaration', referenceStruct, decl.struct) && i++;
testCase('Decode Data Sample', referenceObject, obj) && i++;
console.log('----------');
console.log(i + ' of 2 tests success');
console.log('----------');
}
function testCase(msg, r, t) {
var test = compareObjects(r, t);
console.log('Test "' + msg + '": ' + (test?'Success':'Fail'));
if(!test) {
console.log(t);
console.log('but expected: ');
console.log(r);
}
return test;
}
function compareObjects(o, p) {
var i,
keysO = Object.keys(o).sort(),
keysP = Object.keys(p).sort();
if (keysO.length !== keysP.length)
if (keysO.length !== keysP.length) {
return false;//not the same nr of keys
if (keysO.join('') !== keysP.join(''))
}
if (keysO.join('') !== keysP.join('')) {
return false;//different keys
}
for (i=0;i<keysO.length;++i) {
if (o[keysO[i]] instanceof Array) {
if (!(p[keysO[i]] instanceof Array))
var x = o[keysO[i]],
y = p[keysO[i]];
if (x instanceof Array) {
if (!(y instanceof Array))
return false;
//if (compareObjects(o[keysO[i]], p[keysO[i]] === false) return false
//would work, too, and perhaps is a better fit, still, this is easy, too
if (p[keysO[i]].sort().join('') !== o[keysO[i]].sort().join(''))
if (y.join('') !== x.join(''))
return false;
}
else if (o[keysO[i]] instanceof Date) {
if (!(p[keysO[i]] instanceof Date)
else if (x instanceof Date) {
if (!(y instanceof Date))
return false;
if ((''+o[keysO[i]]) !== (''+p[keysO[i]]))
if ((''+x) !== (''+y))
return false;
}
else if (o[keysO[i]] instanceof Function) {
if (!(p[keysO[i]] instanceof Function)
else if (x instanceof Function) {
if (!(y instanceof Function))
return false;
//ignore functions, or check them regardless?
//ignore functions
}
else if (o[keysO[i]] instanceof Object) {
if (!(p[keysO[i]] instanceof Object)
if (x instanceof Object) {
if (!(y instanceof Object))
return false;
if (o[keysO[i]] === o) {//self reference?
if (p[keysO[i]] !== p)
if (x === o) {//self reference?
if (y !== p)
return false;
}
else if (compareObjects(o[keysO[i]], p[keysO[i]]) === false)
else if (compareObjects(x, y) === false) {
return false;//WARNING: does not deal with circular refs other than ^^
}
}
else if(Number(x) === x) { //number
if((x|0) === x) { //int
if(x!==y) {
return false;
}
}
else { //float point, allow deviation
var diff = Math.abs(x-y);
if(diff > 1e-6) {
return false;
}