Skip to content

Commit

Permalink
Implement (de)serialization and binary encoding
Browse files Browse the repository at this point in the history
Fixes #9.
  • Loading branch information
novemberborn committed Apr 13, 2017
1 parent 7cff3ff commit 0d9dee6
Show file tree
Hide file tree
Showing 29 changed files with 958 additions and 6 deletions.
4 changes: 4 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const compare = require('./lib/compare')
const describe = require('./lib/describe')
const diff = require('./lib/diff')
const format = require('./lib/format')
const serialize = require('./lib/serialize')

exports.compare = compare.compare
exports.compareDescriptors = compare.compareDescriptors
Expand All @@ -15,3 +16,6 @@ exports.diffDescriptors = diff.diffDescriptors

exports.format = format.format
exports.formatDescriptor = format.formatDescriptor

exports.serialize = serialize.serialize
exports.deserialize = serialize.deserialize
17 changes: 16 additions & 1 deletion lib/complexValues/arguments.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,20 @@ function describe (props) {
}
exports.describe = describe

function deserialize (state, recursor) {
return new DeserializedArgumentsValue(state, recursor)
}
exports.deserialize = deserialize

const tag = Symbol('ArgumentsValue')
exports.tag = tag

class ArgumentsValue extends object.ObjectValue {
compare (expected) {
if (expected.isComplex !== true) return UNEQUAL

// Calling code may decide that Arguments can be compared to Arrays
// When used on the left-hand side of a comparison, argument values may be
// compared to arrays.
if (expected.stringTag === 'Array') return AMBIGUOUS

return super.compare(expected)
Expand All @@ -31,3 +37,12 @@ class ArgumentsValue extends object.ObjectValue {
Object.defineProperty(ArgumentsValue.prototype, 'tag', { value: tag })

const DescribedArgumentsValue = object.DescribedMixin(ArgumentsValue)

class DeserializedArgumentsValue extends object.DeserializedMixin(ArgumentsValue) {
compare (expected) {
// Deserialized argument values may only be compared to argument values.
return expected.isComplex === true && expected.stringTag === 'Array'
? UNEQUAL
: super.compare(expected)
}
}
6 changes: 6 additions & 0 deletions lib/complexValues/arrayBuffer.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ function describe (props) {
}
exports.describe = describe

function deserialize (state, recursor) {
return new DeserializedArrayBufferValue(state, recursor)
}
exports.deserialize = deserialize

const tag = Symbol('ArrayBufferValue')
exports.tag = tag

Expand All @@ -22,3 +27,4 @@ class ArrayBufferValue extends typedArray.TypedArrayValue {}
Object.defineProperty(ArrayBufferValue.prototype, 'tag', { value: tag })

const DescribedArrayBufferValue = typedArray.DescribedMixin(ArrayBufferValue)
const DeserializedArrayBufferValue = typedArray.DeserializedMixin(ArrayBufferValue)
6 changes: 6 additions & 0 deletions lib/complexValues/dataView.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ function describe (props) {
}
exports.describe = describe

function deserialize (state, recursor) {
return new DeserializedDataViewValue(state, recursor)
}
exports.deserialize = deserialize

const tag = Symbol('DataViewValue')
exports.tag = tag

Expand All @@ -21,3 +26,4 @@ class DataViewValue extends typedArray.TypedArrayValue {}
Object.defineProperty(DataViewValue.prototype, 'tag', { value: tag })

const DescribedDataViewValue = typedArray.DescribedMixin(DataViewValue)
const DeserializedDataViewValue = typedArray.DeserializedMixin(DataViewValue)
16 changes: 16 additions & 0 deletions lib/complexValues/date.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ function describe (props) {
}
exports.describe = describe

function deserialize (state, recursor) {
return new DeserializedDateValue(state, recursor)
}
exports.deserialize = deserialize

const tag = Symbol('DateValue')
exports.tag = tag

Expand All @@ -26,7 +31,18 @@ class DateValue extends object.ObjectValue {
}
})
}

serialize () {
return [this.value.toISOString(), super.serialize()]
}
}
Object.defineProperty(DateValue.prototype, 'tag', { value: tag })

const DescribedDateValue = object.DescribedMixin(DateValue)

class DeserializedDateValue extends object.DeserializedMixin(DateValue) {
constructor (state, recursor) {
super(state[1], recursor)
this.value = new Date(state[0])
}
}
16 changes: 16 additions & 0 deletions lib/complexValues/error.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ function describe (props) {
}
exports.describe = describe

function deserialize (state, recursor) {
return new DeserializedErrorValue(state, recursor)
}
exports.deserialize = deserialize

const tag = Symbol('ErrorValue')
exports.tag = tag

Expand Down Expand Up @@ -59,6 +64,10 @@ class ErrorValue extends object.ObjectValue {
}
})
}

serialize () {
return [this.name, super.serialize()]
}
}
Object.defineProperty(ErrorValue.prototype, 'tag', { value: tag })

Expand Down Expand Up @@ -111,3 +120,10 @@ class DescribedErrorValue extends object.DescribedMixin(ErrorValue) {
return { size, next }
}
}

class DeserializedErrorValue extends object.DeserializedMixin(ErrorValue) {
constructor (state, recursor) {
super(state[1], recursor)
this.name = state[0]
}
}
35 changes: 35 additions & 0 deletions lib/complexValues/function.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
'use strict'

const constants = require('../constants')
const getStringTag = require('../getStringTag')
const isEnumerable = require('../isEnumerable')
const NOOP_RECURSOR = require('../recursorUtils').NOOP_RECURSOR
const object = require('./object')

const UNEQUAL = constants.UNEQUAL
const SHALLOW_EQUAL = constants.SHALLOW_EQUAL

// Node.js 4 provides Function, more recent versions use GeneratorFunction
const generatorsHaveGeneratorTag = getStringTag(function * () {}) === 'GeneratorFunction'

function describe (props) {
const fn = props.value
return new DescribedFunctionValue(Object.assign({
Expand All @@ -17,6 +21,11 @@ function describe (props) {
}
exports.describe = describe

function deserialize (state, recursor) {
return new DeserializedFunctionValue(state, recursor)
}
exports.deserialize = deserialize

const tag = Symbol('FunctionValue')
exports.tag = tag

Expand All @@ -40,6 +49,10 @@ class FunctionValue extends object.ObjectValue {
}
})
}

serialize () {
return [this.name, generatorsHaveGeneratorTag, super.serialize()]
}
}
Object.defineProperty(FunctionValue.prototype, 'tag', { value: tag })

Expand Down Expand Up @@ -85,3 +98,25 @@ class DescribedFunctionValue extends object.DescribedMixin(FunctionValue) {
return { size, next }
}
}

class DeserializedFunctionValue extends object.DeserializedMixin(FunctionValue) {
constructor (state, recursor) {
super(state[2], recursor)
this.name = state[0]
this.trustStringTag = state[1]
}

compare (expected) {
if (this.tag !== expected.tag) return UNEQUAL
if (this.name !== expected.name) return UNEQUAL

// Assume `stringTag` is either 'Function' or 'GeneratorFunction', and that
// it always equals `ctor`. Since Node.js 4 only provides 'Function', even
// for generator functions, only compare `stringTag` if the serialized value
// legitimately would have been `Function`, and the expected `stringTag` can
// reliably be inferred.
if (this.trustStringTag && generatorsHaveGeneratorTag && this.stringTag !== expected.stringTag) return UNEQUAL

return SHALLOW_EQUAL
}
}
2 changes: 2 additions & 0 deletions lib/complexValues/global.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ function describe () {
}
exports.describe = describe

exports.deserialize = describe

const tag = Symbol('GlobalValue')
exports.tag = tag

Expand Down
16 changes: 16 additions & 0 deletions lib/complexValues/map.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ function describe (props) {
}
exports.describe = describe

function deserialize (state, recursor) {
return new DeserializedMapValue(state, recursor)
}
exports.deserialize = deserialize

const tag = Symbol('MapValue')
exports.tag = tag

Expand All @@ -32,6 +37,10 @@ class MapValue extends object.ObjectValue {
? UNEQUAL
: super.compare(expected)
}

serialize () {
return [this.size, super.serialize()]
}
}
Object.defineProperty(MapValue.prototype, 'tag', { value: tag })

Expand All @@ -55,3 +64,10 @@ class DescribedMapValue extends object.DescribedMixin(MapValue) {
return { size, next }
}
}

class DeserializedMapValue extends object.DeserializedMixin(MapValue) {
constructor (state, recursor) {
super(state[1], recursor)
this.size = state[0]
}
}
38 changes: 38 additions & 0 deletions lib/complexValues/object.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ function describe (props) {
}
exports.describe = describe

function deserialize (state, recursor) {
return new DeserializedObjectValue(state, recursor)
}
exports.deserialize = deserialize

const tag = Symbol('ObjectValue')
exports.tag = tag

Expand All @@ -47,12 +52,17 @@ class ObjectValue {
format (gutter, indent, innerIndent) {
return new ObjectFormatter(this, gutter, indent, innerIndent)
}

serialize () {
return [this.ctor, this.pointer, this.stringTag, this.unwrapped, this.isArray, this.isIterable, this.isList]
}
}
Object.defineProperty(ObjectValue.prototype, 'isComplex', { value: true })
Object.defineProperty(ObjectValue.prototype, 'tag', { value: tag })
exports.ObjectValue = ObjectValue

const DescribedObjectValue = DescribedMixin(ObjectValue)
const DeserializedObjectValue = DeserializedMixin(ObjectValue)

function DescribedMixin (base) {
return class extends base {
Expand Down Expand Up @@ -204,3 +214,31 @@ function DescribedMixin (base) {
}
}
exports.DescribedMixin = DescribedMixin

function DeserializedMixin (base) {
return class extends base {
constructor (state, recursor) {
super({
ctor: state[0],
pointer: state[1],
stringTag: state[2],
unwrapped: state[3],
isArray: state[4],
isIterable: state[5],
isList: state[6]
})

if (recursor) {
let replayState
this.createRecursor = () => {
const replay = recursorUtils.replay(replayState, () => ({ size: -1, next: recursor }))
replayState = replay.state
return replay.recursor.next
}
} else {
this.createRecursor = () => () => null
}
}
}
}
exports.DeserializedMixin = DeserializedMixin
11 changes: 11 additions & 0 deletions lib/complexValues/promise.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ function describe (props) {
}
exports.describe = describe

function deserialize (props) {
return new DeserializedPromiseValue(props)
}
exports.deserialize = deserialize

const tag = Symbol('PromiseValue')
exports.tag = tag

Expand All @@ -27,3 +32,9 @@ class DescribedPromiseValue extends object.DescribedMixin(PromiseValue) {
}
}

class DeserializedPromiseValue extends object.DeserializedMixin(PromiseValue) {
compare (expected) {
// Deserialized promises can never be compared using object references.
return super.compare(expected)
}
}
17 changes: 17 additions & 0 deletions lib/complexValues/regexp.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ function describe (props) {
}
exports.describe = describe

function deserialize (state, recursor) {
return new DeserializedRegexpValue(state, recursor)
}
exports.deserialize = deserialize

const tag = Symbol('RegexpValue')
exports.tag = tag

Expand Down Expand Up @@ -56,7 +61,19 @@ class RegexpValue extends object.ObjectValue {
}
})
}

serialize () {
return [this.flags, this.source, super.serialize()]
}
}
Object.defineProperty(RegexpValue.prototype, 'tag', { value: tag })

const DescribedRegexpValue = object.DescribedMixin(RegexpValue)

class DeserializedRegexpValue extends object.DeserializedMixin(RegexpValue) {
constructor (state, recursor) {
super(state[2], recursor)
this.flags = state[0]
this.source = state[1]
}
}
Loading

0 comments on commit 0d9dee6

Please sign in to comment.