src/index.ts5.2 KB · typescript
export type Parser<T> = (input: string, pos: number) => Result<[T, number], ParseError>;
export type Result<T, E> = { ok: true; value: T } | { ok: false; error: E };
export type ParseError = { pos: number; message: string };
export const ok = <T>(value: T): Result<T, never> => ({ ok: true, value });
export const err = <E>(error: E): Result<never, E> => ({ ok: false, error });
export const tag = (str: string): Parser<string> => (input, pos) => {
if (input.startsWith(str, pos)) {
return ok([str, pos + str.length]);
}
return err({ pos, message: `Expected '${str}'` });
};
export const sequence = <T extends any[]>(...parsers: { [K in keyof T]: Parser<T[K]> }): Parser<T> => (input, pos) => {
const results = [] as unknown as T;
let currentPos = pos;
for (const parser of parsers) {
const result = parser(input, currentPos);
if (!result.ok) {
return result as any;
}
results.push(result.value[0]);
currentPos = result.value[1];
}
return ok([results, currentPos]);
};
export const alt = <T>(...parsers: Parser<T>[]): Parser<T> => (input, pos) => {
for (const parser of parsers) {
const result = parser(input, pos);
if (result.ok) {
return result;
}
}
return err({ pos, message: "All alternatives failed" });
};
export const map = <T, U>(parser: Parser<T>, fn: (val: T) => U): Parser<U> => (input, pos) => {
const result = parser(input, pos);
if (!result.ok) return result as any;
const [val, nextPos] = result.value;
return ok([fn(val), nextPos]);
};
export const many0 = <T>(parser: Parser<T>): Parser<T[]> => (input, pos) => {
const results: T[] = [];
let currentPos = pos;
while (true) {
const result = parser(input, currentPos);
if (!result.ok) {
break;
}
results.push(result.value[0]);
currentPos = result.value[1];
}
return ok([results, currentPos]);
};
export const takeWhile = (predicate: (char: string) => boolean): Parser<string> => (input, pos) => {
let currentPos = pos;
while (currentPos < input.length && predicate(input[currentPos])) {
currentPos++;
}
return ok([input.slice(pos, currentPos), currentPos]);
};
export const takeWhile1 = (predicate: (char: string) => boolean): Parser<string> => (input, pos) => {
if (pos >= input.length || !predicate(input[pos])) {
return err({ pos, message: "Predicate failed on first character" });
}
let currentPos = pos + 1;
while (currentPos < input.length && predicate(input[currentPos])) {
currentPos++;
}
return ok([input.slice(pos, currentPos), currentPos]);
};
export const digit1 = takeWhile1(c => c >= '0' && c <= '9');
export const alpha1 = takeWhile1(c => (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'));
export const multispace0 = takeWhile(c => c === ' ' || c === '\t' || c === '\n' || c === '\r');
export const optional = <T>(parser: Parser<T>): Parser<T | null> => (input, pos) => {
const result = parser(input, pos);
if (result.ok) {
return result;
}
return ok([null, pos]);
};
export const oneOf = (chars: string): Parser<string> => (input, pos) => {
if (pos < input.length && chars.includes(input[pos])) {
return ok([input[pos], pos + 1]);
}
return err({ pos, message: `Expected one of '${chars}'` });
};
export const noneOf = (chars: string): Parser<string> => (input, pos) => {
if (pos < input.length && !chars.includes(input[pos])) {
return ok([input[pos], pos + 1]);
}
return err({ pos, message: `Expected none of '${chars}'` });
};
export const separatedList0 = <T, S>(parser: Parser<T>, separator: Parser<S>): Parser<T[]> => (input, pos) => {
const first = parser(input, pos);
if (!first.ok) {
return ok([[], pos]);
}
const results = [first.value[0]];
let currentPos = first.value[1];
while (true) {
const sep = separator(input, currentPos);
if (!sep.ok) break;
const next = parser(input, sep.value[1]);
if (!next.ok) break;
results.push(next.value[0]);
currentPos = next.value[1];
}
return ok([results, currentPos]);
};
export const delimited = <I, T, O>(start: Parser<I>, parser: Parser<T>, end: Parser<O>): Parser<T> => (input, pos) => {
const s = start(input, pos);
if (!s.ok) return s as any;
const p = parser(input, s.value[1]);
if (!p.ok) return p as any;
const e = end(input, p.value[1]);
if (!e.ok) return e as any;
return ok([p.value[0], e.value[1]]);
};
export const preceded = <I, T>(prefix: Parser<I>, parser: Parser<T>): Parser<T> => (input, pos) => {
const pre = prefix(input, pos);
if (!pre.ok) return pre as any;
return parser(input, pre.value[1]);
};
export const terminated = <T, O>(parser: Parser<T>, suffix: Parser<O>): Parser<T> => (input, pos) => {
const p = parser(input, pos);
if (!p.ok) return p;
const suf = suffix(input, p.value[1]);
if (!suf.ok) return suf as any;
return ok([p.value[0], suf.value[1]]);
};
export const eof: Parser<null> = (input, pos) => {
if (pos >= input.length) {
return ok([null, pos]);
}
return err({ pos, message: "Expected EOF" });
};
export const peek = <T>(parser: Parser<T>): Parser<T> => (input, pos) => {
const result = parser(input, pos);
if (result.ok) {
return ok([result.value[0], pos]); // Return value but keep original pos
}
return result;
};