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    }