bread-n-butter
bread-n-butter copied to clipboard
Missing notFollowedBy
As you requested, I open a new issue for the functions which where missing for my project. The most important ones were then, skip and notFollowedBy, anyway I saw that the former where already on the TODO list. I think that lookahead is the negative function of notFollowedBy, so it probably is a good idea to add it too, even if I would name it followedBy.
If you want, I have a little bit of available time and can make PRs to add these functions.
As you saw, then and skip are easy but I don't like the names... Do you have any suggestions? They could also be functions instead of methods? I've spend a lot of time thinking on this and don't have any names I like. .then is confusing because of promises, I think...
What kind of parser did you write? I've actually never used lookahead or notFollowedBy in my own parsers, but I can see how they would be useful.
I never felt that then was a bad name, even regarding promises. In a way, it's working a little bit like a promise.
But if you want another name, let me suggest: but, reset, take, use, thenTake, thenUse…
Anyway, I like the fact that they are Parser methods, I find it easier to write a new parser this way.
In my project, I wrote a NoSQL DML parser. I know there are other solutions, but I used notFollowedBy in 2 situations:
- be sure that a number is not followed by a (second) decimal point,
- be sure that a type identifier is not followed by a letter (this is to ensure that the detected type is not the written type but only beginning the same way).
I could also add a bnb.alt (bnb.choice maybe?) or something like Parsimmon.alt for when you have big chains, so it's a bit more readable potentially.
// current
const jsonValue: bnb.Parser<JSONValue> = bnb.lazy(() => {
return jsonObject
.or(jsonArray)
.or(jsonString)
.or(jsonNumber)
.or(jsonNull)
.or(jsonTrue)
.or(jsonFalse)
.thru(token);
});
// new way?
const jsonValue2: bnb.Parser<JSONValue> = bnb.lazy(() => {
return bnb
.choice(
jsonObject,
jsonArray,
jsonString,
jsonNumber,
jsonNull,
jsonTrue,
jsonFalse
)
.thru(token);
});
Can you link me to your parser, if it's open source? I'd like to just see notFollowedBy in your code.
Maybe we could have .followedBy(anotherParser) and .notFollowedBy(anotherParser)? Seems like both get decent use (I searched GitHub for code usage), and don't have any clear alternatives. Plus they're kinda confusing to implement yourself.
Good idea for choice, it's also something I was using a lot, even if or was a good workaround. next is a good name, too. Your PR are great.
Unfortunately, I can't provide a link to my parser as it's not public. But don't hesitate and ask me if you want help to implement followedBy and notFollowedBy…
export function lookahead<B>(parser: Parser<B>) {
return new Parser(function (context) {
const a = parser.action(context)
if (a.type !== ActionResultType.OK) {
return a
}
return merge(a, context.ok(a.value))
})
}
export function notFollowing<B>(parser: Parser<B>) {
return new Parser(function (context) {
const a = parser.action(context)
if (a.type === ActionResultType.OK) {
const b = context.fail<null>([ `not '${a.value}'` ])
return merge(a, b)
}
return merge(a, context.ok(null))
})
}
Let me know if there is a problem with restoring original signatures -- I will fix that.
lookahead is definitely something we missed from Parsimmon. It is quite useful especially when you want to match invalid content (for skipping syntax errors and keep parsing):
const op = bnb.choice(
bnb.text('&&'),
bnb.text('||'));
const fruit = bnb.choice(
bnb.text('apple'),
bnb.text('banana'),
bnb.match(/[^&\|]+/).lookahead(op.or(bnb.eof)) // catch-all for invalid fruit names
);
const fruitExpr = fruit.sepBy(op);
This can tolerate invalid fruit until another operator or EOF is encountered:
fruitExpr.parse('apple||super duper smelly durian&&banana')
=> ['apple', 'super duper smelly durian', 'banana']
I might take a look if someone sends a PR, but I haven't been actively working on this library for a while