View Javadoc

1   package cz.cuni.amis.pogamut.shady;
2   
3   /**
4    * Interface that unified various arguments used when shade is calling
5    * the primitive. 
6    * @author Honza
7    */
8   public interface IArgument<T> {
9   
10      T getValue();
11  }
12  
13  /**
14   * TODO:Stub
15   * @author Honza
16   */
17  class Arg<T> implements IArgument<T> {
18  
19      private final T value;
20  
21      public Arg(T value) {
22          this.value = value;
23      }
24  
25      @Override
26      public T getValue() {
27          return value;
28      }
29  
30      @Override
31      public boolean equals(Object obj) {
32          if (obj == null) {
33              return false;
34          }
35          if (obj == this) {
36              return true;
37          }
38          if (!(obj instanceof IArgument)) {
39              return false;
40          }
41          IArgument arg = (IArgument) obj;
42          return this.value == null ? arg.getValue() == null : value.equals(arg.getValue());
43      }
44  }
45  
46  class ArgInt extends Arg<Integer> {
47  
48      public ArgInt(int number) {
49          super(number);
50      }
51  }
52  
53  class ArgFloat extends Arg<Double> {
54  
55      public ArgFloat(double number) {
56          super(number);
57      }
58  }
59  
60  class ArgChar extends Arg<Character> {
61  
62      public ArgChar(char character) {
63          super(character);
64      }
65  
66      /**
67       * The the escaped char (e.g. '\n' but without quotes) and return its actual
68       * value.
69       * <p/>
70       * There is slight difference in valid strings ("'", "\'" and "\"") vs valid 
71       * chars ('"', '\"' and '\''), but not all are interchangeable.
72       * 
73       */
74      public static char unescape(String escapedChar) throws ParseException {
75          if (escapedChar.length() == 0) {
76              throw new ParseException("The string representing the escaped character must have legth > 0.");
77          }
78          StringBuilder sb = new StringBuilder(escapedChar);
79          char result = parseCharacter(sb);
80          if (sb.length() > 0)
81              throw new ParseException("There still are some unparsed characters remaining(" + sb.length() + "):" + sb.toString());
82          return result;
83      }
84  
85      /**
86       * Take string starting with a single escaped character (e.g. 'ahoj', 
87       * '\nWorld'..) try to parse the first character and return it. Remove the 
88       * characters representing the escaped character form the sb.
89       * @param sb string that should start with escaped character
90       * @return parsed character
91       * @throws ParseException
92       */
93      protected static char parseCharacter(StringBuilder sb) throws ParseException {
94          char result = sb.charAt(0);
95          sb.deleteCharAt(0);
96          if (result == '\\') {
97              Character parsed = ArgChar.parseEscapeSequence(sb);
98              if (parsed == null) {
99                  throw new ParseException("Unable to unescape sequence:" + sb.toString());
100             }
101             result = parsed;
102         }
103         return result;
104     }
105 
106     
107     /**
108      * The the escaped character literal (e.g.  'a', '\n', '\0' or '\u3067' 
109      * including quotes) and return the character that is represented by this literal.
110      */
111     public static char parseCharacterListeral(String charLiteral) throws ParseException {
112         StringBuilder sb = new StringBuilder(charLiteral);
113         if (sb.charAt(0) != '\'') {
114             throw new ParseException("Expecting \' at the start of " + charLiteral.toString());
115         }
116         sb.deleteCharAt(0);
117 
118         char result = parseCharacter(sb);
119 
120         if (sb.length() != 1) {
121             throw new ParseException("Expecting exactly one character (single quote), but " + sb.length() + " characters remain:" + sb.toString());
122         }
123         if (sb.charAt(0) != '\'') {
124             throw new ParseException("Expecting ending double quote, but got: " + sb.charAt(0));
125         }
126         return result;
127     }
128 
129     /**
130      * Try to parse single escape sequence (we assume that backslash has already 
131      * been eaten):
132      * <ul>
133      *  <li>[0-3][0-7][0-7] - into character with specified octal number</li>
134      *  <li>[0-7][0-7]</li>
135      *  <li>[0-7]</li>
136      *  <li>b,t,n,r,f,",',\ - into backspace, tab, LF, CR, FF, ", ' and \</li>
137      *  <li>(u)+\p{XDigit}\p{XDigit}\p{XDigit}\p{XDigit} - into character with specified hexa number</li>
138      * </ul>
139      * If there is no escape sequence, don't touch sb and return null.
140      * @param sb the escaped char should be at the beginning of sb.
141      * @return character represented by escape sequence or null
142      */
143     protected static Character parseEscapeSequence(StringBuilder sb) {
144         // take care of octal first, it uses no prefix
145         Character octal = parseOctal(sb);
146         if (octal != null) {
147             return octal;
148         }
149         // take care of single char escape sequence
150         Character simpleEscape = parseSingleEscape(sb);
151         if (simpleEscape != null) {
152             return simpleEscape;
153         }
154         Character unicode = parseUnicode(sb);
155         if (unicode != null) {
156             return unicode;
157         }
158         return null;
159     }
160 
161     /**
162      * Try to parse allowed octal number (0-255 ~o0-o377) at the start of 
163      * the passed string.
164      * @param sb string buffer that may have octal number at the start. If octal 
165      *        number is found, remove it from the start.
166      * @return parsed number or null if not found.
167      */
168     protected static Character parseOctal(StringBuilder sb) {
169         if (sb.length() >= 3) {
170             String octal3 = sb.substring(0, 3);
171             if (octal3.matches("[0-3][0-7][0-7]")) {
172                 sb.delete(0, 3);
173                 return Character.toChars(Integer.parseInt(octal3, 8))[0];
174             }
175         }
176         if (sb.length() >= 2) {
177             String octal2 = sb.substring(0, 2);
178             if (octal2.matches("[0-7][0-7]")) {
179                 sb.delete(0, 2);
180                 return Character.toChars(Integer.parseInt(octal2, 8))[0];
181             }
182         }
183         String octal1 = sb.substring(0, 1);
184         if (octal1.matches("[0-7]")) {
185             sb.delete(0, 1);
186             return Character.toChars(Integer.parseInt(octal1, 8))[0];
187         }
188         return null;
189     }
190 
191     /**
192      * Check if there is a single char escape sequence (we assume that backslash
193      * has already been deleted) and if it is, remove it from sb and return 
194      * the escaped character.
195      * Example: sequence 'nAndy said.' could would return character '\n' and sb 
196      * would delete first character, thus being 'Andy said.'
197      * @param sb
198      * @return escaped character or null if there is something else (e.g. m)
199      */
200     protected static Character parseSingleEscape(StringBuilder sb) {
201         char ch = sb.charAt(0);
202         char res;
203         switch (ch) {
204             case 'b':
205                 res = '\b'; // backspace BS
206                 break;
207             case 't':
208                 res = '\t'; // horizontal tab
209                 break;
210             case 'n':
211                 res = '\n'; // linefeed LF
212                 break;
213             case 'f':
214                 res = '\f'; // form feed FF
215                 break;
216             case 'r':
217                 res = '\r'; // carriage return CR
218                 break;
219             case '"':
220                 res = '\"'; // double quote
221                 break;
222             case '\'':
223                 res = '\''; // single quote
224                 break;
225             case '\\':
226                 res = '\\'; // backslash
227                 break;
228             default:
229                 return null;
230         }
231         sb.deleteCharAt(0);
232         return res;
233     }
234 
235     /**
236      * Check if there is unicode escape at the start of sb (without backslash)
237      * and if it is, parse it, remove it from the sb and return parsed unicode
238      * character. 
239      * Example: 'u306A\u306b \u30673059 \u304B?' ('nani desu ka?') would return
240      * character '\u306A' (na) and in sb would be '\u306b \u30673059 \u304B?'
241      * @param sb sb, unescaped character will be removed
242      * @return unescaped character or null
243      */
244     protected static Character parseUnicode(StringBuilder sb) {
245         String regexp = "u+\\p{XDigit}\\p{XDigit}\\p{XDigit}\\p{XDigit}.*";
246         if (sb.toString().matches(regexp)) {
247             while (sb.charAt(0) == 'u') {
248                 sb.deleteCharAt(0);
249             }
250             String hexaString = sb.substring(0, 4);
251             sb.delete(0, 4);
252             return Character.toChars(Integer.parseInt(hexaString, 16))[0];
253         }
254         return null;
255     }
256 }
257 
258 /**
259  * Store the string value as an argument. We want to allow escaping (e.g. \n, \t ..)
260  * and we are using syntax defined by the ({@linkplain http://java.sun.com/docs/books/jls/second_edition/html/lexical.doc.html#101084 java lexical structure}).
261  * <p/>
262  * <em>The constructor won't automatically unescape the passed value</em>. Use {@link ArgString#parseStringLiteral(java.lang.StringBuilder) }.
263  * @author Honza
264  */
265 class ArgString extends Arg<String> {
266 
267     /**
268      * Take the string and use it as the value of the argument. Be careful, 
269      * in most cases, use of {@link ArgString#unescape(java.lang.String)}
270      * is recommended (it parses escaped string into an unescaped form).
271      * @param string value of the argument
272      */
273     public ArgString(String string) {
274         super(string);
275     }
276 
277     /**
278      * Get unescaped version of passed string. 
279      * @param escapedString string escaped according to java lexical structure, 
280      *        without double quotes (e.g. Hello\nWorld)
281      * @return unescaped string 
282      * @throws ParseException if there is an error in the escaping
283      */
284     public static String unescape(String escapedString) throws ParseException {
285         if (escapedString.length() == 0) {
286             return escapedString;
287         }
288         return parseStringCharacters(new StringBuilder(escapedString));
289     }
290 
291     /**
292      * Take the escaped string, parse it and return the unescaped value.
293      * @param string string to parse, incl. quotes (e.g. "Foo\nBar")
294      * <p/>
295      * <cite>StringLiteral: " [StringCharacters] "</cite>
296      * @return parsed string
297      */
298     protected static String parseStringLiteral(String escaped) throws ParseException {
299         StringBuilder sb = new StringBuilder(escaped);
300         if (sb.charAt(0) != '\"') {
301             throw new ParseException("Expecting \" at the start of " + escaped.toString());
302         }
303         sb.deleteCharAt(0);
304 
305         // StringCharacters nonterminal is optional. If it is missing, ther is 
306         // only ending double quote.
307         String parsed = "";
308         if (sb.length() != 1) {
309             parsed = parseStringCharacters(sb);
310         }
311 
312         if (sb.length() != 1) {
313             throw new ParseException("Expecting exactly one character (double quote), but " + sb.length() + " characters remain:" + sb.toString());
314         }
315 
316         if (sb.charAt(0) != '\"') {
317             throw new ParseException("Expecting ending double quote, but got: " + sb.charAt(0));
318         }
319 
320         return parsed.toString();
321     }
322 
323     /**
324      * Parse nonterminal <em>StringCharacters: ( StringCharacter )+</em>
325      * @param sb escaped string to be parsed, something may be even after last 
326      *        <em>StringCharacter</em>. This will be modified during progress, 
327      *        the parsed characters will be "eaten"
328      * @return unescaped string
329      */
330     private static String parseStringCharacters(StringBuilder sb) throws ParseException {
331         Character ch;
332         StringBuilder stringCharacters = new StringBuilder();
333         while ((ch = parseStringCharacter(sb)) != null) {
334             stringCharacters.append(ch);
335         }
336         return stringCharacters.toString();
337     }
338 
339     /**
340      * Extract the <em>StringCharacter</em> from the sb and return it.
341      * <p/>
342      * <pre>
343      * StringCharacter: InputCharacter but not " or \
344      * StringCharacter: EscapeSequence
345      * InputCharacter: UnicodeInputCharacter but not CR or LF
346      * </pre>
347      * Basically when you expand it all, you will get
348      * <pre>
349      *  \(u)+ [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F]  ~ Unicode char
350      *  \[btnfr"'\]
351      *  \[0-3][0-7][0-7]
352      *  \[0-7][0-7]
353      *  \[0-7]
354      * </pre>
355      * @param sb sequence of chars that is used to extract the character. Is 
356      *        modified (characters are removed) during parsing.
357      * @return found character if there is a <em>StringCharacter</em>, null otherwise
358      */
359     protected static Character parseStringCharacter(StringBuilder sb) throws ParseException {
360         if (sb.length() == 0) {
361             return null;
362         }
363         char ch = sb.charAt(0);
364         if (ch == '\"' || ch == '\r' || ch == '\n') {
365             return null;
366         }
367 
368         sb.deleteCharAt(0);
369         // Is that an escape sequence
370         if (ch == '\\') {
371             Character res = ArgChar.parseEscapeSequence(sb);
372             if (res == null) {
373                 throw new ParseException("Unable to unescape sequence:" + sb.toString());
374             }
375             return res;
376         }
377         return ch;
378     }
379 }