Skip to content

Commit

Permalink
Use Set to improve performance of union (#298)
Browse files Browse the repository at this point in the history
  • Loading branch information
matthemsteger committed Jul 9, 2020
1 parent 3e141b8 commit 9561a44
Show file tree
Hide file tree
Showing 19 changed files with 613 additions and 539 deletions.
21 changes: 21 additions & 0 deletions src/parsimmon.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,15 @@ function bufferExists() {
return typeof Buffer !== "undefined";
}

function setExists() {
if (Parsimmon._supportsSet !== undefined) {
return Parsimmon._supportsSet;
}
var exists = typeof Set !== "undefined";
Parsimmon._supportsSet = exists;
return exists;
}

function ensureBuffer() {
if (!bufferExists()) {
throw new Error(
Expand Down Expand Up @@ -388,6 +397,18 @@ function makeLineColumnIndex(input, i) {

// Returns the sorted set union of two arrays of strings
function union(xs, ys) {
// for newer browsers/node we can improve performance by using
// modern JS
if (setExists() && Array.from) {
// eslint-disable-next-line no-undef
var set = new Set(xs);
for (var y = 0; y < ys.length; y++) {
set.add(ys[y]);
}
var arr = Array.from(set);
arr.sort();
return arr;
}
var obj = {};
for (var i = 0; i < xs.length; i++) {
obj[xs[i]] = true;
Expand Down
3 changes: 2 additions & 1 deletion test/.eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
},
"globals":{
"Parsimmon": true,
"assert": true
"assert": true,
"testSetScenario": true
}
}
54 changes: 28 additions & 26 deletions test/core/alt.test.js
Original file line number Diff line number Diff line change
@@ -1,37 +1,39 @@
"use strict";

it("Parsimmon.alt", function() {
var toNode = function(nodeType) {
return function(value) {
return { type: nodeType, value: value };
testSetScenario(function() {
it("Parsimmon.alt", function() {
var toNode = function(nodeType) {
return function(value) {
return { type: nodeType, value: value };
};
};
};

var stringParser = Parsimmon.seq(
Parsimmon.string('"'),
Parsimmon.regexp(/[^"]*/),
Parsimmon.string('"')
).map(toNode("string"));
var stringParser = Parsimmon.seq(
Parsimmon.string('"'),
Parsimmon.regexp(/[^"]*/),
Parsimmon.string('"')
).map(toNode("string"));

var identifierParser = Parsimmon.regexp(/[a-zA-Z]*/).map(
toNode("identifier")
);
var identifierParser = Parsimmon.regexp(/[a-zA-Z]*/).map(
toNode("identifier")
);

var parser = Parsimmon.alt(stringParser, identifierParser);
var parser = Parsimmon.alt(stringParser, identifierParser);

assert.deepEqual(parser.parse('"a string, to be sure"').value, {
type: "string",
value: ['"', "a string, to be sure", '"']
});
assert.deepEqual(parser.parse('"a string, to be sure"').value, {
type: "string",
value: ['"', "a string, to be sure", '"']
});

assert.deepEqual(parser.parse("anIdentifier").value, {
type: "identifier",
value: "anIdentifier"
});
assert.deepEqual(parser.parse("anIdentifier").value, {
type: "identifier",
value: "anIdentifier"
});

assert.throws(function() {
Parsimmon.alt("not a parser");
});
assert.throws(function() {
Parsimmon.alt("not a parser");
});

assert.strictEqual(Parsimmon.alt().parse("").status, false);
assert.strictEqual(Parsimmon.alt().parse("").status, false);
});
});
42 changes: 22 additions & 20 deletions test/core/chain.test.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,30 @@
"use strict";

describe("chain", function() {
it("asserts that a parser is returned", function() {
var parser1 = Parsimmon.letter.chain(function() {
return "not a parser";
});
assert.throws(function() {
parser1.parse("x");
});
testSetScenario(function() {
describe("chain", function() {
it("asserts that a parser is returned", function() {
var parser1 = Parsimmon.letter.chain(function() {
return "not a parser";
});
assert.throws(function() {
parser1.parse("x");
});

assert.throws(function() {
Parsimmon.letter.then("x");
assert.throws(function() {
Parsimmon.letter.then("x");
});
});
});

it("with a function that returns a parser, continues with that parser", function() {
var piped;
var parser = Parsimmon.string("x").chain(function(x) {
piped = x;
return Parsimmon.string("y");
});
it("with a function that returns a parser, continues with that parser", function() {
var piped;
var parser = Parsimmon.string("x").chain(function(x) {
piped = x;
return Parsimmon.string("y");
});

assert.deepEqual(parser.parse("xy"), { status: true, value: "y" });
assert.equal(piped, "x");
assert.ok(!parser.parse("x").status);
assert.deepEqual(parser.parse("xy"), { status: true, value: "y" });
assert.equal(piped, "x");
assert.ok(!parser.parse("x").status);
});
});
});
118 changes: 60 additions & 58 deletions test/core/createLanguage.test.js
Original file line number Diff line number Diff line change
@@ -1,71 +1,73 @@
"use strict";

describe("Parsimmon.createLanguage", function() {
before(function() {
Object.prototype.NASTY = "dont extend Object.prototype please";
});
testSetScenario(function() {
describe("Parsimmon.createLanguage", function() {
before(function() {
Object.prototype.NASTY = "dont extend Object.prototype please";
});

after(function() {
delete Object.prototype.NASTY;
});
after(function() {
delete Object.prototype.NASTY;
});

it("should return an object of parsers", function() {
var lang = Parsimmon.createLanguage({
a: function() {
return Parsimmon.string("a");
},
b: function() {
return Parsimmon.string("b");
}
it("should return an object of parsers", function() {
var lang = Parsimmon.createLanguage({
a: function() {
return Parsimmon.string("a");
},
b: function() {
return Parsimmon.string("b");
}
});
assert.ok(Parsimmon.isParser(lang.a));
assert.ok(Parsimmon.isParser(lang.b));
});
assert.ok(Parsimmon.isParser(lang.a));
assert.ok(Parsimmon.isParser(lang.b));
});

it("should allow direct recursion in parsers", function() {
var lang = Parsimmon.createLanguage({
Parentheses: function(r) {
return Parsimmon.alt(
Parsimmon.string("()"),
Parsimmon.string("(")
.then(r.Parentheses)
.skip(Parsimmon.string(")"))
);
}
it("should allow direct recursion in parsers", function() {
var lang = Parsimmon.createLanguage({
Parentheses: function(r) {
return Parsimmon.alt(
Parsimmon.string("()"),
Parsimmon.string("(")
.then(r.Parentheses)
.skip(Parsimmon.string(")"))
);
}
});
lang.Parentheses.tryParse("(((())))");
});
lang.Parentheses.tryParse("(((())))");
});

it("should ignore non-own properties", function() {
var obj = Object.create({
foo: function() {
return Parsimmon.of(1);
}
it("should ignore non-own properties", function() {
var obj = Object.create({
foo: function() {
return Parsimmon.of(1);
}
});
var lang = Parsimmon.createLanguage(obj);
assert.strictEqual(lang.foo, undefined);
});
var lang = Parsimmon.createLanguage(obj);
assert.strictEqual(lang.foo, undefined);
});

it("should allow indirect recursion in parsers", function() {
var lang = Parsimmon.createLanguage({
Value: function(r) {
return Parsimmon.alt(r.Number, r.Symbol, r.List);
},
Number: function() {
return Parsimmon.regexp(/[0-9]+/).map(Number);
},
Symbol: function() {
return Parsimmon.regexp(/[a-z]+/);
},
List: function(r) {
return Parsimmon.string("(")
.then(Parsimmon.sepBy(r.Value, r._))
.skip(Parsimmon.string(")"));
},
_: function() {
return Parsimmon.optWhitespace;
}
it("should allow indirect recursion in parsers", function() {
var lang = Parsimmon.createLanguage({
Value: function(r) {
return Parsimmon.alt(r.Number, r.Symbol, r.List);
},
Number: function() {
return Parsimmon.regexp(/[0-9]+/).map(Number);
},
Symbol: function() {
return Parsimmon.regexp(/[a-z]+/);
},
List: function(r) {
return Parsimmon.string("(")
.then(Parsimmon.sepBy(r.Value, r._))
.skip(Parsimmon.string(")"));
},
_: function() {
return Parsimmon.optWhitespace;
}
});
lang.Value.tryParse("(list 1 2 foo (list nice 3 56 989 asdasdas))");
});
lang.Value.tryParse("(list 1 2 foo (list nice 3 56 989 asdasdas))");
});
});
56 changes: 29 additions & 27 deletions test/core/many.test.js
Original file line number Diff line number Diff line change
@@ -1,34 +1,36 @@
"use strict";

describe("many", function() {
it("simple case", function() {
var letters = Parsimmon.letter.many();
assert.deepEqual(letters.parse("x").value, ["x"]);
assert.deepEqual(letters.parse("xyz").value, ["x", "y", "z"]);
assert.deepEqual(letters.parse("").value, []);
assert.ok(!letters.parse("1").status);
assert.ok(!letters.parse("xyz1").status);
});

it("followed by then", function() {
var parser = Parsimmon.string("x")
.many()
.then(Parsimmon.string("y"));
assert.equal(parser.parse("y").value, "y");
assert.equal(parser.parse("xy").value, "y");
assert.equal(parser.parse("xxxxxy").value, "y");
});

it("throws on parsers that accept zero characters", function() {
var parser = Parsimmon.regexp(/a*/).many();
assert.throws(function() {
parser.parse("a");
testSetScenario(function() {
describe("many", function() {
it("simple case", function() {
var letters = Parsimmon.letter.many();
assert.deepEqual(letters.parse("x").value, ["x"]);
assert.deepEqual(letters.parse("xyz").value, ["x", "y", "z"]);
assert.deepEqual(letters.parse("").value, []);
assert.ok(!letters.parse("1").status);
assert.ok(!letters.parse("xyz1").status);
});
assert.throws(function() {
parser.parse("b");

it("followed by then", function() {
var parser = Parsimmon.string("x")
.many()
.then(Parsimmon.string("y"));
assert.equal(parser.parse("y").value, "y");
assert.equal(parser.parse("xy").value, "y");
assert.equal(parser.parse("xxxxxy").value, "y");
});
assert.throws(function() {
parser.parse("");

it("throws on parsers that accept zero characters", function() {
var parser = Parsimmon.regexp(/a*/).many();
assert.throws(function() {
parser.parse("a");
});
assert.throws(function() {
parser.parse("b");
});
assert.throws(function() {
parser.parse("");
});
});
});
});
26 changes: 14 additions & 12 deletions test/core/map.test.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
"use strict";

describe("map", function() {
it("with a function, pipes the value in and uses that return value", function() {
var piped;
var parser = Parsimmon.string("x").map(function(x) {
piped = x;
return "y";
testSetScenario(function() {
describe("map", function() {
it("with a function, pipes the value in and uses that return value", function() {
var piped;
var parser = Parsimmon.string("x").map(function(x) {
piped = x;
return "y";
});
assert.deepEqual(parser.parse("x"), { status: true, value: "y" });
assert.equal(piped, "x");
});
assert.deepEqual(parser.parse("x"), { status: true, value: "y" });
assert.equal(piped, "x");
});

it("asserts that a function was given", function() {
assert.throws(function() {
Parsimmon.string("x").map("not a function");
it("asserts that a function was given", function() {
assert.throws(function() {
Parsimmon.string("x").map("not a function");
});
});
});
});
Loading

0 comments on commit 9561a44

Please sign in to comment.