Java tutorial
/* * Minecraft Programming Language (MPL): A language for easy development of command block * applications including an IDE. * * Copyright (C) 2016 Adrodoc55 * * This file is part of MPL. * * MPL is free software: you can redistribute it and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * MPL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General * Public License for more details. * * You should have received a copy of the GNU General Public License along with MPL. If not, see * <http://www.gnu.org/licenses/>. * * * * Minecraft Programming Language (MPL): Eine Sprache fr die einfache Entwicklung von Commandoblock * Anwendungen, inklusive einer IDE. * * Copyright (C) 2016 Adrodoc55 * * Diese Datei ist Teil von MPL. * * MPL ist freie Software: Sie knnen diese unter den Bedingungen der GNU General Public License, * wie von der Free Software Foundation, Version 3 der Lizenz oder (nach Ihrer Wahl) jeder spteren * verffentlichten Version, weiterverbreiten und/oder modifizieren. * * MPL wird in der Hoffnung, dass es ntzlich sein wird, aber OHNE JEDE GEWHRLEISTUNG, * bereitgestellt; sogar ohne die implizite Gewhrleistung der MARKTFHIGKEIT oder EIGNUNG FR EINEN * BESTIMMTEN ZWECK. Siehe die GNU General Public License fr weitere Details. * * Sie sollten eine Kopie der GNU General Public License zusammen mit MPL erhalten haben. Wenn * nicht, siehe <http://www.gnu.org/licenses/>. */ package de.adrodoc55.minecraft.mpl.interpretation; import static de.adrodoc55.minecraft.mpl.ast.chainparts.MplNotify.NOTIFY; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collection; import java.util.Deque; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; import javax.annotation.Nullable; import org.antlr.v4.runtime.ANTLRInputStream; import org.antlr.v4.runtime.BaseErrorListener; import org.antlr.v4.runtime.CommonTokenStream; import org.antlr.v4.runtime.RecognitionException; import org.antlr.v4.runtime.Recognizer; import org.antlr.v4.runtime.Token; import org.antlr.v4.runtime.TokenStream; import org.antlr.v4.runtime.tree.ParseTreeWalker; import org.antlr.v4.runtime.tree.TerminalNode; import com.google.common.collect.LinkedListMultimap; import com.google.common.collect.ListMultimap; import de.adrodoc55.commons.FileUtils; import de.adrodoc55.minecraft.coordinate.Orientation3D; import de.adrodoc55.minecraft.mpl.antlr.MplBaseListener; import de.adrodoc55.minecraft.mpl.antlr.MplLexer; import de.adrodoc55.minecraft.mpl.antlr.MplParser; import de.adrodoc55.minecraft.mpl.antlr.MplParser.AutoContext; import de.adrodoc55.minecraft.mpl.antlr.MplParser.BreakDeclarationContext; import de.adrodoc55.minecraft.mpl.antlr.MplParser.BreakpointContext; import de.adrodoc55.minecraft.mpl.antlr.MplParser.CommandContext; import de.adrodoc55.minecraft.mpl.antlr.MplParser.ConditionalContext; import de.adrodoc55.minecraft.mpl.antlr.MplParser.ContinueDeclarationContext; import de.adrodoc55.minecraft.mpl.antlr.MplParser.ElseDeclarationContext; import de.adrodoc55.minecraft.mpl.antlr.MplParser.FileContext; import de.adrodoc55.minecraft.mpl.antlr.MplParser.IfDeclarationContext; import de.adrodoc55.minecraft.mpl.antlr.MplParser.ImportDeclarationContext; import de.adrodoc55.minecraft.mpl.antlr.MplParser.IncludeContext; import de.adrodoc55.minecraft.mpl.antlr.MplParser.InstallContext; import de.adrodoc55.minecraft.mpl.antlr.MplParser.InterceptContext; import de.adrodoc55.minecraft.mpl.antlr.MplParser.ModusContext; import de.adrodoc55.minecraft.mpl.antlr.MplParser.MplCommandContext; import de.adrodoc55.minecraft.mpl.antlr.MplParser.NotifyDeclarationContext; import de.adrodoc55.minecraft.mpl.antlr.MplParser.OrientationContext; import de.adrodoc55.minecraft.mpl.antlr.MplParser.ProcessContext; import de.adrodoc55.minecraft.mpl.antlr.MplParser.ProjectContext; import de.adrodoc55.minecraft.mpl.antlr.MplParser.ScriptFileContext; import de.adrodoc55.minecraft.mpl.antlr.MplParser.SkipDeclarationContext; import de.adrodoc55.minecraft.mpl.antlr.MplParser.StartContext; import de.adrodoc55.minecraft.mpl.antlr.MplParser.StopContext; import de.adrodoc55.minecraft.mpl.antlr.MplParser.ThenContext; import de.adrodoc55.minecraft.mpl.antlr.MplParser.UninstallContext; import de.adrodoc55.minecraft.mpl.antlr.MplParser.WaitforContext; import de.adrodoc55.minecraft.mpl.antlr.MplParser.WhileDeclarationContext; import de.adrodoc55.minecraft.mpl.ast.chainparts.ChainPart; import de.adrodoc55.minecraft.mpl.ast.chainparts.ModifiableChainPart; import de.adrodoc55.minecraft.mpl.ast.chainparts.MplBreakpoint; import de.adrodoc55.minecraft.mpl.ast.chainparts.MplCommand; import de.adrodoc55.minecraft.mpl.ast.chainparts.MplIf; import de.adrodoc55.minecraft.mpl.ast.chainparts.MplIntercept; import de.adrodoc55.minecraft.mpl.ast.chainparts.MplNotify; import de.adrodoc55.minecraft.mpl.ast.chainparts.MplStart; import de.adrodoc55.minecraft.mpl.ast.chainparts.MplStop; import de.adrodoc55.minecraft.mpl.ast.chainparts.MplWaitfor; import de.adrodoc55.minecraft.mpl.ast.chainparts.loop.MplBreak; import de.adrodoc55.minecraft.mpl.ast.chainparts.loop.MplContinue; import de.adrodoc55.minecraft.mpl.ast.chainparts.loop.MplWhile; import de.adrodoc55.minecraft.mpl.ast.chainparts.program.MplProcess; import de.adrodoc55.minecraft.mpl.ast.chainparts.program.MplProgram; import de.adrodoc55.minecraft.mpl.commands.Conditional; import de.adrodoc55.minecraft.mpl.commands.Mode; import de.adrodoc55.minecraft.mpl.commands.chainlinks.MplSkip; import de.adrodoc55.minecraft.mpl.compilation.CompilerException; import de.adrodoc55.minecraft.mpl.compilation.MplSource; import de.adrodoc55.minecraft.mpl.interpretation.ChainPartBuffer.ChainPartBufferImpl; /** * @author Adrodoc55 */ public class MplInterpreter extends MplBaseListener { public static MplInterpreter interpret(File programFile) throws IOException { MplInterpreter interpreter = new MplInterpreter(programFile); FileContext ctx = interpreter.parse(programFile); if (interpreter.getProgram().getExceptions().isEmpty()) { new ParseTreeWalker().walk(interpreter, ctx); } return interpreter; } private FileContext parse(File programFile) throws IOException { byte[] bytes = Files.readAllBytes(programFile.toPath()); ANTLRInputStream input = new ANTLRInputStream(FileUtils.toUnixLineEnding(new String(bytes))); MplLexer lexer = new MplLexer(input); TokenStream tokens = new CommonTokenStream(lexer); MplParser parser = new MplParser(tokens); parser.removeErrorListeners(); parser.addErrorListener(new BaseErrorListener() { @Override public void syntaxError(Recognizer<?, ?> recognizer, Object token, int line, int charPositionInLine, String message, RecognitionException cause) { MplSource source = toSource((Token) token); addException(new CompilerException(source, message)); } }); FileContext context = parser.file(); // Trees.inspect(context, parser); return context; } private final File programFile; private final List<String> lines; private final ListMultimap<String, Include> includes = LinkedListMultimap.create(); private final Set<File> imports = new HashSet<>(); private MplInterpreter(File programFile) throws IOException { this.programFile = programFile; lines = Files.readAllLines(programFile.toPath()); // FIXME was sinnvolleres als null reinstecken addFileImport(null, programFile.getParentFile()); } public File getProgramFile() { return programFile; } private final MplProgram program = new MplProgram(); public MplProgram getProgram() { return program; } private void addException(CompilerException ex1) { program.getExceptions().add(ex1); } /** * Returns the mapping of process names to includes required by that process. A key of null * indicates that this include is not required by a specific process, but by an explicit include * of a project. * * @return the mapping of process names */ public ListMultimap<String, Include> getIncludes() { return includes; } // ---------------------------------------------------------------------------------------------------- public MplSource toSource(@Nullable Token token) { String line = token != null ? lines.get(token.getLine() - 1) : null; return new MplSource(programFile, token, line); } @Override public void exitFile(FileContext ctx) { if (program.getOrientation() == null) { program.setOrientation(new Orientation3D()); } } @Override public void enterImportDeclaration(ImportDeclarationContext ctx) { Token token = ctx.STRING().getSymbol(); String importPath = MplLexerUtils.getContainedString(token); File file = new File(programFile.getParentFile(), importPath); addFileImport(ctx, file); } @Override public void enterProject(ProjectContext ctx) { Token oldToken = program.getToken(); Token newToken = ctx.PROJECT().getSymbol(); if (oldToken != null) { String message = "A file can only contain a single project"; MplSource oldSource = toSource(oldToken); addException(new CompilerException(oldSource, message)); MplSource newSource = toSource(newToken); addException(new CompilerException(newSource, message)); return; } String name = ctx.IDENTIFIER().getText(); program.setName(name); program.setToken(newToken); } @Override public void enterOrientation(OrientationContext ctx) { String def = MplLexerUtils.getContainedString(ctx.STRING().getSymbol()); Token newToken = ctx.ORIENTATION().getSymbol(); Orientation3D oldOrientation = program.getOrientation(); if (oldOrientation != null) { String type = program.isScript() ? "script" : "project"; String message = "A " + type + " can only have a single orientation"; Token oldToken = oldOrientation.getToken(); MplSource oldSource = toSource(oldToken); addException(new CompilerException(oldSource, message)); MplSource newSource = toSource(newToken); addException(new CompilerException(newSource, message)); return; } Orientation3D newOrientation = new Orientation3D(def, newToken); program.setOrientation(newOrientation); } @Override public void enterInclude(IncludeContext ctx) { String includePath = MplLexerUtils.getContainedString(ctx.STRING().getSymbol()); Token token = ctx.STRING().getSymbol(); File file = new File(programFile.getParentFile(), includePath); List<File> files = new ArrayList<File>(); if (!addFile(files, file, token)) { return; } MplSource source = toSource(token); Include include = new Include(source, files); if (includes.get(null).contains(include)) { addException(new CompilerException(source, "Duplicate include")); return; } includes.put(null, include); } /** * Adds the file to the list of imports that will be used to search processes. If the file is a * directory all direct subfiles will be added, this is not recursive. * * @param ctx the token context that this import originated from * @param file the file to import */ private void addFileImport(ImportDeclarationContext ctx, File file) { Token token = ctx != null ? ctx.STRING().getSymbol() : null; if (imports.contains(file)) { MplSource source = toSource(token); addException(new CompilerException(source, "Duplicate import")); return; } addFile(imports, file, token); } /** * Adds the File to the specified Collection. If the File is a Directory all relevant children are * added instead. If any Problem occurs, an Exception will be added to the exceptions-List. * * @param files the Collection to add to * @param file the File * @param token the Token to display in potential Exceptions * @return true if something was added to the Collection, false otherwise */ private boolean addFile(Collection<File> files, File file, @Nullable Token token) { if (file.isFile()) { files.add(file); return true; } else if (file.isDirectory()) { boolean added = false; for (File f : file.listFiles()) { if (f.isFile() && (f.equals(programFile) || f.getName().endsWith(".mpl"))) { files.add(f); added = true; } } return added; } else if (!file.exists()) { String path = FileUtils.getCanonicalPath(file); MplSource source = toSource(token); addException(new CompilerException(source, "Could not find '" + path + "'")); return false; } else { MplSource source = toSource(token); addException( new CompilerException(source, "Can only import Files and Directories, not: '" + file + "'")); return false; } } private final Deque<ChainPartBuffer> chainBufferStack = new LinkedList<>(); private ChainPartBuffer chainBuffer; // private IfBuffer ifBuffer; private void newChainBuffer() { chainBufferStack.push(chainBuffer); chainBuffer = new ChainPartBufferImpl(); // this.ifBuffer = new IfBuffer(chainBuffer); } private void popChainBuffer() { // ifBuffer is not recovered, because it is currently not required chainBuffer = chainBufferStack.poll(); // ifBuffer = null; } private MplProcess process; @Override public void enterInstall(InstallContext ctx) { newChainBuffer(); } @Override public void exitInstall(InstallContext ctx) { MplProcess install = program.getInstall(); if (install == null) { install = new MplProcess("install"); program.setInstall(install); } install.addAll(chainBuffer.getChainParts()); popChainBuffer(); } @Override public void enterUninstall(UninstallContext ctx) { newChainBuffer(); } @Override public void exitUninstall(UninstallContext ctx) { MplProcess install = program.getUninstall(); if (install == null) { install = new MplProcess("uninstall"); program.setUninstall(install); } install.addAll(chainBuffer.getChainParts()); popChainBuffer(); } @Override public void enterScriptFile(ScriptFileContext ctx) { program.setScript(true); newChainBuffer(); } @Override public void exitScriptFile(ScriptFileContext ctx) { process = new MplProcess(); Deque<ChainPart> chainParts = chainBuffer.getChainParts(); process.setChainParts(chainParts); program.addProcess(process); process = null; popChainBuffer(); } @Override public void enterProcess(ProcessContext ctx) { String name = ctx.IDENTIFIER().getText(); boolean repeat = ctx.REPEAT() != null; MplSource source = toSource(ctx.IDENTIFIER().getSymbol()); process = new MplProcess(name, repeat, source); newChainBuffer(); } @Override public void exitProcess(ProcessContext ctx) { Deque<ChainPart> chainParts = chainBuffer.getChainParts(); process.setChainParts(chainParts); program.addProcess(process); process = null; popChainBuffer(); } private ModifierBuffer modifierBuffer; @Override public void enterMplCommand(MplCommandContext ctx) { modifierBuffer = new ModifierBuffer(); } @Override public void exitMplCommand(MplCommandContext ctx) { modifierBuffer = null; } @Override public void enterModus(ModusContext ctx) { if (ctx.IMPULSE() != null) { modifierBuffer.setModeToken(ctx.IMPULSE().getSymbol()); modifierBuffer.setMode(Mode.IMPULSE); return; } if (ctx.CHAIN() != null) { modifierBuffer.setModeToken(ctx.CHAIN().getSymbol()); modifierBuffer.setMode(Mode.CHAIN); return; } if (ctx.REPEAT() != null) { modifierBuffer.setModeToken(ctx.REPEAT().getSymbol()); modifierBuffer.setMode(Mode.REPEAT); return; } } @Override public void enterConditional(ConditionalContext ctx) { if (ctx.UNCONDITIONAL() != null) { modifierBuffer.setConditionalToken(ctx.UNCONDITIONAL().getSymbol()); modifierBuffer.setConditional(Conditional.UNCONDITIONAL); return; } if (ctx.CONDITIONAL() != null) { modifierBuffer.setConditionalToken(ctx.CONDITIONAL().getSymbol()); modifierBuffer.setConditional(Conditional.CONDITIONAL); return; } if (ctx.INVERT() != null) { modifierBuffer.setConditionalToken(ctx.INVERT().getSymbol()); modifierBuffer.setConditional(Conditional.INVERT); return; } } @Override public void enterAuto(AutoContext ctx) { if (ctx.ALWAYS_ACTIVE() != null) { modifierBuffer.setNeedsRedstoneToken(ctx.ALWAYS_ACTIVE().getSymbol()); modifierBuffer.setNeedsRedstone(false); return; } if (ctx.NEEDS_REDSTONE() != null) { modifierBuffer.setNeedsRedstoneToken(ctx.NEEDS_REDSTONE().getSymbol()); modifierBuffer.setNeedsRedstone(true); return; } } /** * Check that the given {@link Token} is null. If it is not null add a {@link CompilerException}. * * @param part - name of the {@link ChainPart} that may not have this modifier * @param token to check */ private void checkNoModifier(String part, Token token) { if (token == null) { return; } MplSource source = toSource(token); addException(new CompilerException(source, "Illegal modifier for " + part + "; only unconditional, conditional and invert are permitted")); } private void addModifiableChainPart(ModifiableChainPart chainPart) { Conditional conditional = chainPart.getConditional(); if (conditional == Conditional.UNCONDITIONAL) { chainBuffer.add(chainPart); return; } ChainPart prev = chainBuffer.getChainParts().peekLast(); if (prev == null) { Token token = modifierBuffer.getConditionalToken(); MplSource source = toSource(token); addException(new CompilerException(source, "The first part of a chain must be unconditional")); return; } if (prev.canBeDependedOn()) { chainPart.setPrevious(prev); chainBuffer.add(chainPart); } else { Token token = modifierBuffer.getConditionalToken(); MplSource source = toSource(token); addException(new CompilerException(source, conditional.name().toLowerCase() + " cannot depend on " + prev.getName())); } } @Override public void enterCommand(CommandContext ctx) { String commandString = ctx.COMMAND().getText(); MplCommand command = new MplCommand(commandString, modifierBuffer); addModifiableChainPart(command); } private String lastStartIdentifier; @Override public void enterStart(StartContext ctx) { TerminalNode identifier = ctx.IDENTIFIER(); String process = identifier.getText(); MplStart start = new MplStart(process, modifierBuffer); addModifiableChainPart(start); checkNoModifier(start.getName(), modifierBuffer.getModeToken()); checkNoModifier(start.getName(), modifierBuffer.getNeedsRedstoneToken()); lastStartIdentifier = process; if (program.isScript()) { return; } String srcProcess = this.process != null ? this.process.getName() : null; Token token = identifier.getSymbol(); MplSource source = toSource(token); Include include = new Include(source, process, imports); includes.put(srcProcess, include); } @Override public void enterStop(StopContext ctx) { Token token = ctx.STOP().getSymbol(); MplSource source = toSource(token); String process; if (ctx.IDENTIFIER() != null) { process = ctx.IDENTIFIER().getText(); } else if (this.process != null) { if (this.process.isRepeating()) { process = this.process.getName(); } else { addException(new CompilerException(source, "An impulse process cannot be stopped")); return; } } else { addException(new CompilerException(source, "Missing identifier")); return; } MplStop stop = new MplStop(process, modifierBuffer); addModifiableChainPart(stop); checkNoModifier(stop.getName(), modifierBuffer.getModeToken()); checkNoModifier(stop.getName(), modifierBuffer.getNeedsRedstoneToken()); } @Override public void enterWaitfor(WaitforContext ctx) { TerminalNode identifier = ctx.IDENTIFIER(); String event; if (identifier != null) { event = identifier.getText(); if (ctx.NOTIFY() != null) { event += NOTIFY; } } else if (lastStartIdentifier != null) { event = lastStartIdentifier += NOTIFY; lastStartIdentifier = null; } else { Token token = ctx.WAITFOR().getSymbol(); MplSource source = toSource(token); addException( new CompilerException(source, "Missing identifier; no previous start was found to wait for")); return; } MplWaitfor waitfor = new MplWaitfor(event, modifierBuffer); addModifiableChainPart(waitfor); checkNoModifier(waitfor.getName(), modifierBuffer.getModeToken()); checkNoModifier(waitfor.getName(), modifierBuffer.getNeedsRedstoneToken()); } @Override public void enterNotifyDeclaration(NotifyDeclarationContext ctx) { if (this.process == null) { Token token = ctx.NOTIFY().getSymbol(); MplSource source = toSource(token); addException(new CompilerException(source, "notify can only be used in a process")); return; } String process = this.process.getName(); MplNotify notify = new MplNotify(process, modifierBuffer); addModifiableChainPart(notify); checkNoModifier(notify.getName(), modifierBuffer.getModeToken()); checkNoModifier(notify.getName(), modifierBuffer.getNeedsRedstoneToken()); } @Override public void enterIntercept(InterceptContext ctx) { TerminalNode identifier = ctx.IDENTIFIER(); String process = identifier.getText(); MplIntercept intercept = new MplIntercept(process, modifierBuffer); addModifiableChainPart(intercept); checkNoModifier(intercept.getName(), modifierBuffer.getModeToken()); checkNoModifier(intercept.getName(), modifierBuffer.getNeedsRedstoneToken()); } @Override public void enterBreakpoint(BreakpointContext ctx) { int line = ctx.BREAKPOINT().getSymbol().getLine(); String source = programFile.getName() + " : line " + line; MplBreakpoint breakpoint = new MplBreakpoint(source, modifierBuffer); addModifiableChainPart(breakpoint); checkNoModifier(breakpoint.getName(), modifierBuffer.getModeToken()); checkNoModifier(breakpoint.getName(), modifierBuffer.getNeedsRedstoneToken()); // getProject().setHasBreakpoint(true); } @Override public void enterSkipDeclaration(SkipDeclarationContext ctx) { if (process != null && process.isRepeating() && chainBuffer.getChainParts().isEmpty()) { MplSource source = toSource(ctx.SKIP_TOKEN().getSymbol()); addException(new CompilerException(source, "skip cannot be the first command of a repeating process")); return; } chainBuffer.add(new MplSkip()); } @Override public void enterIfDeclaration(IfDeclarationContext ctx) { boolean not = ctx.NOT() != null; String condition = ctx.COMMAND().getText(); chainBuffer = new MplIf(chainBuffer, not, condition); } @Override public void enterThen(ThenContext ctx) { ((MplIf) chainBuffer).enterThen(); } @Override public void enterElseDeclaration(ElseDeclarationContext ctx) { ((MplIf) chainBuffer).enterElse(); } @Override public void exitIfDeclaration(IfDeclarationContext ctx) { MplIf mplIf = (MplIf) chainBuffer; chainBuffer = mplIf.exit(); chainBuffer.add(mplIf); } private Deque<MplWhile> loops = new ArrayDeque<>(); @Override public void enterWhileDeclaration(WhileDeclarationContext ctx) { TerminalNode identifier = ctx.IDENTIFIER(); String label = identifier != null ? identifier.getText() : null; boolean not = ctx.NOT() != null; boolean trailing = ctx.DO() != null; TerminalNode command = ctx.COMMAND(); String condition = command != null ? command.getText() : null; MplWhile mplWhile = new MplWhile(chainBuffer, label, not, trailing, condition); loops.push(mplWhile); chainBuffer = mplWhile; } @Override public void exitWhileDeclaration(WhileDeclarationContext ctx) { loops.pop(); MplWhile mplWhile = (MplWhile) chainBuffer; chainBuffer = mplWhile.exit(); chainBuffer.add(mplWhile); } @Override public void enterBreakDeclaration(BreakDeclarationContext ctx) { TerminalNode identifier = ctx.IDENTIFIER(); String label = identifier != null ? identifier.getText() : null; MplWhile loop; if (label == null) { MplSource source = toSource(ctx.BREAK().getSymbol()); loop = findParentLoop(source); } else { MplSource source = toSource(ctx.IDENTIFIER().getSymbol()); loop = findParentLoop(label, source); } if (loop == null) { return; } MplBreak mplBreak = new MplBreak(label, loop, modifierBuffer); addModifiableChainPart(mplBreak); checkNoModifier(mplBreak.getName(), modifierBuffer.getModeToken()); checkNoModifier(mplBreak.getName(), modifierBuffer.getNeedsRedstoneToken()); } @Override public void enterContinueDeclaration(ContinueDeclarationContext ctx) { TerminalNode identifier = ctx.IDENTIFIER(); String label = identifier != null ? identifier.getText() : null; MplWhile loop; if (label == null) { MplSource source = toSource(ctx.CONTINUE().getSymbol()); loop = findParentLoop(source); } else { MplSource source = toSource(ctx.IDENTIFIER().getSymbol()); loop = findParentLoop(label, source); } if (loop == null) { return; } MplContinue mplContinue = new MplContinue(label, loop, modifierBuffer); addModifiableChainPart(mplContinue); checkNoModifier(mplContinue.getName(), modifierBuffer.getModeToken()); checkNoModifier(mplContinue.getName(), modifierBuffer.getNeedsRedstoneToken()); } public MplWhile findParentLoop(MplSource source) { MplWhile loop; loop = loops.peek(); if (loop == null) { addException(new CompilerException(source, source.token.getText() + " can only be used in a loop")); } return loop; } public MplWhile findParentLoop(String label, MplSource source) { MplWhile loop = null; for (MplWhile mplWhile : loops) { if (label.equals(mplWhile.getLabel())) { loop = mplWhile; break; } } if (loop == null) { addException(new CompilerException(source, "Missing label " + label)); } return loop; } }