blob: 10e28dbe88f70ad6b4bffd43d1378c08d1282607 [file] [log] [blame]
Richard S. Hallaf656a02009-06-11 16:07:20 +00001/*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
Guillaume Nodetb4e055d2009-06-22 15:21:30 +000019// DWB14: parser loops if // comment at start of program
20// DWB15: allow program to have trailing ';'
Richard S. Hallaf656a02009-06-11 16:07:20 +000021package aQute.shell.runtime;
22
23import java.util.*;
24
25public class Parser {
26 int current = 0;
27 CharSequence text;
28 boolean escaped;
29 static final String SPECIAL = "<;|{[\"'$'`(=";
30
31 public Parser(CharSequence program) {
32 text = program;
33 }
34
35 void ws() {
Guillaume Nodetb4e055d2009-06-22 15:21:30 +000036 // derek: BUGFIX: loop if comment at beginning of input
37 //while (!eof() && Character.isWhitespace(peek())) {
38 while (!eof() && (Character.isWhitespace(peek()) || current == 0)) {
39 if (current != 0 || Character.isWhitespace(peek()))
40 current++;
Richard S. Hallaf656a02009-06-11 16:07:20 +000041 if (peek() == '/' && current < text.length()-2 && text.charAt(current + 1) == '/') {
42 comment();
43 }
Guillaume Nodetb4e055d2009-06-22 15:21:30 +000044 if (current == 0)
45 break;
Richard S. Hallaf656a02009-06-11 16:07:20 +000046 }
47 }
48
49 private void comment() {
50 while (!eof() && peek() != '\n' && peek() != '\r')
51 next();
52 }
53
54 boolean eof() {
55 return current >= text.length();
56 }
57
58 char peek() {
59 escaped = false;
60 if (eof())
61 return 0;
62
63 char c = text.charAt(current);
64
65 if (c == '\\') {
66 escaped = true;
Guillaume Nodetb4e055d2009-06-22 15:21:30 +000067 ++current;
68 if (eof())
69 throw new RuntimeException("Eof found after \\"); // derek
70
71 c = text.charAt(current);
Richard S. Hallaf656a02009-06-11 16:07:20 +000072
73 switch (c) {
74 case 't':
75 c = '\t';
76 break;
77 case '\r':
78 case '\n':
79 c = ' ';
80 break;
81 case 'b':
82 c = '\b';
83 break;
84 case 'f':
85 c = '\f';
86 break;
87 case 'n':
88 c = '\n';
89 break;
90 case 'r':
91 c = '\r';
92 break;
93 case 'u':
94 c = unicode();
95 break;
96 default:
97 // We just take the next character literally
98 // but have the escaped flag set, important for {},[] etc
99 }
100 }
101 return c;
102 }
103
104 public List<List<List<CharSequence>>> program() {
105 List<List<List<CharSequence>>> program = new ArrayList<List<List<CharSequence>>>();
106 ws();
107 if (!eof()) {
108 program.add(statements());
109 while (peek() == '|') {
110 current++;
111 program.add(statements());
112 }
113 }
114 if (!eof())
115 throw new RuntimeException("Program has trailing text: "
116 + context(current));
117
118 return program;
119 }
120
121 CharSequence context(int around) {
122 return text.subSequence(Math.max(0, current - 20), Math.min(text
123 .length(), current + 4));
124 }
125
126 public List<List<CharSequence>> statements() {
127 List<List<CharSequence>> statements = new ArrayList<List<CharSequence>>();
128 statements.add(statement());
129 while (peek() == ';') {
130 current++;
Guillaume Nodetb4e055d2009-06-22 15:21:30 +0000131 // derek: BUGFIX: allow trailing ;
132 ws();
133 if (!eof())
134 statements.add(statement());
Richard S. Hallaf656a02009-06-11 16:07:20 +0000135 }
136 return statements;
137 }
138
139 public List<CharSequence> statement() {
140 List<CharSequence> statement = new ArrayList<CharSequence>();
Guillaume Nodetb4e055d2009-06-22 15:21:30 +0000141 statement.add(value());
Richard S. Hallaf656a02009-06-11 16:07:20 +0000142 while (!eof()) {
143 ws();
144 if (peek() == '|' || peek() == ';')
145 break;
146
147 if (!eof())
148 statement.add(messy());
149 }
150 return statement;
151 }
152
153 public CharSequence messy() {
154 char c = peek();
155 if (c > 0 && SPECIAL.indexOf(c)< 0) {
156 int start = current++;
157 while (!eof()) {
158 c = peek();
159 if (c == ';' || c == '|' || Character.isWhitespace(c))
160 break;
161 next();
162 }
163
164 return text.subSequence(start, current);
165 } else
166 return value();
167 }
168
169 CharSequence value() {
170 ws();
171
172 int start = current;
173 char c = next();
174 switch (c) {
175 case '{':
176 return text.subSequence(start, find('}', '{'));
177 case '(':
178 return text.subSequence(start, find(')', '('));
179 case '[':
180 return text.subSequence(start, find(']', '['));
181 case '"':
182 return text.subSequence(start + 1, quote('"'));
183 case '\'':
184 return text.subSequence(start + 1, quote('\''));
185 case '<':
186 return text.subSequence(start, find('>', '<'));
187 case '$':
188 value();
189 return text.subSequence(start, current);
190 }
191
192 if (Character.isJavaIdentifierPart(c)) {
193 // Some identifier or number
194 while (!eof()) {
195 c = peek();
196 if (c!=':' && !Character.isJavaIdentifierPart(c) && c != '.')
197 break;
198 next();
199 }
200 } else {
201 // Operator, repeat while in operator class
202 while (!eof()) {
203 c = peek();
204 if (Character.isWhitespace(c)
205 || Character.isJavaIdentifierPart(c))
206 break;
207 }
208 }
Guillaume Nodetb4e055d2009-06-22 15:21:30 +0000209
Richard S. Hallaf656a02009-06-11 16:07:20 +0000210 return text.subSequence(start, current);
211 }
212
213 char next() {
214 char c = peek();
215 current++;
216 return c;
217 }
218
219 char unicode() {
220 if (current + 4 > text.length())
221 throw new IllegalArgumentException(
222 "Unicode \\u escape at eof at pos ..." + context(current)
223 + "...");
224
225 String s = text.subSequence(current, current + 4).toString();
226 int n = Integer.parseInt(s, 16);
227 return (char) n;
228 }
229
230 private int find(char target, char deeper) {
231 int start = current;
232 int level = 1;
233
234 while (level != 0) {
235 if (eof())
236 throw new RuntimeException(
237 "Eof found in the middle of a compound for '" + target
238 + deeper + "', begins at " + context(start));
239
240 char c = next();
241 if (!escaped) {
242 if (c == target)
243 level--;
244 else if (c == deeper)
245 level++;
246 else if (c == '"')
247 quote('"');
248 else if (c == '\'')
249 quote('\'');
250 else if (c == '`')
251 quote('`');
252 }
253 }
254 return current;
255 }
256
257 int quote(char which) {
258 while (!eof() && (peek() != which || escaped))
259 next();
260
261 return current++;
262 }
263
264 CharSequence findVar() {
265 int start = current - 1;
266 char c = peek();
267
268 if (c == '{') {
269 next();
270 int end = find('}', '{');
271 return text.subSequence(start, end);
272 }
273
274 if (Character.isJavaIdentifierStart(c)) {
275 while (!eof() && Character.isJavaIdentifierPart(c) || c == '.') {
276 next();
277 }
278 return text.subSequence(start, current);
279 }
280 throw new IllegalArgumentException(
281 "Reference to variable does not match syntax of a variable: "
282 + context(start));
283 }
284
285 public String toString() {
286 return "..." + context(current) + "...";
287 }
288
289 public String unescape() {
290 StringBuilder sb = new StringBuilder();
291 while (!eof())
292 sb.append(next());
293 return sb.toString();
294 }
295}