/*
# reference (Python) versions of 'compress' and 'uncompress'
# only 'uncompress' function is featured lower as JavaScript
# if you want to unit test "roundtrip", just transcribe the reference
# 'compress' function from Python into JavaScript

def compress(data):

keys =   '0123456789abcdef'
values = 'klmnopqrstuvwxyz'
mapping = dict(zip(keys, values))
vals = []
for key in data.keys():
value = data[key]
try:
keystring = hex(key)[2:]
keystring = keystring[:-1] + mapping[keystring[-1:]]
except:
keystring = key.join(["'","'"])
#print('Keystring is %s' % keystring)

try:
if value < 0:
valuestring = hex(value)[3:]
numberprefix = '-'
else:
valuestring = hex(value)[2:]
numberprefix = ''
valuestring = numberprefix + valuestring[:-1] + mapping[valuestring[-1:]]
except:
if type(value) == dict:
valuestring = compress(value)
else:
raise Exception("Don't know what to do with value type %s" % type(value))

vals.append(keystring+valuestring)

return '{' + ''.join(vals) + '}'

def uncompress(data):

decoded = '0123456789abcdef'
encoded = 'klmnopqrstuvwxyz'
mapping = dict(zip(encoded, decoded))

sign = +1
stringmode = False
stringparts = []

output = {}

activeobject = output
parentchain = []

keyparts = ''
valueparts = ''

key = None

ending = set(encoded)

i = 1
l = len(data) - 1 # stripping starting, ending {}
while i != l: # stripping {}
# -, {, }, ' are special.

ch = data[i]
i += 1

if ch == "'":
if stringmode:
# end of string mode
stringmode = False
key = ''.join(stringparts)
else:
# start of string mode
stringmode = True
stringparts = []
elif stringmode == True:
#print("Adding %s to stringpart" % ch)
stringparts.append(ch)

elif ch == '{':
# start of object
parentchain.append( [activeobject, key] )
activeobject = {}
key = None
#DEBUG = True
elif ch == '}':
# end of object
parent, key = parentchain.pop()
parent[key] = activeobject
key = None
activeobject = parent
#DEBUG = False

elif ch == '-':
sign = -1
else:
# must be number
if key == None:
#debug("In Key. It is '%s', ch is '%s'" % (keyparts, ch))
if ch in ending:
#debug("End of key")
keyparts += mapping[ch]
key = int(keyparts, 16) * sign
sign = +1
keyparts = ''
else:
keyparts += ch
else:
#debug("In value. It is '%s', ch is '%s'" % (valueparts, ch))
if ch in ending:
#debug("End of value")
valueparts += mapping[ch]
activeobject[key] = int(valueparts, 16) * sign
sign = +1
key = None
valueparts = ''
else:
valueparts += ch

#debug(activeobject)

return output

 
Uncompresses data compressed into custom, base16-like format.
@public
@function
@param
@returns {Type}
function uncompress(data) {
    var decoded = '0123456789abcdef',
        encoded = 'klmnopqrstuvwxyz',
        mapping = {};

    for (var i = 0; i < encoded.length; i++) {
      mapping[encoded[i]] = decoded[i];
    }

    var undef,
        output = {},
        sign = 1,
        stringparts // undef. will be [] in string mode
    ,
        activeobject = output,
        parentchain = [],
        parent_key_pair,
        keyparts = '',
        valueparts = '',
        key // undef. will be Truthy when Key is resolved.
    ,
        datalen = data.length - 1 // stripping ending }
    ,
        ch;
    i = 1; // stripping starting {

    while (i != datalen) {
      // - { } ' are special.
      ch = data[i];
      i += 1;

      if (ch == "'") {
        if (stringparts) {
          // end of string mode
          key = stringparts.join('');
          stringparts = undef;
        } else {
          // start of string mode
          stringparts = [];
        }
      } else if (stringparts) {
        stringparts.push(ch);
      } else if (ch == '{') {
        // start of object
        parentchain.push([activeobject, key]);
        activeobject = {};
        key = undef;
      } else if (ch == '}') {
        // end of object
        parent_key_pair = parentchain.pop();
        parent_key_pair[0][parent_key_pair[1]] = activeobject;
        key = undef;
        activeobject = parent_key_pair[0];
      } else if (ch == '-') {
        sign = -1;
      } else {
        // must be number
        if (key === undef) {
          if (mapping.hasOwnProperty(ch)) {
            keyparts += mapping[ch];
            key = parseInt(keyparts, 16) * sign;
            sign = +1;
            keyparts = '';
          } else {
            keyparts += ch;
          }
        } else {
          if (mapping.hasOwnProperty(ch)) {
            valueparts += mapping[ch];
            activeobject[key] = parseInt(valueparts, 16) * sign;
            sign = +1;
            key = undef;
            valueparts = '';
          } else {
            valueparts += ch;
          }
        }
      }
    } // end while


    return output;
  }