Skip to content
This repository has been archived by the owner on Jan 6, 2025. It is now read-only.

Commit

Permalink
memoize the Parser per AST (#475)
Browse files Browse the repository at this point in the history
  • Loading branch information
tim-smart authored Oct 18, 2023
1 parent 135072e commit 46dcfeb
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 14 deletions.
5 changes: 5 additions & 0 deletions .changeset/modern-forks-raise.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@effect/schema": patch
---

memoize the Parser per AST
49 changes: 35 additions & 14 deletions src/Parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import * as Effect from "effect/Effect"
import * as Either from "effect/Either"
import { pipe } from "effect/Function"
import { globalValue } from "effect/GlobalValue"
import * as Option from "effect/Option"
import * as Predicate from "effect/Predicate"
import * as ReadonlyArray from "effect/ReadonlyArray"
Expand All @@ -18,7 +19,7 @@ const getEither = (
ast: AST.AST,
isDecoding: boolean
): (i: unknown, options?: AST.ParseOptions) => Either.Either<ParseResult.ParseError, any> =>
go(ast, isDecoding) as any
goMemo(ast, isDecoding) as any

const getSync = (ast: AST.AST, isDecoding: boolean) => {
const parser = getEither(ast, isDecoding)
Expand All @@ -38,7 +39,7 @@ const getOption = (ast: AST.AST, isDecoding: boolean) => {
}

const getEffect = (ast: AST.AST, isDecoding: boolean) => {
const parser = go(ast, isDecoding)
const parser = goMemo(ast, isDecoding)
return (input: unknown, options?: AST.ParseOptions) =>
parser(input, { ...options, isEffectAllowed: true })
}
Expand Down Expand Up @@ -248,11 +249,31 @@ interface Parser<I, A> {
*/
export const defaultParseOption: AST.ParseOptions = {}

const decodeMemoMap = globalValue(
Symbol.for("@effect/schema/Parser/decodeMemoMap"),
() => new WeakMap<AST.AST, Parser<any, any>>()
)
const encodeMemoMap = globalValue(
Symbol.for("@effect/schema/Parser/encodeMemoMap"),
() => new WeakMap<AST.AST, Parser<any, any>>()
)

const goMemo = (ast: AST.AST, isDecoding: boolean): Parser<any, any> => {
const memoMap = isDecoding ? decodeMemoMap : encodeMemoMap
const memo = memoMap.get(ast)
if (memo) {
return memo
}
const parser = go(ast, isDecoding)
memoMap.set(ast, parser)
return parser
}

const go = (ast: AST.AST, isDecoding: boolean): Parser<any, any> => {
switch (ast._tag) {
case "Refinement": {
if (isDecoding) {
const from = go(ast.from, true)
const from = goMemo(ast.from, true)
return (i, options) =>
handleForbidden(
ParseResult.flatMap(
Expand All @@ -269,8 +290,8 @@ const go = (ast: AST.AST, isDecoding: boolean): Parser<any, any> => {
options
)
} else {
const from = go(AST.to(ast), true)
const to = go(dropRightRefinement(ast.from), false)
const from = goMemo(AST.to(ast), true)
const to = goMemo(dropRightRefinement(ast.from), false)
return (
i,
options
Expand All @@ -279,8 +300,8 @@ const go = (ast: AST.AST, isDecoding: boolean): Parser<any, any> => {
}
case "Transform": {
const transform = getFinalTransformation(ast.transformation, isDecoding)
const from = isDecoding ? go(ast.from, true) : go(ast.to, false)
const to = isDecoding ? go(ast.to, true) : go(ast.from, false)
const from = isDecoding ? goMemo(ast.from, true) : goMemo(ast.to, false)
const to = isDecoding ? goMemo(ast.to, true) : goMemo(ast.from, false)
return (i1, options) =>
handleForbidden(
ParseResult.flatMap(
Expand Down Expand Up @@ -330,10 +351,10 @@ const go = (ast: AST.AST, isDecoding: boolean): Parser<any, any> => {
return fromRefinement(ast, (u): u is any => Predicate.isString(u) && regex.test(u))
}
case "Tuple": {
const elements = ast.elements.map((e) => go(e.type, isDecoding))
const elements = ast.elements.map((e) => goMemo(e.type, isDecoding))
const rest = pipe(
ast.rest,
Option.map(ReadonlyArray.mapNonEmpty((ast) => go(ast, isDecoding)))
Option.map(ReadonlyArray.mapNonEmpty((ast) => goMemo(ast, isDecoding)))
)
let requiredLen = ast.elements.filter((e) => !e.isOptional).length
if (Option.isSome(ast.rest)) {
Expand Down Expand Up @@ -562,7 +583,7 @@ const go = (ast: AST.AST, isDecoding: boolean): Parser<any, any> => {
const propertySignatures: Array<Parser<any, any>> = []
const expectedKeys: Record<PropertyKey, null> = {}
for (const ps of ast.propertySignatures) {
propertySignatures.push(go(ps.type, isDecoding))
propertySignatures.push(goMemo(ps.type, isDecoding))
expectedKeys[ps.name] = null
}

Expand All @@ -573,8 +594,8 @@ const go = (ast: AST.AST, isDecoding: boolean): Parser<any, any> => {
} = {}
for (const is of ast.indexSignatures) {
indexSignatures.push([
go(is.parameter, isDecoding),
go(is.type, isDecoding)
goMemo(is.parameter, isDecoding),
goMemo(is.type, isDecoding)
])
const base = AST.getParameterBase(is.parameter)
if (AST.isSymbolKeyword(base)) {
Expand Down Expand Up @@ -792,7 +813,7 @@ const go = (ast: AST.AST, isDecoding: boolean): Parser<any, any> => {
const len = ownKeys.length
const map = new Map<any, Parser<any, any>>()
for (let i = 0; i < ast.types.length; i++) {
map.set(ast.types[i], go(ast.types[i], isDecoding))
map.set(ast.types[i], goMemo(ast.types[i], isDecoding))
}
return (input, options) => {
const es: Array<[number, ParseResult.ParseErrors]> = []
Expand Down Expand Up @@ -909,7 +930,7 @@ const go = (ast: AST.AST, isDecoding: boolean): Parser<any, any> => {
}
}
case "Lazy": {
const get = Internal.memoizeThunk(() => go(ast.f(), isDecoding))
const get = Internal.memoizeThunk(() => goMemo(ast.f(), isDecoding))
return (a, options) => get()(a, options)
}
}
Expand Down

0 comments on commit 46dcfeb

Please sign in to comment.