initial commit
This commit is contained in:
Executable
+317
@@ -0,0 +1,317 @@
|
||||
// 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(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(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
|
||||
};
|
||||
|
||||
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();
|
||||
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);
|
||||
};
|
||||
|
||||
});
|
||||
Reference in New Issue
Block a user