clarity-pattern-parser
Version:
Parsing Library for Typescript and Javascript.
1,514 lines (1,491 loc) • 175 kB
JavaScript
function defaultVisitor(node) {
return node;
}
let idIndex$c = 0;
class Node {
get id() {
return this._id;
}
get type() {
return this._type;
}
get name() {
return this._name;
}
get value() {
return this.toString();
}
get firstIndex() {
return this._firstIndex;
}
get lastIndex() {
return this._lastIndex;
}
get startIndex() {
return this._firstIndex;
}
get endIndex() {
return this._lastIndex + 1;
}
get parent() {
return this._parent;
}
get children() {
return this._children;
}
get hasChildren() {
return this._children.length > 0;
}
get isLeaf() {
return !this.hasChildren;
}
constructor(type, name, firstIndex, lastIndex, children = [], value = "") {
this._id = String(idIndex$c++);
this._type = type;
this._name = name;
this._firstIndex = firstIndex;
this._lastIndex = lastIndex;
this._parent = null;
this._children = children;
this._value = value;
this._children.forEach(c => c._parent = this);
}
removeChild(node) {
const index = this._children.indexOf(node);
if (index > -1) {
this._children.splice(index, 1);
node._parent = null;
}
}
findChildIndex(node) {
return this._children.indexOf(node);
}
spliceChildren(index, deleteCount, ...items) {
const removedItems = this._children.splice(index, deleteCount, ...items);
removedItems.forEach(i => i._parent = null);
items.forEach(i => i._parent = this);
return removedItems;
}
removeAllChildren() {
this.spliceChildren(0, this._children.length);
}
replaceChild(newNode, referenceNode) {
const index = this.findChildIndex(referenceNode);
if (index > -1) {
this.spliceChildren(index, 1, newNode);
newNode._parent = this;
referenceNode._parent = null;
}
}
replaceWith(newNode) {
if (this._parent != null) {
this._parent.replaceChild(newNode, this);
}
}
insertBefore(newNode, referenceNode) {
newNode._parent = this;
if (referenceNode == null) {
this._children.push(newNode);
return;
}
const index = this.findChildIndex(referenceNode);
if (index > -1) {
this._children.splice(index, 0, newNode);
}
}
appendChild(newNode) {
this.append(newNode);
}
append(...nodes) {
nodes.forEach((newNode) => {
newNode._parent = this;
this._children.push(newNode);
});
}
nextSibling() {
if (this._parent == null) {
return null;
}
const children = this._parent._children;
const index = children.indexOf(this);
if (index > -1 && index < children.length - 1) {
return children[index + 1];
}
return null;
}
previousSibling() {
if (this._parent == null) {
return null;
}
const children = this._parent._children;
const index = children.indexOf(this);
if (index > -1 && index > 0) {
return children[index - 1];
}
return null;
}
find(predicate, breadthFirst = false) {
let match = null;
if (breadthFirst) {
this.walkBreadthFirst(n => {
if (predicate(n)) {
match = n;
return false;
}
});
}
else {
this.walkUp(n => {
if (predicate(n)) {
match = n;
return false;
}
});
}
return match;
}
findAll(predicate, breadthFirst = false) {
const matches = [];
if (breadthFirst) {
this.walkBreadthFirst(n => {
if (predicate(n)) {
matches.push(n);
}
});
}
else {
this.walkUp(n => {
if (predicate(n)) {
matches.push(n);
}
});
}
return matches;
}
findRoot() {
let pattern = this;
while (true) {
if (pattern.parent == null) {
return pattern;
}
pattern = pattern.parent;
}
}
findAncestor(predicate) {
let parent = this._parent;
while (parent != null) {
if (predicate(parent)) {
return parent;
}
parent = parent._parent;
}
return null;
}
walkUp(callback) {
var _a;
const childrenCopy = this._children.slice();
const result = childrenCopy.every(c => c.walkUp(callback));
return ((_a = callback(this)) !== null && _a !== void 0 ? _a : true) && result;
}
walkDown(callback) {
var _a;
const childrenCopy = this._children.slice();
return ((_a = callback(this)) !== null && _a !== void 0 ? _a : true) && childrenCopy.every(c => c.walkDown(callback));
}
walkBreadthFirst(callback) {
const queue = [this];
while (queue.length > 0) {
const current = queue.shift();
if (callback(current) === false) {
return false;
}
queue.push(...current.children);
}
return true;
}
transform(visitors) {
const childrenCopy = this._children.slice();
const visitor = visitors[this.name] == null ? defaultVisitor : visitors[this.name];
const children = childrenCopy.map(c => c.transform(visitors));
this.removeAllChildren();
this.append(...children);
return visitor(this);
}
flatten() {
const nodes = [];
this.walkDown((node) => {
if (!node.hasChildren) {
nodes.push(node);
}
});
return nodes;
}
compact() {
const value = this.toString();
this.removeAllChildren();
this._value = value;
}
remove() {
if (this._parent != null) {
this._parent.removeChild(this);
}
}
clone() {
const node = new Node(this._type, this._name, this._firstIndex, this._lastIndex, this._children.map((c) => c.clone()), this._value);
return node;
}
normalize(startIndex = this._firstIndex) {
let length = 0;
let runningOffset = startIndex;
if (this.children.length === 0) {
length = this._value.length;
}
else {
for (let x = 0; x < this.children.length; x++) {
const child = this.children[x];
const childLength = child.normalize(runningOffset);
runningOffset += childLength;
length += childLength;
}
}
this._firstIndex = startIndex;
this._lastIndex = Math.max(startIndex + length - 1, 0);
return length;
}
toString() {
if (this._children.length === 0) {
return this._value;
}
return this._children.map(c => c.toString()).join("");
}
toCycleFreeObject() {
return {
id: this._id,
type: this._type,
name: this._name,
value: this.toString(),
startIndex: this.startIndex,
endIndex: this.endIndex,
children: this._children.map(c => c.toCycleFreeObject()),
};
}
toJson(space) {
return JSON.stringify(this.toCycleFreeObject(), null, space);
}
isEqual(node) {
return node._type === this._type &&
node._name === this._name &&
node._firstIndex === this._firstIndex &&
node._lastIndex === this._lastIndex &&
// Compare the PUBLIC value (`toString()`), not the private `_value`
// field. A composite node built incrementally (children appended after
// construction, e.g. by PrecedenceTree) keeps a stale `_value` of "",
// even though `toString()`/`value` reflect its children — so comparing
// `_value` gave false negatives. For a leaf `value === _value`; for a
// parent it's the child-join, which is already implied by the recursive
// children check below, so this can never mask a real difference.
node.value === this.value &&
this._children.length === node._children.length &&
this._children.every((child, index) => child.isEqual(node._children[index]));
}
static createValueNode(type, name, value = "") {
return new Node(type, name, 0, 0, [], value);
}
static createNode(type, name, children = []) {
const value = children.map(c => c.toString()).join("");
return new Node(type, name, 0, 0, children, value);
}
}
function compact(node, nodeMap) {
node.walkBreadthFirst(n => {
if (nodeMap[n.name]) {
n.compact();
}
});
return node;
}
function remove(node, nodeMap) {
node.walkBreadthFirst(n => {
if (nodeMap[n.name]) {
n.remove();
}
});
return node;
}
/******************************************************************************
Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
function __awaiter(thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
}
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
var e = new Error(message);
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
};
const tokens = (pattern, arg) => {
if (pattern.type === "regex" && Array.isArray(arg)) {
const regex = pattern;
const tokens = [];
arg.forEach(token => {
if (typeof token === "string") {
tokens.push(token);
}
});
regex.setTokens(tokens);
}
};
function generateErrorMessage(pattern, cursor) {
const furthestMatch = cursor.leafMatch;
if (furthestMatch == null || furthestMatch.node == null || furthestMatch.pattern == null) {
const suggestions = cleanSuggestions(pattern.getTokens()).join(", ");
return `Error at line 1, column 1. Hint: ${suggestions}`;
}
const endIndex = furthestMatch.node.endIndex;
if (endIndex === 0) {
const suggestions = cleanSuggestions(pattern.getTokens()).join(", ");
return `Error at line 1, column 1. Hint: ${suggestions}`;
}
const lastPattern = furthestMatch.pattern;
const suggestions = cleanSuggestions(lastPattern.getNextTokens());
const strUpToError = cursor.substring(0, endIndex);
const lines = strUpToError.split("\n");
const lastLine = lines[lines.length - 1];
const line = lines.length;
const column = lastLine.length;
return `Error at line ${line}, column ${column}. Hint: ${suggestions}`;
}
function cleanSuggestions(suggestions) {
return suggestions.map(s => s.trim()).filter(s => s.length > 0);
}
let contextId = 0;
class Context {
get id() {
return this._id;
}
get type() {
return this._type;
}
get name() {
return this._name;
}
get parent() {
return this._parent;
}
set parent(pattern) {
this._parent = pattern;
}
get children() {
return this._children;
}
get startedOnIndex() {
return this.children[0].startedOnIndex;
}
getPatternWithinContext(name) {
if (this._name === name || this._referencePatternName === name) {
return this;
}
return this._patterns[name] || null;
}
getPatternsWithinContext() {
return Object.assign({}, this._patterns);
}
constructor(name, pattern, context = []) {
this._id = `context-${contextId++}`;
this._type = "context";
this._name = name;
this._parent = null;
this._patterns = {};
this._referencePatternName = name;
const clonedPattern = pattern.clone();
context.forEach(p => this._patterns[p.name] = p);
clonedPattern.parent = this;
this._pattern = clonedPattern;
this._children = [clonedPattern];
}
parse(cursor) {
return this._pattern.parse(cursor);
}
exec(text, record) {
return this._pattern.exec(text, record);
}
test(text, record) {
return this._pattern.test(text, record);
}
clone(name = this._name) {
const clone = new Context(name, this._pattern.clone(name), Object.values(this._patterns));
clone._referencePatternName = this._referencePatternName;
clone._id = this._id;
return clone;
}
getTokens() {
return this._pattern.getTokens();
}
getTokensAfter(_childReference) {
if (this._parent == null) {
return [];
}
return this._parent.getTokensAfter(this);
}
getNextTokens() {
if (this._parent == null) {
return [];
}
return this._parent.getTokensAfter(this);
}
getPatterns() {
return this._pattern.getPatterns();
}
getPatternsAfter(_childReference) {
if (this._parent == null) {
return [];
}
return this._parent.getPatternsAfter(this);
}
getNextPatterns() {
if (this._parent == null) {
return [];
}
return this._parent.getPatternsAfter(this);
}
find(predicate) {
return this._pattern.find(predicate);
}
isEqual(pattern) {
return pattern.type === this.type && this.children.every((c, index) => c.isEqual(pattern.children[index]));
}
}
function findPattern(pattern, predicate) {
let children = [];
if (pattern.type === "reference") {
children = [];
}
else {
children = pattern.children;
}
for (const child of children) {
const result = findPattern(child, predicate);
if (result !== null) {
return result;
}
}
if (predicate(pattern)) {
return pattern;
}
else {
return null;
}
}
function clonePatterns(patterns) {
return patterns.map(p => p.clone());
}
function filterOutNull(nodes) {
const filteredNodes = [];
for (const node of nodes) {
if (node !== null) {
filteredNodes.push(node);
}
}
return filteredNodes;
}
function isRecursivePattern(pattern) {
let onPattern = pattern.parent;
let depth = 0;
while (onPattern != null) {
if (onPattern.id === pattern.id) {
depth++;
}
onPattern = onPattern.parent;
if (depth > 1) {
return true;
}
}
return false;
}
class ParseError {
constructor(startIndex, lastIndex, pattern) {
this.firstIndex = startIndex;
this.startIndex = startIndex;
this.lastIndex = lastIndex;
this.endIndex = lastIndex + 1;
this.pattern = pattern;
}
}
class CursorHistory {
constructor() {
this._isRecording = false;
this._leafMatches = [{ pattern: null, node: null }];
this._furthestError = null;
this._currentError = null;
this._rootMatch = { pattern: null, node: null };
this._patterns = [];
this._nodes = [];
this._errors = [];
this._records = [];
}
get isRecording() {
return this._isRecording;
}
get rootMatch() {
return this._rootMatch;
}
get leafMatch() {
return this._leafMatches[this._leafMatches.length - 1];
}
get leafMatches() {
return this._leafMatches;
}
get furthestError() {
return this._furthestError;
}
get errors() {
return this._errors;
}
get error() {
return this._currentError;
}
get records() {
return this._records;
}
get nodes() {
return this._nodes;
}
get patterns() {
return this._patterns;
}
recordMatch(pattern, node) {
const record = {
pattern,
ast: node,
error: null
};
if (this._isRecording) {
this._patterns.push(pattern);
this._nodes.push(node);
this._records.push(record);
}
this._rootMatch.pattern = pattern;
this._rootMatch.node = node;
const leafMatch = this._leafMatches[this._leafMatches.length - 1];
const isFurthestMatch = leafMatch.node === null || node.lastIndex > leafMatch.node.lastIndex;
const isSameIndexMatch = leafMatch.node === null || node.lastIndex === leafMatch.node.lastIndex;
if (isFurthestMatch) {
// This is to save on GC churn.
const match = this._leafMatches.pop();
match.pattern = pattern;
match.node = node;
this._leafMatches.length = 0;
this._leafMatches.push(match);
}
else if (isSameIndexMatch) {
const isAncestor = this._leafMatches.some((m) => {
var _a;
let parent = (_a = m.pattern) === null || _a === void 0 ? void 0 : _a.parent;
while (parent != null) {
if (parent === pattern.parent) {
return true;
}
parent = parent.parent;
}
return false;
});
if (!isAncestor) {
this._leafMatches.unshift({ pattern, node });
}
}
}
recordErrorAt(startIndex, lastIndex, pattern) {
const error = new ParseError(startIndex, lastIndex, pattern);
const record = {
pattern,
ast: null,
error
};
this._currentError = error;
if (this._furthestError === null || lastIndex > this._furthestError.lastIndex) {
this._furthestError = error;
}
if (this._isRecording) {
this._errors.push(error);
this.records.push(record);
}
}
resolveError() {
this._currentError = null;
}
startRecording() {
this._isRecording = true;
}
stopRecording() {
this._isRecording = false;
}
}
const segmenter = new Intl.Segmenter("und", { granularity: "grapheme" });
class Cursor {
get text() {
return this._text;
}
get isOnFirst() {
return this._index === 0;
}
get isOnLast() {
return this._index === this.getLastIndex();
}
get isRecording() {
return this._history.isRecording;
}
get rootMatch() {
return this._history.rootMatch;
}
get allMatchedNodes() {
return this._history.nodes;
}
get allMatchedPatterns() {
return this._history.patterns;
}
get leafMatch() {
return this._history.leafMatch;
}
get leafMatches() {
return this._history.leafMatches;
}
get furthestError() {
return this._history.furthestError;
}
get error() {
return this._history.error;
}
get errors() {
return this._history.errors;
}
get records() {
return this._history.records;
}
get index() {
return this._index;
}
get length() {
return this._length;
}
get hasError() {
return this._history.error != null;
}
get currentChar() {
const index = this.getCharStartIndex(this._index);
return this.text.slice(index, index + this._charSize[index]);
}
constructor(text) {
this._text = text;
this._length = text.length;
this._charSize = [];
this._charMap = [];
this._index = 0;
this._history = new CursorHistory();
let index = 0;
for (const segment of segmenter.segment(text)) {
const size = segment.segment.length;
for (let i = 0; i < size; i++) {
this._charMap.push(index);
this._charSize.push(size);
}
index += size;
}
}
hasNext() {
const index = this._charMap[this._index];
const charSize = this._charSize[index];
return index + charSize < this._length;
}
next() {
if (this.hasNext()) {
const index = this._charMap[this._index];
const size = this._charSize[index];
this.moveTo(index + size);
}
}
hasPrevious() {
var _a;
const index = this._charMap[this._index];
const previousIndex = (_a = this._charMap[index - 1]) !== null && _a !== void 0 ? _a : -1;
return previousIndex >= 0;
}
previous() {
var _a;
if (this.hasPrevious()) {
const index = this._charMap[this._index];
const previousIndex = (_a = this._charMap[index - 1]) !== null && _a !== void 0 ? _a : -1;
this.moveTo(previousIndex);
}
}
moveTo(position) {
if (position >= 0 && position < this._length) {
this._index = this._charMap[position];
}
}
moveToFirstChar() {
this._index = 0;
}
moveToLastChar() {
this._index = this.getLastIndex();
}
getLastIndex() {
return this._length - 1;
}
substring(first, last) {
return this._text.slice(first, last + 1);
}
recordMatch(pattern, node) {
this._history.recordMatch(pattern, node);
}
recordErrorAt(startIndex, lastIndex, onPattern) {
this._history.recordErrorAt(startIndex, lastIndex, onPattern);
}
resolveError() {
this._history.resolveError();
}
startRecording() {
this._history.startRecording();
}
stopRecording() {
this._history.stopRecording();
}
getCharStartIndex(index) {
return this._charMap[index];
}
getCharEndIndex(index) {
var _a;
let startIndex = this.getCharStartIndex(index);
return (_a = startIndex + this._charSize[startIndex]) !== null && _a !== void 0 ? _a : 1;
}
getCharLastIndex(index) {
var _a;
return (_a = this.getCharEndIndex(index) - 1) !== null && _a !== void 0 ? _a : 0;
}
}
function execPattern(pattern, text, record = false) {
const cursor = new Cursor(text);
if (cursor.length === 0) {
return { ast: null, cursor };
}
record && cursor.startRecording();
let ast = pattern.parse(cursor);
const resultLength = ast == null ? 0 : ast.value.length;
if (ast != null) {
const isMatch = ast.value === text;
if (!isMatch && !cursor.hasError) {
ast = null;
cursor.recordErrorAt(resultLength, cursor.length, pattern);
}
}
else {
cursor.recordErrorAt(resultLength, cursor.length, pattern);
}
return {
ast: ast,
cursor
};
}
function testPattern(pattern, text, record = false) {
const result = execPattern(pattern, text, record);
return !result.cursor.hasError;
}
let idIndex$b = 0;
class Sequence {
get id() {
return this._id;
}
get type() {
return this._type;
}
get name() {
return this._name;
}
get parent() {
return this._parent;
}
set parent(pattern) {
this._parent = pattern;
}
get children() {
return this._children;
}
get startedOnIndex() {
return this._firstIndex;
}
constructor(name, sequence) {
if (sequence.length === 0) {
throw new Error("Need at least one pattern with a 'sequence' pattern.");
}
const children = clonePatterns(sequence);
this._assignChildrenToParent(children);
this._id = `sequence-${idIndex$b++}`;
this._type = "sequence";
this._name = name;
this._parent = null;
this._children = children;
this._firstIndex = -1;
this._nodes = [];
}
_assignChildrenToParent(children) {
for (const child of children) {
child.parent = this;
}
}
test(text, record = false) {
return testPattern(this, text, record);
}
exec(text, record = false) {
return execPattern(this, text, record);
}
parse(cursor) {
this._firstIndex = cursor.index;
this._nodes = [];
const passed = this.tryToParse(cursor);
if (passed) {
const node = this.createNode(cursor);
if (node !== null) {
cursor.recordMatch(this, node);
}
return node;
}
return null;
}
tryToParse(cursor) {
let passed = false;
for (let i = 0; i < this._children.length; i++) {
const runningCursorIndex = cursor.index;
const nextPatternIndex = i + 1;
const hasMorePatterns = nextPatternIndex < this._children.length;
const node = this._children[i].parse(cursor);
const hasNoError = !cursor.hasError;
const hadMatch = node !== null;
if (hasNoError) {
this._nodes.push(node);
if (hasMorePatterns) {
if (hadMatch) {
if (cursor.hasNext()) {
// We had a match. Increment the cursor and use the next pattern.
cursor.next();
continue;
}
else {
// We are at the end of the text, it may still be valid, if all the
// following patterns are optional.
if (this._areRemainingPatternsOptional(i)) {
passed = true;
break;
}
// We didn't finish the parsing sequence.
cursor.recordErrorAt(this._firstIndex, cursor.index + 1, this);
break;
}
}
else {
// An optional pattern did not matched, try from the same spot on the next
// pattern.
cursor.moveTo(runningCursorIndex);
continue;
}
}
else {
// If we don't have any results from what we parsed then record error.
const lastNode = this.getLastValidNode();
if (lastNode === null && !this._areAllPatternsOptional()) {
cursor.recordErrorAt(this._firstIndex, cursor.index, this);
break;
}
// The sequence was parsed fully.
passed = true;
break;
}
}
else {
// The pattern failed.
cursor.moveTo(this._firstIndex);
break;
}
}
return passed;
}
getLastValidNode() {
const nodes = filterOutNull(this._nodes);
if (nodes.length === 0) {
return null;
}
return nodes[nodes.length - 1];
}
_areAllPatternsOptional() {
return this._areRemainingPatternsOptional(-1);
}
_areRemainingPatternsOptional(fromIndex) {
const startOnIndex = fromIndex + 1;
const length = this._children.length;
for (let i = startOnIndex; i < length; i++) {
const pattern = this._children[i];
if (pattern.type !== "optional") {
return false;
}
}
return true;
}
createNode(cursor) {
const children = filterOutNull(this._nodes);
if (children.length === 0) {
cursor.moveTo(this._firstIndex);
return null;
}
const lastIndex = children[children.length - 1].lastIndex;
cursor.moveTo(lastIndex);
return new Node("sequence", this._name, this._firstIndex, lastIndex, children);
}
getTokens() {
const tokens = [];
for (const pattern of this._children) {
if (isRecursivePattern(pattern) && pattern === this._children[0]) {
return tokens;
}
tokens.push(...pattern.getTokens());
if (pattern.type !== "optional" && pattern.type !== "not") {
break;
}
}
return tokens;
}
getTokensAfter(childReference) {
const patterns = this.getPatternsAfter(childReference);
const tokens = [];
patterns.forEach(p => tokens.push(...p.getTokens()));
return tokens;
}
getNextTokens() {
if (this.parent == null) {
return [];
}
return this.parent.getTokensAfter(this);
}
getPatterns() {
const patterns = [];
for (const pattern of this._children) {
if (isRecursivePattern(pattern) && pattern === this._children[0]) {
return patterns;
}
patterns.push(...pattern.getPatterns());
if (pattern.type !== "optional" && pattern.type !== "not") {
break;
}
}
return patterns;
}
getPatternsAfter(childReference) {
const patterns = [];
let nextSiblingIndex = -1;
let index = -1;
for (let i = 0; i < this._children.length; i++) {
if (this._children[i] === childReference) {
nextSiblingIndex = i + 1;
index = i;
break;
}
}
// The child reference isn't one of the child patterns.
if (index === -1) {
return [];
}
// The reference pattern is the last child. So ask the parent for the next pattern.
if (nextSiblingIndex === this._children.length && this._parent !== null) {
return this._parent.getPatternsAfter(this);
}
// Send back as many optional patterns as possible.
for (let i = nextSiblingIndex; i < this._children.length; i++) {
const child = this._children[i];
patterns.push(child);
if (child.type !== "optional") {
break;
}
// If we are on the last child and its options then ask for the next pattern from the parent.
if (i === this._children.length - 1 && this._parent !== null) {
patterns.push(...this._parent.getPatternsAfter(this));
}
}
return patterns;
}
getNextPatterns() {
if (this.parent == null) {
return [];
}
return this.parent.getPatternsAfter(this);
}
find(predicate) {
return findPattern(this, predicate);
}
clone(name = this._name) {
const clone = new Sequence(name, this._children);
clone._id = this._id;
return clone;
}
isEqual(pattern) {
return pattern.type === this.type && this.children.every((c, index) => c.isEqual(pattern.children[index]));
}
}
var Association;
(function (Association) {
Association[Association["left"] = 0] = "left";
Association[Association["right"] = 1] = "right";
})(Association || (Association = {}));
class PrecedenceTree {
constructor(precedenceMap = {}, associationMap = {}) {
this._prefixPlaceholder = Node.createNode("placeholder", "prefix-placeholder");
this._prefixNode = null;
this._postfixPlaceholder = Node.createNode("placeholder", "postfix-placeholder");
this._postfixNode = null;
this._binaryPlaceholder = Node.createNode("placeholder", "binary-placeholder");
this._atomNode = null;
this._binaryNode = null;
this._precedenceMap = precedenceMap;
this._associationMap = associationMap;
this._revertBinary = () => { };
}
addPrefix(name, ...prefix) {
const lastPrefixNode = this._prefixNode;
if (lastPrefixNode == null) {
const node = Node.createNode("expression", name, [...prefix]);
this._prefixNode = node;
this._prefixNode.append(this._prefixPlaceholder);
return;
}
const node = Node.createNode("expression", name, [...prefix]);
this._prefixPlaceholder.replaceWith(node);
node.append(this._prefixPlaceholder);
this._prefixNode = node;
}
addPostfix(name, ...postfix) {
const lastPostfixNode = this._postfixNode;
if (lastPostfixNode == null) {
const node = Node.createNode("expression", name, [this._postfixPlaceholder, ...postfix]);
this._postfixNode = node;
return;
}
const node = Node.createNode("expression", name, [lastPostfixNode, ...postfix]);
this._postfixNode = node;
}
addBinary(name, ...delimiterNode) {
const lastBinaryNode = this._binaryNode;
const lastPrecendece = this._getPrecedenceFromNode(this._binaryNode);
const precedence = this._getPrecedence(name);
const association = this._associationMap[name];
const lastAtomNode = this._compileAtomNode();
if (lastAtomNode == null) {
throw new Error("Cannot add a binary without an atom node.");
}
this._binaryPlaceholder.remove();
if (lastBinaryNode == null) {
const node = Node.createNode("expression", name, [lastAtomNode, ...delimiterNode, this._binaryPlaceholder]);
this._binaryNode = node;
this._revertBinary = () => {
lastAtomNode.remove();
this._binaryNode = lastAtomNode;
};
return;
}
if (precedence === lastPrecendece && association === Association.right) {
const node = Node.createNode("expression", name, [lastAtomNode, ...delimiterNode, this._binaryPlaceholder]);
lastBinaryNode.appendChild(node);
this._revertBinary = () => {
node.replaceWith(lastAtomNode);
this._binaryNode = lastBinaryNode;
};
this._binaryNode = node;
}
else if (precedence === lastPrecendece) {
const node = Node.createNode("expression", name, []);
lastBinaryNode.replaceWith(node);
lastBinaryNode.appendChild(lastAtomNode);
node.append(lastBinaryNode, ...delimiterNode, this._binaryPlaceholder);
this._revertBinary = () => {
lastBinaryNode.remove();
node.replaceWith(lastBinaryNode);
this._binaryNode = lastBinaryNode;
};
this._binaryNode = node;
}
else if (precedence > lastPrecendece) {
let ancestor = lastBinaryNode.parent;
let root = lastBinaryNode;
while (ancestor != null) {
const nodePrecedence = this._precedenceMap[ancestor.name];
if (nodePrecedence > precedence) {
break;
}
// A right-associated ancestor of EQUAL precedence must keep the
// incoming operator nested to its right, so stop climbing here
// (do NOT absorb it as a left operand). Without this, chained
// right-associated operators with a tighter operator between
// them (e.g. `a as b + c as d`, `add` tighter than a
// right-associated `as`) would wrongly left-nest into
// `(a as (b+c)) as d` instead of `a as ((b+c) as d)`.
if (nodePrecedence === precedence &&
this._associationMap[ancestor.name] === Association.right) {
break;
}
root = ancestor;
ancestor = ancestor.parent;
}
lastBinaryNode.appendChild(lastAtomNode);
const node = Node.createNode("expression", name, []);
root.replaceWith(node);
node.append(root, ...delimiterNode, this._binaryPlaceholder);
this._revertBinary = () => {
root.remove();
node.replaceWith(root);
this._binaryNode = root;
};
this._binaryNode = node;
}
else {
const node = Node.createNode("expression", name, [lastAtomNode, ...delimiterNode, this._binaryPlaceholder]);
lastBinaryNode.appendChild(node);
this._revertBinary = () => {
lastAtomNode.remove();
node.replaceWith(lastAtomNode);
this._binaryNode = lastBinaryNode;
};
this._binaryNode = node;
}
}
_getPrecedenceFromNode(node) {
if (node == null) {
return 0;
}
return this._getPrecedence(node.name);
}
_getPrecedence(name) {
if (this._precedenceMap[name] != null) {
return this._precedenceMap[name];
}
return 0;
}
_compileAtomNode() {
let node = this._atomNode;
if (this._prefixNode != null && this._postfixNode != null && this._atomNode != null) {
let firstNode = this._prefixNode;
let secondNode = this._postfixNode;
let firstPlaceholder = this._prefixPlaceholder;
let secondPlaceholder = this._postfixPlaceholder;
const prefixName = this._prefixNode.name;
const postfixName = this._postfixNode.name;
const prefixPrecedence = this._getPrecedence(prefixName);
const postfixPrecedence = this._getPrecedence(postfixName);
// The Precedence is the index of the patterns, so its lower not higher :\
if (prefixPrecedence > postfixPrecedence) {
firstNode = this._postfixNode;
secondNode = this._prefixNode;
firstPlaceholder = this._postfixPlaceholder;
secondPlaceholder = this._prefixPlaceholder;
}
node = firstNode.findRoot();
firstPlaceholder.replaceWith(this._atomNode);
secondPlaceholder.replaceWith(node);
node = secondNode;
}
else {
if (this._prefixNode != null && this._atomNode != null) {
node = this._prefixNode.findRoot();
this._prefixPlaceholder.replaceWith(this._atomNode);
}
if (this._postfixNode != null && node != null) {
this._postfixPlaceholder.replaceWith(node);
node = this._postfixNode;
}
}
this._prefixNode = null;
this._atomNode = null;
this._postfixNode = null;
if (node == null) {
return null;
}
return node.findRoot();
}
addAtom(node) {
this._atomNode = node;
}
hasAtom() {
return this._atomNode != null;
}
commit() {
if (this._binaryNode == null) {
return this._compileAtomNode();
}
const atomNode = this._compileAtomNode();
if (atomNode == null) {
this._revertBinary();
let root = this._binaryNode.findRoot();
this.reset();
return root;
}
else {
this._binaryPlaceholder.replaceWith(atomNode);
const root = this._binaryNode.findRoot();
this.reset();
return root;
}
}
reset() {
this._prefixNode = null;
this._atomNode = null;
this._postfixNode = null;
this._binaryNode = null;
}
}
let indexId$1 = 0;
class Expression {
get id() {
return this._id;
}
get type() {
return this._type;
}
get name() {
return this._name;
}
get parent() {
return this._parent;
}
set parent(pattern) {
this._parent = pattern;
}
get children() {
return this._patterns;
}
get prefixPatterns() {
return this._prefixPatterns;
}
get atomPatterns() {
return this._atomPatterns;
}
get postfixPatterns() {
return this._postfixPatterns;
}
get infixPatterns() {
return this._infixPatterns;
}
// @deprecated use infixPatterns instead
get binaryPatterns() {
return this._infixPatterns;
}
get originalPatterns() {
return this._originalPatterns;
}
get startedOnIndex() {
return this._firstIndex;
}
constructor(name, patterns) {
if (patterns.length === 0) {
throw new Error("Need at least one pattern with an 'expression' pattern.");
}
this._id = `expression-${indexId$1++}`;
this._type = "expression";
this._name = name;
this._originalName = name;
this._parent = null;
this._cachedParent = null;
this._firstIndex = 0;
this._atomPatterns = [];
this._prefixPatterns = [];
this._prefixNames = [];
this._postfixPatterns = [];
this._postfixNames = [];
this._infixPatterns = [];
this._infixNames = [];
this._associationMap = {};
this._precedenceMap = {};
this._originalPatterns = patterns;
this._shouldStopParsing = false;
this._hasOrganized = false;
this._patterns = [];
this._precedenceTree = new PrecedenceTree({}, {});
this._atomsIdToAncestorsMap = {};
}
_organizePatterns(patterns) {
const finalPatterns = [];
patterns.forEach((pattern, index) => {
if (this._isAtom(pattern)) {
const atom = pattern.clone();
atom.parent = this;
this._atomPatterns.push(atom);
finalPatterns.push(atom);
}
else if (this._isPrefix(pattern)) {
const name = this._extractName(pattern);
const prefix = this._extractPrefix(pattern);
prefix.parent = this;
this._precedenceMap[name] = index;
this._prefixPatterns.push(prefix);
this._prefixNames.push(name);
finalPatterns.push(prefix);
}
else if (this._isPostfix(pattern)) {
const name = this._extractName(pattern);
const postfix = this._extractPostfix(pattern);
postfix.parent = this;
this._precedenceMap[name] = index;
this._postfixPatterns.push(postfix);
this._postfixNames.push(name);
finalPatterns.push(postfix);
}
else if (this._isBinary(pattern)) {
const name = this._extractName(pattern);
const infix = this._extractInfix(pattern);
infix.parent = this;
this._precedenceMap[name] = index;
this._infixPatterns.push(infix);
this._infixNames.push(name);
if (pattern.type === "right-associated") {
this._associationMap[name] = Association.right;
}
else {
this._associationMap[name] = Association.left;
}
finalPatterns.push(infix);
}
});
this._patterns = finalPatterns;
this._precedenceTree = new PrecedenceTree(this._precedenceMap, this._associationMap);
return finalPatterns;
}
_cacheAncestors() {
for (let atom of this._atomPatterns) {
const id = atom.id;
const ancestors = this._atomsIdToAncestorsMap[id] = [];
let pattern = this.parent;
while (pattern != null) {
if (pattern.id === id) {
ancestors.push(pattern);
}
pattern = pattern.parent;
}
}
}
_extractName(pattern) {
if (pattern.type === "right-associated") {
return pattern.children[0].name;
}
return pattern.name;
}
_isPrefix(pattern) {
pattern = this._unwrapAssociationIfNecessary(pattern);
const lastChild = pattern.children[pattern.children.length - 1];
const referenceCount = this._referenceCount(pattern);
const lastChildIsReference = this._isRecursiveReference(lastChild);
return lastChildIsReference &&
referenceCount === 1;
}
_extractPrefix(pattern) {
pattern = this._unwrapAssociationIfNecessary(pattern);
return new Sequence(`${pattern.name}-prefix`, pattern.children.slice(0, -1));
}
_isAtom(pattern) {
pattern = this._unwrapAssociationIfNecessary(pattern);
const firstChild = pattern.children[0];
const lastChild = pattern.children[pattern.children.length - 1];
const firstChildIsReference = this._isRecursiveReference(firstChild);
const lastChildIsReference = this._isRecursiveReference(lastChild);
return !firstChildIsReference && !lastChildIsReference;
}
_isPostfix(pattern) {
pattern = this._unwrapAssociationIfNecessary(pattern);
const firstChild = pattern.children[0];
const referenceCount = this._referenceCount(pattern);
const firstChildIsReference = this._isRecursiveReference(firstChild);
return firstChildIsReference &&
referenceCount === 1;
}
_extractPostfix(pattern) {
pattern = this._unwrapAssociationIfNecessary(pattern);
return new Sequence(`${pattern.name}-postfix`, pattern.children.slice(1));
}
_isBinary(pattern) {
pattern = this._unwrapAssociationIfNecessary(pattern);
const firstChild = pattern.children[0];
const lastChild = pattern.children[pattern.children.length - 1];
const firstChildIsReference = this._isRecursiveReference(firstChild);
const lastChildIsReference = this._isRecursiveReference(lastChild);
return firstChildIsReference && lastChildIsReference && pattern.children.length > 2;
}
_extractInfix(pattern) {
pattern = this._unwrapAssociationIfNecessary(pattern);
const children = pattern.children.slice(1, -1);
const infixSequence = new Sequence(`${pattern.name}-delimiter`, children);
return infixSequence;
}
_unwrapAssociationIfNecessary(pattern) {
if (pattern.type === "right-associated") {
pattern = pattern.children[0];
}
if (pattern.type === "reference") {
pattern.parent = this;
pattern = pattern.getReferencePatternSafely();
pattern.parent = null;
}
return pattern;
}
_referenceCount(pattern) {
return pattern.children.filter(p => this._isRecursiveReference(p)).length;
}
_isRecursiveReference(pattern) {
if (pattern == null) {
return false;
}
return pattern.name === this._originalName;
}
build() {
if (!this._hasOrganized || this._cachedParent !== this.parent) {
this._cachedParent = this.parent;
this._hasOrganized = true;
this._organizePatterns(this._originalPatterns);
this._cacheAncestors();
}
}
parse(cursor) {
this._firstIndex = cursor.index;
this.build();
// If there are not any atom nodes then nothing can be found.
if (this._atomPatterns.length < 1) {
cursor.moveTo(this._firstIndex);
cursor.recordErrorAt(this._firstIndex, this._firstIndex, this);
return null;
}
const node = this._tryToParse(cursor);
if (node != null) {
node.normalize(this._firstIndex);
cursor.moveTo(node.lastIndex);
cursor.resolveError();
return node;
}
cursor.moveTo(this._firstIndex);
cursor.recordErrorAt(this._firstIndex, this._firstIndex, this);
return null;
}
_tryToParse(cursor) {
this._shouldStopParsing = false;
while (true) {
cursor.resolveError();
this._tryToMatchPrefix(cursor);
if (this._shouldStopParsing) {
break;
}
this._tryToMatchAtom(cursor);
if (this._shouldStopParsing) {