Skip to content

Commit

Permalink
feat(combinators/sepBy1): add sepBy1 combinator
Browse files Browse the repository at this point in the history
  • Loading branch information
norskeld committed Jul 2, 2022
1 parent 190de09 commit 52b7ddf
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 1 deletion.
57 changes: 57 additions & 0 deletions docs/content/combinators/sepBy1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
---
title: 'sepBy1'
kind: 'composite'
description: 'sepBy combinator parses zero or more occurrences of parser, separated by sep. Returns a list of values (without separator) returned by parser.'
---

```typescript {{ withLineNumbers: false }}
function sepBy1<T, S>(parser: Parser<T>, sep: Parser<S>): Parser<Array<T>>
```

## Description

`sepBy1` combinator parses *one* or more occurrences of `parser`, separated by `sep`. Returns a list of values (without separator) returned by `parser`. Otherwise returns an error produced by `parser`.

## Usage

```typescript
const Parser = sepBy1(uint(), string('+'))
```

<details>
<summary>Output</summary>

### Success

```typescript
run(Parser).with('1+2+3+4')
{
isOk: true,
pos: 7,
value: [ 1, 2, 3, 4 ]
}
```

```typescript
run(Parser).with('1-two')
{
isOk: true,
pos: 1,
value: [ 1 ]
}
```

### Failure

```typescript
run(Parser).with('one+two')
{
isOk: false,
pos: 0,
expected: 'unsigned integer'
}
```
</details>
1 change: 1 addition & 0 deletions docs/src/lib/sidebar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export function getSidebarItems(): Array<SectionNode> {
createItem('mapTo', '/combinators/mapTo'),
createItem('optional', '/combinators/optional'),
createItem('sepBy', '/combinators/sepBy'),
createItem('sepBy1', '/combinators/sepBy1'),
createItem('sequence', '/combinators/sequence'),
createItem('takeLeft', '/combinators/takeLeft'),
createItem('takeMid', '/combinators/takeMid'),
Expand Down
28 changes: 27 additions & 1 deletion src/__tests__/combinators/sepBy.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { sepBy } from '../../combinators/sepBy'
import { sepBy, sepBy1 } from '../../combinators/sepBy'
import { string } from '../../parsers/string'
import { run, result, should, describe } from '../@helpers'

Expand Down Expand Up @@ -27,3 +27,29 @@ describe('sepBy', (it) => {
should.matchState(actual, expected)
})
})

describe('sepBy1', (it) => {
it('should succeed with an array of matched strings without separator', () => {
const parser = sepBy1(string('x'), string('!'))
const actual = run(parser, 'x!x!x!')
const expected = result(true, ['x', 'x', 'x'])

should.matchState(actual, expected)
})

it(`should succeed with an array of matched string if separator didn't match`, () => {
const parser = sepBy1(string('x'), string('!'))
const actual = run(parser, 'x-y')
const expected = result(true, ['x'])

should.matchState(actual, expected)
})

it('should fail with expectation of the parser if nothing matched', () => {
const parser = sepBy1(string('hello'), string('?'))
const actual = run(parser, 'bye?bye?')
const expected = result(false, 'hello')

should.matchState(actual, expected)
})
})
30 changes: 30 additions & 0 deletions src/combinators/sepBy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,33 @@ export function sepBy<T, S>(parser: Parser<T>, sep: Parser<S>): Parser<Array<T>>
}
}
}

export function sepBy1<T, S>(parser: Parser<T>, sep: Parser<S>): Parser<Array<T>> {
return {
parse(input, pos) {
// Run the parser once to get the first value.
const resultP = parser.parse(input, pos)

// If the parser succeeds, run the parser and separator parser many times.
if (resultP.isOk) {
const resultS = many(sequence(sep, parser)).parse(input, resultP.pos)
const values = [resultP.value]

// If the parsers succeed, concatenate the values sans the separator.
resultS.value.forEach(([, value]) => values.push(value))

return {
isOk: true,
pos: resultS.pos,
value: values
}
}

return {
isOk: false,
pos: resultP.pos,
expected: resultP.expected
}
}
}
}

0 comments on commit 52b7ddf

Please sign in to comment.