package net.minecraft.command.parser; import java.util.ArrayList; import java.util.List; import java.util.Stack; import net.minecraft.command.ICommandSender; 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.collections.MetaColl; import net.minecraft.command.type.ICachedParse; import net.minecraft.command.type.custom.command.ParserCommands; import net.minecraft.command.type.metadata.MetaID; import net.minecraft.command.type.metadata.MetaProvider; import net.minecraft.event.HoverEvent; import net.minecraft.event.HoverEvent.Action; import net.minecraft.util.ChatComponentText; import net.minecraft.util.ChatComponentTranslation; import net.minecraft.util.IChatComponent; public class DebugParser extends Parser { private final ICommandSender sender; private final boolean reducedOutput; public static final MetaID<IChatComponent> hintID = new MetaID<>(MetaColl.typeHint); public DebugParser(final ICommandSender sender, final String toParse, final int startIndex, final boolean reducedOutput) { super(toParse, startIndex, false); this.sender = sender; this.reducedOutput = reducedOutput; } private final Stack<AbstractTree> messages = new Stack<>(); private AbstractTree newElement = null; private AbstractTree lastMessage; /** * * @return Always <code>null</code> */ @Override public CommandArg<Integer> parseCommand() throws SyntaxErrorException { this.messages.push(this.reducedOutput ? new TopLevelMessageRed() : new TopLevelMessage(this.sender)); try { ParserCommands.parse(this, false); if (this.endReached()) return null; this.SEE("Unexpected ')' "); } catch (final SyntaxErrorException e) { if (this.reducedOutput) if (this.lastMessage != null) this.lastMessage.print("- ", this.sender); throw e; } catch (final Throwable t) { throw this.handleFatalError("Fatal error while parsing command: ", t); } finally { if (!this.reducedOutput) this.cleanStack(0); } return null; } @Override public SyntaxErrorException SEE(final String s, final String postfix, final Throwable cause, final Object... errorObjects) { this.newMessage(this.addLocation(new ChatComponentText(s + "around index " + this.index + postfix))); return SyntaxErrorException.see; } private void newMessage(final IChatComponent message) { final AbstractTree toAdd; if (this.newElement == null) toAdd = new MessageTree(message); else { toAdd = new MessageTreeWC(message, this.newElement); this.newElement = null; } this.lastMessage = toAdd; if (this.messages.peek().skipped == 0) this.messages.peek().add(toAdd); else this.messages.push(toAdd); } @Override public SyntaxErrorException SEE(final String s, final boolean appendIndex, final Throwable cause, final Object... errorObjects) { this.newMessage(this.addLocation(appendIndex ? new ChatComponentText(s + ("around index " + this.index)) : new ChatComponentTranslation(s, errorObjects))); return SyntaxErrorException.see; } @Override public WrongUsageException WUE(final String s, final Object... errorObjects) { this.newMessage(new ChatComponentTranslation(s, errorObjects)); return WrongUsageException.wue; } @Override public <D> boolean pushMetadata(final MetaProvider<D> data, final D parserData) { if (this.newElement != null) { this.messages.push(this.newElement); this.newElement = null; } ++this.messages.peek().skipped; return true; } @Override public void popMetadata(final MetaProvider<?> data) { if (this.messages.peek().skipped > 0) { --this.messages.peek().skipped; return; } final AbstractTree current = this.messages.pop(); if (this.newElement != null) current.add(this.newElement); this.newElement = current; --this.messages.peek().skipped; } private void cleanStack(final int stackSize) { while (this.messages.size() > stackSize) { final AbstractTree current = DebugParser.this.messages.pop(); if (DebugParser.this.newElement != null) current.add(DebugParser.this.newElement); DebugParser.this.newElement = current; } } @Override public <D> void supplyHint(final MetaProvider<D> hint, final D data) { final IChatComponent message = hint.getData(hintID, this, data); if (message == null) return; this.newMessage(this.addLocation(message)); } private IChatComponent addLocation(final IChatComponent message) { message.getChatStyle().setChatHoverEvent(new HoverEvent(Action.SHOW_TEXT, ParsingUtilities.location(this))); return message; } private static abstract class AbstractTree { protected int skipped = 0; protected abstract void add(final AbstractTree next); protected abstract void print(String indent, ICommandSender sender); } private static class MessageTree extends AbstractTree { protected final IChatComponent message; public List<AbstractTree> neighbors; public MessageTree(final IChatComponent message) { this.message = message; this.neighbors = null; } @Override public void add(final AbstractTree next) { if (this.neighbors == null) this.neighbors = new ArrayList<>(); this.neighbors.add(next); } @Override protected void print(final String indent, final ICommandSender sender) { sender.addChatMessage(new ChatComponentText(indent).appendSibling(this.message)); if (this.neighbors == null) return; for (final AbstractTree next : this.neighbors) next.print(indent, sender); } } private static class MessageTreeWC extends MessageTree { private final AbstractTree child; public MessageTreeWC(final IChatComponent message, final AbstractTree newElement) { super(message); this.child = newElement; } @Override public void print(final String indent, final ICommandSender sender) { sender.addChatMessage(new ChatComponentText(indent).appendSibling(this.message)); this.child.print(" " + indent, sender); if (this.neighbors == null) return; for (final AbstractTree next : this.neighbors) next.print(indent, sender); } } private static class TopLevelMessage extends AbstractTree { private final ICommandSender sender; public TopLevelMessage(final ICommandSender sender) { this.sender = sender; } @Override public void add(final AbstractTree next) { next.print("- ", this.sender); } @Override public void print(final String indent, final ICommandSender sender) { } } private static class TopLevelMessageRed extends AbstractTree { @Override protected void add(final AbstractTree next) { } @Override protected void print(final String indent, final ICommandSender sender) { } } @Override protected IVersionManager<?, ?> newVersionManager() { return new 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(DebugParser.this, context); return new ResParserState.Success(DebugParser.this.getIndex(), DebugParser.this.defContext, this, res); } catch (final SyntaxErrorException ex) { return new ResParserState.Error(DebugParser.this.getIndex(), DebugParser.this.defContext, this, ex); } } @Override protected SnapshotState saveSnapshot() { return new SnapshotState( DebugParser.this.index, DebugParser.this.defContext, this, DebugParser.this.messages.size(), DebugParser.this.messages.peek().skipped); } @Override protected void restoreSnapshot(final SnapshotState state) { super.restoreSnapshot(state); DebugParser.this.cleanStack(state.stackSize); DebugParser.this.messages.peek().skipped = state.topSkipped; DebugParser.this.newElement.skipped = 0; } } private static class SnapshotState extends ISnapshotState<ResParserState> { public final int stackSize; public final int topSkipped; public SnapshotState(final int index, final Context defContext, final IVersionManager<ResParserState, ?> versionManager, final int stackSize, final int topSkipped) { super(index, defContext, versionManager); this.stackSize = stackSize; this.topSkipped = topSkipped; } } }