syntax-cli-prog
Version:
Syntactic analysis toolkit, language agnostic parsers generator.
499 lines (419 loc) • 18.8 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); /**
* The MIT License (MIT)
* Copyright (c) 2015-present Dmitry Soshnikov <dmitry.soshnikov@gmail.com>
*/
var _canonicalCollection = require('./canonical-collection');
var _canonicalCollection2 = _interopRequireDefault(_canonicalCollection);
var _grammarSymbol = require('../grammar/grammar-symbol');
var _grammarSymbol2 = _interopRequireDefault(_grammarSymbol);
var _tablePrinter = require('../table-printer');
var _tablePrinter2 = _interopRequireDefault(_tablePrinter);
var _specialSymbols = require('../special-symbols');
var _colors = require('colors');
var _colors2 = _interopRequireDefault(_colors);
var _debug = require('../debug');
var _debug2 = _interopRequireDefault(_debug);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
/**
* The LR parsing table is built by traversing the graph of the
* canonical collection of LR items. Rows of the table correspond to
* the state (closure) numbers, and columns are terminals (for "action"
* part) and non-terminals (for "goto" part).
*
* For the example grammar (where S' -> S is the augmented production):
*
* +------------+
* | S' -> S |
* +------------+
* | S -> A A |
* | A -> "a" A |
* | | "b" |
* +------------+
*
* The LR(0) parsing table, which is used by LR(0) and SLR(1) parsers,
* looks like this:
*
* State Action Goto
* +---+-----+-----+-----++-----+-----+
* | │ "a" │ "b" │ $ ││ S │ A │
* +---+-----+-----+-----++-----+-----+
* | 0 │ s2 │ s4 │ ││ 3 │ 1 │
* +---+-----+-----+-----++-----+-----+
* | 1 │ s2 │ s4 │ ││ │ 5 │
* +---+-----+-----+-----++-----+-----+
* | 2 │ s2 │ s4 │ ││ │ 6 │
* +---+-----+-----+-----++-----+-----+
* | 3 │ │ │ acc ││ │ │
* +---+-----+-----+-----++-----+-----+
* | 4 │ r3 │ r3 │ r3 ││ │ │
* +---+-----+-----+-----++-----+-----+
* | 5 │ r1 │ r1 │ r1 ││ │ │
* +---+-----+-----+-----++-----+-----+
* | 6 │ r2 │ r2 │ r2 ││ │ │
* +---+-----+-----+-----++-----+-----+
*
* Note: for LR(1) items used by LALR(1) and CLR(1) number of reduce steps
* may decrease. Also number of states may increase in case of CLR(1).
*
* - State: number of a state (closure) in the graph
*
* Action:
*
* - s<n> - Shift<n>: "shift" action (move a symbol onto the stack) and
* transit to the state n next
*
* - r<k> - Reduce<k>: "reduce" action (replace RHS of a production <k>
* which is on top of the stack, with its LHS.
*
* - acc - "accept" action, successful parse.
*
* Goto:
*
* - <n> - goto to state <n> in the graph.
*
* Examples:
*
* - 0:A -> 3: if we're in the state 0, and see non-terminal A, go to
* the state 3.
*
* - 1:"a" -> s2: if we're in the state 1, and see the "a" terminal,
* shift it from buffer onto the stack, and go to the state 2.
*
* - 5:"b" -> r2: if we're in the state 5, and see the "b" terminal,
* reduce the RHS of the production 2 on top of the stack to its
* LHS non-terminal.
*/
/**
* Type of an entry in the parsing table.
*/
var EntryType = {
ERROR: 0,
GOTO: 1,
SHIFT: 2,
REDUCE: 3,
ACCEPT: 4,
SR_CONFLICT: 5,
RR_CONFLICT: 6
};
/**
* LR parsing table class.
*/
var LRParsingTable = function () {
/**
* The table is built from the canonical collection,
* which was built for the specific grammar.
*/
function LRParsingTable(_ref) {
var _ref$canonicalCollect = _ref.canonicalCollection,
canonicalCollection = _ref$canonicalCollect === undefined ? null : _ref$canonicalCollect,
grammar = _ref.grammar,
_ref$resolveConflicts = _ref.resolveConflicts,
resolveConflicts = _ref$resolveConflicts === undefined ? false : _ref$resolveConflicts;
_classCallCheck(this, LRParsingTable);
this._grammar = grammar;
this._canonicalCollection = canonicalCollection;
this._shouldResolveConflicts = resolveConflicts;
if (!this._canonicalCollection) {
this._canonicalCollection = new _canonicalCollection2.default({
grammar: this._grammar
});
}
// Stores conflicts data.
this._conflictsData = {};
_debug2.default.time('Building LR parsing table');
this._action = grammar.getTerminals().concat(grammar.getTokens(), _grammarSymbol2.default.get(_specialSymbols.EOF));
this._goto = grammar.getNonTerminals();
this._table = {};
this._build();
_debug2.default.timeEnd('Building LR parsing table');
}
_createClass(LRParsingTable, [{
key: 'get',
value: function get() {
return this._table;
}
/**
* Returns conflicts data.
*/
}, {
key: 'getConflictsData',
value: function getConflictsData() {
return this._conflictsData;
}
}, {
key: 'print',
value: function print() {
var _this = this;
this._grammar.print();
console.info('\n' + this._grammar.getMode().toString() + ' parsing table:\n');
var actionSymbols = this._action.map(function (actionSymbol) {
return actionSymbol.getSymbol();
});
var nonTerminals = this._grammar.getNonTerminals().map(function (nonTerminal) {
return nonTerminal.getSymbol();
});
var printer = new _tablePrinter2.default({
head: [''].concat(actionSymbols, nonTerminals)
});
var _loop = function _loop(stateNumber) {
var tableRow = _this._table[stateNumber];
var stateLabel = _colors2.default.blue(stateNumber);
var row = _defineProperty({}, stateLabel, []);
// Action part.
actionSymbols.forEach(function (actionSymbol) {
var entry = tableRow[actionSymbol] || '';
if (_this._hasConflict(entry)) {
entry = _colors2.default.red(entry);
} else if (entry === 'acc') {
entry = _colors2.default.green(entry);
}
row[stateLabel].push(entry);
});
// Goto part.
nonTerminals.forEach(function (nonTerminal) {
row[stateLabel].push(tableRow[nonTerminal] || '');
});
printer.push(row);
};
for (var stateNumber in this._table) {
_loop(stateNumber);
}
console.info(printer.toString());
console.info('');
}
}, {
key: '_hasConflict',
value: function _hasConflict(entry) {
var entryType = LRParsingTable.getEntryType(entry);
return entryType === EntryType.RR_CONFLICT || entryType === EntryType.SR_CONFLICT;
}
}, {
key: '_build',
value: function _build() {
var _this2 = this;
this._canonicalCollection.getStates().forEach(function (state) {
// Fill actions and goto for this state (row).
var row = _this2._table[state.getNumber()] = {};
state.getItems().forEach(function (item) {
// For final item we should "reduce". In LR(0) type we
// reduce unconditionally for every terminal, in other types
// e.g. SLR(1) consider lookahead (follow) sets.
if (item.isFinal()) {
var production = item.getProduction();
// For the final item of the augmented production,
// the action is "acc" (accept).
if (production.isAugmented()) {
row[_specialSymbols.EOF] = 'acc';
} else {
// Otherwise, reduce.
_this2._action.forEach(function (terminal) {
if (_this2._shouldReduce(item, terminal)) {
_this2._putActionEntry(row, terminal.getSymbol(), 'r' + production.getNumber());
}
});
}
} else {
var transitionSymbol = item.getCurrentSymbol();
var nextState = item.goto().getNumber();
// Other terminals do "shift" action and go to the next state,
// and non-terminals just go to the next
if (_this2._grammar.isTokenSymbol(transitionSymbol)) {
_this2._putActionEntry(row, transitionSymbol.getSymbol(), 's' + nextState);
} else {
row[transitionSymbol.getSymbol()] = nextState;
}
}
});
// Resolve conflicts, if any.
_this2._resolveConflicts(state, row);
});
}
}, {
key: '_getStateConflictData',
value: function _getStateConflictData(state) {
var stateNumber = state.getNumber();
if (!this._conflictsData.hasOwnProperty(stateNumber)) {
this._conflictsData[stateNumber] = {};
}
return this._conflictsData[stateNumber];
}
}, {
key: '_initSymbolConflictData',
value: function _initSymbolConflictData(state, symbol, conflict) {
this._getStateConflictData(state)[symbol] = {
conflict: conflict,
resolved: false
};
}
}, {
key: '_resolveConflicts',
value: function _resolveConflicts(state, row) {
for (var symbol in row) {
var entry = row[symbol];
var entryType = LRParsingTable.getEntryType(entry);
// Shift-reduce.
if (entryType === EntryType.SR_CONFLICT) {
this._initSymbolConflictData(state, symbol, entry);
this._resolveSRConflict(state, row, symbol);
}
// Reduce-reduce.
else if (entryType === EntryType.RR_CONFLICT) {
this._initSymbolConflictData(state, symbol, entry);
this._resolveRRConflict(state, row, symbol);
}
}
}
}, {
key: '_resolveSRConflict',
value: function _resolveSRConflict(state, row, symbol) {
var entry = row[symbol];
var operators = this._grammar.getOperators();
var _splitSRParts = this.splitSRParts(entry),
_splitSRParts2 = _slicedToArray(_splitSRParts, 2),
reducePart = _splitSRParts2[0],
shiftPart = _splitSRParts2[1];
// Default resolution is to shift if no precedence is specified.
if (!operators.hasOwnProperty(symbol)) {
if (this._shouldResolveConflicts) {
row[symbol] = shiftPart;
this._getStateConflictData(state)[symbol].resolved = 'no precedence, shift by default';
}
return;
}
// Else, working with operators precedence.
var _operators$symbol = operators[symbol],
symbolPrecedence = _operators$symbol.precedence,
symbolAssoc = _operators$symbol.assoc;
var productionPrecedence = this._grammar.getProduction(reducePart.slice(1)).getPrecedence();
var symbolConflictData = this._getStateConflictData(state)[symbol];
// 1. If production's precedence is higher, the choice is to reduce:
//
// R: E -> E * E • (reduce since `*` > `+`)
// S: E -> E • + E
//
if (productionPrecedence > symbolPrecedence) {
row[symbol] = reducePart;
symbolConflictData.resolved = 'reduce (higher production precedence)';
}
// 2. If the symbol's precedence is higher, the choice is to shift:
//
// E -> E + E •
// E -> E • * E (shift since `*` > `+`)
//
else if (symbolPrecedence > productionPrecedence) {
row[symbol] = shiftPart;
symbolConflictData.resolved = 'shift (higher symbol precedence)';
}
// 3. If they have equal precedence, the choice is made based on the
// associativity of that precedence level:
//
// E -> E * E • (choose to reduce since `*` is left-associative)
// E -> E • * E
//
// This case we want `id * id * id` to be left-associative, i.e.
// `(id * id) * id`, but not right-associative, that would be
// `id * (id * id)`.
//
else if (productionPrecedence === symbolPrecedence && productionPrecedence !== 0 && symbolPrecedence !== 0) {
// Left-assoc.
if (symbolAssoc === 'left') {
row[symbol] = reducePart;
symbolConflictData.resolved = 'reduce (same precedence, left-assoc)';
} else if (symbolAssoc === 'right') {
row[symbol] = shiftPart;
symbolConflictData.resolved = 'shift (same precedence, right-assoc)';
} else if (symbolAssoc === 'nonassoc') {
// No action on `nonassoc`.
delete row[symbol];
symbolConflictData.resolved = 'removed conflict for nonassoc';
}
}
}
}, {
key: '_resolveRRConflict',
value: function _resolveRRConflict(state, row, symbol) {
if (!this._shouldResolveConflicts) {
return;
}
var entry = row[symbol];
var _entry$split = entry.split('/'),
_entry$split2 = _slicedToArray(_entry$split, 2),
r1 = _entry$split2[0],
r2 = _entry$split2[1];
var symbolConflictData = this._getStateConflictData(state)[symbol];
// R/R conflicts are resolved by choosing a production that
// goes first in the grammar (i.e. its number is smaller).
row[symbol] = Number(r1.slice(1)) < Number(r2.slice(1)) ? r1 : r2;
symbolConflictData.resolved = 'first in order production is chosen ' + row[symbol].slice(1);
}
}, {
key: 'splitSRParts',
value: function splitSRParts(entry) {
var srConflict = entry.split('/');
return LRParsingTable.getEntryType(srConflict[0]) === EntryType.REDUCE ? [srConflict[0], srConflict[1]] : [srConflict[1], srConflict[0]];
}
}, {
key: '_shouldReduce',
value: function _shouldReduce(item, terminal) {
var reduceSet = item.getReduceSet();
// LR(0) reduces for all terminals.
if (reduceSet === true) {
return true;
}
// SLR(1) considers Follow(LHS), LALR(1) and CLR(1)
// considers lookahead sets.
return reduceSet.hasOwnProperty(terminal.getSymbol());
}
}, {
key: '_putActionEntry',
value: function _putActionEntry(row, column, entry) {
var previousEntry = row[column];
// In case we have a transition on the same
// symbol, and an action was already registered.
if (previousEntry === entry) {
return;
}
// Register an entry handling possible "shift-reduce" (s/r)
// or "reduce-reduce" (r/r) conflicts.
// Exclude duplicates for possibly the same conflict entry.
if (previousEntry) {
previousEntry = previousEntry.split('/');
if (previousEntry.indexOf(entry) === -1) {
previousEntry.push(entry);
}
entry = previousEntry.join('/');
}
row[column] = entry;
}
}], [{
key: 'getEntryType',
value: function getEntryType(entry) {
if (typeof entry === 'number') {
return EntryType.GOTO;
} else if (entry.indexOf('/') !== -1) {
return entry.indexOf('s') !== -1 ? EntryType.SR_CONFLICT : EntryType.RR_CONFLICT;
} else if (entry[0] === 's') {
return EntryType.SHIFT;
} else if (entry[0] === 'r') {
return EntryType.REDUCE;
} else if (entry === 'acc') {
return EntryType.ACCEPT;
}
return EntryType.ERROR;
}
}, {
key: 'EntryType',
get: function get() {
return EntryType;
}
}]);
return LRParsingTable;
}();
exports.default = LRParsingTable;