crazy-parser
Version:
A light-weight parser combinator
186 lines (139 loc) • 4.47 kB
text/typescript
export type Validator<A, I = unknown, E extends TypeError = TypeError> =
(input: I) => A | E
export const str: Validator<string> = input =>
{
if (typeof input != "string")
return new TypeError(`Expected string, got ${JSON.stringify(input)}`)
return input
}
export const num: Validator<number> = (input =>
{
if (typeof input != "number")
return new TypeError(`Expected number, got ${JSON.stringify(input)}`)
return input
})
export const bool: Validator<boolean> = input =>
{
if (typeof input != "boolean")
return new TypeError(`Expected boolean, got ${JSON.stringify(input)}`)
return input
}
export const nil: Validator<null> = input =>
{
if (input !== null)
return new TypeError(`Expected null, got ${JSON.stringify(input)}`)
return null
}
export const array: <A>(inner: Validator<A>) => Validator<A[]> = inner => input =>
{
if (! Array.isArray(input))
return new TypeError(`Expected array, got ${JSON.stringify(input)}`)
for (let i in input)
{
const item = input[i]
const result = inner(item)
if (result instanceof TypeError)
return new TypeError(`Invalid index ${i}: ${result.message}`)
}
return structuredClone(input)
}
export const obj: <Vs extends Record<string, any>, _Vs = Vs>(
inner: { [k in keyof Vs]: Validator<Vs[k]> },
fallback?: { [k in keyof _Vs]?: _Vs[k] }
) => Validator<{ [i in keyof Vs]: Vs[i] }>
= (inner, fallback) => input =>
{
if (typeof input != "object" || input === null || Array.isArray(input))
return new TypeError(`Expected object, got ${JSON.stringify(input)}`)
let _input = input as Record<string, unknown>
for (const key in inner)
{
const validator = inner[key]
if (! (key in _input))
{
if (! fallback || ! (key in fallback))
return new TypeError(`Missing key: ${key}`)
if (input == _input)
_input = structuredClone(_input)
_input[key] = (fallback as any)[key]
continue
}
const result = validator((_input as any)[key])
if (result instanceof TypeError)
{
if (! fallback || ! (key in fallback))
return new TypeError(`Invalid key ${key}: ${result.message}`)
if (input == _input)
_input = structuredClone(_input)
_input[key] = (fallback as any)[key]
}
}
return _input as any
}
export const sequence: <Ts extends any[]>(...inners: { [k in keyof Ts]: Validator<Ts[k]> }) => Validator<Ts> = (...inners) => input =>
{
if (! Array.isArray(input))
return new TypeError(`Expected tuple, got ${JSON.stringify(input)}`)
if (input.length != inners.length)
return new TypeError(`Expected tuple of length ${inners.length}, got ${JSON.stringify(input)}`)
for (const i in inners)
{
const validator = inners[i]
const item = input[i]
const result = validator(item)
if (result instanceof TypeError)
return new TypeError(`Invalid index ${i}: ${result.message}`)
}
return input as any
}
export const asum: <Ts extends any[]>(...inners: { [k in keyof Ts]: Validator<Ts[k]> }) => Validator<Ts[number]> = (...inners) => input =>
{
const errors: TypeError[] = []
for (const validator of inners)
{
const result = validator(input)
if (result instanceof TypeError)
errors.push(result)
else
return result
}
return new TypeError(`No alternatives matched, got errors: ${errors.map(e => e.message).join("; ")}`)
}
type Ands<Ts extends any[]> =
Ts extends [infer Head, ...infer Tail]
? Head & Ands<Tail>
: unknown
export const ands: <Ts extends any[]>(...inners: { [k in keyof Ts]: Validator<Ts[k]> }) => Validator<Ands<Ts>> = (...inners) => input =>
{
for (const i in inners)
{
const validator = inners[i]
const result = validator(input)
if (result instanceof TypeError)
return new TypeError(`Failed at step ${i}: ${result.message}`)
}
return input as any
}
export const eq: <A>(value: A) => Validator<A> = value => input =>
{
if (input !== value)
return new TypeError(`Expected ${JSON.stringify(value)}, got ${JSON.stringify(input)}`)
return value
}
export const where: <A, NewA extends A = A>(
validator: Validator<A>,
predicate: (a: A) => boolean,
error?: TypeError,
) => Validator<NewA> = (validator, predicate, error) => input =>
{
const result = validator(input)
if (result instanceof TypeError)
return result
if (! predicate(result))
return error ?? new TypeError(`Value did not satisfy predicate, got ${JSON.stringify(result)}`)
return result as any
}
export function lazy<V extends Validator<any>>(vg: () => V): V
{
return (input => vg()(input)) as V
}