vexflow
Version:
A JavaScript library for rendering music notation and guitar tablature.
165 lines (164 loc) • 4.95 kB
JavaScript
import { log, RuntimeError } from './util.js';
function L(...args) {
if (Parser.DEBUG)
log('VexFlow.Parser', args);
}
const NO_ERROR_POS = -1;
function flattenMatches(r) {
if ('matchedString' in r)
return r.matchedString;
if ('results' in r)
return flattenMatches(r.results);
const results = r;
if (results.length === 1)
return flattenMatches(results[0]);
if (results.length === 0)
return null;
return results.map(flattenMatches);
}
export class Parser {
constructor(grammar) {
this.grammar = grammar;
this.line = '';
this.pos = 0;
this.errorPos = NO_ERROR_POS;
}
parse(line) {
this.line = line;
this.pos = 0;
this.errorPos = NO_ERROR_POS;
const result = this.expect(this.grammar.begin());
result.errorPos = this.errorPos;
return result;
}
matchFail(returnPos) {
if (this.errorPos === NO_ERROR_POS)
this.errorPos = this.pos;
this.pos = returnPos;
}
matchSuccess() {
this.errorPos = NO_ERROR_POS;
}
matchToken(token, noSpace = false) {
const regexp = noSpace ? new RegExp('^((' + token + '))') : new RegExp('^((' + token + ')\\s*)');
const workingLine = this.line.slice(this.pos);
const result = workingLine.match(regexp);
if (result !== null) {
return {
success: true,
matchedString: result[2],
incrementPos: result[1].length,
pos: this.pos,
};
}
else {
return { success: false, pos: this.pos };
}
}
expectOne(rule, maybe = false) {
const results = [];
const pos = this.pos;
let allMatches = true;
let oneMatch = false;
maybe = maybe === true || rule.maybe === true;
if (rule.expect) {
for (const next of rule.expect) {
const localPos = this.pos;
const result = this.expect(next);
if (result.success) {
results.push(result);
oneMatch = true;
if (rule.or)
break;
}
else {
allMatches = false;
if (!rule.or) {
this.pos = localPos;
break;
}
}
}
}
const gotOne = (rule.or && oneMatch) || allMatches;
const success = gotOne || maybe === true;
const numMatches = gotOne ? 1 : 0;
if (maybe && !gotOne)
this.pos = pos;
if (success) {
this.matchSuccess();
}
else {
this.matchFail(pos);
}
return { success, results, numMatches };
}
expectOneOrMore(rule, maybe = false) {
const results = [];
const pos = this.pos;
let numMatches = 0;
let more = true;
do {
const result = this.expectOne(rule);
if (result.success && result.results) {
numMatches++;
results.push(result.results);
}
else {
more = false;
}
} while (more);
const success = numMatches > 0 || maybe === true;
if (maybe && !(numMatches > 0))
this.pos = pos;
if (success) {
this.matchSuccess();
}
else {
this.matchFail(pos);
}
return { success, results, numMatches };
}
expectZeroOrMore(rule) {
return this.expectOneOrMore(rule, true);
}
expect(ruleFunc) {
L('Evaluating rule function:', ruleFunc);
if (!ruleFunc) {
throw new RuntimeError('Invalid rule function');
}
let result;
const rule = ruleFunc.bind(this.grammar)();
if (rule.token) {
result = this.matchToken(rule.token, rule.noSpace === true);
if (result.success) {
this.pos += result.incrementPos;
}
}
else if (rule.expect) {
if (rule.oneOrMore) {
result = this.expectOneOrMore(rule);
}
else if (rule.zeroOrMore) {
result = this.expectZeroOrMore(rule);
}
else {
result = this.expectOne(rule);
}
}
else {
L(rule);
throw new RuntimeError('Bad grammar! No `token` or `expect` property ' + rule);
}
const matches = [];
result.matches = matches;
if (result.results) {
result.results.forEach((r) => matches.push(flattenMatches(r)));
}
if (rule.run && result.success) {
rule.run({ matches });
}
return result;
}
}
Parser.DEBUG = false;