Skip to content

Commit

Permalink
perf: improve performance by simplifying values passed around
Browse files Browse the repository at this point in the history
- 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.
  • Loading branch information
norskeld committed May 13, 2022
1 parent 4eb856d commit 505dad8
Show file tree
Hide file tree
Showing 43 changed files with 254 additions and 252 deletions.
2 changes: 1 addition & 1 deletion src/combinators/chain.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Parser } from '../state'
import { type Parser } from '../state'

import { sequence } from './sequence'
import { many } from './many'
Expand Down
12 changes: 6 additions & 6 deletions src/combinators/choice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,19 @@ import type { Parser, Result, ToUnion } from '../state'
export function choice<T extends Array<Parser<unknown>>>(...ps: T): Parser<ToUnion<T>>
export function choice<T>(...ps: Array<Parser<T>>): Parser<T> {
return {
parse(state) {
parse(input, pos) {
let nextResult: Result<T> | 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
}
}
Expand Down
18 changes: 11 additions & 7 deletions src/combinators/error.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
import { failure, type Parser } from '../state'
import { type Parser } from '../state'

export function error<T>(parser: Parser<T>, expected: string): Parser<T> {
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
}
}
}
}
Expand Down
27 changes: 16 additions & 11 deletions src/combinators/many.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,28 @@
import { success, type Parser } from '../state'
import { type Parser } from '../state'

export function many<T>(parser: Parser<T>): Parser<Array<T>> {
return {
parse(state) {
let values: Array<T> = []
let nextState = state
parse(input, pos) {
const values: Array<T> = []
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
}
}
}
}
Expand Down
18 changes: 11 additions & 7 deletions src/combinators/map.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
import { success, type Parser } from '../state'
import { type Parser } from '../state'

export function map<T, R>(parser: Parser<T>, fn: (value: T) => R): Parser<R> {
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
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/combinators/optional.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Parser } from '../state'
import { type Parser } from '../state'

import { nothing } from '../parsers/nothing'
import { choice } from './choice'
Expand Down
2 changes: 1 addition & 1 deletion src/combinators/sepBy.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Parser } from '../state'
import { type Parser } from '../state'

import { sequence } from './sequence'
import { many } from './many'
Expand Down
26 changes: 15 additions & 11 deletions src/combinators/sequence.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,33 @@
import { success, type Parser, type ToTuple } from '../state'
import type { Parser, ToTuple } from '../state'

export function sequence<T extends Array<Parser<unknown>>>(...ps: T): Parser<ToTuple<T>>
export function sequence<T>(...ps: Array<Parser<T>>): Parser<Array<T>> {
return {
parse(state) {
let values: Array<T> = []
let nextState = state
parse(input, pos) {
const values: Array<T> = []
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
}
}
}
}
2 changes: 1 addition & 1 deletion src/combinators/take.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Parser } from '../state'
import { type Parser } from '../state'

import { sequence } from './sequence'
import { map } from './map'
Expand Down
12 changes: 8 additions & 4 deletions src/parsers/defer.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { failure, type Parser } from '../state'
import { type Parser } from '../state'

interface Deferred<T> extends Parser<T> {
with(parser: Parser<T>): void
Expand All @@ -12,12 +12,16 @@ export function defer<T>(): Deferred<T> {
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.`
}
}
}
}
18 changes: 13 additions & 5 deletions src/parsers/eof.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@
import { success, failure, type Parser } from '../state'
import { type Parser } from '../state'

export function eof(): Parser<null> {
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'
}
}
}
}
Expand Down
19 changes: 12 additions & 7 deletions src/parsers/float.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
import { success, type Parser } from '../state'
import { type Parser } from '../state'

import { regexp } from './regexp'

const FLOAT_RE = /-?\d+\.\d+/g

export function float(): Parser<number> {
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
}
}
Expand Down
18 changes: 11 additions & 7 deletions src/parsers/integer.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { success, type Parser } from '../state'
import { type Parser } from '../state'

import { regexp } from './regexp'

Expand All @@ -7,15 +7,19 @@ const INT_UNSIGNED_RE = /\d+/g

function createIntegerParser(re: RegExp, expectation: string, radix: number): Parser<number> {
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
}
}
Expand Down
10 changes: 7 additions & 3 deletions src/parsers/nothing.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import { success, type Parser } from '../state'
import { type Parser } from '../state'

export function nothing(): Parser<null> {
return {
parse(state) {
return success(state, null)
parse(_, pos) {
return {
isOk: true,
pos,
value: null
}
}
}
}
24 changes: 16 additions & 8 deletions src/parsers/regexp.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,29 @@
import { success, failure, type Parser } from '../state'
import { type Parser } from '../state'

export function regexp(re: RegExp, expected: string): Parser<string> {
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
}
}
}
}
Expand Down
16 changes: 7 additions & 9 deletions src/parsers/rest.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import { success, type Parser } from '../state'
import { type Parser } from '../state'

export function rest(): Parser<string> {
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)
}
}
}
}
Loading

1 comment on commit 505dad8

@vercel
Copy link

@vercel vercel bot commented on 505dad8 May 13, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.