365 lines
13 KiB
JavaScript
365 lines
13 KiB
JavaScript
// Copyright 2014 Cognitect. All Rights Reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS-IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
goog.provide("com.cognitect.transit.impl.decoder");
|
|
goog.require("com.cognitect.transit.util");
|
|
goog.require("com.cognitect.transit.delimiters");
|
|
goog.require("com.cognitect.transit.caching");
|
|
goog.require("com.cognitect.transit.types");
|
|
|
|
goog.scope(function () {
|
|
|
|
var decoder = com.cognitect.transit.impl.decoder,
|
|
util = com.cognitect.transit.util,
|
|
d = com.cognitect.transit.delimiters,
|
|
caching = com.cognitect.transit.caching,
|
|
types = com.cognitect.transit.types;
|
|
|
|
// =========================================================================
|
|
// Decoder
|
|
|
|
/**
|
|
* @constructor
|
|
*/
|
|
decoder.Tag = function Transit$Tag(s) {
|
|
this.str = s;
|
|
};
|
|
|
|
decoder.tag = function (s) {
|
|
return new decoder.Tag(s);
|
|
};
|
|
|
|
decoder.isTag = function (x) {
|
|
return x && (x instanceof decoder.Tag);
|
|
};
|
|
|
|
decoder.isGroundHandler = function (handler) {
|
|
switch (handler) {
|
|
case "_":
|
|
case "s":
|
|
case "?":
|
|
case "i":
|
|
case "d":
|
|
case "b":
|
|
case "'":
|
|
case "array":
|
|
case "map":
|
|
return true;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
/**
|
|
* A transit decoder
|
|
* @constructor
|
|
*/
|
|
decoder.Decoder = function Transit$Decoder(options) {
|
|
this.options = options || {};
|
|
this.handlers = {};
|
|
for (var h in this.defaults.handlers) {
|
|
this.handlers[h] = this.defaults.handlers[h];
|
|
}
|
|
for (var h in this.options["handlers"]) {
|
|
if (decoder.isGroundHandler(h)) {
|
|
throw new Error("Cannot override handler for ground type \"" + h + "\"");
|
|
}
|
|
this.handlers[h] = this.options["handlers"][h];
|
|
}
|
|
this.preferStrings = this.options["preferStrings"] != null ? this.options["preferStrings"] : this.defaults.preferStrings;
|
|
this.preferBuffers = this.options["preferBuffers"] != null ? this.options["preferBuffers"] : this.defaults.preferBuffers;
|
|
this.defaultHandler = this.options["defaultHandler"] || this.defaults.defaultHandler;
|
|
/* NOT PUBLIC */
|
|
this.mapBuilder = this.options["mapBuilder"];
|
|
this.arrayBuilder = this.options["arrayBuilder"];
|
|
};
|
|
|
|
|
|
decoder.Decoder.prototype.defaults = {
|
|
handlers: {
|
|
"_": function (v, d) {
|
|
return types.nullValue();
|
|
},
|
|
"?": function (v, d) {
|
|
return types.boolValue(v);
|
|
},
|
|
"b": function (v, d) {
|
|
return types.binary(v, d);
|
|
},
|
|
"i": function (v, d) {
|
|
return types.intValue(v);
|
|
},
|
|
"n": function (v, d) {
|
|
return types.bigInteger(v);
|
|
},
|
|
"d": function (v, d) {
|
|
return types.floatValue(v);
|
|
},
|
|
"f": function (v, d) {
|
|
return types.bigDecimalValue(v);
|
|
},
|
|
"c": function (v, d) {
|
|
return types.charValue(v);
|
|
},
|
|
":": function (v, d) {
|
|
return types.keyword(v);
|
|
},
|
|
"$": function (v, d) {
|
|
return types.symbol(v);
|
|
},
|
|
"r": function (v, d) {
|
|
return types.uri(v);
|
|
},
|
|
"z": function (v, d) {
|
|
return types.specialDouble(v);
|
|
},
|
|
|
|
// tagged
|
|
"'": function (v, d) {
|
|
return v;
|
|
},
|
|
"m": function (v, d) {
|
|
return types.date(v);
|
|
},
|
|
"t": function (v, d) {
|
|
return types.verboseDate(v);
|
|
},
|
|
"u": function (v, d) {
|
|
return types.uuid(v);
|
|
},
|
|
"set": function (v, d) {
|
|
return types.set(v);
|
|
},
|
|
"list": function (v, d) {
|
|
return types.list(v);
|
|
},
|
|
"link": function (v, d) {
|
|
return types.link(v);
|
|
},
|
|
"cmap": function (v, d) {
|
|
return types.map(v, false);
|
|
}
|
|
},
|
|
defaultHandler: function (c, val) {
|
|
return types.taggedValue(c, val);
|
|
},
|
|
preferStrings: true,
|
|
preferBuffers: true
|
|
};
|
|
|
|
/**
|
|
* @param {*} node
|
|
* @param {*} cache
|
|
* @param {boolean=} asMapKey
|
|
* @param {boolean=} tagValue
|
|
* @returns {*}
|
|
*/
|
|
decoder.Decoder.prototype.decode = function (node, cache, asMapKey, tagValue) {
|
|
if (node == null) return null;
|
|
|
|
var t = typeof node;
|
|
|
|
switch (t) {
|
|
case "string":
|
|
return this.decodeString(node, cache, asMapKey, tagValue);
|
|
break;
|
|
case "object":
|
|
if (util.isArray(node)) {
|
|
if (node[0] === "^ ") {
|
|
return this.decodeArrayHash(node, cache, asMapKey, tagValue);
|
|
} else {
|
|
return this.decodeArray(node, cache, asMapKey, tagValue);
|
|
}
|
|
} else {
|
|
return this.decodeHash(node, cache, asMapKey, tagValue);
|
|
}
|
|
break;
|
|
}
|
|
|
|
return node;
|
|
};
|
|
decoder.Decoder.prototype["decode"] = decoder.Decoder.prototype.decode;
|
|
|
|
decoder.Decoder.prototype.decodeString = function (string, cache, asMapKey, tagValue) {
|
|
if (caching.isCacheable(string, asMapKey)) {
|
|
var val = this.parseString(string, cache, false);
|
|
if (cache) {
|
|
cache.write(val, asMapKey);
|
|
}
|
|
return val;
|
|
} else if (caching.isCacheCode(string)) {
|
|
return cache.read(string, asMapKey);
|
|
} else {
|
|
return this.parseString(string, cache, asMapKey);
|
|
}
|
|
};
|
|
|
|
decoder.Decoder.prototype.decodeHash = function (hash, cache, asMapKey, tagValue) {
|
|
var ks = util.objectKeys(hash),
|
|
key = ks[0],
|
|
tag = ks.length == 1 ? this.decode(key, cache, false, false) : null;
|
|
|
|
if (decoder.isTag(tag)) {
|
|
var val = hash[key],
|
|
handler = this.handlers[tag.str];
|
|
if (handler != null) {
|
|
return handler(this.decode(val, cache, false, true), this);
|
|
} else {
|
|
return types.taggedValue(tag.str, this.decode(val, cache, false, false));
|
|
}
|
|
} else if (this.mapBuilder) {
|
|
if ((ks.length < (types.SMALL_ARRAY_MAP_THRESHOLD * 2)) && this.mapBuilder.fromArray) {
|
|
var nodep = [];
|
|
for (var i = 0; i < ks.length; i++) {
|
|
var strKey = ks[i];
|
|
nodep.push(this.decode(strKey, cache, true, false));
|
|
nodep.push(this.decode(hash[strKey], cache, false, false));
|
|
}
|
|
return this.mapBuilder.fromArray(nodep, hash);
|
|
} else {
|
|
var ret = this.mapBuilder.init(hash);
|
|
for (var i = 0; i < ks.length; i++) {
|
|
var strKey = ks[i];
|
|
ret = this.mapBuilder.add(ret,
|
|
this.decode(strKey, cache, true, false),
|
|
this.decode(hash[strKey], cache, false, false),
|
|
hash);
|
|
}
|
|
return this.mapBuilder.finalize(ret, hash);
|
|
}
|
|
} else {
|
|
var nodep = [];
|
|
|
|
for (var i = 0; i < ks.length; i++) {
|
|
var strKey = ks[i];
|
|
nodep.push(this.decode(strKey, cache, true, false));
|
|
nodep.push(this.decode(hash[strKey], cache, false, false));
|
|
}
|
|
|
|
return types.map(nodep, false);
|
|
}
|
|
};
|
|
|
|
decoder.Decoder.prototype.decodeArrayHash = function (node, cache, asMapKey, tagValue) {
|
|
if (this.mapBuilder) {
|
|
if ((node.length < ((types.SMALL_ARRAY_MAP_THRESHOLD * 2) + 1)) && this.mapBuilder.fromArray) {
|
|
var nodep = [];
|
|
for (var i = 1; i < node.length; i += 2) {
|
|
nodep.push(this.decode(node[i], cache, true, false));
|
|
nodep.push(this.decode(node[i + 1], cache, false, false));
|
|
}
|
|
return this.mapBuilder.fromArray(nodep, node);
|
|
} else {
|
|
var ret = this.mapBuilder.init(node);
|
|
for (var i = 1; i < node.length; i += 2) {
|
|
ret = this.mapBuilder.add(ret,
|
|
this.decode(node[i], cache, true, false),
|
|
this.decode(node[i + 1], cache, false, false),
|
|
node)
|
|
}
|
|
return this.mapBuilder.finalize(ret, node);
|
|
}
|
|
} else {
|
|
var nodep = [];
|
|
|
|
// collect keys
|
|
for (var i = 1; i < node.length; i += 2) {
|
|
nodep.push(this.decode(node[i], cache, true, false));
|
|
nodep.push(this.decode(node[i + 1], cache, false, false));
|
|
}
|
|
|
|
return types.map(nodep, false);
|
|
}
|
|
};
|
|
|
|
decoder.Decoder.prototype.decodeArray = function (node, cache, asMapKey, tagValue) {
|
|
if (tagValue) {
|
|
var ret = [];
|
|
for (var i = 0; i < node.length; i++) {
|
|
ret.push(this.decode(node[i], cache, asMapKey, false));
|
|
}
|
|
return ret;
|
|
} else {
|
|
var cacheIdx = cache && cache.idx;
|
|
// tagged value as 2-array case
|
|
if ((node.length === 2) &&
|
|
(typeof node[0] === "string")) {
|
|
var tag = this.decode(node[0], cache, false, false);
|
|
if (decoder.isTag(tag)) {
|
|
var val = node[1],
|
|
handler = this.handlers[tag.str];
|
|
if (handler != null) {
|
|
var ret = handler(this.decode(val, cache, asMapKey, true), this);
|
|
return ret;
|
|
} else {
|
|
return types.taggedValue(tag.str, this.decode(val, cache, asMapKey, false))
|
|
}
|
|
}
|
|
}
|
|
|
|
// rewind cache
|
|
if (cache && (cacheIdx != cache.idx)) {
|
|
cache.idx = cacheIdx;
|
|
}
|
|
|
|
if (this.arrayBuilder) {
|
|
// NOTE: hard coded for ClojureScript for now - David
|
|
if (node.length <= 32 && this.arrayBuilder.fromArray) {
|
|
var arr = [];
|
|
for (var i = 0; i < node.length; i++) {
|
|
arr.push(this.decode(node[i], cache, asMapKey, false));
|
|
}
|
|
return this.arrayBuilder.fromArray(arr, node);
|
|
} else {
|
|
var ret = this.arrayBuilder.init(node);
|
|
for (var i = 0; i < node.length; i++) {
|
|
ret = this.arrayBuilder.add(ret, this.decode(node[i], cache, asMapKey, false), node);
|
|
}
|
|
return this.arrayBuilder.finalize(ret, node);
|
|
}
|
|
} else {
|
|
var ret = [];
|
|
for (var i = 0; i < node.length; i++) {
|
|
ret.push(this.decode(node[i], cache, asMapKey, false));
|
|
}
|
|
return ret;
|
|
}
|
|
}
|
|
};
|
|
|
|
decoder.Decoder.prototype.parseString = function (string, cache, asMapKey) {
|
|
if (string.charAt(0) === d.ESC) {
|
|
var c = string.charAt(1);
|
|
if (c === d.ESC || c === d.SUB || c === d.RES) {
|
|
return string.substring(1);
|
|
} else if (c === d.TAG) {
|
|
return decoder.tag(string.substring(2));
|
|
} else {
|
|
var handler = this.handlers[c];
|
|
if (handler == null) {
|
|
return this.defaultHandler(c, string.substring(2));
|
|
} else {
|
|
return handler(string.substring(2), this);
|
|
}
|
|
}
|
|
} else {
|
|
return string;
|
|
}
|
|
};
|
|
|
|
decoder.decoder = function (options) {
|
|
return new decoder.Decoder(options);
|
|
};
|
|
|
|
});
|