Java tutorial
/* * Copyright (c) 2013, R. van Twisk * All rights reserved. * Licensed under the The BSD 3-Clause License; * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://opensource.org/licenses/BSD-3-Clause * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of the aic-util nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.rvantwisk.gcodeparser; import com.rvantwisk.gcodeparser.exceptions.SimException; import com.rvantwisk.gcodeparser.exceptions.SimValidationException; import org.apache.commons.lang3.StringUtils; import java.nio.charset.Charset; import java.text.DecimalFormat; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * GCode parser * Parses a GCode file and send's the parsed blocks to the MachineController * The machine controller can then decide to support some commands or, throw a exception or whatever. * The machine controller can else decide to convert some codes, for example G2 to linear X/Y/Z steps. * Or, if you wish just drive some graphics. This all depends on the implementation of the Machine Controller * <p/> * The GCode parser will also test for correctness of the GCode file, for example it dis-allows F0 with G1 motions * <p/> * User: rvt * Date: 12/3/13 * Time: 8:34 AM * <p/> * TODO: Change in such a way that the capabilities can easily be extended. */ public class GCodeParser { private static String SEPARATOR = System.getProperty("line.separator"); private final MachineStatus machineStatus = new MachineStatus(); // kept's track of machine status after end of block private final MachineStatus intermediateStatus = new MachineStatus(); // Keeps tracking of machine status during block processing private final MachineController machineController[]; // A machine controller to send parsed block's + machine status into private final AbstractMachineValidator machineValidator; // A machine controller to send parsed block's + machine status into private final Pattern GCODEPATTERN = Pattern .compile("([GXYZABCDFHIJKLMNPQRSTUVW]o?)\\s*([0-9.+-]+)?(\\s*/?\\s*)([0-9.+-]+)?"); private final Pattern COMMENTS1 = Pattern.compile("\\(.*\\)"); // Comment between () private final Pattern COMMENTS2 = Pattern.compile("\\;.*"); // comment after ; private DecimalFormat wordFormatter = new DecimalFormat("#.#########"); // Formatting and trimming of numbers private String currentLine = ""; // Hold's the current line between begin and endblock calls private int currentLineNumber = 1; public GCodeParser(final AbstractMachineValidator machineValidator, final StringBuilder input, final MachineController... machineController) throws SimException { for (Object c : machineController) { if (!(c instanceof MachineController)) { throw new IllegalArgumentException( "StatisticLimitsController only accepts type's of MachineController"); } } this.machineController = machineController; this.machineValidator = machineValidator; Charset charset = Charset.forName("UTF-8"); String[] lines = input.toString().split(SEPARATOR); for (final String line : lines) { currentLine = line; parseLine(); currentLineNumber++; } for (MachineController controller : this.machineController) { controller.end(this, intermediateStatus); } } private void parseLine() throws SimException { // Remove comments between () and all comments after ; final StringBuilder parsedLine = new StringBuilder( COMMENTS2.matcher(COMMENTS1.matcher(currentLine).replaceAll("")).replaceAll("")); // A map that holds all parsed codes Map<String, ParsedWord> block = new HashMap<>(10); // Hold's the current parsed word ParsedWord thisWord; while ((thisWord = findWordInBlock(parsedLine)) != null) { final int pos = parsedLine.indexOf(thisWord.asRead); parsedLine.replace(pos, pos + thisWord.asRead.length(), ""); // We can have multiple G/M words within a block, so we move them to the 'key' String blockKey = thisWord.word; if (blockKey.equals("G") || blockKey.equals("M")) { blockKey = thisWord.parsed.replace('.', '_'); // Store gwords with a . as _ } if (block.containsKey(blockKey)) { throw new SimValidationException("Multiple " + thisWord.word + " words on one line."); } else { block.put(blockKey, thisWord); } } // First verify if the block itself is valid before we process it if (machineValidator != null) machineValidator.preVerify(block); // Copy to intermediate status to ensure our machine status is always valid intermediateStatus.copyFrom(machineStatus); // Notify the controller that we are about to start a new block, the block itself is valid, for example there we be no G1's and G0 on one line for (MachineController controller : this.machineController) { controller.startBlock(this, intermediateStatus, Collections.unmodifiableMap(block)); } intermediateStatus.startBlock(); // Copy the block to the machine intermediateStatus.setBlock(block); // Block en, no more data will come in for this block intermediateStatus.endBlock(); // Verify machine's state, for example if a R was found, do we also have a valid G to accompany with it? if (machineValidator != null) machineValidator.postVerify(intermediateStatus); // Notify the controller that everything was ok, now teh controller start 'running' the data for (MachineController controller : this.machineController) { controller.endBlock(this, intermediateStatus, Collections.unmodifiableMap(block)); } // setup new and valid machine status machineStatus.copyFrom(intermediateStatus); } /** * Find the next gcode in the current block * * @param gcodeBlock * @return TODO: Parse commands */ public ParsedWord findWordInBlock(final StringBuilder gcodeBlock) { Matcher myMatcher = GCODEPATTERN.matcher(gcodeBlock); if (myMatcher.find()) { try { final String g0 = myMatcher.group(0); final String g1 = myMatcher.group(1); final String g2 = myMatcher.group(2); final String g4 = myMatcher.group(4); final Double v = Double.valueOf(g2); final String value = wordFormatter.format(v); // public ParsedWord(String word, String parsed, Double value, String asRead) {..} // When a G block was found and a G 'value' use that examples G4/10 or G4 10 if (g1 == "G") { if (g4 != null && !StringUtils.isEmpty(g4)) { return new ParsedWord(g1, g1 + value, Double.valueOf(g4), g0); } else { return new ParsedWord(g1, g1 + value, null, g0); } } else { return new ParsedWord(g1, g1 + value, v, g0); } } catch (Exception e) { // System.out.print(e.getMessage() + ":"+gcodeBlock.toString()); } } return null; } /** * Test of the current command holds a specific word * If the word contains more then 1 character we check the complete word, else we check the letter * * @param currentBlock Block * @param word A word examples G94, A, B, G0 G30.1, G30_1 * @return */ private boolean hasWord(final Map<String, ParsedWord> currentBlock, final String word) { return currentBlock.containsKey(word); } /** * Returns a word count within teh current block * This is usefull to find multiple the same words within a modal group in the current block * * @param currentBlock * @param enumClass * @param <T> * @return */ private <T extends Enum<T>> int wordCount(Map<String, ParsedWord> currentBlock, Class<T> enumClass) { int wordCount = 0; T[] items = enumClass.getEnumConstants(); for (T item : items) { if (hasWord(currentBlock, item.toString())) { wordCount++; } } return wordCount; } /** * Replace a word within a GCODE block * * @param GCodeBLock * @param replaceWith * @return * @throws Exception */ protected String replaceWord(final String GCodeBLock, final String replaceWith) throws SimValidationException { if (replaceWith.startsWith("M") || replaceWith.startsWith("G")) { throw new SimValidationException("M and G words cannot be replaced at this moment."); } final ParsedWord word = findWordInBlock(new StringBuilder(GCodeBLock)); if (word == null) { return GCodeBLock + replaceWith; } else { return GCodeBLock.replace(word.asRead, replaceWith); } } /** * Helper to find multiple words in teh same block * * @param currentBlock * @param enumClass * @param <T> * @return */ private <T extends Enum<T>> boolean hasMultipleWords(Map<String, ParsedWord> currentBlock, Class<T> enumClass) { return wordCount(currentBlock, enumClass) > 1; } public String getCurrentLine() { return currentLine; } public int getCurrentLineNumber() { return currentLineNumber; } }