Home About Contact
JavaScript , Parser Combinator

「改善版2024)kotlin でパーサーコンビネータを実装する HtmlWriter の導入」のコードを Vanilla JS に書きかえ

改善版2024)kotlin でパーサーコンビネータを実装する HtmlWriter の導入で書いたコードを JavaScript に書きかえたのでその覚えがきです。

ことの成り行き上、 HtmlWriter という名称を使ってはいますが、今のところは別に HTML に変換するわけでもないです。 だったら Writer とかいう名称にすればよかったのですが。 ただ、対象文字列をパースしたあとHTML変換する、というユーケースも当然あるので、 それを想定した場合は、HtmlWriter という名称は適切なので、とりあえずこのままで。

環境

$ node --version
v20.18.0

HtmlWriter と letter, zeroOrMore パーサーを実装

// index.js

const stringToArray = (text) => { return Array.from(text) }
const head = (text) => { return Array.from(text)[0] }
const tail = (text) => { return Array.from(text).slice(1).join("") }

const blockNothing = { kind: 'Nothing', value: null }
const blockJust = (value)=> {
    return { kind: 'Just', value: value}
}

const parseResult = (ok, xs)=> {
    return {ok: ok, xs: xs}
}

const htmlWriter = (cs, r)=> {
    const appendResult = (r1, r2)=>{
        if( r1.ok && r2.ok ){
            return parseResult(true, r1.xs.concat(r2.xs))
        } else {
            return parseResult(false, r1.xs.concat(r2.xs))
        }
    }

    return {
        cs: cs,
        r: r,
        parse: (p)=> {
            const w = p(cs)
            return htmlWriter(w.cs, appendResult(r, w.r))
        }
    }
}

const ngHtmlWriter = (cs)=> {
    return htmlWriter(cs, parseResult(false, []))
}

const okHtmlWriter = (cs, xs)=> {
    return htmlWriter(cs, parseResult(true, xs))
}

const letter = (toHtmlBlock)=> {
    const re = /[a-zA-Z]/

    return (cs)=> {
        if( cs.length<1 ){
            return ngHtmlWriter(cs)
        } else {
            const c = head(cs)
            if( re.exec(c) ){
                return okHtmlWriter(tail(cs), [toHtmlBlock(c)])
            } else {
                return ngHtmlWriter(cs)
            }
        }
    }
}

const zeroOrMore = (parser0)=> {
    const f = (parser, cs, acc)=> {
        if( cs.length==0 ){
            return { first: cs, second: acc }
        } else {
            const w = parser(cs)
            if( w.r.ok ){
                return f(parser, w.cs, acc.concat(w.r.xs))
            } else {
                return { first: cs, second: acc }
            }
        }
    }

    return (cs0) => {
        const pair = f(parser0, cs0, [])
        return okHtmlWriter(pair.first, pair.second)
    }
}


const text = "HelloWorld!"

const p = zeroOrMore(letter(blockJust))

const charList = stringToArray(text)
const initParseResult = parseResult(true, [])
const result = htmlWriter(charList, initParseResult).parse( p )
console.log(result)

if( result.r.ok ){
    result.r.xs.forEach((item)=>{
        console.log(item)
    })
}

元の kotlin コードがないと、 このコードを見ただけでは内容を解読するのが難しい。 やはり TypeScript で書いてそれを javascript にするのが正解なのかもしれない。

とりあえず、実行する。

$ node index.js 
{
  cs: '!',
  r: {
    ok: true,
    xs: [
      [Object], [Object],
      [Object], [Object],
      [Object], [Object],
      [Object], [Object],
      [Object], [Object]
    ]
  },
  parse: [Function: parse]
}
{ kind: 'Just', value: 'H' }
{ kind: 'Just', value: 'e' }
{ kind: 'Just', value: 'l' }
{ kind: 'Just', value: 'l' }
{ kind: 'Just', value: 'o' }
{ kind: 'Just', value: 'W' }
{ kind: 'Just', value: 'o' }
{ kind: 'Just', value: 'r' }
{ kind: 'Just', value: 'l' }
{ kind: 'Just', value: 'd' }

letter が /a-zA-Z/ にマッチするだけなので最後の ! はパースされずに残ります。