1: /*
2:
3: Copyright (C) 2012 by Nobuhide Tsuda
4:
5: RuviEdit のライセンスは MIT+嫌GPL なライセンスです。
6: 無保証・無サポートですが、無償で利用でき、商用アプリでもソースコードを流用することが可能です。
7: (ソースコードを流用した場合、流用部分の著作権・ライセンスはRuviEditのそれのままです)
8: ただし筆者は、プログラマにとって不自由極まりないのに自由だ自由だと言い張るGPL系が嫌いなので、
9: RuviEdit のソースをGPL系プロジェクトで使用することを禁止します。
10: GPLプロジェクトでは一切の流用を禁止しますが、LGPLプロジェクトでは動的リンクによる流用は許可します。
11:
12: */
13: #include "ViewTokenizer.h"
14: #include "EditView.h"
15:
16: /**
17:
18: ===
19: " ダブルクォート認識、nextString('"') コール、m_quote = '"'
20: a
21: " nextString: m_quote を見つけた時点でリターン
22: ===
23: " ダブルクォート認識、nextString('"') コール、m_quote = '"'
24: a
25: #
26: { nextString: #{ を認識した時点でリターン、inBrace フラグON
27: exp nextToken() で普通に処理される
28: } inBrace フラグONの状態で } を発見した場合は nextString(m_quote) コール
29: inBrase フラグをOFFに
30: b
31: " nextString: m_quote を見つけた時点でリターン
32: ===
33: " ダブルクォート認識、nextString('"') コール、m_quote = '"'
34: a
35: 改行 バッファ末尾でリターン、token の最後の文字が m_quote かどうかで判定(?)
36: 改行を見つけた時点で、highlightBlock() からリターンするので、
37: tn 経由で状態を保持することは出来ない
38: → blockState にその情報を持たせる
39: b
40: " nextString: m_quote を見つけた時点でリターン
41: ===
42: " nest = 1, quoteStack = Empty
43: a
44: ( nest = 2, quoteStack = Empty
45: #
46: { quoteStack = ", 2
47: ' quoteStack = ", 2
48: str
49: ' quoteStack = ", 2
50: } nest = 2, quoteStack = Empty
51: ) nest = 1, quoteStack = Empty
52: "
53:
54: */
55:
56: bool isLetterOrNumberOrUnderbar(const QChar &ch);
57: bool asciiSymbolTable[] = {
58: /* 0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
59: /* 1 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
60: /* 2 */ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
61: /* 3 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1,
62: /* 4 */ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
63: /* 5 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, // _ (0x5f) はシンボルとみなす
64: /* 6 */ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
65: /* 7 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0,
66: /* 8 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
67: /* 9 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
68: /* a */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
69: /* b */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
70: /* c */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
71: /* d */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
72: /* e */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
73: /* f */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
74: };
75: bool isAsciiSymbol(QChar qch)
76: {
77: ushort code = qch.unicode();
78: return code < 0x80 && asciiSymbolTable[(uchar)code];
79: }
80: bool isHexChar(QChar qch)
81: {
82: return (qch >= '0' && qch <= '9')
83: || (qch >= 'a' && qch <= 'f')
84: || (qch >= 'A' && qch <= 'F');
85: }
86: bool isJustBeforeRegexp(const QString &text, const QString &prev2)
87: {
88: if( text == "]" || text == ")" || text == "." )
89: return false;
90: if( text.isEmpty() || isAsciiSymbol(text[0]) )
91: return true;
92: if( prev2 == "." && text[0].isLetter() ) // .メソッド /regexp/ の場合
93: return true;
94: return text == "p" || text == "puts" || text == "print"
95: || text == "if" || text == "elsif"
96: || text == "while" || text == "until";
97: }
98:
99: ViewTokenizer::ViewTokenizer(const QString &buffer, EditView *view)
100: : m_buffer(buffer), m_view(view)
101:
102: {
103: //m_inBrace = false;
104: m_ix = 0;
105: m_tokenType = UNDEF;
106: //m_openQuote = '\0';
107: //m_nestLevel = 1;
108: //nextToken();
109: }
110:
111: ViewTokenizer::~ViewTokenizer(void)
112: {
113: }
114: // nextString() は nextToken() で [1] 開始クォート類を発見した場合、または
115: // [2] マルチライン文字列 または ヒアドキュメント状態のとき、RubySyntaxHighliter::highlightBlock()
116: // からコールされる
117: // [1] の場合は、quoteStack に新しいアイテムを積むが
118: // [2] の場合は、呼ぶ前に積まれている(前行最後の状態をそのまま持ち越す)ものとする
119: // [1] と [2] の区別は quoteLength が0かどうかで判定する([2] の場合は 0)
120: // ストリング処理中はタイプ・終了quote・ネストレベルをスタックに積んでおく
121: // SQ_STRING 以外で #{ を発見した場合は、タイプ・終了quote・ネストレベルをスタックにプッシュ
122: // } により元のコンテキストに復帰する場合は、スタックから終了quote、ネストレベルをポップ
123: uchar ViewTokenizer::nextString(char quote, int quoteLength,
124: uchar strType, // DQ, BQ なら #{ でターミネイト
125: char openQuote) // ([{< の場合
126: {
127: //m_view->doOutput2(QString("Enter ViewTokenizer::nextString(strType = %1)\n")
128: // .arg(strType));
129: StringItem si;
130: if( quoteLength ) // " ' ` 等を発見した場合、マルチライン文字列からの場合は quoteLength = 0 で呼ばれるはず
131: si = StringItem(strType, quote, openQuote, 1);
132: else {
133: si = m_quoteStack.last();
134: m_quoteStack.pop_back();
135: strType = si.m_type == HERE_DOCUMENT ? si.m_strType : si.m_type;
136: }
137: #if 0
138: if( quoteLength ) // " ' ` 等を発見した場合、マルチライン文字列からの場合は quoteLength = 0 で呼ばれるはず
139: m_quoteStack.push_back(StringItem(strType, quote, openQuote, 1));
140: //StringItem &si = !m_quoteStack.isEmpty() ? m_quoteStack.last() : StringItem(0, 0, 0, 0);
141: StringItem si = StringItem(0, 0, 0, 0);
142: if( !m_quoteStack.isEmpty() ) {
143: si = m_quoteStack.last();
144: m_quoteStack.pop_back();
145: }
146: #endif
147: m_foundExp = false;
148: m_mlString = false;
149: m_tokenOffset = m_ix;
150: m_ix += (m_quoteLength = quoteLength);
151: for(;;) {
152: if( m_ix >= m_buffer.length() ) { // 次の行に続く場合
153: m_mlString = true;
154: m_tokenCloseQuoteLength = 0;
155: m_quoteStack.push_back(si);
156: break;
157: }
158: if( strType != SQ_STRING && m_buffer.mid(m_ix).startsWith("#{") ) {
159: m_foundExp = true;
160: m_quoteStack.push_back(si);
161: m_quoteStack.push_back(StringItem(EXPAND_EXP, '}'));
162: m_ix += (m_tokenCloseQuoteLength = 2);
163: break;
164: }
165: QChar c = m_buffer[m_ix++];
166: if( c == si.m_closeQuote && !--si.m_nestLevel ) {
167: m_tokenCloseQuoteLength = 1;
168: break;
169: }
170: if( si.m_openQuote != '\0' && c == si.m_openQuote )
171: ++si.m_nestLevel;
172: else if( c == '\\' && m_ix + 1 < m_buffer.length() )
173: ++m_ix;
174: }
175: m_tokenText = m_buffer.mid(m_tokenOffset, m_ix - m_tokenOffset);
176: return m_tokenType = strType;
177: }
178: uchar ViewTokenizer::regexp(char quote, int quoteLength,
179: char openQuote) // ([{< の場合
180: {
181: //m_quote = quote;
182: m_ix += (m_quoteLength = quoteLength);
183: int lvl = 1;
184: while( m_ix < m_buffer.length() ) {
185: QChar qch = m_buffer[m_ix++];
186: if( qch == quote && !--lvl ) {
187: m_tokenCloseQuoteLength = 1;
188: break;
189: }
190: if( openQuote != '\0' && qch == openQuote )
191: ++lvl;
192: else if( qch == '\\' && m_ix < m_buffer.length() )
193: ++m_ix;
194: }
195: m_tokenText = m_buffer.mid(m_tokenOffset, m_ix - m_tokenOffset);
196: return m_tokenType = REGEXP;
197: }
198: uchar ViewTokenizer::percentSymbol(char quote, int quoteLength)
199: {
200: m_ix += (m_quoteLength = quoteLength);
201: while( m_ix < m_buffer.length() && m_buffer[m_ix++] != quote ) {}
202: m_tokenText = m_buffer.mid(m_tokenOffset, m_ix - m_tokenOffset);
203: return m_tokenType = PERCENT_SYMBOL;
204: }
205: void ViewTokenizer::popNestLevelAndQuote()
206: {
207: if( m_quoteStack.isEmpty() ) return;
208: //StringItem si = m_quoteStack[m_quoteStack.size() - 1];
209: m_quoteStack.pop_back();
210: //m_nestLevel = si.m_nestLevel;
211: //m_quote = si.m_closeQuote;
212: }
213: void ViewTokenizer::skipChar()
214: {
215: if( m_ix < m_buffer.size() )
216: ++m_ix;
217: }
218: uchar ViewTokenizer::nextToken()
219: {
220: if( m_tokenType == END_OF_BUFFER || m_buffer.isEmpty() )
221: return m_tokenType = END_OF_BUFFER;
222: m_foundExp = false;
223: m_tokenCloseQuoteLength = 0;
224: #if 0
225: if( !m_inBrace ) {
226: m_nestLevel = 1;
227: m_openQuote = '\0';
228: }
229: #endif
230: m_prev2Text = m_prevText;
231: m_prevText = m_tokenText;
232: m_mlString = false;
233: while( m_ix < m_buffer.length() && m_buffer[m_ix].isSpace() )
234: ++m_ix;
235: if( m_ix >= m_buffer.length() )
236: return m_tokenType = END_OF_BUFFER;
237: m_tokenOffset = m_ix;
238: if( m_buffer[m_ix].isNumber() ) {
239: if( m_buffer[m_ix] == '0' && m_ix + 2 < m_buffer.length() ) {
240: // 0b の後は 0 or 1 のみ許されるのだが、
241: // 0b123 の場合、23 は10進数なので、構文強調的には 0 1 に限る意味があまりない
242: // 0b23 の場合、0b はエラーなのだが、そこまで厳密に処理する必要も無いと考える
243: QChar base = m_buffer[m_ix+1].toLower();
244: if( (base == 'b' || base == 'o' || base == 'd')
245: && m_buffer[m_ix+2].isNumber() )
246: {
247: m_ix += 2;
248: while( ++m_ix < m_buffer.length() && m_buffer[m_ix].isNumber() ) {}
249: m_tokenText = m_buffer.mid(m_tokenOffset, m_ix - m_tokenOffset);
250: return m_tokenType = NUMBER;
251: }
252: if( base == 'x' && isHexChar(m_buffer[m_ix+2]) ) {
253: m_ix += 2;
254: while( ++m_ix < m_buffer.length() && isHexChar(m_buffer[m_ix]) ) {}
255: m_tokenText = m_buffer.mid(m_tokenOffset, m_ix - m_tokenOffset);
256: return m_tokenType = NUMBER;
257: }
258: }
259: // \d+[.\d+][[e|E][+|-]\d+]
260: while( ++m_ix < m_buffer.length() && m_buffer[m_ix].isNumber() ) {}
261: if( m_ix + 1 < m_buffer.length() && m_buffer[m_ix] == '.'
262: && m_buffer[m_ix+1].isNumber() ) // 123. は不可
263: {
264: while( ++m_ix < m_buffer.length() && m_buffer[m_ix].isNumber() ) {}
265: }
266: int ix = m_ix;
267: if( m_ix < m_buffer.length() && (m_buffer[m_ix] == 'e' || m_buffer[m_ix] == 'E') ) {
268: if( ++ix < m_buffer.length() && (m_buffer[ix] == '+' || m_buffer[ix] == '-') )
269: ++ix;
270: if( ix < m_buffer.length() && m_buffer[ix].isNumber() ) {
271: while( ++ix < m_buffer.length() && m_buffer[ix].isNumber() ) {}
272: m_ix = ix;
273: }
274: }
275: m_tokenText = m_buffer.mid(m_tokenOffset, m_ix - m_tokenOffset);
276: return m_tokenType = NUMBER;
277: }
278: if( isLetterOrNumberOrUnderbar(m_buffer[m_ix]) ) {
279: while( ++m_ix < m_buffer.length() && isLetterOrNumberOrUnderbar(m_buffer[m_ix]) ) {}
280: if( m_ix < m_buffer.length() ) {
281: if( ((m_prevText == "def" || m_prevText == "." || m_prevText == ":")
282: && (m_buffer[m_ix] == '!' || m_buffer[m_ix] == '?'))
283: || (m_prevText == "def" && m_buffer[m_ix] == '=') )
284: {
285: ++m_ix;
286: }
287: }
288: m_tokenText = m_buffer.mid(m_tokenOffset, m_ix - m_tokenOffset);
289: return m_tokenType = IDENT;
290: }
291: if( m_prevText != "def" ) {
292: if( m_buffer[m_ix] == '\"' )
293: return nextString('\"', 1, DQ_STRING);
294: if( m_buffer[m_ix] == '\'' )
295: return nextString('\'', 1, SQ_STRING);
296: if( m_buffer[m_ix] == '`' )
297: return nextString('`', 1, BQ_STRING);
298: if( m_buffer[m_ix] == '%' ) {
299: uchar type = DQ_STRING;
300: int n = 1;
301: if( m_ix + 1 < m_buffer.size() ) {
302: QChar t = m_buffer[m_ix + 1];
303: if( t == 'Q' || t == 'W' || t == 's' )
304: ++n;
305: else if( t == 'q' || t == 'w' ) {
306: ++n;
307: type = SQ_STRING;
308: } else if( t == 'r' ) {
309: ++n;
310: type = REGEXP;
311: } else if( t == 'x' ) {
312: ++n;
313: type = BQ_STRING;
314: }
315: QChar qc;
316: if( m_ix + n < m_buffer.size() &&
317: isAsciiSymbol(qc = m_buffer[m_ix + n]) )
318: {
319: char quote = (char)m_buffer[m_ix + n].unicode();
320: char openQuote = quote;
321: int i = QString("([{<").indexOf(quote);
322: if( i >= 0 ) quote = ")]}>"[i];
323: if( t == 'r' )
324: return regexp(quote, n + 1, openQuote);
325: else if( t == 's' )
326: return percentSymbol(quote, n + 1);
327: else
328: return nextString(quote, n + 1, type, openQuote);
329: }
330: }
331: }
332: }
333: if( inBrace() && m_buffer[m_ix] == '}' ) {
334: //m_inBrace = false;
335: popNestLevelAndQuote();
336: uchar rc = nextString();
337: m_quoteLength = 1; // for '}'
338: return rc;
339: }
340: if( m_ix + 7 < m_buffer.length()
341: && m_buffer.mid(m_ix, 7) == QString("?\\M-\\C-") )
342: {
343: m_tokenText = m_buffer.mid(m_ix, 8);
344: m_ix += 8;
345: return m_tokenType = NUMBER;
346: }
347: if( m_buffer[m_ix] == '?' && m_ix + 4 < m_buffer.length()
348: && m_buffer[m_ix + 1] == '\\'
349: && (m_buffer[m_ix + 2] == 'C' || m_buffer[m_ix + 2] == 'M')
350: && m_buffer[m_ix + 3] == '-' )
351: {
352: m_tokenText = m_buffer.mid(m_ix, 5);
353: m_ix += 5;
354: return m_tokenType = NUMBER;
355: }
356: m_tokenText = m_buffer[m_ix++];
357: if( m_prevText == "def" ) { // def 直後の記号列はメソッド名とみなす
358: while( m_ix < m_buffer.length() && m_buffer[m_ix] != '('
359: && isAsciiSymbol(m_buffer[m_ix]) )
360: {
361: m_tokenText += m_buffer[m_ix++];
362: }
363: return m_tokenType = SYMBOL;
364: }
365: if( m_tokenText == "/" ) {
366: if( isJustBeforeRegexp(m_prevText, m_prev2Text) ) {
367: return regexp('/', 1);
368: }
369: }
370: if( m_tokenText == "$" && m_ix < m_buffer.length() && !m_buffer[m_ix].isLetter() ) {
371: m_tokenText += m_buffer[m_ix++];
372: } else if( m_tokenText == QChar('<')
373: && m_ix < m_buffer.length()
374: && m_buffer[m_ix] == QChar('<') ) // << はひとつのトークンにする
375: {
376: m_tokenText += QChar('<');
377: ++m_ix;
378: } else if( (m_tokenText == QChar('!') || m_tokenText == QChar('=') ||
379: m_tokenText == QChar('>') || m_tokenText == QChar('<'))
380: && m_ix < m_buffer.length()
381: && m_buffer[m_ix] == QChar('=') ) // == または != または >= または <= はひとつのトークンにする
382: {
383: m_tokenText += QChar('=');
384: ++m_ix;
385: } else if( (m_tokenText == ":" || m_tokenText == ".")
386: && m_ix < m_buffer.length() && m_tokenText == m_buffer[m_ix] )
387: {
388: m_tokenText += m_buffer[m_ix]; // ::
389: ++m_ix;
390: }
391: return m_tokenType = SYMBOL;
392: }
393: bool ViewTokenizer::inBrace() const
394: {
395: return !m_quoteStack.isEmpty() && m_quoteStack.last().m_type == EXPAND_EXP;
396: }
397: