diff --git a/runtime/JavaScript/spec/BitSetSpec.js b/runtime/JavaScript/spec/BitSetSpec.js new file mode 100644 index 0000000000..e77e8d21dd --- /dev/null +++ b/runtime/JavaScript/spec/BitSetSpec.js @@ -0,0 +1,110 @@ +import BitSet from "../src/antlr4/misc/BitSet.js"; + +describe('test BitSet', () => { + + it("is empty", () => { + const bs = new BitSet(); + expect(bs.length).toEqual(0); + }) + + it("sets 1 value", () => { + const bs = new BitSet(); + bs.set(67); + expect(bs.length).toEqual(1); + expect(bs.get(67)).toBeTrue(); + }) + + it("clears 1 value", () => { + const bs = new BitSet(); + bs.set(67); + bs.clear(67) + expect(bs.length).toEqual(0); + expect(bs.get(67)).toBeFalse(); + }) + + it("sets 2 consecutive values", () => { + const bs = new BitSet(); + bs.set(67); + bs.set(68); + expect(bs.length).toEqual(2); + expect(bs.get(67)).toBeTrue(); + expect(bs.get(68)).toBeTrue(); + }) + + it("sets 2 close values", () => { + const bs = new BitSet(); + bs.set(67); + bs.set(70); + expect(bs.length).toEqual(2); + expect(bs.get(67)).toBeTrue(); + expect(bs.get(70)).toBeTrue(); + }) + + it("sets 2 distant values", () => { + const bs = new BitSet(); + bs.set(67); + bs.set(241); + expect(bs.length).toEqual(2); + expect(bs.get(67)).toBeTrue(); + expect(bs.get(241)).toBeTrue(); + }) + + it("combines 2 identical sets", () => { + const bs1 = new BitSet(); + bs1.set(67); + const bs2 = new BitSet(); + bs2.set(67); + bs1.or(bs2); + expect(bs1.length).toEqual(1); + expect(bs1.get(67)).toBeTrue(); + }) + + it("combines 2 distinct sets", () => { + const bs1 = new BitSet(); + bs1.set(67); + const bs2 = new BitSet(); + bs2.set(69); + bs1.or(bs2); + expect(bs1.length).toEqual(2); + expect(bs1.get(67)).toBeTrue(); + expect(bs1.get(69)).toBeTrue(); + }) + + it("combines 2 overlapping sets", () => { + const bs1 = new BitSet(); + bs1.set(67); + bs1.set(69); + const bs2 = new BitSet(); + bs2.set(69); + bs2.set(71); + bs1.or(bs2); + expect(bs1.length).toEqual(3); + expect(bs1.get(67)).toBeTrue(); + expect(bs1.get(69)).toBeTrue(); + expect(bs1.get(71)).toBeTrue(); + }) + + it("returns values", () => { + const bs = new BitSet(); + bs.set(67); + bs.set(69); + const values = bs.values(); + expect(values).toEqual([67, 69]); + }) + + it("counts bits", () => { + for(let i= 0; i <= 0xFF; i++) { + // count bits the slow but easy to understand way (Kernighan method) + let count1 = 0; + let value = i; + while(value) { + if(value & 1) + count1++; + value >>= 1; + } + // count bits the fast way + const count2 = BitSet._bitCount(i); + expect(count2).toEqual(count1); + } + }) +}) diff --git a/runtime/JavaScript/src/antlr4/atn/LL1Analyzer.js b/runtime/JavaScript/src/antlr4/atn/LL1Analyzer.js index 7e6d9ed76c..7668d1ad1c 100644 --- a/runtime/JavaScript/src/antlr4/atn/LL1Analyzer.js +++ b/runtime/JavaScript/src/antlr4/atn/LL1Analyzer.js @@ -134,9 +134,9 @@ export default class LL1Analyzer { return; } if (ctx !== PredictionContext.EMPTY) { - const removed = calledRuleStack.has(s.ruleIndex); + const removed = calledRuleStack.get(s.ruleIndex); try { - calledRuleStack.remove(s.ruleIndex); + calledRuleStack.clear(s.ruleIndex); // run thru all possible stack tops in ctx for (let i = 0; i < ctx.length; i++) { const returnState = this.atn.states[ctx.getReturnState(i)]; @@ -144,7 +144,7 @@ export default class LL1Analyzer { } }finally { if (removed) { - calledRuleStack.add(s.ruleIndex); + calledRuleStack.set(s.ruleIndex); } } return; @@ -153,15 +153,15 @@ export default class LL1Analyzer { for(let j=0; j>> 5] |= 1 << index % 32; } - or(set) { - Object.keys(set.data).map(alt => this.add(alt), this); + get(index) { + BitSet._checkIndex(index) + const slot = index >>> 5; + if (slot >= this.data.length) { + return false; + } + return (this.data[slot] & 1 << index % 32) !== 0; } - remove(value) { - delete this.data[value]; + clear(index) { + BitSet._checkIndex(index) + const slot = index >>> 5; + if (slot < this.data.length) { + this.data[slot] &= ~(1 << index); + } } - has(value) { - return this.data[value] === true; + or(set) { + const minCount = Math.min(this.data.length, set.data.length); + for (let k = 0; k < minCount; ++k) { + this.data[k] |= set.data[k]; + } + if (this.data.length < set.data.length) { + this._resize((set.data.length << 5) - 1); + const c = set.data.length; + for (let k = minCount; k < c; ++k) { + this.data[k] = set.data[k]; + } + } } values() { - return Object.keys(this.data); + const result = new Array(this.length); + let pos = 0; + const length = this.data.length; + for (let k = 0; k < length; ++k) { + let l = this.data[k]; + while (l !== 0) { + const t = l & -l; + result[pos++] = (k << 5) + BitSet._bitCount(t - 1); + l ^= t; + } + } + return result; } minValue() { - return Math.min.apply(null, this.values()); + for (let k = 0; k < this.data.length; ++k) { + let l = this.data[k]; + if (l !== 0) { + let result = 0; + while ((l & 1) === 0) { + result++; + l >>= 1; + } + return result + (32 * k); + } + } + return 0; } hashCode() { @@ -47,7 +90,34 @@ export default class BitSet { return "{" + this.values().join(", ") + "}"; } - get length(){ - return this.values().length; + get length() { + return this.data.map(l => BitSet._bitCount(l)).reduce((s, v) => s + v, 0); + } + + _resize(index) { + const count = index + 32 >>> 5; + if (count <= this.data.length) { + return; + } + const data = new Uint32Array(count); + data.set(this.data); + data.fill(0, this.data.length); + this.data = data; + } + + static _checkIndex(index) { + if (index < 0) + throw new RangeError("index cannot be negative"); + } + + static _bitCount(l) { + // see https://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetParallel + let count = 0; + l = l - ((l >> 1) & 0x55555555); + l = (l & 0x33333333) + ((l >> 2) & 0x33333333); + l = (l + (l >> 4)) & 0x0f0f0f0f; + l = l + (l >> 8); + l = l + (l >> 16); + return count + l & 0x3f; } }