Nemerle には標準で PEG のパーサージェネレーターのライブラリが付属しています。それを使って Brainfuck の単純なパーサーを書いてみます。
まず Brainfuck の構文を表す variant[A] を定義します。
using System.Collections.Generic;
variant BrainfuckExpression
{
| MoveRight
| MoveLeft
| Increment
| Decrement
| Put
| Get
| BeginLoop
| EndLoop
| Loop { Begin : BeginLoop;
Body : List[BrainfuckExpression];
End : EndLoop; }
}
次にパーサーです。 PegGrammerAttribute に直接 PEG の文法を放り込んでやり,それぞれの実装をクラス内で定義やるだけです。文法と実装がマッチしていなければコンパイルエラーになります。
簡単のために,改行を含む空白文字やコメントを一切許容しない,もっとも単純な文法を定義します。
using System;
using System.Collections.Generic;
using Nemerle.Peg;
[PegGrammar(Options = EmitDebugSources, Commands,
grammar
{
SingleCommand : BrainfuckExpression = '>' / '<' / '+' / '-' / ',' / '.';
BeginLoop : BrainfuckExpression = '[';
EndLoop : BrainfuckExpression = ']';
Loop : BrainfuckExpression = BeginLoop Commands* EndLoop;
Commands : List[BrainfuckExpression] = (Loop / SingleCommand)*;
})]
class BrainfuckParser
{
SingleCommand(token : NToken) : BrainfuckExpression
{
match (this._parsingSource.OriginalText[token.StartPos])
{
| '>' => BrainfuckExpression.MoveRight();
| '<' => BrainfuckExpression.MoveLeft();
| '+' => BrainfuckExpression.Increment();
| '-' => BrainfuckExpression.Decrement();
| ',' => BrainfuckExpression.Get();
| '.' => BrainfuckExpression.Put();
| _ => throw NotSupportedException();
}
}
BeginLoop(_ : NToken) : BrainfuckExpression
{
BrainfuckExpression.BeginLoop();
}
EndLoop(_ : NToken) : BrainfuckExpression
{
BrainfuckExpression.EndLoop();
}
Loop(begin : BrainfuckExpression, body : List[List[BrainfuckExpression]], end : BrainfuckExpression) : BrainfuckExpression.Loop
{
BrainfuckExpression.Loop(begin :> BrainfuckExpression.BeginLoop,
body[0],
end :> BrainfuckExpression.EndLoop);
}
Commands(commands : List[BrainfuckExpression]) : List[BrainfuckExpression]
{
commands;
}
}
これだけで Brainfuck のパーサーが完成です。小難しいことはすべて勝手にやってくれます。なお,コンパイルする際には, Nemerle.Peg.dll と Nemerle.Peg.Macros.dll を参照に加える必要があります。
実際に使う際は,普通にインスタンスを生成して Parse メソッドを呼ぶだけです。パースに成功すれば option.Some が,失敗すれば option.None が返ってきます。
using System;
using System.Console;
using System.ComponentModel;
using Nemerle.Assertions;
module Program
{
Main() : void
{
// Hello, world!
def source = "+++++++++[>++++++++>+++++++++++>+++++<<<-]>.>++.+++++++..+++.>-.------------.<++++++++.--------.+++.------.--------.>+.";
_ = Execute(source);
}
Execute(source : string, memorySize = 0x1000 : int) : array[char]
requires source != null
otherwise throw ArgumentNullException("source")
requires memorySize > 0
otherwise throw ArgumentOutOfRangeException("memorySize")
ensures value != null
{
def parser = BrainfuckParser();
def memory = array (memorySize);
mutable pointer = 0;
match (parser.Parse(source))
{
| option.Some(commands) =>
foreach (c in commands)
{
Proceed(c, memory, ref pointer);
}
| option.None => throw FormatException();
}
memory;
}
Proceed(command : BrainfuckExpression, memory : array[char], pointer : ref int) : void
requires memory != null
otherwise throw ArgumentNullException("memory")
requires 0 <= pointer && pointer < memory.Length
otherwise throw ArgumentOutOfRangeException("pointer")
ensures 0 <= pointer && pointer < memory.Length
otherwise throw OutOfMemoryException()
{
match (command)
{
| BrainfuckExpression.MoveRight => ++pointer;
| BrainfuckExpression.MoveLeft => --pointer;
| BrainfuckExpression.Increment => ++memory[pointer];
| BrainfuckExpression.Decrement => --memory[pointer];
| BrainfuckExpression.Get => memory[pointer] = ReadKey(true).KeyChar;
| BrainfuckExpression.Put => Write(memory[pointer]);
| BrainfuckExpression.Loop as loop =>
while (memory[pointer] != 0)
{
foreach (c in loop.Body)
{
Proceed(c, memory, ref pointer);
}
}
| _ => throw InvalidEnumArgumentException();
}
}
}
このコードだけでも, Nemerle の特徴が随所に表れている気がします。
感想
Nemerle は Java 界隈で言うところの Scala 的な位置づけの言語だと思います。 C# のようなメジャーな言語とのギャップも小さく,かつ関数型の強力な表現力も持っています。マクロによる構文拡張はチート級です[B]。
.NET の言語としては Visual Studio によるサポートが弱いのがネックですが[C],とても書きやすい言語だと思います。
ソースコード
ソースコードを Bitbucket にアップロードしています。