Typescriptで作成したパーサコンビネータを使ってJSONをパースしてみる

以前作成したTypescriptでのパーサコンビネータを利用してJSONをパースしてみたいと思います。

まずJSONの構文定義を確認してみます。確認したところarrayはelementsを含み,elementsはvalueを含みvalueはarrayを含むといった再帰的な構造を扱える必要があることがわかりますので以前作成したor-parserを修正してパーザ初期化後に候補のパーザを足せるようにする事により、valueパーザを使用するarrayパーザを初期化→arrayパーザ自体をvalueパーザの候補に追加とする事で再帰的に処理できるようにします。以前のor-parserは以下のように修正します。

import {Parser} from './parser'
import {ParseResult, ParseSuccess, ParseFailer} from './parser-result'

export class OrParser implements Parser<[Array<any>]> {
    psArray: Array<Parser<any>> = new Array()
    constructor(psArray: Array<Parser<any>>) {
        this.psArray = psArray
    }
    addParser(parser: Parser<any>): void {
        this.psArray.push(parser)
    }

    parse(input: string) :ParseResult<any> {
        const results: Array<ParseResult<any>> = new Array()
        for(const i in this.psArray) {
            const psresult = this.psArray[i].parse(input)
            if(psresult instanceof ParseSuccess) {
                return new ParseSuccess<any>(this, psresult, psresult.value, psresult.next);    
            } else {
                results.push(psresult)
            }
        }
        return new ParseFailer<any>(this, results, null,input);
    }
}

addParserにより候補パーザを後から追加できるようにしています。

valueをパースできるようにする

JSONの構文定義からvalueの部分のパーザを作成します。

const dqParer = P.string('"')
const dqStop = P.stop(new Array('"'))
const wordParse = P.triplet(dqParer, dqStop, dqParer)

const intParse =  P.plus(P.or(arryToParsers(Array("1","2","3","4","5","6","7","8","9","0"))))
const floatingParse = P.seq(intParse, P.option(P.seq(P.string("."),intParse)))
const numParse = P.seq(P.option(P.string("-")), floatingParse)

const nullParse = P.string("null")
const trueParse = P.string("true")
const falseParse = P.string("false")

const valueParser = P.or([wordParse, numParse, nullParse, trueParse, falseParse])

objectとarrayは後から追加するようにします。

elementsをパースできるようにする

const spSeq = P.continues(P.string(" "))
const cmParse = P.string(",")
const elementsParser = P.seq(valueParser, P.continues(P.seq(P.triplet(spSeq, cmParse, spSeq), valueParser)))

elementsの定義は value , elements となっており自分自身を不空ようにしていますが, elementsの部分は, valueの連続と同じなのでこのように実装します。

arrayをパースできるようにする

const aoParser = P.triplet(spSeq, P.string("["), spSeq)
const acParser = P.triplet(spSeq, P.string("]"), spSeq)
const arrayParser = P.triplet(aoParser, P.option(elementsParser), acParser)
valueParser.addParser(arrayParser)

arrayのパースを作成した後,valueParserに追加するようにしています。

membersをパースできるようにする

const coParer = P.string(":")
const pairParser = P.seq(P.seq(P.triplet(spSeq, wordParse, spSeq), coParer), P.triplet(spSeq, valueParser, spSeq))
const membersParser = P.seq(pairParser, P.continues(P.seq(P.triplet(spSeq, cmParse, spSeq), pairParser)))

objectをパースできるようにする

const objParser = P.triplet(P.triplet(spSeq, P.string("{"), spSeq), P.option(membersParser), P.triplet(spSeq, P.string("}"), spSeq))
valueParser.addParser(objParser)

objectのパースを作成した後,valueParserに追加するようにしています。

jsonをパースできるようにする

const jsonParser = P.seq(valueParser, P.seq(spSeq, P.end()))

valueParserでパース後に残りの文字が無くなっていたらパース完了としています。

それでは動きを見てみます。

const str ='[1,2,{"1":[1,2,"a"]},"3", [ -5, [{"a": "b"}] ] ]'
const result  = jsonParser.parse(str.replace("\r","").replace("\n",""))
console.info(result)

ParseSuccess {parser: SeqParser, childResult: Array(2), value: Array(2), next: ""}

成功しているようです。あとは結果の値を扱いやすい形に変換したら良さそう。