Packages

A compact text encoding for ints, used in sourcemaps

Current section

Files

Jump to
vlq src vlq.gleam
Raw

src/vlq.gleam

import gleam/bit_array
import gleam/int
import gleam/list
const min_int_32 = -2_147_483_648
/// Encode a series of ints to a base64 VLQ string.
pub fn encode64(numbers: List(Int)) -> String {
list.fold(numbers, "", fn(acc, n) { acc <> encode_single(n) })
}
fn encode_single(n: Int) -> String {
case n == min_int_32 {
// Special case: -2^31 encodes as "B" (sign bit 1, magnitude 0)
True -> "B"
False -> {
let value = case n < 0 {
True -> int.bitwise_or(int.bitwise_shift_left(-n, 1), 1)
False -> int.bitwise_shift_left(n, 1)
}
encode_vlq(value, "")
}
}
}
fn encode_vlq(value: Int, acc: String) -> String {
let chunk = int.bitwise_and(value, 31)
let remaining = int.bitwise_shift_right(value, 5)
case remaining {
0 -> acc <> index_to_char(chunk)
_ -> encode_vlq(remaining, acc <> index_to_char(int.bitwise_or(chunk, 32)))
}
}
fn index_to_char(i: Int) -> String {
case i {
0 -> "A"
1 -> "B"
2 -> "C"
3 -> "D"
4 -> "E"
5 -> "F"
6 -> "G"
7 -> "H"
8 -> "I"
9 -> "J"
10 -> "K"
11 -> "L"
12 -> "M"
13 -> "N"
14 -> "O"
15 -> "P"
16 -> "Q"
17 -> "R"
18 -> "S"
19 -> "T"
20 -> "U"
21 -> "V"
22 -> "W"
23 -> "X"
24 -> "Y"
25 -> "Z"
26 -> "a"
27 -> "b"
28 -> "c"
29 -> "d"
30 -> "e"
31 -> "f"
32 -> "g"
33 -> "h"
34 -> "i"
35 -> "j"
36 -> "k"
37 -> "l"
38 -> "m"
39 -> "n"
40 -> "o"
41 -> "p"
42 -> "q"
43 -> "r"
44 -> "s"
45 -> "t"
46 -> "u"
47 -> "v"
48 -> "w"
49 -> "x"
50 -> "y"
51 -> "z"
52 -> "0"
53 -> "1"
54 -> "2"
55 -> "3"
56 -> "4"
57 -> "5"
58 -> "6"
59 -> "7"
60 -> "8"
61 -> "9"
62 -> "+"
_ -> "/"
}
}
/// Dencode a base64 VLQ string to a series of ints.
pub fn decode64(s: String) -> Result(List(Int), Nil) {
decode_bytes(bit_array.from_string(s), 0, 0, [])
}
fn decode_bytes(
bytes: BitArray,
acc: Int,
shift: Int,
results: List(Int),
) -> Result(List(Int), Nil) {
case bytes {
<<byte, rest:bytes>> -> {
case ascii_to_index(byte) {
Error(Nil) -> Error(Nil)
Ok(index) -> {
let chunk = int.bitwise_and(index, 31)
let new_acc =
int.bitwise_or(acc, int.bitwise_shift_left(chunk, shift))
let continuation = int.bitwise_and(index, 32) != 0
case continuation {
True -> decode_bytes(rest, new_acc, shift + 5, results)
False -> {
let value = decode_value(new_acc)
decode_bytes(rest, 0, 0, [value, ..results])
}
}
}
}
}
<<>> -> Ok(list.reverse(results))
_ -> Error(Nil)
}
}
fn decode_value(encoded: Int) -> Int {
let sign = int.bitwise_and(encoded, 1)
let magnitude = int.bitwise_shift_right(encoded, 1)
case sign, magnitude {
1, 0 -> min_int_32
1, _ -> -magnitude
_, _ -> magnitude
}
}
fn ascii_to_index(byte: Int) -> Result(Int, Nil) {
case byte {
_ if byte >= 65 && byte <= 90 -> Ok(byte - 65)
_ if byte >= 97 && byte <= 122 -> Ok(byte - 97 + 26)
_ if byte >= 48 && byte <= 57 -> Ok(byte - 48 + 52)
43 -> Ok(62)
47 -> Ok(63)
_ -> Error(Nil)
}
}