カラクリサイクル

『輝かしい青春』なんて無かった人の雑記

PEG.js で機械的に構文を定義したい時に ejs 使ったら便利だった

と言う話です。

話の前提とか理由

大体、

the.nyarla.net

という辺りの問題。

で、もうちょっと詳し書くと、今作っている nytra という軽量マークアップ言語 (markdown とかはてな記法とかの類い) では PEG.js を使って構文定義をしていて、 かつ、インラインの軽量マークアップ言語を定義する際に、

各構文が、自身の構文を除く、他のすべてのインライン構文を子要素として持つ

と言う定義を書かないと上手く動かなそうな感じが有りました。

それで、その辺りを上手く動かすために、上記の構文定義をしたないとなー、という感じだったんですが、 その構文定義を手作業で書こうとすると、どう考えてもミスが出そうだし、 あと同じ様な構文を各構文毎に定義するのが正直つらみしか無さそうだったので、

じゃ、テンプレート処理すれば良いじゃん

ってなって、

とりあえずサクっと何も考えずに使える ejs 使うかー

という意思決定をして、実際使ってみたら便利だった、というのが今回の話です。

今現在の処理の流れ

実際のパーザのコードを生成する流れとしては、

  1. ejs を含んだ PEG.js 用のコードを書く
  2. ejs のテンプレート処理をして PEG.js のコードを生成する
  3. 生成した PEG.js のコードから実際のパーザのコードを吐かせる

という感じになっています。

そして、下記が実際にパーザを生成する時に使ってるコードです:

'use strict';

const ejs   = require('ejs');
const peg   = require('pegjs');

const fs    = require('fs');
const path  = require('path');

"inline.pegjs.ejs block.pegjs.ejs".split(' ').forEach((fn) => {
  fs.readFile(path.join(__dirname, fn), (err, template) => {
    if ( err !== null ) {
      throw err;
    }

    var source = ejs.render(template.toString(), {}, {});
    var output = peg.generate(source, { output: "source", format: "commonjs" });

    fs.writeFile(path.join(__dirname, "..", "dist", fn.replace(".pegjs.ejs", ".js")), output, (err) => {
      if ( err !== null ) {
        throw err;
      }
    });
  });
});

なんか割とザツいですね。

ejs + PEG.js + HAST を組み合わせると、軽量マークアップ言語を作るの凄く楽

今現在、 nytra のコードは、

の三つを組み合わせつつ書いているのですが、HAST とその周辺ツールのおかげで、 Abstract Syntax Tree を自前で用意しなくて済んでいるし、あと、 HAST から HTML に変換するのも HAST の周辺ツールを使うだけで生成できちゃってるので、 そう言った意味合いでは、

  • ejs + PEG.js + HAST

という組み合わせを見付けられた、というのは、今回凄く良かったかなーとか思っています。はい。

以上

なんか今日は割とザツに記事書いてますが、大体そんな感じでした。はい。