clarity-pattern-parser
Version:
Parsing Library for Typescript and Javascript.
252 lines (199 loc) • 6.52 kB
text/typescript
import { Node } from "../ast/Node";
import { Cursor } from "./Cursor";
import { findPattern } from "./findPattern";
import { ParseResult } from "./ParseResult";
import { Pattern } from "./Pattern";
import { testPattern } from './testPattern';
import { execPattern } from "./execPattern";
let idIndex = 0;
export interface FiniteRepeatOptions {
divider?: Pattern;
min?: number;
max?: number;
trimDivider?: boolean;
}
export class FiniteRepeat implements Pattern {
private _id: string;
private _type: string;
private _name: string;
private _parent: Pattern | null;
private _children: Pattern[];
private _hasDivider: boolean;
private _min: number;
private _max: number;
private _trimDivider: boolean;
private _firstIndex: number;
get id() {
return this._id;
}
get type() {
return this._type;
}
get name() {
return this._name;
}
get parent() {
return this._parent;
}
set parent(value: Pattern | null) {
this._parent = value;
}
get children() {
return this._children;
}
get min() {
return this._min;
}
get max() {
return this._max;
}
get startedOnIndex() {
return this._firstIndex;
}
constructor(name: string, pattern: Pattern, options: FiniteRepeatOptions = {}) {
this._id = `finite-repeat-${idIndex++}`;
this._type = "finite-repeat";
this._name = name;
this._parent = null;
this._children = [];
this._hasDivider = options.divider != null;
this._min = options.min != null ? Math.max(options.min, 1) : 1;
this._max = Math.max(this.min, options.max || this.min);
this._trimDivider = options.trimDivider == null ? false : options.trimDivider;
this._firstIndex = 0;
for (let i = 0; i < this._max; i++) {
const child = pattern.clone();
child.parent = this;
this._children.push(child);
if (options.divider != null && (i < this._max - 1 || !this._trimDivider)) {
const divider = options.divider.clone();
divider.parent = this;
this._children.push(divider);
}
}
}
parse(cursor: Cursor): Node | null {
this._firstIndex = cursor.index;
const nodes: Node[] = [];
const modulo = this._hasDivider ? 2 : 1;
let matchCount = 0;
for (let i = 0; i < this._children.length; i++) {
const childPattern = this._children[i];
const runningIndex = cursor.index;
const node = childPattern.parse(cursor);
if (cursor.hasError) {
break;
}
if (i % modulo === 0 && !cursor.hasError) {
matchCount++;
}
if (node == null) {
cursor.moveTo(runningIndex);
} else {
nodes.push(node);
if (cursor.hasNext()) {
cursor.next();
} else {
break;
}
}
}
const endedOnDivider = this._hasDivider && nodes.length % modulo === 0;
if (this._trimDivider && endedOnDivider) {
const node = nodes.pop() as Node;
cursor.moveTo(node.firstIndex);
}
if (matchCount < this._min) {
const lastIndex = cursor.index;
cursor.moveTo(this._firstIndex);
cursor.recordErrorAt(this._firstIndex, lastIndex, this);
return null;
}
if (nodes.length === 0 && !cursor.hasError) {
cursor.moveTo(this._firstIndex);
return null;
}
const firstIndex = nodes[0].firstIndex;
const lastIndex = nodes[nodes.length - 1].lastIndex;
cursor.resolveError();
cursor.moveTo(lastIndex);
const node = new Node(
this._type,
this.name,
firstIndex,
lastIndex,
nodes
);
return node;
}
test(text: string, record = false): boolean {
return testPattern(this, text, record);
}
exec(text: string, record = false): ParseResult {
return execPattern(this, text, record);
}
clone(name = this._name): Pattern {
let min = this._min;
let max = this._max;
const clone = new FiniteRepeat(
name,
this._children[0],
{
divider: this._hasDivider ? this._children[1] : undefined,
min,
max,
trimDivider: this._trimDivider
}
);
clone._id = this._id;
return clone;
}
getTokens(): string[] {
return this._children[0].getTokens();
}
getTokensAfter(childReference: Pattern): string[] {
const patterns = this.getPatternsAfter(childReference);
const tokens: string[] = [];
patterns.forEach(p => tokens.push(...p.getTokens()));
return tokens;
}
getNextTokens(): string[] {
if (this._parent == null) {
return [];
}
return this._parent.getTokensAfter(this);
}
getPatterns(): Pattern[] {
return this._children[0].getPatterns();
}
getPatternsAfter(childReference: Pattern): Pattern[] {
const childIndex = this._children.indexOf(childReference);
// If Reference Pattern isn't a child.
if (childIndex === -1) {
return [];
}
// If Reference Pattern is the last pattern. Ask for the parents next patterns
if (childIndex === this._children.length - 1) {
if (this._parent == null) {
return [];
} else {
return this._parent.getPatternsAfter(this);
}
}
// Get the next childs patterns.
const nextChild = this._children[childIndex + 1];
return nextChild.getPatterns();
}
getNextPatterns(): Pattern[] {
if (this._parent == null) {
return [];
}
return this._parent.getPatternsAfter(this);
}
find(predicate: (p: Pattern) => boolean): Pattern | null {
return findPattern(this, predicate);
}
isEqual(pattern: FiniteRepeat): boolean {
return pattern.type === this.type && this.children.every((c, index) => c.isEqual(pattern.children[index]));
}
}