package net.minecraft.command.parser;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.regex.Matcher;
import org.apache.commons.collections4.trie.PatriciaTrie;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
import com.google.common.collect.UnmodifiableIterator;
import net.minecraft.command.ParsingUtilities;
import net.minecraft.command.SyntaxErrorException;
import net.minecraft.command.WrongUsageException;
import net.minecraft.command.arg.ArgWrapper;
import net.minecraft.command.arg.CommandArg;
import net.minecraft.command.arg.LabelWrapper;
import net.minecraft.command.arg.Setter;
import net.minecraft.command.arg.Setter.SetterProvider;
import net.minecraft.command.collections.Types;
import net.minecraft.command.completion.ITabCompletion;
import net.minecraft.command.completion.TCDSet;
import net.minecraft.command.completion.TabCompletion;
import net.minecraft.command.parser.CompletionParser.CompletionData;
import net.minecraft.command.type.ICachedParse;
import net.minecraft.command.type.IExParse;
import net.minecraft.command.type.custom.command.ParserCommands;
import net.minecraft.command.type.management.CConvertable;
import net.minecraft.command.type.management.TypeID;
import net.minecraft.command.type.metadata.MetaProvider;
import net.minecraft.entity.Entity;
public class Parser
{
public final String toParse;
public final int len;
protected int index;
private final IVersionManager<?, ?> versionManager;
private final List<Matcher> matchers;
public Context defContext;
public final boolean catchStack;
public boolean suppressEx;
public Parser(final String toParse, final int startIndex, final boolean catchStack)
{
this.defContext = Context.defContext;
this.index = startIndex;
this.toParse = toParse;
this.len = toParse.length();
this.versionManager = this.newVersionManager();
this.matchers = new ArrayList<>(MatcherRegistry.getCount());
this.suppressEx = false;
this.catchStack = catchStack;
}
public Parser(final String toParse, final int startIndex)
{
this(toParse, startIndex, false);
}
public Parser(final String toParse)
{
this(toParse, 0);
}
@Override
public String toString()
{
return this.toParse + '\n' + StringUtils.repeat(' ', this.index) + '^';
}
protected SyntaxErrorException handleFatalError(final String messageStart, final Throwable t)
{
if (t instanceof StackOverflowError)
return this.SEE(messageStart + " Too recursive", false);
return this.SEE(messageStart + t.getMessage() + " ", t);
}
public static CommandArg<Integer> parseCommand(final String toParse, final int startIndex) throws SyntaxErrorException
{
return new Parser(toParse, startIndex, false).parseCommand();
}
public CommandArg<Integer> parseCommand() throws SyntaxErrorException
{
final CommandArg<Integer> ret;
try
{
ret = ParserCommands.parse(this, false);
} catch (final SyntaxErrorException e)
{
throw e;
} catch (final Throwable t)
{
throw this.handleFatalError("Fatal error while parsing command: ", t);
}
if (this.endReached())
return ret;
throw this.SEE("Unexpected ')' ");
}
public static CommandArg<Integer> parseCommand(final String toParse) throws SyntaxErrorException
{
return parseCommand(toParse, 0);
}
public static TCDSet parseCompletion(final CompletionData cData, final int startIndex)
{
final CompletionParser completionParser = new CompletionParser(cData.toMatch.substring(0, cData.cursorIndex), startIndex, cData);
try
{
ParserCommands.parse(completionParser, false);
} catch (final SyntaxErrorException e)
{
completionParser.complete(true, 0);
}
return completionParser.getTCDSet();
}
public static TCDSet parseCompletion(final CompletionData cData)
{
return parseCompletion(cData, 0);
}
public static CommandArg<List<String>> parseStatsTarget(final String toParse) throws SyntaxErrorException
{
final Parser parser = new Parser(toParse, 0);
final CommandArg<List<String>> ret;
try
{
ret = Types.scoreHolderList.parse(parser).arg();
} catch (final SyntaxErrorException e)
{
throw e;
} catch (final Throwable t)
{
throw parser.handleFatalError("Fatal error while parsing UUID-List: ", t);
}
if (parser.endReached())
return ret;
throw parser.SEE("Parsing endend unexpectedly ");
}
public static CommandArg<String> parseScoreHolder(final String toParse) throws SyntaxErrorException
{
final Parser parser = new Parser(toParse, 0);
final CommandArg<String> ret;
try
{
ret = Types.scoreHolder.parse(parser).arg();
} catch (final SyntaxErrorException e)
{
throw e;
} catch (final Throwable t)
{
throw parser.handleFatalError("Fatal error while parsing UUID: ", t);
}
if (parser.endReached())
return ret;
throw parser.SEE("Parsing endend unexpectedly ");
}
public static CommandArg<List<Entity>> parseEntityList(final String toParse) throws SyntaxErrorException
{
final Parser parser = new Parser(toParse, 0);
final CommandArg<List<Entity>> ret;
try
{
ret = Types.entityList.parse(parser).arg();
} catch (final SyntaxErrorException e)
{
throw e;
} catch (final Throwable t)
{
throw parser.handleFatalError("Fatal error while parsing Entity-List: ", t);
}
if (parser.endReached())
return ret;
throw parser.SEE("Parsing endend unexpectedly ");
}
public Matcher getMatcher(final MatcherRegistry m)
{
final int id = m.getId();
if (id < this.matchers.size())
{
Matcher ret = this.matchers.get(id);
if (ret != null)
return ret;
ret = m.matcher(this.toParse);
this.matchers.set(id, ret);
return ret;
}
for (int i = this.matchers.size(); i < id; ++i)
this.matchers.add(null);
final Matcher ret = m.matcher(this.toParse);
this.matchers.add(ret);
return ret;
}
@SuppressWarnings("unused")
public <D> boolean pushMetadata(final MetaProvider<D> data, final D parserData)
{
return false;
}
@SuppressWarnings("unused")
public void popMetadata(final MetaProvider<?> data)
{
}
/**
* Calls {@link #supplyHint(Hint, D)} with <code>null</code> as second argument
*/
public void supplyHint(final MetaProvider<?> hint)
{
this.supplyHint(hint, null);
}
@SuppressWarnings("unused")
public <D> void supplyHint(final MetaProvider<D> hint, final D data)
{
}
public SyntaxErrorException SEE(final Object... errorObjects)
{
return this.SEE("commands.generic.syntax", false, null, errorObjects);
}
public SyntaxErrorException SEE(final String s, final Object... errorObjects)
{
return this.SEE(s, true, null, errorObjects);
}
public SyntaxErrorException SEE(final String s, final Throwable cause, final Object... errorObjects)
{
return this.SEE(s, true, cause, errorObjects);
}
public SyntaxErrorException SEE(final String s, final boolean appendIndex, final Object... errorObjects)
{
return this.SEE(s, appendIndex, null, errorObjects);
}
public SyntaxErrorException SEE(final String s, final boolean appendIndex)
{
return this.SEE(s, appendIndex, null, new Object[0]);
}
public SyntaxErrorException SEE(final String s, final String postfix, final Object... errorObjects)
{
return this.SEE(s, postfix, null, errorObjects);
}
public SyntaxErrorException SEE(final String s, final String postfix)
{
return this.SEE(s, postfix, null, new Object[0]);
}
public SyntaxErrorException SEE(final String s, final String postfix, final Throwable cause, final Object... errorObjects)
{
final int start = this.index > 3 ? this.index - 4 : 0;
final int end = this.index < this.toParse.length() - 4 ? this.index + 3 : this.toParse.length();
return this.createSEE(
s
+ "around index "
+ this.index
+ postfix
+ (start > 0 ? " (�" : " (")
+ this.toParse.substring(start, this.index)
+ "|"
+ this.toParse.substring(this.index, end)
+ (end < this.toParse.length() ? "�)" : ")"),
cause,
errorObjects);
}
public SyntaxErrorException SEE(final String s, final boolean appendIndex, final Throwable cause, final Object... errorObjects)
{
if (!appendIndex)
return this.createSEE(s, cause, errorObjects);
return this.SEE(s, "", cause, errorObjects);
}
private SyntaxErrorException createSEE(final String s, final Throwable cause, final Object... errorObjects)
{
if (this.suppressEx)
return SyntaxErrorException.see;
return new SyntaxErrorException(s, cause, true, this.catchStack, errorObjects);
}
public WrongUsageException WUE(final String message, final Object... errorObjects)
{
if (this.suppressEx)
return WrongUsageException.wue;
return new WrongUsageException(message, true, this.catchStack, errorObjects);
}
public boolean find(final Matcher m)
{
return m.find(this.index);
}
public boolean find(final MatcherRegistry m)
{
return this.find(this.getMatcher(m));
}
public void incIndex()
{
++this.index;
}
public void incIndex(final int amount)
{
this.index += amount;
}
public void incIndex(final Matcher m)
{
this.index += m.group().length();
}
public boolean findInc(final Matcher m)
{
final boolean ret = m.find(this.index);
if (ret)
this.index += m.group().length();
return ret;
}
public boolean findInc(final MatcherRegistry m)
{
return this.findInc(this.getMatcher(m));
}
/**
* Does not check if index valid
*/
public char consumeNextChar()
{
return this.toParse.charAt(this.index++);
}
public int getIndex()
{
return this.index;
}
public boolean endReached()
{
return this.index == this.len;
}
public final boolean checkSpace()
{
return this.find(ParsingUtilities.spaceMatcher);
}
public boolean isSnapshot()
{
return this.versionManager.isSnapshot();
}
public Set<ITabCompletion> getLabelCompletions()
{
final Set<ITabCompletion> completions = new HashSet<>();
for (final String name : this.versionManager.labelKeysIterable())
completions.add(new TabCompletion(name));
return completions;
}
public Set<ITabCompletion> getLabelCompletions(final CConvertable<?, ?> target)
{
final Set<ITabCompletion> completions = new HashSet<>();
for (final Entry<String, LabelWrapper<?>> entry : this.versionManager.labelIterable())
if (target.convertableFrom(entry.getValue().type))
completions.add(new TabCompletion(entry.getKey()));
return completions;
}
public void addLabel(final String label, final LabelWrapper<?> value) throws SyntaxErrorException
{
this.versionManager.addLabel(label, value);
}
public <T> SetterProvider<T> getLabelSetterTyped(final String label, final TypeID<T> type, final boolean allowConversion) throws SyntaxErrorException
{
return this.getLabelSafe(label).getLabelSetterTyped(this, type, allowConversion);
}
public <T> Setter<T> getLabelSetter(final String label, final TypeID<T> type, final boolean allowConversion) throws SyntaxErrorException
{
return this.getLabelSafe(label).getLabelSetter(this, type, allowConversion);
}
public LabelWrapper<?> getLabel(final String label)
{
return this.versionManager.getLabel(label);
}
public LabelWrapper<?> getLabelSafe(final String label) throws SyntaxErrorException
{
final LabelWrapper<?> ret = this.getLabel(label);
if (ret == null)
throw this.SEE("Label '" + label + "' not registered ");
return ret;
}
public <T, D> T parseSnapshot(final IExParse<T, D> target, final D parserData) throws SyntaxErrorException
{
return this.versionManager.parseSnapshot(target, parserData);
}
public ArgWrapper<?> parseCached(final ICachedParse target, final Context context, final CacheID cacheID) throws SyntaxErrorException
{
return this.versionManager.parseCached(target, context, cacheID);
}
protected static abstract class IParserState
{
public final int index;
public final Context defContext;
public IParserState(final int index, final Context defContext)
{
this.index = index;
this.defContext = defContext;
}
@Override
public boolean equals(final Object other)
{
if (!(other instanceof ParserState))
return false;
final ParserState state = (ParserState) other;
return this.index == state.index && this.defContext.equals(state.defContext);
}
@Override
public int hashCode()
{
return this.index ^ this.defContext.hashCode();
}
}
protected static class ParserState extends IParserState
{
public final Context context;
public final CacheID id;
public ParserState(final int index, final Context defContext, final Context context, final CacheID id)
{
super(index, defContext);
this.context = context;
this.id = id;
}
@Override
public boolean equals(final Object other)
{
if (!(other instanceof ParserState))
return false;
final ParserState state = (ParserState) other;
return super.equals(state) && this.context.equals(state.context) && this.id == state.id;
}
@Override
public int hashCode()
{
return super.hashCode() ^ this.context.hashCode() ^ this.id.hashCode();
}
}
protected static abstract class IResParserState<S extends IResParserState<S>> extends IParserState
{
public abstract ArgWrapper<?> res() throws SyntaxErrorException;
public final Version<S> version;
public IResParserState(final int index, final Context defContext, final IVersionManager<S, ?> versionManager)
{
super(index, defContext);
this.version = versionManager.useVersion();
}
}
protected static abstract class ISnapshotState<R extends IResParserState<R>> extends IParserState
{
public final Version<R> version;
public ISnapshotState(final int index, final Context defContext, final IVersionManager<R, ?> versionManager)
{
super(index, defContext);
this.version = versionManager.useVersion();
}
}
protected IVersionManager<?, ?> newVersionManager()
{
return new VersionManager();
}
protected static final class Version<R extends IResParserState<R>>
{
protected int versionNumber;
public Version(final int versionNumber)
{
this.versionNumber = versionNumber;
}
Map<ParserState, R> cachedResults = null;
protected Version<R> next = null;
protected R getCachedResult(final ParserState state)
{
return this.cachedResults == null ? null : this.cachedResults.get(state);
}
protected void addCachedResult(final ParserState initialState, final R result)
{
if (this.cachedResults == null)
this.cachedResults = new HashMap<>();
this.cachedResults.put(initialState, result);
}
protected boolean invalidateTail()
{
Version<R> curr = this.next;
if (curr == null)
return false;
do
{
curr.versionNumber = Integer.MAX_VALUE;
curr.cachedResults = null;
} while ((curr = curr.next) != null);
this.next = null;
return true;
}
protected boolean valid()
{
return this.versionNumber != Integer.MAX_VALUE;
}
protected boolean valid(final int versionNumber)
{
return this.versionNumber <= versionNumber;
}
}
public abstract class IVersionManager<R extends IResParserState<R>, S extends ISnapshotState<R>>
{
private int versionNumber;
protected Version<R> version;
protected boolean changed = true;
protected int snapshotCount = 0;
private final PatriciaTrie<Pair<Version<R>, LabelWrapper<?>>> labels;
public IVersionManager()
{
this.versionNumber = 0;
this.version = new Version<>(0);
this.labels = new PatriciaTrie<>();
}
public Version<R> useVersion()
{
this.changed = false;
return this.version;
}
public void changed()
{
if (this.changed)
return;
this.changed = true;
this.version.invalidateTail();
this.version = (this.version.next = new Version<>(++this.versionNumber));
}
protected ParserState getInitState(final Context context, final CacheID id)
{
return new ParserState(Parser.this.index, Parser.this.defContext, context, id);
}
public void setState(final R state)
{
this.changed = false;
this.version = state.version;
this.versionNumber = this.version.versionNumber;
Parser.this.index = state.index;
Parser.this.defContext = state.defContext;
}
public ArgWrapper<?> parseCached(final ICachedParse target, final Context context, final CacheID cacheID) throws SyntaxErrorException
{
if (this.changed || !this.isSnapshot())
return target.iCachedParse(Parser.this, context);
final Version<R> initVersion = this.version;
final ParserState initialState = this.getInitState(context, cacheID);
if (initialState == null)
return target.iCachedParse(Parser.this, context);
R res = initVersion.getCachedResult(initialState);
if (res != null && res.version.valid())
{
this.setState(res);
return res.res();
}
res = this.parseFetchState(target, context);
initVersion.addCachedResult(initialState, res);
return res.res();
}
public boolean isSnapshot()
{
return this.snapshotCount > 0;
}
/**
* Exceptions can be caught, as long as they are rethrown by the {@link ResParserState#res()} method of the return value
*/
protected abstract R parseFetchState(final ICachedParse target, final Context context) throws SyntaxErrorException;
protected abstract S saveSnapshot();
protected void restoreSnapshot(final S state)
{
Parser.this.index = state.index;
Parser.this.defContext = state.defContext;
this.restoreSnapshot(state.version);
}
@SuppressWarnings("unused")
protected void passSnapshot(final S state)
{
}
@SuppressWarnings("unused")
protected void finalizeSnapshot(final S state)
{
}
protected void restoreSnapshot(final Version<R> version)
{
this.version = version;
this.versionNumber = version.versionNumber;
this.changed = false;
}
public <T, D> T parseSnapshot(final IExParse<T, D> target, final D parserData) throws SyntaxErrorException
{
final S snapshot = this.saveSnapshot();
++this.snapshotCount;
try
{
final T ret = target.parse(Parser.this, parserData);
this.passSnapshot(snapshot);
return ret;
} catch (final SyntaxErrorException e)
{
this.restoreSnapshot(snapshot);
throw e;
} finally
{
this.finalizeSnapshot(snapshot);
--this.snapshotCount;
}
}
private boolean putLabel(final String label, final LabelWrapper<?> value)
{
final Pair<Version<R>, LabelWrapper<?>> arg = this.labels.get(label);
if (arg != null && arg.getLeft().valid(this.versionNumber))
return false;
this.labels.put(label, new ImmutablePair<Version<R>, LabelWrapper<?>>(this.version, value));
return true;
}
public void addLabel(final String label, final LabelWrapper<?> value) throws SyntaxErrorException
{
if (!this.putLabel(label, value))
throw Parser.this.SEE("Label '" + label + "' already in use (", ")");
}
public LabelWrapper<?> getLabel(final String label)
{
final Pair<Version<R>, LabelWrapper<?>> ret = this.labels.get(label);
if (ret == null || !ret.getLeft().valid(this.versionNumber))
return null;
return ret.getRight();
}
public Iterable<Entry<String, LabelWrapper<?>>> labelIterable()
{
return new Iterable<Map.Entry<String, LabelWrapper<?>>>()
{
@Override
public Iterator<Entry<String, LabelWrapper<?>>> iterator()
{
return IVersionManager.this.labelIterator();
}
};
}
public Iterable<String> labelKeysIterable()
{
return new Iterable<String>()
{
@Override
public Iterator<String> iterator()
{
return IVersionManager.this.labelKeysIterator();
}
};
}
public UnmodifiableIterator<Entry<String, LabelWrapper<?>>> labelIterator()
{
final Iterator<Entry<String, Pair<Version<R>, LabelWrapper<?>>>> it = this.labelEntryIterator();
return new UnmodifiableIterator<Map.Entry<String, LabelWrapper<?>>>()
{
@Override
public boolean hasNext()
{
return it.hasNext();
}
@Override
public Entry<String, LabelWrapper<?>> next()
{
final Entry<String, Pair<Version<R>, LabelWrapper<?>>> value = it.next();
return new ImmutablePair<String, LabelWrapper<?>>(value.getKey(), value.getValue().getRight());
}
};
}
public UnmodifiableIterator<String> labelKeysIterator()
{
final Iterator<Entry<String, Pair<Version<R>, LabelWrapper<?>>>> it = this.labelEntryIterator();
return new UnmodifiableIterator<String>()
{
@Override
public boolean hasNext()
{
return it.hasNext();
}
@Override
public String next()
{
final Entry<String, Pair<Version<R>, LabelWrapper<?>>> value = it.next();
return value.getKey();
}
};
}
private UnmodifiableIterator<Entry<String, Pair<Version<R>, LabelWrapper<?>>>> labelEntryIterator()
{
final Iterator<Entry<String, Pair<Version<R>, LabelWrapper<?>>>> it = this.labels.entrySet().iterator();
final int versionNumber = this.versionNumber;
return new UnmodifiableIterator<Entry<String, Pair<Version<R>, LabelWrapper<?>>>>()
{
private Entry<String, Pair<Version<R>, LabelWrapper<?>>> next = this.getNext();
private Entry<String, Pair<Version<R>, LabelWrapper<?>>> getNext()
{
while (it.hasNext())
{
final Entry<String, Pair<Version<R>, LabelWrapper<?>>> entry = it.next();
if (entry.getValue().getLeft().valid(versionNumber))
return entry;
}
return null;
}
@Override
public boolean hasNext()
{
return this.next != null;
}
@Override
public Entry<String, Pair<Version<R>, LabelWrapper<?>>> next()
{
final Entry<String, Pair<Version<R>, LabelWrapper<?>>> ret = this.next;
this.next = this.getNext();
return ret;
}
};
}
}
protected static abstract class ResParserState extends IResParserState<ResParserState>
{
public ResParserState(final int index, final Context defContext, final IVersionManager<ResParserState, ?> versionManager)
{
super(index, defContext, versionManager);
}
protected static class Success extends ResParserState
{
private final ArgWrapper<?> res;
public Success(final int index, final Context defContext, final IVersionManager<ResParserState, ?> versionManager, final ArgWrapper<?> res)
{
super(index, defContext, versionManager);
this.res = res;
}
@Override
public ArgWrapper<?> res() throws SyntaxErrorException
{
return this.res;
}
}
protected static class Error extends ResParserState
{
private final SyntaxErrorException ex;
public Error(final int index, final Context defContext, final IVersionManager<ResParserState, ?> versionManager, final SyntaxErrorException ex)
{
super(index, defContext, versionManager);
this.ex = ex;
}
@Override
public ArgWrapper<?> res() throws SyntaxErrorException
{
throw this.ex;
}
}
}
protected static class SnapshotState extends ISnapshotState<ResParserState>
{
public SnapshotState(final int index, final Context defContext, final IVersionManager<ResParserState, ?> versionManager)
{
super(index, defContext, versionManager);
}
}
private class VersionManager extends IVersionManager<ResParserState, SnapshotState>
{
@Override
protected ResParserState parseFetchState(final ICachedParse target, final Context context) throws SyntaxErrorException
{
try
{
final ArgWrapper<?> res = target.iCachedParse(Parser.this, context);
return new ResParserState.Success(Parser.this.getIndex(), Parser.this.defContext, this, res);
} catch (final SyntaxErrorException ex)
{
return new ResParserState.Error(Parser.this.getIndex(), Parser.this.defContext, this, ex);
}
}
@Override
protected SnapshotState saveSnapshot()
{
return new SnapshotState(Parser.this.getIndex(), Parser.this.defContext, this);
}
}
}