tscc-compiler
Version:
An LALR(1) compiler compiler written in Typescript
263 lines (258 loc) • 8.47 kB
text/typescript
// 区间集合
import { DEBUG } from './common';
export var oo = Number.POSITIVE_INFINITY;
export var _oo = Number.NEGATIVE_INFINITY;
class Interval<T>{
public a: number;
public b: number;
public prev: Interval<T>;
public next: Interval<T>;
public parent: IntervalSet<T>;
public data: T = null;
constructor(a: number, b: number){
this.a = a;
this.b = b;
}
public insertBefore(a: number, b: number, data?: T){
if(this.parent.isValid(this) && !this.parent.noMerge && this.a === b + 1){
this.a = a;
return this;
}
else {
var it = this.parent.createInterval(a, b, data);
it.prev = this.prev;
it.next = this;
this.prev.next = it;
this.prev = it;
return it;
}
}
public contains(a: number): boolean{
return this.a <= a && this.b >= a;
}
public overlaps(a: number, b: number): boolean{
// return !(cm(a,this.b) > 0 || cm(b,this.a) < 0);
return !(a > this.b || b < this.a);
}
public insertAfter(a: number, b: number, data?: T): Interval<T>{
if(this.parent.isValid(this) && !this.parent.noMerge && this.b === a - 1){
this.b = b;
return this;
}
else {
var it = this.parent.createInterval(a, b, data);
it.prev = this;
it.next = this.next;
this.next.prev = it;
this.next = it;
return it;
}
}
public splitLeft(a: number): Interval<T>{
//DEBUG && console.assert(this.parent.noMerge);
if(a > this.a){
// don't pass the data, use union instead
var ret = this.insertBefore(this.a, a - 1, this.data);
this.a = a;
return ret;
}
return this;
}
public splitRight(b: number): Interval<T>{
//DEBUG && console.assert(this.parent.noMerge);
if(b < this.b){
var ret = this.insertAfter(b + 1, this.b, this.data);
this.b = b;
return ret;
}
return this;
}
public remove(): Interval<T>{
this.prev.next = this.next;
this.next.prev = this.prev;
return this;
}
public checkMerge(): Interval<T>{
if(this.a !== _oo && this.prev.a !== null && this.a === this.prev.b + 1){
this.a = this.prev.a;
this.prev.remove();
}
if(this.b !== oo && this.next.a !== null && this.b === this.next.a - 1){
this.b = this.next.b;
this.next.remove();
}
return this;
}
public toString(mapper: (a: number) => string): string{
var ret = '';
function dfmapper(c: number): string{
return c === oo ? '+oo' : c === _oo ? '-oo' : c.toString();
}
var a = (mapper || dfmapper)(this.a);
var b = (mapper || dfmapper)(this.b);
if(this.a === this.b){
ret += a;
}
else{
ret += this.a === _oo ? '(' + a : '[' + a;
ret += ',';
ret += this.b === oo ? b + ')' : b + ']';
}
this.data && (ret += this.parent.dataOp.stringify(this.data));
return ret;
}
}
function checkArg(a: number, b: number){
if(a > b){
throw new Error(`illegal argument: "a"(${a}) must be no more than "b"(${b})`);
}
}
export interface DataOperator<T> {
createData(): T;
union(dest: T, src: T): void;
stringify(d: T): string;
}
export class IntervalSet<T>{
public head: Interval<T>;
public tail: Interval<T>;
public noMerge: boolean;
public dataOp: DataOperator<T>;
constructor(dataOp?: DataOperator<T>){
this.head = new Interval(0,0);
this.head.parent = this;
this.tail = new Interval(null,null);
this.tail.parent = this;
this.head.next = this.tail;
this.tail.prev = this.head;
this.noMerge = typeof dataOp !== 'undefined';
this.dataOp = dataOp || null;
}
isValid(it: Interval<T>): boolean{
return it !== this.head && it !== this.tail;
}
createInterval(a: number, b: number, data: T = null): Interval<T>{
var ret = new Interval<T>(a,b);
ret.parent = this;
this.dataOp && (ret.data = this.dataOp.createData(), data !== null && this.dataOp.union(ret.data, data));
return ret;
}
fitPoint(a: number, b: number): Interval<T>{
for(var it = this.head;it !== this.tail;it = it.next){
if((it === this.head || a > it.b) && (it.next === this.tail || b < it.next.a)){
return it;
}
}
return null;
}
overlaped(a: number, b: number): Interval<T>[]{
var start = null,end = null;
var it = this.head.next;
for(;it !== this.tail && !it.overlaps(a,b);it = it.next);
if(it === this.tail){
return null;
}
start = end = it;
for(;it !== this.tail && it.overlaps(a,b);it = it.next){
end = it;
}
return [start,end];
}
/**
* add an interval into the set
* @param data - The extra data associated with the added interval, which is only valid when noMerge = true
*
*/
add(a: number, b: number = a, data?: T){
var noMerge = this.noMerge;
// /b = b || a;
DEBUG && checkArg(a,b);
var overlap = this.overlaped(a,b);
if(overlap === null){
this.fitPoint(a,b).insertAfter(a,b,data);
}
else {
if(!noMerge){
var a1 = a < overlap[0].a ? a : overlap[0].a;
var b1 = b > overlap[1].b ? b : overlap[1].b;
overlap[0].a = a1;
overlap[0].b = b1;
overlap[0].next = overlap[1].next;
overlap[1].next.prev = overlap[0];
overlap[0].checkMerge();
}
else {
if(overlap[0].contains(a)){
overlap[0].splitLeft(a);
}
else {
overlap[0].insertBefore(a, overlap[0].a - 1, data);
}
if(overlap[1].contains(b)) {
overlap[1].splitRight(b);
}
else {
overlap[1].insertAfter(overlap[1].b + 1, b, data);
}
for(var it = overlap[0];it !== overlap[1];it = it.next){
// it.dataSet.add(data);
this.dataOp.union(it.data, data);
if(it.b + 1 < it.next.a){
it.insertAfter(it.b + 1, it.next.a - 1, data);
it = it.next;
}
}
// overlap[1].dataSet.add(data);
this.dataOp.union(overlap[1].data, data);
}
}
return this;
}
remove(a: number, b: number): IntervalSet<T>{
checkArg(a,b);
var overlap = this.overlaped(a,b);
if(overlap !== null){
overlap[0].contains(a) && overlap[0].splitLeft(a);
overlap[1].contains(b) && overlap[1].splitRight(b);
overlap[0].prev.next = overlap[1].next;
overlap[1].next.prev = overlap[0].prev;
}
return this;
}
removeAll(): IntervalSet<T>{
this.head.next = this.tail;
this.tail.prev = this.head;
return this;
}
forEach(cb: (a: number, b: number, it: Interval<T>) => any): IntervalSet<T>{
for(var it = this.head.next;it !== this.tail;it = it.next){
cb(it.a,it.b,it);
}
return this;
}
union(s: IntervalSet<T>): IntervalSet<T>{
for(var it = s.head.next;it !== s.tail;it = it.next){
this.add(it.a,it.b);
}
return this;
}
contains(a: number): boolean{
for(var it = this.head.next;it !== this.tail;it = it.next){
if(it.contains(a)){
return true;
}
}
return false;
}
toString(mapper: (a: number) => string){
var ret = '';
var t = false;
for(var it = this.head.next;it !== this.tail;it = it.next){
if(t){
ret += ',';
}
t = true;
ret += it.toString(mapper);
}
return ret === '' ? 'phi' : ret;
}
}