Improve shorthand parser

This commit is contained in:
Luck 2019-11-25 00:34:23 +00:00
parent 500385cd3e
commit 787f691f44
No known key found for this signature in database
GPG Key ID: EFA9B3EC5FD90F8B
2 changed files with 136 additions and 100 deletions

View File

@ -74,7 +74,7 @@ public abstract class AbstractNode<N extends ScopedNode<N, B>, B extends NodeBui
this.contexts = contexts;
this.metadata = metadata;
this.resolvedShorthand = this instanceof PermissionNode ? ImmutableList.copyOf(ShorthandParser.parseShorthand(this.key)) : ImmutableList.of();
this.resolvedShorthand = this instanceof PermissionNode ? ImmutableList.copyOf(ShorthandParser.expandShorthand(this.key)) : ImmutableList.of();
this.hashCode = calculateHashCode();
}

View File

@ -26,45 +26,123 @@
package me.lucko.luckperms.common.node.utils;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterators;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Iterator;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public final class ShorthandParser {
private ShorthandParser() {}
/**
* Utility to expand shorthand nodes
*/
public enum ShorthandParser {
private static final List<Function<String, Iterable<String>>> PARSERS = ImmutableList.<Function<String, Iterable<String>>>builder()
.add(new ListParser())
.add(new CharacterRangeParser())
.add(new NumericRangeParser())
.build();
/**
* Expands "1-4" to ["1", "2", "3", "4"]
*/
NUMERIC_RANGE {
@Override
public Iterator<String> extract(String input) {
int index = input.indexOf(RANGE_SEPARATOR);
if (index == -1 || index == 0 || index == (input.length() - 1)) {
return null;
}
public static Set<String> parseShorthand(String s) {
Set<String> results = new HashSet<>(1);
return new RangeIterator(Integer.parseInt(input.substring(0, index)), Integer.parseInt(input.substring(index + 1))) {
@Override
protected String toString(int i) {
return Integer.toString(i);
}
};
}
},
/**
* Expands "a-d" to ["a", "b", "c", "d"]
*/
CHARACTER_RANGE {
@Override
public Iterator<String> extract(String input) {
if (input.length() != 3 || input.charAt(1) != RANGE_SEPARATOR) {
return null;
}
return new RangeIterator(input.charAt(0), input.charAt(2)) {
@Override
protected String toString(int i) {
return Character.toString((char) i);
}
};
}
},
/**
* Expands "aa,bb,cc" to ["aa", "bb", "cc"]
*/
LIST {
private final Splitter splitter = Splitter.on(LIST_SEPARATOR).omitEmptyStrings();
@Override
public Iterator<String> extract(String input) {
if (input.indexOf(LIST_SEPARATOR) == -1) {
return Iterators.singletonIterator(input);
}
return this.splitter.split(input).iterator();
}
};
/**
* Expands the given input in shorthand notation to a number of strings.
*
* @param input the input shorthand
* @return an iterator of the resultant strings, or optionally null if the input was invalid
* @throws IllegalArgumentException if the input was invalid
*/
abstract Iterator<String> extract(String input) throws IllegalArgumentException;
/** Character used to open a group */
private static final char OPEN_GROUP = '{';
/** Character used to close a group */
private static final char CLOSE_GROUP = '}';
/** Character used to separate items in a list */
private static final char LIST_SEPARATOR = ',';
/** Character used to indicate a range between two values */
private static final char RANGE_SEPARATOR = '-';
/** The parsers */
private static final ShorthandParser[] PARSERS = values();
/**
* Parses and expands the shorthand format.
*
* @param s the string to expand
* @return the expanded result
*/
public static Set<String> expandShorthand(String s) {
Set<String> results = new HashSet<>();
results.add(s);
Set<String> workSet = new HashSet<>();
while (true) {
Set<String> working = new HashSet<>(results.size());
int beforeSize = results.size();
boolean work = false;
for (String str : results) {
Set<String> ret = captureResults(str);
if (ret != null) {
working.addAll(ret);
Set<String> expanded = matchGroup(str);
if (expanded != null) {
work = true;
workSet.addAll(expanded);
} else {
working.add(str);
workSet.add(str);
}
}
if (working.size() != beforeSize) {
results = working;
if (work) {
// set results := workSet, and init an empty workSet for the next iteration
// (we actually sneakily reuse the existing results HashSet to avoid having to create a new one)
Set<String> temp = results;
results = workSet;
workSet = temp;
workSet.clear();
continue;
}
@ -77,13 +155,13 @@ public final class ShorthandParser {
return results;
}
private static Set<String> captureResults(String s) {
int openingIndex = s.indexOf('{');
private static Set<String> matchGroup(String s) {
int openingIndex = s.indexOf(OPEN_GROUP);
if (openingIndex == -1) {
return null;
}
int closingIndex = s.indexOf('}');
int closingIndex = s.indexOf(CLOSE_GROUP);
if (closingIndex < openingIndex) {
return null;
}
@ -92,91 +170,49 @@ public final class ShorthandParser {
String after = s.substring(closingIndex + 1);
String between = s.substring(openingIndex + 1, closingIndex);
Set<String> results = new HashSet<>(10);
Set<String> results = new HashSet<>();
for (Function<String, Iterable<String>> parser : PARSERS) {
Iterable<String> res = parser.apply(between);
if (res != null) {
for (String r : res) {
results.add(before + r + after);
for (ShorthandParser parser : PARSERS) {
try {
Iterator<String> res = parser.extract(between);
if (res != null) {
while (res.hasNext()) {
results.add(before + res.next() + after);
}
// break after one parser has matched
break;
}
} catch (IllegalArgumentException e) {
// ignore
}
}
return results;
}
private static class ListParser implements Function<String, Iterable<String>> {
private static final Splitter SPLITTER = Splitter.on(',');
/**
* Implements an iterator over a given range of ints.
*/
private abstract static class RangeIterator implements Iterator<String> {
private final int max;
private int next;
RangeIterator(int a, int b) {
this.max = Math.max(a, b);
this.next = Math.min(a, b);
}
protected abstract String toString(int i);
@Override
public Iterable<String> apply(String s) {
if (!s.contains(",")) {
return Collections.singleton(s);
}
return SPLITTER.split(s);
}
}
private static class NumericRangeParser implements Function<String, Iterable<String>> {
private static Integer parseInt(String a) {
try {
return Integer.parseInt(a);
} catch (NumberFormatException e) {
return null;
}
public final boolean hasNext() {
return this.next <= this.max;
}
@Override
public Iterable<String> apply(String s) {
int index = s.indexOf("-");
if (index == -1) {
return null;
}
Integer before = parseInt(s.substring(0, index));
if (before == null) {
return null;
}
Integer after = parseInt(s.substring(index + 1));
if (after == null) {
return null;
}
return IntStream.rangeClosed(before, after).mapToObj(Integer::toString).collect(Collectors.toList());
}
}
private static class CharacterRangeParser implements Function<String, Iterable<String>> {
private static List<String> getCharRange(char a, char b) {
List<String> s = new ArrayList<>();
for (char c = a; c <= b; c++) {
s.add(Character.toString(c));
}
return s;
}
@Override
public Iterable<String> apply(String s) {
int index = s.indexOf("-");
if (index == -1) {
return null;
}
String before = s.substring(0, index);
if (before.length() != 1) {
return null;
}
String after = s.substring(index + 1);
if (after.length() != 1) {
return null;
}
return getCharRange(before.charAt(0), after.charAt(0));
public final String next() {
return toString(this.next++);
}
}