francisrstokes / arcsecond

✨Zero Dependency Parser Combinator Library for JS Based on Haskell's Parsec
558 stars 28 forks source link

`customSepByWithSequenceOf` of tutorial 4 does allow trailing comma #110

Open devurandom opened 1 year ago

devurandom commented 1 year ago

With following code, copied almost unchanged from tutorial 4 (changes are in template arguments, to workaround #109):

import {
    char,
    letters,
    Parser,
    possibly,
    sepBy,
    sequenceOf,
} from "arcsecond";

const customSepByWithSequenceOf = <T>(separatorParser: Parser<string>) => (valueParser: Parser<T>) =>
    sequenceOf([
        sepBy(separatorParser)(valueParser) as Parser<T>,//<< https://github.com/francisrstokes/arcsecond/issues/109
        possibly(separatorParser),
    ]).map(([values, _]) => values);

export const parse = (input: string) => customSepByWithSequenceOf(char(","))(letters).run(input);

I get:

❯ pnpm build

> arcsecond-repro2@0.1.0 build [REDACTED]/arcsecond-repro2
> tsc -p .

❯ node
Welcome to Node.js v18.17.1.
Type ".help" for more information.
> p = await import("./dist/parser.js?v1")
[Module: null prototype] { parse: [Function: parse] }
> p.parse("a,b,")
{
  isError: true,
  error: 'ParseError (position 4): Expecting letters',
  index: 4,
  data: null
}

customSepByWithSequenceOf is unable to parse a list with trailing comma, which according to https://github.com/francisrstokes/arcsecond/blob/main/tutorial/tutorial-part-4.md#writing-custom-combinators it should be able to do.

Please find a complete example in https://github.com/devurandom/arcsecond-issue-110-repro.

devurandom commented 1 year ago

The coroutine example works with small changes:

const sepByTrailing = <T>(sep: Parser<string>, val: Parser<T>) =>
    coroutine((run) => {
        const results: T[] = [];

        while (true) {
            const valResult = run(either(val));
            if (valResult.isError) break;

            results.push(valResult.value);

            const sepResult = run(either(sep));
            if (sepResult.isError) break;
        }

        return results;
    });

I used <T>(sep: Parser<string>, val: Parser<T>) => ... instead of separatorParser => valueParser => ... to avoid https://github.com/francisrstokes/arcsecond/issues/109.

I results.push(valResult.value) instead of results.push(valResult), because I did not want to work with any[], but was unable find type for results to make this happen. I tried variations of ResultType<T, E, D>[], but got:

TS2345: Argument of type  { isError: boolean; value: T; }  is not assignable to parameter of type  ResultType<T, E, D> 
  Type  { isError: boolean; value: T; }  is missing the following properties from type  Ok<T, D> :  index, result, data