From 3ad27c1dcf6839634ebe35c66266ec5a6460cc48 Mon Sep 17 00:00:00 2001 From: Eric Vergnaud Date: Fri, 8 Mar 2024 18:15:24 +0100 Subject: [PATCH 1/8] add test spec --- runtime/JavaScript/spec/BitSetSpec.js | 94 ++++++++++++++++++++ runtime/JavaScript/src/antlr4/misc/BitSet.js | 2 +- 2 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 runtime/JavaScript/spec/BitSetSpec.js diff --git a/runtime/JavaScript/spec/BitSetSpec.js b/runtime/JavaScript/spec/BitSetSpec.js new file mode 100644 index 0000000000..1b9350c667 --- /dev/null +++ b/runtime/JavaScript/spec/BitSetSpec.js @@ -0,0 +1,94 @@ +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.add(67); + expect(bs.length).toEqual(1); + expect(bs.has(67)).toBeTrue(); + }) + + it("clears 1 value", () => { + const bs = new BitSet(); + bs.add(67); + bs.remove(67) + expect(bs.length).toEqual(0); + expect(bs.has(67)).toBeFalse(); + }) + + it("sets 2 consecutive values", () => { + const bs = new BitSet(); + bs.add(67); + bs.add(68); + expect(bs.length).toEqual(2); + expect(bs.has(67)).toBeTrue(); + expect(bs.has(68)).toBeTrue(); + }) + + it("sets 2 close values", () => { + const bs = new BitSet(); + bs.add(67); + bs.add(70); + expect(bs.length).toEqual(2); + expect(bs.has(67)).toBeTrue(); + expect(bs.has(70)).toBeTrue(); + }) + + it("sets 2 distant values", () => { + const bs = new BitSet(); + bs.add(67); + bs.add(241); + expect(bs.length).toEqual(2); + expect(bs.has(67)).toBeTrue(); + expect(bs.has(241)).toBeTrue(); + }) + + it("combines 2 identical sets", () => { + const bs1 = new BitSet(); + bs1.add(67); + const bs2 = new BitSet(); + bs2.add(67); + bs1.or(bs2); + expect(bs1.length).toEqual(1); + expect(bs1.has(67)).toBeTrue(); + }) + + it("combines 2 distinct sets", () => { + const bs1 = new BitSet(); + bs1.add(67); + const bs2 = new BitSet(); + bs2.add(69); + bs1.or(bs2); + expect(bs1.length).toEqual(2); + expect(bs1.has(67)).toBeTrue(); + expect(bs1.has(69)).toBeTrue(); + }) + + it("combines 2 overlapping sets", () => { + const bs1 = new BitSet(); + bs1.add(67); + bs1.add(69); + const bs2 = new BitSet(); + bs2.add(69); + bs2.add(71); + bs1.or(bs2); + expect(bs1.length).toEqual(3); + expect(bs1.has(67)).toBeTrue(); + expect(bs1.has(69)).toBeTrue(); + expect(bs1.has(71)).toBeTrue(); + }) + + it("returns values", () => { + const bs = new BitSet(); + bs.add(67); + bs.add(69); + const values = bs.values(); + expect(values).toEqual(['67', '69']); + }) +}) diff --git a/runtime/JavaScript/src/antlr4/misc/BitSet.js b/runtime/JavaScript/src/antlr4/misc/BitSet.js index 90df4e9c7e..d407e9cb46 100644 --- a/runtime/JavaScript/src/antlr4/misc/BitSet.js +++ b/runtime/JavaScript/src/antlr4/misc/BitSet.js @@ -28,7 +28,7 @@ export default class BitSet { } values() { - return Object.keys(this.data); + return Object.keys(this.data); // .map(s => parseInt(s)) } minValue() { From c169c923dcfd4f17672623effa07fa1fb7ab1141 Mon Sep 17 00:00:00 2001 From: Eric Vergnaud Date: Fri, 8 Mar 2024 18:52:41 +0100 Subject: [PATCH 2/8] use array view --- runtime/JavaScript/spec/BitSetSpec.js | 2 +- runtime/JavaScript/src/antlr4/misc/BitSet.js | 93 +++++++++++++++++--- 2 files changed, 82 insertions(+), 13 deletions(-) diff --git a/runtime/JavaScript/spec/BitSetSpec.js b/runtime/JavaScript/spec/BitSetSpec.js index 1b9350c667..deeb0b9189 100644 --- a/runtime/JavaScript/spec/BitSetSpec.js +++ b/runtime/JavaScript/spec/BitSetSpec.js @@ -89,6 +89,6 @@ describe('test BitSet', () => { bs.add(67); bs.add(69); const values = bs.values(); - expect(values).toEqual(['67', '69']); + expect(values).toEqual([67, 69]); }) }) diff --git a/runtime/JavaScript/src/antlr4/misc/BitSet.js b/runtime/JavaScript/src/antlr4/misc/BitSet.js index d407e9cb46..456c70ba03 100644 --- a/runtime/JavaScript/src/antlr4/misc/BitSet.js +++ b/runtime/JavaScript/src/antlr4/misc/BitSet.js @@ -8,31 +8,74 @@ import equalArrays from "../utils/equalArrays.js"; export default class BitSet { constructor() { - this.data = []; + this.data = new Uint32Array(1); } - add(value) { - this.data[value] = true; + add(index) { + this._checkIndex(index) + this._resize(index); + this.data[index >>> 5] |= 1 << index % 32; } - or(set) { - Object.keys(set.data).map(alt => this.add(alt), this); + has(index) { + this._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]; + remove(index) { + this._checkIndex(index) + const slot = index >>> 5; + if (slot < this.data.length) { + this.data[index >>> 5] &= ~(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); // .map(s => parseInt(s)) + 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) + this._bitCount(t - 1); + l ^= t; + } + } + return result; } minValue() { - return Math.min.apply(null, this.values()); + for (let k = 0; k < 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() { @@ -48,6 +91,32 @@ export default class BitSet { } get length(){ - return this.values().length; + return this.data.map(l => this._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; + } + + _checkIndex(index) { + if(index < 0) + throw new RangeError("index cannot be negative"); + } + + _bitCount(l) { + 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; } } From 3b76fea0f41951dae676b5d4aef6d046b3a9354f Mon Sep 17 00:00:00 2001 From: Eric Vergnaud Date: Fri, 8 Mar 2024 18:59:31 +0100 Subject: [PATCH 3/8] oops --- runtime/JavaScript/src/antlr4/misc/BitSet.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/JavaScript/src/antlr4/misc/BitSet.js b/runtime/JavaScript/src/antlr4/misc/BitSet.js index 456c70ba03..bd9ae68344 100644 --- a/runtime/JavaScript/src/antlr4/misc/BitSet.js +++ b/runtime/JavaScript/src/antlr4/misc/BitSet.js @@ -64,7 +64,7 @@ export default class BitSet { } minValue() { - for (let k = 0; k < length; ++k) { + for (let k = 0; k < this.data.length; ++k) { let l = this.data[k]; if (l !== 0) { let result = 0; From 25c5b9b39b25a744a4f5401c05fbd6ee12d5406b Mon Sep 17 00:00:00 2001 From: Eric Vergnaud Date: Fri, 8 Mar 2024 19:08:29 +0100 Subject: [PATCH 4/8] refactor BitSet API Signed-off-by: Eric Vergnaud --- runtime/JavaScript/spec/BitSetSpec.js | 66 +++++++++---------- .../JavaScript/src/antlr4/atn/LL1Analyzer.js | 12 ++-- .../src/antlr4/atn/ParserATNSimulator.js | 10 +-- .../src/antlr4/atn/PredictionMode.js | 4 +- .../antlr4/error/DiagnosticErrorListener.js | 2 +- runtime/JavaScript/src/antlr4/misc/BitSet.js | 6 +- 6 files changed, 50 insertions(+), 50 deletions(-) diff --git a/runtime/JavaScript/spec/BitSetSpec.js b/runtime/JavaScript/spec/BitSetSpec.js index deeb0b9189..ddbe85514c 100644 --- a/runtime/JavaScript/spec/BitSetSpec.js +++ b/runtime/JavaScript/spec/BitSetSpec.js @@ -9,85 +9,85 @@ describe('test BitSet', () => { it("sets 1 value", () => { const bs = new BitSet(); - bs.add(67); + bs.set(67); expect(bs.length).toEqual(1); - expect(bs.has(67)).toBeTrue(); + expect(bs.get(67)).toBeTrue(); }) it("clears 1 value", () => { const bs = new BitSet(); - bs.add(67); - bs.remove(67) + bs.set(67); + bs.clear(67) expect(bs.length).toEqual(0); - expect(bs.has(67)).toBeFalse(); + expect(bs.get(67)).toBeFalse(); }) it("sets 2 consecutive values", () => { const bs = new BitSet(); - bs.add(67); - bs.add(68); + bs.set(67); + bs.set(68); expect(bs.length).toEqual(2); - expect(bs.has(67)).toBeTrue(); - expect(bs.has(68)).toBeTrue(); + expect(bs.get(67)).toBeTrue(); + expect(bs.get(68)).toBeTrue(); }) it("sets 2 close values", () => { const bs = new BitSet(); - bs.add(67); - bs.add(70); + bs.set(67); + bs.set(70); expect(bs.length).toEqual(2); - expect(bs.has(67)).toBeTrue(); - expect(bs.has(70)).toBeTrue(); + expect(bs.get(67)).toBeTrue(); + expect(bs.get(70)).toBeTrue(); }) it("sets 2 distant values", () => { const bs = new BitSet(); - bs.add(67); - bs.add(241); + bs.set(67); + bs.set(241); expect(bs.length).toEqual(2); - expect(bs.has(67)).toBeTrue(); - expect(bs.has(241)).toBeTrue(); + expect(bs.get(67)).toBeTrue(); + expect(bs.get(241)).toBeTrue(); }) it("combines 2 identical sets", () => { const bs1 = new BitSet(); - bs1.add(67); + bs1.set(67); const bs2 = new BitSet(); - bs2.add(67); + bs2.set(67); bs1.or(bs2); expect(bs1.length).toEqual(1); - expect(bs1.has(67)).toBeTrue(); + expect(bs1.get(67)).toBeTrue(); }) it("combines 2 distinct sets", () => { const bs1 = new BitSet(); - bs1.add(67); + bs1.set(67); const bs2 = new BitSet(); - bs2.add(69); + bs2.set(69); bs1.or(bs2); expect(bs1.length).toEqual(2); - expect(bs1.has(67)).toBeTrue(); - expect(bs1.has(69)).toBeTrue(); + expect(bs1.get(67)).toBeTrue(); + expect(bs1.get(69)).toBeTrue(); }) it("combines 2 overlapping sets", () => { const bs1 = new BitSet(); - bs1.add(67); - bs1.add(69); + bs1.set(67); + bs1.set(69); const bs2 = new BitSet(); - bs2.add(69); - bs2.add(71); + bs2.set(69); + bs2.set(71); bs1.or(bs2); expect(bs1.length).toEqual(3); - expect(bs1.has(67)).toBeTrue(); - expect(bs1.has(69)).toBeTrue(); - expect(bs1.has(71)).toBeTrue(); + expect(bs1.get(67)).toBeTrue(); + expect(bs1.get(69)).toBeTrue(); + expect(bs1.get(71)).toBeTrue(); }) it("returns values", () => { const bs = new BitSet(); - bs.add(67); - bs.add(69); + bs.set(67); + bs.set(69); const values = bs.values(); expect(values).toEqual([67, 69]); }) 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; } - has(index) { + get(index) { this._checkIndex(index) const slot = index >>> 5; if (slot >= this.data.length) { @@ -26,7 +26,7 @@ export default class BitSet { return (this.data[slot] & 1 << index % 32) !== 0; } - remove(index) { + clear(index) { this._checkIndex(index) const slot = index >>> 5; if (slot < this.data.length) { From 3868900341debf0b0de4f8992be8e83b47cda5d0 Mon Sep 17 00:00:00 2001 From: Eric Vergnaud Date: Sat, 9 Mar 2024 12:48:50 +0100 Subject: [PATCH 5/8] document algorithm --- runtime/JavaScript/src/antlr4/misc/BitSet.js | 1 + 1 file changed, 1 insertion(+) diff --git a/runtime/JavaScript/src/antlr4/misc/BitSet.js b/runtime/JavaScript/src/antlr4/misc/BitSet.js index 2ee2e4f054..3fc2e20ebe 100644 --- a/runtime/JavaScript/src/antlr4/misc/BitSet.js +++ b/runtime/JavaScript/src/antlr4/misc/BitSet.js @@ -111,6 +111,7 @@ export default class BitSet { } _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); From d725c6e3e391627d6e675919f158f3ce8b8be869 Mon Sep 17 00:00:00 2001 From: Eric Vergnaud Date: Sat, 9 Mar 2024 12:58:42 +0100 Subject: [PATCH 6/8] more tests --- runtime/JavaScript/spec/BitSetSpec.js | 16 ++++++++++++++++ runtime/JavaScript/src/antlr4/misc/BitSet.js | 14 +++++++------- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/runtime/JavaScript/spec/BitSetSpec.js b/runtime/JavaScript/spec/BitSetSpec.js index ddbe85514c..e77e8d21dd 100644 --- a/runtime/JavaScript/spec/BitSetSpec.js +++ b/runtime/JavaScript/spec/BitSetSpec.js @@ -91,4 +91,20 @@ describe('test BitSet', () => { 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/misc/BitSet.js b/runtime/JavaScript/src/antlr4/misc/BitSet.js index 3fc2e20ebe..b196a94d6a 100644 --- a/runtime/JavaScript/src/antlr4/misc/BitSet.js +++ b/runtime/JavaScript/src/antlr4/misc/BitSet.js @@ -12,13 +12,13 @@ export default class BitSet { } set(index) { - this._checkIndex(index) + BitSet._checkIndex(index) this._resize(index); this.data[index >>> 5] |= 1 << index % 32; } get(index) { - this._checkIndex(index) + BitSet._checkIndex(index) const slot = index >>> 5; if (slot >= this.data.length) { return false; @@ -27,7 +27,7 @@ export default class BitSet { } clear(index) { - this._checkIndex(index) + BitSet._checkIndex(index) const slot = index >>> 5; if (slot < this.data.length) { this.data[index >>> 5] &= ~(1 << index); @@ -56,7 +56,7 @@ export default class BitSet { let l = this.data[k]; while (l !== 0) { const t = l & -l; - result[pos++] = (k << 5) + this._bitCount(t - 1); + result[pos++] = (k << 5) + BitSet._bitCount(t - 1); l ^= t; } } @@ -91,7 +91,7 @@ export default class BitSet { } get length(){ - return this.data.map(l => this._bitCount(l)).reduce((s,v) => s + v, 0); + return this.data.map(l => BitSet._bitCount(l)).reduce((s,v) => s + v, 0); } _resize(index) { @@ -105,12 +105,12 @@ export default class BitSet { this.data = data; } - _checkIndex(index) { + static _checkIndex(index) { if(index < 0) throw new RangeError("index cannot be negative"); } - _bitCount(l) { + static _bitCount(l) { // see https://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetParallel let count = 0; l = l - ((l >> 1) & 0x55555555); From e4a77df168ee0c74c0fd760b6410730018dbbe3b Mon Sep 17 00:00:00 2001 From: Eric Vergnaud Date: Sun, 10 Mar 2024 13:09:21 +0100 Subject: [PATCH 7/8] fix formatting --- runtime/JavaScript/src/antlr4/misc/BitSet.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/runtime/JavaScript/src/antlr4/misc/BitSet.js b/runtime/JavaScript/src/antlr4/misc/BitSet.js index b196a94d6a..e3811c202e 100644 --- a/runtime/JavaScript/src/antlr4/misc/BitSet.js +++ b/runtime/JavaScript/src/antlr4/misc/BitSet.js @@ -64,7 +64,7 @@ export default class BitSet { } minValue() { - for (let k = 0; k < this.data.length; ++k) { + for (let k = 0; k < this.data.length; ++k) { let l = this.data[k]; if (l !== 0) { let result = 0; @@ -90,8 +90,8 @@ export default class BitSet { return "{" + this.values().join(", ") + "}"; } - get length(){ - return this.data.map(l => BitSet._bitCount(l)).reduce((s,v) => s + v, 0); + get length() { + return this.data.map(l => BitSet._bitCount(l)).reduce((s, v) => s + v, 0); } _resize(index) { @@ -106,7 +106,7 @@ export default class BitSet { } static _checkIndex(index) { - if(index < 0) + if (index < 0) throw new RangeError("index cannot be negative"); } From 9478da438a657a2208870321d2ab000012c39651 Mon Sep 17 00:00:00 2001 From: Eric Vergnaud Date: Sun, 10 Mar 2024 16:50:32 +0100 Subject: [PATCH 8/8] reuse slot --- runtime/JavaScript/src/antlr4/misc/BitSet.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/JavaScript/src/antlr4/misc/BitSet.js b/runtime/JavaScript/src/antlr4/misc/BitSet.js index e3811c202e..f820fb2b35 100644 --- a/runtime/JavaScript/src/antlr4/misc/BitSet.js +++ b/runtime/JavaScript/src/antlr4/misc/BitSet.js @@ -30,7 +30,7 @@ export default class BitSet { BitSet._checkIndex(index) const slot = index >>> 5; if (slot < this.data.length) { - this.data[index >>> 5] &= ~(1 << index); + this.data[slot] &= ~(1 << index); } }