UNPKG

syntax-cli-prog

Version:

Syntactic analysis toolkit, language agnostic parsers generator.

252 lines (188 loc) 9.67 kB
'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;