/* * #%~ * Overture Testing Framework * %% * Copyright (C) 2008 - 2014 Overture * %% * This program 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. * * This program 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 this program. If not, see * <http://www.gnu.org/licenses/gpl-3.0.html>. * #~% */ package org.overture.core.testing; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.io.File; import java.io.IOException; import java.util.LinkedList; import java.util.List; import java.util.Vector; import org.overture.ast.definitions.SClassDefinition; import org.overture.ast.expressions.PExp; import org.overture.ast.lex.Dialect; import org.overture.ast.lex.LexLocation; import org.overture.ast.modules.AModuleModules; import org.overture.ast.node.INode; import org.overture.config.Release; import org.overture.config.Settings; import org.overture.parser.lex.LexException; import org.overture.parser.syntax.ParserException; import org.overture.parser.util.ParserUtil; import org.overture.typechecker.TypeCheckException; import org.overture.typechecker.util.TypeCheckerUtil; import org.overture.typechecker.util.TypeCheckerUtil.TypeCheckResult; /** * Parse and Type Check VDM Sources. This class is the main interaction point with the Overture parser and type checker. * It calls on them to process and construct ASTs from various kinds of sources.<br> * <br> * All methods in this class will cause test failures if they cannot process the source. If you need more fine-grained * control over the parsing and type checking processes, we suggest using {@link ParserUtil} and {@link TypeCheckerUtil} * . * * @author ldc */ public abstract class ParseTcFacade { static { // Omit paths from locations. Allows error comparison on multiple machines. LexLocation.absoluteToStringLocation = false; } public static String UTF8 = "UTF-8"; /** * Parse and type check a VDM model. This method will try to check the model in VDM classic and VDM 10. It assumes * the model sources are encoded in UTF-8. * * @param sources * the {@link List} of {@link File} containing the model's sources * @param testName * the name of the test calling this method (used for failure reporting) * @param dialect * the VDM {@link Dialect} the source is written in * @return the AST of the model, as a list of {@link INode} * @throws LexException * @throws ParserException */ public static List<INode> typedAstWithRetry(List<File> sources, String testName, Dialect dialect) throws ParserException, LexException { return typedAst(sources, testName, dialect, true); } /** * Parse (but don't type check) a VDM model encoded in a string. * * @param model * the String representing the model * @param dialect * the VDM dialect the model is in * @return the AST of the model, as a list of {@link INode} */ public static List<INode> parseModelString(String model, Dialect dialect) { Settings.dialect = dialect; List<INode> r = new LinkedList<INode>(); switch (dialect) { case VDM_SL: r.addAll(ParserUtil.parseSl(model).result); break; case VDM_PP: r.addAll(ParserUtil.parseOo(model).result); break; case VDM_RT: r.addAll(ParserUtil.parseOo(model).result); break; default: fail("Unrecognised dialect:" + dialect); return null; } return r; } /** * Parse (but don't type check) a VDM expression encoded in a string. * * @param expression * the String representing the expression * @return the {@link PExp} AST node of the expression */ public static PExp parseExpressionString(String expression) throws ParserException, LexException { return ParserUtil.parseExpression(expression).result; } /** * Parse and type check a VDM expression encoded in a string using VDM_SL. Failure results in an exception. * * @param expression * the String representing the expression * @return the {@link PExp} AST node of the expression * * @see #parseModelString(String, Dialect) */ public static PExp parseTcExpressionString(String expression) throws ParserException, LexException { return parseTcExpressionString(expression, Dialect.VDM_SL); } /** * Parse and type check a VDM expression encoded in a string using the specified dialect. Failure results in an exception. * * @param expression * the String representing the expression * @param dialect * the {@link Dialect} to use when checking * @return the {@link PExp} AST node of the expression */ public static PExp parseTcExpressionString(String expression, Dialect dialect) throws ParserException, LexException, TypeCheckException { Settings.dialect = dialect; TypeCheckResult<PExp> res = TypeCheckerUtil.typeCheckExpression(expression); if (res.errors.isEmpty()) { return res.result; } throw new TypeCheckException("Cannot type check string expression.", null, res.result); } /** * Parse and type check a VDM model encoded in a string. * * @param model * the String representing the model * @param dialect * the VDM dialect the model is in * @return the AST of the model, as a list of {@link INode} */ public static List<INode> typedAstFromString(String model, Dialect dialect) throws ParserException, LexException { Settings.dialect = dialect; List<INode> r = new LinkedList<INode>(); switch (dialect) { case VDM_SL: r.addAll(TypeCheckerUtil.typeCheckSl(model).result); break; case VDM_PP: r.addAll(TypeCheckerUtil.typeCheckPp(model).result); break; case VDM_RT: r.addAll(TypeCheckerUtil.typeCheckRt(model).result); break; default: fail("Unrecognised dialect:" + dialect); return null; } return r; } /** * Parse and type check a VDM model. This method will check the model in whatever language release is currently set. * It assumes the model sources are encoded in UTF-8. * * @param sources * the {@link List} of {@link File} containing the model's sources * @param testName * the name of the test calling this method (used for failure reporting) * @param dialect * the VDM {@link Dialect} the source is written in * @return the AST of the model, as a list of {@link INode} * @throws LexException * @throws ParserException */ public static List<INode> typedAstNoRetry(List<File> sources, String testName, Dialect dialect) throws ParserException, LexException { return typedAst(sources, testName, dialect, false); } /** * Parse and type check a single VDM source file. It assumes the model source is encoded in UTF-8. * * @param sourcePath * a {@link String} with the path to a single VDM model source * @param testName * the name of the test calling this method (used for failure reporting) * @return the AST of the model as a {@link List} of {@link INode}. * @throws IOException * @throws ParserException * @throws LexException */ public static List<INode> typedAst(String sourcePath, String testName) throws IOException, ParserException, LexException { String[] parts = sourcePath.split("\\."); String ext; if (parts.length == 1) { ext = "vdm" + sourcePath.substring(sourcePath.length() - 2, sourcePath.length()).toLowerCase(); } else { ext = parts[1]; } File f = new File(sourcePath); List<File> sources = new Vector<File>(); sources.add(f); if (ext.equals("vdmsl") | ext.equals("vdm")) { return parseTcSlContent(sources, testName, true); } else { if (ext.equals("vdmpp") | ext.equals("vpp")) { List<File> files = new Vector<File>(); files.add(f); return parseTcPpContent(files, testName, true); } else { if (ext.equals("vdmrt")) { List<File> files = new Vector<File>(); files.add(f); return parseTcRtContent(files, testName, true); } else { fail("Unexpected extension in file " + sourcePath + ". Only .vdmpp, .vdmsl and .vdmrt allowed"); } } } // only needed to compile. will never hit because of fail() return null; } private static List<INode> typedAst(List<File> content, String testName, Dialect dialect, boolean retry) throws ParserException, LexException { switch (dialect) { case VDM_SL: return parseTcSlContent(content, testName, retry); case VDM_PP: return parseTcPpContent(content, testName, retry); case VDM_RT: return parseTcRtContent(content, testName, retry); default: fail("Unrecognised dialect:" + dialect); return null; } } // These 3 methods have so much duplicated code because we cannot // return the TC results since their types are all different. private static List<INode> parseTcRtContent(List<File> content, String testName, boolean retry) throws ParserException, LexException { Settings.dialect = Dialect.VDM_RT; TypeCheckResult<List<SClassDefinition>> TC = TypeCheckerUtil.typeCheckRt(content, UTF8); // retry with other dialect if (retry && (!TC.parserResult.errors.isEmpty() || !TC.errors.isEmpty())) { if (Settings.release == Release.CLASSIC) { Settings.release = Release.VDM_10; return parseTcRtContent(content, testName, false); } if (Settings.release == Release.VDM_10) { Settings.release = Release.CLASSIC; return parseTcRtContent(content, testName, false); } } checkTcResult(TC); List<INode> r = new LinkedList<INode>(); r.addAll(TC.result); return r; } private static List<INode> parseTcPpContent(List<File> content, String testName, boolean retry) { Settings.dialect = Dialect.VDM_PP; TypeCheckResult<List<SClassDefinition>> TC = TypeCheckerUtil.typeCheckPp(content, UTF8); // retry with other dialect if (retry && (!TC.parserResult.errors.isEmpty() || !TC.errors.isEmpty())) { if (Settings.release == Release.CLASSIC) { Settings.release = Release.VDM_10; return parseTcPpContent(content, testName, false); } if (Settings.release == Release.VDM_10) { Settings.release = Release.CLASSIC; return parseTcPpContent(content, testName, false); } } checkTcResult(TC); List<INode> r = new LinkedList<INode>(); r.addAll(TC.result); return r; } private static List<INode> parseTcSlContent(List<File> content, String testName, boolean retry) { Settings.dialect = Dialect.VDM_SL; TypeCheckResult<List<AModuleModules>> TC = TypeCheckerUtil.typeCheckSl(content, UTF8); // retry with other dialect if (retry && (!TC.parserResult.errors.isEmpty() || !TC.errors.isEmpty())) { if (Settings.release == Release.CLASSIC) { Settings.release = Release.VDM_10; return parseTcSlContent(content, testName, false); } if (Settings.release == Release.VDM_10) { Settings.release = Release.CLASSIC; return parseTcSlContent(content, testName, false); } } checkTcResult(TC); List<INode> r = new LinkedList<INode>(); r.addAll(TC.result); return r; } protected static void checkTcResult( @SuppressWarnings("rawtypes") TypeCheckResult TC) { assertTrue("Parse Error:\n" + TC.parserResult.getErrorString(), TC.parserResult.errors.isEmpty()); assertTrue("Type Check Error:\n" + TC.getErrorString(), TC.errors.isEmpty()); } }