皆さん、DSLを作ることってありますか? 複雑な設定が可能な社内ツールを作るとき、 「DSLを許容したら自由度が上がって素敵では?」 と思うこと、ありますよね。 私個人としては、エンジニア向けのインタフェースとして、DSLで社内ツールを作ると、作る当時は楽しいものの、複雑なことを許容する分保守性に問題がでてくるデメリットも有り、近年は設定はyamlで書ける範囲にすることが多いです。
一方で、非エンジニア向けに、ロジックをビジュアルなDSLで提供できたら良いなってこともありませんか。 例えばjoinとfilterのみに絞ったデータ集計ツールを作りたい、行動Aをしたあとに行動Bをした一部のユーザーにキャンペーンメールを送りたい、などのロジックを安全にかつ柔軟に提供したいというシーンです。 ビジュアルで書けるって範囲にすれば、複雑度も一定の範囲になるのでありかなと思ってます。 こういう、ロジックを非エンジニアが書けるツールというのは需要があるものの、そこの作り込みに時間をかけると無限に時間が溶けていきますよね。
そこで今回はビジュアルプログラミングのためのフレームワークBlocklyを使ってUI部分を考えないですむビジュアルDSLに入門してみます。
正規表現をビジュアルに組み立てたい
改めましてこんにちは、CTOの大垣です。 この記事はエムスリーアドベントカレンダー2025の23日目の記事です。 前日は髙橋さんによるNVFP4: 4ビットの浮動小数点でLLMを学習する仕組みでした。LLMには皆さんお世話になってるでしょうし、小型化・高速化は喫緊の課題ですよね!
さて、普段の仕事で正規表現書いている時、あれ、これでパターン網羅してるんだっけ、とか、ここ一部だけ除外するのどういう記法だったっけ?とかってなることありますよね。最近はLLMに頼って書くことも多いですが、やはり簡単に書けるに越したことはありません。ということで、ビジュアルDSLの例題として正規表現エディタを作ってみました!
早速、完成したアプリケーション
静的なページなので、以下にiframe埋め込み版も用意しました。ぜひ遊んでください!
UIをパースして正規表現を作る
当然Blocklyが提供しているのはUI部分だけなので、そこから実際にやりたいこと、今回の場合だと正規表現、に落とすのは各自で実装していく必要があります。 ただし、BlocklyのUIは裏側ではグラフとして表現されるので、想像以上にシンプルに書けます。 つまり、文字列でDSLを作るのと違って、構文木を作る部分がまるっとスキップできるってことです。
実際のgenerateRegexFromBlock関数の一部を以下に示します。
export function generateRegexFromBlock(block) { if (block.type === 'regex_or') { // 左側のパターンを生成 let leftPattern = '' let leftBlock = block.getInputTargetBlock('LEFT') while (leftBlock) { leftPattern += generateRegexFromBlock(leftBlock) leftBlock = leftBlock.getNextBlock() } // 右側のパターンを生成 let rightPattern = '' let rightBlock = block.getInputTargetBlock('RIGHT') while (rightBlock) { rightPattern += generateRegexFromBlock(rightBlock) rightBlock = rightBlock.getNextBlock() } return `${leftPattern}|${rightPattern}` } // if block_type=....が続く }
既に木になってるので、木のトラバースとして、このように、教科書的な再起関数を一つ書くだけで終了です。
という形で簡単なので、想定通り負債化も防ぎやすいかなという所感です。 マーケティングオートメーションとか、社内データ抽出ツール向けに社内独自DSLを作るのにもとても役立ちそうです。

UIで文法違反を防ぐ
ビジュアルプログラミングであることの嬉しさは、見た目で何やってるかわかる、ってのはもちろんですが、個人的に重要だと思うのは、 文字列と違って、文法違反をできなくなる制約が作りやすいことだと考えます。 型の視覚的表現とも言えます。
文法チェックの手段としては2通りあり、
- UIの形状で拒否する
- UIの動きとして拒否する
ということが出来ます。
UIの形状で拒否する
これはまさにビジュアルプログラミングの真骨頂です

この例のように、今回正規表現の基本形を、パターン+量指定子のリスト、と捉えました。 その考えをビジュアルに落とすと、ブロックが縦に接続されるのがパターンの連続、横に接続するのが量指定子、とすればうまく行きそうです。 以下にBlocklyでの設定を解説します。
パターンの例: 数字文字(\d)の接続定義

今回の正規表現エディタの一番オーソドックスな形です。 ピースの接続位置が3つありますね。input_value, previousStatement, nextStatementの3つを定義した場合にこのピースの形になります。

量指定子の例: 0回以上(*)の接続定義


これだけです。つまり、outputを誰かにつなぐことしか出来ない、previousStatementもnextStatementもない、という定義をするとこの形になるわけですね。 output: nullというのは、後で説明しますが、型の指定がない、という意味で、なんでもくっつくということです。
UIの動きとして拒否する

これを実現するのはまさに型、です。以下でGroupOnlyという型を用意してそれでルールを書くテクニックを解説します。
グループの定義

グループのブロックの定義を見てみます。4つの接続がありますね。a(x)*bのように、前(a)と中(x)と量指定子(*)と後(b)がくっつきうるからです。

ここで注目するのはGroupOnlyという新しい接続型がでてきたことです。これはその名の通り、グループの内側にしかいれることが出来ない接続です。
ORの定義

ではGroupOnlyがどこで使われるかというと、ORで使われるんですね。
どういうことかというと、a|bという式は、()で囲まないと前後接続、量指定子、全てが不可能なんです。
たとえば、a|bにcを接続しようと思うと、(a|b)cが正しく、a|bcだと、想定と異なりacという文字列がマッチしなくなります。

後ろの接続については純粋に消せばいいのでnextStatementを作らなければいいです。 一方で、previousStatementについては、"グループの中に入れる"接続もpreviousStatementなので消すことが出来ず、型を用いて、previousStatement: GroupOnlyという接続パターンが必要になるのです。 この定義だけで、先に示したように、くっつきそうなピースなんだけど、動きで拒否される、という実装になります。自分で作ると大変なのでBlocklyのこの機能めっちゃありがたい!
まとめ
Blocklyでは、接続の形と型の2つの設計ツールを活かして文法違反ができない安全なビジュアルDSLを作れることを学びました。 少し複雑なロジックが書けるUIを、特に社内ツールとして提供したい時にBlocklyは第一選択となりうるのではないでしょうか!
We are Hiring!
エムスリーでは型を活かして安全に、場合によってDSLを使って皆が使いやすいシステムを構築するエンジニアを募集しています。