今回は自作DSLのChainCC (Chain type Compiler Creation script)のほとんどの処理が完成したので、紹介しようと思います。
開発経緯
前に作った言語 (ParserScript (非推奨), UCCScript) と関係があるので、それらを踏まえて書きます。
ParserScript
強いところ
・コマンドベースだから簡単
・処理中に別の処理を書ける
弱いところ
・コードを書く難易度が高め
・演算できない
・保守性低い
・時々、バグる
・読みづらい
UCCScript
強いところ
・BNF風に書ける
・制御構文がある
・演算ができる
・使いたい文字列を参照できる
弱いところ
・実装が難しい
・仕様上のバグがある
・まだ読みづらい
二つの言語には上のような長所と短所があります。なので、ChainCCの開発目標としては
・コマンドベースで簡単
・ある程度の制御構文なら簡単に再現できる
・演算ができる
・使いたい文字列を参照できる
・バグなし
・まあまあ読みやすい
です。
どう解決したのか
開発途中というのもあって、大体完成しています。
ある程度の制御構文なら簡単に再現できる
ParserScript, UCCScript:「コードの先が分からない、、、」
ParserScriptとUCCScriptは与えられた文字しかわかりません。「〜または〜」とか「〜に0回以上マッチ」の実装が複雑なのです。
ChainCC:「コマンドをまとめとけば良くね?(以下、このことを「グループ」と呼ぶ)」
if not match
.after match "\("
.after call Expr
.after match "\)"
loop
.after match "a"
ChainCCは.after
でコマンドをまとめられます。その間はコマンドは実行されずに読み込まれ、終端に来ると今まで読み込んだコマンドを実行します。
使いたい文字列を参照できる
UCCScript:「私は囲まれたパターンにマッチした文字列を参照できるのよ」
ParserScript:「そのせいで、処理は複雑になったし、仕様上のバグも発生したよね」
UCCScript:「...」
実際、繰り返し中に配列の参照を二つ以上使うと、動作は問題ないが、バグることが確認されています。
ChainCC:「配列の参照はなくして、繰り返しの最後で削除すれば?」
loop
.after (
ref R0
.after match "a"
)
.after if <R0>
.after push <R0>
.after result last
.after delete R0
現在の仕様上、R0
のパターンにマッチしなければ自動的にR0
は消されるため、条件分岐が必要。
ParserScriptの「処理中に別の処理を書ける」を利用したものです。UCCScriptとは違い、配列に保存しなくても、その場で処理できます。
まあまあ読みやすい
ParserScriptはとにかく分かりづらいコマンドが多すぎるので、作者 (私)自身もう忘れたので、UCCScriptとChainCCの比較をします。
以下は文字を逆向きにするコード
UCCScript
First Sample End
$Sample In { <{ Char }> }
Is (Load Index[$0+[0] 1 -])
(ForB[0 \Index > !][
@1[0 \Index]
(Load Index[\Index 1 -])
])
End
ChainCC
first Sample
label Sample
loop
.after (
ref R0
.after match "."
)
.after if <R0>
.after push <R0>
.after result first
.after delete R0
end
圧倒的にChainCCの方が読みやすいですよね。
lock, unlock (予定)
グループの最初につけるタイプ。lock
を使い、その後に続くグループを次回から、コマンド実行毎に実行する。first
またはlast
をつけて、いつ実行するのかを決める。
unlock
にfirst
またはlast
を付け、開放する
逆ポーランド記法に変換するコードを書いてみる
ChainCC
trim ' '
first Expression
label Factor
ref R0
.after match ""(-)?[0-9]+(\.[0-9]+)?""
if not match
.after if ""\(""
.after match ""\(""
.after (
ref R0
.after call Expression
)
.after match ""\)""
if <R0>
.after push <R0>
if not <R0>
.after push """"
result last
end
label Term
ref R0
.after call Factor
push ?<R0>
ope rev
gotob CCC_0001
push <R0>
result last
#CCC_0000
if not ""(\*|/)""
.after goto CCC_0001
ref R1
.after match ""(\*|/)""
ref R2
.after call Factor
if <R1>
.after if <R2>
.after push "" ""
.after result last
.after push <R2>
.after result last
.after push "" ""
.after result last
.after push <R1>
.after result last
if <R1>
.after delete R1
if <R2>
.after delete R2
goto CCC_0000
#CCC_0001
end
label Expression
ref R0
.after call Term
push ?<R0>
ope rev
gotob CCC_0001
push <R0>
result last
#CCC_0000
if not ""(\+|-)""
.after goto CCC_0001
ref R1
.after match ""(\+|-)""
ref R2
.after call Term
if <R1>
.after if <R2>
.after push "" ""
.after result last
.after push <R2>
.after result last
.after push "" ""
.after result last
.after push <R1>
.after result last
if <R1>
.after delete R1
if <R2>
.after delete R2
goto CCC_0000
#CCC_0001
end
--追記
・Factor
、Expression
まではいいですがTerm
を使うとエラーが発生しました。呼び出しの深さにも対応させる予定です
--追記の追記
・オーバーフローしていたので、gotoコマンド、gotobコマンド(条件gotoコマンド)を導入して、呼ばれる関数の数を減らして、対応しました。
終わりに
現在、ChainCCにはラベルの機能などがないので、そこを実装して完成です。
--追記
・バグがまだたくさんありますが、公開しました(ChainCC-Ver.0.0)。
・バグ修正とともに機能も追加して対応しています。
--追記の追記
・完成版を公開しました(ChainCC-Ver.1.0)。
--追記の追記の追記
・ChainCC 1.0の修正版:ChainCC-Ver.2.0