/******************************************************************************* * * Copyright (c) 2008 Fujitsu Services Ltd. * * Author: Nick Battle * * This file is part of VDMJ. * * VDMJ 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. * * VDMJ 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 VDMJ. If not, see <http://www.gnu.org/licenses/>. * ******************************************************************************/ package org.overture.parser.syntax; import java.io.PrintWriter; import java.util.Arrays; import java.util.List; import java.util.Vector; import org.overture.ast.intf.lex.ILexLocation; import org.overture.ast.intf.lex.ILexToken; import org.overture.ast.lex.Dialect; import org.overture.ast.lex.LexIdentifierToken; import org.overture.ast.lex.LexNameToken; import org.overture.ast.lex.LexToken; import org.overture.ast.lex.VDMToken; import org.overture.ast.messages.InternalException; import org.overture.parser.lex.LexException; import org.overture.parser.lex.LexTokenReader; import org.overture.parser.messages.LocatedException; import org.overture.parser.messages.VDMError; import org.overture.parser.messages.VDMWarning; /** * The parent class of all syntax readers. */ public abstract class SyntaxReader { /** The lexical analyser. */ protected final LexTokenReader reader; /** The dialect of VDM that we're parsing. */ protected final Dialect dialect; /** A DefinitionReader, if created. */ protected DefinitionReader definitionReader = null; /** An ExpressionReader, if created. */ protected ExpressionReader expressionReader = null; /** A PatternReader, if created. */ protected PatternReader patternReader = null; /** A TypeReader, if created. */ protected TypeReader typeReader = null; /** A BindReader, if created. */ protected BindReader bindReader = null; /** A StatementReader, if created. */ protected StatementReader statementReader = null; /** A ClassReader, if created. */ protected ClassReader classReader = null; /** The errors raised. */ private List<VDMError> errors = new Vector<VDMError>(); /** The warnings raised. */ private List<VDMWarning> warnings = new Vector<VDMWarning>(); /** The sub-readers defined, if any. */ private List<SyntaxReader> readers = new Vector<SyntaxReader>(); /** The maximum number of syntax errors allowed in one Reader. */ private static final int MAX = 100; /** * Create a reader with the given lexical analyser and VDM++ flag. */ protected SyntaxReader(LexTokenReader reader) { this.reader = reader; this.dialect = reader.dialect; } protected SyntaxReader() { this.reader = null; this.dialect = null; } /** * Read the next token from the lexical analyser, and advance by one token. * * @return The next token. */ protected LexToken nextToken() throws LexException { return reader.nextToken(); } /** * Return the last token read by the lexical analyser without advancing. Repeated calls to this method will return * the same result. * * @return The last token again. */ protected LexToken lastToken() throws LexException { return reader.getLast(); } /** * Return the last token read, and also advance by one token. This is equivalent to calling {@link #lastToken} * followed by {@link #nextToken}, but returning the result of lastToken. * * @return The last token. * @throws LexException */ protected LexToken readToken() throws LexException { LexToken tok = reader.getLast(); reader.nextToken(); return tok; } /** * Set the name of the current module or class. Unqualified symbol names use this as their module/class name. See * {@link #idToName}. * * @param module */ public void setCurrentModule(String module) { reader.currentModule = module; } /** * @return The current module/class name. */ public String getCurrentModule() { return reader.currentModule; } /** * Convert an identifier into a name. A name is an identifier that has a module name qualifier, so this method uses * the current module to convert the identifier passed in. * * @param id * The identifier to convert * @return The corresponding name. */ protected LexNameToken idToName(LexIdentifierToken id) { LexNameToken name = new LexNameToken(reader.currentModule, id); return name; } /** * Return the last token, converted to a {@link LexIdentifierToken}. If the last token is not an identifier token, * an exception is thrown with the message passed in. * * @return The last token as a LexIdentifierToken. * @throws LexException */ protected LexIdentifierToken lastIdToken() throws ParserException, LexException { LexToken tok = reader.getLast(); if (tok.type == VDMToken.IDENTIFIER) { LexIdentifierToken id = (LexIdentifierToken) tok; if (id.isOld()) { throwMessage(2295, "Can't use old name here", tok); } return id; } throwMessage(2058, "Expecting Identifier"); return null; } /** * Return the last token, converted to a {@link LexNameToken}. If the last token is not a name token, or an * identifier token that can be converted to a name, an exception is thrown with the message passed in. * * @return The last token as a LexIdentifierToken. * @throws LexException * @throws ParserException */ protected LexNameToken lastNameToken() throws LexException, ParserException { LexToken tok = reader.getLast(); if (tok instanceof LexNameToken) { LexNameToken name = (LexNameToken) tok; if (name.old) { throwMessage(2295, "Can't use old name here", tok); } return name; } else if (tok instanceof LexIdentifierToken) { LexIdentifierToken id = (LexIdentifierToken) tok; if (id.isOld()) { throwMessage(2295, "Can't use old name here", tok); } return new LexNameToken(reader.currentModule, id); } throwMessage(2059, "Expecting a name"); return null; } /** * Return the last token as an identifier, and advance by one token. This is similar to calling {@link #lastIdToken} * followed by nextToken, and returning the result of the lastIdToken. * * @param message * The message to throw if the last token is not an id. * @return The last token as a LexIdentifierToken. * @throws LexException * @throws ParserException */ protected LexIdentifierToken readIdToken(String message) throws LexException, ParserException { LexToken tok = reader.getLast(); if (tok.type == VDMToken.IDENTIFIER) { nextToken(); LexIdentifierToken id = (LexIdentifierToken) tok; if (id.isOld()) { throwMessage(2295, "Can't use old name here", tok); } return id; } if (tok.type == VDMToken.NAME) { message = "Found qualified name " + tok + ". " + message; } throwMessage(2060, message); return null; } /** * Return the last token as a name, and advance by one token. This is similar to calling {@link #lastNameToken} * followed by nextToken, and returning the result of the lastNameToken. * * @param message * The message to throw if the last token is not a name. * @return The last token as a LexNameToken. * @throws LexException * @throws ParserException */ protected LexNameToken readNameToken(String message) throws LexException, ParserException { LexToken tok = reader.getLast(); nextToken(); if (tok instanceof LexNameToken) { LexNameToken name = (LexNameToken) tok; if (name.old) { throwMessage(2295, "Can't use old name here", tok); } return name; } else if (tok instanceof LexIdentifierToken) { LexIdentifierToken id = (LexIdentifierToken) tok; if (id.isOld()) { throwMessage(2295, "Can't use old name here", tok); } return new LexNameToken(reader.currentModule, id); } throwMessage(2061, message); return null; } /** * @return A new DefinitionReader. */ protected DefinitionReader getDefinitionReader() { if (definitionReader == null) { definitionReader = new DefinitionReader(reader); readers.add(definitionReader); } return definitionReader; } /** * @return A new DefinitionReader. */ protected ExpressionReader getExpressionReader() { if (expressionReader == null) { expressionReader = new ExpressionReader(reader); readers.add(expressionReader); } return expressionReader; } /** * @return A new PatternReader. */ protected PatternReader getPatternReader() { if (patternReader == null) { patternReader = new PatternReader(reader); readers.add(patternReader); } return patternReader; } /** * @return A new TypeReader. */ protected TypeReader getTypeReader() { if (typeReader == null) { typeReader = new TypeReader(reader); readers.add(typeReader); } return typeReader; } /** * @return A new BindReader. */ protected BindReader getBindReader() { if (bindReader == null) { bindReader = new BindReader(reader); readers.add(bindReader); } return bindReader; } /** * @return A new StatementReader. */ protected StatementReader getStatementReader() { if (statementReader == null) { statementReader = new StatementReader(reader); readers.add(statementReader); } return statementReader; } /** * @return A new ClassReader. */ protected ClassReader getClassReader() { if (classReader == null) { classReader = new ClassReader(reader); readers.add(classReader); } return classReader; } public void close() { reader.close(); } /** * If the last token is as expected, advance, else raise an error. * * @param tok * The token type to check for. * @param number * The error number. * @param message * The error message to raise if the token is not as expected. * @throws LexException * @throws ParserException */ protected void checkFor(VDMToken tok, int number, String message) throws LexException, ParserException { if (lastToken().is(tok)) { nextToken(); } else { throwMessage(number, message); } } /** * If the last token is the one passed, advance by one, else do nothing. * * @param tok * The token type to check for. * @return True if the token was skipped. * @throws LexException */ protected boolean ignore(VDMToken tok) throws LexException { if (lastToken().is(tok)) { nextToken(); return true; } else { return false; } } /** * Raise a {@link ParserException} at the current location. * * @param number * The error number. * @param message * The error message. * @throws ParserException * @throws LexException */ protected void throwMessage(int number, String message) throws ParserException, LexException { throw new ParserException(number, message, lastToken().location, reader.getTokensRead()); } /** * Raise a {@link ParserException} at the location of the token passed in. * * @param number * The error number. * @param message * The error message. * @param token * The location of the error. * @throws ParserException */ protected void throwMessage(int number, String message, ILexToken token) throws ParserException { throw new ParserException(number, message, token.getLocation(), reader.getTokensRead()); } /** * Raise a {@link ParserException} with a given token depth. * @param number The error number. * @param message The error message. * @param depth The depth of the exception (tokens read). * * @throws ParserException */ protected void throwMessage(int number, String message, int depth) throws ParserException, LexException { throw new ParserException(number, message, lastToken().location, depth); } /** * Raise a syntax error and attempt to recover. The error is added to the errors list, and if this exceeds 100 * errors the parser is aborted. The "after" and "upto" lists of token types are then used to control the advance of * the parser to skip beyond the error. Tokens are read until one occurs in either list or EOF is reached. If the * token is in the "after" list, one more token is read before returning; if it is in the "upto" list, the last * token is left pointing to the token before returning. If EOF is reached, the method returns. * * @param error * The exception that caused the error. * @param after * A list of tokens to recover to, and step one beyond. * @param upto * A list of tokens to recover to. */ protected void report(LocatedException error, VDMToken[] after, VDMToken[] upto) { VDMError vdmerror = new VDMError(error); errors.add(vdmerror); if (errors.size() >= MAX - 1) { errors.add(new VDMError(9, "Too many syntax errors", error.location)); throw new InternalException(9, "Too many syntax errors"); } // Either leave one token beyond something in the after list, or // at something in the next upto list. List<VDMToken> afterList = Arrays.asList(after); List<VDMToken> uptoList = Arrays.asList(upto); try { VDMToken tok = lastToken().type; while (!uptoList.contains(tok) && tok != VDMToken.EOF) { if (afterList.contains(tok)) { nextToken(); break; } tok = nextToken().type; } } catch (LexException le) { errors.add(new VDMError(le)); } } /** * Report a warning. Unlike errors, this does no token recovery. */ protected void warning(int no, String msg, ILexLocation location) { VDMWarning vdmwarning = new VDMWarning(no, msg, location); warnings.add(vdmwarning); if (warnings.size() >= MAX - 1) { errors.add(new VDMError(9, "Too many warnings", location)); throw new InternalException(9, "Too many warnings"); } } /** * @return The error count from all readers that can raise errors. */ public int getErrorCount() { int size = 0; for (SyntaxReader rdr : readers) { size += rdr.getErrorCount(); } return size + errors.size(); } /** * @return The errors from all readers that can raise errors. */ public List<VDMError> getErrors() { List<VDMError> list = new Vector<VDMError>(); for (SyntaxReader rdr : readers) { list.addAll(rdr.getErrors()); } list.addAll(errors); return list; } /** * @return The warning count from all readers that can raise warnings. */ public int getWarningCount() { int size = 0; for (SyntaxReader rdr : readers) { size += rdr.getWarningCount(); } return size + warnings.size(); } /** * @return The warnings from all readers that can raise warnings. */ public List<VDMWarning> getWarnings() { List<VDMWarning> list = new Vector<VDMWarning>(); for (SyntaxReader rdr : readers) { list.addAll(rdr.getWarnings()); } list.addAll(warnings); return list; } /** * Print errors and warnings to the PrintWriter passed. * * @param out */ public void printErrors(PrintWriter out) { for (VDMError e : getErrors()) { out.println(e.toString()); } } public void printWarnings(PrintWriter out) { for (VDMWarning w : getWarnings()) { out.println(w.toString()); } } @Override public String toString() { return reader.toString(); } }