/*******************************************************************************
* Copyright 2012-present Pixate, Inc.
*
* 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.
******************************************************************************/
/**
* Copyright (c) 2012-2013 Pixate, Inc. All rights reserved.
*/
package com.pixate.freestyle.parsing;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import com.pixate.freestyle.util.PXLog;
import com.pixate.freestyle.util.StringUtil;
/**
* Pixate parser base class.
*/
public class PXParserBase<T extends Enum<T>> {
private static final String TAG = PXParserBase.class.getSimpleName();
protected Lexeme<T> currentLexeme;
private List<String> errors;
/**
* Returns the errors for this parser.
*
* @return An errors list (may be <code>null</code>);
*/
public List<String> getErrors() {
return errors;
}
/**
* Advance to the next lexeme in the lexeme stream. The current lexeme is
* returned and the currentLexeme is set to that return value.
*/
public Lexeme<T> advance() {
// TODO: generalize so descendants don't have to override this method
currentLexeme = null;
return currentLexeme;
}
/**
* Add an error message to the list of errors encountered during parsing
*
* @param error The error message to add
*/
public void addError(String error) {
if (!StringUtil.isEmpty(error)) {
if (errors == null) {
errors = new ArrayList<String>(3);
}
errors.add(error);
PXLog.i(TAG, error);
}
}
/**
* Add an error message, including the filename and offset where the error
* occurred
*
* @param error The error message
* @param filename The filename where the error occurred
* @param offset A string representing the offset where the error occurred
*/
public void addError(String error, String filename, String offset) {
if (StringUtil.isEmpty(filename)) {
addError(String.format("[PXEngine.ParseError, offset='%s']: %s", offset, error));
} else {
addError(String.format("[PXEngine.ParseError, file='%s', offset=%s]: %s", filename,
offset, error));
}
}
/**
* Remove all errors that have been previously reported. This should be
* called before a parse begins if the parser instance is being re-used.
*/
public void clearErrors() {
errors = null;
}
/**
* Throw an {@link PXParserException} and add an error message to the list
* of errors collected so far.
*
* @param message The error message
*/
public void errorWithMessage(String message) throws PXParserException {
throw new PXParserException(String.format("Unexpected token type. %s. Found %s", message,
((currentLexeme != null) ? currentLexeme.getType() : "null")));
}
/**
* Assert that the current lexeme matches the specified type. If it does not
* match, then throw an exception
*
* @param type The lexeme type to test against
*/
public void assertType(T type) {
if (currentLexeme == null || currentLexeme.getType() != type) {
errorWithMessage(MessageFormat.format("Expected a {0} token", type.toString()));
}
}
/**
* Assert that the current lexeme matches one of the types in the specified
* set. If it does not match, then throw an exception.
*
* @param types An set containing a collection of types to match against
*/
public void assertTypeInSet(EnumSet<T> types) {
if (!isInTypeSet(types)) {
List<String> typeNames = new ArrayList<String>(types.size());
for (Enum<T> s : types) {
typeNames.add(s.toString());
}
errorWithMessage("Expected a token of one of these types: " + typeNames);
}
}
/**
* Assert that the current lexeme matches the specified type. If it does not
* match, then throw an exception. If the types do match, then advance to
* the next lexeme.
*
* @param type The lexeme type to test against
*/
public Lexeme<T> assertTypeAndAdvance(T type) {
assertType(type);
return advance();
}
/**
* Advance to the next lexeme if the current lexeme matches the specified
* type.
*
* @param type The lexeme type to test against
*/
public void advanceIfIsType(T type) {
if (currentLexeme != null && currentLexeme.getType() == type) {
advance();
}
}
/**
* Advance to the next lexeme if the current lexeme matches the specified
* type. If the type does not match, then add a warning to the current list
* of errors, but do not throw an exception
*
* @param type The lexeme type to test against
* @param warning The warning message to emit
*/
public void advanceIfIsType(T type, String warning) {
if (currentLexeme != null && currentLexeme.getType() == type) {
advance();
} else {
errorWithMessage(warning);
}
}
/**
* Determine if the current lexeme matches the specified type.
*
* @param type
* @return <code>true</code> if the token is current; <code>false</code>
* otherwise.
*/
public boolean isType(T type) {
return (currentLexeme != null && currentLexeme.getType() == type);
}
/**
* Determine if the current lexeme matches one of the types in the specified
* set.
*
* @param set
* @return <code>true</code> if the current lexeme token is in the set;
* <code>false</code> otherwise.
*/
public boolean isInTypeSet(EnumSet<T> set) {
return (currentLexeme != null && set != null && set.contains(currentLexeme.getType()));
}
}