Fix javascript intervals (#3083)

* run jest tests in CI

* fix issue when merging sets of intervals

* fix CI config

* improve readability

* more tests
This commit is contained in:
ericvergnaud 2021-02-14 16:51:41 +08:00 committed by GitHub
parent 1281fb770e
commit 84722e9fcc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 136 additions and 89 deletions

View File

@ -81,6 +81,7 @@ for:
- cinst nodejs.install - cinst nodejs.install
- node --version - node --version
- npm --version - npm --version
- npm install -g yarn@v1.22.10
build_script: build_script:
- cd runtime\JavaScript\ - cd runtime\JavaScript\
- npm install - npm install
@ -88,6 +89,9 @@ for:
- cd ..\.. - cd ..\..
- mvn -q -DskipTests install --batch-mode - mvn -q -DskipTests install --batch-mode
test_script: test_script:
- cd runtime\JavaScript\
- yarn test
- cd ..\..
- cd runtime-testsuite - cd runtime-testsuite
- mvn -q -Dtest=javascript.* test -Dantlr-javascript-npm="C:\Program Files\nodejs\npm.cmd" -Dantlr-javascript-nodejs="C:\Program Files\nodejs\node.exe" - mvn -q -Dtest=javascript.* test -Dantlr-javascript-npm="C:\Program Files\nodejs\npm.cmd" -Dantlr-javascript-nodejs="C:\Program Files\nodejs\node.exe"

View File

@ -7,7 +7,11 @@ echo "installing nodejs..."
curl -sL https://deb.nodesource.com/setup_14.x | sudo -E bash - curl -sL https://deb.nodesource.com/setup_14.x | sudo -E bash -
sudo apt-get install -y nodejs sudo apt-get install -y nodejs
echo node version: $(node --version) echo node version: $(node --version)
echo "done installing nodejs..." echo "done installing nodejs"
echo "installing yarn..."
sudo npm install -g yarn@v1.22.10
echo "done installing yarn"
echo "packaging javascript runtime..." echo "packaging javascript runtime..."
pushd runtime/JavaScript pushd runtime/JavaScript

View File

@ -2,7 +2,22 @@
set -euo pipefail set -euo pipefail
declare -i RESULT=0
pushd runtime/JavaScript
echo "running jest tests..."
yarn test
RESULT+=$?
popd
pushd runtime-testsuite pushd runtime-testsuite
echo "running maven tests..." echo "running maven tests..."
mvn -q -Dtest=javascript.* test mvn -q -Dtest=javascript.* test
RESULT+=$?
popd popd
exit $RESULT

View File

@ -26,7 +26,8 @@
"webpack-cli": "^3.3.12" "webpack-cli": "^3.3.12"
}, },
"scripts": { "scripts": {
"build": "webpack" "build": "webpack",
"test": "jest"
}, },
"engines": { "engines": {
"node": ">=14" "node": ">=14"

View File

@ -52,58 +52,55 @@ class IntervalSet {
this.addInterval(new Interval(l, h + 1)); this.addInterval(new Interval(l, h + 1));
} }
addInterval(v) { addInterval(toAdd) {
if (this.intervals === null) { if (this.intervals === null) {
this.intervals = []; this.intervals = [];
this.intervals.push(v); this.intervals.push(toAdd);
} else { } else {
// find insert pos // find insert pos
for (let k = 0; k < this.intervals.length; k++) { for (let pos = 0; pos < this.intervals.length; pos++) {
const i = this.intervals[k]; const existing = this.intervals[pos];
// distinct range -> insert // distinct range -> insert
if (v.stop < i.start) { if (toAdd.stop < existing.start) {
this.intervals.splice(k, 0, v); this.intervals.splice(pos, 0, toAdd);
return; return;
} }
// contiguous range -> adjust // contiguous range -> adjust
else if (v.stop === i.start) { else if (toAdd.stop === existing.start) {
this.intervals[k].start = v.start; this.intervals[pos].start = toAdd.start;
return; return;
} }
// overlapping range -> adjust and reduce // overlapping range -> adjust and reduce
else if (v.start <= i.stop) { else if (toAdd.start <= existing.stop) {
this.intervals[k] = new Interval(Math.min(i.start, v.start), Math.max(i.stop, v.stop)); this.intervals[pos] = new Interval(Math.min(existing.start, toAdd.start), Math.max(existing.stop, toAdd.stop));
this.reduce(k); this.reduce(pos);
return; return;
} }
} }
// greater than any existing // greater than any existing
this.intervals.push(v); this.intervals.push(toAdd);
} }
} }
addSet(other) { addSet(other) {
if (other.intervals !== null) { if (other.intervals !== null) {
for (let k = 0; k < other.intervals.length; k++) { other.intervals.forEach( toAdd => this.addInterval(toAdd), this);
const i = other.intervals[k];
this.addInterval(new Interval(i.start, i.stop));
}
} }
return this; return this;
} }
reduce(k) { reduce(pos) {
// only need to reduce if k is not the last // only need to reduce if pos is not the last
if (k < this.intervals.length - 1) { if (pos < this.intervals.length - 1) {
const l = this.intervals[k]; const current = this.intervals[pos];
const r = this.intervals[k + 1]; const next = this.intervals[pos + 1];
// if r contained in l // if next contained in current
if (l.stop >= r.stop) { if (current.stop >= next.stop) {
this.intervals = this.intervals.splice(k + 1, 1); this.intervals.splice(pos + 1, 1);
this.reduce(k); this.reduce(pos);
} else if (l.stop >= r.start) { } else if (current.stop >= next.start) {
this.intervals[k] = new Interval(l.start, r.stop); this.intervals[pos] = new Interval(current.start, next.stop);
this.intervals.splice(k + 1, 1); this.intervals.splice(pos + 1, 1);
} }
} }
} }
@ -111,9 +108,8 @@ class IntervalSet {
complement(start, stop) { complement(start, stop) {
const result = new IntervalSet(); const result = new IntervalSet();
result.addInterval(new Interval(start,stop+1)); result.addInterval(new Interval(start,stop+1));
for(let i=0; i<this.intervals.length; i++) { if(this.intervals !== null)
result.removeRange(this.intervals[i]); this.intervals.forEach(toRemove => result.removeRange(toRemove));
}
return result; return result;
} }
@ -130,70 +126,70 @@ class IntervalSet {
} }
} }
removeRange(v) { removeRange(toRemove) {
if(v.start===v.stop-1) { if(toRemove.start===toRemove.stop-1) {
this.removeOne(v.start); this.removeOne(toRemove.start);
} else if (this.intervals !== null) { } else if (this.intervals !== null) {
let k = 0; let pos = 0;
for(let n=0; n<this.intervals.length; n++) { for(let n=0; n<this.intervals.length; n++) {
const i = this.intervals[k]; const existing = this.intervals[pos];
// intervals are ordered // intervals are ordered
if (v.stop<=i.start) { if (toRemove.stop<=existing.start) {
return; return;
} }
// check for including range, split it // check for including range, split it
else if(v.start>i.start && v.stop<i.stop) { else if(toRemove.start>existing.start && toRemove.stop<existing.stop) {
this.intervals[k] = new Interval(i.start, v.start); this.intervals[pos] = new Interval(existing.start, toRemove.start);
const x = new Interval(v.stop, i.stop); const x = new Interval(toRemove.stop, existing.stop);
this.intervals.splice(k, 0, x); this.intervals.splice(pos, 0, x);
return; return;
} }
// check for included range, remove it // check for included range, remove it
else if(v.start<=i.start && v.stop>=i.stop) { else if(toRemove.start<=existing.start && toRemove.stop>=existing.stop) {
this.intervals.splice(k, 1); this.intervals.splice(pos, 1);
k = k - 1; // need another pass pos = pos - 1; // need another pass
} }
// check for lower boundary // check for lower boundary
else if(v.start<i.stop) { else if(toRemove.start<existing.stop) {
this.intervals[k] = new Interval(i.start, v.start); this.intervals[pos] = new Interval(existing.start, toRemove.start);
} }
// check for upper boundary // check for upper boundary
else if(v.stop<i.stop) { else if(toRemove.stop<existing.stop) {
this.intervals[k] = new Interval(v.stop, i.stop); this.intervals[pos] = new Interval(toRemove.stop, existing.stop);
} }
k += 1; pos += 1;
} }
} }
} }
removeOne(v) { removeOne(value) {
if (this.intervals !== null) { if (this.intervals !== null) {
for (let k = 0; k < this.intervals.length; k++) { for (let i = 0; i < this.intervals.length; i++) {
const i = this.intervals[k]; const existing = this.intervals[i];
// intervals is ordered // intervals are ordered
if (v < i.start) { if (value < existing.start) {
return; return;
} }
// check for single value range // check for single value range
else if (v === i.start && v === i.stop - 1) { else if (value === existing.start && value === existing.stop - 1) {
this.intervals.splice(k, 1); this.intervals.splice(i, 1);
return; return;
} }
// check for lower boundary // check for lower boundary
else if (v === i.start) { else if (value === existing.start) {
this.intervals[k] = new Interval(i.start + 1, i.stop); this.intervals[i] = new Interval(existing.start + 1, existing.stop);
return; return;
} }
// check for upper boundary // check for upper boundary
else if (v === i.stop - 1) { else if (value === existing.stop - 1) {
this.intervals[k] = new Interval(i.start, i.stop - 1); this.intervals[i] = new Interval(existing.start, existing.stop - 1);
return; return;
} }
// split existing range // split existing range
else if (v < i.stop - 1) { else if (value < existing.stop - 1) {
const x = new Interval(i.start, v); const replace = new Interval(existing.start, value);
i.start = v + 1; existing.start = value + 1;
this.intervals.splice(k, 0, x); this.intervals.splice(i, 0, replace);
return; return;
} }
} }
@ -218,15 +214,15 @@ class IntervalSet {
toCharString() { toCharString() {
const names = []; const names = [];
for (let i = 0; i < this.intervals.length; i++) { for (let i = 0; i < this.intervals.length; i++) {
const v = this.intervals[i]; const existing = this.intervals[i];
if(v.stop===v.start+1) { if(existing.stop===existing.start+1) {
if ( v.start===Token.EOF ) { if ( existing.start===Token.EOF ) {
names.push("<EOF>"); names.push("<EOF>");
} else { } else {
names.push("'" + String.fromCharCode(v.start) + "'"); names.push("'" + String.fromCharCode(existing.start) + "'");
} }
} else { } else {
names.push("'" + String.fromCharCode(v.start) + "'..'" + String.fromCharCode(v.stop-1) + "'"); names.push("'" + String.fromCharCode(existing.start) + "'..'" + String.fromCharCode(existing.stop-1) + "'");
} }
} }
if (names.length > 1) { if (names.length > 1) {
@ -239,15 +235,15 @@ class IntervalSet {
toIndexString() { toIndexString() {
const names = []; const names = [];
for (let i = 0; i < this.intervals.length; i++) { for (let i = 0; i < this.intervals.length; i++) {
const v = this.intervals[i]; const existing = this.intervals[i];
if(v.stop===v.start+1) { if(existing.stop===existing.start+1) {
if ( v.start===Token.EOF ) { if ( existing.start===Token.EOF ) {
names.push("<EOF>"); names.push("<EOF>");
} else { } else {
names.push(v.start.toString()); names.push(existing.start.toString());
} }
} else { } else {
names.push(v.start.toString() + ".." + (v.stop-1).toString()); names.push(existing.start.toString() + ".." + (existing.stop-1).toString());
} }
} }
if (names.length > 1) { if (names.length > 1) {
@ -260,8 +256,8 @@ class IntervalSet {
toTokenString(literalNames, symbolicNames) { toTokenString(literalNames, symbolicNames) {
const names = []; const names = [];
for (let i = 0; i < this.intervals.length; i++) { for (let i = 0; i < this.intervals.length; i++) {
const v = this.intervals[i]; const existing = this.intervals[i];
for (let j = v.start; j < v.stop; j++) { for (let j = existing.start; j < existing.stop; j++) {
names.push(this.elementName(literalNames, symbolicNames, j)); names.push(this.elementName(literalNames, symbolicNames, j));
} }
} }
@ -272,20 +268,18 @@ class IntervalSet {
} }
} }
elementName(literalNames, symbolicNames, a) { elementName(literalNames, symbolicNames, token) {
if (a === Token.EOF) { if (token === Token.EOF) {
return "<EOF>"; return "<EOF>";
} else if (a === Token.EPSILON) { } else if (token === Token.EPSILON) {
return "<EPSILON>"; return "<EPSILON>";
} else { } else {
return literalNames[a] || symbolicNames[a]; return literalNames[token] || symbolicNames[token];
} }
} }
get length(){ get length(){
let len = 0; return this.intervals.map( interval => interval.length ).reduce((acc, val) => acc + val);
this.intervals.map(function(i) {len += i.length;});
return len;
} }
} }

View File

@ -1,7 +1,14 @@
import antlr4 from "../antlr4/index.js"; import antlr4 from "../antlr4/index.js";
it("merges interval sets properly", () => { it("computes interval set length", () => {
const s1 = new antlr4.IntervalSet();
s1.addOne(20);
s1.addOne(154);
s1.addRange(169, 171);
expect(s1.length).toEqual(5);
});
it("merges simple interval sets", () => {
const s1 = new antlr4.IntervalSet(); const s1 = new antlr4.IntervalSet();
s1.addOne(10); s1.addOne(10);
expect(s1.toString()).toEqual("10"); expect(s1.toString()).toEqual("10");
@ -27,3 +34,25 @@ it("merges interval sets properly", () => {
expect(merged.toString()).toEqual("10..12"); expect(merged.toString()).toEqual("10..12");
}); });
it("merges complex interval sets", () => {
const s1 = new antlr4.IntervalSet();
s1.addOne(20);
s1.addOne(141);
s1.addOne(144);
s1.addOne(154);
s1.addRange(169, 171);
s1.addOne(173);
expect(s1.toString()).toEqual("{20, 141, 144, 154, 169..171, 173}");
const s2 = new antlr4.IntervalSet();
s2.addRange(9, 14);
s2.addOne(53);
s2.addRange(55, 63);
s2.addRange(65, 72);
s2.addRange(74, 117);
s2.addRange(119, 152);
s2.addRange(154, 164);
expect(s2.toString()).toEqual("{9..14, 53, 55..63, 65..72, 74..117, 119..152, 154..164}");
s1.addSet(s2);
expect(s1.toString()).toEqual("{9..14, 20, 53, 55..63, 65..72, 74..117, 119..152, 154..164, 169..171, 173}");
});