Home About Contact
Parser Combinator , TypeScript , Deno

「改善版2024)Kotlin でパーサーコンビネータを実装する HtmlWriter の導入」のコードを TypeScript にする(その2)

その1のコードの HtmlBlock 部分の実装を差しかえられるように Generics にします。

環境

$ deno -version
deno 1.44.1

Generics 関係ない部分の切り離し

文字関連 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 }

パーサー定義を Generics にします

元のパーサーの定義:

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 ToHtmlBlocktype 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 にできました。