Skip to content

Commit

Permalink
feat: add spans
Browse files Browse the repository at this point in the history
- Added spans for all parsers and combinators that explicitly return results.
- Made some minor improvements here and there.
  • Loading branch information
norskeld committed Jan 17, 2023
1 parent dc23f7f commit 88b13b1
Show file tree
Hide file tree
Showing 23 changed files with 100 additions and 31 deletions.
3 changes: 3 additions & 0 deletions src/__tests__/combinators/attempt.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ describe('attempt', () => {

should.beStrictEqual(actual, {
isOk: true,
span: [0, 13],
pos: 13,
value: ['hello', 'let', 'lettuce']
})
Expand All @@ -24,6 +25,7 @@ describe('attempt', () => {

should.beStrictEqual(actual, {
isOk: false,
span: [6, 9],
pos: 9,
expected: 'lettuce'
})
Expand All @@ -34,6 +36,7 @@ describe('attempt', () => {

should.beStrictEqual(actual, {
isOk: false,
span: [6, 6],
pos: 6,
expected: 'let'
})
Expand Down
3 changes: 3 additions & 0 deletions src/__tests__/combinators/lookahead.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ describe('lookahead', () => {

should.beStrictEqual(actual, {
isOk: true,
span: [0, 13],
pos: 13,
value: ['hello', 'let', 'lettuce']
})
Expand All @@ -24,6 +25,7 @@ describe('lookahead', () => {

should.beStrictEqual(actual, {
isOk: false,
span: [6, 9],
pos: 9,
expected: 'lettuce'
})
Expand All @@ -34,6 +36,7 @@ describe('lookahead', () => {

should.beStrictEqual(actual, {
isOk: false,
span: [6, 9],
pos: 9,
expected: 'let'
})
Expand Down
2 changes: 1 addition & 1 deletion src/__tests__/parsers/tryRun.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ describe('tryRun', () => {
deferred.with(string('deferred'))

const actual = () => tryRun(deferred).with('lazy')
const expected = new ParserError({ pos: 8, expected: 'deferred' })
const expected = new ParserError({ pos: 8, span: [0, 4], expected: 'deferred' })

should.throwError(actual, expected)
})
Expand Down
4 changes: 3 additions & 1 deletion src/combinators/attempt.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Parser } from '@types'
import type { Parser, Span } from '@types'

/**
* Applies `parser` without consuming any input. It doesn't care if `parser` succeeds or fails, it
Expand All @@ -18,6 +18,7 @@ export function attempt<T>(parser: Parser<T>): Parser<T> {
case true: {
return {
isOk: true,
span: [pos, pos] as Span,
pos,
value: result.value
}
Expand All @@ -27,6 +28,7 @@ export function attempt<T>(parser: Parser<T>): Parser<T> {
case false: {
return {
isOk: false,
span: [pos, pos] as Span,
pos,
expected: result.expected
}
Expand Down
3 changes: 2 additions & 1 deletion src/combinators/error.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Parser } from '@types'
import type { Parser, Span } from '@types'

/**
* Replaces `parser`'s error message with `expected`.
Expand All @@ -21,6 +21,7 @@ export function error<T>(parser: Parser<T>, expected: string): Parser<T> {
case false: {
return {
isOk: false,
span: [pos, result.pos] as Span,
pos,
expected
}
Expand Down
1 change: 1 addition & 0 deletions src/combinators/lookahead.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export function lookahead<T>(parser: Parser<T>): Parser<T> {
case true: {
return {
isOk: true,
span: result.span,
pos,
value: result.value
}
Expand Down
6 changes: 4 additions & 2 deletions src/combinators/many.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Parser, SafeParser } from '@types'
import type { Parser, SucceedingParser, Span } from '@types'

/**
* Applies `parser` *zero* or more times, collecting its results. Never fails.
Expand All @@ -7,7 +7,7 @@ import type { Parser, SafeParser } from '@types'
*
* @returns Array of the returned values of `parser`
*/
export function many<T>(parser: Parser<T>): SafeParser<Array<T>> {
export function many<T>(parser: Parser<T>): SucceedingParser<Array<T>> {
return {
parse(input, pos) {
const values: Array<T> = []
Expand All @@ -26,6 +26,7 @@ export function many<T>(parser: Parser<T>): SafeParser<Array<T>> {

return {
isOk: true,
span: [pos, nextPos] as Span,
pos: nextPos,
value: values
}
Expand Down Expand Up @@ -65,6 +66,7 @@ export function many1<T>(parser: Parser<T>): Parser<Array<T>> {

return {
isOk: true,
span: [pos, nextPos] as Span,
pos: nextPos,
value: values
}
Expand Down
9 changes: 6 additions & 3 deletions src/combinators/map.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Parser } from '@types'
import type { Parser, Span } from '@types'

/**
* Applies `fn` to the `parser`'s result.
Expand All @@ -8,17 +8,20 @@ import type { Parser } from '@types'
*
* @returns Result of `fn`
*/
export function map<T, R>(parser: Parser<T>, fn: (value: T) => R): Parser<R> {
export function map<T, R>(parser: Parser<T>, fn: (value: T, span: Span) => R): Parser<R> {
return {
parse(input, pos) {
const result = parser.parse(input, pos)

switch (result.isOk) {
case true: {
const span = [pos, result.pos] as Span

return {
isOk: true,
span,
pos: result.pos,
value: fn(result.value)
value: fn(result.value, span)
}
}

Expand Down
14 changes: 11 additions & 3 deletions src/combinators/sepBy.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { many } from './many'
import { sequence } from './sequence'

import type { Parser } from '@types'
import type { Parser, Span } from '@types'

/**
* Parses *zero* or more occurrences of `parser`, separated by `sep`. Never fails.
Expand All @@ -23,17 +23,21 @@ export function sepBy<T, S>(parser: Parser<T>, sep: Parser<S>): Parser<Array<T>>
const values = [resultP.value]

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

return {
isOk: true,
span: [pos, resultS.pos] as Span,
pos: resultS.pos,
value: values
}
}

return {
isOk: true,
span: [pos, resultP.pos] as Span,
pos: resultP.pos,
value: []
}
Expand Down Expand Up @@ -61,17 +65,21 @@ export function sepBy1<T, S>(parser: Parser<T>, sep: Parser<S>): Parser<Array<T>
const values = [resultP.value]

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

return {
isOk: true,
span: [pos, resultS.pos] as Span,
pos: resultS.pos,
value: values
}
}

return {
isOk: false,
span: [pos, resultP.pos] as Span,
pos: resultP.pos,
expected: resultP.expected
}
Expand Down
3 changes: 2 additions & 1 deletion src/combinators/sequence.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Parser, ToTuple } from '@types'
import type { Parser, Span, ToTuple } from '@types'

/**
* Applies `ps` parsers in order, until *all* of them succeed.
Expand Down Expand Up @@ -32,6 +32,7 @@ export function sequence<T>(...ps: Array<Parser<T>>): Parser<Array<T>> {

return {
isOk: true,
span: [pos, nextPos] as Span,
pos: nextPos,
value: values
}
Expand Down
4 changes: 3 additions & 1 deletion src/combinators/until.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Parser } from '@types'
import type { Parser, Span } from '@types'

/**
* Applies source `parser`, collects its output, and stops after `terminator` parser succeeds.
Expand All @@ -23,6 +23,7 @@ export function takeUntil<T, S>(parser: Parser<T>, terminator: Parser<S>): Parse
case true: {
return {
isOk: true,
span: [pos, resultT.pos] as Span,
pos: resultT.pos,
value: [values, resultT.value]
}
Expand Down Expand Up @@ -68,6 +69,7 @@ export function skipUntil<T, S>(parser: Parser<T>, terminator: Parser<S>): Parse
case true: {
return {
isOk: true,
span: [pos, resultT.pos] as Span,
pos: resultT.pos,
value: resultT.value
}
Expand Down
4 changes: 3 additions & 1 deletion src/parsers/any.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Parser } from '@types'
import type { Parser, Span } from '@types'

/**
* Parses any single character from the input and returns it. Fails at the end of input.
Expand All @@ -11,6 +11,7 @@ export function any(): Parser<string> {
if (input.length === pos) {
return {
isOk: false,
span: [pos, pos] as Span,
pos,
expected: 'any @ reached the end of input'
}
Expand All @@ -21,6 +22,7 @@ export function any(): Parser<string> {

return {
isOk: true,
span: [pos, nextPos] as Span,
pos: nextPos,
value
}
Expand Down
3 changes: 2 additions & 1 deletion src/parsers/defer.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Parser } from '@types'
import type { Parser, Span } from '@types'

/**
* Intersection type to add a method for deferred parser definition.
Expand Down Expand Up @@ -71,6 +71,7 @@ export function defer<T>(): Deferred<T> {

return {
isOk: false,
span: [pos, pos] as Span,
pos,
expected: `Deferred parser wasn't initialized.`
}
Expand Down
8 changes: 4 additions & 4 deletions src/parsers/eof.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Parser } from '@types'
import type { Parser, Span } from '@types'

/**
* Only succeeds at the end of the input.
Expand All @@ -8,12 +8,11 @@ import type { Parser } from '@types'
export function eof(): Parser<null> {
return {
parse(input, pos) {
const isEof = pos === input.length

switch (isEof) {
switch (pos === input.length) {
case true: {
return {
isOk: true,
span: [pos, pos] as Span,
pos: input.length,
value: null
}
Expand All @@ -22,6 +21,7 @@ export function eof(): Parser<null> {
case false: {
return {
isOk: false,
span: [pos, pos] as Span,
pos,
expected: 'end of input'
}
Expand Down
7 changes: 5 additions & 2 deletions src/parsers/noneOf.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Parser } from '@types'
import type { Parser, Span } from '@types'

/**
* Ensures that none of the characters in the given string matches the current character.
Expand All @@ -15,6 +15,7 @@ export function noneOf(chars: string): Parser<string> {
if (input.length === pos) {
return {
isOk: false,
span: [pos, pos] as Span,
pos,
expected: 'noneOf @ reached the end of input'
}
Expand All @@ -26,14 +27,16 @@ export function noneOf(chars: string): Parser<string> {
if (!charset.includes(char)) {
return {
isOk: true,
span: [pos, nextPos] as Span,
pos: nextPos,
value: char
}
}

return {
isOk: false,
pos,
span: [pos, pos] as Span,
pos: nextPos,
expected: `none of: ${charset.join(', ')}`
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/parsers/nothing.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Parser } from '@types'
import type { Parser, Span } from '@types'

/**
* Simply resolves to `null`.
Expand All @@ -10,6 +10,7 @@ export function nothing(): Parser<null> {
parse(_, pos) {
return {
isOk: true,
span: [pos, pos] as Span,
pos,
value: null
}
Expand Down
Loading

0 comments on commit 88b13b1

Please sign in to comment.