blob: 8585847c193f0f79bed18a03aeee985b042c2e95 [file] [log] [blame]
Richard S. Hallfbd735b2009-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 */
19package aQute.shell.runtime;
20
21import java.util.*;
22
23public class Parser {
24 int current = 0;
25 CharSequence text;
26 boolean escaped;
27 static final String SPECIAL = "<;|{[\"'$'`(=";
28
29 public Parser(CharSequence program) {
30 text = program;
31 }
32
33 void ws() {
34 while (!eof() && Character.isWhitespace(peek())) {
35 current++;
36 if (peek() == '/' && current < text.length()-2 && text.charAt(current + 1) == '/') {
37 comment();
38 }
39 }
40 }
41
42 private void comment() {
43 while (!eof() && peek() != '\n' && peek() != '\r')
44 next();
45 }
46
47 boolean eof() {
48 return current >= text.length();
49 }
50
51 char peek() {
52 escaped = false;
53 if (eof())
54 return 0;
55
56 char c = text.charAt(current);
57
58 if (c == '\\') {
59 escaped = true;
60 c = text.charAt(++current);
61
62 switch (c) {
63 case 't':
64 c = '\t';
65 break;
66 case '\r':
67 case '\n':
68 c = ' ';
69 break;
70 case 'b':
71 c = '\b';
72 break;
73 case 'f':
74 c = '\f';
75 break;
76 case 'n':
77 c = '\n';
78 break;
79 case 'r':
80 c = '\r';
81 break;
82 case 'u':
83 c = unicode();
84 break;
85 default:
86 // We just take the next character literally
87 // but have the escaped flag set, important for {},[] etc
88 }
89 }
90 return c;
91 }
92
93 public List<List<List<CharSequence>>> program() {
94 List<List<List<CharSequence>>> program = new ArrayList<List<List<CharSequence>>>();
95 ws();
96 if (!eof()) {
97 program.add(statements());
98 while (peek() == '|') {
99 current++;
100 program.add(statements());
101 }
102 }
103 if (!eof())
104 throw new RuntimeException("Program has trailing text: "
105 + context(current));
106
107 return program;
108 }
109
110 CharSequence context(int around) {
111 return text.subSequence(Math.max(0, current - 20), Math.min(text
112 .length(), current + 4));
113 }
114
115 public List<List<CharSequence>> statements() {
116 List<List<CharSequence>> statements = new ArrayList<List<CharSequence>>();
117 statements.add(statement());
118 while (peek() == ';') {
119 current++;
120 statements.add(statement());
121 }
122 return statements;
123 }
124
125 public List<CharSequence> statement() {
126 List<CharSequence> statement = new ArrayList<CharSequence>();
127 statement.add(value());
128 while (!eof()) {
129 ws();
130 if (peek() == '|' || peek() == ';')
131 break;
132
133 if (!eof())
134 statement.add(messy());
135 }
136 return statement;
137 }
138
139 public CharSequence messy() {
140 char c = peek();
141 if (c > 0 && SPECIAL.indexOf(c)< 0) {
142 int start = current++;
143 while (!eof()) {
144 c = peek();
145 if (c == ';' || c == '|' || Character.isWhitespace(c))
146 break;
147 next();
148 }
149
150 return text.subSequence(start, current);
151 } else
152 return value();
153 }
154
155 CharSequence value() {
156 ws();
157
158 int start = current;
159 char c = next();
160 switch (c) {
161 case '{':
162 return text.subSequence(start, find('}', '{'));
163 case '(':
164 return text.subSequence(start, find(')', '('));
165 case '[':
166 return text.subSequence(start, find(']', '['));
167 case '"':
168 return text.subSequence(start + 1, quote('"'));
169 case '\'':
170 return text.subSequence(start + 1, quote('\''));
171 case '<':
172 return text.subSequence(start, find('>', '<'));
173 case '$':
174 value();
175 return text.subSequence(start, current);
176 }
177
178 if (Character.isJavaIdentifierPart(c)) {
179 // Some identifier or number
180 while (!eof()) {
181 c = peek();
182 if (c!=':' && !Character.isJavaIdentifierPart(c) && c != '.')
183 break;
184 next();
185 }
186 } else {
187 // Operator, repeat while in operator class
188 while (!eof()) {
189 c = peek();
190 if (Character.isWhitespace(c)
191 || Character.isJavaIdentifierPart(c))
192 break;
193 }
194 }
195 return text.subSequence(start, current);
196 }
197
198 char next() {
199 char c = peek();
200 current++;
201 return c;
202 }
203
204 char unicode() {
205 if (current + 4 > text.length())
206 throw new IllegalArgumentException(
207 "Unicode \\u escape at eof at pos ..." + context(current)
208 + "...");
209
210 String s = text.subSequence(current, current + 4).toString();
211 int n = Integer.parseInt(s, 16);
212 return (char) n;
213 }
214
215 private int find(char target, char deeper) {
216 int start = current;
217 int level = 1;
218
219 while (level != 0) {
220 if (eof())
221 throw new RuntimeException(
222 "Eof found in the middle of a compound for '" + target
223 + deeper + "', begins at " + context(start));
224
225 char c = next();
226 if (!escaped) {
227 if (c == target)
228 level--;
229 else if (c == deeper)
230 level++;
231 else if (c == '"')
232 quote('"');
233 else if (c == '\'')
234 quote('\'');
235 else if (c == '`')
236 quote('`');
237 }
238 }
239 return current;
240 }
241
242 int quote(char which) {
243 while (!eof() && (peek() != which || escaped))
244 next();
245
246 return current++;
247 }
248
249 CharSequence findVar() {
250 int start = current - 1;
251 char c = peek();
252
253 if (c == '{') {
254 next();
255 int end = find('}', '{');
256 return text.subSequence(start, end);
257 }
258
259 if (Character.isJavaIdentifierStart(c)) {
260 while (!eof() && Character.isJavaIdentifierPart(c) || c == '.') {
261 next();
262 }
263 return text.subSequence(start, current);
264 }
265 throw new IllegalArgumentException(
266 "Reference to variable does not match syntax of a variable: "
267 + context(start));
268 }
269
270 public String toString() {
271 return "..." + context(current) + "...";
272 }
273
274 public String unescape() {
275 StringBuilder sb = new StringBuilder();
276 while (!eof())
277 sb.append(next());
278 return sb.toString();
279 }
280}