UNPKG

clarity-pattern-parser

Version:

Parsing Library for Typescript and Javascript.

256 lines (198 loc) 5.81 kB
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; } }