Comparing version 0.2.1 to 0.2.2
@@ -108,22 +108,4 @@ // Copyright 2012 The Obvious Corporation. | ||
var allRefs = oid.createMap(); | ||
var holeSet = allowHoles ? oid.createSet() : undefined; | ||
var holes = allowHoles ? oid.createMap() : undefined; | ||
/** | ||
* 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."); | ||
} | ||
} | ||
function addHole(x) { | ||
holeSet.add(x); | ||
} | ||
function nop() { | ||
@@ -140,4 +122,4 @@ // This space intentionally left blank. | ||
function addRef(obj, innerVisitor, mustBeHole) { | ||
if (mustBeHole) { | ||
holeCheck(); | ||
if (mustBeHole && !allowHoles) { | ||
throw new Error("Hole-ful graph, but no hole filter."); | ||
} | ||
@@ -151,3 +133,11 @@ | ||
if (mustBeHole) { | ||
addHole(obj); | ||
var already = holes.get(obj); | ||
if (!already) { | ||
var replacement = holeFilter(obj); | ||
if (replacement === obj) { | ||
throw new Error("Bogus hole non-replacement."); | ||
} | ||
holes.set(obj, replacement); | ||
iterate.iterate(replacement, visitor); | ||
} | ||
} else if (innerVisitor) { | ||
@@ -165,6 +155,9 @@ innerVisitor(); | ||
if (Buffer.isBuffer(obj)) { | ||
// Suppress inner visit for buffers. | ||
// Suppress the inner visit for buffers, because it is too | ||
// odious to imagine having to cons up all the keys for | ||
// the buffer indices. (Too bad there's no | ||
// `Object.getOwnNamedProperties()` or somesuch.) | ||
addRef(obj, undefined); | ||
} else { | ||
addRef(obj, innerVisitor); | ||
addRef(obj, innerVisitor, !type.isMap(obj)); | ||
} | ||
@@ -193,19 +186,6 @@ } | ||
var holes = undefined; | ||
if (allowHoles) { | ||
holes = oid.createMap(); | ||
allowHoles = false; | ||
holeSet.forEach(function (obj) { | ||
var replacement = holeFilter(obj); | ||
holes.set(obj, replacement); | ||
iterate.iterate(replacement, visitor); | ||
}); | ||
} | ||
var backRefs = oid.createSet(); | ||
allRefs.forEach(function (key, value) { | ||
if (value > 1) { | ||
backRefs.add(key); | ||
allRefs.forEach(function (ref, count) { | ||
if (count > 1) { | ||
backRefs.add(ref); | ||
} | ||
@@ -316,16 +296,11 @@ }); | ||
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 handleBackRef(obj) { | ||
/** | ||
* Handle the cases where the given object is either a back-reference | ||
* or an as-yet unwritten hole. Returns `true` if this function | ||
* ended up writing out the value or `false` if not. | ||
*/ | ||
function handleBackRefOrHole(obj) { | ||
if (!backRefSet.has(obj)) { | ||
// No multiple references for this value. | ||
return false; | ||
return handlePossibleHole(obj); | ||
} | ||
@@ -342,3 +317,3 @@ | ||
backRefCount++; | ||
return false; | ||
return handlePossibleHole(obj); | ||
} | ||
@@ -353,6 +328,21 @@ | ||
return true; | ||
function handlePossibleHole(obj) { | ||
if (!holeMap) { | ||
return false; | ||
} | ||
var replacement = holeMap.get(obj, obj); | ||
if (replacement === obj) { | ||
return false; | ||
} | ||
writeType(HOLE_DEF_TYPE); | ||
iterate.iterate(replacement, visitor); | ||
return true; | ||
} | ||
} | ||
function visitObject(obj, innerVisitor) { | ||
if (handleBackRef(obj)) { | ||
if (handleBackRefOrHole(obj)) { | ||
// Nothing else to do. | ||
@@ -372,3 +362,3 @@ return; | ||
} else { | ||
writeHole(obj); | ||
throw new Error("Missing replacement for hole."); | ||
} | ||
@@ -395,3 +385,3 @@ } | ||
function visitArray(arr, innerVisitor) { | ||
if (handleBackRef(arr)) { | ||
if (handleBackRefOrHole(arr)) { | ||
// Nothing else to do. | ||
@@ -421,4 +411,4 @@ return; | ||
function visitFunction(func, innerVisitor) { | ||
if (!handleBackRef(func)) { | ||
writeHole(func); | ||
if (!handleBackRefOrHole(func)) { | ||
throw new Error("Missing replacement for hole."); | ||
} | ||
@@ -428,3 +418,3 @@ } | ||
function visitString(str) { | ||
if (handleBackRef(str)) { | ||
if (handleBackRefOrHole(str)) { | ||
// Nothing else to do. | ||
@@ -431,0 +421,0 @@ return; |
@@ -30,2 +30,5 @@ // Copyright 2012 The Obvious Corporation. | ||
/** extended type name */ | ||
var DATE = "date"; | ||
/** type name */ | ||
@@ -38,2 +41,5 @@ var FUNCTION = "function"; | ||
/** extended type name */ | ||
var MAP = "map"; | ||
/** extended type name */ | ||
var NULL = "null"; | ||
@@ -56,3 +62,9 @@ | ||
/** the prototype of plain (map) objects */ | ||
var OBJECT_PROTOTYPE = Object.getPrototypeOf({}); | ||
/** the prototype of date objects */ | ||
var DATE_PROTOTYPE = Date.prototype; | ||
/* | ||
@@ -82,6 +94,10 @@ * Helper functions | ||
return NULL; | ||
} else if (Array.isArray(value)) { | ||
} else if (isArray(value)) { | ||
return ARRAY; | ||
} else if (Buffer.isBuffer(value)) { | ||
} else if (isBuffer(value)) { | ||
return BUFFER; | ||
} else if (isDate(value)) { | ||
return DATE; | ||
} else if (isMap(value)) { | ||
return MAP; | ||
} else { | ||
@@ -103,2 +119,3 @@ return OBJECT; | ||
case BOOLEAN: | ||
case DATE: | ||
case INT: | ||
@@ -130,7 +147,11 @@ case NUMBER: | ||
function isBoolean(x) { | ||
return (x === true) || (x === false); | ||
} | ||
// For symmetry. | ||
var isBuffer = Buffer.isBuffer; | ||
function isBoolean(x) { | ||
return (x === true) || (x === false); | ||
function isDate(x) { | ||
return Object.getPrototypeOf(x) === DATE_PROTOTYPE; | ||
} | ||
@@ -150,2 +171,22 @@ | ||
/** | ||
* A "map" is an object whose prototype is the default object prototype | ||
* and which furthermore defines no enumerable dynamic properties. | ||
*/ | ||
function isMap(x) { | ||
if (Object.getPrototypeOf(x) !== OBJECT_PROTOTYPE) { | ||
return false; | ||
} | ||
var keys = Object.keys(x); | ||
for (var i = 0; i < keys.length; i++) { | ||
var props = Object.getOwnPropertyDescriptor(x, keys[i]); | ||
if (props.get || props.set) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
} | ||
function isNumber(x) { | ||
@@ -198,2 +239,8 @@ return (typeof x) === NUMBER; | ||
function assertDate(x) { | ||
if (!isDate(x)) { | ||
failType(x, DATE); | ||
} | ||
} | ||
function assertDefined(x) { | ||
@@ -224,2 +271,8 @@ if (!isDefined(x)) { | ||
function assertMap(x) { | ||
if (!isMap(x)) { | ||
failType(x, MAP); | ||
} | ||
} | ||
function assertString(x) { | ||
@@ -254,2 +307,3 @@ if (!isString(x)) { | ||
assertBuffer: assertBuffer, | ||
assertDate: assertDate, | ||
assertDefined: assertDefined, | ||
@@ -259,2 +313,3 @@ assertFunction: assertFunction, | ||
assertNumber: assertNumber, | ||
assertMap: assertMap, | ||
assertString: assertString, | ||
@@ -266,2 +321,3 @@ assertUint: assertUint, | ||
isBuffer: isBuffer, | ||
isDate: isDate, | ||
isDefined: isDefined, | ||
@@ -271,2 +327,3 @@ isInt: isInt, | ||
isNumber: isNumber, | ||
isMap: isMap, | ||
isString: isString, | ||
@@ -273,0 +330,0 @@ isUint: isUint, |
{ | ||
"name": "bidar", | ||
"version": "0.2.1", | ||
"version": "0.2.2", | ||
"keywords": | ||
@@ -5,0 +5,0 @@ ["object", "serialization", "data", "graph"], |
@@ -155,9 +155,18 @@ Bidar: Binary Data Representation. | ||
A "hole" is any object that is not representable as pure data. This | ||
includes functions and (non-array, non-buffer) objects whose prototype | ||
is not the default object prototype. | ||
includes: | ||
* functions | ||
* non-array non-buffer objects whose prototype is not the default | ||
object prototype | ||
* objects that define any dynamic properties (getters or setters) | ||
The hole filter is called with a single argument -- the original | ||
"hole" object encountered in the graph -- and is expected to return a | ||
serializable pure data object in response. That object is marked as | ||
a "hole replacement" in the serialized output. | ||
serializable replacement object in response. That object is marked as | ||
a "hole replacement" in the serialized output. It is possible for a | ||
hole replacement to itself have holes in it; this is fine, so long as | ||
the hole replacer can successfully replace all the holes, eventually | ||
bottoming out at pure data in some form. | ||
@@ -258,3 +267,3 @@ ### parse(buf, [holeFiller]) | ||
times from an array inner visitor, and is always called before | ||
`visitObjectBinding() is called for the named object bindings. The | ||
`visitObjectBinding()` is called for the named object bindings. The | ||
`missing` parameter (placed after `innerVisitor` both so that it | ||
@@ -261,0 +270,0 @@ is easily ignored and so that the `innerVisitor` is always the |
@@ -314,3 +314,6 @@ // Copyright 2012 The Obvious Corporation. | ||
function testSimpleHole() { | ||
/** | ||
* Test top-level hole replacement, where the hole is a function. | ||
*/ | ||
function testSimpleHole_function() { | ||
var serialized = bidar.serialize(theHole, holeFilter); | ||
@@ -340,2 +343,55 @@ var parsed = bidar.parse(serialized, holeFiller); | ||
/** | ||
* Test top-level hole replacement, where the hole is an object with | ||
* a non-default prototype. | ||
*/ | ||
function testSimpleHole_prototype() { | ||
var theHole = Object.create({ x: "in proto" }); | ||
var theReplacement = "yay"; | ||
var serialized = bidar.serialize(theHole, holeFilter); | ||
var parsed = bidar.parse(serialized, holeFiller); | ||
assert.equal(parsed, theReplacement); | ||
function holeFilter(x) { | ||
assert.equal(x, theHole); | ||
return "blort"; | ||
} | ||
function holeFiller(x) { | ||
assert.equal(x, "blort"); | ||
return theReplacement; | ||
} | ||
} | ||
/** | ||
* Test top-level hole replacement, where the hole is an object with | ||
* a dynamic property binding. | ||
*/ | ||
function testSimpleHole_dynamic() { | ||
roundTrip({ get foo() { return 1; } }, "one"); | ||
roundTrip({ set foo(value) { /*empty*/ } }, "two"); | ||
roundTrip({ | ||
get bar() { return 1; }, | ||
set bar(value) { /*empty*/ } | ||
}); | ||
function roundTrip(theHole, theReplacement) { | ||
var serialized = bidar.serialize(theHole, holeFilter); | ||
var parsed = bidar.parse(serialized, holeFiller); | ||
assert.equal(parsed, theReplacement); | ||
function holeFilter(x) { | ||
assert.equal(x, theHole); | ||
return "blort"; | ||
} | ||
function holeFiller(x) { | ||
assert.equal(x, "blort"); | ||
return theReplacement; | ||
} | ||
} | ||
} | ||
function testInnerHole() { | ||
@@ -369,3 +425,6 @@ var obj = { x: hole1, y: hole2, z: [ hole1, hole2, "stuff" ] }; | ||
function test_fail_hole() { | ||
/** | ||
* Test hole replacement failures, when the hole is a function. | ||
*/ | ||
function test_fail_hole_function() { | ||
var obj = { a: hole }; | ||
@@ -382,2 +441,3 @@ | ||
// This is an attempt to replace a would-be hole with itself. | ||
function f2() { | ||
@@ -387,19 +447,50 @@ bidar.serialize(obj, filter); | ||
function filter(x) { | ||
// An arbitrary-but-different hole. | ||
return filter; | ||
// The same hole. | ||
return x; | ||
} | ||
} | ||
assert.throws(f2, /Holes not allowed in holes/); | ||
assert.throws(f2, /Bogus hole non-replacement/); | ||
function f3() { | ||
// This is a case where the first layer replacement is okay, but | ||
// the second is a circular reference. | ||
function f2() { | ||
bidar.serialize(obj, filter); | ||
function filter(x) { | ||
// The same hole. | ||
return x; | ||
// An arbitrary-but-different hole. | ||
return filter; | ||
} | ||
} | ||
assert.throws(f3, /Holes not allowed in holes/); | ||
assert.throws(f2, /Bogus hole non-replacement/); | ||
} | ||
/** | ||
* Test hole replacement failure, when the hole is an object with a | ||
* non-default prototype. | ||
*/ | ||
function test_fail_hole_prototype() { | ||
var obj = Object.create({ stuff: "in prototype" }); | ||
obj.foo = "bar"; | ||
function f1() { | ||
bidar.serialize(obj); | ||
} | ||
assert.throws(f1, /Hole-ful graph, but no hole filter/); | ||
} | ||
/** | ||
* Test hole replacement failure, when the hole is an object with a | ||
* dynamic binding. | ||
*/ | ||
function test_fail_hole_dynamic() { | ||
var obj = { | ||
get x() { return 10; } | ||
}; | ||
function f1() { | ||
bidar.serialize(obj); | ||
} | ||
assert.throws(f1, /Hole-ful graph, but no hole filter/); | ||
} | ||
function testLargeString() { | ||
@@ -484,5 +575,9 @@ var string1 = "abcdefg1234567890---THIS IS YOUR CAPTAIN SPEAKING---" + | ||
testCircularObject(); | ||
testSimpleHole(); | ||
testSimpleHole_function(); | ||
testSimpleHole_prototype(); | ||
testSimpleHole_dynamic(); | ||
testInnerHole(); | ||
test_fail_hole(); | ||
test_fail_hole_function(); | ||
test_fail_hole_prototype(); | ||
test_fail_hole_dynamic(); | ||
testLargeString(); | ||
@@ -489,0 +584,0 @@ |
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
90664
2145
372