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    }