package jdk.internal.org.jline.reader.impl;
import java.util.*;
import java.util.function.Predicate;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import jdk.internal.org.jline.reader.CompletingParsedLine;
import jdk.internal.org.jline.reader.EOFError;
import jdk.internal.org.jline.reader.ParsedLine;
import jdk.internal.org.jline.reader.Parser;
public class DefaultParser implements Parser {
public enum Bracket {
ROUND,
CURLY,
SQUARE,
ANGLE;
}
private char[] quoteChars = {'\'', '"'};
private char[] escapeChars = {'\\'};
private boolean eofOnUnclosedQuote;
private boolean eofOnEscapedNewLine;
private char[] openingBrackets = null;
private char[] closingBrackets = null;
private String regexVariable = "[a-zA-Z_]{1,}[a-zA-Z0-9_-]*((.|\\['|\\[\\\"|\\[)[a-zA-Z0-9_-]*(|'\\]|\\\"\\]|\\])){0,1}";
private String regexCommand = "[:]{0,1}[a-zA-Z]{1,}[a-zA-Z0-9_-]*";
private int commandGroup = 4;
public DefaultParser quoteChars(final char[] chars) {
this.quoteChars = chars;
return this;
}
public DefaultParser escapeChars(final char[] chars) {
this.escapeChars = chars;
return this;
}
public DefaultParser eofOnUnclosedQuote(boolean eofOnUnclosedQuote) {
this.eofOnUnclosedQuote = eofOnUnclosedQuote;
return this;
}
public DefaultParser eofOnUnclosedBracket(Bracket... brackets) {
setEofOnUnclosedBracket(brackets);
return this;
}
public DefaultParser eofOnEscapedNewLine(boolean eofOnEscapedNewLine) {
this.eofOnEscapedNewLine = eofOnEscapedNewLine;
return this;
}
public DefaultParser regexVariable(String regexVariable) {
this.regexVariable = regexVariable;
return this;
}
public DefaultParser regexCommand(String regexCommand) {
this.regexCommand = regexCommand;
return this;
}
public DefaultParser commandGroup(int commandGroup) {
this.commandGroup = commandGroup;
return this;
}
public void setQuoteChars(final char[] chars) {
this.quoteChars = chars;
}
public char[] getQuoteChars() {
return this.quoteChars;
}
public void setEscapeChars(final char[] chars) {
this.escapeChars = chars;
}
public char[] getEscapeChars() {
return this.escapeChars;
}
public void setEofOnUnclosedQuote(boolean eofOnUnclosedQuote) {
this.eofOnUnclosedQuote = eofOnUnclosedQuote;
}
public boolean isEofOnUnclosedQuote() {
return eofOnUnclosedQuote;
}
public void setEofOnEscapedNewLine(boolean eofOnEscapedNewLine) {
this.eofOnEscapedNewLine = eofOnEscapedNewLine;
}
public boolean isEofOnEscapedNewLine() {
return eofOnEscapedNewLine;
}
public void setEofOnUnclosedBracket(Bracket... brackets) {
if (brackets == null) {
openingBrackets = null;
closingBrackets = null;
} else {
Set<Bracket> bs = new HashSet<>(Arrays.asList(brackets));
openingBrackets = new char[bs.size()];
closingBrackets = new char[bs.size()];
int i = 0;
for (Bracket b : bs) {
switch (b) {
case ROUND:
openingBrackets[i] = '(';
closingBrackets[i] = ')';
break;
case CURLY:
openingBrackets[i] = '{';
closingBrackets[i] = '}';
break;
case SQUARE:
openingBrackets[i] = '[';
closingBrackets[i] = ']';
break;
case ANGLE:
openingBrackets[i] = '<';
closingBrackets[i] = '>';
break;
}
i++;
}
}
}
public void setRegexVariable(String regexVariable) {
this.regexVariable = regexVariable;
}
public void setRegexCommand(String regexCommand) {
this.regexCommand = regexCommand;
}
public void setCommandGroup(int commandGroup) {
this.commandGroup = commandGroup;
}
@Override
public boolean validCommandName(String name) {
return name != null && name.matches(regexCommand);
}
@Override
public boolean validVariableName(String name) {
return name != null && name.matches(regexVariable);
}
@Override
public String getCommand(final String line) {
String out = "";
Pattern patternCommand = Pattern.compile("^\\s*" + regexVariable + "=(" + regexCommand + ")(\\s+.*|$)");
Matcher matcher = patternCommand.matcher(line);
if (matcher.find()) {
out = matcher.group(commandGroup);
} else {
out = line.trim().split("\\s+")[0];
int idx = out.indexOf("=");
if (idx > -1) {
out = out.substring(idx + 1);
}
if (!out.matches(regexCommand)) {
out = "";
}
}
return out;
}
@Override
public String getVariable(final String line) {
String out = null;
Pattern patternCommand = Pattern.compile("^\\s*(" + regexVariable + ")\\s*=[^=~].*");
Matcher matcher = patternCommand.matcher(line);
if (matcher.find()) {
out = matcher.group(1);
}
return out;
}
public ParsedLine parse(final String line, final int cursor, ParseContext context) {
List<String> words = new LinkedList<>();
StringBuilder current = new StringBuilder();
int wordCursor = -1;
int wordIndex = -1;
int quoteStart = -1;
int rawWordCursor = -1;
int rawWordLength = -1;
int rawWordStart = 0;
BracketChecker bracketChecker = new BracketChecker(cursor);
boolean quotedWord = false;
for (int i = 0; (line != null) && (i < line.length()); i++) {
if (i == cursor) {
wordIndex = words.size();
wordCursor = current.length();
rawWordCursor = i - rawWordStart;
}
if (quoteStart < 0 && isQuoteChar(line, i)) {
quoteStart = i;
if (current.length()==0) {
quotedWord = true;
if (context == ParseContext.SPLIT_LINE) {
current.append(line.charAt(i));
}
} else {
current.append(line.charAt(i));
}
} else if (quoteStart >= 0 && line.charAt(quoteStart) == line.charAt(i) && !isEscaped(line, i)) {
if (!quotedWord || context == ParseContext.SPLIT_LINE) {
current.append(line.charAt(i));
} else if (rawWordCursor >= 0 && rawWordLength < 0) {
rawWordLength = i - rawWordStart + 1;
}
quoteStart = -1;
quotedWord = false;
} else if (quoteStart < 0 && isDelimiter(line, i)) {
if (current.length() > 0) {
words.add(current.toString());
current.setLength(0);
if (rawWordCursor >= 0 && rawWordLength < 0) {
rawWordLength = i - rawWordStart;
}
}
rawWordStart = i + 1;
} else {
if (!isEscapeChar(line, i)) {
current.append(line.charAt(i));
if (quoteStart < 0) {
bracketChecker.check(line, i);
}
} else if (context == ParseContext.SPLIT_LINE) {
current.append(line.charAt(i));
}
}
}
if (current.length() > 0 || cursor == line.length()) {
words.add(current.toString());
if (rawWordCursor >= 0 && rawWordLength < 0) {
rawWordLength = line.length() - rawWordStart;
}
}
if (cursor == line.length()) {
wordIndex = words.size() - 1;
wordCursor = words.get(words.size() - 1).length();
rawWordCursor = cursor - rawWordStart;
rawWordLength = rawWordCursor;
}
if (context != ParseContext.COMPLETE) {
if (eofOnEscapedNewLine && isEscapeChar(line, line.length() - 1)) {
throw new EOFError(-1, -1, "Escaped new line", "newline");
}
if (eofOnUnclosedQuote && quoteStart >= 0) {
throw new EOFError(-1, -1, "Missing closing quote", line.charAt(quoteStart) == '\''
? "quote" : "dquote");
}
if (bracketChecker.isClosingBracketMissing() || bracketChecker.isOpeningBracketMissing()) {
String message = null;
String missing = null;
if (bracketChecker.isClosingBracketMissing()) {
message = "Missing closing brackets";
missing = "add: " + bracketChecker.getMissingClosingBrackets();
} else {
message = "Missing opening bracket";
missing = "missing: " + bracketChecker.getMissingOpeningBracket();
}
throw new EOFError(-1, -1, message, missing,
bracketChecker.getOpenBrackets(), bracketChecker.getNextClosingBracket());
}
}
String openingQuote = quotedWord ? line.substring(quoteStart, quoteStart + 1) : null;
return new ArgumentList(line, words, wordIndex, wordCursor, cursor, openingQuote, rawWordCursor, rawWordLength);
}
public boolean isDelimiter(final CharSequence buffer, final int pos) {
return !isQuoted(buffer, pos) && !isEscaped(buffer, pos) && isDelimiterChar(buffer, pos);
}
public boolean isQuoted(final CharSequence buffer, final int pos) {
return false;
}
public boolean isQuoteChar(final CharSequence buffer, final int pos) {
if (pos < 0) {
return false;
}
if (quoteChars != null) {
for (char e : quoteChars) {
if (e == buffer.charAt(pos)) {
return !isEscaped(buffer, pos);
}
}
}
return false;
}
@Override
public boolean isEscapeChar(char ch) {
if (escapeChars != null) {
for (char e : escapeChars) {
if (e == ch) {
return true;
}
}
}
return false;
}
public boolean isEscapeChar(final CharSequence buffer, final int pos) {
if (pos < 0) {
return false;
}
char ch = buffer.charAt(pos);
return isEscapeChar(ch) && !isEscaped(buffer, pos);
}
public boolean isEscaped(final CharSequence buffer, final int pos) {
if (pos <= 0) {
return false;
}
return isEscapeChar(buffer, pos - 1);
}
public boolean isDelimiterChar(CharSequence buffer, int pos) {
return Character.isWhitespace(buffer.charAt(pos));
}
private boolean isRawEscapeChar(char key) {
if (escapeChars != null) {
for (char e : escapeChars) {
if (e == key) {
return true;
}
}
}
return false;
}
private boolean isRawQuoteChar(char key) {
if (quoteChars != null) {
for (char e : quoteChars) {
if (e == key) {
return true;
}
}
}
return false;
}
private class BracketChecker {
private int missingOpeningBracket = -1;
private List<Integer> nested = new ArrayList<>();
private int openBrackets = 0;
private int cursor;
private String nextClosingBracket;
public BracketChecker(int cursor) {
this.cursor = cursor;
}
public void check(final CharSequence buffer, final int pos) {
if (openingBrackets == null || pos < 0) {
return;
}
int bid = bracketId(openingBrackets, buffer, pos);
if (bid >= 0) {
nested.add(bid);
} else {
bid = bracketId(closingBrackets, buffer, pos);
if (bid >= 0) {
if (!nested.isEmpty() && bid == nested.get(nested.size()-1)) {
nested.remove(nested.size()-1);
} else {
missingOpeningBracket = bid;
}
}
}
if (cursor > pos) {
openBrackets = nested.size();
if (nested.size() > 0) {
nextClosingBracket = String.valueOf(closingBrackets[nested.get(nested.size() - 1)]);
}
}
}
public boolean isOpeningBracketMissing() {
return missingOpeningBracket != -1;
}
public String getMissingOpeningBracket() {
if (!isOpeningBracketMissing()) {
return null;
}
return Character.toString(openingBrackets[missingOpeningBracket]);
}
public boolean isClosingBracketMissing() {
return !nested.isEmpty();
}
public String getMissingClosingBrackets() {
if (!isClosingBracketMissing()) {
return null;
}
StringBuilder out = new StringBuilder();
for (int i = nested.size() - 1; i > -1; i--) {
out.append(closingBrackets[nested.get(i)]);
}
return out.toString();
}
public int getOpenBrackets() {
return openBrackets;
}
public String getNextClosingBracket() {
return nested.size() == 2 ? nextClosingBracket : null;
}
private int bracketId(final char[] brackets, final CharSequence buffer, final int pos) {
for (int i=0; i < brackets.length; i++) {
if (buffer.charAt(pos) == brackets[i]) {
return i;
}
}
return -1;
}
}
public class ArgumentList implements ParsedLine, CompletingParsedLine
{
private final String line;
private final List<String> words;
private final int wordIndex;
private final int wordCursor;
private final int cursor;
private final String openingQuote;
private final int rawWordCursor;
private final int rawWordLength;
@Deprecated
public ArgumentList(final String line, final List<String> words,
final int wordIndex, final int wordCursor,
final int cursor) {
this(line, words, wordIndex, wordCursor, cursor,
null, wordCursor, words.get(wordIndex).length());
}
public ArgumentList(final String line, final List<String> words,
final int wordIndex, final int wordCursor,
final int cursor, final String openingQuote,
final int rawWordCursor, final int rawWordLength) {
this.line = line;
this.words = Collections.unmodifiableList(Objects.requireNonNull(words));
this.wordIndex = wordIndex;
this.wordCursor = wordCursor;
this.cursor = cursor;
this.openingQuote = openingQuote;
this.rawWordCursor = rawWordCursor;
this.rawWordLength = rawWordLength;
}
public int wordIndex() {
return this.wordIndex;
}
public String word() {
if ((wordIndex < 0) || (wordIndex >= words.size())) {
return "";
}
return words.get(wordIndex);
}
public int wordCursor() {
return this.wordCursor;
}
public List<String> words() {
return this.words;
}
public int cursor() {
return this.cursor;
}
public String line() {
return line;
}
public CharSequence escape(CharSequence candidate, boolean complete) {
StringBuilder sb = new StringBuilder(candidate);
Predicate<Integer> needToBeEscaped;
String quote = openingQuote;
boolean middleQuotes = false;
if (openingQuote==null) {
for (int i=0; i < sb.length(); i++) {
if (isQuoteChar(sb, i)) {
middleQuotes = true;
break;
}
}
}
if (escapeChars != null) {
if (openingQuote != null) {
needToBeEscaped = i -> isRawEscapeChar(sb.charAt(i)) || String.valueOf(sb.charAt(i)).equals(openingQuote);
}
else if (middleQuotes) {
needToBeEscaped = i -> isRawEscapeChar(sb.charAt(i));
}
else {
needToBeEscaped = i -> isDelimiterChar(sb, i) || isRawEscapeChar(sb.charAt(i)) || isRawQuoteChar(sb.charAt(i));
}
for (int i = 0; i < sb.length(); i++) {
if (needToBeEscaped.test(i)) {
sb.insert(i++, escapeChars[0]);
}
}
} else if (openingQuote == null && !middleQuotes) {
for (int i = 0; i < sb.length(); i++) {
if (isDelimiterChar(sb, i)) {
quote = "'";
break;
}
}
}
if (quote != null) {
sb.insert(0, quote);
if (complete) {
sb.append(quote);
}
}
return sb;
}
@Override
public int rawWordCursor() {
return rawWordCursor;
}
@Override
public int rawWordLength() {
return rawWordLength;
}
}
}