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 にアップロードしています。