/*
* Copyright 2010 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.sommeri.sourcemap;
import java.util.ArrayList;
import java.util.List;
/**
* Class for parsing the line maps in SourceMap v2.
*
* @author johnlenz@google.com (John Lenz)
* @author jschorr@google.com (Joseph Schorr)
*/
class SourceMapLineDecoder {
/**
* Decodes a line in a character map into a list of mapping IDs.
*/
static List<Integer> decodeLine(String lineSource) {
return decodeLine(new StringParser(lineSource));
}
private SourceMapLineDecoder() {}
static LineEntry decodeLineEntry(String in, int lastId) {
return decodeLineEntry(new StringParser(in), lastId);
}
private static LineEntry decodeLineEntry(StringParser reader, int lastId) {
int repDigits = 0;
// Determine the number of digits used for the repetition count.
// Each "!" indicates another base64 digit.
for (char peek = reader.peek(); peek == '!'; peek = reader.peek()) {
repDigits++;
reader.next(); // consume the "!"
}
int idDigits = 0;
int reps = 0;
if (repDigits == 0) {
// No repetition digit escapes, so the next character represents the
// number of digits in the id (bottom 2 bits) and the number of
// repetitions (top 4 digits).
char digit = reader.next();
int value = addBase64Digit(digit, 0);
reps = (value >> 2);
idDigits = (value & 3);
} else {
char digit = reader.next();
idDigits = addBase64Digit(digit, 0);
int value = 0;
for (int i = 0; i < repDigits; i++) {
digit = reader.next();
value = addBase64Digit(digit, value);
}
reps = value;
}
// Adjust for 1 offset encoding.
reps += 1;
idDigits += 1;
// Decode the id token.
int value = 0;
for (int i = 0; i < idDigits; i++) {
char digit = reader.next();
value = addBase64Digit(digit, value);
}
int mappingId = getIdFromRelativeId(value, idDigits, lastId);
return new LineEntry(mappingId, reps);
}
private static List<Integer> decodeLine(StringParser reader) {
List<Integer> result = new ArrayList<Integer>(512);
int lastId = 0;
while (reader.hasNext()) {
LineEntry entry = decodeLineEntry(reader, lastId);
lastId = entry.id;
for (int i=0; i < entry.reps; i++) {
result.add(entry.id);
}
}
return result;
}
/**
* Build base64 number a digit at a time, most significant digit first.
*/
private static int addBase64Digit(char digit, int previousValue) {
return (previousValue * 64) + Base64.fromBase64(digit);
}
/**
* @return the id from the relative id.
*/
static int getIdFromRelativeId(int rawId, int digits, int lastId) {
// The value range depends on the number of digits
int base = 1 << (digits * 6);
return ((rawId >= base/2) ? rawId - base : rawId) + lastId;
}
/**
* Simple class for tracking a single entry in a line map.
*/
static class LineEntry {
final int id;
final int reps;
public LineEntry(int id, int reps) {
this.id = id;
this.reps = reps;
}
}
/**
* A simple class for maintaining the current location
* in the input.
*/
static class StringParser {
final String content;
int current = 0;
StringParser(String content) {
this.content = content;
}
char next() {
return content.charAt(current++);
}
char peek() {
return content.charAt(current);
}
boolean hasNext() {
return current < content.length() -1;
}
}
}