This commit is contained in:
Douglas Crockford 2018-04-29 14:14:49 -07:00
parent 2a76286e00
commit 58df70e97f
3 changed files with 424 additions and 7 deletions

View file

@ -1,6 +1,6 @@
/*
cycle.js
2017-02-07
2017-06-12
Public Domain.
@ -69,12 +69,13 @@ if (typeof JSON.decycle !== "function") {
// one of the weird builtin objects.
if (
typeof value === "object" && value !== null &&
!(value instanceof Boolean) &&
!(value instanceof Date) &&
!(value instanceof Number) &&
!(value instanceof RegExp) &&
!(value instanceof String)
typeof value === "object"
&& value !== null
&& !(value instanceof Boolean)
&& !(value instanceof Date)
&& !(value instanceof Number)
&& !(value instanceof RegExp)
&& !(value instanceof String)
) {
// If the value is an object or array, look to see if we have already

380
jsonzip.js Normal file
View file

@ -0,0 +1,380 @@
// jsonzip.js
// 2014-05-20
// This will be a JavaScript implementation of JSONzip.
var JSONzip = (function () {
'use strict';
// Constants
var bcd = [
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', '-', '+', 'E'
],
education = 1000000,
end = 256, // End of string code.
endOfNumber = bcd.length,
// The first positive integer that cannot be
int4 = 16, // encoded in 4 bits.
int7 = 144, // encoded in 7 bits.
int14 = 16528, // encoded in 14 bits.
// The value code for
zipEmptyObject = 0, // an empty object.
zipEmptyArray = 1, // an empty array.
zipTrue = 2, // true.
zipFalse = 3, // false.
zipNull = 4, // null.
zipObject = 5, // a non-empty object.
zipArrayString = 6, // an array with a string as its first element.
zipArrayValue = 7; // an array with other value as its first element.
function huff(domain) {
// The huff function produces a Huffman encoder/decoder object.
var symbols = [],
table,
toLearn = 1000000,
upToDate = false,
width;
// Make the leaf symbols.
for (int i = 0; i < domain; i += 1) {
symbols[i] = {
integer: i
weight: 0
};
}
// Make the links.
for (int i = domain; i < length; i += 1) {
symbols[i] = {
weight: 0
};
}
return {
generate: function () {
var avail, first, head, i, next, previous, second, symbol;
if (!upToDate) {
// Phase One: Sort the symbols by weight into a linked list.
head = symbols[0];
previous = head;
table = undefined;
head.next = undefined;
for (i = 1; i < domain; i += 1) {
symbol = symbols[i];
// If this symbol weighs less than the head, then it becomes the new head.
if (symbol.weight < head.weight) {
symbol.next = head;
head = symbol;
} else {
// We will start the search from the previous symbol instead of the head unless
// the current symbol weights less than the previous symbol.
if (symbol.weight < previous.weight) {
previous = head;
}
// Find a connected pair (previous and next) where the symbol weighs the same
// or more than previous but less than the next. Link the symbol between them.
while (true) {
next = previous.next;
if (next === undefined || symbol.weight < next.weight) {
break;
}
previous = next;
}
symbol.next = next;
previous.next = symbol;
previous = symbol;
}
}
// Phase Two: Make new symbols from the two lightest symbols until only one
// symbol remains. The final symbol becomes the root of the table binary tree.
avail = domain;
previous = head;
while (true) {
first = head;
second = first.next;
head = second.next;
symbol = symbols[avail];
avail += 1;
symbol.weight = first.weight + second.weight;
symbol.zero = first;
symbol.one = second;
symbol.back = undefined;
first.back = symbol;
second.back = symbol;
if (head === undefined) {
break;
}
// Insert the new symbol back into the sorted list.
if (symbol.weight < head.weight) {
symbol.next = head;
head = symbol;
previous = head;
} else {
while (true) {
next = previous.next;
if (next === undefined || symbol.weight < next.weight) {
break;
}
previous = next;
}
symbol.next = next;
previous.next = symbol;
previous = symbol;
}
}
// The last remaining symbol is the root of the table.
table = symbol;
upToDate = true;
}
},
read: function read(bitreader) {
var symbol = table;
width = 0;
while (symbol.integer === undefined) {
width += 1;
symbol = bitreader.bit()
? symbol.one
: symbol.zero;
}
tick(symbol.integer);
return symbol.integer;
},
tick: function tick(int) {
if (toLearn > 0) {
toLearn -= 1;
symbols[value].weight += 1;
upToDate = false;
}
},
write: function write(value, bitwriter) {
width = 0;
(function writebit(symbol) {
var back = symbol.back;
if (back !== undefined) {
width += 1;
writebit(back);
if (back.zero === symbol) {
return bitwriter.zero();
}
return bitwriter.one();
}
}(symbols[value]));
tick(value);
}
};
}
function make_state() {
return {
namehuff: huff(end + 1),
namehuffext: huff(end + 1),
namekeep: keep(9),
stringhuff: huff(end + 1),
stringhuffext: huff(end + 1),
stringkeep: keep(11),
valuekeep: keep(10),
generate: function generate() {
this.namehuff.generate();
this.namehuffext.generate();
this.stringhuff.generate();
this.stringhuffext.generate();
}
};
}
return {
encoder: function (writer) {
var nr_bits = 0, // The number of bits available in unwritten
state = make_state(),
unwritten = 0, // The number of bits written so far
vacant = 0; // The unwritten byte
// The encoder function returns a new encoder object.
// A writer is a function that takes a number (0..255) and delivers it to an
// output.
function write(bits, width) {
// Write bits:width to the writer, a function that accepts the next byte of
// output.
var give;
if (bits === 0 && width === 0) {
return;
}
if (width <= 0 || width > 32) {
throw new TypeError("Bad read width " + width);
}
while (width > 0) {
give = Math.min(width, vacant);
unwritten |= (
(bits >>> (width - give)) & ((1 << give) - 1)
) << (vacant - give);
width -= give;
nrBits += give;
vacant -= give;
if (vacant == 0) {
writer(unwritten);
unwritten = 0;
vacant = 8;
}
}
}
function one() {
return write(1, 1);
}
function zero() {
return write(0, 1);
}
function pad(width) {
var gap = nrBits % width,
padding;
if (gap < 0) {
gap += width;
}
if (gap != 0) {
padding = width - gap;
while (padding > 0) {
zero();
padding -= 1;
}
}
}
return {
flush: function () {
},
pad: function (size) {
},
zip: function (value) {
state.generate();
}
};
},
decoder: function (reader) {
// The decoder function returns a new decoder object.
// A reader is a function that returns the next byte from the input,
// or undefined if there is nothing left.
var available = 0, // The number of bits available in unread
nr_bits = 0, // The number of bits read so far
state = make_state(),
unread = 0; // The unread byte
function read(width) {
// Read (width) bits from the reader, returning the bits as a small positive
// integer.
var result = 0,
take; // The number of bits to take from the current byte
// If no bits are requested, return zero.
if (width === 0) {
return 0;
}
// Make sure the width is reasonable.
if (width < 0 || width > 32) {
throw new TypeError("Bad read width " + width);
}
// Loop until the width is satisified.
while (width > 0) {
// If all of the available bits have been taken, read the next byte.
if (available == 0) {
unread = reader();
if (unread === undefined || unread === '') {
throw new TypeError("Attempt to read past end.");
}
if typeof unread === 'string') {
if (unread.length !== 1) {
throw new TypeError("Data size error.");
}
unread = unread.charCodeAt(0);
}
if (typeof unread !== 'number') {
throw new TypeError("Data type error.");
}
if (unread < 0) {
unread += 256;
}
if (unread < 0 || unread >= 256) {
throw new TypeError("Data size error.");
}
available = 8;
}
// Take some bits from the current unread byte and combine them into the result.
take = Math.min(width, available);
result |= (
(unread >>> (available - take)) & ((1 << take) - 1)
) << (width - take);
nr_bits += take;
available -= take;
width -= take;
}
return result;
}
function bit() {
// Read a bit, returning it as a boolean.
return read(1) != 0;
}
function pad(factor) {
var padding = factor - (nr_bits % factor),
result = true;
while (padding > 0) {
if (bit()) {
result = false;
}
padding -= 1;
}
return result;
}
return {
pad: function (size) {
},
unzip: function () {
state.generate();
}
};
}
};
}());

36
test.html Normal file
View file

@ -0,0 +1,36 @@
<html><body><script src="cycle.js"></script>
<pre><p id=output></p>
<script>
function out(s) {
document.getElementById('output').innerHTML += s + '<br>';
}
function doTest() {
var i, o, s, jsonObj;
try {
o = {
number: 98.6,
string: 'Why is it called string?'
};
o.object = o;
o.array = [o];
out(JSON.stringify(JSON.decycle(
[o, o],
function (value) {
if (typeof value === 'string') {
return 'because';
} else {
return value;
}
}
)));
} catch (e) {
debugger;
out(" name: " + e.name);
out(" message: " + e.message);
}
}
doTest();
</script></pre></body></html>