Skip to content

Instantly share code, notes, and snippets.

@s-shin
Created September 15, 2018 07:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save s-shin/b5e0fd8ac63bcceb61c5846c89814112 to your computer and use it in GitHub Desktop.
Save s-shin/b5e0fd8ac63bcceb61c5846c89814112 to your computer and use it in GitHub Desktop.
// MIT License
//------------------------------------------------------------------------------
// Readers
//------------------------------------------------------------------------------
export interface Cursor {
line: number;
column: number;
}
export interface Reader {
clone(): Reader;
readChar(): string;
getCurrentCursor(): Cursor;
}
export class StringReader implements Reader {
private index = 0;
constructor(private text: string) {}
clone() {
const r = new StringReader(this.text);
r.index = this.index;
return r;
}
readChar() {
const s = this.text[this.index++];
if (this.index > this.text.length) {
this.index = this.text.length;
}
return s;
}
getCurrentCursor() {
const c = { line: 1, column: 0 };
for (let i = 0; i < this.index; i++) {
if (this.text[i] === "\n") {
c.line++;
c.column = 0;
} else {
c.column++;
}
}
return c;
}
}
//------------------------------------------------------------------------------
// Parsers
//------------------------------------------------------------------------------
export interface ParseResult<Value> {
reader: Reader;
value?: Value;
error?: Error;
}
export type Parser<Value> = (reader: Reader) => ParseResult<Value>;
//---
//! [Parser Generator] Parse single UTF-16 character.
export const char = (c: string, opts = { invert: false }): Parser<string> => reader => {
if (c.length !== 1) {
throw new Error("'c' is invalid length");
}
const rd = reader.clone();
const rc = rd.readChar();
if (opts.invert ? c === rc : c !== rc) {
return { reader, error: new Error("not matched") };
}
return { reader: rd, value: rc };
};
//---
/*
for i in {2..10}; do
vs=""
for j in $(seq 1 $i); do
if (($j > 1)); then vs+=", "; fi
vs+="V${j}"
done
ps=""
for j in $(seq 1 $i); do
if (($j > 1)); then ps+=", "; fi
ps+="p${j}: Parser<V${j}>"
done
rvs=""
for j in $(seq 1 $i); do
if (($j > 1)); then rvs+=" | "; fi
rvs+="V${j}"
done
echo " <${vs}>(${ps}): Parser<${rvs}>"
done
*/
// prettier-ignore
export interface OneOf {
<Value>(...ps: Parser<Value>[]): Parser<Value>;
<V1, V2>(p1: Parser<V1>, p2: Parser<V2>): Parser<V1 | V2>
<V1, V2, V3>(p1: Parser<V1>, p2: Parser<V2>, p3: Parser<V3>): Parser<V1 | V2 | V3>
<V1, V2, V3, V4>(p1: Parser<V1>, p2: Parser<V2>, p3: Parser<V3>, p4: Parser<V4>): Parser<V1 | V2 | V3 | V4>
<V1, V2, V3, V4, V5>(p1: Parser<V1>, p2: Parser<V2>, p3: Parser<V3>, p4: Parser<V4>, p5: Parser<V5>): Parser<V1 | V2 | V3 | V4 | V5>
<V1, V2, V3, V4, V5, V6>(p1: Parser<V1>, p2: Parser<V2>, p3: Parser<V3>, p4: Parser<V4>, p5: Parser<V5>, p6: Parser<V6>): Parser<V1 | V2 | V3 | V4 | V5 | V6>
<V1, V2, V3, V4, V5, V6, V7>(p1: Parser<V1>, p2: Parser<V2>, p3: Parser<V3>, p4: Parser<V4>, p5: Parser<V5>, p6: Parser<V6>, p7: Parser<V7>): Parser<V1 | V2 | V3 | V4 | V5 | V6 | V7>
<V1, V2, V3, V4, V5, V6, V7, V8>(p1: Parser<V1>, p2: Parser<V2>, p3: Parser<V3>, p4: Parser<V4>, p5: Parser<V5>, p6: Parser<V6>, p7: Parser<V7>, p8: Parser<V8>): Parser<V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8>
<V1, V2, V3, V4, V5, V6, V7, V8, V9>(p1: Parser<V1>, p2: Parser<V2>, p3: Parser<V3>, p4: Parser<V4>, p5: Parser<V5>, p6: Parser<V6>, p7: Parser<V7>, p8: Parser<V8>, p9: Parser<V9>): Parser<V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9>
<V1, V2, V3, V4, V5, V6, V7, V8, V9, V10>(p1: Parser<V1>, p2: Parser<V2>, p3: Parser<V3>, p4: Parser<V4>, p5: Parser<V5>, p6: Parser<V6>, p7: Parser<V7>, p8: Parser<V8>, p9: Parser<V9>, p10: Parser<V10>): Parser<V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | V10>
}
const _oneOf = (...ps: Parser<any>[]): Parser<any> => reader => {
for (const p of ps) {
const r = p(reader);
if (r.error) {
return { reader, error: r.error };
}
return r;
}
return { reader, error: new Error("not matched") };
};
//! [Parser Combinator]
export const oneOf: OneOf = _oneOf;
//---
/*
for i in {1..10}; do
vs=""
for j in $(seq 1 $i); do
if (($j > 1)); then vs+=", "; fi
vs+="V${j}"
done
ps=""
for j in $(seq 1 $i); do
if (($j > 1)); then ps+=", "; fi
ps+="p${j}: Parser<V${j}>"
done
echo " <${vs}>(${ps}): Parser<[${vs}]>"
done
*/
// prettier-ignore
export interface Seq {
<Value>(...ps: Parser<Value>[]): Parser<Value[]>;
<V1, V2>(p1: Parser<V1>, p2: Parser<V2>): Parser<[V1, V2]>
<V1, V2>(p1: Parser<V1>, p2: Parser<V2>): Parser<[V1, V2]>
<V1, V2, V3>(p1: Parser<V1>, p2: Parser<V2>, p3: Parser<V3>): Parser<[V1, V2, V3]>
<V1, V2, V3, V4>(p1: Parser<V1>, p2: Parser<V2>, p3: Parser<V3>, p4: Parser<V4>): Parser<[V1, V2, V3, V4]>
<V1, V2, V3, V4, V5>(p1: Parser<V1>, p2: Parser<V2>, p3: Parser<V3>, p4: Parser<V4>, p5: Parser<V5>): Parser<[V1, V2, V3, V4, V5]>
<V1, V2, V3, V4, V5, V6>(p1: Parser<V1>, p2: Parser<V2>, p3: Parser<V3>, p4: Parser<V4>, p5: Parser<V5>, p6: Parser<V6>): Parser<[V1, V2, V3, V4, V5, V6]>
<V1, V2, V3, V4, V5, V6, V7>(p1: Parser<V1>, p2: Parser<V2>, p3: Parser<V3>, p4: Parser<V4>, p5: Parser<V5>, p6: Parser<V6>, p7: Parser<V7>): Parser<[V1, V2, V3, V4, V5, V6, V7]>
<V1, V2, V3, V4, V5, V6, V7, V8>(p1: Parser<V1>, p2: Parser<V2>, p3: Parser<V3>, p4: Parser<V4>, p5: Parser<V5>, p6: Parser<V6>, p7: Parser<V7>, p8: Parser<V8>): Parser<[V1, V2, V3, V4, V5, V6, V7, V8]>
<V1, V2, V3, V4, V5, V6, V7, V8, V9>(p1: Parser<V1>, p2: Parser<V2>, p3: Parser<V3>, p4: Parser<V4>, p5: Parser<V5>, p6: Parser<V6>, p7: Parser<V7>, p8: Parser<V8>, p9: Parser<V9>): Parser<[V1, V2, V3, V4, V5, V6, V7, V8, V9]>
<V1, V2, V3, V4, V5, V6, V7, V8, V9, V10>(p1: Parser<V1>, p2: Parser<V2>, p3: Parser<V3>, p4: Parser<V4>, p5: Parser<V5>, p6: Parser<V6>, p7: Parser<V7>, p8: Parser<V8>, p9: Parser<V9>, p10: Parser<V10>): Parser<[V1, V2, V3, V4, V5, V6, V7, V8, V9, V10]>
}
const _seq = (...ps: Parser<any>[]): Parser<any[]> => reader => {
const values = Array<any>();
let rd = reader;
for (const p of ps) {
const r = p(rd);
if (r.error) {
return { reader, error: r.error };
}
values.push(r.value);
rd = r.reader;
}
return { reader: rd, value: values };
};
//! [Parser Combinator]
export const seq = _seq as Seq;
//---
//! [Parser Generator]
export const charIn = (cs: string, opts = { invert: false }): Parser<string> => reader => {
// slow oneline: oneOf(...cs.split("").map(c => char(c, opts)))(reader);
const rd = reader.clone();
const c = rd.readChar();
const idx = cs.indexOf(c);
if (opts.invert ? idx >= 0 : idx === -1) {
return { reader, error: new Error("not matched") };
}
return { reader: rd, value: c };
};
//! [Parser Generator]
export const charRange = (start: string, end: string, opts = { invert: false }): Parser<string> => reader => {
const rd = reader.clone();
const c = rd.readChar();
const cc = c.charCodeAt(0);
const included = start.charCodeAt(0) <= cc && cc <= end.charCodeAt(0);
if (opts.invert ? included : !included) {
return { reader, error: new Error("not matched") };
}
return { reader: rd, value: c };
};
//! [Parser Generator] Parse string (sequence of UTF-16 characters).
export const string = (s: string): Parser<string> => reader => join(seq(...s.split("").map(c => char(c))))(reader);
//---
export const DEFAULT_MANY_OPTIONS: {
min?: number;
max?: number;
} = { min: 0, max: 1024 * 1024 };
//! [Parser Combinator]
export const many = <Value>(p: Parser<Value>, opts = DEFAULT_MANY_OPTIONS): Parser<Value[]> => reader => {
opts.min = opts.min || 0;
opts.max = opts.max || 1024 * 1024;
const values = Array<Value>();
let rd = reader;
let i = 0;
while (i < opts.max) {
const r = p(rd);
if (r.error) {
break;
}
values.push(r.value!);
rd = r.reader;
i++;
}
if (i < opts.min) {
return { reader, error: new Error("against min option") };
}
return { reader: rd, value: values };
};
//---
//! Wrap a parser and transform the result value.
interface Transformer<V1, V2> {
(p: Parser<V1>): Parser<V2>;
}
//! [Transformer Generator]
export const transform = <V1, V2>(fn: (v: V1) => V2): Transformer<V1, V2> => p => reader => {
const r = p(reader);
if (r.error) {
return { reader, error: r.error };
}
return { reader: r.reader, value: fn(r.value!) };
};
//! [Transformer Generator]
export const reduce = <V, RV = V>(fn: (v: V[]) => RV): Transformer<V[], RV> => p => reader => transform(fn)(p)(reader);
//! [Transformer]
export const join: Transformer<string[], string> = p => reader =>
transform<string[], string>(ss => ss.join(""))(p)(reader);
//---
export const factory = <V>(fn: (self: Parser<V>) => Parser<V>) => {
let p: Parser<V>;
const pp: Parser<V> = reader => p(reader);
p = fn(pp);
return pp;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment