Created
September 15, 2018 07:43
-
-
Save s-shin/b5e0fd8ac63bcceb61c5846c89814112 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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