]> gitweb.fperrin.net Git - Dictionary.git/blob - jars/icu4j-4_2_1-src/src/com/ibm/icu/text/TransliteratorParser.java
icu4jsrc
[Dictionary.git] / jars / icu4j-4_2_1-src / src / com / ibm / icu / text / TransliteratorParser.java
1 //##header\r
2 /*\r
3 **********************************************************************\r
4 *   Copyright (c) 2001-2009, International Business Machines\r
5 *   Corporation and others.  All Rights Reserved.\r
6 **********************************************************************\r
7 */\r
8 package com.ibm.icu.text;\r
9 \r
10 import com.ibm.icu.impl.IllegalIcuArgumentException;\r
11 import com.ibm.icu.impl.Utility;\r
12 \r
13 import java.util.ArrayList;\r
14 import java.util.List;\r
15 import java.util.Vector;\r
16 import java.util.Hashtable;\r
17 import java.text.ParsePosition;\r
18 import com.ibm.icu.lang.*;\r
19 import com.ibm.icu.impl.UCharacterProperty;\r
20 \r
21 class TransliteratorParser {\r
22 \r
23     //----------------------------------------------------------------------\r
24     // Data members\r
25     //----------------------------------------------------------------------\r
26 \r
27     /**\r
28      * PUBLIC data member.\r
29      * A Vector of RuleBasedTransliterator.Data objects, one for each discrete group\r
30      * of rules in the rule set\r
31      */\r
32     public Vector dataVector;\r
33 \r
34     /**\r
35      * PUBLIC data member.\r
36      * A Vector of Strings containing all of the ID blocks in the rule set\r
37      */\r
38     public Vector idBlockVector;\r
39 \r
40     /**\r
41      * The current data object for which we are parsing rules\r
42      */\r
43     private RuleBasedTransliterator.Data curData;\r
44 \r
45     /**\r
46      * PUBLIC data member containing the parsed compound filter, if any.\r
47      */\r
48     public UnicodeSet compoundFilter;\r
49 \r
50 \r
51     private int direction;\r
52 \r
53     /**\r
54      * Temporary symbol table used during parsing.\r
55      */\r
56     private ParseData parseData;\r
57 \r
58     /**\r
59      * Temporary vector of set variables.  When parsing is complete, this\r
60      * is copied into the array data.variables.  As with data.variables,\r
61      * element 0 corresponds to character data.variablesBase.\r
62      */\r
63     private Vector variablesVector;\r
64 \r
65     /**\r
66      * Temporary table of variable names.  When parsing is complete, this is\r
67      * copied into data.variableNames.\r
68      */\r
69     private Hashtable variableNames;\r
70 \r
71     /**\r
72      * String of standins for segments.  Used during the parsing of a single\r
73      * rule.  segmentStandins.charAt(0) is the standin for "$1" and corresponds\r
74      * to StringMatcher object segmentObjects.elementAt(0), etc.\r
75      */\r
76     private StringBuffer segmentStandins;\r
77 \r
78     /**\r
79      * Vector of StringMatcher objects for segments.  Used during the\r
80      * parsing of a single rule.  \r
81      * segmentStandins.charAt(0) is the standin for "$1" and corresponds\r
82      * to StringMatcher object segmentObjects.elementAt(0), etc.\r
83      */\r
84     private Vector segmentObjects;\r
85 \r
86     /**\r
87      * The next available stand-in for variables.  This starts at some point in\r
88      * the private use area (discovered dynamically) and increments up toward\r
89      * <code>variableLimit</code>.  At any point during parsing, available\r
90      * variables are <code>variableNext..variableLimit-1</code>.\r
91      */\r
92     private char variableNext;\r
93 \r
94     /**\r
95      * The last available stand-in for variables.  This is discovered\r
96      * dynamically.  At any point during parsing, available variables are\r
97      * <code>variableNext..variableLimit-1</code>.  During variable definition\r
98      * we use the special value variableLimit-1 as a placeholder.\r
99      */\r
100     private char variableLimit;\r
101 \r
102     /**\r
103      * When we encounter an undefined variable, we do not immediately signal\r
104      * an error, in case we are defining this variable, e.g., "$a = [a-z];".\r
105      * Instead, we save the name of the undefined variable, and substitute\r
106      * in the placeholder char variableLimit - 1, and decrement\r
107      * variableLimit.\r
108      */\r
109     private String undefinedVariableName;\r
110 \r
111     /**\r
112      * The stand-in character for the 'dot' set, represented by '.' in\r
113      * patterns.  This is allocated the first time it is needed, and\r
114      * reused thereafter.\r
115      */\r
116     private int dotStandIn = -1;\r
117 \r
118     //----------------------------------------------------------------------\r
119     // Constants\r
120     //----------------------------------------------------------------------\r
121 \r
122     // Indicator for ID blocks\r
123     private static final String ID_TOKEN = "::";\r
124     private static final int ID_TOKEN_LEN = 2;\r
125 \r
126 /*\r
127 (reserved for future expansion)\r
128     // markers for beginning and end of rule groups\r
129     private static final String BEGIN_TOKEN = "BEGIN";\r
130     private static final String END_TOKEN = "END";\r
131 */\r
132 \r
133     // Operators\r
134     private static final char VARIABLE_DEF_OP   = '=';\r
135     private static final char FORWARD_RULE_OP   = '>';\r
136     private static final char REVERSE_RULE_OP   = '<';\r
137     private static final char FWDREV_RULE_OP    = '~'; // internal rep of <> op\r
138 \r
139     private static final String OPERATORS = "=><\u2190\u2192\u2194";\r
140     private static final String HALF_ENDERS = "=><\u2190\u2192\u2194;";\r
141 \r
142     // Other special characters\r
143     private static final char QUOTE               = '\'';\r
144     private static final char ESCAPE              = '\\';\r
145     private static final char END_OF_RULE         = ';';\r
146     private static final char RULE_COMMENT_CHAR   = '#';\r
147 \r
148     private static final char CONTEXT_ANTE        = '{'; // ante{key\r
149     private static final char CONTEXT_POST        = '}'; // key}post\r
150     private static final char CURSOR_POS          = '|';\r
151     private static final char CURSOR_OFFSET       = '@';\r
152     private static final char ANCHOR_START        = '^';\r
153 \r
154     private static final char KLEENE_STAR         = '*';\r
155     private static final char ONE_OR_MORE         = '+';\r
156     private static final char ZERO_OR_ONE         = '?';\r
157 \r
158     private static final char DOT                 = '.';\r
159     private static final String DOT_SET           = "[^[:Zp:][:Zl:]\\r\\n$]";\r
160 \r
161     // By definition, the ANCHOR_END special character is a\r
162     // trailing SymbolTable.SYMBOL_REF character.\r
163     // private static final char ANCHOR_END       = '$';\r
164 \r
165     // Segments of the input string are delimited by "(" and ")".  In the\r
166     // output string these segments are referenced as "$1", "$2", etc.\r
167     private static final char SEGMENT_OPEN        = '(';\r
168     private static final char SEGMENT_CLOSE       = ')';\r
169 \r
170     // A function is denoted &Source-Target/Variant(text)\r
171     private static final char FUNCTION            = '&';\r
172 \r
173     // Aliases for some of the syntax characters. These are provided so\r
174     // transliteration rules can be expressed in XML without clashing with\r
175     // XML syntax characters '<', '>', and '&'.\r
176     private static final char ALT_REVERSE_RULE_OP = '\u2190'; // Left Arrow\r
177     private static final char ALT_FORWARD_RULE_OP = '\u2192'; // Right Arrow\r
178     private static final char ALT_FWDREV_RULE_OP  = '\u2194'; // Left Right Arrow\r
179     private static final char ALT_FUNCTION        = '\u2206'; // Increment (~Greek Capital Delta)\r
180     \r
181     // Special characters disallowed at the top level\r
182     private static UnicodeSet ILLEGAL_TOP = new UnicodeSet("[\\)]");\r
183 \r
184     // Special characters disallowed within a segment\r
185     private static UnicodeSet ILLEGAL_SEG = new UnicodeSet("[\\{\\}\\|\\@]");\r
186 \r
187     // Special characters disallowed within a function argument\r
188     private static UnicodeSet ILLEGAL_FUNC = new UnicodeSet("[\\^\\(\\.\\*\\+\\?\\{\\}\\|\\@]");\r
189 \r
190     //----------------------------------------------------------------------\r
191     // class ParseData\r
192     //----------------------------------------------------------------------\r
193 \r
194     /**\r
195      * This class implements the SymbolTable interface.  It is used\r
196      * during parsing to give UnicodeSet access to variables that\r
197      * have been defined so far.  Note that it uses variablesVector,\r
198      * _not_ data.variables.\r
199      */\r
200     private class ParseData implements SymbolTable {\r
201 \r
202         /**\r
203          * Implement SymbolTable API.\r
204          */\r
205         public char[] lookup(String name) {\r
206             return (char[]) variableNames.get(name);\r
207         }\r
208 \r
209         /**\r
210          * Implement SymbolTable API.\r
211          */\r
212         public UnicodeMatcher lookupMatcher(int ch) {\r
213             // Note that we cannot use data.lookup() because the\r
214             // set array has not been constructed yet.\r
215             int i = ch - curData.variablesBase;\r
216             if (i >= 0 && i < variablesVector.size()) {\r
217                 return (UnicodeMatcher) variablesVector.elementAt(i);\r
218             }\r
219             return null;\r
220         }\r
221 \r
222         /**\r
223          * Implement SymbolTable API.  Parse out a symbol reference\r
224          * name.\r
225          */\r
226         public String parseReference(String text, ParsePosition pos, int limit) {\r
227             int start = pos.getIndex();\r
228             int i = start;\r
229             while (i < limit) {\r
230                 char c = text.charAt(i);\r
231                 if ((i==start && !UCharacter.isUnicodeIdentifierStart(c)) ||\r
232                     !UCharacter.isUnicodeIdentifierPart(c)) {\r
233                     break;\r
234                 }\r
235                 ++i;\r
236             }\r
237             if (i == start) { // No valid name chars\r
238                 return null;\r
239             }\r
240             pos.setIndex(i);\r
241             return text.substring(start, i);\r
242         }\r
243 \r
244         /**\r
245          * Return true if the given character is a matcher standin or a plain\r
246          * character (non standin).\r
247          */\r
248         public boolean isMatcher(int ch) {\r
249             // Note that we cannot use data.lookup() because the\r
250             // set array has not been constructed yet.\r
251             int i = ch - curData.variablesBase;\r
252             if (i >= 0 && i < variablesVector.size()) {\r
253                 return variablesVector.elementAt(i) instanceof UnicodeMatcher;\r
254             }\r
255             return true;\r
256         }\r
257 \r
258         /**\r
259          * Return true if the given character is a replacer standin or a plain\r
260          * character (non standin).\r
261          */\r
262         public boolean isReplacer(int ch) {\r
263             // Note that we cannot use data.lookup() because the\r
264             // set array has not been constructed yet.\r
265             int i = ch - curData.variablesBase;\r
266             if (i >= 0 && i < variablesVector.size()) {\r
267                 return variablesVector.elementAt(i) instanceof UnicodeReplacer;\r
268             }\r
269             return true;\r
270         }\r
271     }\r
272 \r
273     //----------------------------------------------------------------------\r
274     // classes RuleBody, RuleArray, and RuleReader\r
275     //----------------------------------------------------------------------\r
276 \r
277     /**\r
278      * A private abstract class representing the interface to rule\r
279      * source code that is broken up into lines.  Handles the\r
280      * folding of lines terminated by a backslash.  This folding\r
281      * is limited; it does not account for comments, quotes, or\r
282      * escapes, so its use to be limited.\r
283      */\r
284     private static abstract class RuleBody {\r
285 \r
286         /**\r
287          * Retrieve the next line of the source, or return null if\r
288          * none.  Folds lines terminated by a backslash into the\r
289          * next line, without regard for comments, quotes, or\r
290          * escapes.\r
291          */\r
292         String nextLine() {\r
293             String s = handleNextLine();\r
294             if (s != null &&\r
295                 s.length() > 0 &&\r
296                 s.charAt(s.length() - 1) == '\\') {\r
297 \r
298                 StringBuffer b = new StringBuffer(s);\r
299                 do {\r
300                     b.deleteCharAt(b.length()-1);\r
301                     s = handleNextLine();\r
302                     if (s == null) {\r
303                         break;\r
304                     }\r
305                     b.append(s);\r
306                 } while (s.length() > 0 &&\r
307                          s.charAt(s.length() - 1) == '\\');\r
308 \r
309                 s = b.toString();\r
310             }\r
311             return s;\r
312         }\r
313 \r
314         /**\r
315          * Reset to the first line of the source.\r
316          */\r
317         abstract void reset();\r
318 \r
319         /**\r
320          * Subclass method to return the next line of the source.\r
321          */\r
322         abstract String handleNextLine();\r
323     }\r
324 \r
325     /**\r
326      * RuleBody subclass for a String[] array.\r
327      */\r
328     private static class RuleArray extends RuleBody {\r
329         String[] array;\r
330         int i;\r
331         public RuleArray(String[] array) { this.array = array; i = 0; }\r
332         public String handleNextLine() {\r
333             return (i < array.length) ? array[i++] : null;\r
334         }\r
335         public void reset() {\r
336             i = 0;\r
337         }\r
338     }\r
339 \r
340     /*\r
341      * RuleBody subclass for a ResourceReader.\r
342      */\r
343 /*    private static class RuleReader extends RuleBody {\r
344         ResourceReader reader;\r
345         public RuleReader(ResourceReader reader) { this.reader = reader; }\r
346         public String handleNextLine() {\r
347             try {\r
348                 return reader.readLine();\r
349             } catch (java.io.IOException e) {}\r
350             return null;\r
351         }\r
352         public void reset() {\r
353             reader.reset();\r
354         }\r
355     }*/\r
356 \r
357     //----------------------------------------------------------------------\r
358     // class RuleHalf\r
359     //----------------------------------------------------------------------\r
360 \r
361     /**\r
362      * A class representing one side of a rule.  This class knows how to\r
363      * parse half of a rule.  It is tightly coupled to the method\r
364      * TransliteratorParser.parseRule().\r
365      */\r
366     private static class RuleHalf {\r
367 \r
368         public String text;\r
369 \r
370         public int cursor = -1; // position of cursor in text\r
371         public int ante = -1;   // position of ante context marker '{' in text\r
372         public int post = -1;   // position of post context marker '}' in text\r
373 \r
374         // Record the offset to the cursor either to the left or to the\r
375         // right of the key.  This is indicated by characters on the output\r
376         // side that allow the cursor to be positioned arbitrarily within\r
377         // the matching text.  For example, abc{def} > | @@@ xyz; changes\r
378         // def to xyz and moves the cursor to before abc.  Offset characters\r
379         // must be at the start or end, and they cannot move the cursor past\r
380         // the ante- or postcontext text.  Placeholders are only valid in\r
381         // output text.  The length of the ante and post context is\r
382         // determined at runtime, because of supplementals and quantifiers.\r
383         public int cursorOffset = 0; // only nonzero on output side\r
384 \r
385         // Position of first CURSOR_OFFSET on _right_.  This will be -1\r
386         // for |@, -2 for |@@, etc., and 1 for @|, 2 for @@|, etc.\r
387         private int cursorOffsetPos = 0;\r
388 \r
389         public boolean anchorStart = false;\r
390         public boolean anchorEnd   = false;\r
391 \r
392         /**\r
393          * The segment number from 1..n of the next '(' we see\r
394          * during parsing; 1-based.\r
395          */\r
396         private int nextSegmentNumber = 1;\r
397 \r
398         /**\r
399          * Parse one side of a rule, stopping at either the limit,\r
400          * the END_OF_RULE character, or an operator.\r
401          * @return the index after the terminating character, or\r
402          * if limit was reached, limit\r
403          */\r
404         public int parse(String rule, int pos, int limit,\r
405                          TransliteratorParser parser) {\r
406             int start = pos;\r
407             StringBuffer buf = new StringBuffer();\r
408             pos = parseSection(rule, pos, limit, parser, buf, ILLEGAL_TOP, false);\r
409             text = buf.toString();\r
410 \r
411             if (cursorOffset > 0 && cursor != cursorOffsetPos) {\r
412                 syntaxError("Misplaced " + CURSOR_POS, rule, start);\r
413             }\r
414 \r
415             return pos;\r
416         }\r
417 \r
418         /**\r
419          * Parse a section of one side of a rule, stopping at either\r
420          * the limit, the END_OF_RULE character, an operator, or a\r
421          * segment close character.  This method parses both a\r
422          * top-level rule half and a segment within such a rule half.\r
423          * It calls itself recursively to parse segments and nested\r
424          * segments.\r
425          * @param buf buffer into which to accumulate the rule pattern\r
426          * characters, either literal characters from the rule or\r
427          * standins for UnicodeMatcher objects including segments.\r
428          * @param illegal the set of special characters that is illegal during\r
429          * this parse.\r
430          * @param isSegment if true, then we've already seen a '(' and\r
431          * pos on entry points right after it.  Accumulate everything\r
432          * up to the closing ')', put it in a segment matcher object,\r
433          * generate a standin for it, and add the standin to buf.  As\r
434          * a side effect, update the segments vector with a reference\r
435          * to the segment matcher.  This works recursively for nested\r
436          * segments.  If isSegment is false, just accumulate\r
437          * characters into buf.\r
438          * @return the index after the terminating character, or\r
439          * if limit was reached, limit\r
440          */\r
441         private int parseSection(String rule, int pos, int limit,\r
442                                  TransliteratorParser parser,\r
443                                  StringBuffer buf,\r
444                                  UnicodeSet illegal,\r
445                                  boolean isSegment) {\r
446             int start = pos;\r
447             ParsePosition pp = null;\r
448             int quoteStart = -1; // Most recent 'single quoted string'\r
449             int quoteLimit = -1;\r
450             int varStart = -1; // Most recent $variableReference\r
451             int varLimit = -1;\r
452             int[] iref = new int[1];\r
453             int bufStart = buf.length();\r
454 \r
455         main:\r
456             while (pos < limit) {\r
457                 // Since all syntax characters are in the BMP, fetching\r
458                 // 16-bit code units suffices here.\r
459                 char c = rule.charAt(pos++);\r
460                 if (UCharacterProperty.isRuleWhiteSpace(c)) {\r
461                     continue;\r
462                 }\r
463                 // HALF_ENDERS is all chars that end a rule half: "<>=;"\r
464                 if (HALF_ENDERS.indexOf(c) >= 0) {\r
465                     if (isSegment) {\r
466                         syntaxError("Unclosed segment", rule, start);\r
467                     }\r
468                     break main;\r
469                 }\r
470                 if (anchorEnd) {\r
471                     // Text after a presumed end anchor is a syntax err\r
472                     syntaxError("Malformed variable reference", rule, start);\r
473                 }\r
474                 if (UnicodeSet.resemblesPattern(rule, pos-1)) {\r
475                     if (pp == null) {\r
476                         pp = new ParsePosition(0);\r
477                     }\r
478                     pp.setIndex(pos-1); // Backup to opening '['\r
479                     buf.append(parser.parseSet(rule, pp));\r
480                     pos = pp.getIndex();                    \r
481                     continue;\r
482                 }\r
483                 // Handle escapes\r
484                 if (c == ESCAPE) {\r
485                     if (pos == limit) {\r
486                         syntaxError("Trailing backslash", rule, start);\r
487                     }\r
488                     iref[0] = pos;\r
489                     int escaped = Utility.unescapeAt(rule, iref);\r
490                     pos = iref[0];\r
491                     if (escaped == -1) {\r
492                         syntaxError("Malformed escape", rule, start);\r
493                     }\r
494                     parser.checkVariableRange(escaped, rule, start);\r
495                     UTF16.append(buf, escaped);\r
496                     continue;\r
497                 }\r
498                 // Handle quoted matter\r
499                 if (c == QUOTE) {\r
500                     int iq = rule.indexOf(QUOTE, pos);\r
501                     if (iq == pos) {\r
502                         buf.append(c); // Parse [''] outside quotes as [']\r
503                         ++pos;\r
504                     } else {\r
505                         /* This loop picks up a run of quoted text of the\r
506                          * form 'aaaa' each time through.  If this run\r
507                          * hasn't really ended ('aaaa''bbbb') then it keeps\r
508                          * looping, each time adding on a new run.  When it\r
509                          * reaches the final quote it breaks.\r
510                          */\r
511                         quoteStart = buf.length();\r
512                         for (;;) {\r
513                             if (iq < 0) {\r
514                                 syntaxError("Unterminated quote", rule, start);\r
515                             }\r
516                             buf.append(rule.substring(pos, iq));\r
517                             pos = iq+1;\r
518                             if (pos < limit && rule.charAt(pos) == QUOTE) {\r
519                             // Parse [''] inside quotes as [']\r
520                                 iq = rule.indexOf(QUOTE, pos+1);\r
521                             // Continue looping\r
522                             } else {\r
523                                 break;\r
524                             }\r
525                         }\r
526                         quoteLimit = buf.length();\r
527                         \r
528                         for (iq=quoteStart; iq<quoteLimit; ++iq) {\r
529                             parser.checkVariableRange(buf.charAt(iq), rule, start);\r
530                         }\r
531                     }\r
532                     continue;\r
533                 }\r
534 \r
535                 parser.checkVariableRange(c, rule, start);\r
536 \r
537                 if (illegal.contains(c)) {\r
538                     syntaxError("Illegal character '" + c + '\'', rule, start);\r
539                 }\r
540 \r
541                 switch (c) {\r
542                     \r
543                 //------------------------------------------------------\r
544                 // Elements allowed within and out of segments\r
545                 //------------------------------------------------------\r
546                 case ANCHOR_START:\r
547                     if (buf.length() == 0 && !anchorStart) {\r
548                         anchorStart = true;\r
549                     } else {\r
550                         syntaxError("Misplaced anchor start",\r
551                                     rule, start);\r
552                     }\r
553                     break;\r
554                 case SEGMENT_OPEN:\r
555                     {\r
556                         // bufSegStart is the offset in buf to the first\r
557                         // character of the segment we are parsing.\r
558                         int bufSegStart = buf.length();\r
559 \r
560                         // Record segment number now, since nextSegmentNumber\r
561                         // will be incremented during the call to parseSection\r
562                         // if there are nested segments.\r
563                         int segmentNumber = nextSegmentNumber++; // 1-based\r
564 \r
565                         // Parse the segment\r
566                         pos = parseSection(rule, pos, limit, parser, buf, ILLEGAL_SEG, true);\r
567 \r
568                         // After parsing a segment, the relevant characters are\r
569                         // in buf, starting at offset bufSegStart.  Extract them\r
570                         // into a string matcher, and replace them with a\r
571                         // standin for that matcher.\r
572                         StringMatcher m =\r
573                             new StringMatcher(buf.substring(bufSegStart),\r
574                                               segmentNumber, parser.curData);\r
575 \r
576                         // Record and associate object and segment number\r
577                         parser.setSegmentObject(segmentNumber, m);\r
578                         buf.setLength(bufSegStart);\r
579                         buf.append(parser.getSegmentStandin(segmentNumber));\r
580                     }\r
581                     break;\r
582                 case FUNCTION:\r
583                 case ALT_FUNCTION:\r
584                     {\r
585                         iref[0] = pos;\r
586                         TransliteratorIDParser.SingleID single = TransliteratorIDParser.parseFilterID(rule, iref);\r
587                         // The next character MUST be a segment open\r
588                         if (single == null ||\r
589                             !Utility.parseChar(rule, iref, SEGMENT_OPEN)) {\r
590                             syntaxError("Invalid function", rule, start);\r
591                         }\r
592 \r
593                         Transliterator t = single.getInstance();\r
594                         if (t == null) {\r
595                             syntaxError("Invalid function ID", rule, start);\r
596                         }\r
597 \r
598                         // bufSegStart is the offset in buf to the first\r
599                         // character of the segment we are parsing.\r
600                         int bufSegStart = buf.length();\r
601 \r
602                         // Parse the segment\r
603                         pos = parseSection(rule, iref[0], limit, parser, buf, ILLEGAL_FUNC, true);\r
604 \r
605                         // After parsing a segment, the relevant characters are\r
606                         // in buf, starting at offset bufSegStart.\r
607                         FunctionReplacer r =\r
608                             new FunctionReplacer(t,\r
609                                 new StringReplacer(buf.substring(bufSegStart), parser.curData));\r
610 \r
611                         // Replace the buffer contents with a stand-in\r
612                         buf.setLength(bufSegStart);\r
613                         buf.append(parser.generateStandInFor(r));\r
614                     }\r
615                     break;\r
616                 case SymbolTable.SYMBOL_REF:\r
617                     // Handle variable references and segment references "$1" .. "$9"\r
618                     {\r
619                         // A variable reference must be followed immediately\r
620                         // by a Unicode identifier start and zero or more\r
621                         // Unicode identifier part characters, or by a digit\r
622                         // 1..9 if it is a segment reference.\r
623                         if (pos == limit) {\r
624                             // A variable ref character at the end acts as\r
625                             // an anchor to the context limit, as in perl.\r
626                             anchorEnd = true;\r
627                             break;\r
628                         }\r
629                         // Parse "$1" "$2" .. "$9" .. (no upper limit)\r
630                         c = rule.charAt(pos);\r
631                         int r = UCharacter.digit(c, 10);\r
632                         if (r >= 1 && r <= 9) {\r
633                             iref[0] = pos;\r
634                             r = Utility.parseNumber(rule, iref, 10);\r
635                             if (r < 0) {\r
636                                 syntaxError("Undefined segment reference",\r
637                                             rule, start);\r
638                             }\r
639                             pos = iref[0];\r
640                             buf.append(parser.getSegmentStandin(r));\r
641                         } else {\r
642                             if (pp == null) { // Lazy create\r
643                                 pp = new ParsePosition(0);\r
644                             }\r
645                             pp.setIndex(pos);\r
646                             String name = parser.parseData.\r
647                                 parseReference(rule, pp, limit);\r
648                             if (name == null) {\r
649                                 // This means the '$' was not followed by a\r
650                                 // valid name.  Try to interpret it as an\r
651                                 // end anchor then.  If this also doesn't work\r
652                                 // (if we see a following character) then signal\r
653                                 // an error.\r
654                                 anchorEnd = true;\r
655                                 break;\r
656                             }\r
657                             pos = pp.getIndex();\r
658                             // If this is a variable definition statement,\r
659                             // then the LHS variable will be undefined.  In\r
660                             // that case appendVariableDef() will append the\r
661                             // special placeholder char variableLimit-1.\r
662                             varStart = buf.length();\r
663                             parser.appendVariableDef(name, buf);\r
664                             varLimit = buf.length();\r
665                         }\r
666                     }\r
667                     break;\r
668                 case DOT:\r
669                     buf.append(parser.getDotStandIn());\r
670                     break;\r
671                 case KLEENE_STAR:\r
672                 case ONE_OR_MORE:\r
673                 case ZERO_OR_ONE:\r
674                     // Quantifiers.  We handle single characters, quoted strings,\r
675                     // variable references, and segments.\r
676                     //  a+      matches  aaa\r
677                     //  'foo'+  matches  foofoofoo\r
678                     //  $v+     matches  xyxyxy if $v == xy\r
679                     //  (seg)+  matches  segsegseg\r
680                     {\r
681                         if (isSegment && buf.length() == bufStart) {\r
682                             // The */+ immediately follows '('\r
683                             syntaxError("Misplaced quantifier", rule, start);\r
684                             break;\r
685                         } \r
686  \r
687                         int qstart, qlimit;\r
688                         // The */+ follows an isolated character or quote\r
689                         // or variable reference\r
690                         if (buf.length() == quoteLimit) {\r
691                             // The */+ follows a 'quoted string'\r
692                             qstart = quoteStart;\r
693                             qlimit = quoteLimit;\r
694                         } else if (buf.length() == varLimit) {\r
695                             // The */+ follows a $variableReference\r
696                             qstart = varStart;\r
697                             qlimit = varLimit;\r
698                         } else {\r
699                             // The */+ follows a single character, possibly\r
700                             // a segment standin\r
701                             qstart = buf.length() - 1;\r
702                             qlimit = qstart + 1;\r
703                         }\r
704 \r
705                         UnicodeMatcher m;\r
706                         try {\r
707                             m = new StringMatcher(buf.toString(), qstart, qlimit,\r
708                                               0, parser.curData);\r
709                         } catch (RuntimeException e) {\r
710                             final String precontext = pos < 50 ? rule.substring(0, pos) : "..." + rule.substring(pos - 50, pos);\r
711                             final String postContext = limit-pos <= 50 ? rule.substring(pos, limit) : rule.substring(pos, pos+50) + "...";\r
712                             throw (RuntimeException)\r
713                                 new IllegalIcuArgumentException("Failure in rule: " + precontext + "$$$"\r
714                                         + postContext)\r
715 //#if defined(FOUNDATION10) || defined(J2SE13)\r
716 //#else\r
717                                 .initCause(e)\r
718 //#endif\r
719                                 ;\r
720                         }\r
721                         int min = 0;\r
722                         int max = Quantifier.MAX;\r
723                         switch (c) {\r
724                         case ONE_OR_MORE:\r
725                             min = 1;\r
726                             break;\r
727                         case ZERO_OR_ONE:\r
728                             min = 0;\r
729                             max = 1;\r
730                             break;\r
731                             // case KLEENE_STAR:\r
732                             //    do nothing -- min, max already set\r
733                         }\r
734                         m = new Quantifier(m, min, max);\r
735                         buf.setLength(qstart);\r
736                         buf.append(parser.generateStandInFor(m));\r
737                     }\r
738                     break;\r
739 \r
740                 //------------------------------------------------------\r
741                 // Elements allowed ONLY WITHIN segments\r
742                 //------------------------------------------------------\r
743                 case SEGMENT_CLOSE:\r
744                     // assert(isSegment);\r
745                     // We're done parsing a segment.\r
746                     break main;\r
747 \r
748                 //------------------------------------------------------\r
749                 // Elements allowed ONLY OUTSIDE segments\r
750                 //------------------------------------------------------\r
751                 case CONTEXT_ANTE:\r
752                     if (ante >= 0) {\r
753                         syntaxError("Multiple ante contexts", rule, start);\r
754                     }\r
755                     ante = buf.length();\r
756                     break;\r
757                 case CONTEXT_POST:\r
758                     if (post >= 0) {\r
759                         syntaxError("Multiple post contexts", rule, start);\r
760                     }\r
761                     post = buf.length();\r
762                     break;\r
763                 case CURSOR_POS:\r
764                     if (cursor >= 0) {\r
765                         syntaxError("Multiple cursors", rule, start);\r
766                     }\r
767                     cursor = buf.length();\r
768                     break;\r
769                 case CURSOR_OFFSET:\r
770                     if (cursorOffset < 0) {\r
771                         if (buf.length() > 0) {\r
772                             syntaxError("Misplaced " + c, rule, start);\r
773                         }\r
774                         --cursorOffset;\r
775                     } else if (cursorOffset > 0) {\r
776                         if (buf.length() != cursorOffsetPos || cursor >= 0) {\r
777                             syntaxError("Misplaced " + c, rule, start);\r
778                         }\r
779                         ++cursorOffset;\r
780                     } else {\r
781                         if (cursor == 0 && buf.length() == 0) {\r
782                             cursorOffset = -1;\r
783                         } else if (cursor < 0) {\r
784                             cursorOffsetPos = buf.length();\r
785                             cursorOffset = 1;\r
786                         } else {\r
787                             syntaxError("Misplaced " + c, rule, start);\r
788                         }\r
789                     }\r
790                     break;\r
791 \r
792                 //------------------------------------------------------\r
793                 // Non-special characters\r
794                 //------------------------------------------------------\r
795                 default:\r
796                     // Disallow unquoted characters other than [0-9A-Za-z]\r
797                     // in the printable ASCII range.  These characters are\r
798                     // reserved for possible future use.\r
799                     if (c >= 0x0021 && c <= 0x007E &&\r
800                         !((c >= '0' && c <= '9') ||\r
801                           (c >= 'A' && c <= 'Z') ||\r
802                           (c >= 'a' && c <= 'z'))) {\r
803                         syntaxError("Unquoted " + c, rule, start);\r
804                     }\r
805                     buf.append(c);\r
806                     break;\r
807                 }\r
808             }\r
809             return pos;\r
810         }\r
811 \r
812         /**\r
813          * Remove context.\r
814          */\r
815         void removeContext() {\r
816             text = text.substring(ante < 0 ? 0 : ante,\r
817                                   post < 0 ? text.length() : post);\r
818             ante = post = -1;\r
819             anchorStart = anchorEnd = false;\r
820         }\r
821 \r
822         /**\r
823          * Return true if this half looks like valid output, that is, does not\r
824          * contain quantifiers or other special input-only elements.\r
825          */\r
826         public boolean isValidOutput(TransliteratorParser parser) {\r
827             for (int i=0; i<text.length(); ) {\r
828                 int c = UTF16.charAt(text, i);\r
829                 i += UTF16.getCharCount(c);\r
830                 if (!parser.parseData.isReplacer(c)) {\r
831                     return false;\r
832                 }\r
833             }\r
834             return true;\r
835         }\r
836 \r
837         /**\r
838          * Return true if this half looks like valid input, that is, does not\r
839          * contain functions or other special output-only elements.\r
840          */\r
841         public boolean isValidInput(TransliteratorParser parser) {\r
842             for (int i=0; i<text.length(); ) {\r
843                 int c = UTF16.charAt(text, i);\r
844                 i += UTF16.getCharCount(c);\r
845                 if (!parser.parseData.isMatcher(c)) {\r
846                     return false;\r
847                 }\r
848             }\r
849             return true;\r
850         }\r
851     }\r
852 \r
853     //----------------------------------------------------------------------\r
854     // PUBLIC methods\r
855     //----------------------------------------------------------------------\r
856 \r
857     /**\r
858      * Constructor.\r
859      */\r
860     public TransliteratorParser() {\r
861     }\r
862 \r
863     /**\r
864      * Parse a set of rules.  After the parse completes, examine the public\r
865      * data members for results.\r
866      */\r
867     public void parse(String rules, int dir) {\r
868         parseRules(new RuleArray(new String[] { rules }), dir);\r
869     }\r
870    \r
871     /*\r
872      * Parse a set of rules.  After the parse completes, examine the public\r
873      * data members for results.\r
874      */\r
875 /*    public void parse(ResourceReader rules, int direction) {\r
876         parseRules(new RuleReader(rules), direction);\r
877     }*/\r
878 \r
879     //----------------------------------------------------------------------\r
880     // PRIVATE methods\r
881     //----------------------------------------------------------------------\r
882 \r
883     /**\r
884      * Parse an array of zero or more rules.  The strings in the array are\r
885      * treated as if they were concatenated together, with rule terminators\r
886      * inserted between array elements if not present already.\r
887      *\r
888      * Any previous rules are discarded.  Typically this method is called exactly\r
889      * once, during construction.\r
890      *\r
891      * The member this.data will be set to null if there are no rules.\r
892      *\r
893      * @exception IllegalIcuArgumentException if there is a syntax error in the\r
894      * rules\r
895      */\r
896     void parseRules(RuleBody ruleArray, int dir) {\r
897         boolean parsingIDs = true;\r
898         int ruleCount = 0;\r
899 \r
900         dataVector = new Vector();\r
901         idBlockVector = new Vector();\r
902         curData = null;\r
903         direction = dir;\r
904         compoundFilter = null;\r
905         variablesVector = new Vector();\r
906         variableNames = new Hashtable();\r
907         parseData = new ParseData();\r
908 \r
909         List errors = new ArrayList();\r
910         int errorCount = 0;\r
911 \r
912         ruleArray.reset();\r
913 \r
914         StringBuffer idBlockResult = new StringBuffer();\r
915 \r
916         // The compound filter offset is an index into idBlockResult.\r
917         // If it is 0, then the compound filter occurred at the start,\r
918         // and it is the offset to the _start_ of the compound filter\r
919         // pattern.  Otherwise it is the offset to the _limit_ of the\r
920         // compound filter pattern within idBlockResult.\r
921         this.compoundFilter = null;\r
922         int compoundFilterOffset = -1;\r
923 \r
924     main:\r
925         for (;;) {\r
926             String rule = ruleArray.nextLine();\r
927             if (rule == null) {\r
928                 break;\r
929             }\r
930             int pos = 0;\r
931             int limit = rule.length();\r
932             while (pos < limit) {\r
933                 char c = rule.charAt(pos++);\r
934                 if (UCharacterProperty.isRuleWhiteSpace(c)) {\r
935                     continue;\r
936                 }\r
937                 // Skip lines starting with the comment character\r
938                 if (c == RULE_COMMENT_CHAR) {\r
939                     pos = rule.indexOf("\n", pos) + 1;\r
940                     if (pos == 0) {\r
941                         break; // No "\n" found; rest of rule is a commnet\r
942                     }\r
943                     continue; // Either fall out or restart with next line\r
944                 }\r
945 \r
946                 // skip empty rules\r
947                 if (c == END_OF_RULE)\r
948                     continue;\r
949 \r
950                 // Often a rule file contains multiple errors.  It's\r
951                 // convenient to the rule author if these are all reported\r
952                 // at once.  We keep parsing rules even after a failure, up\r
953                 // to a specified limit, and report all errors at once.\r
954                 try {\r
955                     ++ruleCount;\r
956 \r
957                     // We've found the start of a rule or ID.  c is its first\r
958                     // character, and pos points past c.\r
959                     --pos;\r
960                     // Look for an ID token.  Must have at least ID_TOKEN_LEN + 1\r
961                     // chars left.\r
962                     if ((pos + ID_TOKEN_LEN + 1) <= limit &&\r
963                             rule.regionMatches(pos, ID_TOKEN, 0, ID_TOKEN_LEN)) {\r
964                         pos += ID_TOKEN_LEN;\r
965                         c = rule.charAt(pos);\r
966                         while (UCharacterProperty.isRuleWhiteSpace(c) && pos < limit) {\r
967                             ++pos;\r
968                             c = rule.charAt(pos);\r
969                         }\r
970                         int[] p = new int[] { pos };\r
971 \r
972                         if (!parsingIDs) {\r
973                             if (curData != null) {\r
974                                 if (direction == Transliterator.FORWARD)\r
975                                     dataVector.add(curData);\r
976                                 else\r
977                                     dataVector.insertElementAt(curData, 0);\r
978                                 curData = null;\r
979                             }\r
980                             parsingIDs = true;\r
981                         }\r
982 \r
983                         TransliteratorIDParser.SingleID id =\r
984                             TransliteratorIDParser.parseSingleID(\r
985                                           rule, p, direction);\r
986                         if (p[0] != pos && Utility.parseChar(rule, p, END_OF_RULE)) {\r
987                             // Successful ::ID parse.\r
988 \r
989                             if (direction == Transliterator.FORWARD) {\r
990                                 idBlockResult.append(id.canonID).append(END_OF_RULE);\r
991                             } else {\r
992                                 idBlockResult.insert(0, id.canonID + END_OF_RULE);\r
993                             }\r
994 \r
995                         } else {\r
996                             // Couldn't parse an ID.  Try to parse a global filter\r
997                             int[] withParens = new int[] { -1 };\r
998                             UnicodeSet f = TransliteratorIDParser.parseGlobalFilter(rule, p, direction, withParens, null);\r
999                             if (f != null && Utility.parseChar(rule, p, END_OF_RULE)) {\r
1000                                 if ((direction == Transliterator.FORWARD) ==\r
1001                                     (withParens[0] == 0)) {\r
1002                                     if (compoundFilter != null) {\r
1003                                         // Multiple compound filters\r
1004                                         syntaxError("Multiple global filters", rule, pos);\r
1005                                     }\r
1006                                     compoundFilter = f;\r
1007                                     compoundFilterOffset = ruleCount;\r
1008                                }\r
1009                             } else {\r
1010                                 // Invalid ::id\r
1011                                 // Can be parsed as neither an ID nor a global filter\r
1012                                 syntaxError("Invalid ::ID", rule, pos);\r
1013                             }\r
1014                         }\r
1015 \r
1016                         pos = p[0];\r
1017                     } else {\r
1018                         if (parsingIDs) {\r
1019                             if (direction == Transliterator.FORWARD)\r
1020                                 idBlockVector.add(idBlockResult.toString());\r
1021                             else\r
1022                                 idBlockVector.insertElementAt(idBlockResult.toString(), 0);\r
1023                             idBlockResult.delete(0, idBlockResult.length());\r
1024                             parsingIDs = false;\r
1025                             curData = new RuleBasedTransliterator.Data();\r
1026 \r
1027                             // By default, rules use part of the private use area\r
1028                             // E000..F8FF for variables and other stand-ins.  Currently\r
1029                             // the range F000..F8FF is typically sufficient.  The 'use\r
1030                             // variable range' pragma allows rule sets to modify this.\r
1031                             setVariableRange(0xF000, 0xF8FF);\r
1032                         }\r
1033 \r
1034                         if (resemblesPragma(rule, pos, limit)) {\r
1035                             int ppp = parsePragma(rule, pos, limit);\r
1036                             if (ppp < 0) {\r
1037                                 syntaxError("Unrecognized pragma", rule, pos);\r
1038                             }\r
1039                             pos = ppp;\r
1040                         // Parse a rule\r
1041                         } else {\r
1042                             pos = parseRule(rule, pos, limit);\r
1043                         }\r
1044                     }\r
1045                 } catch (IllegalArgumentException e) {\r
1046                     if (errorCount == 30) {\r
1047                         errors.add(new IllegalIcuArgumentException("\nMore than 30 errors; further messages squelched")\r
1048 //#if defined(FOUNDATION10) || defined(J2SE13)\r
1049 //#else\r
1050                             .initCause(e)\r
1051 //#endif\r
1052                             );\r
1053                         break main;\r
1054                     }\r
1055                     e.fillInStackTrace();\r
1056                     errors.add(e);\r
1057                     ++errorCount;\r
1058                     pos = ruleEnd(rule, pos, limit) + 1; // +1 advances past ';'\r
1059                 }\r
1060             }\r
1061         }\r
1062         if (parsingIDs && idBlockResult.length() > 0) {\r
1063             if (direction == Transliterator.FORWARD)\r
1064                 idBlockVector.add(idBlockResult.toString());\r
1065             else\r
1066                 idBlockVector.insertElementAt(idBlockResult.toString(), 0);\r
1067         }\r
1068         else if (!parsingIDs && curData != null) {\r
1069             if (direction == Transliterator.FORWARD)\r
1070                 dataVector.add(curData);\r
1071             else\r
1072                 dataVector.insertElementAt(curData, 0);\r
1073         }\r
1074 \r
1075         // Convert the set vector to an array\r
1076         for (int i = 0; i < dataVector.size(); i++) {\r
1077             RuleBasedTransliterator.Data data = (RuleBasedTransliterator.Data)dataVector.get(i);\r
1078             data.variables = new Object[variablesVector.size()];\r
1079             variablesVector.copyInto(data.variables);\r
1080             data.variableNames = new Hashtable();\r
1081             data.variableNames.putAll(variableNames);\r
1082         }\r
1083         variablesVector = null;\r
1084 \r
1085         // Do more syntax checking and index the rules\r
1086         try {\r
1087             if (compoundFilter != null) {\r
1088                 if ((direction == Transliterator.FORWARD &&\r
1089                      compoundFilterOffset != 1) ||\r
1090                     (direction == Transliterator.REVERSE &&\r
1091                      compoundFilterOffset != ruleCount)) {\r
1092                     throw new IllegalIcuArgumentException("Compound filters misplaced");\r
1093                 }\r
1094             }\r
1095 \r
1096             for (int i = 0; i < dataVector.size(); i++) {\r
1097                 RuleBasedTransliterator.Data data = (RuleBasedTransliterator.Data)dataVector.get(i);\r
1098                 data.ruleSet.freeze();\r
1099             }\r
1100 \r
1101             if (idBlockVector.size() == 1 && ((String)idBlockVector.get(0)).length() == 0)\r
1102                 idBlockVector.remove(0);\r
1103 \r
1104         } catch (IllegalArgumentException e) {\r
1105             e.fillInStackTrace();\r
1106             errors.add(e);\r
1107         }\r
1108 \r
1109         if (errors.size() != 0) {\r
1110 //#if defined(FOUNDATION10) || defined(J2SE13)\r
1111 //#else\r
1112             for (int i = errors.size()-1; i > 0; --i) {\r
1113                 RuntimeException previous = (RuntimeException) errors.get(i-1);\r
1114                 while (previous.getCause() != null) {\r
1115                     previous = (RuntimeException) previous.getCause(); // chain specially\r
1116                 }\r
1117                 previous.initCause((RuntimeException) errors.get(i));\r
1118             }\r
1119 //#endif\r
1120             throw (RuntimeException) errors.get(0);\r
1121             // if initCause not supported: throw new IllegalArgumentException(errors.toString());\r
1122         }\r
1123     }\r
1124 \r
1125     /**\r
1126      * MAIN PARSER.  Parse the next rule in the given rule string, starting\r
1127      * at pos.  Return the index after the last character parsed.  Do not\r
1128      * parse characters at or after limit.\r
1129      *\r
1130      * Important:  The character at pos must be a non-whitespace character\r
1131      * that is not the comment character.\r
1132      *\r
1133      * This method handles quoting, escaping, and whitespace removal.  It\r
1134      * parses the end-of-rule character.  It recognizes context and cursor\r
1135      * indicators.  Once it does a lexical breakdown of the rule at pos, it\r
1136      * creates a rule object and adds it to our rule list.\r
1137      *\r
1138      * This method is tightly coupled to the inner class RuleHalf.\r
1139      */\r
1140     private int parseRule(String rule, int pos, int limit) {\r
1141         // Locate the left side, operator, and right side\r
1142         int start = pos;\r
1143         char operator = 0;\r
1144 \r
1145         // Set up segments data\r
1146         segmentStandins = new StringBuffer();\r
1147         segmentObjects = new Vector();\r
1148 \r
1149         RuleHalf left  = new RuleHalf();\r
1150         RuleHalf right = new RuleHalf();\r
1151 \r
1152         undefinedVariableName = null;\r
1153         pos = left.parse(rule, pos, limit, this);\r
1154 \r
1155         if (pos == limit ||\r
1156             OPERATORS.indexOf(operator = rule.charAt(--pos)) < 0) {\r
1157             syntaxError("No operator pos=" + pos, rule, start);\r
1158         }\r
1159         ++pos;\r
1160 \r
1161         // Found an operator char.  Check for forward-reverse operator.\r
1162         if (operator == REVERSE_RULE_OP &&\r
1163             (pos < limit && rule.charAt(pos) == FORWARD_RULE_OP)) {\r
1164             ++pos;\r
1165             operator = FWDREV_RULE_OP;\r
1166         }\r
1167 \r
1168         // Translate alternate op characters.\r
1169         switch (operator) {\r
1170         case ALT_FORWARD_RULE_OP:\r
1171             operator = FORWARD_RULE_OP;\r
1172             break;\r
1173         case ALT_REVERSE_RULE_OP:\r
1174             operator = REVERSE_RULE_OP;\r
1175             break;\r
1176         case ALT_FWDREV_RULE_OP:\r
1177             operator = FWDREV_RULE_OP;\r
1178             break;\r
1179         }\r
1180 \r
1181         pos = right.parse(rule, pos, limit, this);\r
1182 \r
1183         if (pos < limit) {\r
1184             if (rule.charAt(--pos) == END_OF_RULE) {\r
1185                 ++pos;\r
1186             } else {\r
1187                 // RuleHalf parser must have terminated at an operator\r
1188                 syntaxError("Unquoted operator", rule, start);\r
1189             }\r
1190         }\r
1191 \r
1192         if (operator == VARIABLE_DEF_OP) {\r
1193             // LHS is the name.  RHS is a single character, either a literal\r
1194             // or a set (already parsed).  If RHS is longer than one\r
1195             // character, it is either a multi-character string, or multiple\r
1196             // sets, or a mixture of chars and sets -- syntax error.\r
1197 \r
1198             // We expect to see a single undefined variable (the one being\r
1199             // defined).\r
1200             if (undefinedVariableName == null) {\r
1201                 syntaxError("Missing '$' or duplicate definition", rule, start);\r
1202             }\r
1203             if (left.text.length() != 1 || left.text.charAt(0) != variableLimit) {\r
1204                 syntaxError("Malformed LHS", rule, start);\r
1205             }\r
1206             if (left.anchorStart || left.anchorEnd ||\r
1207                 right.anchorStart || right.anchorEnd) {\r
1208                 syntaxError("Malformed variable def", rule, start);\r
1209             }\r
1210             // We allow anything on the right, including an empty string.\r
1211             int n = right.text.length();\r
1212             char[] value = new char[n];\r
1213             right.text.getChars(0, n, value, 0);\r
1214             variableNames.put(undefinedVariableName, value);\r
1215 \r
1216             ++variableLimit;\r
1217             return pos;\r
1218         }\r
1219 \r
1220         // If this is not a variable definition rule, we shouldn't have\r
1221         // any undefined variable names.\r
1222         if (undefinedVariableName != null) {\r
1223             syntaxError("Undefined variable $" + undefinedVariableName,\r
1224                         rule, start);\r
1225         }\r
1226 \r
1227         // Verify segments\r
1228         if (segmentStandins.length() > segmentObjects.size()) {\r
1229             syntaxError("Undefined segment reference", rule, start);\r
1230         }\r
1231         for (int i=0; i<segmentStandins.length(); ++i) {\r
1232             if (segmentStandins.charAt(i) == 0) {\r
1233                 syntaxError("Internal error", rule, start); // will never happen\r
1234             }\r
1235         }\r
1236         for (int i=0; i<segmentObjects.size(); ++i) {\r
1237             if (segmentObjects.elementAt(i) == null) {\r
1238                 syntaxError("Internal error", rule, start); // will never happen\r
1239             }\r
1240         }\r
1241 \r
1242         // If the direction we want doesn't match the rule\r
1243         // direction, do nothing.\r
1244         if (operator != FWDREV_RULE_OP &&\r
1245             ((direction == Transliterator.FORWARD) != (operator == FORWARD_RULE_OP))) {\r
1246             return pos;\r
1247         }\r
1248 \r
1249         // Transform the rule into a forward rule by swapping the\r
1250         // sides if necessary.\r
1251         if (direction == Transliterator.REVERSE) {\r
1252             RuleHalf temp = left;\r
1253             left = right;\r
1254             right = temp;\r
1255         }\r
1256 \r
1257         // Remove non-applicable elements in forward-reverse\r
1258         // rules.  Bidirectional rules ignore elements that do not\r
1259         // apply.\r
1260         if (operator == FWDREV_RULE_OP) {\r
1261             right.removeContext();\r
1262             left.cursor = -1;\r
1263             left.cursorOffset = 0;\r
1264         }\r
1265 \r
1266         // Normalize context\r
1267         if (left.ante < 0) {\r
1268             left.ante = 0;\r
1269         }\r
1270         if (left.post < 0) {\r
1271             left.post = left.text.length();\r
1272         }\r
1273 \r
1274         // Context is only allowed on the input side.  Cursors are only\r
1275         // allowed on the output side.  Segment delimiters can only appear\r
1276         // on the left, and references on the right.  Cursor offset\r
1277         // cannot appear without an explicit cursor.  Cursor offset\r
1278         // cannot place the cursor outside the limits of the context.\r
1279         // Anchors are only allowed on the input side.\r
1280         if (right.ante >= 0 || right.post >= 0 || left.cursor >= 0 ||\r
1281             (right.cursorOffset != 0 && right.cursor < 0) ||\r
1282             // - The following two checks were used to ensure that the\r
1283             // - the cursor offset stayed within the ante- or postcontext.\r
1284             // - However, with the addition of quantifiers, we have to\r
1285             // - allow arbitrary cursor offsets and do runtime checking.\r
1286             //(right.cursorOffset > (left.text.length() - left.post)) ||\r
1287             //(-right.cursorOffset > left.ante) ||\r
1288             right.anchorStart || right.anchorEnd ||\r
1289             !left.isValidInput(this) || !right.isValidOutput(this) ||\r
1290             left.ante > left.post) {\r
1291             syntaxError("Malformed rule", rule, start);\r
1292         }\r
1293 \r
1294         // Flatten segment objects vector to an array\r
1295         UnicodeMatcher[] segmentsArray = null;\r
1296         if (segmentObjects.size() > 0) {\r
1297             segmentsArray = new UnicodeMatcher[segmentObjects.size()];\r
1298             segmentObjects.toArray(segmentsArray);\r
1299         }\r
1300 \r
1301         curData.ruleSet.addRule(new TransliterationRule(\r
1302                                      left.text, left.ante, left.post,\r
1303                                      right.text, right.cursor, right.cursorOffset,\r
1304                                      segmentsArray,\r
1305                                      left.anchorStart, left.anchorEnd,\r
1306                                      curData));\r
1307 \r
1308         return pos;\r
1309     }\r
1310 \r
1311     /**\r
1312      * Set the variable range to [start, end] (inclusive).\r
1313      */\r
1314     private void setVariableRange(int start, int end) {\r
1315         if (start > end || start < 0 || end > 0xFFFF) {\r
1316             throw new IllegalIcuArgumentException("Invalid variable range " + start + ", " + end);\r
1317         }\r
1318         \r
1319         curData.variablesBase = (char) start; // first private use\r
1320 \r
1321         if (dataVector.size() == 0) {\r
1322             variableNext = (char) start;\r
1323             variableLimit = (char) (end + 1);\r
1324         }\r
1325     }\r
1326 \r
1327     /**\r
1328      * Assert that the given character is NOT within the variable range.\r
1329      * If it is, signal an error.  This is neccesary to ensure that the\r
1330      * variable range does not overlap characters used in a rule.\r
1331      */\r
1332     private void checkVariableRange(int ch, String rule, int start) {\r
1333         if (ch >= curData.variablesBase && ch < variableLimit) {\r
1334             syntaxError("Variable range character in rule", rule, start);\r
1335         }\r
1336     }\r
1337 \r
1338     // (The following method is part of an unimplemented feature.\r
1339     // Remove this clover pragma after the feature is implemented.\r
1340     // 2003-06-11 ICU 2.6 Alan)\r
1341     ///CLOVER:OFF\r
1342     /**\r
1343      * Set the maximum backup to 'backup', in response to a pragma\r
1344      * statement.\r
1345      */\r
1346     private void pragmaMaximumBackup(int backup) {\r
1347         //TODO Finish\r
1348         throw new IllegalIcuArgumentException("use maximum backup pragma not implemented yet");\r
1349     }\r
1350     ///CLOVER:ON\r
1351 \r
1352     // (The following method is part of an unimplemented feature.\r
1353     // Remove this clover pragma after the feature is implemented.\r
1354     // 2003-06-11 ICU 2.6 Alan)\r
1355     ///CLOVER:OFF\r
1356     /**\r
1357      * Begin normalizing all rules using the given mode, in response\r
1358      * to a pragma statement.\r
1359      */\r
1360     private void pragmaNormalizeRules(Normalizer.Mode mode) {\r
1361         //TODO Finish\r
1362         throw new IllegalIcuArgumentException("use normalize rules pragma not implemented yet");\r
1363     }\r
1364     ///CLOVER:ON\r
1365 \r
1366     /**\r
1367      * Return true if the given rule looks like a pragma.\r
1368      * @param pos offset to the first non-whitespace character\r
1369      * of the rule.\r
1370      * @param limit pointer past the last character of the rule.\r
1371      */\r
1372     static boolean resemblesPragma(String rule, int pos, int limit) {\r
1373         // Must start with /use\s/i\r
1374         return Utility.parsePattern(rule, pos, limit, "use ", null) >= 0;\r
1375     }\r
1376 \r
1377     /**\r
1378      * Parse a pragma.  This method assumes resemblesPragma() has\r
1379      * already returned true.\r
1380      * @param pos offset to the first non-whitespace character\r
1381      * of the rule.\r
1382      * @param limit pointer past the last character of the rule.\r
1383      * @return the position index after the final ';' of the pragma,\r
1384      * or -1 on failure.\r
1385      */\r
1386     private int parsePragma(String rule, int pos, int limit) {\r
1387         int[] array = new int[2];\r
1388 \r
1389         // resemblesPragma() has already returned true, so we\r
1390         // know that pos points to /use\s/i; we can skip 4 characters\r
1391         // immediately\r
1392         pos += 4;\r
1393         \r
1394         // Here are the pragmas we recognize:\r
1395         // use variable range 0xE000 0xEFFF;\r
1396         // use maximum backup 16;\r
1397         // use nfd rules;\r
1398         int p = Utility.parsePattern(rule, pos, limit, "~variable range # #~;", array);\r
1399         if (p >= 0) {\r
1400             setVariableRange(array[0], array[1]);\r
1401             return p;\r
1402         }\r
1403 \r
1404         p = Utility.parsePattern(rule, pos, limit, "~maximum backup #~;", array);\r
1405         if (p >= 0) {\r
1406             pragmaMaximumBackup(array[0]);\r
1407             return p;\r
1408         }\r
1409 \r
1410         p = Utility.parsePattern(rule, pos, limit, "~nfd rules~;", null);\r
1411         if (p >= 0) {\r
1412             pragmaNormalizeRules(Normalizer.NFD);\r
1413             return p;\r
1414         }\r
1415 \r
1416         p = Utility.parsePattern(rule, pos, limit, "~nfc rules~;", null);\r
1417         if (p >= 0) {\r
1418             pragmaNormalizeRules(Normalizer.NFC);\r
1419             return p;\r
1420         }\r
1421 \r
1422         // Syntax error: unable to parse pragma\r
1423         return -1;\r
1424     }\r
1425 \r
1426     /**\r
1427      * Throw an exception indicating a syntax error.  Search the rule string\r
1428      * for the probable end of the rule.  Of course, if the error is that\r
1429      * the end of rule marker is missing, then the rule end will not be found.\r
1430      * In any case the rule start will be correctly reported.\r
1431      * @param msg error description\r
1432      * @param rule pattern string\r
1433      * @param start position of first character of current rule\r
1434      */\r
1435     static final void syntaxError(String msg, String rule, int start) {\r
1436         int end = ruleEnd(rule, start, rule.length());\r
1437         throw new IllegalIcuArgumentException(msg + " in \"" +\r
1438                                            Utility.escape(rule.substring(start, end)) + '"');\r
1439     }\r
1440 \r
1441     static final int ruleEnd(String rule, int start, int limit) {\r
1442         int end = Utility.quotedIndexOf(rule, start, limit, ";");\r
1443         if (end < 0) {\r
1444             end = limit;\r
1445         }\r
1446         return end;\r
1447     }\r
1448 \r
1449     /**\r
1450      * Parse a UnicodeSet out, store it, and return the stand-in character\r
1451      * used to represent it.\r
1452      */\r
1453     private final char parseSet(String rule, ParsePosition pos) {\r
1454         UnicodeSet set = new UnicodeSet(rule, pos, parseData);\r
1455         if (variableNext >= variableLimit) {\r
1456             throw new RuntimeException("Private use variables exhausted");\r
1457         }\r
1458         set.compact();\r
1459         return generateStandInFor(set);\r
1460     }\r
1461 \r
1462     /**\r
1463      * Generate and return a stand-in for a new UnicodeMatcher or UnicodeReplacer.\r
1464      * Store the object.\r
1465      */\r
1466     char generateStandInFor(Object obj) {\r
1467         // assert(obj != null);\r
1468 \r
1469         // Look up previous stand-in, if any.  This is a short list\r
1470         // (typical n is 0, 1, or 2); linear search is optimal.\r
1471         for (int i=0; i<variablesVector.size(); ++i) {\r
1472             if (variablesVector.elementAt(i) == obj) { // [sic] pointer comparison\r
1473                 return (char) (curData.variablesBase + i);\r
1474             }\r
1475         }\r
1476 \r
1477         if (variableNext >= variableLimit) {\r
1478             throw new RuntimeException("Variable range exhausted");\r
1479         }\r
1480         variablesVector.addElement(obj);\r
1481         return variableNext++;\r
1482     }\r
1483 \r
1484     /**\r
1485      * Return the standin for segment seg (1-based).\r
1486      */\r
1487     public char getSegmentStandin(int seg) {\r
1488         if (segmentStandins.length() < seg) {\r
1489             segmentStandins.setLength(seg);\r
1490         }\r
1491         char c = segmentStandins.charAt(seg-1);\r
1492         if (c == 0) {\r
1493             if (variableNext >= variableLimit) {\r
1494                 throw new RuntimeException("Variable range exhausted");\r
1495             }\r
1496             c = variableNext++;\r
1497             // Set a placeholder in the master variables vector that will be\r
1498             // filled in later by setSegmentObject().  We know that we will get\r
1499             // called first because setSegmentObject() will call us.\r
1500             variablesVector.addElement(null);\r
1501             segmentStandins.setCharAt(seg-1, c);\r
1502         }\r
1503         return c;\r
1504     }\r
1505     \r
1506     /**\r
1507      * Set the object for segment seg (1-based).\r
1508      */\r
1509     public void setSegmentObject(int seg, StringMatcher obj) {\r
1510         // Since we call parseSection() recursively, nested\r
1511         // segments will result in segment i+1 getting parsed\r
1512         // and stored before segment i; be careful with the\r
1513         // vector handling here.\r
1514         if (segmentObjects.size() < seg) {\r
1515             segmentObjects.setSize(seg);\r
1516         }\r
1517         int index = getSegmentStandin(seg) - curData.variablesBase;\r
1518         if (segmentObjects.elementAt(seg-1) != null ||\r
1519             variablesVector.elementAt(index) != null) {\r
1520             throw new RuntimeException(); // should never happen\r
1521         }\r
1522         segmentObjects.setElementAt(obj, seg-1);\r
1523         variablesVector.setElementAt(obj, index);\r
1524     }\r
1525 \r
1526     /**\r
1527      * Return the stand-in for the dot set.  It is allocated the first\r
1528      * time and reused thereafter.\r
1529      */\r
1530     char getDotStandIn() {\r
1531         if (dotStandIn == -1) {\r
1532             dotStandIn = generateStandInFor(new UnicodeSet(DOT_SET));\r
1533         }\r
1534         return (char) dotStandIn;\r
1535     }\r
1536 \r
1537     /**\r
1538      * Append the value of the given variable name to the given\r
1539      * StringBuffer.\r
1540      * @exception IllegalIcuArgumentException if the name is unknown.\r
1541      */\r
1542     private void appendVariableDef(String name, StringBuffer buf) {\r
1543         char[] ch = (char[]) variableNames.get(name);\r
1544         if (ch == null) {\r
1545             // We allow one undefined variable so that variable definition\r
1546             // statements work.  For the first undefined variable we return\r
1547             // the special placeholder variableLimit-1, and save the variable\r
1548             // name.\r
1549             if (undefinedVariableName == null) {\r
1550                 undefinedVariableName = name;\r
1551                 if (variableNext >= variableLimit) {\r
1552                     throw new RuntimeException("Private use variables exhausted");\r
1553                 }\r
1554                 buf.append((char) --variableLimit);\r
1555             } else {\r
1556                 throw new IllegalIcuArgumentException("Undefined variable $"\r
1557                                                    + name);\r
1558             }\r
1559         } else {\r
1560             buf.append(ch);\r
1561         }\r
1562     }\r
1563 }\r
1564 \r
1565 //eof\r