1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32 package jp.sourceforge.tokenizer;
33
34 import java.util.ArrayList;
35 import java.util.List;
36 import java.util.regex.Matcher;
37
38 /**
39 * <p>
40 * 字句解析を行います。入力文字列を解析して、トークンに分解します。
41 * </p>
42 * <p>
43 * 分解するトークンは、{@link TokenInfo}インターフェイスを実装したクラスを作成し、そのインスタンスをコンストラクタで与えてください。 {@link Tokenizer}クラスは、{@link TokenInfo#getTokenPattern()}メソッドが返す正規表現を使用して文字列を解析し、合致する文字列をトークンとして切り出します。
44 * </p>
45 *
46 * @author uguu@users.sourceforge.jp
47 */
48 public final class Tokenizer {
49
50 private String text;
51
52 private Token current;
53
54 /**
55 * <p>
56 * インスタンスを初期化します。
57 * </p>
58 *
59 * @param text
60 * 解析対象の文字列。nullの場合、{@link NullPointerException}例外をスローします。
61 */
62 public Tokenizer(String text) {
63
64 if (text == null) {
65 throw new NullPointerException("textがnullです。");
66 }
67
68 this.text = text;
69 }
70
71 /**
72 * <p>
73 * 現在読み込んでいるトークンを返します。初期化直後のトークンが読み込まれていない状態では、nullを返します。文字列の終端まで解析が完了している場合は{@link EofToken}インスタンスを返します。
74 * </p>
75 *
76 * @return 現在読み込んでいるトークン、null、または{@link EofToken}インスタンス。
77 */
78 public Token current() {
79 return this.current;
80 }
81
82 /**
83 * <p>
84 * 文字列を解析し、トークンを読み込みます。読み込むトークンはreadTokenInfos引数で指定します。skipTokenInfos引数で指定したトークンは読み込まれず、スキップされます。複数のトークンに一致した場合、{@link TokenizerException}例外をスローします。
85 * </p>
86 *
87 * @param readTokenInfos
88 * 読み込むトークンの配列。nullの場合、配列にnullが含まれる場合、{@link NullPointerException}例外をスローします。配列の要素が0の場合、{@link IllegalArgumentException}例外をスローします。
89 * @param skipTokenInfos
90 * スキップするトークンの配列。nullの場合、配列にnullが含まれる場合、{@link NullPointerException}例外をスローします。
91 * @return トークンを読み込んだ場合はtrue、トークンの正規表現に一致しなかった場合、文字列の終端まで解析した場合、false。
92 */
93 public boolean read(TokenInfo[] readTokenInfos, TokenInfo[] skipTokenInfos) {
94
95 if (readTokenInfos == null) {
96 throw new NullPointerException("readTokenInfosがnullです。");
97 }
98 for (int i = 0; i < readTokenInfos.length; i++) {
99 if (readTokenInfos[i] == null) {
100 throw new NullPointerException("readTokenInfos[" + i + "]がnullです。");
101 }
102 }
103 if (readTokenInfos.length == 0) {
104 throw new IllegalArgumentException("readTokenInfosの要素数が0です。");
105 }
106 if (skipTokenInfos == null) {
107 throw new NullPointerException("skipTokenInfosがnullです。");
108 }
109 for (int i = 0; i < skipTokenInfos.length; i++) {
110 if (skipTokenInfos[i] == null) {
111 throw new NullPointerException("skipTokenInfos[" + i + "]がnullです。");
112 }
113 }
114
115 if (this.current instanceof EofToken) {
116 return false;
117 }
118 if (this.text.length() == 0) {
119 this.current = new EofToken(0, 0);
120 return false;
121 }
122
123 List hitReadTokenList = new ArrayList();
124 String hitText = null;
125 for (int i = 0; i < readTokenInfos.length; i++) {
126 Matcher m = readTokenInfos[i].getTokenPattern().matcher(this.text);
127 if (m.find() && m.start() == 0) {
128 hitReadTokenList.add(readTokenInfos[i]);
129 hitText = m.group();
130 }
131 }
132 List hitSkipTokenList = new ArrayList();
133 for (int i = 0; i < skipTokenInfos.length; i++) {
134 Matcher m = skipTokenInfos[i].getTokenPattern().matcher(this.text);
135 if (m.find() && m.start() == 0) {
136 hitSkipTokenList.add(skipTokenInfos[i]);
137 hitText = m.group();
138 }
139 }
140 if (hitReadTokenList.size() == 0 && hitSkipTokenList.size() == 0) {
141 return false;
142 } else if ((hitReadTokenList.size() + hitSkipTokenList.size()) > 1) {
143 hitReadTokenList.addAll(hitSkipTokenList);
144 TokenInfo[] tokens = (TokenInfo[]) hitReadTokenList.toArray(new TokenInfo[0]);
145 throw new TokenizerException(0, 0, tokens);
146 } else if (hitReadTokenList.size() == 1) {
147 TokenInfo tokenInfo = (TokenInfo) hitReadTokenList.get(0);
148 this.current = tokenInfo.createToken(hitText, 0, 0);
149 this.text = this.text.substring(hitText.length());
150 return true;
151 } else {
152 this.text = this.text.substring(hitText.length());
153 return this.read(readTokenInfos, skipTokenInfos);
154 }
155 }
156
157 }