ArduinoParser.java
/*******************************************************************************
* jArduino: Arduino C++ Code Generation From Java
* Copyright 2020 Tony Washer
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package net.sourceforge.jarduino.message;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.Locale;
import net.sourceforge.jarduino.ArduinoException;
/**
* Parser.
*/
public final class ArduinoParser {
/**
* Number format (US format to match .dbc standard).
*/
private static final NumberFormat FORMAT = NumberFormat.getInstance(Locale.US);
/**
* Private constructor.
*/
private ArduinoParser() {
}
/**
* parse a file.
* @param pDBCFile the DBCFile
* @param pCharSet the character set to use
* @return the parsed system
* @throws ArduinoException on error
*/
public static ArduinoSystem parseFile(final File pDBCFile,
final Charset pCharSet) throws ArduinoException {
try (FileInputStream myStream = new FileInputStream(pDBCFile)) {
return parseStream(pDBCFile.getName().replace(".dbc", ""), pCharSet, myStream);
} catch (IOException e) {
throw new ArduinoException("Failed to open file", e);
}
}
/**
* parse an input stream.
* @param pName the name
* @param pCharSet the character set to use
* @param pInput the input stream
* @return the parsed system
* @throws ArduinoException on error
*/
public static ArduinoSystem parseStream(final String pName,
final Charset pCharSet,
final InputStream pInput) throws ArduinoException {
/* Protect against exceptions */
try (InputStreamReader myInputReader = new InputStreamReader(pInput, pCharSet);
BufferedReader myReader = new BufferedReader(myInputReader)) {
/* The current message */
ArduinoSystem mySystem = null;
ArduinoMessage myMessage = null;
/* Read the header entry */
boolean systemFound = false;
for (;;) {
/* Read next line */
String myLine = readNextLine(myReader);
if (myLine == null) {
break;
}
myLine = myLine.trim();
/* If we have not yet found the system */
if (!systemFound) {
/* if we have found the system */
if (myLine.startsWith(ArduinoNode.MARKER + ArduinoChar.COLON)) {
/* Allocate the system and note the fact */
mySystem = ArduinoSystem.parseSystem(pName, myLine);
systemFound = true;
}
/* else if we have a message */
} else {
/* Parse the line */
myMessage = parseLine(mySystem, myMessage, myLine);
}
}
/* return the system */
return mySystem;
/* Handle exceptions */
} catch (IOException e) {
throw new ArduinoException("Failed to read file", e);
}
}
/**
* read next line allowing for LF embedded in quotes.
* @param pReader the input reader
* @return the next line
* @throws ArduinoException on error
*/
private static String readNextLine(final BufferedReader pReader) throws ArduinoException {
/* Protect against exceptions */
try {
String myLine = null;
/* Loop reading lines */
for (;;) {
/* Read next line */
final String myPart = pReader.readLine();
if (myPart == null) {
return myLine;
}
/* Append to any existing line */
myLine = myLine == null
? myPart
: myLine + ArduinoChar.LF + myPart;
/* Count the number of quotes in the line */
final int myCount = countQuotes(myLine);
/* Line is complete if all quotes are completed */
if (myCount % 2 == 0) {
return myLine;
}
}
/* Handle exceptions */
} catch (IOException e) {
throw new ArduinoException("Failed to read line", e);
}
}
/**
* Count the number of quotes in the line.
* @param pLine the current line
* @return the count of quotes
*/
private static int countQuotes(final String pLine) {
/* Handle no quote at all */
int myIndex = pLine.indexOf(ArduinoChar.QUOTE);
if (myIndex == -1) {
return 0;
}
/* Loop looking for more quotes */
for (int myCount = 1; ; myCount++) {
myIndex = pLine.indexOf(ArduinoChar.QUOTE, myIndex + 1);
if (myIndex == -1) {
return myCount;
}
}
}
/**
* process line.
* @param pSystem the system
* @param pMessage the current message
* @param pLine the line
* @return the current message
* @throws ArduinoException on error
*/
public static ArduinoMessage parseLine(final ArduinoSystem pSystem,
final ArduinoMessage pMessage,
final String pLine) throws ArduinoException {
/* Access current message and token */
ArduinoMessage myMessage = pMessage;
switch (nextToken(pLine)) {
/* Message */
case ArduinoMessage.MARKER:
myMessage = ArduinoMessage.parseMessage(pSystem, pLine);
break;
/* Signal */
case ArduinoSignal.MARKER:
if (myMessage != null) {
ArduinoSignal.parseSignal(myMessage, pLine);
}
break;
/* Comment */
case ArduinoComments.MARKER:
ArduinoComments.parseComment(pSystem, pLine);
break;
/* attributeDef */
case ArduinoAttributes.MARKER_DEF:
ArduinoAttributes.parseAttributeDef(pSystem, ArduinoAttributes.MARKER_DEF, pLine);
break;
/* attributeDefault */
case ArduinoAttributes.MARKER_DEFAULT:
ArduinoAttributes.parseAttributeDefault(pSystem, ArduinoAttributes.MARKER_DEFAULT, pLine);
break;
/* attribute */
case ArduinoAttributes.MARKER:
ArduinoAttributes.parseAttribute(pSystem, ArduinoAttributes.MARKER, pLine);
break;
/* attributeRelDef */
case ArduinoAttributes.MARKER_REL_DEF:
ArduinoAttributes.parseAttributeDef(pSystem, ArduinoAttributes.MARKER_REL_DEF, pLine);
break;
/* attributeRelDefault */
case ArduinoAttributes.MARKER_REL_DEFAULT:
ArduinoAttributes.parseAttributeDefault(pSystem, ArduinoAttributes.MARKER_REL_DEFAULT, pLine);
break;
/* attributeRel */
case ArduinoAttributes.MARKER_REL:
ArduinoAttributes.parseAttribute(pSystem, ArduinoAttributes.MARKER_REL, pLine);
break;
/* xmitters */
case ArduinoNode.MARKER_TX:
ArduinoNode.parseXmitNodes(pSystem, pLine);
break;
/* values */
case ArduinoValues.MARKER:
ArduinoValues.parseValues(pSystem, pLine);
break;
/* Ignore if we do not recognise */
default:
break;
}
/* return the current message */
return myMessage;
}
/**
* Obtain the next token.
* @param pSource the current line
* @return the next token
*/
static String nextToken(final String pSource) {
final int myLen = pSource.length();
for (int i = 0; i < myLen; i++) {
if (Character.isWhitespace(pSource.charAt(i))) {
return pSource.substring(0, i);
}
}
return pSource;
}
/**
* Obtain the next token which must be between quotes.
* @param pSource the current line
* @return the next quoted token
* @throws ArduinoParserException on error
*/
static String nextQuotedToken(final String pSource) throws ArduinoParserException {
/* Token must begin with quote */
if (pSource.length() < 2 || pSource.charAt(0) != ArduinoChar.QUOTE) {
throw new ArduinoParserException("Missing start quote", pSource);
}
/* Must have another quote */
final int myIndex = pSource.indexOf(ArduinoChar.QUOTE, 1);
if (myIndex == -1) {
throw new ArduinoParserException("Missing end quote", pSource);
}
/* Return the token */
return pSource.substring(1, myIndex);
}
/**
* Strip the token from the line.
* @param pSource the current line
* @param pToken the starting token
* @return the stripped line
*/
static String stripToken(final String pSource,
final String pToken) {
return pSource.startsWith(pToken)
? pSource.substring(pToken.length()).trim()
: pSource;
}
/**
* Strip the quoted token from the line.
* @param pSource the current line
* @param pToken the starting token
* @return the stripped line
*/
static String stripQuotedToken(final String pSource,
final String pToken) {
return stripToken(pSource.substring(1), pToken).substring(1).trim();
}
/**
* Parse number.
* @param pNumberDef the number representation
* @return the number
* @throws ArduinoParserException on error
*/
static Number parseNumber(final String pNumberDef) throws ArduinoParserException {
/* Protect against exceptions */
try {
/* Obtain the numberDef as UpperCase and without + */
final String myNumber = pNumberDef.toUpperCase().replace('+', '0');
/* Parse the number */
return FORMAT.parse(myNumber);
/* Catch parsing errors */
} catch (ParseException e) {
throw new ArduinoParserException("Failed to parse number", pNumberDef);
}
}
/**
* Obtain the number of decimals in a number.
* @param pNumberDef the number representation (US format)
* @return the number of decimals
* @throws ArduinoParserException on error
*/
static int determineNumDecimals(final String pNumberDef) throws ArduinoParserException {
/* Protect against exceptions */
try {
/* Access the definition */
String myDef = pNumberDef;
int numDecimals = 0;
/* Look for Exponential representation */
int myIndex = pNumberDef.indexOf('e');
if (myIndex == -1) {
myIndex = pNumberDef.indexOf('E');
}
/* If we found exponential representation */
if (myIndex != -1) {
numDecimals = -Integer.parseInt(myDef.substring(myIndex + 1));
myDef = myDef.substring(0, myIndex);
}
/* Look for Decimal representation */
myIndex = myDef.indexOf(ArduinoChar.DEC);
if (myIndex != -1) {
numDecimals += myDef.substring(myIndex + 1).length();
}
/* Return the number of decimals */
return numDecimals;
/* Catch parsing errors */
} catch (NumberFormatException e) {
throw new ArduinoParserException("Failed to parse number", pNumberDef);
}
}
/**
* Parser exception.
*/
static final class ArduinoParserException extends Exception {
/**
* Serial versionID.
*/
private static final long serialVersionUID = 8237660286012917142L;
/**
* The detail.
*/
private final String theDetail;
/**
* Constructor.
* @param pMessage the message
* @param pDetail the detail
*/
ArduinoParserException(final String pMessage,
final String pDetail) {
super(pMessage);
theDetail = pDetail;
}
/**
* Obtain the detail.
* @return the detail
*/
public String getDetail() {
return theDetail;
}
}
}