S-JIS[2013-01-14/2013-01-20] 変更履歴

EclipseプラグインDMDLエディター(キーワード)

Eclipseプラグイン開発自作DMDLエディターでキーワードに色を付けてみる。

 

概要

DMDLではデータ(プロパティ)の型の種類が決まっているので、型に色を付けてみる。

item = {
    code : LONG;
    id   : TEXT;
    name : TEXT;
};

本当だったら、構文解析をした上でプロパティーの部分(つまりコロン以降セミコロンまでの間)だけ色を付けたいところだけど、構文解析は難しそうなので、ひとまず(場所に関わらず)特定のキーワードに色を付ける方針とする。


DMScanner

キーワードに色を付ける為のScannerを用意する。

package jp.hishidama.eclipse_plugin.dmdl_editor.editors;

import java.util.HashSet;
import java.util.Set;

import org.eclipse.jface.text.TextAttribute;
import org.eclipse.jface.text.rules.IRule;
import org.eclipse.jface.text.rules.IToken;
import org.eclipse.jface.text.rules.IWordDetector;
import org.eclipse.jface.text.rules.RuleBasedScanner;
import org.eclipse.jface.text.rules.Token;
import org.eclipse.jface.text.rules.WordRule;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.RGB;
/**
 * データモデルScanner.
 */
public class DMScanner extends RuleBasedScanner {
	static final String[] DMDL_PROPERTY_TYPE = {
		"INT", "LONG", "FLOAT", "DOUBLE", "TEXT", "DECIMAL", "DATE", "DATETIME", "BOOLEAN", "BYTE", "SHORT"
	};
	/**
	 * コンストラクター.
	 *
	 * @param colorManager
	 */
	public DMScanner(ColorManager colorManager) {
		RGB c = new RGB(192, 0, 0);
		int style = SWT.BOLD;
		IToken typeToken = new Token(new TextAttribute(colorManager.getColor(c), null, style));

		IRule[] rules = { new DMWordRule(DMDL_PROPERTY_TYPE, typeToken), };
		setRules(rules);
	}

TextAttributeは引数が3つのコンストラクターがある。
第1引数が文字の色、第2引数が背景色、第3引数はスタイル。スタイルにはSWTクラスに定義されている値を指定する。

	static class DMWordRule extends WordRule {
		/** コンストラクター. */
		public DMWordRule(String[] words, IToken token) {
			super(new DMWordDetector());
			for (String word : words) {
				addWord(word, token);
			}
		}
	}

特定のキーワードのスタイルを変えるにはWordRuleが使える。
addWord()にキーワードとスタイルを追加する。

WordRuleにはコンストラクターでIWordDetectorを渡さないといけないので、これも用意する。
キーワードの開始文字キーワードを構成する文字を判定するクラス。
これらの判定はあまり厳密である必要は無さそう。「明らかにキーワードの文字でない」という判定を先に行って実行効率を上げる為にあると思われる。

	static class DMWordDetector implements IWordDetector {
		@Override
		public boolean isWordStart(char c) {
			return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z');
		}

		@Override
		public boolean isWordPart(char c) {
			return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z');
		}
	}
}

DMWordDetectorの修正


DMDLConfiguration

DMDLConfigurationを修正し、今回作ったDMScannerを呼ぶようにする。

	@Override
	public IPresentationReconciler getPresentationReconciler(ISourceViewer sourceViewer) {
		PresentationReconciler reconciler = new PresentationReconciler();

		{ // データモデルの色の設定
			RGB defaultColor = new RGB(0, 0, 0);
			DMScanner scanner = new DMScanner(colorManager);
			scanner.setDefaultReturnToken(new Token(new TextAttribute(colorManager.getColor(defaultColor))));

			DefaultDamagerRepairer dr = new DefaultDamagerRepairer(scanner);
			reconciler.setDamager(dr, IDocument.DEFAULT_CONTENT_TYPE);
			reconciler.setRepairer(dr, IDocument.DEFAULT_CONTENT_TYPE);
		}
		{ // コメントの色の設定
			RGB c = new RGB(0, 192, 0);
			NonRuleBasedDamagerRepairer dr = new NonRuleBasedDamagerRepairer(new TextAttribute(colorManager.getColor(c)));
			reconciler.setDamager(dr, DMDLPartitionScanner.DMDL_COMMENT);
			reconciler.setRepairer(dr, DMDLPartitionScanner.DMDL_COMMENT);
		}

		return reconciler;
	}

前回まで「デフォルトの色の設定」をしていた箇所を変更。
といってもScannerの種類を変えただけだけど。


その他のキーワードを追加

DMDLではプロパティーの型以外にもjoined・summarizedとかsum・countとかがキーワードなので、調子に乗って(笑)これにも色を付けてみる。

DMScanner.java:

public class DMScanner extends RuleBasedScanner {
	static final String[] DMDL_PROPERTY_TYPE = {
		"INT", "LONG", "FLOAT", "DOUBLE", "TEXT", "DECIMAL", "DATE", "DATETIME", "BOOLEAN", "BYTE", "SHORT"
	};
	static final String[] MODEL_TYPE = {
		"joined", "summarized", "projective"
	};
	static final String[] SUMMARIZED_TYPE = {
		"any", "sum", "max", "min", "count"
	};
	/**
	 * コンストラクター.
	 *
	 * @param colorManager
	 */
	public DMScanner(ColorManager colorManager) {
		RGB c = new RGB(192, 0, 0);
		IToken typeToken  = new Token(new TextAttribute(colorManager.getColor(c), null, SWT.NORMAL));
		IToken modelToken = new Token(new TextAttribute(colorManager.getColor(c), null, SWT.BOLD));
		IToken sumToken   = new Token(new TextAttribute(colorManager.getColor(c), null, SWT.NORMAL));

		IRule[] rules = {
			new DMWordRule(DMDL_PROPERTY_TYPE, typeToken),
			new DMWordRule(MODEL_TYPE,         modelToken),
			new DMWordRule(SUMMARIZED_TYPE,    sumToken),
		};
		setRules(rules);
	}

案の定、「count」をプロパティー名に使うと色が変わってしまうが^^;、今は仕方ない。

summarized order_summary = order => {
    any item_code -> code;
    sum price -> total;
    count item_code -> count;
} % code;

説明文の色付け

DMDLでは、データモデル名やプロパティー名の前に説明を付けることが出来る。
説明はダブルクォーテーションで囲んだ文字列となる。

"アイテム"
itme = {
    "商品コード" code : LONG;
    "ID"         id   : TEXT;
    "商品名"     name : TEXT;
};

これもキーワードと同様にScannerに追加できる。

DMScanner.java:

	/**
	 * コンストラクター.
	 *
	 * @param colorManager
	 */
	public DMScanner(ColorManager colorManager) {
〜
		IToken descToken = new Token(new TextAttribute(colorManager.getColor(new RGB(0, 0, 192)), null, SWT.NORMAL));

		IRule[] rules = {
			new DMWordRule(DMDL_PROPERTY_TYPE, typeToken),
			new DMWordRule(MODEL_TYPE, modelToken),
			new DMWordRule(SUMMARIZED_TYPE, sumToken),
			new SingleLineRule("\"", "\"", descToken),
		};
		setRules(rules);
	}

アノテーションの色付け

DMDLでは「@キーワード」で色々な指定をすることが出来る。
(DMDLの正式な用語ではこれのことを何と呼んでいるかよく分からないが^^;
 個人的にはJavaのアノテーションに似ているので「アノテーション」と呼んでいる)

これにも色を付けてみる。
「@」で始まって、英数字およびピリオドが続くもの…というのにぴったりのRuleクラスが見つからなかったので、IRuleインターフェースを直接実装する。
(参考: NumberRuleクラス)

DMAnnotationRule.java:

package jp.hishidama.eclipse_plugin.dmdl_editor.editors;

import org.eclipse.core.runtime.Assert;
import org.eclipse.jface.text.rules.ICharacterScanner;
import org.eclipse.jface.text.rules.IRule;
import org.eclipse.jface.text.rules.IToken;
import org.eclipse.jface.text.rules.Token;
public class DMAnnotationRule implements IRule {
	private IToken token;

	/**
	 * コンストラクター.
	 *
	 * @param token
	 */
	public DMAnnotationRule(IToken token) {
		Assert.isNotNull(token);
		this.token = token;
	}
	@Override
	public IToken evaluate(ICharacterScanner scanner) {
		int c = scanner.read();
		if (c == '@') {
			do {
				c = scanner.read();
			} while (isWordPart((char) c));
			scanner.unread();
			return token;
		}

		scanner.unread();
		return Token.UNDEFINED;
	}
	protected boolean isWordPart(char c) {
		return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || ('0' <= c && c <= '9') || c == '.';
	}
}

DMScanner.java:

	/**
	 * コンストラクター.
	 *
	 * @param colorManager
	 */
	public DMScanner(ColorManager colorManager) {
		RGB c = new RGB(192, 0, 0);
〜
		IToken annToken  = new Token(new TextAttribute(colorManager.getColor(c), null, SWT.BOLD));
		IToken descToken = new Token(new TextAttribute(colorManager.getColor(new RGB(0, 0, 192)), null, SWT.NORMAL));

		IRule[] rules = {
			new DMWordRule(DMDL_PROPERTY_TYPE, typeToken),
			new DMWordRule(MODEL_TYPE, modelToken),
			new DMWordRule(SUMMARIZED_TYPE, sumToken),
			new DMAnnotationRule(annToken),
			new SingleLineRule("\"", "\"", descToken),
		};
		setRules(rules);
	}

DMWordDetectorの修正

上記のDMWordDetectorだと、「count1」のような単語があると「count」部分だけ色が付いてしまう。
これは見た目の直感に反するので、DMWordDetectorを修正する。

	static class DMWordDetector implements IWordDetector {
		protected boolean accept(char c) {
			switch (c) {
			case ' ':
			case '\t':
			case '\r':
			case '\n':
			case ':':
			case ';':
			case '%':
			case '=':
			case '-':
			case '+':
				return false;
			default:
				return true;
			}
		}
		@Override
		public boolean isWordStart(char c) {
			return accept(c);
		}

		@Override
		public boolean isWordPart(char c) {
			return accept(c);
		}
	}

これで、「count1」のようにキーワードの後ろに何か続いている場合は色が変わらなくなった。

が、「test_count」のようにキーワードの前に何か付いている場合には「count」部分に色が付いてしまう。
対処方法


WordRuleの正しい使い方

前述の方法では、単語の一部がキーワードの場合に、キーワード部分だけ色が付いてしまう。[2013-01-20]
(例:countがキーワードの場合、「test_count」の「count」部分に色が付いてしまう)
これは、WordRuleの使い方を間違っていた為だったorz

WordRuleのコンストラクターは以下のようになっている。

	public WordRule(IWordDetector detector, IToken defaultToken) {

引数のdefaultTokenは“指定された単語でない場合に返すToken”で、これを指定しない場合はToken.UNDEFINEDとなる。
解釈した文字列が指定された単語でなかった場合は以下のような処理になる。

前述の方法ではdefaultTokenは指定していなかったので、UNDEFINED扱いになっていた。
「test_count」のような文字列の場合、「test_count」そのものはキーワードではないので、UNDEFINEDになる。
別ルールでも適用されるものは無いので、先頭の「t」という文字は無視される。
そして、残った部分に対して改めて解釈を行う。つまり「est_count」がキーワードかどうか解釈し、同様にUNDEFINEDとなる。
これを繰り返していくと、「test_」部分は無視される。
そして次に「count」が解釈され、これはキーワードなので指定されたTokenが適用される。

つまり、WordRuleを使うに当たってdefaultTokenを指定していれば、キーワードでない文字列でも別ルール適用の試行は行われないので、
「test_count」のような文字列はdefaultTokenが適用され、一部分だけ再解釈されるようなことにならない。

このやり方は、WordRuleそのものは1つだけでないと成り立たない。
複数のWordRuleを使おうとしたのが間違いだったようだ。したがって、正しい使い方は以下の様になる。

間違った使い方 正しい使い方
IRule[] rules = {
	new DMWordRule(DMDL_PROPERTY_TYPE, typeToken),
	new DMWordRule(MODEL_TYPE, modelToken),
	new DMWordRule(SUMMARIZED_TYPE, sumToken),
	new DMAnnotationRule(annToken),
	new SingleLineRule("\"", "\"", descToken),
};
IToken defaultToken = new Token(new TextAttribute(colorManager.getColor(new RGB(0,0,0), null, SWT.NORMAL));
DMWordRule wordRule = new DMWordRule(defaultToken);
wordRule.addWords(DMDL_PROPERTY_TYPE, typeToken);
wordRule.addWords(MODEL_TYPE,         modelToken);
wordRule.addWords(SUMMARIZED_TYPE,    sumToken);

IRule[] rules = {
	wordRule,
	new DMAnnotationRule(annToken),
	new SingleLineRule("\"", "\"", descToken),
};

 

class DMWordRule extends WordRule {
	/** コンストラクター. */
	public DMWordRule(String[] words, IToken token) {
		super(new DMWordDetector());

		for (String word : words) {
			addWord(word, token);
		}
	}
}
class DMWordRule extends WordRule {
	/** コンストラクター. */
	public DMWordRule(IToken defaultToken) {
		super(new DMWordDetector(), defaultToken);
	}
	public void addWords(String[] words, IToken token) {
		for (String word : words) {
			addWord(word, token);
		}
	}
}

Eclipseプラグインへ戻る / Eclipseへ戻る / 技術メモへ戻る
メールの送信先:ひしだま