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 Tokenizer tokenizer;
047
048 /** . */
049 final CommandDescriptor<T> command;
050
051 Request(Mode mode, Tokenizer tokenizer, CommandDescriptor<T> command) {
052 this.mode = mode;
053 this.tokenizer = tokenizer;
054 this.command = command;
055 }
056 }
057
058 /**
059 * The output.
060 */
061 static class Response<T> {
062
063 /** . */
064 Status status;
065
066 /** . */
067 LinkedList<Event> events;
068
069 /** . */
070 CommandDescriptor<T> command;
071
072 Response(Status status) {
073 this.status = status;
074 this.events = null;
075 this.command = null;
076 }
077
078 Response() {
079 this.status = null;
080 this.events = null;
081 this.command = null;
082 }
083
084 void add(Event event) {
085 if (events == null) {
086 events = new LinkedList<Event>();
087 }
088 events.add(event);
089 }
090
091 void addAll(Collection<Event> toAdd) {
092 if (events == null) {
093 events = new LinkedList<Event>();
094 }
095 events.addAll(toAdd);
096 }
097 }
098
099 /**
100 * Process a request.
101 *
102 * @param req the request
103 * @param <T> the generic type of the command
104 * @return the response
105 */
106 abstract <T> Response<T> process(Request<T> req);
107
108 static class ReadingOption extends Status {
109
110 <T> Response<T> process(Request<T> req) {
111 Response<T> response = new Response<T>();
112 Token token = req.tokenizer.peek();
113 if (token == null) {
114 response.add(new Event.Stop.Done(req.tokenizer.getIndex()));
115 } else if (token instanceof Token.Whitespace) {
116 response.add(new Event.Separator((Token.Whitespace) token));
117 req.tokenizer.next();
118 } else {
119 Token.Literal literal = (Token.Literal)token;
120 if (literal instanceof Token.Literal.Option) {
121 Token.Literal.Option optionToken = (Token.Literal.Option)literal;
122 if (optionToken.getName().length() == 0 && optionToken instanceof Token.Literal.Option.Long) {
123 req.tokenizer.next();
124 if (req.tokenizer.hasNext()) {
125 response.status = new Status.WantReadArg();
126 } else {
127 if (req.mode == Mode.INVOKE) {
128 response.status = new Status.Done();
129 response.add(new Event.Stop.Done(req.tokenizer.getIndex()));
130 } else {
131 response.add(new Event.Stop.Unresolved.NoSuchOption(optionToken));
132 }
133 }
134 } else {
135 OptionDescriptor desc = req.command.resolveOption(literal.getValue());
136 if (desc != null) {
137 req.tokenizer.next();
138 int arity = desc.getArity();
139 LinkedList<Token.Literal.Word> values = new LinkedList<Token.Literal.Word>();
140 while (arity > 0) {
141 if (req.tokenizer.hasNext()) {
142 Token a = req.tokenizer.peek();
143 if (a instanceof Token.Whitespace) {
144 req.tokenizer.next();
145 if (req.tokenizer.hasNext() && req.tokenizer.peek() instanceof Token.Literal.Word) {
146 // ok
147 } else {
148 req.tokenizer.pushBack();
149 break;
150 }
151 } else {
152 Token.Literal b = (Token.Literal)a;
153 if (b instanceof Token.Literal.Word) {
154 values.addLast((Token.Literal.Word)b);
155 req.tokenizer.next();
156 arity--;
157 } else {
158 req.tokenizer.pushBack();
159 break;
160 }
161 }
162 } else {
163 break;
164 }
165 }
166 response.add(new Event.Option(req.command, desc, optionToken, values));
167 } else {
168 response.add(new Event.Stop.Unresolved.NoSuchOption(optionToken));
169 }
170 }
171 } else {
172 Token.Literal.Word wordLiteral = (Token.Literal.Word)literal;
173 CommandDescriptor<T> m = req.command.getSubordinate(wordLiteral.getValue());
174 if (m != null) {
175 response.command = m;
176 req.tokenizer.next();
177 response.add(new Event.Subordinate.Explicit(m, wordLiteral));
178 } else {
179 response.status = new Status.WantReadArg();
180 }
181 }
182 }
183 return response;
184 }
185
186 }
187
188 static class WantReadArg extends Status {
189 @Override
190 <T> Response<T> process(Request<T> req) {
191 switch (req.mode) {
192 case INVOKE:
193 return new Response<T>(new Status.ComputeArg());
194 case COMPLETE:
195 return new Response<T>(new Status.ReadingArg());
196 default:
197 throw new AssertionError();
198 }
199 }
200 }
201
202 static class ComputeArg extends Status {
203
204 @Override
205 <T> Response<T> process(Request<T> req) {
206 Token token = req.tokenizer.peek();
207 Response<T> response = new Response<T>();
208 if (token == null) {
209 response.add(new Event.Stop.Done(req.tokenizer.getIndex()));
210 } else if (token instanceof Token.Whitespace) {
211 response.add(new Event.Separator((Token.Whitespace) token));
212 req.tokenizer.next();
213 } else {
214
215 //
216 List<? extends ArgumentDescriptor> arguments = req.command.getArguments();
217
218 // Count the number ok remaining non whitespace;
219 int tokenCount = 0;
220 int wordCount = 0;
221 do {
222 Token t = req.tokenizer.next();
223 if (t instanceof Token.Literal) {
224 wordCount++;
225 }
226 tokenCount++;
227 }
228 while (req.tokenizer.hasNext());
229 req.tokenizer.pushBack(tokenCount);
230
231 //
232 int oneCount = 0;
233 int zeroOrOneCount = 0;
234 int index = 0;
235 for (ArgumentDescriptor argument : arguments) {
236 Multiplicity multiplicity = argument.getMultiplicity();
237 if (multiplicity == Multiplicity.SINGLE) {
238 if (argument.isRequired()) {
239 if (oneCount + 1 > wordCount) {
240 break;
241 }
242 oneCount++;
243 } else {
244 zeroOrOneCount++;
245 }
246 }
247 index++;
248 }
249
250 // This the number of arguments we can satisfy
251 arguments = arguments.subList(0, index);
252
253 // How many words we can consume for zeroOrOne and zeroOrMore
254 int toConsume = wordCount - oneCount;
255
256 // Correct the zeroOrOneCount and adjust toConsume
257 zeroOrOneCount = Math.min(zeroOrOneCount, toConsume);
258 toConsume -= zeroOrOneCount;
259
260 // The remaining
261 LinkedList<Event> events = new LinkedList<Event>();
262 for (ArgumentDescriptor argument : arguments) {
263 int size;
264 switch (argument.getMultiplicity()) {
265 case SINGLE:
266 if (argument.isRequired()) {
267 size = 1;
268 } else {
269 if (zeroOrOneCount > 0) {
270 zeroOrOneCount--;
271 size = 1;
272 } else {
273 size = 0;
274 }
275 }
276 break;
277 case MULTI:
278 // We consume the remaining
279 size = toConsume;
280 toConsume = 0;
281 break;
282 default:
283 throw new AssertionError();
284 }
285
286 // Now take care of the argument
287 if (size > 0) {
288 List<Token.Literal> values = new ArrayList<Token.Literal>(size);
289 while (size > 0) {
290 Token t = req.tokenizer.next();
291 if (t instanceof Token.Literal) {
292 values.add(((Token.Literal)t));
293 size--;
294 }
295 }
296 events.addLast(new Event.Argument(req.command, argument, values));
297
298 // Add the whitespace if needed
299 if (req.tokenizer.hasNext() && req.tokenizer.peek() instanceof Token.Whitespace) {
300 events.addLast(new Event.Separator((Token.Whitespace) req.tokenizer.next()));
301 }
302 }
303 }
304
305 //
306 events.addLast(new Event.Stop.Done(req.tokenizer.getIndex()));
307
308 //
309 response.status = new Status.Done();
310 response.addAll(events);
311 }
312 return response;
313 }
314 }
315
316 static class Done extends Status {
317 @Override
318 <T> Response<T> process(Request<T> req) {
319 throw new IllegalStateException();
320 }
321 }
322
323 static class ReadingArg extends Status {
324
325 /** . */
326 private final int index;
327
328 ReadingArg() {
329 this(0);
330 }
331
332 private ReadingArg(int index) {
333 this.index = index;
334 }
335
336 ReadingArg next() {
337 return new ReadingArg(index + 1);
338 }
339
340 @Override
341 <T> Response<T> process(Request<T> req) {
342 Token token = req.tokenizer.peek();
343 Response<T> response = new Response<T>();
344 if (token == null) {
345 response.add(new Event.Stop.Done(req.tokenizer.getIndex()));
346 } else if (token instanceof Token.Whitespace) {
347 response.add(new Event.Separator((Token.Whitespace) token));
348 req.tokenizer.next();
349 } else {
350 final Token.Literal literal = (Token.Literal)token;
351 List<? extends ArgumentDescriptor> arguments = req.command.getArguments();
352 if (index < arguments.size()) {
353 ArgumentDescriptor argument = arguments.get(index);
354 switch (argument.getMultiplicity()) {
355 case SINGLE:
356 req.tokenizer.next();
357 response.add(new Event.Argument(req.command, argument, Arrays.asList(literal)));
358 response.status = next();
359 break;
360 case MULTI:
361 req.tokenizer.next();
362 List<Token.Literal> values = new ArrayList<Token.Literal>();
363 values.add(literal);
364 while (req.tokenizer.hasNext()) {
365 Token capture = req.tokenizer.next();
366 if (capture instanceof Token.Literal) {
367 values.add(((Token.Literal)capture));
368 } else {
369 if (req.tokenizer.hasNext()) {
370 // Ok
371 } else {
372 req.tokenizer.pushBack();
373 break;
374 }
375 }
376 }
377 response.add(new Event.Argument(req.command, argument, values));
378 }
379 } else {
380 response.add(new Event.Stop.Unresolved.TooManyArguments(literal));
381 }
382 }
383 return response;
384 }
385 }
386 }