parseit.js
Version:
Customizable parsing library for converting a list of tokens into an AST tree
178 lines • 6.91 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.either = void 0;
const rule_1 = __importStar(require("./rule"));
/** Grammar object class. Used to define rules and other parsing details */
class Grammar {
rules = {};
currentData = {};
ignored = [];
/** The name of the rule from which the parser will begin matching */
startRule = "";
/** Define a rule variation */
rule = (name) => {
this.currentData = {
name: name,
content: []
};
return this.chain();
};
/** Define a starting rule, from which the parser will begin matching */
startFrom(rule) {
this.startRule = rule;
}
/** Ignore specific tokens globally. This can be overrided with a "overrideIgnore" rule method */
ignore(...args) {
args.forEach(a => {
let p = convertStringToAtom(a);
if (p)
this.ignored.push(p);
});
}
/** Check if a token is globally ignored */
getIgnored(token, rule) {
const ignored = (rule && rule.ignored) ? rule.ignored : this.ignored;
return ignored.filter(f => f.name == token.type && (f.value === undefined || f.value == token.value))[0];
}
/** Get a rule by name. Will return a rule object with all of it's variations */
getRule(name) {
return this.rules[name];
}
/** Add tokens and other rules to the rule normally */
from = (...args) => {
args.forEach(a => {
let p = convertStringToAtom(a);
if (p)
this.currentData.content.push(p);
});
return this.chain();
};
/** Add tokens and other rules within a binary loop */
binaryLoop = (args) => {
const loop = new rule_1.GrammarBinaryLoop();
args.forEach(a => {
let p = convertStringToAtom(a);
if (p)
loop.content.push(p);
});
this.currentData.content.push(loop);
return this.chain();
};
/** Add a statement and a separator within a block loop */
blockLoop = (statement, separator) => {
const loop = new rule_1.GrammarBlockLoop();
loop.content = [convertStringToAtom(statement)];
if (separator)
loop.content.push(convertStringToAtom(separator));
this.currentData.content.push(loop);
return this.chain();
};
/** Override global ignore settings for this rule variation. This will completely replace the global ignore settings */
overrideIgnore = (...args) => {
this.currentData.ignored = [];
args.forEach(a => {
let p = convertStringToAtom(a);
if (p)
this.currentData.ignored.push(p);
});
return this.chain();
};
/** By default, when going to the next rule, the parser rewinds itself to the tokens that were previosly skip.
This can sometimes cause infinite loops. Use this method to prevent this behaviour for the rule variation */
preventRollback = () => {
this.currentData.preventRollback = true;
return this.chain();
};
/** Convert data automatically to a specific AST node. The arguments for the node's constructor must be in the correct order */
as = (match) => {
this.currentData.match = match;
this.make();
};
/** Keep the existing data as is. This will pass the first data item to the next rule */
pass = () => {
this.currentData.match = rule_1.GrammarRuleSpecialMatch.PASS;
this.make();
};
/** Use a function to convert the data in some way. This function takes an array of data items as the argument */
decide = (func) => {
this.currentData.match = rule_1.GrammarRuleSpecialMatch.FUNC;
this.currentData.func = func;
this.make();
};
/** Selects a certain item from the data list. Similar to pass, except instead of the first, you can pick any index */
select = (index) => {
this.currentData.match = rule_1.GrammarRuleSpecialMatch.FUNC;
this.currentData.func = (data) => data[index];
this.make();
};
make = () => {
if (!this.rules[this.currentData.name])
this.rules[this.currentData.name] = [];
this.rules[this.currentData.name].push(new rule_1.default(this.currentData.name, this.currentData.content, this.currentData.match, this.currentData.func, this.currentData.ignored, this.currentData.preventRollback));
};
chain = () => {
return {
from: this.from,
binaryLoop: this.binaryLoop,
blockLoop: this.blockLoop,
overrideIgnore: this.overrideIgnore,
preventRollback: this.preventRollback,
as: this.as,
pass: this.pass,
decide: this.decide,
select: this.select
};
};
}
exports.default = Grammar;
/** This selector allows multiple variants inside a single rule. Useful with loops */
function either(...args) {
const variants = args.map(a => convertStringToAtom(a));
const component = new rule_1.GrammarEither();
component.variants = variants;
return component;
}
exports.either = either;
function convertStringToAtom(str) {
let p;
let skip = false;
if (typeof str != 'string')
return str;
if (str.endsWith("&")) {
str = str.slice(0, -1);
skip = true;
}
if (str.startsWith("@")) {
// slice off "@" at the beginning
// and split into name and value by the ":" separator
let strSpl = str.slice(1).split(':');
p = new rule_1.GrammarAtom(strSpl[0], rule_1.GrammarAtomType.TOKEN, strSpl[1], skip);
}
if (str.startsWith("$"))
p = new rule_1.GrammarAtom(str.slice(1), rule_1.GrammarAtomType.RULE);
return p;
}
//# sourceMappingURL=grammar.js.map