ひきぷろのプログラミング日記

プログラミングの日記です。

(C#) 字句解析ツールを作ってみた

"C# Tokenizer" で検索しても見つからないので作ってみました。

github.com

使い方

CSV ファイルの分解ツールを作る手順を書いてみます。

プロジェクトの作成

  • Visual Studio でプロジェクトを作成します。
  • GitHub から Hikipuro/Text 内のファイルをコピーしてください。

クラスの作成

  • CSVTokenizer.cs という名前でクラスを作成します。
  • using Hikipuro.Text; をファイルの先頭あたりに追加してください。

列挙型の作成

  • クラス内に、 CSV ファイルで使用する、文字の種類を表す列挙型を作成します。
public enum TokenType {
	Comma,		// コンマ
	NewLine,	// 改行
	Number,		// 数値
	String,		// 文字列
}

実行用のメソッドの作成

  • 字句解析を実行するためのメソッドを作成します。
public static TokenList<TokenType> Tokenize(string text) {
}

トークンの分解規則を追加

  • 文字列をトークンに分解するための規則を、正規表現で追加します。
// Tokenizer オブジェクトを準備する
Tokenizer<TokenType> tokenizer = new Tokenizer<TokenType>();

// トークンの分解規則を追加する
tokenizer.AddPattern(TokenType.Comma, ",");
tokenizer.AddPattern(TokenType.NewLine, "\r\n|\r|\n");
tokenizer.AddPattern(TokenType.Number, @"\d+[.]?\d*");
tokenizer.AddPattern(TokenType.String, @"""((?<=\\)""|[^\r\n""])*""");

トークン分解時のイベントを追加

  • トークン分解時に発生するイベントを追加します。
// リストにトークンを追加する直前に発生するイベント
// - e.cancel = true; で追加しない
tokenizer.BeforeAddToken += (object sender, BeforeAddTokenEventArgs<TokenType> e) => {
	// 文字列にマッチした場合
	if (e.TokenMatch.Type == TokenType.String) {
		// 前後からダブルクォートを取り除く
		string matchText = e.TokenMatch.Text;
		matchText = matchText.Trim('"');
		e.TokenMatch.Text = matchText;
	}
};

字句解析を実行

// トークンに分解する
TokenList<TokenType> tokens = tokenizer.Tokenize(text);
return tokens;


このエントリで説明したコードは、次のファイルにまとめています。

Tokenizer/CSVTokenizer.cs at master · hikipuro/Tokenizer · GitHub

分らないことがあったら、コメントで教えてくださいー。

追記 (2016/10/30)

一気に最後までトークンを分解すると不便なケースがあったので、ステップ実行用の機能を追加しました。

// JSON ファイルの分解
string text = "json object";

// Tokenizer オブジェクトを準備する
Tokenizer<TokenType> tokenizer = new Tokenizer<TokenType>();

// トークンの分解規則を追加する
tokenizer.AddPattern(TokenType.NewLine, "\r\n|\r|\n");
tokenizer.AddPattern(TokenType.Comma, ",");
tokenizer.AddPattern(TokenType.Colon, ":");
tokenizer.AddPattern(TokenType.OpenBrace, "{");
tokenizer.AddPattern(TokenType.CloseBrace, "}");
tokenizer.AddPattern(TokenType.OpenBracket, @"\[");
tokenizer.AddPattern(TokenType.CloseBracket, @"\]");
tokenizer.AddPattern(TokenType.Null, "null");
tokenizer.AddPattern(TokenType.True, "true");
tokenizer.AddPattern(TokenType.False, "false");
tokenizer.AddPattern(TokenType.Number, @"\d+[.]?\d*");
tokenizer.AddPattern(TokenType.String, @"""((?<=\\)""|[^\r\n""])*""");
tokenizer.AddPattern(TokenType.Space, @"\s+");

// ステップ実行用のオブジェクトを作成する
SteppingTokenizer<TokenType> stepping = tokenizer.CreateSteppingTokenizer(text);

// トークンに分解する
TokenList<TokenType> tokens = new TokenList<TokenType>();
while (stepping.HasNext) {
    Token<TokenType> token = stepping.Next();
    switch (token.Type) {
    case TokenType.NewLine:
    case TokenType.Space:
        break;
    default:
        tokens.Add(token);
        break;
    }
}