AccessViolation Exception

仕事でもはんだづけ、家でもはんだづけ

c#でSpracheを使った構文解析(1)

なにか適当なプログラム組んでいる時に、ふと簡易スクリプト的なものを食わせたりしたくなることが多々あります。ただそのために構文解析器を自作するのも本末転倒ですし何かライブラリで出来ないかと思い探してみるとIronyとかSpracheとかがヒットする模様。

使ってみた感じIronyはBNFベースで記述するだけで動く、Spracheはscalaのパーサコンビネータlinqで構文定義するような印象を受けました。

今回はSpracheを使って構文解析をしてみようと思います。

導入

nugetから導入できます。

文字を取得する

基本的にParse.にあるものを利用します。パーサ自体はstatic readonlyで定義しておけばいいので

static readonly Parser<char> character = Parse.Letter;

var result = character.Parse("hoge");//h

これで1文字目のhが取得できます。

他にもSprache.Parseを読めばいろいろありますが

public static readonly Parser<char> AnyChar;
public static readonly Parser<string> Decimal;
public static readonly Parser<char> Digit;
public static readonly Parser<char> Letter;
public static readonly Parser<char> LetterOrDigit;
public static readonly Parser<char> Lower;
public static readonly Parser<string> Number;
public static readonly Parser<char> Numeric;
public static readonly Parser<char> Upper;
public static readonly Parser<char> WhiteSpace;

public static Parser<char> Char(char c);
public static Parser<char> Chars(string c);

が基本的なところです。

文字の塊を取得する

現状だと最初の一文字しか取得できません。というのも

static readonly Parser<char> digit = Parse.Digit;

var result = digit.Parse("123");//1

の結果は1しか取れません。そこでこうすると文字列が取れます。

static readonly Parser<string> digit = Parse.Digit.AtLeastOnce().Text();

var result = digit.Parse("123");//123

ここでDigitを表しているひとかたまりを取得するにはMany()AtLeastOnce()を使います。*1

こいつらはParser<IEnumerable<T>>を返し、一致していれば連続して列挙してくれます。

それともう一つText()も使います。こいつはParser<IEnumerable<char>>を文字列に変換してくれます。

それとParse.Digitを連続して返すので、空白などがあった場合はそこで打ち切られるので"100 200 300"を投げた時の結果は100だけパースされます。

同じものの繰り返しを取得する

すぐ上に書いた"100 200 300"からすべての数値を取ることを考えます。

Many()を使えば行けるだろうとすぐ上に書きました。その通りにすればこうなります。

static readonly Parser<string> digit = Parse.Digit.AtLeastOnce().Text();
static readonly Parser<IEnumerable<string>> digits = digit.Many();

var result = digits.Parse("100 200 300");//[100]

残念ながらこれでは空白があって列挙できません。前後の空白等を無視するにはToken()を使います。

static readonly Parser<string> digit = Parse.Digit.AtLeastOnce().Token().Text();
static readonly Parser<IEnumerable<string>> digits = digit.Many();

var result = digits.Parse("100 200 300");//[100, 200, 300]

指定文字を取得する

ここまでの流れから少し外れますが、これまでで使ったParse.Letterは任意の文字ですが、指定文字をパースしたいときは

static readonly Parser<char> att = Parse.Char('@');

もちろんこれだけでは@が取れるだけですが、この先構文を作っていく時には必須になります。


'Many()'と'AtLeastOnce()'の違い

Many()はそのパーサで連続で列挙できるものまで返す(ただしない場合は空)で

AtLeastOnce()はそのパーサで連続で列挙できるものまで返す(最低1つはある、ない場合は例外を吐く)

その他にも今回の例もParse.Decimalなど標準で用意してくれているものが多い

c#でSpracheを使った構文解析(2) - AccessViolation Exception

*1:Parse.Numberで一発でできるけど、ここでは説明のためにくどいことしてます。