@lahmatiy/jison
Version:
A parser generator with Bison's API
178 lines (142 loc) • 5.23 kB
JavaScript
exports.packTable = function packTable(table) {
function encodeNum(num) {
return num < 32
? e64[num]
: e64[(num & 0b11111) | 0b100000] + e64[num >> 5];
}
function packSubtable(table) {
// reduce consequence repetitions
table = table.replace(/(.+?)\1+/g, (m, p) => {
const t = encodeNum(m.length / p.length);
return t.length + 2 + p.length < m.length
? '<' + t + p + '>'
: m;
});
// reduce repetitions by back ref
const rx = /([^<>]{6,})(.*?)\1/;
let match = null;
let round = 0;
while (round < 16 && (match = table.match(rx))) {
const idx = String.fromCharCode(32 + round); // 0x20-0x2f
const pattern = match[1];
const parts = table.split(pattern);
table = '';
for (let i = 0; i < parts.length; i++) {
if (i === 1) {
table += '[' + idx + pattern + ']';
} else if (i !== 0) {
table += idx;
}
table += parts[i];
}
round++;
}
return table;
}
const e64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789~|=';
const indexTable = [];
const indexRowMap = new Map();
const stateTables = [
'',
'',
'',
''
];
for (let i = 0; i < table.length; i++) {
const row = table[i];
const maxIndex = Math.max(...Object.keys(row).map(Number));
let encodedRow = '';
for (let j = 0; j <= maxIndex; j += 3) {
let chunk = 0;
for (let k = 0; k < 3; k++) {
const index = j + k;
if (index in row) {
const [state, ref] = Array.isArray(row[index]) ? row[index] : [0, row[index]];
if (state !== 3) {
chunk |= (state + 1) << (k * 2);
stateTables[state] += encodeNum(ref);
} else {
stateTables[state] += encodeNum(i) + encodeNum(index);
}
}
}
encodedRow += e64[chunk];
}
if (!indexRowMap.has(encodedRow)) {
indexRowMap.set(encodedRow, indexRowMap.size);
}
indexTable.push(encodeNum(indexRowMap.get(encodedRow)));
}
return [
indexTable.join(''),
[...indexRowMap.keys()].join(';'),
...stateTables
].map(packSubtable).join('=');
};
exports.unpackTable = function unpackTable(tables) {
function unpackSubtable(table) {
// restore repetitions by ref
const refs = table.match(/[\x20-\x2f]/g);
const last = refs ? Math.max(...refs.map(ref => ref.charCodeAt() - 32)) : -1;
for (let i = last; i >= 0; i--) {
const idx = (32 + i).toString(16);
let pattern;
table = table
.replace(new RegExp('\\[\\x' + idx + '(.+?)\\]'), (_, repl) => (pattern = repl))
.replace(new RegExp('\\x' + idx, 'g'), pattern);
}
// restore repetitions
table = table.replace(/<([^>]+?)>/g, (_, p) => {
let n = d64[p[0]];
let offset = 1;
if (n >> 5 === 1) {
n = (n & 0b11111) | (d64[p[1]] << 5);
offset = 2;
}
return p.slice(offset).repeat(n);
});
return table;
}
function decodeNums(encoded) {
const nums = [];
for (let j = 0; j < encoded.length; j++) {
const b1 = d64[encoded[j]];
if (b1 & 0b100000) {
const b2 = d64[encoded[++j]];
nums.push((b1 & 0b11111) | (b2 << 5));
} else {
nums.push(b1);
}
}
return nums;
}
const e64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789~|=';
const d64 = e64.split('').reduce((map, ch, idx) => (map[ch] = idx, map), Object.create(null));
const [indexTable, indexRows, ...stateTables] = tables.split('=').map(unpackSubtable);
const decodedIndexRows = indexRows.split(';').map(row => {
const states = [];
for (let i = 0; i < row.length; i++) {
for (let j = 0, chunk = d64[row[i]]; j < 3; j++) {
states.push((chunk >> (j * 2)) & 0b11);
}
}
return states;
});
const decodedStateTables = stateTables.map(decodeNums);
const result = decodeNums(indexTable).map(rowIdx => {
const rowIndexes = decodedIndexRows[rowIdx];
const row = Object.create(null);
for (let i = 0; i < rowIndexes.length; i++) {
const state = rowIndexes[i];
if (state !== 0) {
const ref = decodedStateTables[state - 1].shift();
row[i] = state === 1 ? ref : [state - 1, ref];
}
}
return row;
});
for (let i = 0; i < decodedStateTables[3].length; i += 2) {
result[decodedStateTables[3][i]][decodedStateTables[3][i + 1]] = [3];
}
return result;
};