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
- node --version
- npm --version
- npm install -g yarn@v1.22.10
build_script:
- cd runtime\JavaScript\
- npm install
@ -88,6 +89,9 @@ for:
- cd ..\..
- mvn -q -DskipTests install --batch-mode
test_script:
- cd runtime\JavaScript\
- yarn test
- cd ..\..
- 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"

View File

@ -7,7 +7,11 @@ echo "installing nodejs..."
curl -sL https://deb.nodesource.com/setup_14.x | sudo -E bash -
sudo apt-get install -y nodejs
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..."
pushd runtime/JavaScript

View File

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

View File

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

View File

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

View File

@ -1,7 +1,14 @@
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();
s1.addOne(10);
expect(s1.toString()).toEqual("10");
@ -27,3 +34,25 @@ it("merges interval sets properly", () => {
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}");
});