syntax-cli-prog
Version:
Syntactic analysis toolkit, language agnostic parsers generator.
252 lines (188 loc) • 9.67 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
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 _grammarSymbol = require('../grammar/grammar-symbol');
var _grammarSymbol2 = _interopRequireDefault(_grammarSymbol);
var _llParsingTable = require('./ll-parsing-table');
var _llParsingTable2 = _interopRequireDefault(_llParsingTable);
var _llParserGeneratorDefault = require('./ll-parser-generator-default');
var _llParserGeneratorDefault2 = _interopRequireDefault(_llParserGeneratorDefault);
var _tokenizer = require('../tokenizer');
var _tokenizer2 = _interopRequireDefault(_tokenizer);
var _specialSymbols = require('../special-symbols');
var _debug = require('../debug');
var _debug2 = _interopRequireDefault(_debug);
var _os = require('os');
var _os2 = _interopRequireDefault(_os);
var _path = require('path');
var _path2 = _interopRequireDefault(_path);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
/**
* Implements LL(1) parsing algorithm.
*/
var LLParser = function () {
function LLParser(_ref) {
var grammar = _ref.grammar,
parserModule = _ref.parserModule;
_classCallCheck(this, LLParser);
this._grammar = grammar;
this._parserModule = parserModule;
this._table = new _llParsingTable2.default({
grammar: grammar
});
// If there are conflicts, parsing is not possible.
this._validateConflicts();
this._tokenizer = new _tokenizer2.default({
lexGrammar: grammar.getLexGrammar()
});
// Parsing stack.
this._stack = [];
// Stores production numbers used at parsing.
this._productionNumbers = [];
}
/**
* Returns production numbers used to parse a string.
*/
_createClass(LLParser, [{
key: 'getProductionNumbers',
value: function getProductionNumbers() {
return this._productionNumbers;
}
}, {
key: 'parse',
value: function parse(string) {
// If parser module has been generated, use it.
if (this._parserModule) {
_debug2.default.time('LL parsing from module');
var value = this._parserModule.parse(string);
_debug2.default.timeEnd('LL parsing from module');
return {
status: 'accept',
value: value
};
}
_debug2.default.time('LL parsing');
this._tokenizer.initString(string);
// Initialize the stack with the `$` at the bottom, and the start symbol.
this._stack = [_grammarSymbol2.default.get(_specialSymbols.EOF), _grammarSymbol2.default.get(this._grammar.getStartSymbol())];
this._productionNumbers = [];
var token = this._tokenizer.getNextToken();
var top = null;
do {
top = this._stack.pop();
// Terminal is on the stack, just advance.
if (this._grammar.isTokenSymbol(top) && top.getSymbol() === token.type) {
// We already popped the symbol from the stack,
// so just advance the cursor.
token = this._tokenizer.getNextToken();
continue;
}
// Else, it's a non-terminal, do derivation (replace it
// in the stack with corresponding production).
this._doDerivation(top, token);
} while (this._tokenizer.hasMoreTokens() || this._stack.length > 1);
// If the string reached EOF, and we still have non-terminal symbols
// on the stack, we need to clean them up, they have to derive ε.
while (this._stack.length !== 1) {
this._doDerivation(this._stack.pop(), token);
}
// At the end the stack should contain only `$`,
// as well as the last token should be the `$` marker.
if (!this._stack[0].isEOF() || token.type !== _specialSymbols.EOF) {
this._parseError('stack is not empty: ' + this._stack.map(function (s) {
return s.getSymbol();
}) + (', ' + token.value));
}
_debug2.default.timeEnd('LL parsing');
return {
status: 'accept',
semanticValue: true
};
}
}, {
key: '_doDerivation',
value: function _doDerivation(top, token) {
var derivedRHS = this._getDerivedRHS(top, token);
// If we have production like F -> ε, we should just pop
// the symbol, and don't push its derivation (the ε).
if (!derivedRHS[0].isEpsilon()) {
var _stack;
(_stack = this._stack).push.apply(_stack, _toConsumableArray(derivedRHS));
}
}
}, {
key: '_getDerivedRHS',
value: function _getDerivedRHS(top, token) {
var nextProductionNumber = this._table.get()[top.getSymbol()][token.type];
if (!nextProductionNumber) {
this._unexpectedToken(token);
}
if (this._table.entryHasConflict(nextProductionNumber)) {
this._parseError('Found conflict in state ' + top.getSymbol() + ':' + token.type + '. ' + ('Predicted productions: ' + nextProductionNumber));
}
var nextProduction = this._grammar.getProduction(nextProductionNumber);
this._productionNumbers.push(nextProductionNumber);
// We should return reversed RHS in order to push on the stack.
return nextProduction.getRHS().slice().reverse();
}
}, {
key: '_validateConflicts',
value: function _validateConflicts() {
if (!this._table.hasConflicts()) {
return;
}
var messages = [''];
var conflicts = this._table.getConflicts();
for (var nonTerminal in conflicts) {
var conflictMessage = nonTerminal + ': ';
var row = conflicts[nonTerminal];
var rowMessages = [];
for (var terminal in row) {
rowMessages.push(terminal + ' -- ' + row[terminal]);
}
conflictMessage += rowMessages.join(', ');
messages.push(conflictMessage);
}
this._parseError('Grammar has conflicts:\n' + messages.join('\n- '));
}
}, {
key: '_unexpectedEndOfInput',
value: function _unexpectedEndOfInput() {
this._parseError('Unexpected end of input.');
}
}, {
key: '_unexpectedToken',
value: function _unexpectedToken(token) {
if (token.type === _specialSymbols.EOF) {
this._unexpectedEndOfInput();
}
this._tokenizer.throwUnexpectedToken(token.value, token.startLine, token.startColumn);
}
}, {
key: '_parseError',
value: function _parseError(message) {
throw new SyntaxError(message);
}
}], [{
key: 'fromParserGenerator',
value: function fromParserGenerator(_ref2) {
var grammar = _ref2.grammar;
// Generate parser in the temp directory.
var outputFile = _path2.default.resolve(_os2.default.tmpdir(), '.syntax-parser.js');
var parserModule = new _llParserGeneratorDefault2.default({
grammar: grammar,
outputFile: outputFile
}).generate();
return new LLParser({ grammar: grammar, parserModule: parserModule });
}
}]);
return LLParser;
}();
exports.default = LLParser;