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
020package org.crsh.cli.impl.parser;
021
022import org.crsh.cli.descriptor.ArgumentDescriptor;
023import org.crsh.cli.descriptor.CommandDescriptor;
024import org.crsh.cli.impl.Multiplicity;
025import org.crsh.cli.descriptor.OptionDescriptor;
026import org.crsh.cli.impl.tokenizer.Token;
027import org.crsh.cli.impl.tokenizer.Tokenizer;
028
029import java.util.ArrayList;
030import java.util.Arrays;
031import java.util.Collection;
032import java.util.LinkedList;
033import java.util.List;
034
035abstract 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}