base64-vlq.js
  1  /* -*- Mode: js; js-indent-level: 2; -*- */
  2  /*
  3   * Copyright 2011 Mozilla Foundation and contributors
  4   * Licensed under the New BSD license. See LICENSE or:
  5   * http://opensource.org/licenses/BSD-3-Clause
  6   *
  7   * Based on the Base 64 VLQ implementation in Closure Compiler:
  8   * https://code.google.com/p/closure-compiler/source/browse/trunk/src/com/google/debugging/sourcemap/Base64VLQ.java
  9   *
 10   * Copyright 2011 The Closure Compiler Authors. All rights reserved.
 11   * Redistribution and use in source and binary forms, with or without
 12   * modification, are permitted provided that the following conditions are
 13   * met:
 14   *
 15   *  * Redistributions of source code must retain the above copyright
 16   *    notice, this list of conditions and the following disclaimer.
 17   *  * Redistributions in binary form must reproduce the above
 18   *    copyright notice, this list of conditions and the following
 19   *    disclaimer in the documentation and/or other materials provided
 20   *    with the distribution.
 21   *  * Neither the name of Google Inc. nor the names of its
 22   *    contributors may be used to endorse or promote products derived
 23   *    from this software without specific prior written permission.
 24   *
 25   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 26   * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 27   * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 28   * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 29   * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 30   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 31   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 32   * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 33   * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 34   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 35   * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 36   */
 37  
 38  var base64 = require('./base64');
 39  
 40  // A single base 64 digit can contain 6 bits of data. For the base 64 variable
 41  // length quantities we use in the source map spec, the first bit is the sign,
 42  // the next four bits are the actual value, and the 6th bit is the
 43  // continuation bit. The continuation bit tells us whether there are more
 44  // digits in this value following this digit.
 45  //
 46  //   Continuation
 47  //   |    Sign
 48  //   |    |
 49  //   V    V
 50  //   101011
 51  
 52  var VLQ_BASE_SHIFT = 5;
 53  
 54  // binary: 100000
 55  var VLQ_BASE = 1 << VLQ_BASE_SHIFT;
 56  
 57  // binary: 011111
 58  var VLQ_BASE_MASK = VLQ_BASE - 1;
 59  
 60  // binary: 100000
 61  var VLQ_CONTINUATION_BIT = VLQ_BASE;
 62  
 63  /**
 64   * Converts from a two-complement value to a value where the sign bit is
 65   * placed in the least significant bit.  For example, as decimals:
 66   *   1 becomes 2 (10 binary), -1 becomes 3 (11 binary)
 67   *   2 becomes 4 (100 binary), -2 becomes 5 (101 binary)
 68   */
 69  function toVLQSigned(aValue) {
 70    return aValue < 0
 71      ? ((-aValue) << 1) + 1
 72      : (aValue << 1) + 0;
 73  }
 74  
 75  /**
 76   * Converts to a two-complement value from a value where the sign bit is
 77   * placed in the least significant bit.  For example, as decimals:
 78   *   2 (10 binary) becomes 1, 3 (11 binary) becomes -1
 79   *   4 (100 binary) becomes 2, 5 (101 binary) becomes -2
 80   */
 81  function fromVLQSigned(aValue) {
 82    var isNegative = (aValue & 1) === 1;
 83    var shifted = aValue >> 1;
 84    return isNegative
 85      ? -shifted
 86      : shifted;
 87  }
 88  
 89  /**
 90   * Returns the base 64 VLQ encoded value.
 91   */
 92  exports.encode = function base64VLQ_encode(aValue) {
 93    var encoded = "";
 94    var digit;
 95  
 96    var vlq = toVLQSigned(aValue);
 97  
 98    do {
 99      digit = vlq & VLQ_BASE_MASK;
100      vlq >>>= VLQ_BASE_SHIFT;
101      if (vlq > 0) {
102        // There are still more digits in this value, so we must make sure the
103        // continuation bit is marked.
104        digit |= VLQ_CONTINUATION_BIT;
105      }
106      encoded += base64.encode(digit);
107    } while (vlq > 0);
108  
109    return encoded;
110  };
111  
112  /**
113   * Decodes the next base 64 VLQ value from the given string and returns the
114   * value and the rest of the string via the out parameter.
115   */
116  exports.decode = function base64VLQ_decode(aStr, aIndex, aOutParam) {
117    var strLen = aStr.length;
118    var result = 0;
119    var shift = 0;
120    var continuation, digit;
121  
122    do {
123      if (aIndex >= strLen) {
124        throw new Error("Expected more digits in base 64 VLQ value.");
125      }
126  
127      digit = base64.decode(aStr.charCodeAt(aIndex++));
128      if (digit === -1) {
129        throw new Error("Invalid base64 digit: " + aStr.charAt(aIndex - 1));
130      }
131  
132      continuation = !!(digit & VLQ_CONTINUATION_BIT);
133      digit &= VLQ_BASE_MASK;
134      result = result + (digit << shift);
135      shift += VLQ_BASE_SHIFT;
136    } while (continuation);
137  
138    aOutParam.value = fromVLQSigned(result);
139    aOutParam.rest = aIndex;
140  };