モチベーション
.Net (monoでも良い)上のオリジナルのプログラミング言語を作りたい。
方法
自分で設計したオリジナル言語からBoo構文木への変換ができれば、意味解析や実行用コード出力等は全てBooコンパイラがやってくれるはず。
前の投稿では、Booコンパイラパイプラインを構文解析(Parsingクラス)ステップだけにして Boo プログラムの構文木生成をしたが、今回はBooコンパイラパイプラインの中の構文解析ステップを自分言語用のコンパイラステップに置き換えて、オリジナル言語の実装をする。
目標とするプログラミング言語
ソースを簡単にするために、次の簡単(すぎる)言語を想定する。
a = 134;
> a;
b = hogehoge!; > b;
- Booと違ってフリーテキスト。; 記号で文を区切る。
- '=' があれば代入文。代入左辺は変数名。右辺は文字列定数直値だけ。
- '> 変数名' で変数値の出力。
ソース
次が自分言語解析コンパイラステップのクラス。
# 毎度のことながらこのプログラムは python ではなくて Boo ですよ。
import Boo.Lang.Parser
import Boo.Lang.Compiler
import Boo.Lang.Compiler.Steps
import Boo.Lang.Compiler.IO
import Boo.Lang.Compiler.Ast
import Boo.Lang.Compiler.Pipelines
class MyCompilerStep( ICompilerStep ):
# コンパイラ起動時にコンテキストを受けとる
private context as CompilerContext
def Initialize( ctxt as CompilerContext ):
context = ctxt
# コンパイル実行時処理
def Run():
unit = context.CompileUnit
# CompileUnit.Paramsters.Input = 入力プログラム列
for input in context.Parameters.Input:
# 入力単位毎にモジュール構文木(Module)を作る。
# モジュールに名前つけると出力名になる。
name = input.Name;
module = Module( LexicalInfo( name ), name )
unit.Modules.Add( module )
using r = input.Open(): # 入力を読んで解析。
text = r.ReadToEnd()
stmts = text.Split( char(';') ) # ';' で区切る。
for s in stmts:
s = s.Trim()
continue if len(s) <= 0
# '=' を含むなら代入文構文木をつくる。
if s.IndexOf( '=' ) >= 0:
toks = s.Split( char('=') ) # '=' で左辺と右辺に分ける。
module.Globals.Add( [|
$(ReferenceExpression(Name:toks[0].Trim())) = $(toks[1].Trim())
|] )
# '>' で始まるなら print マクロ構文木をつくる。
elif s.StartsWith( '>' ):
vn = s[1:].Trim() # '>' の後が変数名。
module.Globals.Add( [|
print $(ReferenceExpression(Name:vn))
|] )
次は上記 MyCompilerStep を使うメインプログラム部分。
# 上のプログラムの続き
# サンプル自分言語プログラム
text = """
a = 134;
> a;
b = hogehige!; > b;
"""
c = BooCompiler()
p = c.Parameters
p.Pipeline = CompileToFile() # CompileToFile は .exe ファイル出力までするパイプライン
p.Pipeline.Replace( Parsing, MyCompilerStep() ) # Parsing クラスのコンパイラステップを自分のコンパイラステップに置き換え。
p.Input.Add( StringInput( "test1", text ) ) # テキストファイル入力なら FileInput("ファイルパス")。
ctxt = c.Run()
実行すると test1.exe ができる( "test1" は上記コード中で StringInput の名前として指定。FileInput() なら入力ファイル名になる)。それを実行した出力結果は次になる。
134
hogehige!
注意点
- 自分言語のコンパイルエラーを実装するなら、context.Errors にエラーを追加する。raise や assert を使うと一律 internal compiler error になるので注意。
- コンパイルエラーに正しくソース上の位置を表示させたり、デバッガで自分言語ソースをきちんとポイントさせたいなら、生成する構文木(Boo.Compiler.Ast.Nodeクラス)の LexicalInfo プロパティに、対応する入力テキスト上の位置を設定する。例えば上記最後の '>' 命令のところなら次のようにする。
node = [|
print $(ReferenceExpression(Name:vn))
|]
node.LexicalInfo = LexicalInfo( name, 行番号 ) # '行番号' は適当になんとかする。
module.Globals.Add( node )
結論
エラー処理など全くしない簡単すぎる例だが、文字列の解析+構文木生成+αで .Net 用のプログラミング言語を作れるのはとても楽だと思う(言語処理系の実装でメンドクサイ部分は意味解析、コード出力、最適化なので)。
予告
Unityscript/C#/Boo のどれでもない自分言語を Unity 上の開発言語にできるようにしてみる。