/** * PermissionsEx * Copyright (C) zml and PermissionsEx contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package ninja.leaping.permissionsex.util.command.args; import ninja.leaping.permissionsex.util.Translatable; import java.util.ArrayList; import java.util.Collections; import java.util.List; import static ninja.leaping.permissionsex.util.Translations.t; /** * Parser for converting a quoted string into a list of arguments * * Grammar is roughly (yeah, this is not really a proper grammar but it gives you an idea of what's happening: * * WHITESPACE = Character.isWhiteSpace(codePoint) * CHAR := (all unicode) * ESCAPE := '\' CHAR * QUOTE = ' | " * UNQUOTED_ARG := (CHAR | ESCAPE)+ WHITESPACE * QUOTED_ARG := QUOTE (CHAR | ESCAPE)+ QUOTE * ARGS := ((UNQUOTED_ARG | QUOTED_ARG) WHITESPACE+)+ */ public class QuotedStringParser { private final boolean lenient; private final String buffer; private int index = -1; /** * Parse from a string of args * @param args The raw string of arguments to parse * @return A list of argument strings parsed as specified in the pseudo-grammar in the class documentation * @throws ArgumentParseException if args are presented with invalid syntax */ public static CommandArgs parseFrom(String args, boolean lenient) throws ArgumentParseException { return new QuotedStringParser(args, lenient).parse(); } private QuotedStringParser(String args, boolean lenient) { this.buffer = args; this.lenient = lenient; } public CommandArgs parse() throws ArgumentParseException { if (buffer.length() == 0) { // Fast track for argless commands return new CommandArgs(buffer, Collections.<CommandArgs.SingleArg>emptyList()); } List<CommandArgs.SingleArg> returnedArgs = new ArrayList<>(buffer.length() / 8); skipWhiteSpace(); while (hasMore()) { int startIdx = index + 1; String arg = nextArg(); returnedArgs.add(new CommandArgs.SingleArg(arg, startIdx, index)); skipWhiteSpace(); } return new CommandArgs(buffer, returnedArgs); } // Utility methods private boolean hasMore() { return index + 1 < buffer.length(); } private int peek() throws ArgumentParseException { if (!hasMore()) { throw createException(t("Buffer overrun while parsing args")); } return buffer.codePointAt(index + 1); } private int next() throws ArgumentParseException { if (!hasMore()) { throw createException(t("Buffer overrun while parsing args")); } return buffer.codePointAt(++index); } public ArgumentParseException createException(Translatable message) { return new ArgumentParseException(message, buffer, index); } // Parsing methods private void skipWhiteSpace() throws ArgumentParseException { if (!hasMore()) { return; } while (Character.isWhitespace(peek())) { next(); } } private String nextArg() throws ArgumentParseException { StringBuilder argBuilder = new StringBuilder(); int codePoint = peek(); if (codePoint == '"' || codePoint == '\'') { // quoted string parseQuotedString(codePoint, argBuilder); } else { parseUnquotedString(argBuilder); } return argBuilder.toString(); } private void parseQuotedString(int startQuotation, StringBuilder builder) throws ArgumentParseException { // Consume the start quotation character int nextCodePoint = next(); if (nextCodePoint != startQuotation) { throw createException(t("Actual next character '%c' did not match expected quotation character '%c'", nextCodePoint, startQuotation)); } while (true) { if (!hasMore()) { if (lenient) { return; } else { throw createException(t("Unterminated quoted string found")); //, new StringBuilder(1).appendCodePoint(nextCodePoint).toString()); } } nextCodePoint = next(); if (nextCodePoint == startQuotation) { return; } else if (nextCodePoint == '\\') { parseEscape(builder); } else { builder.appendCodePoint(nextCodePoint); } } } private void parseUnquotedString(StringBuilder builder) throws ArgumentParseException { while (hasMore()) { int nextCodePoint = next(); if (Character.isWhitespace(nextCodePoint)) { return; } else if (nextCodePoint == '\\') { parseEscape(builder); } else { builder.appendCodePoint(nextCodePoint); } } } private void parseEscape(StringBuilder builder) throws ArgumentParseException { builder.appendCodePoint(next()); // TODO: Unicode character escapes (\u00A7 type } }