antlr4-runtime
Version:
JavaScript runtime for ANTLR4
261 lines (242 loc) • 7.04 kB
JavaScript
/* Copyright (c) 2012-2022 The ANTLR Project. All rights reserved.
* Use of this file is governed by the BSD 3-clause license that
* can be found in the LICENSE.txt file in the project root.
*/
import Token from '../Token.js';
import Interval from "./Interval.js";
export default class IntervalSet {
constructor() {
this.intervals = null;
this.readOnly = false;
}
first(v) {
if (this.intervals === null || this.intervals.length===0) {
return Token.INVALID_TYPE;
} else {
return this.intervals[0].start;
}
}
addOne(v) {
this.addInterval(new Interval(v, v + 1));
}
addRange(l, h) {
this.addInterval(new Interval(l, h + 1));
}
addInterval(toAdd) {
if (this.intervals === null) {
this.intervals = [];
this.intervals.push(toAdd.clone());
} else {
// find insert pos
for (let pos = 0; pos < this.intervals.length; pos++) {
const existing = this.intervals[pos];
// distinct range -> insert
if (toAdd.stop < existing.start) {
this.intervals.splice(pos, 0, toAdd);
return;
}
// contiguous range -> adjust
else if (toAdd.stop === existing.start) {
this.intervals[pos] = new Interval(toAdd.start, existing.stop)
return;
}
// overlapping range -> adjust and reduce
else if (toAdd.start <= existing.stop) {
this.intervals[pos] = new Interval(Math.min(existing.start, toAdd.start), Math.max(existing.stop, toAdd.stop));
this.reduce(pos);
return;
}
}
// greater than any existing
this.intervals.push(toAdd.clone());
}
}
addSet(other) {
if (other.intervals !== null) {
other.intervals.forEach( toAdd => this.addInterval(toAdd), this);
}
return this;
}
reduce(pos) {
// only need to reduce if pos is not the last
if (pos < this.intervals.length - 1) {
const current = this.intervals[pos];
const next = this.intervals[pos + 1];
// if next contained in current
if (current.stop >= next.stop) {
this.intervals.splice(pos + 1, 1);
this.reduce(pos);
} else if (current.stop >= next.start) {
this.intervals[pos] = new Interval(current.start, next.stop);
this.intervals.splice(pos + 1, 1);
}
}
}
complement(start, stop) {
const result = new IntervalSet();
result.addInterval(new Interval(start, stop + 1));
if(this.intervals !== null)
this.intervals.forEach(toRemove => result.removeRange(toRemove));
return result;
}
contains(item) {
if (this.intervals === null) {
return false;
} else {
for (let k = 0; k < this.intervals.length; k++) {
if(this.intervals[k].contains(item)) {
return true;
}
}
return false;
}
}
removeRange(toRemove) {
if(toRemove.start===toRemove.stop-1) {
this.removeOne(toRemove.start);
} else if (this.intervals !== null) {
let pos = 0;
for(let n=0; n<this.intervals.length; n++) {
const existing = this.intervals[pos];
// intervals are ordered
if (toRemove.stop<=existing.start) {
return;
}
// check for including range, split it
else if(toRemove.start>existing.start && toRemove.stop<existing.stop) {
this.intervals[pos] = new Interval(existing.start, toRemove.start);
const x = new Interval(toRemove.stop, existing.stop);
this.intervals.splice(pos, 0, x);
return;
}
// check for included range, remove it
else if(toRemove.start<=existing.start && toRemove.stop>=existing.stop) {
this.intervals.splice(pos, 1);
pos = pos - 1; // need another pass
}
// check for lower boundary
else if(toRemove.start<existing.stop) {
this.intervals[pos] = new Interval(existing.start, toRemove.start);
}
// check for upper boundary
else if(toRemove.stop<existing.stop) {
this.intervals[pos] = new Interval(toRemove.stop, existing.stop);
}
pos += 1;
}
}
}
removeOne(value) {
if (this.intervals !== null) {
for (let i = 0; i < this.intervals.length; i++) {
const existing = this.intervals[i];
// intervals are ordered
if (value < existing.start) {
return;
}
// check for single value range
else if (value === existing.start && value === existing.stop - 1) {
this.intervals.splice(i, 1);
return;
}
// check for lower boundary
else if (value === existing.start) {
this.intervals[i] = new Interval(existing.start + 1, existing.stop);
return;
}
// check for upper boundary
else if (value === existing.stop - 1) {
this.intervals[i] = new Interval(existing.start, existing.stop - 1);
return;
}
// split existing range
else if (value < existing.stop - 1) {
const replace = new Interval(existing.start, value);
existing.start = value + 1;
this.intervals.splice(i, 0, replace);
return;
}
}
}
}
toString(literalNames, symbolicNames, elemsAreChar) {
literalNames = literalNames || null;
symbolicNames = symbolicNames || null;
elemsAreChar = elemsAreChar || false;
if (this.intervals === null) {
return "{}";
} else if(literalNames!==null || symbolicNames!==null) {
return this.toTokenString(literalNames, symbolicNames);
} else if(elemsAreChar) {
return this.toCharString();
} else {
return this.toIndexString();
}
}
toCharString() {
const names = [];
for (let i = 0; i < this.intervals.length; i++) {
const existing = this.intervals[i];
if(existing.stop===existing.start+1) {
if ( existing.start===Token.EOF ) {
names.push("<EOF>");
} else {
names.push("'" + String.fromCharCode(existing.start) + "'");
}
} else {
names.push("'" + String.fromCharCode(existing.start) + "'..'" + String.fromCharCode(existing.stop-1) + "'");
}
}
if (names.length > 1) {
return "{" + names.join(", ") + "}";
} else {
return names[0];
}
}
toIndexString() {
const names = [];
for (let i = 0; i < this.intervals.length; i++) {
const existing = this.intervals[i];
if(existing.stop===existing.start+1) {
if ( existing.start===Token.EOF ) {
names.push("<EOF>");
} else {
names.push(existing.start.toString());
}
} else {
names.push(existing.start.toString() + ".." + (existing.stop-1).toString());
}
}
if (names.length > 1) {
return "{" + names.join(", ") + "}";
} else {
return names[0];
}
}
toTokenString(literalNames, symbolicNames) {
const names = [];
for (let i = 0; i < this.intervals.length; i++) {
const existing = this.intervals[i];
for (let j = existing.start; j < existing.stop; j++) {
names.push(this.elementName(literalNames, symbolicNames, j));
}
}
if (names.length > 1) {
return "{" + names.join(", ") + "}";
} else {
return names[0];
}
}
elementName(literalNames, symbolicNames, token) {
if (token === Token.EOF) {
return "<EOF>";
} else if (token === Token.EPSILON) {
return "<EPSILON>";
} else {
return literalNames[token] || symbolicNames[token];
}
}
get length(){
return this.intervals.map( interval => interval.length ).reduce((acc, val) => acc + val);
}
}