clarity-pattern-parser
Version:
Parsing Library for Typescript and Javascript.
256 lines (198 loc) • 5.81 kB
text/typescript
import { Node } from "../ast/Node";
import { Cursor } from "./Cursor";
import { Pattern } from "./Pattern";
import { findPattern } from "./findPattern";
import { ParseResult } from "./ParseResult";
import { Context } from "./Context";
import { testPattern } from "./testPattern";
import { execPattern } from "./execPattern";
let idIndex = 0;
export class Reference implements Pattern {
private _id: string;
private _type: string;
private _name: string;
private _referencePatternName: string;
private _parent: Pattern | null;
private _cachedPattern: Pattern | null;
private _pattern: Pattern | null;
private _children: Pattern[];
private _firstIndex: number;
private _cachedAncestors: boolean;
private _recursiveAncestors: Reference[];
get id(): string {
return this._id;
}
get type(): string {
return this._type;
}
get name(): string {
return this._name;
}
get parent(): Pattern | null {
return this._parent;
}
set parent(pattern: Pattern | null) {
this._parent = pattern;
}
get children(): Pattern[] {
return this._children;
}
get startedOnIndex() {
return this._firstIndex;
}
constructor(name: string, referencePatternName?: string) {
this._id = `reference-${idIndex++}`;
this._type = "reference";
this._name = name;
this._referencePatternName = referencePatternName || name;
this._parent = null;
this._pattern = null;
this._cachedPattern = null;
this._children = [];
this._firstIndex = 0;
this._cachedAncestors = false;
this._recursiveAncestors = [];
}
test(text: string, record = false): boolean {
return testPattern(this, text, record);
}
exec(text: string, record = false): ParseResult {
return execPattern(this, text, record);
}
parse(cursor: Cursor): Node | null {
this._firstIndex = cursor.index;
const pattern = this.getReferencePatternSafely();
this._cacheAncestors(pattern.id);
if (this._isBeyondRecursiveAllowance()) {
cursor.recordErrorAt(this._firstIndex, this._firstIndex, this);
return null;
}
return pattern.parse(cursor);
}
private _cacheAncestors(id: string) {
if (!this._cachedAncestors) {
this._cachedAncestors = true;
let pattern: Pattern | null = this.parent;
while (pattern != null) {
if (pattern.id === id) {
this._recursiveAncestors.push(pattern as Reference);
}
pattern = pattern.parent;
}
}
}
private _isBeyondRecursiveAllowance() {
let depth = 0;
for (let pattern of this._recursiveAncestors) {
if (pattern.startedOnIndex === this.startedOnIndex) {
depth++;
if (depth > 1) {
return true;
}
}
}
return false;
}
getReferencePatternSafely(): Pattern {
if (this._pattern === null) {
let pattern: Pattern | null = null;
if (this._cachedPattern == null) {
pattern = this._findPattern();
} else {
pattern = this._cachedPattern;
}
if (pattern === null) {
throw new Error(`Couldn't find '${this._referencePatternName}' pattern within tree.`);
}
const clonedPattern = pattern.clone(this._name);
clonedPattern.parent = this;
this._pattern = clonedPattern;
this._children = [this._pattern];
}
return this._pattern;
}
private _findPattern(): Pattern | null {
let pattern = this._parent;
while (pattern != null) {
if (pattern.type !== "context") {
pattern = pattern.parent;
continue;
}
const foundPattern = (pattern as Context).getPatternWithinContext(this._referencePatternName);
if (foundPattern != null && this._isValidPattern(foundPattern)) {
return foundPattern;
}
pattern = pattern.parent;
}
const root = this._getRoot();
return findPattern(root, (pattern: Pattern) => {
return pattern.name === this._referencePatternName && this._isValidPattern(pattern);
});
}
private _isValidPattern(pattern: Pattern) {
if (pattern.type === "reference") {
return false;
}
if (pattern.type === "context" && pattern.children[0].type === "reference") {
return false;
}
return true;
}
private _getRoot(): Pattern {
let node: Pattern = this;
while (true) {
const parent = node.parent;
if (parent == null) {
break;
} else {
node = parent;
}
}
return node;
}
getTokens(): string[] {
return this.getReferencePatternSafely().getTokens();
}
getTokensAfter(_lastMatched: Pattern): string[] {
if (this._parent == null) {
return [];
}
return this._parent.getTokensAfter(this);
}
getNextTokens(): string[] {
if (this.parent == null) {
return [];
}
return this.parent.getTokensAfter(this);
}
getPatterns(): Pattern[] {
return this.getReferencePatternSafely().getPatterns();
}
getPatternsAfter(_childReference: Pattern): Pattern[] {
if (this._parent == null) {
return [];
}
return this._parent.getPatternsAfter(this);
}
getNextPatterns(): Pattern[] {
if (this.parent == null) {
return [];
}
return this.parent.getPatternsAfter(this);
}
find(_predicate: (p: Pattern) => boolean): Pattern | null {
return null;
}
clone(name = this._name): Pattern {
const clone = new Reference(name, this._referencePatternName);
clone._id = this._id;
// Optimize future clones, by caching the pattern we already found.
if (this._pattern != null) {
clone._cachedPattern = this._pattern;
}
return clone;
}
isEqual(pattern: Reference): boolean {
return pattern.type === this.type && pattern.name === this.name;
}
}