From 853eb22c14dc7f17fb7d773571f046e2cb3433e5 Mon Sep 17 00:00:00 2001 From: Matt Fenwick Date: Thu, 21 Jan 2016 04:08:00 -0600 Subject: [PATCH 1/4] add new combinators and refactor new: - `update` - `updateStateForKey` - `putStateForKey` - `getStateForKey` refactor: - don't make assumptions about what the state is - this requires changes to be made to count and position itemizers, because they had been assuming the state to be something. Now they just assume the state has a specific key with a value of the right type - write `put`, `get` in terms of `update` - presented value of `put` and `update` is now the previous value, instead of null - pull helper functions out into separate module --- lib/combinators.js | 145 ++++++++++++++++++++++++-------------------- lib/funcs.js | 60 ++++++++++++++++++ test/combinators.js | 114 ++++++++++++++++++++++------------ test/cst.js | 23 ++++--- 4 files changed, 228 insertions(+), 114 deletions(-) create mode 100644 lib/funcs.js diff --git a/lib/combinators.js b/lib/combinators.js index 248ee66..2c6df28 100644 --- a/lib/combinators.js +++ b/lib/combinators.js @@ -1,6 +1,7 @@ "use strict"; -var M = require('./maybeerror.js'); +var M = require('./maybeerror.js'), + F = require('./funcs.js'); function Parser(f) { @@ -46,10 +47,6 @@ function good(value, rest, state) { return M.pure(result(value, rest, state)); } -function compose(f, g) { - return function(x) { return f(g(x)); }; -} - function fmap(g, parser) { /* @@ -98,10 +95,7 @@ function error(e) { /* e -> Parser e s (m t) a */ - function f(_xs_, _s_) { - return M.error(e); - } - return new Parser(f); + return new Parser(F.constF(M.error(e))); } function catchError(f, parser) { @@ -126,40 +120,76 @@ function mapError(f, parser) { */ checkFunction('mapError', f); checkParser('mapError', parser); - return catchError(compose(error, f), parser); + return catchError(F.compose(error, f), parser); } -function put(xs) { +function update(g) { /* - m t -> Parser e s (m t) a + (m t -> m t) -> Parser e s (m t) (m t) */ - function f(_xs_, s) { - return good(null, xs, s); + checkFunction('update', g); + function f(xs, s) { + return good(xs, g(xs), s); } return new Parser(f); } -function putState(s) { +function put(xs) { /* - s -> Parser e s (m t) a + m t -> Parser e s (m t) (m t) */ - function f(xs, _s_) { - return good(null, xs, s); - } - return new Parser(f); + return update(F.constF(xs)); } +// Parser e s (m t) (m t) +var get = update(F.id); + function updateState(g) { /* - (s -> s) -> Parser e s (m t) a + (s -> s) -> Parser e s (m t) s */ checkFunction('updateState', g); function f(xs, s) { - return good(null, xs, g(s)); + return good(s, xs, g(s)); + } + return new Parser(f); +} + +function putState(s) { + /* + s -> Parser e s (m t) s + */ + return updateState(F.constF(s)); +} + +// Parser e s (m t) s +var getState = updateState(F.id); + +function updateStateForKey(key, g) { + /* + x -> (y -> z) -> Parser e s (m t) z + */ + checkFunction('updateStateForKey', g); + function f(xs, s) { + return good(s[key], xs, F.mapOverObject(key, g, s)); } return new Parser(f); } +function putStateForKey(key, newValue) { + /* + y -> z -> Parser e s (m t) z + */ + return updateStateForKey(key, F.constF(newValue)); +} + +function getStateForKey(key) { + /* + y -> Parser e s (m t) z + */ + return updateStateForKey(key, F.id); +} + function check(predicate, parser) { /* (a -> Bool) -> Parser e s (m t) a -> Parser e s (m t) a @@ -212,15 +242,11 @@ function many1(parser) { return check(function(x) {return x.length > 0;}, many0(parser)); } -function _get_args(args, ix) { - return Array.prototype.slice.call(args, ix); -} - function seq() { /* [Parser e s (m t) a] -> Parser e s (m t) [a] */ - var parsers = _get_args(arguments, 0); + var parsers = F.getArgs(arguments, 0); parsers.map(checkParser.bind(null, 'seq')); // can I use `forEach` here instead of `map`? function f(xs, s) { var vals = [], @@ -246,7 +272,7 @@ function appP(p) { /* Parser e s (m t) (a -> ... -> z) -> Parser e s (m t) a -> ... -> Parser e s (m t) z */ - var parsers = _get_args(arguments, 1); + var parsers = F.getArgs(arguments, 1); checkParser('appP', p); parsers.map(checkParser.bind(null, 'appP')); return bind(p, function(f) { @@ -261,25 +287,17 @@ function app(f) { /* (a -> ... -> z) -> Parser e s (m t) a -> ... -> Parser e s (m t) z */ - var args = _get_args(arguments, 1); + var args = F.getArgs(arguments, 1); return appP.apply(null, [pure(f)].concat(args)); } -function _first(x, _) { - return x; -} - -function _second(_, y) { - return y; -} - function seq2L(p1, p2) { /* Parser e s (m t) a -> Parser e s (m t) b -> Parser e s (m t) a */ checkParser('seq2L', p1); checkParser('seq2L', p2); - return app(_first, p1, p2); + return app(F.first, p1, p2); } function seq2R(p1, p2) { @@ -288,7 +306,7 @@ function seq2R(p1, p2) { */ checkParser('seq2R', p1); checkParser('seq2R', p2); - return app(_second, p1, p2); + return app(F.second, p1, p2); } function lookahead(parser) { @@ -326,7 +344,7 @@ function alt() { /* [Parser e s (m t) a] -> Parser e s (m t) a */ - var parsers = _get_args(arguments, 0); + var parsers = F.getArgs(arguments, 0); parsers.map(checkParser.bind(null, 'alt')); // use `forEach` here, too? function f(xs, s) { var r = M.zero; @@ -377,17 +395,13 @@ function _buildSepByValue(fst, pairs) { }; } -function _pair(x, y) { - return [x, y]; -} - function sepBy1(parser, separator) { /* Parser e s (m t) a -> Parser e s (m t) b -> Parser e s (m t) {'values': [a], 'separators': [b]} */ return app(_buildSepByValue, parser, - many0(app(_pair, separator, parser))); + many0(app(F.pair, separator, parser))); } function sepBy0(parser, separator) { @@ -398,13 +412,7 @@ function sepBy0(parser, separator) { } // Parser e s (m t) a -var zero = new Parser(function(_xs_, _s_) {return M.zero;}); - -// Parser e s (m t) (m t) -var get = new Parser(function(xs, s) {return good(xs, xs, s);}); - -// Parser e s (m t) s -var getState = new Parser(function(xs, s) {return good(s, xs, s);}); +var zero = new Parser(F.constF(M.zero)); function _build_set(elems) { @@ -484,23 +492,27 @@ function Itemizer(f) { } -function _bump(char, position) { +function _bump(char, s) { /* only treats `\n` as newline */ - var line = position[0], - col = position[1]; - if ( char === '\n' ) { - return [line + 1, 1]; - } - return [line, col + 1]; + return F.mapOverObject('position', function(position) { + var line = position[0], + col = position[1]; + if ( char === '\n' ) { + return [line + 1, 1]; + } + return [line, col + 1]; + }, s); } -var basic = Itemizer(function(first, s) { return s; }), +var basic = Itemizer(F.second), // assumes the state is a 2-tuple of integers (line, column) position = Itemizer(_bump), // assumes that state is an integer -- how many tokens have been consumed - count = Itemizer(function(first, s) { return s + 1; }); + count = Itemizer(function(first, s) { + return F.mapOverObject('count', F.inc, s); + }); function run(parser, input_string, state) { @@ -521,9 +533,15 @@ module.exports = { 'error' : error, 'catchError' : catchError, 'mapError' : mapError, + 'update' : update, 'put' : put, - 'putState' : putState, + 'get' : get, 'updateState': updateState, + 'putState' : putState, + 'getState' : getState, + 'updateStateForKey' : updateStateForKey, + 'putStateForKey' : putStateForKey, + 'getStateForKey' : getStateForKey, 'check' : check, 'many0' : many0, 'many1' : many1, @@ -538,8 +556,6 @@ module.exports = { 'commit' : commit, 'alt' : alt, 'zero' : zero, - 'get' : get, - 'getState' : getState, 'sepBy0' : sepBy0, 'sepBy1' : sepBy1, @@ -552,4 +568,3 @@ module.exports = { 'checkParser' : checkParser, 'good' : good }; - diff --git a/lib/funcs.js b/lib/funcs.js new file mode 100644 index 0000000..9b26614 --- /dev/null +++ b/lib/funcs.js @@ -0,0 +1,60 @@ +'use strict'; + +function compose(f, g) { + return function(x) { return f(g(x)); }; +} + +function id(x) { + return x; +} + +function constF(x) { + return function() { return x; }; +} + +function mapOverObject(key, f, obj) { + if ( !obj.hasOwnProperty(key) ) { + throw new Error('object does not have key ' + key); + } + var out = {}; + Object.getOwnPropertyNames(obj).forEach(function(name) { + if ( name === key ) { + out[name] = f(obj[name]); + } else { + out[name] = obj[name]; + } + }); + return out; +} + +function first(x, _) { + return x; +} + +function second(_, y) { + return y; +} + +function getArgs(args, ix) { + return Array.prototype.slice.call(args, ix); +} + +function pair(x, y) { + return [x, y]; +} + +function inc(x) { + return x + 1; +} + +module.exports = { + 'compose' : compose, + 'id' : id, + 'constF' : constF, + 'mapOverObject' : mapOverObject, + 'first' : first, + 'second' : second, + 'getArgs' : getArgs, + 'pair' : pair, + 'inc' : inc +}; diff --git a/test/combinators.js b/test/combinators.js index 8e6fdc3..a6892fc 100644 --- a/test/combinators.js +++ b/test/combinators.js @@ -58,86 +58,96 @@ testModule('combinators', function() { }); testModule("line/column", function() { + + function pos(p) { + return {'position': p}; + } + test("ItemPosition", function() { - deepEqual(iz2.item.parse('', [1, 1]), M.zero); - deepEqual(iz2.item.parse('abcdef', [1, 1]), good('a', 'bcdef', [1, 2])); - deepEqual(iz2.item.parse('\nbcdef', [1, 1]), good('\n', 'bcdef', [2, 1])); + deepEqual(iz2.item.parse('', pos([1, 1])), M.zero); + deepEqual(iz2.item.parse('abcdef', pos([1, 1])), good('a', 'bcdef', pos([1, 2]))); + deepEqual(iz2.item.parse('\nbcdef', pos([1, 1])), good('\n', 'bcdef', pos([2, 1]))); }); test("Literal", function() { var val = iz2.literal('3'); - deepEqual(val.parse('345', [3, 8]), good('3', '45', [3, 9])); - deepEqual(val.parse('45', [3, 8]), M.zero); + deepEqual(val.parse('345', pos([3, 8])), good('3', '45', pos([3, 9]))); + deepEqual(val.parse('45', pos([3, 8])), M.zero); }); test("Satisfy", function() { - var v1 = iz2.satisfy(function(x) {return x > '3';}).parse('123', [2, 2]), - v2 = iz2.satisfy(function(x) {return x < '3';}).parse('123', [2, 2]); + var v1 = iz2.satisfy(function(x) {return x > '3';}).parse('123', pos([2, 2])), + v2 = iz2.satisfy(function(x) {return x < '3';}).parse('123', pos([2, 2])); deepEqual(v1, M.zero); - deepEqual(v2, good('1', '23', [2, 3])); + deepEqual(v2, good('1', '23', pos([2, 3]))); }); test("String", function() { var parser = iz2.string('abc'), - v1 = parser.parse('abcdef', [4, 3]), - v2 = parser.parse('abdef', [4, 3]); - deepEqual(v1, good('abc', 'def', [4, 6])); + v1 = parser.parse('abcdef', pos([4, 3])), + v2 = parser.parse('abdef', pos([4, 3])); + deepEqual(v1, good('abc', 'def', pos([4, 6]))); deepEqual(v2, M.zero); }); test("Not1", function() { var val = iz2.not1(iz2.literal('2')); - deepEqual(val.parse('234', [1, 1]), M.zero); - deepEqual(val.parse('345', [1, 1]), good('3', '45', [1, 2])); + deepEqual(val.parse('234', pos([1, 1])), M.zero); + deepEqual(val.parse('345', pos([1, 1])), good('3', '45', pos([1, 2]))); }); test("oneOf", function() { var p = iz2.oneOf('abc'); - deepEqual(p.parse('cqrs', [3,4]), good('c', 'qrs', [3,5])); - deepEqual(p.parse('aqrs', [8,1]), good('a', 'qrs', [8,2])); - deepEqual(p.parse('dqrs', [2,2]), M.zero); + deepEqual(p.parse('cqrs', pos([3,4])), good('c', 'qrs', pos([3,5]))); + deepEqual(p.parse('aqrs', pos([8,1])), good('a', 'qrs', pos([8,2]))); + deepEqual(p.parse('dqrs', pos([2,2])), M.zero); }); }); testModule("count tokens", function() { + + function ct(c) { + return {'count': c}; + } + test("ItemPosition", function() { - deepEqual(iz3.item.parse('', 8), M.zero); - deepEqual(iz3.item.parse('abcdef', 5), good('a', 'bcdef', 6)); - deepEqual(iz3.item.parse('\nbcdef', 100), good('\n', 'bcdef', 101)); + deepEqual(iz3.item.parse('', ct(8)), M.zero); + deepEqual(iz3.item.parse('abcdef', ct(5)), good('a', 'bcdef', ct(6))); + deepEqual(iz3.item.parse('\nbcdef', ct(100)), good('\n', 'bcdef', ct(101))); }); test("Literal", function() { var val = iz3.literal('3'); - deepEqual(val.parse('345', 8), good('3', '45', 9)); - deepEqual(val.parse('45', 8), M.zero); + deepEqual(val.parse('345', ct(8)), good('3', '45', ct(9))); + deepEqual(val.parse('45', ct(8)), M.zero); }); test("Satisfy", function() { - var v1 = iz3.satisfy(function(x) {return x > '3';}).parse('123', 22), - v2 = iz3.satisfy(function(x) {return x < '3';}).parse('123', 22); + var v1 = iz3.satisfy(function(x) {return x > '3';}).parse('123', ct(22)), + v2 = iz3.satisfy(function(x) {return x < '3';}).parse('123', ct(22)); deepEqual(v1, M.zero); - deepEqual(v2, good('1', '23', 23)); + deepEqual(v2, good('1', '23', ct(23))); }); test("String", function() { var parser = iz3.string('abc'), - v1 = parser.parse('abcdef', 43), - v2 = parser.parse('abdef', 43); - deepEqual(v1, good('abc', 'def', 46)); + v1 = parser.parse('abcdef', ct(43)), + v2 = parser.parse('abdef', ct(43)); + deepEqual(v1, good('abc', 'def', ct(46))); deepEqual(v2, M.zero); }); test("Not1", function() { var val = iz3.not1(iz3.literal('2')); - deepEqual(val.parse('234', 61), M.zero); - deepEqual(val.parse('345', 61), good('3', '45', 62)); + deepEqual(val.parse('234', ct(61)), M.zero); + deepEqual(val.parse('345', ct(61)), good('3', '45', ct(62))); }); test("oneOf", function() { var p = iz3.oneOf('abc'); - deepEqual(p.parse('cqrs', 4), good('c', 'qrs', 5)); - deepEqual(p.parse('aqrs', 8), good('a', 'qrs', 9)); - deepEqual(p.parse('dqrs', 7), M.zero); + deepEqual(p.parse('cqrs', ct(4)), good('c', 'qrs', ct(5))); + deepEqual(p.parse('aqrs', ct(8)), good('a', 'qrs', ct(9))); + deepEqual(p.parse('dqrs', ct(7)), M.zero); }); }); @@ -229,19 +239,38 @@ testModule('combinators', function() { test("Put", function() { var val = C.put('xyz'); - deepEqual(val.parse('abc', []), good(null, 'xyz', [])); + deepEqual(val.parse('abc', []), good('abc', 'xyz', [])); + }); + + test("Update", function() { + var parser = C.update(function(x) {return x + 'z';}); + deepEqual(parser.parse('abc', []), good('abc', 'abcz', [])); }); test("PutState", function() { var v1 = C.putState(29).parse('abc123', 2); - deepEqual(v1, good(null, 'abc123', 29)); + deepEqual(v1, good(2, 'abc123', 29)); }); test("UpdateState", function() { var v1 = C.updateState(function(x) {return x * 4;}).parse('abc', 18); - deepEqual(v1, good(null, 'abc', 72)); + deepEqual(v1, good(18, 'abc', 72)); }); + test("PutStateForKey", function() { + var s1 = {'mykey': 3}, + s2 = {'mykey': 4}; + var parser = C.putStateForKey('mykey', 4); + deepEqual(parser.parse('abc', s1), good(3, 'abc', s2)); + }); + + test("UpdateStateForKey", function() { + var s1 = {'mykey': 3}, + s2 = {'mykey': 4}; + var parser = C.updateStateForKey('mykey', function(x) {return x + 1;}); + deepEqual(parser.parse('abc', s1), good(3, 'abc', s2)); + }); + test("Check", function() { var val = C.check(function(x) {return x.length > 3;}, C.get); deepEqual(val.parse('abcde', []), good('abcde', 'abcde', [])); @@ -363,10 +392,11 @@ testModule('combinators', function() { }); test("Lookahead", function() { - var parser = C.lookahead(iz3.oneOf([2,3])); - deepEqual(parser.parse([2,3,4,5], 41), good(2, [2,3,4,5], 41)); - deepEqual(parser.parse([3,4,5], 41), good(3, [3,4,5], 41)); - deepEqual(parser.parse([4,5], null), M.zero); + var parser = C.lookahead(iz3.oneOf([2,3])), + s = {'count': 41}; + deepEqual(parser.parse([2,3,4,5], s), good(2, [2,3,4,5], s)); + deepEqual(parser.parse([3,4,5], s), good(3, [3,4,5], s)); + deepEqual(parser.parse([4,5], s), M.zero); }); test("Not0", function() { @@ -393,6 +423,12 @@ testModule('combinators', function() { deepEqual(C.getState.parse('abc', 123), good(123, 'abc', 123)); }); + test("GetStateForKey", function() { + var s1 = {'mykey': 4}; + var parser = C.getStateForKey('mykey'); + deepEqual(parser.parse('abc', s1), good(4, 'abc', s1)) + }); + test("when using function where Parser is expected, the 'actual' key appears in error message", function() { var p = iz1.literal(2); try { diff --git a/test/cst.js b/test/cst.js index 5358285..80f6bc4 100644 --- a/test/cst.js +++ b/test/cst.js @@ -25,6 +25,9 @@ testModule('cst', function() { return obj; } + function ct(c) { + return {'count': c}; + } test("CutSuccess", function() { deepEqual(cut('oops', basic.item).parse('abc', null), good('a', 'bc', null)); @@ -54,12 +57,12 @@ testModule('cst', function() { }); test("NodeSuccess", function() { - deepEqual(node('blar').parse('abc', 17), - good(cstnode('blar', 17, 17), 'abc', 17)); - deepEqual(node('blar', ['a', count.item]).parse('def', 17), - good(cstnode('blar', 17, 18, ['a', 'd']), 'ef', 18)); - deepEqual(node('blar', ['a', count.item], ['b', count.item]).parse('def', 17), - good(cstnode('blar', 17, 19, ['a', 'd'], ['b', 'e']), 'f', 19)); + deepEqual(node('blar').parse('abc', ct(17)), + good(cstnode('blar', ct(17), ct(17)), 'abc', ct(17))); + deepEqual(node('blar', ['a', count.item]).parse('def', ct(17)), + good(cstnode('blar', ct(17), ct(18), ['a', 'd']), 'ef', ct(18))); + deepEqual(node('blar', ['a', count.item], ['b', count.item]).parse('def', ct(17)), + good(cstnode('blar', ct(17), ct(19), ['a', 'd'], ['b', 'e']), 'f', ct(19))); }); test("NodeFailure", function() { @@ -68,10 +71,10 @@ testModule('cst', function() { }); test("NodeError", function() { - deepEqual(node('blar', ['a', cut('oops', zero)]).parse('abc', 17), - err([['blar', 17], ['oops', 17]])); - deepEqual(node('blar', ['a', count.item], ['b', cut('oops', zero)]).parse('def', 17), - err([['blar', 17], ['oops', 18]])); + deepEqual(node('blar', ['a', cut('oops', zero)]).parse('abc', ct(17)), + err([['blar', ct(17)], ['oops', ct(17)]])); + deepEqual(node('blar', ['a', count.item], ['b', cut('oops', zero)]).parse('def', ct(17)), + err([['blar', ct(17)], ['oops', ct(18)]])); }); }); From 30f07861ca682edec97e0b53bd96ea1c1c8ec66a Mon Sep 17 00:00:00 2001 From: Matt Fenwick Date: Thu, 21 Jan 2016 20:20:03 -0600 Subject: [PATCH 2/4] improve name and arg order --- lib/combinators.js | 8 ++++---- lib/funcs.js | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/combinators.js b/lib/combinators.js index 2c6df28..2ae33ec 100644 --- a/lib/combinators.js +++ b/lib/combinators.js @@ -171,7 +171,7 @@ function updateStateForKey(key, g) { */ checkFunction('updateStateForKey', g); function f(xs, s) { - return good(s[key], xs, F.mapOverObject(key, g, s)); + return good(s[key], xs, F.fmapObject(s, key, g)); } return new Parser(f); } @@ -496,14 +496,14 @@ function _bump(char, s) { /* only treats `\n` as newline */ - return F.mapOverObject('position', function(position) { + return F.fmapObject(s, 'position', function(position) { var line = position[0], col = position[1]; if ( char === '\n' ) { return [line + 1, 1]; } return [line, col + 1]; - }, s); + }); } var basic = Itemizer(F.second), @@ -511,7 +511,7 @@ var basic = Itemizer(F.second), position = Itemizer(_bump), // assumes that state is an integer -- how many tokens have been consumed count = Itemizer(function(first, s) { - return F.mapOverObject('count', F.inc, s); + return F.fmapObject(s, 'count', F.inc); }); diff --git a/lib/funcs.js b/lib/funcs.js index 9b26614..f82e4e6 100644 --- a/lib/funcs.js +++ b/lib/funcs.js @@ -12,7 +12,7 @@ function constF(x) { return function() { return x; }; } -function mapOverObject(key, f, obj) { +function fmapObject(obj, key, f) { if ( !obj.hasOwnProperty(key) ) { throw new Error('object does not have key ' + key); } @@ -51,7 +51,7 @@ module.exports = { 'compose' : compose, 'id' : id, 'constF' : constF, - 'mapOverObject' : mapOverObject, + 'fmapObject': fmapObject, 'first' : first, 'second' : second, 'getArgs' : getArgs, From ce64fdc9d0f1842045367b5b77560e3835497a93 Mon Sep 17 00:00:00 2001 From: Matt Fenwick Date: Thu, 21 Jan 2016 20:26:36 -0600 Subject: [PATCH 3/4] use pointfree style --- lib/combinators.js | 26 ++++++++------------------ 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/lib/combinators.js b/lib/combinators.js index 2ae33ec..c7aa146 100644 --- a/lib/combinators.js +++ b/lib/combinators.js @@ -134,15 +134,10 @@ function update(g) { return new Parser(f); } -function put(xs) { - /* - m t -> Parser e s (m t) (m t) - */ - return update(F.constF(xs)); -} - -// Parser e s (m t) (m t) -var get = update(F.id); + // m t -> Parser e s (m t) (m t) +var put = F.compose(update, F.constF), + // Parser e s (m t) (m t) + get = update(F.id); function updateState(g) { /* @@ -155,15 +150,10 @@ function updateState(g) { return new Parser(f); } -function putState(s) { - /* - s -> Parser e s (m t) s - */ - return updateState(F.constF(s)); -} - -// Parser e s (m t) s -var getState = updateState(F.id); + // s -> Parser e s (m t) s +var putState = F.compose(updateState, F.constF), + // Parser e s (m t) s + getState = updateState(F.id); function updateStateForKey(key, g) { /* From 01ce4654ff74590498a62ea40fe13c029539223b Mon Sep 17 00:00:00 2001 From: Matt Fenwick Date: Thu, 21 Jan 2016 20:35:02 -0600 Subject: [PATCH 4/4] fix jshint warnings --- lib/combinators.js | 2 +- test/combinators.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/combinators.js b/lib/combinators.js index c7aa146..05dfbed 100644 --- a/lib/combinators.js +++ b/lib/combinators.js @@ -500,7 +500,7 @@ var basic = Itemizer(F.second), // assumes the state is a 2-tuple of integers (line, column) position = Itemizer(_bump), // assumes that state is an integer -- how many tokens have been consumed - count = Itemizer(function(first, s) { + count = Itemizer(function(first, s) { return F.fmapObject(s, 'count', F.inc); }); diff --git a/test/combinators.js b/test/combinators.js index a6892fc..81410cb 100644 --- a/test/combinators.js +++ b/test/combinators.js @@ -426,7 +426,7 @@ testModule('combinators', function() { test("GetStateForKey", function() { var s1 = {'mykey': 4}; var parser = C.getStateForKey('mykey'); - deepEqual(parser.parse('abc', s1), good(4, 'abc', s1)) + deepEqual(parser.parse('abc', s1), good(4, 'abc', s1)); }); test("when using function where Parser is expected, the 'actual' key appears in error message", function() {