From 505dad8d173ee3bd92da759233f36b164339b7d7 Mon Sep 17 00:00:00 2001 From: Vladislav Mamon Date: Fri, 13 May 2022 15:29:11 +0500 Subject: [PATCH] perf: improve performance by simplifying values passed around - Got noticeable gains by getting rid of 'state' object and constantly copying input string. - Got rid of helper functions which were causing overhead and unnecessary pressure. --- src/combinators/chain.ts | 2 +- src/combinators/choice.ts | 12 ++++----- src/combinators/error.ts | 18 ++++++++------ src/combinators/many.ts | 27 ++++++++++++--------- src/combinators/map.ts | 18 ++++++++------ src/combinators/optional.ts | 2 +- src/combinators/sepBy.ts | 2 +- src/combinators/sequence.ts | 26 +++++++++++--------- src/combinators/take.ts | 2 +- src/parsers/defer.ts | 12 ++++++--- src/parsers/eof.ts | 18 ++++++++++---- src/parsers/float.ts | 19 +++++++++------ src/parsers/integer.ts | 18 ++++++++------ src/parsers/nothing.ts | 10 +++++--- src/parsers/regexp.ts | 24 ++++++++++++------ src/parsers/rest.ts | 16 ++++++------ src/parsers/run.ts | 4 +-- src/parsers/string.ts | 39 +++++++++++++++++++++--------- src/{state => }/state.ts | 21 ++++++---------- src/state/helpers.ts | 17 ------------- src/state/index.ts | 2 -- tests/@helpers/index.ts | 22 ++++++++--------- tests/combinators/chain.spec.ts | 4 +-- tests/combinators/choice.spec.ts | 4 +-- tests/combinators/error.spec.ts | 4 +-- tests/combinators/many.spec.ts | 4 +-- tests/combinators/map.spec.ts | 6 ++--- tests/combinators/optional.spec.ts | 4 +-- tests/combinators/sepBy.spec.ts | 4 +-- tests/combinators/sequence.spec.ts | 4 +-- tests/combinators/take.spec.ts | 8 +++--- tests/index.spec.ts | 4 --- tests/parsers/defer.spec.ts | 6 ++--- tests/parsers/eof.spec.ts | 4 +-- tests/parsers/eol.spec.ts | 8 +++--- tests/parsers/letter.spec.ts | 18 +++++++------- tests/parsers/nothing.spec.ts | 2 +- tests/parsers/regexp.spec.ts | 22 ++++++++--------- tests/parsers/rest.spec.ts | 4 +-- tests/parsers/run.spec.ts | 6 ++--- tests/parsers/string.spec.ts | 18 +++++++------- tests/parsers/whitespace.spec.ts | 8 +++--- tests/state/helpers.spec.ts | 33 ------------------------- 43 files changed, 254 insertions(+), 252 deletions(-) rename src/{state => }/state.ts (63%) delete mode 100644 src/state/helpers.ts delete mode 100644 src/state/index.ts delete mode 100644 tests/state/helpers.spec.ts diff --git a/src/combinators/chain.ts b/src/combinators/chain.ts index d912850..c45a6b9 100644 --- a/src/combinators/chain.ts +++ b/src/combinators/chain.ts @@ -1,4 +1,4 @@ -import type { Parser } from '../state' +import { type Parser } from '../state' import { sequence } from './sequence' import { many } from './many' diff --git a/src/combinators/choice.ts b/src/combinators/choice.ts index d00329c..178631d 100644 --- a/src/combinators/choice.ts +++ b/src/combinators/choice.ts @@ -3,19 +3,19 @@ import type { Parser, Result, ToUnion } from '../state' export function choice>>(...ps: T): Parser> export function choice(...ps: Array>): Parser { return { - parse(state) { + parse(input, pos) { let nextResult: Result | null = null for (const parser of ps) { - const result = parser.parse(state) + const result = parser.parse(input, pos) - switch (result.kind) { - case 'success': { + switch (result.isOk) { + case true: { return result } - case 'failure': { - if (!nextResult || nextResult.state.index < result.state.index) { + case false: { + if (!nextResult || nextResult.pos < result.pos) { nextResult = result } } diff --git a/src/combinators/error.ts b/src/combinators/error.ts index 28b976d..6229cad 100644 --- a/src/combinators/error.ts +++ b/src/combinators/error.ts @@ -1,17 +1,21 @@ -import { failure, type Parser } from '../state' +import { type Parser } from '../state' export function error(parser: Parser, expected: string): Parser { return { - parse(state) { - const result = parser.parse(state) + parse(input, pos) { + const result = parser.parse(input, pos) - switch (result.kind) { - case 'success': { + switch (result.isOk) { + case true: { return result } - case 'failure': { - return failure(state, expected) + case false: { + return { + isOk: false, + pos, + error: expected + } } } } diff --git a/src/combinators/many.ts b/src/combinators/many.ts index bccc95f..abd5cda 100644 --- a/src/combinators/many.ts +++ b/src/combinators/many.ts @@ -1,23 +1,28 @@ -import { success, type Parser } from '../state' +import { type Parser } from '../state' export function many(parser: Parser): Parser> { return { - parse(state) { - let values: Array = [] - let nextState = state + parse(input, pos) { + const values: Array = [] + let nextPos = pos while (true) { - const result = parser.parse(nextState) + const result = parser.parse(input, nextPos) - switch (result.kind) { - case 'success': { - values = [...values, result.value] - nextState = result.state + switch (result.isOk) { + case true: { + values.push(result.value) + nextPos = result.pos break } - case 'failure': { - return success(nextState, values) + case false: { + return { + isOk: true, + pos: nextPos, + input, + value: values + } } } } diff --git a/src/combinators/map.ts b/src/combinators/map.ts index 62a89a8..f883b18 100644 --- a/src/combinators/map.ts +++ b/src/combinators/map.ts @@ -1,16 +1,20 @@ -import { success, type Parser } from '../state' +import { type Parser } from '../state' export function map(parser: Parser, fn: (value: T) => R): Parser { return { - parse(state) { - const result = parser.parse(state) + parse(input, pos) { + const result = parser.parse(input, pos) - switch (result.kind) { - case 'success': { - return success(result.state, fn(result.value)) + switch (result.isOk) { + case true: { + return { + isOk: true, + pos: result.pos, + value: fn(result.value) + } } - case 'failure': { + case false: { return result } } diff --git a/src/combinators/optional.ts b/src/combinators/optional.ts index 6672d95..e168804 100644 --- a/src/combinators/optional.ts +++ b/src/combinators/optional.ts @@ -1,4 +1,4 @@ -import type { Parser } from '../state' +import { type Parser } from '../state' import { nothing } from '../parsers/nothing' import { choice } from './choice' diff --git a/src/combinators/sepBy.ts b/src/combinators/sepBy.ts index 1d15694..86aad90 100644 --- a/src/combinators/sepBy.ts +++ b/src/combinators/sepBy.ts @@ -1,4 +1,4 @@ -import type { Parser } from '../state' +import { type Parser } from '../state' import { sequence } from './sequence' import { many } from './many' diff --git a/src/combinators/sequence.ts b/src/combinators/sequence.ts index e91d2e2..07a0939 100644 --- a/src/combinators/sequence.ts +++ b/src/combinators/sequence.ts @@ -1,29 +1,33 @@ -import { success, type Parser, type ToTuple } from '../state' +import type { Parser, ToTuple } from '../state' export function sequence>>(...ps: T): Parser> export function sequence(...ps: Array>): Parser> { return { - parse(state) { - let values: Array = [] - let nextState = state + parse(input, pos) { + const values: Array = [] + let nextPos = pos for (const parser of ps) { - const result = parser.parse(nextState) + const result = parser.parse(input, nextPos) - switch (result.kind) { - case 'success': { - values = [...values, result.value] - nextState = result.state + switch (result.isOk) { + case true: { + values.push(result.value) + nextPos = result.pos break } - case 'failure': { + case false: { return result } } } - return success(nextState, values) + return { + isOk: true, + pos: nextPos, + value: values + } } } } diff --git a/src/combinators/take.ts b/src/combinators/take.ts index 242bb7d..ff41128 100644 --- a/src/combinators/take.ts +++ b/src/combinators/take.ts @@ -1,4 +1,4 @@ -import type { Parser } from '../state' +import { type Parser } from '../state' import { sequence } from './sequence' import { map } from './map' diff --git a/src/parsers/defer.ts b/src/parsers/defer.ts index 0275001..ad9f3d2 100644 --- a/src/parsers/defer.ts +++ b/src/parsers/defer.ts @@ -1,4 +1,4 @@ -import { failure, type Parser } from '../state' +import { type Parser } from '../state' interface Deferred extends Parser { with(parser: Parser): void @@ -12,12 +12,16 @@ export function defer(): Deferred { deferred = parser }, - parse(state) { + parse(input, pos) { if (deferred) { - return deferred.parse(state) + return deferred.parse(input, pos) } - return failure(state, `Deferred parser wasn't initialized.`) + return { + isOk: false, + pos, + error: `Deferred parser wasn't initialized.` + } } } } diff --git a/src/parsers/eof.ts b/src/parsers/eof.ts index dbbc62b..b38d5d1 100644 --- a/src/parsers/eof.ts +++ b/src/parsers/eof.ts @@ -1,17 +1,25 @@ -import { success, failure, type Parser } from '../state' +import { type Parser } from '../state' export function eof(): Parser { return { - parse(state) { - const isEof = state.index === state.text.length + parse(input, pos) { + const isEof = pos === input.length switch (isEof) { case true: { - return success({ text: state.text, index: state.text.length }, null) + return { + isOk: true, + pos: input.length, + value: null + } } case false: { - return failure(state, 'end of input') + return { + isOk: false, + pos, + error: 'end of input' + } } } } diff --git a/src/parsers/float.ts b/src/parsers/float.ts index b3e2c94..215646a 100644 --- a/src/parsers/float.ts +++ b/src/parsers/float.ts @@ -1,4 +1,4 @@ -import { success, type Parser } from '../state' +import { type Parser } from '../state' import { regexp } from './regexp' @@ -6,15 +6,20 @@ const FLOAT_RE = /-?\d+\.\d+/g export function float(): Parser { return { - parse(state) { - const result = regexp(FLOAT_RE, 'float').parse(state) + parse(input, pos) { + const result = regexp(FLOAT_RE, 'float').parse(input, pos) - switch (result.kind) { - case 'success': { - return success(result.state, parseFloat(result.value)) + switch (result.isOk) { + case true: { + return { + isOk: true, + input, + pos: result.pos, + value: parseFloat(result.value) + } } - case 'failure': { + case false: { return result } } diff --git a/src/parsers/integer.ts b/src/parsers/integer.ts index 8efc79d..756b9a1 100644 --- a/src/parsers/integer.ts +++ b/src/parsers/integer.ts @@ -1,4 +1,4 @@ -import { success, type Parser } from '../state' +import { type Parser } from '../state' import { regexp } from './regexp' @@ -7,15 +7,19 @@ const INT_UNSIGNED_RE = /\d+/g function createIntegerParser(re: RegExp, expectation: string, radix: number): Parser { return { - parse(state) { - const result = regexp(re, expectation).parse(state) + parse(input, pos) { + const result = regexp(re, expectation).parse(input, pos) - switch (result.kind) { - case 'success': { - return success(result.state, parseInt(result.value, radix)) + switch (result.isOk) { + case true: { + return { + isOk: true, + pos: result.pos, + value: parseInt(result.value, radix) + } } - case 'failure': { + case false: { return result } } diff --git a/src/parsers/nothing.ts b/src/parsers/nothing.ts index 6fa4f58..6645ebd 100644 --- a/src/parsers/nothing.ts +++ b/src/parsers/nothing.ts @@ -1,9 +1,13 @@ -import { success, type Parser } from '../state' +import { type Parser } from '../state' export function nothing(): Parser { return { - parse(state) { - return success(state, null) + parse(_, pos) { + return { + isOk: true, + pos, + value: null + } } } } diff --git a/src/parsers/regexp.ts b/src/parsers/regexp.ts index 1835239..84d60d5 100644 --- a/src/parsers/regexp.ts +++ b/src/parsers/regexp.ts @@ -1,21 +1,29 @@ -import { success, failure, type Parser } from '../state' +import { type Parser } from '../state' export function regexp(re: RegExp, expected: string): Parser { return { - parse(state) { + parse(input, pos) { // Reset RegExp index, because we abuse the 'g' flag. - re.lastIndex = state.index + re.lastIndex = pos // `.exec` is actually a little bit faster than `.test`. - const result = re.exec(state.text) + const result = re.exec(input) - if (result && result.index === state.index) { + if (result && result.index === pos) { const [match] = result - const index = state.index + match.length + const index = pos + match.length - return success({ text: state.text, index }, match) + return { + isOk: true, + pos: index, + value: match + } } else { - return failure(state, expected) + return { + isOk: false, + pos, + error: expected + } } } } diff --git a/src/parsers/rest.ts b/src/parsers/rest.ts index 15d422e..8260b9f 100644 --- a/src/parsers/rest.ts +++ b/src/parsers/rest.ts @@ -1,15 +1,13 @@ -import { success, type Parser } from '../state' +import { type Parser } from '../state' export function rest(): Parser { return { - parse(state) { - return success( - { - text: state.text, - index: state.text.length - }, - state.text.substring(state.index) - ) + parse(input, pos) { + return { + isOk: true, + pos: input.length, + value: input.substring(pos) + } } } } diff --git a/src/parsers/run.ts b/src/parsers/run.ts index 6027aae..48ceded 100644 --- a/src/parsers/run.ts +++ b/src/parsers/run.ts @@ -6,8 +6,8 @@ interface Runnable { export function run(parser: Parser): Runnable { return { - with(text) { - return parser.parse({ text, index: 0 }) + with(input) { + return parser.parse(input, 0) } } } diff --git a/src/parsers/string.ts b/src/parsers/string.ts index 9230266..55eb42c 100644 --- a/src/parsers/string.ts +++ b/src/parsers/string.ts @@ -1,19 +1,28 @@ -import { success, failure, type Parser } from '../state' +import { type Parser } from '../state' + import { size } from '../utils/unicode' export function string(match: string): Parser { return { - parse(state) { - const index = state.index + match.length - const slice = state.text.substring(state.index, index) + parse(input, pos) { + const nextPos = pos + match.length + const slice = input.substring(pos, nextPos) switch (slice === match) { case true: { - return success({ text: state.text, index }, match) + return { + isOk: true, + pos: nextPos, + value: match + } } case false: { - return failure(state, match) + return { + isOk: false, + pos: nextPos, + error: match + } } } } @@ -22,17 +31,25 @@ export function string(match: string): Parser { export function ustring(match: string): Parser { return { - parse(state) { - const index = state.index + size(match) - const slice = state.text.substring(state.index, index) + parse(input, pos) { + const nextPos = pos + size(match) + const slice = input.substring(pos, nextPos) switch (slice === match) { case true: { - return success({ text: state.text, index }, match) + return { + isOk: true, + pos: nextPos, + value: match + } } case false: { - return failure(state, match) + return { + isOk: false, + pos: nextPos, + error: match + } } } } diff --git a/src/state/state.ts b/src/state.ts similarity index 63% rename from src/state/state.ts rename to src/state.ts index 968cdb4..5556254 100644 --- a/src/state/state.ts +++ b/src/state.ts @@ -1,21 +1,16 @@ export interface Parser { - parse(state: State): Result + parse(input: string, pos: number): Result } -export interface State { - text: string - index: number +export type Failure = { + readonly isOk: false + readonly pos: number + readonly error: string } -export interface Failure { - readonly kind: 'failure' - readonly state: State - readonly expected: string -} - -export interface Success { - readonly kind: 'success' - readonly state: State +export type Success = { + readonly isOk: true + readonly pos: number readonly value: T } diff --git a/src/state/helpers.ts b/src/state/helpers.ts deleted file mode 100644 index fc5e47a..0000000 --- a/src/state/helpers.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type { State, Failure, Success } from './state' - -export function success(state: State, value: T): Success { - return { - kind: 'success', - state, - value - } -} - -export function failure(state: State, expected: string): Failure { - return { - kind: 'failure', - state, - expected - } -} diff --git a/src/state/index.ts b/src/state/index.ts deleted file mode 100644 index 47ebc90..0000000 --- a/src/state/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './helpers' -export * from './state' diff --git a/tests/@helpers/index.ts b/tests/@helpers/index.ts index a1c2083..4842172 100644 --- a/tests/@helpers/index.ts +++ b/tests/@helpers/index.ts @@ -1,10 +1,8 @@ import { run as internal$run } from '@lib/parsers/run' import type { Parser, Result } from '@lib/state' -type ResultKind = 'success' | 'failure' - interface ReducedResult { - kind: ResultKind + isOk: boolean value: T } @@ -14,15 +12,15 @@ export const should = { }, matchState(received: Result, expected: ReducedResult): void { - expect(received.kind).toEqual(expected.kind) + expect(received.isOk).toEqual(expected.isOk) - switch (received.kind) { - case 'success': { + switch (received.isOk) { + case true: { return expect(received.value).toEqual(expected.value) } - case 'failure': { - return expect(received.expected).toEqual(expected.value) + case false: { + return expect(received.error).toEqual(expected.value) } } } @@ -32,13 +30,13 @@ export function run(parser: Parser, text: string): Result { return internal$run(parser).with(text) } -export function result(kind: ResultKind, value: T): ReducedResult { - return { kind, value } as const +export function result(isOk: boolean, value: T): ReducedResult { + return { isOk, value } as const } export function testFailure

Parser>(input: string, parser: P) { const actual = run(parser(), input) - const expected = result('failure', actual.kind === 'failure' ? actual.expected : actual.value) + const expected = result(false, !actual.isOk ? actual.error : actual.value) should.matchState(actual, expected) } @@ -49,7 +47,7 @@ export function testSuccess Parser>( parser: P ) { const actual = run(parser(), input) - const expected = result('success', value) + const expected = result(true, value) should.matchState(actual, expected) } diff --git a/tests/combinators/chain.spec.ts b/tests/combinators/chain.spec.ts index bcdd4cf..829503e 100644 --- a/tests/combinators/chain.spec.ts +++ b/tests/combinators/chain.spec.ts @@ -15,14 +15,14 @@ describe(chainl, () => { it('should succeed with eliminated left recursion and reduced to a value', () => { const actual = run(parser, '2 + 2 + 4') - const expected = result('success', 8) + const expected = result(true, 8) should.matchState(actual, expected) }) it('should fail with expectation of the regexp parser', () => { const actual = run(parser, 'x + x + 4') - const expected = result('failure', 'integer') + const expected = result(false, 'integer') should.matchState(actual, expected) }) diff --git a/tests/combinators/choice.spec.ts b/tests/combinators/choice.spec.ts index 874d27d..c6ed436 100644 --- a/tests/combinators/choice.spec.ts +++ b/tests/combinators/choice.spec.ts @@ -7,7 +7,7 @@ describe(choice, () => { it('should succeed with the value of the first successful parser in sequence', () => { const parser = choice(string('left'), string('mid'), string('right')) const actual = run(parser, 'mid') - const expected = result('success', 'mid') + const expected = result(true, 'mid') should.matchState(actual, expected) }) @@ -15,7 +15,7 @@ describe(choice, () => { it('should fail with the expectation of the first parser in sequence', () => { const parser = choice(string('left'), string('mid'), string('right')) const actual = run(parser, 'between') - const expected = result('failure', 'left') + const expected = result(false, 'right') // FIXME: Why 'right'? should.matchState(actual, expected) }) diff --git a/tests/combinators/error.spec.ts b/tests/combinators/error.spec.ts index 85459e9..11ea111 100644 --- a/tests/combinators/error.spec.ts +++ b/tests/combinators/error.spec.ts @@ -7,7 +7,7 @@ describe(error, () => { it('should successfully replace error message (expectation)', () => { const parser = error(string('9000'), 'replaced-error-message') const actual = run(parser, 'xxxx') - const expected = result('failure', 'replaced-error-message') + const expected = result(false, 'replaced-error-message') should.matchState(actual, expected) }) @@ -15,7 +15,7 @@ describe(error, () => { it('should not do anything if a parser succeeds', () => { const parser = error(string('9000'), 'replaced-error-message') const actual = run(parser, '9000') - const expected = result('success', '9000') + const expected = result(true, '9000') should.matchState(actual, expected) }) diff --git a/tests/combinators/many.spec.ts b/tests/combinators/many.spec.ts index 4262308..0a98a9b 100644 --- a/tests/combinators/many.spec.ts +++ b/tests/combinators/many.spec.ts @@ -7,7 +7,7 @@ describe(many, () => { it('should succeed with an array of matched strings', () => { const parser = many(string('x!')) const actual = run(parser, 'x!x!x!') - const expected = result('success', ['x!', 'x!', 'x!']) + const expected = result(true, ['x!', 'x!', 'x!']) should.matchState(actual, expected) }) @@ -15,7 +15,7 @@ describe(many, () => { it('should succeed with an empty array even if nothing matched', () => { const parser = many(string('hello')) const actual = run(parser, 'byebye') - const expected = result('success', []) + const expected = result(true, []) should.matchState(actual, expected) }) diff --git a/tests/combinators/map.spec.ts b/tests/combinators/map.spec.ts index af2dae4..d495fdb 100644 --- a/tests/combinators/map.spec.ts +++ b/tests/combinators/map.spec.ts @@ -7,7 +7,7 @@ describe(map, () => { it('should succeed if a single given parser succeeds', () => { const parser = map(string('9000'), (value) => parseInt(value, 10)) const actual = run(parser, '9000') - const expected = result('success', 9000) + const expected = result(true, 9000) should.matchState(actual, expected) }) @@ -15,7 +15,7 @@ describe(map, () => { it('should fail if a single given parser fails', () => { const parser = map(string('9000'), (value) => parseInt(value, 10)) const actual = run(parser, 'xxxx') - const expected = result('failure', '9000') + const expected = result(false, '9000') should.matchState(actual, expected) }) @@ -25,7 +25,7 @@ describe(mapTo, () => { it('should succeed if a single given parser succeeds', () => { const parser = mapTo(string('9000'), 'constant') const actual = run(parser, '9000') - const expected = result('success', 'constant') + const expected = result(true, 'constant') should.matchState(actual, expected) }) diff --git a/tests/combinators/optional.spec.ts b/tests/combinators/optional.spec.ts index 68d431b..cb6ce7d 100644 --- a/tests/combinators/optional.spec.ts +++ b/tests/combinators/optional.spec.ts @@ -9,7 +9,7 @@ describe(optional, () => { // TODO: This seems to be broken somehow? Because `optional` eats everything after 'Hello'... const parser = sequence(string('Hello'), optional(string('...'))) const actual = run(parser, 'Hello') - const expected = result('success', ['Hello', null]) + const expected = result(true, ['Hello', null]) should.matchState(actual, expected) }) @@ -17,7 +17,7 @@ describe(optional, () => { it('should succeed with null anyway', () => { const parser = optional(string('left')) const actual = run(parser, 'between') - const expected = result('success', null) + const expected = result(true, null) should.matchState(actual, expected) }) diff --git a/tests/combinators/sepBy.spec.ts b/tests/combinators/sepBy.spec.ts index fd491b5..af30157 100644 --- a/tests/combinators/sepBy.spec.ts +++ b/tests/combinators/sepBy.spec.ts @@ -7,7 +7,7 @@ describe(sepBy, () => { it('should succeed with an array of matched strings without separator', () => { const parser = sepBy(string('x'), string('!')) const actual = run(parser, 'x!x!x!') - const expected = result('success', ['x', 'x', 'x']) + const expected = result(true, ['x', 'x', 'x']) should.matchState(actual, expected) }) @@ -15,7 +15,7 @@ describe(sepBy, () => { it('should fail with the value of the first failed parser', () => { const parser = sepBy(string('hello'), string('?')) const actual = run(parser, 'bye?bye?') - const expected = result('failure', 'hello') + const expected = result(false, 'hello') should.matchState(actual, expected) }) diff --git a/tests/combinators/sequence.spec.ts b/tests/combinators/sequence.spec.ts index 708e5d4..1d3bc65 100644 --- a/tests/combinators/sequence.spec.ts +++ b/tests/combinators/sequence.spec.ts @@ -7,7 +7,7 @@ describe(sequence, () => { it('should succeed if a sequence of parsers succeeds', () => { const parser = sequence(string('hello'), string(' '), string('world')) const actual = run(parser, 'hello world') - const expected = result('success', ['hello', ' ', 'world']) + const expected = result(true, ['hello', ' ', 'world']) should.matchState(actual, expected) }) @@ -15,7 +15,7 @@ describe(sequence, () => { it('should fail if a sequence of parsers fails somewhere', () => { const parser = sequence(string('hello'), string(' '), string('world')) const actual = run(parser, 'bye friend') - const expected = result('failure', 'hello') + const expected = result(false, 'hello') should.matchState(actual, expected) }) diff --git a/tests/combinators/take.spec.ts b/tests/combinators/take.spec.ts index 74e4ee4..f5e34e4 100644 --- a/tests/combinators/take.spec.ts +++ b/tests/combinators/take.spec.ts @@ -8,7 +8,7 @@ describe(takeLeft, () => { it('should succeed with the value of the parser on the left-hand side', () => { const parser = takeLeft(string('left'), string('mid')) const actual = run(parser, 'leftmid') - const expected = result('success', 'left') + const expected = result(true, 'left') should.matchState(actual, expected) }) @@ -19,7 +19,7 @@ describe(takeMid, () => { it('should succeed with the value of the parser in the middle', () => { const parser = takeMid(string('left'), string('mid'), string('right')) const actual = run(parser, 'leftmidright') - const expected = result('success', 'mid') + const expected = result(true, 'mid') should.matchState(actual, expected) }) @@ -30,7 +30,7 @@ describe(takeRight, () => { it('should succeed with the value of the parser on right-hand side', () => { const parser = takeRight(string('mid'), string('right')) const actual = run(parser, 'midright') - const expected = result('success', 'right') + const expected = result(true, 'right') should.matchState(actual, expected) }) @@ -41,7 +41,7 @@ describe(takeSides, () => { it('should succeed with the tuple of the first and the last values', () => { const parser = takeSides(string('left'), string('mid'), string('right')) const actual = run(parser, 'leftmidright') - const expected = result('success', ['left', 'right']) + const expected = result(true, ['left', 'right']) should.matchState(actual, expected) }) diff --git a/tests/index.spec.ts b/tests/index.spec.ts index b37ca44..70a8f92 100644 --- a/tests/index.spec.ts +++ b/tests/index.spec.ts @@ -5,10 +5,6 @@ import { should } from '@tests/@helpers' import { expectedCombinators } from './combinators.spec' import { expectedParsers } from './parsers.spec' -it('should expose state helpers', () => { - should.expose(exposed, 'success', 'failure') -}) - it('should re-export combinators', () => { should.expose(exposed, ...expectedCombinators) }) diff --git a/tests/parsers/defer.spec.ts b/tests/parsers/defer.spec.ts index 82cb95b..f62a4af 100644 --- a/tests/parsers/defer.spec.ts +++ b/tests/parsers/defer.spec.ts @@ -10,7 +10,7 @@ describe(defer, () => { deferred.with(string('deferred')) const actual = run(deferred, 'deferred') - const expected = result('success', 'deferred') + const expected = result(true, 'deferred') should.matchState(actual, expected) }) @@ -20,7 +20,7 @@ describe(defer, () => { expect(() => { const actual = run(deferred, 'deferred') - const expected = result('failure', 'deferred') + const expected = result(false, 'deferred') should.matchState(actual, expected) }).toThrow() @@ -32,7 +32,7 @@ describe(defer, () => { deferred.with(string('deferred')) const actual = run(deferred, 'lazy') - const expected = result('failure', 'deferred') + const expected = result(false, 'deferred') should.matchState(actual, expected) }) diff --git a/tests/parsers/eof.spec.ts b/tests/parsers/eof.spec.ts index 0c979b2..38fa3d2 100644 --- a/tests/parsers/eof.spec.ts +++ b/tests/parsers/eof.spec.ts @@ -8,7 +8,7 @@ describe(eof, () => { it('should succeed if reached the end of input', () => { const parser = sequence(string('start'), eof()) const actual = run(parser, 'start') - const expected = result('success', ['start', null]) + const expected = result(true, ['start', null]) should.matchState(actual, expected) }) @@ -16,7 +16,7 @@ describe(eof, () => { it('should fail if did not reach the end of input', () => { const parser = sequence(string('start'), eof()) const actual = run(parser, 'start end') - const expected = result('failure', 'end of input') + const expected = result(false, 'end of input') should.matchState(actual, expected) }) diff --git a/tests/parsers/eol.spec.ts b/tests/parsers/eol.spec.ts index efe3562..3342663 100644 --- a/tests/parsers/eol.spec.ts +++ b/tests/parsers/eol.spec.ts @@ -12,14 +12,14 @@ World describe(eol, () => { it('should succeed if given a newline (Unix)', () => { const actual = run(eol(), '\n') - const expected = result('success', '\n') + const expected = result(true, '\n') should.matchState(actual, expected) }) it('should succeed if given a newline sequence (non-Unix)', () => { const actual = run(eol(), '\r\n') - const expected = result('success', '\r\n') + const expected = result(true, '\r\n') should.matchState(actual, expected) }) @@ -30,7 +30,7 @@ describe(eol, () => { const actualExplicit = run(parser, tcase) const actualImplicit = run(parser, tcaseLit) - const expected = result('success', ['Hello', '\n', 'World', '\n']) + const expected = result(true, ['Hello', '\n', 'World', '\n']) should.matchState(actualExplicit, expected) should.matchState(actualImplicit, expected) @@ -38,7 +38,7 @@ describe(eol, () => { it('should fail if given a string without a newline', () => { const actual = run(sequence(letters(), eol()), 'Hello') - const expected = result('failure', 'end of line') + const expected = result(false, 'end of line') should.matchState(actual, expected) }) diff --git a/tests/parsers/letter.spec.ts b/tests/parsers/letter.spec.ts index f93f2d9..3df074f 100644 --- a/tests/parsers/letter.spec.ts +++ b/tests/parsers/letter.spec.ts @@ -5,21 +5,21 @@ import { run, result, should } from '@tests/@helpers' describe(letter, () => { it('should succeed with an ASCII letter', () => { const actual = run(letter(), 'A') - const expected = result('success', 'A') + const expected = result(true, 'A') should.matchState(actual, expected) }) it('should succeed with a Unicode letter', () => { const actual = run(letter(), 'Â') - const expected = result('success', 'Â') + const expected = result(true, 'Â') should.matchState(actual, expected) }) it('should succeed with a single letter if given multiple letters', () => { const actual = run(letter(), 'ab') - const expected = result('success', 'a') + const expected = result(true, 'a') should.matchState(actual, expected) }) @@ -27,7 +27,7 @@ describe(letter, () => { it('should fail if given something other than a letter', () => { ;['1', '+', '~', '`', ':', `'`].forEach((tcase) => { const actual = run(letter(), tcase) - const expected = result('failure', 'letter') + const expected = result(false, 'letter') should.matchState(actual, expected) }) @@ -37,35 +37,35 @@ describe(letter, () => { describe(letters, () => { it('should succeed with an ASCII letter if given input with a single ASCII letter', () => { const actual = run(letters(), 'A') - const expected = result('success', 'A') + const expected = result(true, 'A') should.matchState(actual, expected) }) it('should succeed with a Unicode letter if given input with a single Unicode letter', () => { const actual = run(letters(), 'Â') - const expected = result('success', 'Â') + const expected = result(true, 'Â') should.matchState(actual, expected) }) it('should succeed with letters if given input with letters', () => { const actual = run(letters(), 'Âne') - const expected = result('success', 'Âne') + const expected = result(true, 'Âne') should.matchState(actual, expected) }) it('should succeed with letters if given input with letters and other symbols', () => { const actual = run(letters(), 'Âne+9000') - const expected = result('success', 'Âne') + const expected = result(true, 'Âne') should.matchState(actual, expected) }) it('should fail if given something other than letters', () => { const actual = run(letters(), '9000+Âne') - const expected = result('failure', 'letters') + const expected = result(false, 'letters') should.matchState(actual, expected) }) diff --git a/tests/parsers/nothing.spec.ts b/tests/parsers/nothing.spec.ts index eb61cc7..70c918a 100644 --- a/tests/parsers/nothing.spec.ts +++ b/tests/parsers/nothing.spec.ts @@ -5,7 +5,7 @@ import { run, result, should } from '@tests/@helpers' describe(nothing, () => { it('should succeed with null value', () => { const actual = run(nothing(), 'test') - const expected = result('success', null) + const expected = result(true, null) should.matchState(actual, expected) }) diff --git a/tests/parsers/regexp.spec.ts b/tests/parsers/regexp.spec.ts index 8f88452..333fdf9 100644 --- a/tests/parsers/regexp.spec.ts +++ b/tests/parsers/regexp.spec.ts @@ -5,13 +5,13 @@ import { run, result, should } from '@tests/@helpers' describe(regexp, () => { it('should succeed if given matching input', () => { const actualDigit = run(regexp(/\d/g, 'digit'), '0') - const expectedDigit = result('success', '0') + const expectedDigit = result(true, '0') const actualDigits = run(regexp(/\d+/g, 'digits'), '9000') - const expectedDigits = result('success', '9000') + const expectedDigits = result(true, '9000') const actualMatchGroups = run(regexp(/\((\s)+\)/g, 'match-groups'), '( )') - const expectedMatchGroups = result('success', '( )') + const expectedMatchGroups = result(true, '( )') should.matchState(actualDigit, expectedDigit) should.matchState(actualDigits, expectedDigits) @@ -20,17 +20,17 @@ describe(regexp, () => { it('should succeed if given a RegExp with Unicode flag', () => { const actualReEmoji = run(regexp(/\w+\s+👌/gu, 'words, spaces, ok emoji'), 'Yes 👌') - const expectedReEmoji = result('success', 'Yes 👌') + const expectedReEmoji = result(true, 'Yes 👌') should.matchState(actualReEmoji, expectedReEmoji) }) it('should succeed if given a RegExp with Unicode property escapes', () => { const actualReEmoji = run(regexp(/\p{Emoji_Presentation}+/gu, 'emoji'), '👌👌👌') - const expectedReEmoji = result('success', '👌👌👌') + const expectedReEmoji = result(true, '👌👌👌') const actualReNonLatin = run(regexp(/\P{Script_Extensions=Latin}+/gu, 'non-latin'), '大阪') - const expectedReNonLation = result('success', '大阪') + const expectedReNonLation = result(true, '大阪') should.matchState(actualReEmoji, expectedReEmoji) should.matchState(actualReNonLatin, expectedReNonLation) @@ -38,17 +38,17 @@ describe(regexp, () => { it('should succeeed if matches the beginning of input', () => { const actualDigits = run(regexp(/\d{2,3}/g, 'first-digits'), '90000') - const expectedDigits = result('success', '900') + const expectedDigits = result(true, '900') should.matchState(actualDigits, expectedDigits) }) it('should fail if does not match input', () => { const actualDigitFailure = run(regexp(/\d/g, 'digit'), 'hello') - const expectedDigitFailure = result('failure', 'digit') + const expectedDigitFailure = result(false, 'digit') const actualEitherFailure = run(regexp(/(spec|test)/g, 'either'), 'spock') - const expectedEitherFailure = result('failure', 'either') + const expectedEitherFailure = result(false, 'either') should.matchState(actualDigitFailure, expectedDigitFailure) should.matchState(actualEitherFailure, expectedEitherFailure) @@ -56,14 +56,14 @@ describe(regexp, () => { it(`should fail if given zero input and regexp with 'one or more' quantifier`, () => { const actualZeroFailure = run(regexp(/\d+/g, 'digits+'), '') - const expectedZeroFailure = result('failure', 'digits+') + const expectedZeroFailure = result(false, 'digits+') should.matchState(actualZeroFailure, expectedZeroFailure) }) it(`should succeed if given zero input and regexp with 'zero or more' quantifier`, () => { const actualZeroSuccess = run(regexp(/\d*/g, 'digits*'), '') - const expectedZeroSuccess = result('success', '') + const expectedZeroSuccess = result(true, '') should.matchState(actualZeroSuccess, expectedZeroSuccess) }) diff --git a/tests/parsers/rest.spec.ts b/tests/parsers/rest.spec.ts index ad85635..c7b306b 100644 --- a/tests/parsers/rest.spec.ts +++ b/tests/parsers/rest.spec.ts @@ -8,7 +8,7 @@ describe(rest, () => { it('should succeed with the rest of input', () => { const parser = sequence(string('start'), rest()) const actual = run(parser, 'startend') - const expected = result('success', ['start', 'end']) + const expected = result(true, ['start', 'end']) should.matchState(actual, expected) }) @@ -16,7 +16,7 @@ describe(rest, () => { it('should succeed with empty string if no input left to consume', () => { const parser = sequence(string('start'), rest()) const actual = run(parser, 'start') - const expected = result('success', ['start', '']) + const expected = result(true, ['start', '']) should.matchState(actual, expected) }) diff --git a/tests/parsers/run.spec.ts b/tests/parsers/run.spec.ts index 73159d8..48eb2af 100644 --- a/tests/parsers/run.spec.ts +++ b/tests/parsers/run.spec.ts @@ -8,7 +8,7 @@ describe(run, () => { it('should succeed if given a succeeding parser', () => { const parser = string('runnable') const actual = run(parser).with('runnable') - const expected = result('success', 'runnable') + const expected = result(true, 'runnable') should.matchState(actual, expected) }) @@ -19,7 +19,7 @@ describe(run, () => { deferred.with(string('deferred')) const actual = run(deferred).with('lazy') - const expected = result('failure', 'deferred') + const expected = result(false, 'deferred') should.matchState(actual, expected) }) @@ -29,7 +29,7 @@ describe(run, () => { expect(() => { const actual = run(deferred).with('deferred') - const expected = result('failure', 'deferred') + const expected = result(false, 'deferred') should.matchState(actual, expected) }).toThrow() diff --git a/tests/parsers/string.spec.ts b/tests/parsers/string.spec.ts index 7e8c5e4..e5e6ed8 100644 --- a/tests/parsers/string.spec.ts +++ b/tests/parsers/string.spec.ts @@ -7,7 +7,7 @@ describe(ustring, () => { const tcase = 'test' const actual = run(ustring(tcase), tcase) - const expected = result('success', tcase) + const expected = result(true, tcase) should.matchState(actual, expected) }) @@ -15,7 +15,7 @@ describe(ustring, () => { it('should succeed if given a Unicode string', () => { ;['语言处理', 'Hëllø!', 'Family :: 👨‍👩‍👧‍👦 👨‍👩‍👧‍👦 👨‍👩‍👧‍👦'].forEach((tcase) => { const actual = run(ustring(tcase), tcase) - const expected = result('success', tcase) + const expected = result(true, tcase) should.matchState(actual, expected) }) @@ -25,7 +25,7 @@ describe(ustring, () => { const tcase = 'test' const actual = run(ustring(tcase), tcase.repeat(2)) - const expected = result('success', tcase) + const expected = result(true, tcase) should.matchState(actual, expected) }) @@ -34,7 +34,7 @@ describe(ustring, () => { const tcase = 'test' const actual = run(ustring(tcase), 'wrong') - const expected = result('failure', tcase) + const expected = result(false, tcase) should.matchState(actual, expected) }) @@ -43,7 +43,7 @@ describe(ustring, () => { const tcase = 'test' const actual = run(ustring(tcase), '') - const expected = result('failure', tcase) + const expected = result(false, tcase) should.matchState(actual, expected) }) @@ -54,7 +54,7 @@ describe(string, () => { const tcase = 'test' const actual = run(string(tcase), tcase) - const expected = result('success', tcase) + const expected = result(true, tcase) should.matchState(actual, expected) }) @@ -63,7 +63,7 @@ describe(string, () => { const tcase = 'test' const actual = run(string(tcase), tcase.repeat(2)) - const expected = result('success', tcase) + const expected = result(true, tcase) should.matchState(actual, expected) }) @@ -72,7 +72,7 @@ describe(string, () => { const tcase = 'test' const actual = run(string(tcase), 'wrong') - const expected = result('failure', tcase) + const expected = result(false, tcase) should.matchState(actual, expected) }) @@ -81,7 +81,7 @@ describe(string, () => { const tcase = 'test' const actual = run(string(tcase), '') - const expected = result('failure', tcase) + const expected = result(false, tcase) should.matchState(actual, expected) }) diff --git a/tests/parsers/whitespace.spec.ts b/tests/parsers/whitespace.spec.ts index c067704..79f04f4 100644 --- a/tests/parsers/whitespace.spec.ts +++ b/tests/parsers/whitespace.spec.ts @@ -10,7 +10,7 @@ describe(whitespace, () => { const space = ' ' const actual = run(whitespace(), space.repeat(4)) - const expected = result('success', space.repeat(4)) + const expected = result(true, space.repeat(4)) should.matchState(actual, expected) }) @@ -19,7 +19,7 @@ describe(whitespace, () => { const space = ' ' const actual = run(whitespace(), space.repeat(4) + 'const') - const expected = result('success', space.repeat(4)) + const expected = result(true, space.repeat(4)) should.matchState(actual, expected) }) @@ -30,14 +30,14 @@ describe(whitespace, () => { const ws = whitespace() const actual = run(sequence(keyword, ws, identifier), 'let identity') - const expected = result('success', ['let', ' ', 'identity']) + const expected = result(true, ['let', ' ', 'identity']) should.matchState(actual, expected) }) it('should fail if given a non-matching input', () => { const actual = run(sequence(string('let'), whitespace(), string('rec')), 'letrec') - const expected = result('failure', 'whitespace') + const expected = result(false, 'whitespace') should.matchState(actual, expected) }) diff --git a/tests/state/helpers.spec.ts b/tests/state/helpers.spec.ts deleted file mode 100644 index 3cf0221..0000000 --- a/tests/state/helpers.spec.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { State, Success, Failure } from '@lib/state/state' -import { success, failure } from '@lib/state/helpers' - -describe(success, () => { - it('should construct a success object if given a state object and value', () => { - const value = 'value' - const state: State = { - text: 'test', - index: 0 - } - - const actual = success({ ...state }, value) - const result: Success = { kind: 'success', state, value } - - expect(actual).toStrictEqual(result) - }) -}) - -describe(failure, () => { - it('should construct a failure object if given a state object and expectation message', () => { - const value = 'value' - const expected = 'value' - const state: State = { - text: 'test', - index: 0 - } - - const actual = failure({ ...state }, value) - const result: Failure = { kind: 'failure', state, expected } - - expect(actual).toStrictEqual(result) - }) -})