改善版2024)kotlin でパーサーコンビネータを実装する HtmlWriter の導入で書いたコードを JavaScript に書きかえたのでその覚えがきです。
ことの成り行き上、 HtmlWriter という名称を使ってはいますが、今のところは別に HTML に変換するわけでもないです。 だったら Writer とかいう名称にすればよかったのですが。 ただ、対象文字列をパースしたあとHTML変換する、というユーケースも当然あるので、 それを想定した場合は、HtmlWriter という名称は適切なので、とりあえずこのままで。
$ node --version
v20.18.0
// 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/ にマッチするだけなので最後の ! はパースされずに残ります。