[javascript]パーサ・コンビネータって

Java パーサコンビネータ 超入門というのを見つけた。

使い方を説明してくれるのでとても助かるけど、

JavaScriptでパーサコンビネータのコンセプトを理解する(「正規表現だけに頼ってはいけない」の続き)

の方がパーサ・コンビネータの仕組みを理解でき、且つ、使わなくなったけどJavascriptの便利な手法を思い出させてくれた。

function 関数(...) {
  return [1,2,3,4, ...];
}

と配列等で容易に複数の値を変えすことができるし、そんな関数を

let [a, b, c, d, ...] = 関数(...)

と呼び出すと、すんなり配列中の a, b, c, d, … に値を入れてくれるトコが地味に嬉しい。

いつのまにかいっぱい返す事になる未来が待っていそうな気がするから new classを返すことが多いけど。

また

function token(str) {
  var len = str.length;
  return function(target, position) {
    if (target.substr(position, len) === str) {
      return [true, str, position + len];
    } else {
      return [false, null, position];
    }
  };
}

の様にreturn function {…} することで、関数ジェネレータを簡易に記述できるのは便利、

但し、呼び出す側が、

token('foobar')('foobar', 0); // => [true, 'foobar', 6]を返す

な感じになってしまうとワークスペース内のファイルの関数を全部チェックする開発環境ではなく、

sakuraエディタで チョコ チョコ叩いている場合は、【見た目文法エラー!】なのが難点。

やはり、

const func = token('foobar');
func('foobar', 0); // => [true, 'foobar', 6]を返す

と書いて関数が戻ってくる雰囲気を醸し出した方が無難な気がする。

many(token('hoge'))('hogehoge', 0); // => [true, ['hoge', 'hoge'], 8] を返す

も、token(‘hoge’)部分がmanyな場合は、あーもーな感じになりそうなんで

let parsers = [];
parsers.push(token('hoge1'));
...
parsers.push(token('hogeN'));
let text = 'hoge1hoge2...hogeN';
let myParser =seq(parsers);
myParser(text, 0); // => [true, ['hoge1', ['hoge2', ...['hogeN']...]]], 8] を返す

と1行がいっぱい膨れ上がってしまいそう。

てか、実際に’hoge1hoge2’と直書きすることは滅多に無いから

もっと、もっと膨れ上がる。

先の記事のソースは「あくまで説明しやすく表記」したものなのは明白だけど、

テストコードってこんな風に書いてたりするよね?。(笑

ps.

先の「理解する」のページのコードをまとめてみた。

簡易のテストケースもパーサーの種類ごとに1つダケ書いてみたら、結構長くなってしまった。

作りかけだからFalseしまくってるのはいいとして

Errorオブジェクトから発生原因を判りやすくしてほしいなぁ。

字句解析とか構文解析は、「,」1つの修正だけでも

見えないところがトンデモない結果になり果ててしまいがちなので、テストはガチでやった方がいいからね。

ソースの説明

  • index.html   テスト画面
  • parcom.js パーサの記事からコードをコピペしてチョコっと加工したもの
  • sample.js パーサのテストパターンを起動し、結果を表示するスクリプト
  • testparcom.js  パーサのテストパターン
  • testbase.js    テストパターンを多少楽にするための testparcom.js の基底クラス
  • libs.js オブジェクトのダンプやErrorオブジェクトから情報を引き出したりするD級パーツ群
  • sample.css テスト画面用CSS

※ん-。まだまだな感じがする。

そもそも、各パーサはサブクラス化した方が良いんじゃにのかな?

更にパーサ コンテナ クラスで括った方が・・・。

アカン。どんどん膨れ上がる一方だ。

ps.2021/12/3

BNFっぽい文法”Call = [ Call ] procedureName [ (argumentList) ] ”と書かれたテキストを

解析するコードを作成中。やっと単純な文法をクリアしたものの、2つ目の文法に進まない。

デバッグでハマりすぎ長いログをスクロールするのが大変。

  • 機能を大幅改善
    • ログ末尾のstartとendを検出しリストタグ(UL,OL,LIとか)で手軽にインデント
      • 以前見つけたTreeViewのコードは流用できた
      • FIXTED:cssは全部見直しするハメになった
      • TODO:try…catch…filannyで確実にendを出力させないと右雪崩が発生する
    • TreeView風に[+][ーで折りたためる
      • TODO: +、ー小さすぎた!
      • TODO: endの行に+を配置するのは良くない。
    • trueやfalseを色分け
      • FIXED: あるあるな後付けデグレードと既存バグ発覚
        • objDumpで処理済みのテキストを再度objDumpしてが気が付かなかった
        • replaceで単語にタグ付けする場合は二度漬け禁止のハズが・・・
          • <span> ⇒ &lt;span> 的な出来事
    • FOXED:ログが雪だまり式に増えていた
      • Case2にCase1のログが
      • Case3にCase2とCase1のログが
      • 以下、増殖
        • 各テストCaseでログクリアする
    • TODO:typeof xxx === “undefined”はしてるけど、xxx === null チェックが甘い

コードの見直しが多すぎて、メソッド単体テストが壊滅状態。

修正してみたら、半壊状態。←いまこのへん。

全文法(287行中7行)をクリアするのはまだまだ先の話。

本当に終わりが見えないなぁ(大笑

            //      syntax = [ rule ]  ;
            setSyntax("syntax", many(refSyntax("defsymbol")));
            // defsymbol: { "\n" } IDENTIFIER "=" rule
            //###setSyntax("defsymbol", seq(optEol,  identifire,  asign,  refSyntax("rule")));
            setSyntax("defsymbol", seq(optEolWs, identifire, asign, refSyntax("rule")));
            // rule: list { "|" list }
            setSyntax("rule", seq(refSyntax("list"), repeat(seq(vl, refSyntax("list")))));
            // list: { seq | block | repeat | option }  "\n" { notes }
            // TODO: repeat(...)の中にoption系が挟まると無限ループするので、何か対策案を考える
            setSyntax("list", seq(repeat(choice(refSyntax("seqList"), refSyntax("block"), refSyntax("repeat"), refSyntax("option"))), eol, opt(refSyntax("notes"))));
            // seqList = seq { seqList }
            setSyntax("seqList", seq(refSyntax("seq"), repeat(seq(/*ws,*/ refSyntax("seqList")))));
            // seq = (regExp | IDENTIFIER | STRING | INTEGER | , )
            setSyntax("seq", choice(reExFmt, identifire, string, number, comma));
            // block: "(" rule ")"
            setSyntax("block", seq(lpr, refSyntax("rule"), rpr));
            // repeat: "{" rule "}"
            setSyntax("repeat", seq(lbr, refSyntax("rule"), rbr));
            // option: "[" rule "]"
            setSyntax("option", seq(lbk, refSyntax("rule"), rbk));
            // notes: "#Notes" "Syntax" eol rule "}}" "#Notes" "End" eol
            setSyntax("notes", seq(notes, ws, syntax, eol, refSyntax("syntax"), notes, ws, end, eol));

見直すのはコレだけなのに。

  • ログの大半がホワイトスペースだから何とかしたい
    • パーサから空白を読み飛ばす字句解析結果のを参照する?
    • パーサで作成したtoken(文字と正規表現)を適切な順に並べる
      • 勝手に空白を読み飛ばすので楽なハズ

クリアしたら、

  • 残りのテストを・・・
  • パーサコンテナを作る
  • 各パーサをサブクラス化
  • コードを読む部分を作る
  • 何かの言語に変換する部分を作る

先はまだまだ長い。

ps.2021/12/6

サブクラス化したもののソースは1000行を越えた。何か間違えている様だ。

サブクラス化 の過程で

  • リテラルにはプロパティが付けられない
    • String(“hohegoge”)ではダメ。
    • new String(“hohegoge”) でOK.
    • その辺は throw Error(“hogehoge”);も同じなんだろう。
  • RegExpにgオプションを付けlastIndexプロパティに位置を指定しても文字列の最初から検索する
    • 一度素振り(検索)させ自力でlastIndexを初期化しないとダメ
    • gは空振り(null)すると、とても機嫌が悪い。
  • RegExpのinputプロパティにオリジナルの文字列のコピーが作られる
    • メモリが心配
  • Refer.call を使ってみた
    • 横に長くなる
      • 伸びた部分だけ見れば良いので見やすいと思う人もいるかも
      • 元に戻す
  • super.メソッドみたいな呼び方ができる
    • なんちゃってオーバーライド風に書いてみると、無限ループする

18行1列まで読めた。 行末の _ 結合演算子がムズイ。

ps.2021/12/7

事前にザックリと空白、改行、その他でトークン化したついでにコメント文を空白化。後は _ かな。

全文からワード単位で字句解析するようになってブラウザの動作は軽くなった気がする。

ワード単位で取り出す時に、文末が” _”なら、次の改行を無視すればいいのかな?

ps.2021/12/8

手直しをしたら余計悪化。

0回ループOKなRepeatがネストしたら無限ループしてしまうので、Sequenceで評価がTrueでもpositionが進まない時は、評価をFalseにしたけど、まだループ。

仕方が無いので、catch exceptionで強制停止。300万行とか凄い行数になってたので、1000行までに制限。

やっと途中経過が可視化できた。ログのネストを深く掘り下げると

ParserInfo (ParserInfo)で、字句解析まで見れるけど。文法解析まで見れない。

ps.2021/12/9

手直し。 ダンプの仕方を見直し。

もう、本題を忘れてダンプに力が入る。

ps.2021/12/11

手直。 ダンプの仕方を見直し。  TreeView.markingをPromiseってみた。設定できてるけど、処理が終わったと返ってこない。

ps.2021/12/13

関数のダンプが修正漏れでエラっていたを訂正。




コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA