-
-
Notifications
You must be signed in to change notification settings - Fork 250
/
Copy pathBase64VLQ.cs
235 lines (188 loc) · 7.81 KB
/
Base64VLQ.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization;
using System.Text;
namespace MadsKristensen.EditorExtensions
{
///<summary>Variable Length Quantity (VLQ) Base 64 Serializer</summary>
///<remarks>Inspired by <see cref="https://github.com/mozilla/source-map"/></remarks>
public static class Base64Vlq
{
// A single base 64 digit can contain 6 bits of data. For the base 64 variable
// length quantities we use in the source map spec, the first bit is the sign,
// the next four bits are the actual value, and the 6th bit is the
// continuation bit. The continuation bit tells us whether there are more
// digits in this value following this digit.
//
// Continuation
// | Sign
// | |
// V V
// 101011
const int VLQ_BASE_SHIFT = 5;
// binary: 100000
const int VLQ_BASE = 1 << VLQ_BASE_SHIFT;
// binary: 011111
const int VLQ_BASE_MASK = VLQ_BASE - 1;
// binary: 100000
const int VLQ_CONTINUATION_BIT = VLQ_BASE;
private static bool IsMappingSeparator(int ch) { return ch == ',' || ch == ';'; }
private static string GetName(int index, string basePath, params string[] sources)
{
if (sources.Length == 0)
return string.Empty;
if (sources.Length > index)
return Path.GetFullPath(Path.Combine(basePath, sources[index]));
throw new VlqException("Invalid index received.");
}
/**
* Converts from a two-complement value to a value where the sign bit is
* is placed in the least significant bit. For example, as decimals:
* 1 becomes 2 (10 binary), -1 becomes 3 (11 binary)
* 2 becomes 4 (100 binary), -2 becomes 5 (101 binary)
*/
private static int ToVLQSigned(int value)
{
return value < 0 ? ((-value) << 1) + 1 : (value << 1) + 0;
}
/**
* Converts to a two-complement value from a value where the sign bit is
* is placed in the least significant bit. For example, as decimals:
* 2 (10 binary) becomes 1, 3 (11 binary) becomes -1
* 4 (100 binary) becomes 2, 5 (101 binary) becomes -2
*/
private static int FromVLQSigned(int value)
{
var isNegative = (value & 1) == 1;
var shifted = value >> 1;
return isNegative ? -shifted : shifted;
}
/**
* Returns the base 64 VLQ encoded value.
*/
public static string Encode(int number)
{
var encoded = new StringBuilder();
int digit;
var vlq = ToVLQSigned(number);
do
{
digit = vlq & VLQ_BASE_MASK;
vlq = vlq >> VLQ_BASE_SHIFT;
if (vlq > 0)
{
// There are still more digits in this value, so we must make sure the
// continuation bit is marked.
digit |= VLQ_CONTINUATION_BIT;
}
encoded.Append(Base64.Base64Encode(digit));
} while (vlq > 0);
return encoded.ToString();
}
public static IEnumerable<CssSourceMapNode> Decode(string vlqValue, string basePath, params string[] sources)
{
int generatedLine = 0, previousSource, previousGeneratedColumn, previousOriginalLine, previousOriginalColumn;
previousSource = previousGeneratedColumn = previousOriginalLine = previousOriginalColumn = 0;
var stream = new StringReader(vlqValue);
while (stream.Peek() != -1)
{
if (stream.Peek() == ',')
{
stream.Read();
continue;
}
if (stream.Peek() == ';')
{
stream.Read();
generatedLine++;
previousGeneratedColumn = 0;
continue;
}
var result = new CssSourceMapNode();
// Generated column.
result.GeneratedColumn = previousGeneratedColumn + VlqDecode(stream);
result.GeneratedLine = generatedLine;
previousGeneratedColumn = result.GeneratedColumn;
if (stream.Peek() < 0 || IsMappingSeparator(stream.Peek()))
yield break;
// Original source.
previousSource += VlqDecode(stream);
result.SourceFilePath = GetName(previousSource, basePath, sources);
if (stream.Peek() < 0 || IsMappingSeparator(stream.Peek()))
throw new VlqException("Found a source, but no line and column");
// Original line.
result.OriginalLine = previousOriginalLine + VlqDecode(stream);
previousOriginalLine = result.OriginalLine;
if (stream.Peek() < 0 || IsMappingSeparator(stream.Peek()))
throw new VlqException("Found a source and line, but no column");
// Original column.
result.OriginalColumn = previousOriginalColumn + VlqDecode(stream);
previousOriginalColumn = result.OriginalColumn;
// Skip Original Name bit; we are not using it.
if (stream.Peek() > 0 && !IsMappingSeparator(stream.Peek()))
stream.Read();
yield return result;
}
}
///<summary>Reads a single VLQ value from a stream of text, advancing the stream to the subsequent character.</summary>
public static int VlqDecode(TextReader stream)
{
var result = 0;
var shift = 0;
bool continuation;
int digit;
do
{
if (stream.Peek() == -1)
throw new VlqException("Expected more digits in base 64 VLQ value.");
digit = Base64.Base64Decode((char)stream.Read());
continuation = (digit & VLQ_CONTINUATION_BIT) != 0;
digit &= VLQ_BASE_MASK;
result += digit << shift;
shift += VLQ_BASE_SHIFT;
} while (continuation);
return FromVLQSigned(result);
}
private static class Base64
{
private static char[] _array = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".ToCharArray();
/**
* Encode an integer in the range of 0 to 63 to a single base 64 digit.
*/
public static char Base64Encode(int number)
{
if (_array.Length > number)
return _array[number];
throw new VlqException("Must be between 0 and 63: " + number);
}
/**
* Decode a single base 64 digit to an integer.
*/
public static int Base64Decode(char character)
{
var index = Array.IndexOf(_array, character);
if (index > -1)
return index;
throw new VlqException("Not a valid base 64 digit: " + character);
}
}
}
// For CA2201: Do not raise reserved exception types.
[Serializable]
public class VlqException : Exception
{
public VlqException()
: base()
{ }
public VlqException(string message)
: base(message)
{ }
public VlqException(string message, Exception innerException)
: base(message, innerException)
{ }
protected VlqException(SerializationInfo info, StreamingContext context)
: base(info, context)
{ }
}
}