From 72b71cd26f4bd1a6b29bb5186025b8f3fa6dc9c6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Anton=20Klar=C3=A9n?= <fys09akl@student.lu.se>
Date: Fri, 6 Mar 2015 16:40:54 +0100
Subject: [PATCH] Added tests and refactored/cleanup array decoding

---
 lib/js/index.html | 117 +-------------------------------
 lib/js/lc.js      | 168 +++++++++++++++++++++++----------------------
 lib/js/tester.js  | 169 +++++++++++++++++++++++++++++++++++++++++-----
 3 files changed, 235 insertions(+), 219 deletions(-)

diff --git a/lib/js/index.html b/lib/js/index.html
index 8c355c0..ff10bd2 100644
--- a/lib/js/index.html
+++ b/lib/js/index.html
@@ -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>
diff --git a/lib/js/lc.js b/lib/js/lc.js
index 0182435..c3af377 100644
--- a/lib/js/lc.js
+++ b/lib/js/lc.js
@@ -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;
             }
     }
 };
diff --git a/lib/js/tester.js b/lib/js/tester.js
index 76a17b7..c8b5944 100644
--- a/lib/js/tester.js
+++ b/lib/js/tester.js
@@ -1,43 +1,176 @@
+'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;
+                }
+            }
+        }
+        else if (x !== y) {
+            return false;
         }
-        if (o[keysO[i]] !== p[keysO[i]])//change !== to != for loose comparison
-            return false;//not the same value
     }
     return true;
 }
+
+})();
+
-- 
GitLab