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 }