Skip to main content

oak_source_map/
vlq.rs

1//! VLQ Base64 encoding and decoding for Source Maps.
2//!
3//! VLQ (Variable Length Quantity) encoding is used in Source Map v3
4//! to compactly represent integers in the mappings string.
5
6use crate::{Result, SourceMapError};
7
8const BASE64_CHARS: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
9
10const BASE64_DECODE: [i8; 256] = {
11    let mut table = [-1i8; 256];
12    let mut i = 0;
13    while i < 64 {
14        table[BASE64_CHARS[i] as usize] = i as i8;
15        i += 1;
16    }
17    table
18};
19
20const VLQ_CONTINUATION_BIT: u8 = 0b100000;
21const VLQ_BASE_MASK: u8 = 0b011111;
22
23/// Encodes a signed integer to VLQ Base64 string.
24///
25/// # Example
26///
27/// ```
28/// use oak_source_map::vlq_encode;
29///
30/// assert_eq!(vlq_encode(0), "A");
31/// assert_eq!(vlq_encode(1), "C");
32/// assert_eq!(vlq_encode(-1), "D");
33/// assert_eq!(vlq_encode(16), "gB");
34/// assert_eq!(vlq_encode(12345), "uBtC");
35/// ```
36pub fn vlq_encode(value: i32) -> String {
37    let mut result = String::new();
38    let mut value = if value < 0 { ((-value) << 1) + 1 } else { value << 1 };
39
40    loop {
41        let mut digit = (value & VLQ_BASE_MASK as i32) as u8;
42        value >>= 5;
43
44        if value > 0 {
45            digit |= VLQ_CONTINUATION_BIT;
46        }
47
48        result.push(BASE64_CHARS[digit as usize] as char);
49
50        if value == 0 {
51            break;
52        }
53    }
54
55    result
56}
57
58/// Decodes a VLQ Base64 string to a signed integer.
59///
60/// # Example
61///
62/// ```
63/// use oak_source_map::vlq_decode;
64///
65/// assert_eq!(vlq_decode("A"), Ok((0, 1)));
66/// assert_eq!(vlq_decode("C"), Ok((1, 1)));
67/// assert_eq!(vlq_decode("D"), Ok((-1, 1)));
68/// assert_eq!(vlq_decode("gB"), Ok((16, 2)));
69/// ```
70pub fn vlq_decode(s: &str) -> Result<(i32, usize)> {
71    vlq_decode_from_slice(s.as_bytes())
72}
73
74/// Decodes VLQ from a byte slice.
75pub fn vlq_decode_from_slice(bytes: &[u8]) -> Result<(i32, usize)> {
76    let mut result: i32 = 0;
77    let mut shift: u32 = 0;
78    let mut count = 0;
79
80    for &byte in bytes {
81        count += 1;
82
83        let decoded = BASE64_DECODE[byte as usize];
84        if decoded < 0 {
85            return Err(SourceMapError::invalid_vlq(count - 1, format!("Invalid Base64 character: '{}'", byte as char)));
86        }
87
88        let decoded = decoded as u8;
89
90        result |= ((decoded & VLQ_BASE_MASK) as i32) << shift;
91        shift += 5;
92
93        if decoded & VLQ_CONTINUATION_BIT == 0 {
94            break;
95        }
96    }
97
98    let is_negative = result & 1 != 0;
99    result >>= 1;
100
101    if is_negative {
102        result = -result;
103    }
104
105    Ok((result, count))
106}
107
108/// Encodes multiple values into a VLQ string with separators.
109pub fn vlq_encode_many(values: &[i32]) -> String {
110    values.iter().map(|v| vlq_encode(*v)).collect()
111}
112
113/// Decodes multiple VLQ values from a string segment.
114pub fn vlq_decode_many(s: &str) -> Result<Vec<i32>> {
115    let bytes = s.as_bytes();
116    let mut pos = 0;
117    let mut result = Vec::new();
118
119    while pos < bytes.len() {
120        let (value, consumed) = vlq_decode_from_slice(&bytes[pos..])?;
121        result.push(value);
122        pos += consumed;
123    }
124
125    Ok(result)
126}