/******************************************************************************* * * 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.interpreter.runtime; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.io.PrintWriter; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.TreeMap; import java.util.Vector; import org.overture.ast.analysis.AnalysisException; import org.overture.ast.definitions.ANamedTraceDefinition; import org.overture.ast.definitions.PDefinition; import org.overture.ast.definitions.SClassDefinition; import org.overture.ast.expressions.PExp; import org.overture.ast.intf.lex.ILexLocation; 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.modules.AModuleModules; import org.overture.ast.node.INode; import org.overture.ast.statements.PStm; import org.overture.ast.typechecker.NameScope; import org.overture.ast.types.PType; import org.overture.ast.util.modules.CombinedDefaultModule; import org.overture.config.Settings; import org.overture.interpreter.assistant.IInterpreterAssistantFactory; import org.overture.interpreter.debug.BreakpointManager; import org.overture.interpreter.debug.DBGPReader; import org.overture.interpreter.messages.Console; import org.overture.interpreter.scheduler.BasicSchedulableThread; import org.overture.interpreter.scheduler.ResourceScheduler; import org.overture.interpreter.traces.CallSequence; import org.overture.interpreter.traces.TestSequence; import org.overture.interpreter.traces.TraceReductionType; import org.overture.interpreter.traces.TraceVariableStatement; import org.overture.interpreter.traces.Verdict; import org.overture.interpreter.values.Value; import org.overture.parser.lex.LexException; import org.overture.parser.lex.LexTokenReader; import org.overture.parser.messages.VDMErrorsException; import org.overture.parser.syntax.ParserException; import org.overture.pog.pub.IProofObligationList; import org.overture.typechecker.Environment; import org.overture.typechecker.FlatEnvironment; import org.overture.typechecker.ModuleEnvironment; import org.overture.typechecker.PrivateClassEnvironment; import org.overture.typechecker.TypeCheckInfo; import org.overture.typechecker.TypeChecker; import org.overture.typechecker.visitor.TypeCheckVisitor; /** * An abstract VDM interpreter. */ abstract public class Interpreter { /** the assistant factory used by this interpreter for e.g. FunctionValues */ protected final IInterpreterAssistantFactory assistantFactory; /** The main thread scheduler */ public ResourceScheduler scheduler; /** The initial execution context. */ public RootContext initialContext; /** A list of breakpoints created. */ public Map<Integer, Breakpoint> breakpoints; /** A list of source files loaded. */ public Map<File, SourceFile> sourceFiles; /** The number of the next breakpoint to be created. */ public int nextbreakpoint = 0; /** A static instance pointer to the interpreter. */ protected static Interpreter instance = null; /** * Create an Interpreter. * * @param assistantFactory * the assistant factory to be used by the interpreter */ public Interpreter(IInterpreterAssistantFactory assistantFactory) { scheduler = new ResourceScheduler(); breakpoints = new TreeMap<Integer, Breakpoint>(); sourceFiles = new HashMap<File, SourceFile>(); this.assistantFactory = assistantFactory; instance = this; } /** * Gets the current assistant factory * * @return */ public IInterpreterAssistantFactory getAssistantFactory() { return assistantFactory; } /** * Get a string version of the environment. * * @return */ public String getInitialContext() { return initialContext.toString(); } /** * Get the global environment. * * @return */ abstract public Environment getGlobalEnvironment(); /** * @return The Interpreter instance. */ public static Interpreter getInstance() { return instance; // NB. last one created } /** * Get the name of the default module or class. Symbols in the default module or class do not have to have their * names qualified when being referred to on the command line. * * @return The default name. */ abstract public String getDefaultName(); /** * Get the filename that contains the default module or class. * * @return The default file name. */ abstract public File getDefaultFile(); /** * Set the default module or class name. * * @param name * The default name. * @throws Exception */ abstract public void setDefaultName(String name) throws Exception; /** * Initialize the initial context. This means that all definition initializers are re-run to put the global * environment back into its original state. This is run implicitly when the interpreter starts, but it can also be * invoked explicitly via the "init" command. * * @param dbgp */ abstract public void init(DBGPReader dbgp); /** * Initialize the context between trace sequences. This is less thorough than the full init, since it does not reset * the scheduler for example. * * @param dbgp */ abstract public void traceInit(DBGPReader dbgp); /** * Parse the line passed, type check it and evaluate it as an expression in the initial context. * * @param line * A VDM expression. * @param dbgp * The DBGPReader, if any * @return The value of the expression. * @throws Exception * Parser, type checking or runtime errors. */ abstract public Value execute(String line, DBGPReader dbgp) throws Exception; /** * Parse the content of the file passed, type check it and evaluate it as an expression in the initial context. * * @param file * A file containing a VDM expression. * @return The value of the expression. * @throws Exception * Parser, type checking or runtime errors. */ public Value execute(File file) throws Exception { BufferedReader br = new BufferedReader(new FileReader(file)); StringBuilder sb = new StringBuilder(); String line = br.readLine(); while (line != null) { sb.append(line); line = br.readLine(); } br.close(); Value result = execute(sb.toString(), null); BasicSchedulableThread.terminateAll(); // NB not a session (used for tests) return result; } /** * Parse the line passed, and evaluate it as an expression in the context passed. Note that this does not type check * the expression. * * @param line * A VDM expression. * @param ctxt * The context in which to evaluate the expression. * @return The value of the expression. * @throws Exception * Parser or runtime errors. */ abstract public Value evaluate(String line, Context ctxt) throws Exception; /** * @return The list of breakpoints currently set. */ public Map<Integer, Breakpoint> getBreakpoints() { return breakpoints; } /** * Get a line of a source file. * * @param src * @return */ public String getSourceLine(ILexLocation src) { return getSourceLine(src.getFile(), src.getStartLine()); } /** * Get a line of a source file by its location. * * @param file * @param line * @return */ public String getSourceLine(File file, int line) { return getSourceLine(file, line, ": "); } /** * Get a line of a source file by its location. * * @param file * @param line * @param sep * @return */ public String getSourceLine(File file, int line, String sep) { SourceFile source = sourceFiles.get(file); if (source == null) { try { source = new SourceFile(file); sourceFiles.put(file, source); } catch (IOException e) { return "Cannot open source file: " + file; } } return line + sep + source.getLine(line); } /** * Get an entire source file object. * * @param file * @return * @throws IOException */ public SourceFile getSourceFile(File file) throws IOException { SourceFile source = sourceFiles.get(file); if (source == null) { source = new SourceFile(file); sourceFiles.put(file, source); } return source; } /** * Get a list of all source files. * * @return */ abstract public Set<File> getSourceFiles(); /** * Get a list of proof obligations for the loaded specification. * * @return A list of POs. * @throws AnalysisException */ abstract public IProofObligationList getProofObligations() throws AnalysisException; /** * Find a statement by file name and line number. * * @param file * The name of the class/module * @param lineno * The line number * @return A Statement object if found, else null. */ abstract public PStm findStatement(File file, int lineno); /** * Find an expression by file name and line number. * * @param file * The name of the file * @param lineno * The line number * @return An Expression object if found, else null. */ abstract public PExp findExpression(File file, int lineno); /** * Find a global environment value by name. * * @param name * The name of the variable * @return A Value object if found, else null. */ public Value findGlobal(LexNameToken name) { return initialContext.check(name); } /** * Set a statement tracepoint. A tracepoint does not stop execution, but evaluates and displays an expression before * continuing. * * @param stmt * The statement to trace. * @param trace * The expression to evaluate. * @return The Breakpoint object created. * @throws Exception * Expression is not valid. */ public Breakpoint setTracepoint(PStm stmt, String trace) throws Exception { BreakpointManager.setBreakpoint(stmt, new Tracepoint(stmt.getLocation(), ++nextbreakpoint, trace)); breakpoints.put(nextbreakpoint, BreakpointManager.getBreakpoint(stmt)); return BreakpointManager.getBreakpoint(stmt); } /** * Set an expression tracepoint. A tracepoint does not stop execution, but evaluates an expression before * continuing. * * @param exp * The expression to trace. * @param trace * The expression to evaluate. * @return The Breakpoint object created. * @throws LexException * @throws ParserException */ public Breakpoint setTracepoint(PExp exp, String trace) throws ParserException, LexException { BreakpointManager.setBreakpoint(exp, new Tracepoint(exp.getLocation(), ++nextbreakpoint, trace)); breakpoints.put(nextbreakpoint, BreakpointManager.getBreakpoint(exp)); return BreakpointManager.getBreakpoint(exp); } /** * Set a statement breakpoint. A breakpoint stops execution and allows the user to query the environment. * * @param stmt * The statement at which to stop. * @param condition * The condition when to stop. * @return The Breakpoint object created. * @throws LexException * @throws ParserException */ public Breakpoint setBreakpoint(PStm stmt, String condition) throws ParserException, LexException { BreakpointManager.setBreakpoint(stmt, new Stoppoint(stmt.getLocation(), ++nextbreakpoint, condition)); breakpoints.put(nextbreakpoint, BreakpointManager.getBreakpoint(stmt)); return BreakpointManager.getBreakpoint(stmt); } /** * Set an expression breakpoint. A breakpoint stops execution and allows the user to query the environment. * * @param exp * The expression at which to stop. * @param condition * The condition when to stop. * @return The Breakpoint object created. * @throws LexException * @throws ParserException */ public Breakpoint setBreakpoint(PExp exp, String condition) throws ParserException, LexException { BreakpointManager.setBreakpoint(exp, new Stoppoint(exp.getLocation(), ++nextbreakpoint, condition)); breakpoints.put(nextbreakpoint, BreakpointManager.getBreakpoint(exp)); return BreakpointManager.getBreakpoint(exp); } /** * Clear the breakpoint given by the number. * * @param bpno * The breakpoint number to remove. * @return The breakpoint object removed, or null. */ public Breakpoint clearBreakpoint(int bpno) { Breakpoint old = breakpoints.remove(bpno); if (old != null) { PStm stmt = findStatement(old.location.getFile(), old.location.getStartLine()); if (stmt != null) { BreakpointManager.setBreakpoint(stmt, new Breakpoint(stmt.getLocation())); } else { PExp exp = findExpression(old.location.getFile(), old.location.getStartLine()); assert exp != null : "Cannot locate old breakpoint?"; BreakpointManager.setBreakpoint(exp, new Breakpoint(exp.getLocation())); } } return old; // null if not found } public void clearBreakpointHits() { for (Entry<Integer, Breakpoint> e : breakpoints.entrySet()) { e.getValue().clearHits(); } } abstract protected PExp parseExpression(String line, String module) throws Exception; public PType typeCheck(PExp expr, Environment env) throws Exception { TypeChecker.clearErrors(); try { PType type = expr.apply(new TypeCheckVisitor(), new TypeCheckInfo(assistantFactory, env, NameScope.NAMESANDSTATE)); if (TypeChecker.getErrorCount() > 0) { throw new VDMErrorsException(TypeChecker.getErrors()); } return type; } catch (Exception e) { throw e; } catch (Throwable e) { e.printStackTrace(); } return null; } public PType typeCheck(PStm stmt, Environment env) throws Exception { TypeChecker.clearErrors(); try { PType type = stmt.apply(new TypeCheckVisitor(), new TypeCheckInfo(assistantFactory, env, NameScope.NAMESANDSTATE)); if (TypeChecker.getErrorCount() > 0) { throw new VDMErrorsException(TypeChecker.getErrors()); } return type; } catch (Exception e) { throw e; } catch (Throwable e) { e.printStackTrace(); } return null; } public PType typeCheck(String line) throws Exception { PExp expr = parseExpression(line, getDefaultName()); return typeCheck(expr, getGlobalEnvironment()); } /** * @param classname * Unused. * @return the class found or null */ public SClassDefinition findClass(String classname) { assert false : "findClass cannot be called for modules"; return null; } public abstract PType findType(String typename); /** * @param module * Unused. * @return the module found or null */ public AModuleModules findModule(String module) { assert false : "findModule cannot be called for classes"; return null; } private static PrintWriter writer = null; public static void setTraceOutput(PrintWriter pw) { writer = pw; } abstract protected ANamedTraceDefinition findTraceDefinition( LexNameToken name); public void runtrace(String name, int testNo, boolean debug) throws Exception { runtrace(name, testNo, debug, 1.0F, TraceReductionType.NONE, 1234); } public boolean runtrace(String name, int testNo, boolean debug, float subset, TraceReductionType type, long seed) throws Exception { LexTokenReader ltr = new LexTokenReader(name, Dialect.VDM_SL); LexToken token = ltr.nextToken(); ltr.close(); LexNameToken lexname = null; switch (token.type) { case NAME: lexname = (LexNameToken) token; if (Settings.dialect == Dialect.VDM_SL && !lexname.module.equals(getDefaultName())) { setDefaultName(lexname.module); } break; case IDENTIFIER: lexname = new LexNameToken(getDefaultName(), (LexIdentifierToken) token); break; default: throw new Exception("Expecting trace name"); } ANamedTraceDefinition tracedef = findTraceDefinition(lexname); if (tracedef == null) { throw new Exception("Trace " + lexname + " not found"); } TestSequence tests = null; Context ctxt = null; ctxt = getInitialTraceContext(tracedef, debug); tests = ctxt.assistantFactory.createANamedTraceDefinitionAssistant().getTests(tracedef, ctxt, subset, type, seed); boolean wasDBGP = Settings.usingDBGP; boolean wasCMD = Settings.usingCmdLine; if (!debug) { Settings.usingCmdLine = false; Settings.usingDBGP = false; } if (writer == null) { writer = Console.out; } if (testNo > tests.size()) { throw new Exception("Trace " + lexname + " only has " + tests.size() + " tests"); } int n = 1; boolean failed = false; writer.println("Generated " + tests.size() + " tests"); for (CallSequence test : tests) { if (testNo > 0 && n != testNo) { n++; continue; } INode traceContainer; Environment rootEnv; if (this instanceof ClassInterpreter) { traceContainer = tracedef.getClassDefinition(); rootEnv = new PrivateClassEnvironment(getAssistantFactory(), tracedef.getClassDefinition(), getGlobalEnvironment()); } else { traceContainer = tracedef.parent(); if(((AModuleModules)traceContainer).getIsFlat()) { //search for the combined module for(AModuleModules m : ((ModuleInterpreter)this).modules) { if(m instanceof CombinedDefaultModule) { traceContainer = m; break; } } } rootEnv = new ModuleEnvironment(getAssistantFactory(), (AModuleModules) traceContainer); } List<Object> result = new Vector<>(); try { typeCheck(traceContainer, this, test, rootEnv); } catch (Exception e) { result.add(e); result.add(Verdict.FAILED); failed = true; } // Bodge until we figure out how to not have explicit op names. String clean = test.toString().replaceAll("\\.\\w+`", "."); if (test.getFilter() > 0) { writer.println("Test " + n + " = " + clean); writer.println("Test " + n + " FILTERED by test " + test.getFilter()); } else { if(!failed) { // Initialize completely between every run... init(ctxt.threadState.dbgp); result = runOneTrace(tracedef, test, debug); } tests.filter(result, test, n); writer.println("Test " + n + " = " + clean); writer.println("Result = " + result); if (result.lastIndexOf(Verdict.PASSED) == -1) { failed = true; // Not passed => failed. } } n++; } init(null); Settings.usingCmdLine = wasCMD; Settings.usingDBGP = wasDBGP; return !failed; } /** * type check a test * * @param classdef * @param interpreter * @param test * @throws AnalysisException * @throws Exception */ public static void typeCheck(INode classdef, Interpreter interpreter, CallSequence test, Environment outer) throws AnalysisException, Exception { FlatEnvironment env = null; if (classdef instanceof SClassDefinition) { env = new FlatEnvironment(interpreter.getAssistantFactory(), classdef.apply(interpreter.getAssistantFactory().getSelfDefinitionFinder()), outer); } else { List<PDefinition> defs = new Vector<>(); if(classdef instanceof AModuleModules) { defs.addAll(((AModuleModules) classdef).getDefs()); } env = new FlatEnvironment(interpreter.getAssistantFactory(), defs, outer); } for (int i = 0; i < test.size(); i++) { PStm statement = test.get(i); if (statement instanceof TraceVariableStatement) { ((TraceVariableStatement) statement).typeCheck(env, NameScope.NAMESANDSTATE); } else { statement = statement.clone(); test.set(i, statement); interpreter.typeCheck(statement, env); } } } abstract public List<Object> runOneTrace(ANamedTraceDefinition tracedef, CallSequence test, boolean debug) throws AnalysisException; abstract public Context getInitialTraceContext( ANamedTraceDefinition tracedef, boolean debug) throws ValueException, AnalysisException; }