/* * Copyright (C) 2012 eXo Platform SAS. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.crsh.cli.impl.parser; import org.crsh.cli.descriptor.ArgumentDescriptor; import org.crsh.cli.descriptor.CommandDescriptor; import org.crsh.cli.impl.Multiplicity; import org.crsh.cli.descriptor.OptionDescriptor; import org.crsh.cli.impl.tokenizer.Token; import org.crsh.cli.impl.tokenizer.Tokenizer; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.LinkedList; import java.util.List; abstract class Status { /** * The input. */ static class Request<T> { /** . */ final Mode mode; /** . */ Tokenizer tokenizer; /** . */ final CommandDescriptor<T> command; Request(Mode mode, Tokenizer tokenizer, CommandDescriptor<T> command) { this.mode = mode; this.tokenizer = tokenizer; this.command = command; } } /** * The output. */ static class Response<T> { /** . */ Status status; /** . */ LinkedList<Event> events; /** . */ CommandDescriptor<T> command; Response(Status status) { this.status = status; this.events = null; this.command = null; } Response() { this.status = null; this.events = null; this.command = null; } void add(Event event) { if (events == null) { events = new LinkedList<Event>(); } events.add(event); } void addAll(Collection<Event> toAdd) { if (events == null) { events = new LinkedList<Event>(); } events.addAll(toAdd); } } /** * Process a request. * * @param req the request * @param <T> the generic type of the command * @return the response */ abstract <T> Response<T> process(Request<T> req); static class ReadingOption extends Status { <T> Response<T> process(Request<T> req) { Response<T> response = new Response<T>(); Token token = req.tokenizer.peek(); if (token == null) { response.add(new Event.Stop.Done(req.tokenizer.getIndex())); } else if (token instanceof Token.Whitespace) { response.add(new Event.Separator((Token.Whitespace) token)); req.tokenizer.next(); } else { Token.Literal literal = (Token.Literal)token; if (literal instanceof Token.Literal.Option) { Token.Literal.Option optionToken = (Token.Literal.Option)literal; if (optionToken.getName().length() == 0 && optionToken instanceof Token.Literal.Option.Long) { req.tokenizer.next(); if (req.tokenizer.hasNext()) { response.status = new Status.WantReadArg(); } else { if (req.mode == Mode.INVOKE) { response.status = new Status.Done(); response.add(new Event.Stop.Done(req.tokenizer.getIndex())); } else { response.add(new Event.Stop.Unresolved.NoSuchOption(optionToken)); } } } else { OptionDescriptor desc = req.command.resolveOption(literal.getValue()); if (desc != null) { req.tokenizer.next(); int arity = desc.getArity(); LinkedList<Token.Literal.Word> values = new LinkedList<Token.Literal.Word>(); while (arity > 0) { if (req.tokenizer.hasNext()) { Token a = req.tokenizer.peek(); if (a instanceof Token.Whitespace) { req.tokenizer.next(); if (req.tokenizer.hasNext() && req.tokenizer.peek() instanceof Token.Literal.Word) { // ok } else { req.tokenizer.pushBack(); break; } } else { Token.Literal b = (Token.Literal)a; if (b instanceof Token.Literal.Word) { values.addLast((Token.Literal.Word)b); req.tokenizer.next(); arity--; } else { req.tokenizer.pushBack(); break; } } } else { break; } } response.add(new Event.Option(req.command, desc, optionToken, values)); } else { response.add(new Event.Stop.Unresolved.NoSuchOption(optionToken)); } } } else { Token.Literal.Word wordLiteral = (Token.Literal.Word)literal; CommandDescriptor<T> m = req.command.getSubordinate(wordLiteral.getValue()); if (m != null) { response.command = m; req.tokenizer.next(); response.add(new Event.Subordinate.Explicit(m, wordLiteral)); } else { response.status = new Status.WantReadArg(); } } } return response; } } static class WantReadArg extends Status { @Override <T> Response<T> process(Request<T> req) { switch (req.mode) { case INVOKE: return new Response<T>(new Status.ComputeArg()); case COMPLETE: return new Response<T>(new Status.ReadingArg()); default: throw new AssertionError(); } } } static class ComputeArg extends Status { @Override <T> Response<T> process(Request<T> req) { Token token = req.tokenizer.peek(); Response<T> response = new Response<T>(); if (token == null) { response.add(new Event.Stop.Done(req.tokenizer.getIndex())); } else if (token instanceof Token.Whitespace) { response.add(new Event.Separator((Token.Whitespace) token)); req.tokenizer.next(); } else { // List<? extends ArgumentDescriptor> arguments = req.command.getArguments(); // Count the number ok remaining non whitespace; int tokenCount = 0; int wordCount = 0; do { Token t = req.tokenizer.next(); if (t instanceof Token.Literal) { wordCount++; } tokenCount++; } while (req.tokenizer.hasNext()); req.tokenizer.pushBack(tokenCount); // int oneCount = 0; int zeroOrOneCount = 0; int index = 0; for (ArgumentDescriptor argument : arguments) { Multiplicity multiplicity = argument.getMultiplicity(); if (multiplicity == Multiplicity.SINGLE) { if (argument.isRequired()) { if (oneCount + 1 > wordCount) { break; } oneCount++; } else { zeroOrOneCount++; } } index++; } // This the number of arguments we can satisfy arguments = arguments.subList(0, index); // How many words we can consume for zeroOrOne and zeroOrMore int toConsume = wordCount - oneCount; // Correct the zeroOrOneCount and adjust toConsume zeroOrOneCount = Math.min(zeroOrOneCount, toConsume); toConsume -= zeroOrOneCount; // The remaining LinkedList<Event> events = new LinkedList<Event>(); for (ArgumentDescriptor argument : arguments) { int size; switch (argument.getMultiplicity()) { case SINGLE: if (argument.isRequired()) { size = 1; } else { if (zeroOrOneCount > 0) { zeroOrOneCount--; size = 1; } else { size = 0; } } break; case MULTI: // We consume the remaining size = toConsume; toConsume = 0; break; default: throw new AssertionError(); } // Now take care of the argument if (size > 0) { List<Token.Literal> values = new ArrayList<Token.Literal>(size); while (size > 0) { Token t = req.tokenizer.next(); if (t instanceof Token.Literal) { values.add(((Token.Literal)t)); size--; } } events.addLast(new Event.Argument(req.command, argument, values)); // Add the whitespace if needed if (req.tokenizer.hasNext() && req.tokenizer.peek() instanceof Token.Whitespace) { events.addLast(new Event.Separator((Token.Whitespace) req.tokenizer.next())); } } } // events.addLast(new Event.Stop.Done(req.tokenizer.getIndex())); // response.status = new Status.Done(); response.addAll(events); } return response; } } static class Done extends Status { @Override <T> Response<T> process(Request<T> req) { throw new IllegalStateException(); } } static class ReadingArg extends Status { /** . */ private final int index; ReadingArg() { this(0); } private ReadingArg(int index) { this.index = index; } ReadingArg next() { return new ReadingArg(index + 1); } @Override <T> Response<T> process(Request<T> req) { Token token = req.tokenizer.peek(); Response<T> response = new Response<T>(); if (token == null) { response.add(new Event.Stop.Done(req.tokenizer.getIndex())); } else if (token instanceof Token.Whitespace) { response.add(new Event.Separator((Token.Whitespace) token)); req.tokenizer.next(); } else { final Token.Literal literal = (Token.Literal)token; List<? extends ArgumentDescriptor> arguments = req.command.getArguments(); if (index < arguments.size()) { ArgumentDescriptor argument = arguments.get(index); switch (argument.getMultiplicity()) { case SINGLE: req.tokenizer.next(); response.add(new Event.Argument(req.command, argument, Arrays.asList(literal))); response.status = next(); break; case MULTI: req.tokenizer.next(); List<Token.Literal> values = new ArrayList<Token.Literal>(); values.add(literal); while (req.tokenizer.hasNext()) { Token capture = req.tokenizer.next(); if (capture instanceof Token.Literal) { values.add(((Token.Literal)capture)); } else { if (req.tokenizer.hasNext()) { // Ok } else { req.tokenizer.pushBack(); break; } } } response.add(new Event.Argument(req.command, argument, values)); } } else { response.add(new Event.Stop.Unresolved.TooManyArguments(literal)); } } return response; } } }