001 /*
002 * Copyright (C) 2012 eXo Platform SAS.
003 *
004 * This is free software; you can redistribute it and/or modify it
005 * under the terms of the GNU Lesser General Public License as
006 * published by the Free Software Foundation; either version 2.1 of
007 * the License, or (at your option) any later version.
008 *
009 * This software is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * You should have received a copy of the GNU Lesser General Public
015 * License along with this software; if not, write to the Free
016 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
017 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
018 */
019
020 package org.crsh.cli.impl.parser;
021
022 import org.crsh.cli.descriptor.ArgumentDescriptor;
023 import org.crsh.cli.descriptor.CommandDescriptor;
024 import org.crsh.cli.impl.Multiplicity;
025 import org.crsh.cli.descriptor.OptionDescriptor;
026 import org.crsh.cli.impl.tokenizer.Token;
027 import org.crsh.cli.impl.tokenizer.Tokenizer;
028
029 import java.util.ArrayList;
030 import java.util.Arrays;
031 import java.util.Collection;
032 import java.util.LinkedList;
033 import java.util.List;
034
035 abstract class Status {
036
037 /**
038 * The input.
039 */
040 static class Request<T> {
041
042 /** . */
043 final Mode mode;
044
045 /** . */
046 final String mainName;
047
048 /** . */
049 Tokenizer tokenizer;
050
051 /** . */
052 final CommandDescriptor<T> command;
053
054 Request(Mode mode, String mainName, Tokenizer tokenizer, CommandDescriptor<T> command) {
055 this.mode = mode;
056 this.mainName = mainName;
057 this.tokenizer = tokenizer;
058 this.command = command;
059 }
060 }
061
062 /**
063 * The output.
064 */
065 static class Response<T> {
066
067 /** . */
068 Status status;
069
070 /** . */
071 LinkedList<Event> events;
072
073 /** . */
074 CommandDescriptor<T> command;
075
076 Response(Status status) {
077 this.status = status;
078 this.events = null;
079 this.command = null;
080 }
081
082 Response() {
083 this.status = null;
084 this.events = null;
085 this.command = null;
086 }
087
088 void add(Event event) {
089 if (events == null) {
090 events = new LinkedList<Event>();
091 }
092 events.add(event);
093 }
094
095 void addAll(Collection<Event> toAdd) {
096 if (events == null) {
097 events = new LinkedList<Event>();
098 }
099 events.addAll(toAdd);
100 }
101 }
102
103 /**
104 * Process a request.
105 *
106 * @param req the request
107 * @param <T> the generic type of the command
108 * @return the response
109 */
110 abstract <T> Response<T> process(Request<T> req);
111
112 static class ReadingOption extends Status {
113
114 <T> Response<T> process(Request<T> req) {
115 Response<T> response = new Response<T>();
116 Token token = req.tokenizer.peek();
117 if (token == null) {
118 response.add(new Event.Stop.Done(req.tokenizer.getIndex()));
119 } else if (token instanceof Token.Whitespace) {
120 response.add(new Event.Separator((Token.Whitespace) token));
121 req.tokenizer.next();
122 } else {
123 Token.Literal literal = (Token.Literal)token;
124 if (literal instanceof Token.Literal.Option) {
125 Token.Literal.Option optionToken = (Token.Literal.Option)literal;
126 if (optionToken.getName().length() == 0 && optionToken instanceof Token.Literal.Option.Long) {
127 req.tokenizer.next();
128 if (req.tokenizer.hasNext()) {
129 CommandDescriptor<T> m = req.command.getSubordinate(req.mainName);
130 if (m != null) {
131 response.command = m;
132 response.add(new Event.Subordinate.Implicit(m, optionToken));
133 }
134 response.status = new Status.WantReadArg();
135 } else {
136 if (req.mode == Mode.INVOKE) {
137 CommandDescriptor<T> m = req.command.getSubordinate(req.mainName);
138 if (m != null) {
139 response.command = m;
140 response.add(new Event.Subordinate.Implicit(m, optionToken));
141 }
142 response.status = new Status.Done();
143 response.add(new Event.Stop.Done(req.tokenizer.getIndex()));
144 } else {
145 CommandDescriptor<T> m = req.command.getSubordinate(req.mainName);
146 if (m != null) {
147 response.command = m;
148 response.add(new Event.Subordinate.Implicit(m, optionToken));
149 response.add(new Event.Stop.Unresolved.NoSuchOption(optionToken));
150 } else {
151 response.add(new Event.Stop.Unresolved.NoSuchOption(optionToken));
152 }
153 }
154 }
155 } else {
156 OptionDescriptor desc = req.command.findOption(literal.getValue());
157 if (desc != null) {
158 req.tokenizer.next();
159 int arity = desc.getArity();
160 LinkedList<Token.Literal.Word> values = new LinkedList<Token.Literal.Word>();
161 while (arity > 0) {
162 if (req.tokenizer.hasNext()) {
163 Token a = req.tokenizer.peek();
164 if (a instanceof Token.Whitespace) {
165 req.tokenizer.next();
166 if (req.tokenizer.hasNext() && req.tokenizer.peek() instanceof Token.Literal.Word) {
167 // ok
168 } else {
169 req.tokenizer.pushBack();
170 break;
171 }
172 } else {
173 Token.Literal b = (Token.Literal)a;
174 if (b instanceof Token.Literal.Word) {
175 values.addLast((Token.Literal.Word)b);
176 req.tokenizer.next();
177 arity--;
178 } else {
179 req.tokenizer.pushBack();
180 break;
181 }
182 }
183 } else {
184 break;
185 }
186 }
187 response.add(new Event.Option(req.command, desc, optionToken, values));
188 } else {
189 // We are reading an unknown option
190 // it could match an option of an implicit command
191 CommandDescriptor<T> m = req.command.getSubordinate(req.mainName);
192 if (m != null) {
193 desc = m.findOption(literal.getValue());
194 if (desc != null) {
195 response.command = m;
196 response.add(new Event.Subordinate.Implicit(m, literal));
197 } else {
198 if (req.command.getOptionNames().size() == 0) {
199 response.command = m;
200 response.add(new Event.Subordinate.Implicit(m, literal));
201 } else {
202 response.add(new Event.Stop.Unresolved.NoSuchOption(optionToken));
203 }
204 }
205 } else {
206 response.add(new Event.Stop.Unresolved.NoSuchOption(optionToken));
207 }
208 }
209 }
210 } else {
211 Token.Literal.Word wordLiteral = (Token.Literal.Word)literal;
212 CommandDescriptor<T> m = req.command.getSubordinate(wordLiteral.getValue());
213 if (m != null && !m.getName().equals(req.mainName)) {
214 response.command = m;
215 req.tokenizer.next();
216 response.add(new Event.Subordinate.Explicit(m, wordLiteral));
217 } else {
218 m = req.command.getSubordinate(req.mainName);
219 if (m != null) {
220 response.add(new Event.Subordinate.Implicit(m, wordLiteral));
221 response.status = new Status.WantReadArg();
222 response.command = m;
223 } else {
224 response.status = new Status.WantReadArg();
225 }
226 }
227 }
228 }
229 return response;
230 }
231
232 }
233
234 static class WantReadArg extends Status {
235 @Override
236 <T> Response<T> process(Request<T> req) {
237 switch (req.mode) {
238 case INVOKE:
239 return new Response<T>(new Status.ComputeArg());
240 case COMPLETE:
241 return new Response<T>(new Status.ReadingArg());
242 default:
243 throw new AssertionError();
244 }
245 }
246 }
247
248 static class ComputeArg extends Status {
249
250 @Override
251 <T> Response<T> process(Request<T> req) {
252 Token token = req.tokenizer.peek();
253 Response<T> response = new Response<T>();
254 if (token == null) {
255 response.add(new Event.Stop.Done(req.tokenizer.getIndex()));
256 } else if (token instanceof Token.Whitespace) {
257 response.add(new Event.Separator((Token.Whitespace) token));
258 req.tokenizer.next();
259 } else {
260
261 //
262 List<? extends ArgumentDescriptor> arguments = req.command.getArguments();
263
264 // Count the number ok remaining non whitespace;
265 int tokenCount = 0;
266 int wordCount = 0;
267 do {
268 Token t = req.tokenizer.next();
269 if (t instanceof Token.Literal) {
270 wordCount++;
271 }
272 tokenCount++;
273 }
274 while (req.tokenizer.hasNext());
275 req.tokenizer.pushBack(tokenCount);
276
277 //
278 int oneCount = 0;
279 int zeroOrOneCount = 0;
280 int index = 0;
281 for (ArgumentDescriptor argument : arguments) {
282 Multiplicity multiplicity = argument.getMultiplicity();
283 if (multiplicity == Multiplicity.SINGLE) {
284 if (argument.isRequired()) {
285 if (oneCount + 1 > wordCount) {
286 break;
287 }
288 oneCount++;
289 } else {
290 zeroOrOneCount++;
291 }
292 }
293 index++;
294 }
295
296 // This the number of arguments we can satisfy
297 arguments = arguments.subList(0, index);
298
299 // How many words we can consume for zeroOrOne and zeroOrMore
300 int toConsume = wordCount - oneCount;
301
302 // Correct the zeroOrOneCount and adjust toConsume
303 zeroOrOneCount = Math.min(zeroOrOneCount, toConsume);
304 toConsume -= zeroOrOneCount;
305
306 // The remaining
307 LinkedList<Event> events = new LinkedList<Event>();
308 for (ArgumentDescriptor argument : arguments) {
309 int size;
310 switch (argument.getMultiplicity()) {
311 case SINGLE:
312 if (argument.isRequired()) {
313 size = 1;
314 } else {
315 if (zeroOrOneCount > 0) {
316 zeroOrOneCount--;
317 size = 1;
318 } else {
319 size = 0;
320 }
321 }
322 break;
323 case MULTI:
324 // We consume the remaining
325 size = toConsume;
326 toConsume = 0;
327 break;
328 default:
329 throw new AssertionError();
330 }
331
332 // Now take care of the argument
333 if (size > 0) {
334 List<Token.Literal> values = new ArrayList<Token.Literal>(size);
335 while (size > 0) {
336 Token t = req.tokenizer.next();
337 if (t instanceof Token.Literal) {
338 values.add(((Token.Literal)t));
339 size--;
340 }
341 }
342 events.addLast(new Event.Argument(req.command, argument, values));
343
344 // Add the whitespace if needed
345 if (req.tokenizer.hasNext() && req.tokenizer.peek() instanceof Token.Whitespace) {
346 events.addLast(new Event.Separator((Token.Whitespace) req.tokenizer.next()));
347 }
348 }
349 }
350
351 //
352 events.addLast(new Event.Stop.Done(req.tokenizer.getIndex()));
353
354 //
355 response.status = new Status.Done();
356 response.addAll(events);
357 }
358 return response;
359 }
360 }
361
362 static class Done extends Status {
363 @Override
364 <T> Response<T> process(Request<T> req) {
365 throw new IllegalStateException();
366 }
367 }
368
369 static class ReadingArg extends Status {
370
371 /** . */
372 private final int index;
373
374 ReadingArg() {
375 this(0);
376 }
377
378 private ReadingArg(int index) {
379 this.index = index;
380 }
381
382 ReadingArg next() {
383 return new ReadingArg(index + 1);
384 }
385
386 @Override
387 <T> Response<T> process(Request<T> req) {
388 Token token = req.tokenizer.peek();
389 Response<T> response = new Response<T>();
390 if (token == null) {
391 response.add(new Event.Stop.Done(req.tokenizer.getIndex()));
392 } else if (token instanceof Token.Whitespace) {
393 response.add(new Event.Separator((Token.Whitespace) token));
394 req.tokenizer.next();
395 } else {
396 final Token.Literal literal = (Token.Literal)token;
397 List<? extends ArgumentDescriptor> arguments = req.command.getArguments();
398 if (index < arguments.size()) {
399 ArgumentDescriptor argument = arguments.get(index);
400 switch (argument.getMultiplicity()) {
401 case SINGLE:
402 req.tokenizer.next();
403 response.add(new Event.Argument(req.command, argument, Arrays.asList(literal)));
404 response.status = next();
405 break;
406 case MULTI:
407 req.tokenizer.next();
408 List<Token.Literal> values = new ArrayList<Token.Literal>();
409 values.add(literal);
410 while (req.tokenizer.hasNext()) {
411 Token capture = req.tokenizer.next();
412 if (capture instanceof Token.Literal) {
413 values.add(((Token.Literal)capture));
414 } else {
415 if (req.tokenizer.hasNext()) {
416 // Ok
417 } else {
418 req.tokenizer.pushBack();
419 break;
420 }
421 }
422 }
423 response.add(new Event.Argument(req.command, argument, values));
424 }
425 } else {
426 response.add(new Event.Stop.Unresolved.TooManyArguments(literal));
427 }
428 }
429 return response;
430 }
431 }
432 }