その1のコードの HtmlBlock 部分の実装を差しかえられるように Generics にします。
$ deno -version
deno 1.44.1
文字関連 Moji のコードを moji.ts へ分離します。
// moji.ts
const stringToArray = (text: string): string[] => { return Array.from(text) }
const toMojiList = (text: string): Moji[] => {
return stringToArray(text).map((c: string)=> {
const m: Moji = { c: c }
return m
})
}
type Moji = {
c: string
}
export type { Moji }
export { toMojiList }
元のパーサーの定義:
type Parser = (ms: Moji[]) => HtmlWriter
type ParseResult = {
ok: boolean
xs: HtmlBlock[]
}
type HtmlWriter = {
ms: Moji[]
r: ParseResult
parse: (p: Parser) => HtmlWriter
}
パースした結果を入れる HtmlBlock 部分を T に置きかえます。
type Parser<T> = (ms: Moji[]) => HtmlWriter<T>
type ParseResult<T> = {
ok: boolean
xs: T[]
}
type HtmlWriter<T> = {
ms: Moji[]
r: ParseResult<T>
parse: (p: Parser<T>) => HtmlWriter<T>
}
htmlWriter 関数の実装:
const parseResult = <T>(ok: boolean, xs: T[]): ParseResult<T> => {
return {ok: ok, xs: xs}
}
const htmlWriter = <T>(ms: Moji[], r: ParseResult<T>): HtmlWriter<T> => {
const appendResult = (r1: ParseResult<T>, r2: ParseResult<T>)=> {
if( r1.ok && r2.ok ){
return parseResult(true, r1.xs.concat(r2.xs))
} else {
return parseResult(false, r1.xs.concat(r2.xs))
}
}
return {
ms: ms,
r: r,
parse: (p: Parser<T>): HtmlWriter<T> => {
const w = p(ms)
return htmlWriter<T>(w.ms, appendResult(r, w.r))
}
}
}
補助関数:
const ngHtmlWriter = <T>(ms: Moji[]): HtmlWriter<T> => {
return htmlWriter<T>(ms, parseResult(false, []))
}
const okHtmlWriter = <T>(ms: Moji[], xs: T[]): HtmlWriter<T> => {
return htmlWriter(ms, parseResult(true, xs))
}
type ToHtmlBlock は type ToSomething にします。
//type ToHtmlBlock = (m: Moji) => HtmlBlock
type ToSomething<T> = (m: Moji) => T
letter パーサー:
const letter = <T>(toSomething: ToSomething<T>): Parser<T>=> {
const re = /[a-zA-Z]/
const p = (ms: Moji[]): HtmlWriter<T> => {
if( ms.length<1 ){
return ngHtmlWriter(ms)
} else {
const m = head(ms)
if( re.exec(m.c) ){
return okHtmlWriter(tail(ms), [toSomething(m)])
} else {
return ngHtmlWriter(ms)
}
}
}
return p
}
zeroOrMore パーサー:
type Pair<T,R> = {
first: T
second: R
}
const zeroOrMore = <T>(parser0: Parser<T>): Parser<T>=> {
const f = (parser: Parser<T>, ms: Moji[], acc: T[]): Pair<Moji[], T[]> => {
if( ms.length==0 ){
return { first: ms, second: acc }
} else {
const w = parser(ms)
if( w.r.ok ){
return f(parser, w.ms, acc.concat(w.r.xs))
} else {
return { first: ms, second: acc }
}
}
}
return (ms0: Moji[]): HtmlWriter<T> => {
const pair = f(parser0, ms0, [])
return okHtmlWriter(pair.first, pair.second)
}
}
これらをまとめて parser.ts にします。
// parser.ts
import { Moji, toMojiList } from "./moji.ts"
const head = (ms: Moji[]): Moji => { return ms[0] }
const tail = (ms: Moji[]): Moji[] => { return ms.slice(1) }
type Parser<T> = (ms: Moji[]) => HtmlWriter<T>
type ParseResult<T> = {
ok: boolean
xs: T[]
}
type HtmlWriter<T> = {
ms: Moji[]
r: ParseResult<T>
parse: (p: Parser<T>) => HtmlWriter<T>
}
const parseResult = <T>(ok: boolean, xs: T[]): ParseResult<T> => {
return {ok: ok, xs: xs}
}
const htmlWriter = <T>(ms: Moji[], r: ParseResult<T>): HtmlWriter<T> => {
const appendResult = (r1: ParseResult<T>, r2: ParseResult<T>)=> {
if( r1.ok && r2.ok ){
return parseResult(true, r1.xs.concat(r2.xs))
} else {
return parseResult(false, r1.xs.concat(r2.xs))
}
}
return {
ms: ms,
r: r,
parse: (p: Parser<T>): HtmlWriter<T> => {
const w = p(ms)
return htmlWriter<T>(w.ms, appendResult(r, w.r))
}
}
}
const ngHtmlWriter = <T>(ms: Moji[]): HtmlWriter<T> => {
return htmlWriter<T>(ms, parseResult(false, []))
}
const okHtmlWriter = <T>(ms: Moji[], xs: T[]): HtmlWriter<T> => {
return htmlWriter(ms, parseResult(true, xs))
}
//type ToHtmlBlock = (m: Moji) => HtmlBlock
type ToSomething<T> = (m: Moji) => T
const letter = <T>(toSomething: ToSomething<T>): Parser<T>=> {
const re = /[a-zA-Z]/
const p = (ms: Moji[]): HtmlWriter<T> => {
if( ms.length<1 ){
return ngHtmlWriter(ms)
} else {
const m = head(ms)
if( re.exec(m.c) ){
return okHtmlWriter(tail(ms), [toSomething(m)])
} else {
return ngHtmlWriter(ms)
}
}
}
return p
}
type Pair<T,R> = {
first: T
second: R
}
const zeroOrMore = <T>(parser0: Parser<T>): Parser<T>=> {
const f = (parser: Parser<T>, ms: Moji[], acc: T[]): Pair<Moji[], T[]> => {
if( ms.length==0 ){
return { first: ms, second: acc }
} else {
const w = parser(ms)
if( w.r.ok ){
return f(parser, w.ms, acc.concat(w.r.xs))
} else {
return { first: ms, second: acc }
}
}
}
return (ms0: Moji[]): HtmlWriter<T> => {
const pair = f(parser0, ms0, [])
return okHtmlWriter(pair.first, pair.second)
}
}
export type { Moji, Parser, ParseResult, HtmlWriter, ToSomething }
export { toMojiList, parseResult, htmlWriter, letter, zeroOrMore }
最後に HtmlBlock の実装とパーサーの実装を main.ts に書きます。
// main.ts
import { Moji, toMojiList } from "./moji.ts"
import { Parser, ParseResult, HtmlWriter, ToSomething, parseResult, htmlWriter, letter, zeroOrMore } from "./parser.ts"
type JustHtmlBlock = { m: Moji }
type NothingHtmlBlock = {}
type HtmlBlock = JustHtmlBlock | NothingHtmlBlock
const text = "HelloWorld!"
const toJustHtmlBlock: ToSomething<HtmlBlock> = (m: Moji): HtmlBlock => {
return { m: m }
}
const p = zeroOrMore( letter<HtmlBlock>(toJustHtmlBlock) )
const mojiList = toMojiList(text)
const initParseResult = parseResult<HtmlBlock>(true, [])
const result = htmlWriter(mojiList, initParseResult).parse( p )
console.log(result)
実行:
$ deno run --check main.ts
{
ms: [ { c: "!" } ],
r: {
ok: true,
xs: [
{ m: { c: "H" } },
{ m: { c: "e" } },
{ m: { c: "l" } },
{ m: { c: "l" } },
{ m: { c: "o" } },
{ m: { c: "W" } },
{ m: { c: "o" } },
{ m: { c: "r" } },
{ m: { c: "l" } },
{ m: { c: "d" } }
]
},
parse: [Function: parse]
}
意図通り作動しました。
これでパーサーを Generics にできました。