Back
1 //------------------------------------------------------------------------------
2 // Module: Language.cpp //
3 // //
4 // Class which encapsulates a programming language's definition //
5 // //
6 // Copyright (c) 2004 by Lars Haendel //
7 // Home: http://www.newty.de //
8 // //
9 // This program is free software and can be used under the terms of the //
10 // GNU licence. See header-file for further information and disclaimer. //
11 // //
12 //------------------------------------------------------------------------------
13
14 #include <iomanip> // due to: setw()
15
16
17 #include "Language.h"
18 #include "FileUtil.h" // file utilities
19 #include "NameUtil.h" // GetFileName()
20 #include "ErrText.h" // TErrText
21
22
23
24 //----------------------------------------------------------------------------------------------------------------------
25 // defines
26
27 // misc.
28 #define SZ_NAME "Name" // language's name
29 #define SZ_SYMBOLS "Symbols"
30 #define SZ_EXTENSION "Extensions" // common/typical file extensions
31 #define SZ_CASE_SENSITIVE "CaseSensitive" // do not ignore case when searching in word lists
32 #define SZ_ALLOW_WHITE_AFTER_FIRST "AllowWhiteAfterFirst" // allow white spaces after first character
33 #define SZ_CHARS_WITHIN_STRING "CharsWithinString" // chars that may be within strings
34 #define DEF_CASE_SENSITIVE true // language is case sensitive
35 #define DEF_ALLOW_WHITE_AFTER_FIRST true
36
37 // regex and identifier type exchange info
38 #define SZ_REGEX "Regex" // regex string
39 #define SZ_TYPE "Type" // item type identified by regex
40 #define SZ_EXCH_TYPE "ExchangeType" // exchange identifier type after regex
41 #define SZ_END_SEQUENCE "EndSequence" // stop sequence for identifier type exchange
42
43
44 // strings
45 #define SZ_STRING "String" // character that starts/ends a string
46 #define SZ_CHARACTER "Character" // character that starts/ends a character/string
47 #define SZ_ESCAPE_CHAR "EscapeChar" // escape character in strings
48 #define SZ_SILLY_STRING_HANDLING "SillyStringHandling" // the string is extended to the next line(s) until the
49 // closing character is found
50
51 #define DEF_STRING '\0'
52 #define DEF_CHARACTER '\0'
53 #define DEF_ESCAPE_CHAR '\0' // escape character in strings
54 #define DEF_SILLY_STRING_HANDLING false
55
56 // comments
57 #define SZ_SINGLE_LINE_COM "SingleLineComment" // start sequence for single line comments
58 #define SZ_MULTI_LINE_COM "MultiLineComment" // start and end sequence for multi line comments
59
60
61 // word lists
62 #define SZ_KEYWORDS "Keywords" // keyword list
63 #define SZ_PREPROCESSOR "Preprocessor" // list with words that start a preprocessor directive
64 #define SZ_WORDLIST "Words" // user word list
65
66 #define WIDTH 20
67
68
69 //----------------------------------------------------------------------------------------------------------------------
70 // try to read type and identifier type exchange information (TInfo object)
71 void ReadTypeAndExchInfo(ifstream& file, TLanguage::TInfo* info, const char*const& szBaseKey)
72 {
73 char szKey[256], szText[256], szType[MAX_TYPE_LEN], szEndSeq[MAX_SEQUENCE_LEN];
74
75 // 1. if specified: read type and check it
76 // a) read
77 sprintf(szKey, "%s%s", szBaseKey, SZ_TYPE); // compose key
78 ReadKeyString(file, szKey, szType, MAX_TYPE_LEN, SEARCH_LINES); // try to read
79 int typeId1 = IdentifyItemName(szType);
80
81 // b) check type and throw exception if no or invalid type was read ...
82 if(typeId1<0 || typeId1==TItemStyle::Bkgnd)
83 {
84 // compose text and throw exception
85 sprintf(szText, "Unknown or illegal item type defined for key '%s'!", szKey);
86 throw TErrText(szText);
87 }
88
89 // 2. try to read type for identifier type exchange and check it
90 sprintf(szKey, "%s%s", szBaseKey, SZ_EXCH_TYPE); // compose key
91 ReadKeyString(file, szKey, szType, MAX_TYPE_LEN, SEARCH_LINES); // try to read
92 int typeId2 = IdentifyItemName(szType);
93
94 // check
95 if(szType[0]!='\0')
96 {
97 if(typeId2<0 || typeId2==TItemStyle::Bkgnd)
98 {
99 // compose text and throw exception
100 sprintf(szText, "Unknown or illegal item type defined for key '%s'!", szKey);
101 throw TErrText(szText);
102 }
103 }
104
105
106 // 3. try to read end sequence for identifier type exchange and check it
107 sprintf(szKey, "%s%s", szBaseKey, SZ_END_SEQUENCE); // compose key
108 ReadKeyString(file, szKey, szEndSeq, MAX_SEQUENCE_LEN, SEARCH_LINES); // try to read
109
110 // check
111 if(szType[0]!='\0') // if identifier type exchange was read ...
112 if(szEndSeq[0]=='\0') // ... then throw excpetion if no end sequence was read
113 {
114 // ... compose text and throw exception
115 sprintf(szText, "No ending sequence defined! Use key '%s' to define one!", szKey);
116 throw TErrText(szText);
117 }
118
119 // insert regex type and identifier type exchange info in list ...
120 info->type1 = (TItemStyle::ItemType) typeId1; // regex type
121 info->type2 = (TItemStyle::ItemType) typeId2; // identifier type exchange type
122 strcpy(info->szEndSeq, szEndSeq); // identifier type exchange end sequence
123 }
124
125
126 //----------------------------------------------------------------------------------------------------------------------
127 // save type and identifier type exchange information (TInfo object)
128 void SaveTypeAndExchInfo(ofstream& file, const TLanguage::TInfo*const& info, const char*const& szBaseKey
129 , const bool& f_SaveType=false)
130 {
131 char szKey[256];
132
133 // compose key for type and write it
134 if(f_SaveType)
135 {
136 sprintf(szKey, "%s%s", szBaseKey, SZ_TYPE);
137 file << setw(WIDTH) << szKey << " = " << GetItemName(info->type1) << endl;
138 }
139
140 // if identifier type exchange info exists: compose key write it
141 if(info->type2>=0)
142 {
143 // type for identifier type exchange
144 sprintf(szKey, "%s%s", szBaseKey, SZ_EXCH_TYPE);
145 file << setw(WIDTH) << szKey << " = " << GetItemName(info->type2) << endl;
146
147 // end sequence
148 sprintf(szKey, "%s%s", szBaseKey, SZ_END_SEQUENCE);
149 file << setw(WIDTH) << szKey << " = \"" << info->szEndSeq << "\"" << endl;
150 }
151 }
152
153
154 //----------------------------------------------------------------------------------------------------------------------
155 // symbols that can be in a number
156 // Note: '.', '+' and '-' are not inlcuded here as there is taken care of in Conv2Html.cpp
157 static const int _nNumberEndingSymbols = 23;
158 static const char szNumberEndingSymbols[_nNumberEndingSymbols] =
159 { '^', '|', '~', '?', '&', '%', '!', ',', ';', ':', '"', '\'',
160 '=', '*', '<', '>', '(', ')', '{', '}', '[', ']', '/' };
161
162
163 //----------------------------------------------------------------------------------------------------------------------
164 // constructor
165 TLanguage::TLanguage()
166 {
167 szSingleLineCommentStart = szMultiLineCommentStart = szMultiLineCommentEnd = NULL;
168 nSingleLineCommentStart = nMultiLineCommentStart = nMultiLineCommentEnd = 0;
169 }
170
171
172 //----------------------------------------------------------------------------------------------------------------------
173 // destructor
174 TLanguage::~TLanguage()
175 {
176 // clear memory
177 delete[] szSingleLineCommentStart;
178 delete[] szMultiLineCommentStart;
179 delete[] szMultiLineCommentEnd;
180 delete[] nSingleLineCommentStart;
181 delete[] nMultiLineCommentStart;
182 delete[] nMultiLineCommentEnd;
183 }
184
185
186 //----------------------------------------------------------------------------------------------------------------------
187 // parse single/multi line comment strings
188 void TLanguage::ParseCommentStrings()
189 {
190 // count number of comment starting/ending sequences
191 nSingleLineComments = CountWords(szSingleLineComment);
192 nMultiLineComments = CountWords(szMultiLineComment)/2;
193
194 // allocate memory for parsed comment start/end sequences
195 szSingleLineCommentStart = new ComSeq[nSingleLineComments];
196 szMultiLineCommentStart = new ComSeq[nMultiLineComments];
197 szMultiLineCommentEnd = new ComSeq[nMultiLineComments];
198 nSingleLineCommentStart = new int[nSingleLineComments];
199 nMultiLineCommentStart = new int[nMultiLineComments];
200 nMultiLineCommentEnd = new int[nMultiLineComments];
201
202 // extract single line comment starting sequences
203 int i=0; // ini string position
204 for(int nSeq=0;nSeq<nSingleLineComments;nSeq++)
205 {
206 nSingleLineCommentStart[nSeq] = GetLengthOfFirstWord(&(szSingleLineComment[i])); // get length of sequence
207 int j=0;
208 for(;j<nSingleLineCommentStart[nSeq];j++) // copy all characters of sequence
209 szSingleLineCommentStart[nSeq][j] = szSingleLineComment[i+j];
210 szSingleLineCommentStart[nSeq][j] = '\0'; // add terminating '\0'
211
212 i += j; // increment string position
213 while(isspace(szSingleLineComment[i]))
214 i++; // skip all whitespaces
215 }
216
217 // extract multi line comment starting/ending sequences
218 i=0; // ini string position
219 for(int nSeq=0;nSeq<nMultiLineComments;nSeq++)
220 {
221 nMultiLineCommentStart[nSeq] = GetLengthOfFirstWord(&(szMultiLineComment[i])); // get length of sequence
222 int j=0;
223 for(;j<nMultiLineCommentStart[nSeq];j++) // copy all characters of sequence
224 szMultiLineCommentStart[nSeq][j] = szMultiLineComment[i+j];
225 szMultiLineCommentStart[nSeq][j] = '\0'; // add terminating '\0'
226 i += j; // increment string position
227 while(isspace(szMultiLineComment[i]))
228 i++; // skip all whitespaces
229
230
231 nMultiLineCommentEnd[nSeq] = GetLengthOfFirstWord(&(szMultiLineComment[i])); // get length of sequence
232 j=0;
233 for(;j<nMultiLineCommentEnd[nSeq];j++) // copy all characters of sequence
234 szMultiLineCommentEnd[nSeq][j] = szMultiLineComment[i+j];
235 szMultiLineCommentEnd[nSeq][j] = '\0'; // add terminating '\0'
236
237
238 i += j; // increment string position
239 while(isspace(szMultiLineComment[i]))
240 i++; // skip all whitespaces
241 }
242 }
243
244
245 //----------------------------------------------------------------------------------------------------------------------
246 // load language definition from file section
247 void TLanguage::Load(ifstream& file)
248 {
249 // read language definition
250 char szText[256]; // string used to compose error texts
251 try
252 {
253 //----------------------------------------------------------------------------------------------------------------
254 // a) read misc. settings
255
256 // language's name
257 ReadKeyString(file, SZ_NAME, szName, MAX_NAME_LEN, SEARCH_LINES);
258 if(szName[0]=='\0')
259 {
260 sprintf(szText, "Language name missing! Please specify it using key: '%s'", SZ_NAME); // compose text
261 throw TErrText(szText); // throw exception
262 }
263
264 // flags
265 f_CaseSensitive = ReadKeyBool(file, SZ_CASE_SENSITIVE, DEF_CASE_SENSITIVE, SEARCH_LINES);
266 f_AllowWhiteAfterFirst = ReadKeyBool(file, SZ_ALLOW_WHITE_AFTER_FIRST, DEF_ALLOW_WHITE_AFTER_FIRST, SEARCH_LINES);
267
268
269 // read symbols, common file extensions and chars that are allowed to be within strings
270 ReadKeyString(file, SZ_SYMBOLS, szSymbols, MAX_SYMBOL_LEN, SEARCH_LINES);
271 ReadKeyString(file, SZ_EXTENSION, szExtensions, STS, SEARCH_LINES);
272 ReadKeyString(file, SZ_CHARS_WITHIN_STRING, szCharsWithinString, MAX_SYMBOL_LEN, SEARCH_LINES);
273
274 // determine # of symbols
275 _nSymbols = SizeOfString(szSymbols);
276 _nCharsWithinString = SizeOfString(szCharsWithinString);
277
278
279 //----------------------------------------------------------------------------------------------------------------
280 // b) try to read regular expression settings
281 char szRegex[MAX_REGEX_LEN];
282 do
283 {
284 char szRegexKey[256];
285 sprintf(szRegexKey, "%s%d", SZ_REGEX, (int) (regex.Size()+1)); // compose key and ...
286 ReadKeyString(file, szRegexKey, szRegex, MAX_REGEX_LEN, SEARCH_LINES); // ... try to read regex string
287
288
289 // if regex string was read in ...
290 if(szRegex[0]!='\0')
291 {
292 // insert regex in regex list ...
293 try
294 {
295 regex.Ins().SetRegex(szRegex); // insert regex string
296 }
297 catch(TErrText err) // ... and if an error occured
298 {
299 sprintf(szText, "Key '%s': %s", szRegexKey, err.szErrText); // compose text
300 throw TErrText(szText); // re-throw exception
301 }
302
303 // try to read corresponding regex type and identifier type exchange info
304 ReadTypeAndExchInfo(file, ®exInfo.Ins(), szRegexKey);
305 }
306 } while(szRegex[0]!='\0'); // repeat as long as regex was read
307
308
309
310 //----------------------------------------------------------------------------------------------------------------
311 // c) read string settings
312 cString = ReadKeyValue(file, SZ_STRING, DEF_STRING, SEARCH_LINES);
313 cCharacter = ReadKeyValue(file, SZ_CHARACTER, DEF_CHARACTER, SEARCH_LINES);
314 cEscapeChar = ReadKeyValue(file, SZ_ESCAPE_CHAR, DEF_ESCAPE_CHAR, SEARCH_LINES);
315 f_SillyStringHandling = ReadKeyBool (file, SZ_SILLY_STRING_HANDLING, DEF_SILLY_STRING_HANDLING, SEARCH_LINES);
316
317 //----------------------------------------------------------------------------------------------------------------
318 // d) read comment settings
319 ReadKeyString(file, SZ_SINGLE_LINE_COM, szSingleLineComment, STS, SEARCH_LINES);
320 ReadKeyString(file, SZ_MULTI_LINE_COM, szMultiLineComment, STS, SEARCH_LINES);
321
322 ParseCommentStrings(); // parse comment strings
323
324 //----------------------------------------------------------------------------------------------------------------
325 // e) read word lists
326 char szKey[256];
327 keywords.Load(file, SZ_KEYWORDS, f_CaseSensitive); // keyword list
328 preproc.Load(file, SZ_PREPROCESSOR, f_CaseSensitive); // list with words that start a preprocessor directive
329
330 bool f_ReadIn;
331 do
332 {
333 char szWordListKey[256];
334 sprintf(szWordListKey, "%s%d", SZ_WORDLIST, (int) (wordLists.Size()+1)); // compose key and ...
335 wordLists.Ins().Load(file, szWordListKey, f_CaseSensitive); // ... try to read another word list
336
337 // if another word list was read in ... try to read identifier type exchange info
338 f_ReadIn = (wordLists.Get().nWords()>0);
339 if(f_ReadIn)
340 ReadTypeAndExchInfo(file, &wordListInfo.Ins(), szWordListKey);
341 else
342 wordLists.Del(); // delete entry again
343
344 } while(f_ReadIn); // repeat as long another word list was read
345
346 }
347 catch(int errNo) // exception handling
348 {
349 sprintf(szText, "Reading key '%s': %s", GetLastKey(), GetLastError(errNo)); // compose text
350 throw TErrText(szText); // propagate exception
351 }
352 }
353
354
355 //----------------------------------------------------------------------------------------------------------------------
356 // write language definition to file section
357 void TLanguage::Save(ofstream& file) const
358 {
359 // set left justified output
360 file << setiosflags(ios::left) << resetiosflags(ios::right);
361
362
363 //----------------------------------------------------------------------------------------------------------------
364 // a) misc. settings
365 file << endl;
366
367 // language's name
368 file << setw(WIDTH) << SZ_NAME << " = \"" << szName << "\"" << endl;
369
370 // symbols
371 if(szSymbols[0]!='\0')
372 file << setw(WIDTH) << SZ_SYMBOLS << " = \"" << szSymbols << "\"" << endl;
373
374 // common file extensions
375 if(szExtensions[0]!='\0')
376 file << setw(WIDTH) << SZ_EXTENSION << " = \"" << szExtensions << "\"" << endl;
377
378 // characters that may occur within keywords/names
379 if(szCharsWithinString[0]!='\0')
380 file << setw(WIDTH) << SZ_CHARS_WITHIN_STRING << " = \"" << szCharsWithinString << "\"" << endl;
381
382 // flags
383 file << setw(WIDTH) << SZ_CASE_SENSITIVE << " = " << FlagToString(f_CaseSensitive) << endl;
384 file << setw(WIDTH) << SZ_ALLOW_WHITE_AFTER_FIRST << " = " << FlagToString(f_AllowWhiteAfterFirst) << endl;
385
386
387 // regex
388 if(regex.Size()>0)
389 for(int i=0;i<regex.Size();i++)
390 {
391 // compose key for regex itself and write it
392 char szKey[256];
393 sprintf(szKey, "%s%d", SZ_REGEX, (int) (i+1));
394 file << setw(WIDTH) << szKey << " = \"" << regex.Get(i).GetRegexString() << "\"" << endl;
395
396 // save type and identifier type exchange info (TInfo object)
397 SaveTypeAndExchInfo(file, ®exInfo.Get(i), szKey, true);
398 }
399
400
401 //----------------------------------------------------------------------------------------------------------------
402 // b) string settings
403 if(cString!='\0')
404 file << setw(WIDTH) << SZ_STRING << " = " << cString << endl;
405 if(cCharacter!='\0')
406 file << setw(WIDTH) << SZ_CHARACTER << " = " << cCharacter << endl;
407 if(cEscapeChar!='\0')
408 file << setw(WIDTH) << SZ_ESCAPE_CHAR << " = " << cEscapeChar << endl;
409 file << setw(WIDTH) << SZ_SILLY_STRING_HANDLING << " = " << FlagToString(f_SillyStringHandling) << endl;
410
411
412 //----------------------------------------------------------------------------------------------------------------
413 // c) comment settings
414 if(szSingleLineComment[0]!='\0')
415 file << setw(WIDTH) << SZ_SINGLE_LINE_COM << " = \"" << szSingleLineComment << "\"" << endl;
416 if(szMultiLineComment[0]!='\0')
417 file << setw(WIDTH) << SZ_MULTI_LINE_COM << " = \"" << szMultiLineComment << "\"" << endl;
418
419
420 //----------------------------------------------------------------------------------------------------------------
421 // d) word lists
422 keywords.Save(file, SZ_KEYWORDS, WIDTH); // keyword list
423 preproc.Save(file, SZ_PREPROCESSOR, WIDTH); // list with words that start a preprocessor directive
424
425 // user word lists
426 if(wordLists.Size()>0)
427 for(int i=0;i<wordLists.Size();i++)
428 {
429 // compose key and write word list
430 char szKey[256];
431 sprintf(szKey, "%s%d", SZ_WORDLIST, (int) (i+1));
432 wordLists.Get(i).Save(file, szKey, WIDTH);
433
434 // save type and identifier type exchange info (TInfo object)
435 SaveTypeAndExchInfo(file, &wordListInfo.Get(i), szKey, true);
436 }
437
438 file << resetiosflags(ios::left) << setiosflags(ios::right);
439 }
440
441
442 //----------------------------------------------------------------------------------------------------------------------
443 // returns true if passed character is within passed string
444 bool TLanguage::IsWithinString(const char& cChar, const char*const& szString, const int& len)
445 {
446 for(int i=0;i<len;i++)
447 if(szString[i]==cChar)
448 return true;
449 return false;
450 }
451
452
453 //----------------------------------------------------------------------------------------------------------------------
454 // returns true if passed string starts with specified sequence
455 int TLanguage::SequenceStartsString(const char*const& szString, const char*const& szSequence, const int& nSeq)
456 {
457 for(int i=0;i<nSeq;i++)
458 if(szString[i]!=szSequence[i] || szString[i]=='\0')
459 return 0; // does not match, return '0'
460
461 return nSeq; // match: return # of characters in sequence
462 }
463
464
465 //----------------------------------------------------------------------------------------------------------------------
466 // returns true if passed character cannot be within a number
467 bool TLanguage::SymbolEndsNumber(const char& cChar) const
468 {
469 // check against hard coded symbols
470 return IsWithinString(cChar, szNumberEndingSymbols, _nNumberEndingSymbols);
471 }
472
473
474 //----------------------------------------------------------------------------------------------------------------------
475 // returns true if passed character string starts a preprocessor
476 int TLanguage::StartsPreproc(const char*const& szString, const int& nChar) const
477 {
478 char szWord[MAX_WORD_LEN]; // will be compared to preprocessor word list
479 int i=0, t;
480
481 // a) copy all characters to 'szWord' note: 'nChar' is the # of characters of the first name in passed string
482 for(t=0;t<nChar;t++)
483 szWord[i++] = szString[t];
484
485
486 // b) if enabled and if only one character is copied to word so far ...
487 if(nChar==1 && f_AllowWhiteAfterFirst)
488 {
489 // skip all following white spaces
490 while(szString[t]!='\0')
491 if(szString[t]== ' ' || szString[t]== '\t')
492 t++;
493 else
494 break;
495
496 // and append following name to 'szWord'
497 while(szString[t]!='\0')
498 {
499 // accept '_', all numbers [0..9] and all characters [A..Z] and [a..z]
500 if(szString[t]!=95 && (szString[t]<48 || szString[t]>57) && (szString[t]<65 || szString[t]>90)
501 && (szString[t]<97 || szString[t]>122))
502 break;
503
504 szWord[i++] = szString[t++]; // copy to 'szWord'
505 }
506 }
507
508 szWord[i] = '\0'; // terminate
509 if(preproc.IsInList(szWord)) // return # of preprocessor characters or zero if no match was found
510 return i;
511 else
512 return 0;
513 }
514
515
516 //----------------------------------------------------------------------------------------------------------------------
517 // returns true if passed string is started with a sequence that starts a multi line comment
518 int TLanguage::StartsMultiLineComment(const char*const& szString) const
519 {
520 for(int i=0;i<nMultiLineComments;i++)
521 {
522 int nChar = SequenceStartsString(szString, szMultiLineCommentStart[i], nMultiLineCommentStart[i]);
523 if(nChar>0)
524 return nChar; // match found, return number of matching characters
525 }
526 return 0; // no match found
527 }
528
529
530 //----------------------------------------------------------------------------------------------------------------------
531 // returns true if passed string is started with a sequence that ends a multi line comment
532 int TLanguage::EndsMultiLineComment(const char*const& szString) const
533 {
534 for(int i=0;i<nMultiLineComments;i++)
535 {
536 int nChar = SequenceStartsString(szString, szMultiLineCommentEnd[i], nMultiLineCommentEnd[i]);
537 if(nChar>0)
538 return nChar; // match found, return number of matching characters
539 }
540 return 0; // no match found
541 }
542
543 //----------------------------------------------------------------------------------------------------------------------
544 // returns true if passed string is started with a sequence that starts a single line comment
545 int TLanguage::StartsSingleLineComment(const char*const& szString) const
546 {
547 for(int i=0;i<nSingleLineComments;i++)
548 {
549 int nChar = SequenceStartsString(szString, szSingleLineCommentStart[i], nSingleLineCommentStart[i]);
550 if(nChar>0)
551 return nChar; // match found, return number of matching characters
552 }
553 return 0; // no match found
554 }
555
556 //----------------------------------------------------------------------------------------------------------------------
557 // returns true if passed string is started with a sequence that matches one of the regexes
558 int TLanguage::StartsWithRegex(const char*const& szString) const
559 {
560 for(int i=0;i<regex.Size();i++) // check all regular expressions
561 {
562 int nChar = regex.Get(i).StartsWithRegex(szString);
563 if(nChar>0) // if match is found ...
564 {
565 lastRegexId = i; // store regex id
566 return nChar; // return # matching characters
567 }
568 }
569
570 return 0;
571 }
572
573
574 //----------------------------------------------------------------------------------------------------------------------
575 // returns true if passed word is found in one of the user word lists
576 bool TLanguage::IsUserWord(const char*const& szWord) const
577 {
578 for(int i=0;i<wordLists.Size();i++) // check all word lists
579 {
580 bool f_Found = wordLists.Get(i).IsInList(szWord);
581 if(f_Found) // if match is found ...
582 {
583 lastWordId = i; // store word id
584 return true; // return true
585 }
586 }
587
588 return false;
589 }
Top |