src/parser.js
/*
* @Author: Gilles Coomans
* @Date: 2017-02-26 01:27:44
* @Last Modified by: Gilles Coomans
* @Last Modified time: 2017-02-26 14:46:43
*/
/**
* The Parser class.
* @public
*/
class Parser {
/**
* @param {Object} rules an object containing rules
* @param {String} defaultRule the default rule to use when parsing
*/
constructor(rules, defaultRule) {
/**
* the rules map
* @type {Object}
*/
this.rules = rules;
/**
* the default rule's name to use
* @type {String}
*/
this.defaultRule = defaultRule;
}
/**
* find rule by name
* @param {String} name the rule's name
* @return {Rule} the finded rule
* @throws {Error} If rule not found
*/
getRule(name) {
const r = this.rules[name];
if (!r)
throw new Error('elenpi parser : rules not found : ' + name);
return r;
}
/**
* Parse provided string with specific rule
* @param {String} string the string to parse
* @param {String} rule the name of the rule to apply. default is null (will use parser's default method if not provided).
* @param {Object} descriptor the main descriptor object
* @return {Object} the decorated descriptor
* @throws {Error} If parsing fail (for any reason)
*/
parse(string, rule = null, descriptor = {}) {
const env = {};
env.parser = this;
env.string = string;
rule = rule || this.getRule(this.defaultRule);
Parser.exec(rule, descriptor, env);
if (!env.error && env.string.length) {
env.error = true;
env.errorMessage = 'string wasn\'t parsed entierly';
}
if (env.error) {
const pos = string.length - env.string.length,
posInFile = getPositionInFile(string, pos);
throw new Error('Parsing failed : ' + (env.errorMessage || 'no rules matched') + ' : (line:' + posInFile.line + ' , col:' + posInFile.col + ') near :\n', string.substring(pos, pos + 50));
}
return descriptor;
}
/**
* Execute a rule (only for those who developing grammars)
* @param {String|Rule} rule the name of the rule to use or the rule itself
* @param {Object} descriptor the descriptor to decorate
* @param {Object} env the inner-job main object where parser, parsed string and eventual errors are stored
* @return {Void} nothing
* @public
* @throws {Error} If rule is string (so it's a rule's name) and referenced rule could not be found with it.
*/
static exec(rule, descriptor, env) {
if (env.error)
return;
if (typeof rule === 'string')
rule = env.parser.getRule(rule);
const rules = rule._queue;
for (let i = 0, current, len = rules.length; i < len; ++i) {
current = rules[i];
if (current.__elenpi__)
Parser.exec(current, descriptor, env);
else // is function
current(env, descriptor);
if (env.error)
break;
}
}
}
function getPositionInFile(string, position) {
const splitted = string.split(/\r|\n/),
len = splitted.length;
let lineNumber = 0,
current = 0,
line,
lineLength;
while (lineNumber < len) {
line = splitted[lineNumber];
lineLength = line.length;
if (position <= (current + lineLength))
break;
current += lineLength;
lineNumber++;
}
return {
line: lineNumber + 1,
col: position - current
};
}
export default Parser;