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 }