syntax-cli-prog
Version:
Syntactic analysis toolkit, language agnostic parsers generator.
274 lines (225 loc) • 8.89 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 _codeUnit = require('../code-unit');
var _codeUnit2 = _interopRequireDefault(_codeUnit);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
/**
* A lexical grammar rule.
*/
var LexRule = function () {
/**
* Format of a rule is: `<matcher> : <tokenType>`.
*
* In the simplest case a terminal matches itself, and the
* token is also the value of the terminal:
*
* a : "a"
*
* In other regexp cases, the token can be a class of
* tokens, like `NUMBER`:
*
* [0-9]+(\.[0-9]+)? : NUMBER
*
* The `startConditions` allows specifying lexer state, and execute
* a rule only if a tokenizer is within this state.
*
* Available options:
*
* - case-insensitive: boolean
*/
function LexRule(_ref) {
var startConditions = _ref.startConditions,
matcher = _ref.matcher,
tokenHandler = _ref.tokenHandler,
_ref$options = _ref.options,
options = _ref$options === undefined ? {} : _ref$options;
_classCallCheck(this, LexRule);
this._startConditions = startConditions;
this._options = options;
this._originalMatcher = matcher;
this._rawMatcher = this._buildRawMatcher(this._originalMatcher);
this._matcher = this._buildMatcher(this._rawMatcher);
this._rawHandler = tokenHandler;
this._handler = this._buildHandler(tokenHandler);
}
/**
* Returns options.
*/
_createClass(LexRule, [{
key: 'getOptions',
value: function getOptions() {
return this._options;
}
/**
* Whether the rule is case-insensitive.
*/
}, {
key: 'isCaseInsensitive',
value: function isCaseInsensitive() {
return !!this._options['case-insensitive'];
}
/**
* Retusn original matcher string.
*/
}, {
key: 'getOriginalMatcher',
value: function getOriginalMatcher() {
return this._originalMatcher;
}
/**
* Retusn raw matcher string.
*/
}, {
key: 'getRawMatcher',
value: function getRawMatcher() {
return this._rawMatcher;
}
/**
* Returns matcher function.
*/
}, {
key: 'getMatcher',
value: function getMatcher() {
return this._matcher;
}
/**
* Returns raw handler.
*/
}, {
key: 'getRawHandler',
value: function getRawHandler() {
return this._rawHandler;
}
/**
* Returns handler.
*/
}, {
key: 'getHandler',
value: function getHandler() {
return this._handler;
}
/**
* Returns token data.
*/
}, {
key: 'getTokenData',
value: function getTokenData(matched, tokenizer) {
return this._handler(matched, tokenizer);
}
/**
* Returns start conditions for this rule.
*/
}, {
key: 'getStartConditions',
value: function getStartConditions() {
return this._startConditions;
}
/**
* Whether this rule has start conditions.
*/
}, {
key: 'hasStartConditions',
value: function hasStartConditions() {
return !!this._startConditions;
}
/**
* Generates a data array like used in lex grammar files
* from the LexRule instance.
*/
}, {
key: 'toData',
value: function toData() {
var data = [this.getOriginalMatcher(), this.getRawHandler()];
if (this.hasStartConditions()) {
data.unshift(this.getStartConditions());
}
return data;
}
/**
* Tokenizer applies all regexp rules from the beginning of the
* non-analized yet substring (i.e. appending ^ to regexp).
*
* E.g. \d+ becomes ^\d+
*
* Note: lookbehind assertions should append ^ not at the beginning
* of the regexp, but at the begininng of the asserting chars:
*
* (?<= )a(?= ) becomes (?<=^ )a(?= )
*/
}, {
key: '_buildRawMatcher',
value: function _buildRawMatcher(originalMatcher) {
var prefix = originalMatcher.substring(0, 4);
// Positive or negative lookbehind.
if (prefix === '(?<=' || prefix === '(?<!') {
return prefix + '^' + originalMatcher.slice(4);
}
// Simple ^.
return '^' + originalMatcher;
}
/**
* Creates an actual regexp object from the regexp string (only for JS target
* langauges, other plugins use rawMatcher instead ad code generation).
*/
}, {
key: '_buildMatcher',
value: function _buildMatcher(rawMatcher) {
this._matcher = null;
try {
var flags = [];
if (this._options['case-insensitive']) {
flags.push('i');
}
this._matcher = new RegExp(rawMatcher, flags.join(''));
} catch (e) {
/* Skip for other languages */
}
return this._matcher;
}
/**
* Builds a wrapper function on top of the handler.
* This is in order to be able directly modify `yytext`,
* (which is closured), and return a token.
*/
}, {
key: '_buildHandler',
value: function _buildHandler(tokenHandler) {
try {
/* Generate the function handler only for JS language */
var handler = _codeUnit2.default.createHandler( /* no params */'', tokenHandler);
return function (matched, tokenizer) {
_codeUnit2.default.setBindings({
yytext: matched,
yyleng: matched.length
});
// Call the handler.
var token = handler.call(tokenizer);
var yytext = _codeUnit2.default.getSandbox().yytext;
// Update the `yyleng` in case `yytext` was modified.
_codeUnit2.default.setBindings({
yyleng: yytext.length
});
// The handler may mutate `yytext` during execution,
// return a possibly updated one, along with the token.
return [yytext, token];
};
} catch (e) {
/* And skip for other languages, which use raw handler in generator */
}
}
}], [{
key: 'matcherFromTerminal',
value: function matcherFromTerminal(terminal) {
return terminal.slice(1, -1).replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&');
}
}]);
return LexRule;
}();
exports.default = LexRule;