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.contexts = contexts;
this.metadata = metadata; 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(); this.hashCode = calculateHashCode();
} }

View File

@ -26,45 +26,123 @@
package me.lucko.luckperms.common.node.utils; package me.lucko.luckperms.common.node.utils;
import com.google.common.base.Splitter; 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.HashSet;
import java.util.List; import java.util.Iterator;
import java.util.Set; 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()) * Expands "1-4" to ["1", "2", "3", "4"]
.add(new CharacterRangeParser()) */
.add(new NumericRangeParser()) NUMERIC_RANGE {
.build(); @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) { return new RangeIterator(Integer.parseInt(input.substring(0, index)), Integer.parseInt(input.substring(index + 1))) {
Set<String> results = new HashSet<>(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); results.add(s);
Set<String> workSet = new HashSet<>();
while (true) { while (true) {
Set<String> working = new HashSet<>(results.size());
int beforeSize = results.size();
boolean work = false;
for (String str : results) { for (String str : results) {
Set<String> ret = captureResults(str); Set<String> expanded = matchGroup(str);
if (ret != null) { if (expanded != null) {
working.addAll(ret); work = true;
workSet.addAll(expanded);
} else { } else {
working.add(str); workSet.add(str);
} }
} }
if (working.size() != beforeSize) { if (work) {
results = working; // 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; continue;
} }
@ -77,13 +155,13 @@ public final class ShorthandParser {
return results; return results;
} }
private static Set<String> captureResults(String s) { private static Set<String> matchGroup(String s) {
int openingIndex = s.indexOf('{'); int openingIndex = s.indexOf(OPEN_GROUP);
if (openingIndex == -1) { if (openingIndex == -1) {
return null; return null;
} }
int closingIndex = s.indexOf('}'); int closingIndex = s.indexOf(CLOSE_GROUP);
if (closingIndex < openingIndex) { if (closingIndex < openingIndex) {
return null; return null;
} }
@ -92,91 +170,49 @@ public final class ShorthandParser {
String after = s.substring(closingIndex + 1); String after = s.substring(closingIndex + 1);
String between = s.substring(openingIndex + 1, closingIndex); 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) { for (ShorthandParser parser : PARSERS) {
Iterable<String> res = parser.apply(between); try {
Iterator<String> res = parser.extract(between);
if (res != null) { if (res != null) {
for (String r : res) { while (res.hasNext()) {
results.add(before + r + after); results.add(before + res.next() + after);
} }
// break after one parser has matched
break;
}
} catch (IllegalArgumentException e) {
// ignore
} }
} }
return results; 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 @Override
public Iterable<String> apply(String s) { public final boolean hasNext() {
if (!s.contains(",")) { return this.next <= this.max;
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;
}
} }
@Override @Override
public Iterable<String> apply(String s) { public final String next() {
int index = s.indexOf("-"); return toString(this.next++);
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));
} }
} }