vlq_parser/
lib.rs

1pub type RawMappings = Vec<Vec<Vec<i32>>>;
2pub type Mappings = Vec<Vec<Option<(i32, i32, i32, i32)>>>;
3use std::collections::HashMap;
4
5pub const COMMA_CHAR: char = ',';
6pub const SPACE_CHAR: char = ' ';
7pub const SEMICOLON_CHAR: char = ';';
8pub const VLQ_TABLE: &str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
9
10fn create_lookup_tables() -> (HashMap<char, u8>, Vec<char>) {
11    let chars = VLQ_TABLE;
12    let mut char_to_integer = HashMap::new();
13    let mut integer_to_char = vec![SPACE_CHAR; 65];
14
15    for (i, c) in chars.chars().enumerate() {
16        char_to_integer.insert(c, i as u8);
17        integer_to_char[i] = c;
18    }
19
20    (char_to_integer, integer_to_char)
21}
22
23fn decode(string: &str) -> Vec<i32> {
24    let (char_to_integer, _) = create_lookup_tables();
25    let mut result = Vec::new();
26    let mut shift = 0;
27    let mut value = 0;
28
29    for c in string.chars() {
30        let integer = match char_to_integer.get(&c) {
31            Some(&val) => val as i32,
32            None => continue,
33        };
34
35        let has_continuation_bit = integer & 32;
36        let integer = integer & 31;
37        value += integer << shift;
38
39        if has_continuation_bit != 0 {
40            shift += 5;
41        } else {
42            let should_negate = value & 1;
43            value >>= 1;
44
45            if should_negate != 0 {
46                result.push(if value == 0 { -0x80000000 } else { -value });
47            } else {
48                result.push(value);
49            }
50            value = 0;
51            shift = 0;
52        }
53    }
54    result
55}
56
57pub fn parse(mappings: &str) -> Mappings {
58    let vlqs = mappings
59        .split(SEMICOLON_CHAR)
60        .map(|line| line.split(COMMA_CHAR).map(decode).collect::<Vec<_>>())
61        .collect::<Vec<_>>();
62
63    process(vlqs)
64}
65
66fn process(decoded: RawMappings) -> Mappings {
67    let mut source_file_index = 0;
68    let mut source_code_line = 0;
69    let mut source_code_column = 0;
70
71    decoded
72        .into_iter()
73        .map(|line| {
74            let mut generated_code_column = 0;
75
76            line.into_iter()
77                .map(|segment| {
78                    if segment.len() == 0 {
79                        return None;
80                    }
81                    generated_code_column += segment[0];
82
83                    source_file_index += segment[1];
84                    source_code_line += segment[2];
85                    source_code_column += segment[3];
86
87                    Some((
88                        generated_code_column,
89                        source_file_index,
90                        source_code_line,
91                        source_code_column,
92                    ))
93                })
94                .collect()
95        })
96        .collect()
97}
98
99pub fn to_source(mappings: &Mappings, line: usize, column: usize) -> Option<(usize, usize)> {
100    let Some(line_mappings) = mappings.get(line - 1) else {
101        return None;
102    };
103
104    for &segment in line_mappings {
105        let Some((gen_col, _, src_line, src_col)) = segment else {
106            continue;
107        };
108        if gen_col as usize >= column {
109            return Some((((src_line + 1) as usize), src_col as usize));
110        }
111    }
112    None
113}
114
115#[cfg(test)]
116mod tests {
117    use crate::{parse, to_source};
118
119    #[test]
120    fn test_dummy() {
121        let input = ";;;;IAQmB,OAAO,GAAE,MAAM,CAAA;;OARzB,KAAK,MAAA,aAAA,CAAA;OACP,CAAQ,MAAA,8BAAA,CAAA;AAEf,MAAM,MAAM,GAAG,MAAM,CAAC;OAIV,SAAA,MAAA;IAFZ,YAAA,MAAA,EAAA,MAAA,EAAA,cAAA,EAAA,MAAA,GAAA,CAAA,CAAA,EAAA,YAAA,GAAA,SAAA,EAAA,SAAA;;;;;uBAGqC,aAAa,CAAA;;;IAL5B,CAAA;;;;;;;;;;;;;;IAKpB,OAAO,CAAC,QAAQ,CAAU,OAAA,EAAA,MAAM,CAAiB;IAEjD,aAAA;;YACE,GAAG,CAAA,MAAA,EAAA,CAAA;YAAH,GAAG,CAuBF,MAAM,CAAC,MAAM,CAAA,CAAA;;;YAtBZ,MAAM,CAAA,MAAA,EAAA,CAAA;YAAN,MAAM,CAoBL,KAAK,CAAC,MAAM,CAAA,CAAA;;;YAnBX,IAAI,QAAC,UAAU,CAAA,CAAA;YAAf,IAAI,CACD,QAAQ,CAAC,EAAE,CAAA,CAAA;YADd,IAAI,CAED,UAAU,CAAC,UAAU,CAAC,IAAI,CAAA,CAAA;YAF7B,IAAI,CAGD,OAAO,CAAC,GAAG,EAAE;gBACZ,GAAO,CAAA;YACT,CAAC,CAAA,CAAA;;QALH,IAAI,CAAA,GAAA,EAAA,CAAA;;YAMJ,IAAI,QAAC,WAAW,CAAA,CAAA;YAAhB,IAAI,CACD,QAAQ,CAAC,EAAE,CAAA,CAAA;YADd,IAAI,CAED,UAAU,CAAC,UAAU,CAAC,IAAI,CAAA,CAAA;YAF7B,IAAI,CAGD,OAAO,CAAC,GAAG,EAAE;gBACZ,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,8BAA8B,EAAE,EAAS,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;YACpF,CAAC,CAAA,CAAA;;QALH,IAAI,CAAA,GAAA,EAAA,CAAA;;YAMJ,IAAI,QAAC,WAAW,CAAA,CAAA;YAAhB,IAAI,CACD,QAAQ,CAAC,EAAE,CAAA,CAAA;YADd,IAAI,CAED,UAAU,CAAC,UAAU,CAAC,IAAI,CAAA,CAAA;YAF7B,IAAI,CAGD,OAAO,CAAC,GAAG,EAAE;gBACZ;oBAAO,CAAC;YACV,CAAC,CAAA,CAAA;;QALH,IAAI,CAAA,GAAA,EAAA,CAAA;QAbN,MAAM,CAAA,GAAA,EAAA,CAAA;QADR,GAAG,CAAA,GAAA,EAAA,CAAA;IAwBJ,CAAA;;;;;;;;AAGH;IACE,IAAO,CAAA;AACT,CAAC;AAED;IACE,IAAO,CAAA;AACT,CAAC;AACD;IACE,MAAM,KAAK,CAAC,MAAM,CAAC,CAAA;AACrB,CAAC;";
122        let mappings = parse(input);
123        let target = to_source(&mappings, 54, 1);
124        assert!(target == Some((20, 8)));
125
126        let target = to_source(&mappings, 52, 1);
127        println!("{:?}", target);
128        assert!(target == None);
129
130        let target = to_source(&mappings, 40, 1);
131        println!("{:?}", target);
132        assert!(target == Some((13, 6)));
133    }
134}