Comparing version 0.2.0 to 0.2.1
@@ -176,10 +176,14 @@ // Copyright 2012 The Obvious Corporation. | ||
innerValue = x[i]; | ||
if (type.isUndefined(innerValue) && !hasOwnProperty(x, i)) { | ||
// The element is simply missing (as opposed to being | ||
// defined but set to `undefined`). | ||
continue; | ||
// A missing element is truly absent, as opposed to being | ||
// defined but set to `undefined`. | ||
var missing = | ||
type.isUndefined(innerValue) && !hasOwnProperty(x, i); | ||
visitor.visitArrayElement(x, i, undefined, innerVisitor, | ||
missing); | ||
if (!missing) { | ||
at++; | ||
} | ||
visitor.visitArrayElement(x, i, innerValue, innerVisitor); | ||
at++; | ||
} | ||
@@ -186,0 +190,0 @@ |
@@ -17,2 +17,3 @@ // Copyright 2012 The Obvious Corporation. | ||
var bufutil = require("./bufutil"); | ||
var iterate = require("./iterate"); | ||
var type = require("./type"); | ||
@@ -94,6 +95,3 @@ | ||
/** the function object `Object.hasOwnProperty` */ | ||
var hasOwnPropertyFunc = Object.hasOwnProperty; | ||
/* | ||
@@ -104,9 +102,2 @@ * Helper functions | ||
/** | ||
* Safe version of `obj.hasOwnProperty()`. | ||
*/ | ||
function hasOwnProperty(obj, name) { | ||
return hasOwnPropertyFunc.call(obj, name); | ||
} | ||
/** | ||
* Determine the sets of all back-referenced objects and all non-data | ||
@@ -118,51 +109,48 @@ * "holes" under the given root. The holes are filtered using the | ||
function findBackRefsAndHoles(root, holeFilter) { | ||
var allowHoles = type.isDefined(holeFilter); | ||
var allRefs = oid.createMap(); | ||
var holeSet = oid.createSet(); | ||
var allowHoles = type.isDefined(holeFilter); | ||
var holeSet = allowHoles ? oid.createSet() : undefined; | ||
visitValue(root); | ||
var holes = oid.createMap(); | ||
allowHoles = false; | ||
holeSet.forEach(function (obj) { | ||
var replacement = holeFilter(obj); | ||
holes.set(obj, replacement); | ||
visitValue(replacement); | ||
}); | ||
var backRefs = oid.createSet(); | ||
allRefs.forEach(function (key, value) { | ||
if (value > 1) { | ||
backRefs.add(key); | ||
/** | ||
* Check to see if holes are currently being allowed. If not, | ||
* complain appropriateley. | ||
*/ | ||
function holeCheck() { | ||
if (allowHoles) { | ||
return; | ||
} else if (type.isUndefined(holeFilter)) { | ||
throw new Error("Hole-ful graph, but no hole filter."); | ||
} else { | ||
throw new Error("Holes not allowed in holes."); | ||
} | ||
}); | ||
} | ||
return { backRefs: backRefs, holes: holes }; | ||
function addHole(x) { | ||
holeSet.add(x); | ||
} | ||
function visitValue(x) { | ||
switch (typeof x) { | ||
case type.BOOLEAN: | ||
case type.NUMBER: | ||
case type.UNDEFINED: { | ||
// Nothing to do for these. | ||
break; | ||
} | ||
function nop() { | ||
// This space intentionally left blank. | ||
} | ||
case type.FUNCTION: { | ||
visitFunction(x); | ||
break; | ||
} | ||
function keepGoing(obj, name, value, innerVisitor) { | ||
if (innerVisitor) { | ||
innerVisitor(); | ||
} | ||
} | ||
function addRef(obj, innerVisitor, mustBeHole) { | ||
if (mustBeHole) { | ||
holeCheck(); | ||
} | ||
case type.OBJECT: { | ||
visitObject(x); | ||
break; | ||
} | ||
var count = allRefs.get(obj, 0) + 1; | ||
case type.STRING: { | ||
visitString(x); | ||
break; | ||
} | ||
allRefs.set(obj, count); | ||
default: { | ||
throw new Error("Unknown value type: " + (typeof x)); | ||
if (count === 1) { | ||
if (mustBeHole) { | ||
addHole(obj); | ||
} else if (innerVisitor) { | ||
innerVisitor(); | ||
} | ||
@@ -172,87 +160,56 @@ } | ||
function visitString(x) { | ||
allRefs.set(x, allRefs.get(x, 0) + 1); | ||
function visitFunction(func, innerVisitor) { | ||
addRef(func, innerVisitor, true); | ||
} | ||
function visitFunction(x) { | ||
holeCheck(); | ||
var count = allRefs.get(x, 0) + 1; | ||
allRefs.set(x, count); | ||
if (count == 1) { | ||
addHole(x); | ||
function visitObject(obj, innerVisitor) { | ||
if (Buffer.isBuffer(obj)) { | ||
// Suppress inner visit for buffers. | ||
addRef(obj, undefined); | ||
} else { | ||
addRef(obj, innerVisitor); | ||
} | ||
} | ||
function visitObject(x) { | ||
if (x === null) { | ||
// Nothing to do. | ||
return; | ||
} | ||
var visitor = { | ||
visitObject: visitObject, | ||
visitArray: addRef, | ||
visitFunction: visitFunction, | ||
visitString: addRef, | ||
var count = allRefs.get(x, 0) + 1; | ||
allRefs.set(x, count); | ||
if (count > 1) { | ||
return; | ||
} | ||
var proto = Object.getPrototypeOf(x); | ||
visitObjectPrototype: keepGoing, | ||
visitObjectBinding: keepGoing, | ||
visitObjectGetter: keepGoing, | ||
visitObjectSetter: keepGoing, | ||
visitArrayElement: keepGoing, | ||
if (Array.isArray(x)) { | ||
// Precondition: We are not prepared to deal with an array | ||
// with a "weird" prototype. | ||
if (proto !== arrayPrototype) { | ||
throw new Error("Invalid array prototype."); | ||
} | ||
visitMap(x); | ||
} else if (proto === objectPrototype) { | ||
visitMap(x); | ||
} else if (Buffer.isBuffer(x)) { | ||
// Nothing more to do here. | ||
} else { | ||
holeCheck(); | ||
addHole(x); | ||
} | ||
} | ||
visitNumber: nop, | ||
visitBoolean: nop, | ||
visitUndefined: nop, | ||
visitNull: nop | ||
}; | ||
function visitMap(x) { | ||
var keys = Object.keys(x); | ||
var length = keys.length; | ||
iterate.iterate(root, visitor); | ||
for (var i = 0; i < length; i++) { | ||
var key = keys[i]; | ||
var descriptor = Object.getOwnPropertyDescriptor(x, key); | ||
var holes = undefined; | ||
visitValue(key); | ||
visitValue(descriptor.value); | ||
if (allowHoles) { | ||
holes = oid.createMap(); | ||
allowHoles = false; | ||
if (descriptor.get) { | ||
visitValue(descriptor.get); | ||
} | ||
holeSet.forEach(function (obj) { | ||
var replacement = holeFilter(obj); | ||
holes.set(obj, replacement); | ||
iterate.iterate(replacement, visitor); | ||
}); | ||
} | ||
if (descriptor.set) { | ||
visitValue(descriptor.set); | ||
} | ||
var backRefs = oid.createSet(); | ||
allRefs.forEach(function (key, value) { | ||
if (value > 1) { | ||
backRefs.add(key); | ||
} | ||
} | ||
}); | ||
function addHole(x) { | ||
holeSet.add(x); | ||
} | ||
/** | ||
* Check to see if holes are currently being allowed. If not, | ||
* complain appropriateley. | ||
*/ | ||
function holeCheck() { | ||
if (allowHoles) { | ||
return; | ||
} else if (type.isUndefined(holeFilter)) { | ||
throw new Error("Hole-ful graph, but no hole filter."); | ||
} else { | ||
throw new Error("Holes not allowed in holes."); | ||
} | ||
} | ||
return { backRefs: backRefs, holes: holes }; | ||
} | ||
@@ -312,3 +269,2 @@ | ||
var tempBuf = new Buffer(9); // temporary buffer | ||
var headerful = true; | ||
@@ -320,7 +276,5 @@ var backRefSet = undefined; | ||
function setHeaderless() { | ||
headerful = false; | ||
} | ||
var visitor; | ||
function writeGraph(root) { | ||
function writeGraph(root, headerful) { | ||
if (headerful) { | ||
@@ -338,3 +292,3 @@ writeMagic(); | ||
writeValue(root); | ||
iterate.iterate(root, visitor); | ||
@@ -359,99 +313,86 @@ if (headerful) { | ||
function writeValue(x) { | ||
switch (typeof x) { | ||
case type.BOOLEAN: writeBoolean(x); break; | ||
case type.FUNCTION: writeFunction(x); break; | ||
case type.NUMBER: writeNumber(x); break; | ||
case type.OBJECT: writeObject(x); break; | ||
case type.STRING: writeString(x); break; | ||
case type.UNDEFINED: writeUndefined(); break; | ||
default: { | ||
throw new Error("Unknown value type: " + (typeof x)); | ||
} | ||
} | ||
function writeBuffer(buf) { | ||
writeType(BUFFER_TYPE); | ||
visitNumber(buf.length); | ||
write(buf); | ||
} | ||
function writeBoolean(x) { | ||
writeType(x ? TRUE_TYPE : FALSE_TYPE); | ||
function writeHole(obj) { | ||
var replacement = holeMap.get(obj, obj); | ||
if (replacement === obj) { | ||
throw new Error("Missing replacement for hole."); | ||
} | ||
writeType(HOLE_DEF_TYPE); | ||
iterate.iterate(replacement, visitor); | ||
} | ||
function writeFunction(x) { | ||
if (!handleBackRef(x)) { | ||
writeHole(x); | ||
function handleBackRef(obj) { | ||
if (!backRefSet.has(obj)) { | ||
// No multiple references for this value. | ||
return false; | ||
} | ||
} | ||
function writeNumber(x) { | ||
// "small" exact integers (representable in 53 or fewer bits) are | ||
// written little-endian, prefixed by the number of bytes as | ||
// an ASCII character ('0'..'7'). Everything else is written as | ||
// an IEEE double, prefixed by 'D'. | ||
var index = backRefMap.get(obj); | ||
// The "true" argument here is the "noAssert" flag. We use | ||
// this not because we're sure that the buffer will be big | ||
// enough (in fact we're sure, but that's not a big deal), but | ||
// because if we don't use it, then writeDoubleLE() will throw | ||
// an exception if you try to write out infinities or NaNs, | ||
// even though the underlying code is perfectly happy doing | ||
// so. (See lib/buffer.js in the Node source for more | ||
// details.) | ||
tempBuf.writeDoubleLE(x, 1, true); | ||
if (type.isUndefined(index)) { | ||
// First time this value has been encountered. Add a | ||
// backref definition type tag, and then return false | ||
// to allow the write to proceed. | ||
writeType(BACK_REF_DEF_TYPE); | ||
backRefMap.set(obj, backRefCount); | ||
backRefCount++; | ||
return false; | ||
} | ||
var count = toSmallIntIfPossible(x, tempBuf); | ||
// Second or subsequent time this value has been encountered: | ||
// Write out a back-reference use, and return true to indicate | ||
// that there is nothing more to write in this case. | ||
if (count > 0) { | ||
write(tempBuf, 0, count); | ||
} else { | ||
tempBuf[0] = DOUBLE_TYPE; | ||
write(tempBuf); | ||
} | ||
writeType(BACK_REF_USE_TYPE); | ||
visitNumber(index); | ||
return true; | ||
} | ||
function writeString(x) { | ||
if (handleBackRef(x)) { | ||
function visitObject(obj, innerVisitor) { | ||
if (handleBackRef(obj)) { | ||
// Nothing else to do. | ||
return; | ||
} | ||
var length = x.length; | ||
writeType(STRING_TYPE); | ||
if (Object.getPrototypeOf(obj) === objectPrototype) { | ||
// Header: Type tag and total number of keys. | ||
writeType(MAP_TYPE); | ||
visitNumber(Object.keys(obj).length); | ||
if (x.length === 0) { | ||
writeNumber(0); | ||
// Main contents. | ||
innerVisitor(); | ||
} else if (Buffer.isBuffer(obj)) { | ||
writeBuffer(obj); | ||
} else { | ||
var buf = new Buffer(x); | ||
writeNumber(buf.length); | ||
write(buf); | ||
writeHole(obj); | ||
} | ||
} | ||
function visitObjectPrototype(obj, proto, innerVisitor) { | ||
throw new Error("Cannot write object with prototype"); | ||
} | ||
function writeUndefined() { | ||
writeType(UNDEFINED_TYPE); | ||
function visitObjectBinding(obj, name, props, innerVisitor) { | ||
visitString(name); | ||
innerVisitor(); | ||
} | ||
function writeObject(x) { | ||
if (x === null) { | ||
writeType(NULL_TYPE); | ||
} else if (handleBackRef(x)) { | ||
// Nothing else to do. | ||
} else if (Array.isArray(x)) { | ||
writeArray(x); | ||
} else if (Object.getPrototypeOf(x) === objectPrototype) { | ||
writeMap(x); | ||
} else if (Buffer.isBuffer(x)) { | ||
writeBuffer(x); | ||
} else { | ||
writeHole(x); | ||
} | ||
function visitObjectGetter(obj, name, getter, innerVisitor) { | ||
throw new Error("Cannot write object with dynamic getter"); | ||
} | ||
function writeArray(x) { | ||
var keys = Object.keys(x); | ||
var length = x.length; | ||
var count = 0; // number of items written; so as to skip array keys | ||
function visitObjectSetter(obj, name, getter, innerVisitor) { | ||
throw new Error("Cannot write object with dynamic setter"); | ||
} | ||
// Precondition: We are not prepared to deal with an array with | ||
// a "weird" prototype. | ||
if (Object.getPrototypeOf(x) !== arrayPrototype) { | ||
throw new Error("Invalid array prototype."); | ||
function visitArray(arr, innerVisitor) { | ||
if (handleBackRef(arr)) { | ||
// Nothing else to do. | ||
return; | ||
} | ||
@@ -463,105 +404,97 @@ | ||
writeType(ARRAY_TYPE); | ||
writeNumber(keys.length); | ||
writeNumber(length); | ||
visitNumber(Object.keys(arr).length); | ||
visitNumber(arr.length); | ||
// The regular array elements. | ||
// Main contents. | ||
innerVisitor(); | ||
} | ||
for (var i = 0; i < length; i++) { | ||
var value = x[i]; | ||
if (type.isUndefined(value) && !hasOwnProperty(x, i)) { | ||
// The element is simply missing (as opposed to being | ||
// defined but set to `undefined`). | ||
writeType(MISSING_ELEMENT_TYPE); | ||
} else { | ||
// The usual case (has a value of some sort). | ||
writeValue(value); | ||
count++; | ||
} | ||
function visitArrayElement(arr, index, value, innerVisitor, missing) { | ||
if (missing) { | ||
writeType(MISSING_ELEMENT_TYPE); | ||
} else { | ||
innerVisitor(); | ||
} | ||
} | ||
// The additional map bindings. The sort on the keys is done | ||
// to guarantee a canonical ordering. | ||
keys = keys.slice(count).sort(); | ||
writeNameBindings(x, keys); | ||
function visitFunction(func, innerVisitor) { | ||
if (!handleBackRef(func)) { | ||
writeHole(func); | ||
} | ||
} | ||
function writeMap(x) { | ||
// Sort the keys to guarantee a canonical ordering. | ||
var keys = Object.keys(x).sort(); | ||
var length = keys.length; | ||
function visitString(str) { | ||
if (handleBackRef(str)) { | ||
// Nothing else to do. | ||
return; | ||
} | ||
// Header: Type tag and total number of keys. | ||
writeType(MAP_TYPE); | ||
writeNumber(length); | ||
var length = str.length; | ||
writeType(STRING_TYPE); | ||
// Main contents. | ||
writeNameBindings(x, keys); | ||
if (str.length === 0) { | ||
visitNumber(0); | ||
} else { | ||
var buf = new Buffer(str); | ||
visitNumber(buf.length); | ||
write(buf); | ||
} | ||
} | ||
/** | ||
* Common binding write code for both arrays and maps. | ||
*/ | ||
function writeNameBindings(x, keys) { | ||
var length = keys.length; | ||
function visitNumber(num) { | ||
// "Small" exact integers (representable in 64 or fewer bits) are | ||
// written little-endian, prefixed by the number of bytes as | ||
// an ASCII character ('0'..'7'). Everything else is written as | ||
// an IEEE double, prefixed by 'D'. | ||
for (var i = 0; i < length; i++) { | ||
var key = keys[i]; | ||
var descriptor = Object.getOwnPropertyDescriptor(x, key); | ||
// The "true" argument here is the "noAssert" flag. We use | ||
// this not because we're sure that the buffer will be big | ||
// enough (in fact we're sure, but that's not a big deal), but | ||
// because if we don't use it, then writeDoubleLE() will throw | ||
// an exception if you try to write out infinities or NaNs, | ||
// even though the underlying code is perfectly happy doing | ||
// so. (See lib/buffer.js in the Node source for more | ||
// details.) | ||
tempBuf.writeDoubleLE(num, 1, true); | ||
if (descriptor.get || descriptor.set) { | ||
throw new Error("Cannot encode dynamic properties."); | ||
} | ||
var count = toSmallIntIfPossible(num, tempBuf); | ||
writeValue(key); | ||
writeValue(descriptor.value); | ||
if (count > 0) { | ||
write(tempBuf, 0, count); | ||
} else { | ||
tempBuf[0] = DOUBLE_TYPE; | ||
write(tempBuf); | ||
} | ||
} | ||
function writeBuffer(x) { | ||
writeType(BUFFER_TYPE); | ||
writeNumber(x.length); | ||
write(x); | ||
function visitBoolean(bool) { | ||
writeType(bool ? TRUE_TYPE : FALSE_TYPE); | ||
} | ||
function writeHole(x) { | ||
var replacement = holeMap.get(x, x); | ||
if (replacement === x) { | ||
throw new Error("Missing replacement for hole."); | ||
} | ||
function visitUndefined() { | ||
writeType(UNDEFINED_TYPE); | ||
} | ||
writeType(HOLE_DEF_TYPE); | ||
writeValue(replacement); | ||
function visitNull() { | ||
writeType(NULL_TYPE); | ||
} | ||
function handleBackRef(x) { | ||
if (!backRefSet.has(x)) { | ||
// No multiple references for this value. | ||
return false; | ||
} | ||
var visitor = { | ||
visitArray: visitArray, | ||
visitArrayElement: visitArrayElement, | ||
visitBoolean: visitBoolean, | ||
visitFunction: visitFunction, | ||
visitNull: visitNull, | ||
visitNumber: visitNumber, | ||
visitObject: visitObject, | ||
visitObjectBinding: visitObjectBinding, | ||
visitObjectGetter: visitObjectGetter, | ||
visitObjectPrototype: visitObjectPrototype, | ||
visitObjectSetter: visitObjectSetter, | ||
visitString: visitString, | ||
visitUndefined: visitUndefined | ||
}; | ||
var index = backRefMap.get(x); | ||
if (type.isUndefined(index)) { | ||
// First time this value has been encountered. Add a | ||
// backref definition type tag, and then return false | ||
// to allow the write to proceed. | ||
writeType(BACK_REF_DEF_TYPE); | ||
backRefMap.set(x, backRefCount); | ||
backRefCount++; | ||
return false; | ||
} | ||
// Second or subsequent time this value has been encountered: | ||
// Write out a back-reference use, and return true to indicate | ||
// that there is nothing more to write in this case. | ||
writeType(BACK_REF_USE_TYPE); | ||
writeValue(index); | ||
return true; | ||
} | ||
return { | ||
setHeaderless: setHeaderless, | ||
writeGraph: writeGraph | ||
writeGraph: writeGraph | ||
}; | ||
@@ -575,3 +508,2 @@ } | ||
function createParser(br, holeFiller) { | ||
var headerful = true; | ||
var read = br.read; | ||
@@ -581,7 +513,3 @@ var tempBuf = new Buffer(9); | ||
function setHeaderless() { | ||
headerful = false; | ||
} | ||
function parseGraph() { | ||
function parseGraph(headerful) { | ||
if (headerful) { | ||
@@ -823,4 +751,3 @@ readMagic(); | ||
return { | ||
parseGraph: parseGraph, | ||
setHeaderless: setHeaderless | ||
parseGraph: parseGraph | ||
}; | ||
@@ -841,3 +768,3 @@ } | ||
serializer.writeGraph(root); | ||
serializer.writeGraph(root, true); | ||
return bw.toBuffer(); | ||
@@ -853,3 +780,3 @@ } | ||
return parser.parseGraph(); | ||
return parser.parseGraph(true); | ||
} | ||
@@ -877,4 +804,3 @@ | ||
serializer.setHeaderless(); | ||
serializer.writeGraph(root); | ||
serializer.writeGraph(root, false); | ||
return bw.toBuffer(); | ||
@@ -890,4 +816,3 @@ } | ||
parser.setHeaderless(); | ||
return parser.parseGraph(); | ||
return parser.parseGraph(false); | ||
} | ||
@@ -894,0 +819,0 @@ |
{ | ||
"name": "bidar", | ||
"version": "0.2.0", | ||
"version": "0.2.1", | ||
"keywords": | ||
@@ -5,0 +5,0 @@ ["object", "serialization", "data", "graph"], |
@@ -74,3 +74,7 @@ Bidar: Binary Data Representation. | ||
### Bonus | ||
Though not directly about serialization, this module also provides | ||
a generic object iterator, which is handy for all sorts of things. | ||
Example | ||
@@ -160,3 +164,3 @@ ------- | ||
### function parse(buf, [holeFiller]) | ||
### parse(buf, [holeFiller]) | ||
@@ -190,3 +194,3 @@ Parse a serialized form into an object graph, returning the root of the | ||
### function parseNoHead(buf, [holeFiller]) | ||
### parseNoHead(buf, [holeFiller]) | ||
### parsePartialNoHead(buf, [startIndex], [holeFiller]) | ||
@@ -253,6 +257,12 @@ ### serializeNoHead(root, [holeFilter]) | ||
* `visitArrayElement(arr, index, value, innerVisitor)` -- called on array | ||
elements, in index order. This is called multiple times from an array | ||
inner visitor, and is always called before `visitObjectBinding() is | ||
called for the named object bindings. | ||
* `visitArrayElement(arr, index, value, innerVisitor, missing)` -- | ||
called on array elements, in index order. This is called multiple | ||
times from an array inner visitor, and is always called before | ||
`visitObjectBinding() is called for the named object bindings. The | ||
`missing` parameter (placed after `innerVisitor` both so that it | ||
is easily ignored and so that the `innerVisitor` is always the | ||
fourth argument for object binding iteration calls) is a boolean | ||
that indicates if an array element is actually missing (as opposed | ||
to being set to `undefined`); it will only ever be `true` if `value` | ||
is `undefined`. | ||
@@ -259,0 +269,0 @@ * `visitFunction(func, innerVisitor)` -- called on function objects. |
@@ -74,5 +74,9 @@ // Copyright 2012 The Obvious Corporation. | ||
function visitArrayElement(arr, index, value, innerVisitor) { | ||
addLog("visitArrayElement(" + index + ")"); | ||
doInner(innerVisitor); | ||
function visitArrayElement(arr, index, value, innerVisitor, missing) { | ||
if (missing) { | ||
addLog("visitArrayElement(" + index + ", missing)"); | ||
} else { | ||
addLog("visitArrayElement(" + index + ")"); | ||
doInner(innerVisitor); | ||
} | ||
} | ||
@@ -224,2 +228,4 @@ | ||
"visitArray()\n" + | ||
" visitArrayElement(0, missing)\n" + | ||
" visitArrayElement(1, missing)\n" + | ||
" visitArrayElement(2)\n" + | ||
@@ -226,0 +232,0 @@ " visitNumber(123)\n" + |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
363
86277
2022