/******************************************************************************* * * 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.values; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.Stack; import java.util.Vector; import org.overture.ast.analysis.AnalysisException; import org.overture.ast.assistant.pattern.PTypeList; import org.overture.ast.definitions.AExplicitFunctionDefinition; import org.overture.ast.definitions.AImplicitFunctionDefinition; import org.overture.ast.definitions.PDefinition; import org.overture.ast.definitions.SClassDefinition; import org.overture.ast.expressions.PExp; import org.overture.ast.factory.AstFactory; import org.overture.ast.intf.lex.ILexLocation; import org.overture.ast.intf.lex.ILexNameToken; import org.overture.ast.lex.LexNameToken; import org.overture.ast.modules.AModuleModules; import org.overture.ast.patterns.APatternListTypePair; import org.overture.ast.patterns.APatternTypePair; import org.overture.ast.patterns.PPattern; import org.overture.ast.types.AFunctionType; import org.overture.ast.types.PType; import org.overture.ast.util.Utils; import org.overture.config.Settings; import org.overture.interpreter.assistant.IInterpreterAssistantFactory; import org.overture.interpreter.assistant.type.PTypeAssistantInterpreter; import org.overture.interpreter.messages.Console; import org.overture.interpreter.runtime.ClassContext; import org.overture.interpreter.runtime.ClassInterpreter; import org.overture.interpreter.runtime.Context; import org.overture.interpreter.runtime.ContextException; import org.overture.interpreter.runtime.Interpreter; import org.overture.interpreter.runtime.ModuleInterpreter; import org.overture.interpreter.runtime.ObjectContext; import org.overture.interpreter.runtime.PatternMatchException; import org.overture.interpreter.runtime.RootContext; import org.overture.interpreter.runtime.StateContext; import org.overture.interpreter.runtime.ValueException; import org.overture.interpreter.runtime.VdmRuntime; import org.overture.interpreter.runtime.VdmRuntimeError; import org.overture.interpreter.solver.IConstraintSolver; import org.overture.interpreter.solver.SolverFactory; import org.overture.typechecker.TypeComparator; import org.overture.typechecker.assistant.pattern.PatternListTC; public class FunctionValue extends Value { private static final long serialVersionUID = 1L; public final ILexLocation location; public final String name; public NameValuePairList typeValues; public AFunctionType type; public final List<List<PPattern>> paramPatternList; public final PExp body; public final FunctionValue precondition; public final FunctionValue postcondition; public final Context freeVariables; // Causes parameter assignments to check their invariants (if any). // This is set to false for inv_() functions, which cannot check them. private final boolean checkInvariants; // Measure function value, if any private ILexNameToken measureName = null; private FunctionValue measure = null; private Map<Long, Stack<Value>> measureValues = null; private Set<Long> measuringThreads = null; private Set<Long> callingThreads = null; private ValueList curriedArgs = null; private boolean isMeasure = false; public ObjectValue self = null; public boolean isStatic = false; public boolean uninstantiated = false; private SClassDefinition classdef = null; final private APatternTypePair result; private FunctionValue(ILexLocation location, String name, AFunctionType type, List<List<PPattern>> paramPatternList, PExp body, FunctionValue precondition, FunctionValue postcondition, Context freeVariables, boolean checkInvariants, ValueList curriedArgs, ILexNameToken measureName, Map<Long, Stack<Value>> measureValues, APatternTypePair result) { this.location = location; this.name = name; this.typeValues = null; this.type = type; this.paramPatternList = paramPatternList; this.body = body; this.precondition = precondition; this.postcondition = postcondition; this.freeVariables = freeVariables; this.checkInvariants = checkInvariants; this.curriedArgs = curriedArgs; this.result = result; if (Settings.measureChecks && measureName != null) { this.measureName = measureName; this.measureValues = measureValues; // NB. a copy of the base FunctionValue's } } public FunctionValue(ILexLocation location, String name, AFunctionType type, PatternListTC paramPatterns, PExp body, Context freeVariables) { this.location = location; this.name = name; this.typeValues = null; this.type = type; this.paramPatternList = new Vector<List<PPattern>>(); this.body = body; this.precondition = null; this.postcondition = null; this.freeVariables = freeVariables; this.checkInvariants = true; this.result = null; paramPatternList.add(paramPatterns); } public FunctionValue(AExplicitFunctionDefinition def, FunctionValue precondition, FunctionValue postcondition, Context freeVariables) { this.location = def.getLocation(); this.name = def.getName().getName(); this.typeValues = null; this.type = (AFunctionType) def.getType(); this.paramPatternList = def.getParamPatternList(); this.body = def.getBody(); this.precondition = precondition; this.postcondition = postcondition; this.freeVariables = freeVariables; this.checkInvariants = !def.getIsTypeInvariant(); this.classdef = def.getClassDefinition(); this.result = null; if (Settings.measureChecks && def.getMeasureDef() != null) { measureName = def.getMeasureDef().getName(); measureValues = Collections.synchronizedMap(new HashMap<Long, Stack<Value>>()); } } public FunctionValue(AImplicitFunctionDefinition def, FunctionValue precondition, FunctionValue postcondition, Context freeVariables) { this.location = def.getLocation(); this.name = def.getName().getName(); this.typeValues = null; this.type = (AFunctionType) def.getType(); this.paramPatternList = new Vector<List<PPattern>>(); PatternListTC plist = Interpreter.getInstance().getAssistantFactory().createPatternList(); for (APatternListTypePair ptp : def.getParamPatterns()) { plist.addAll(ptp.getPatterns()); } this.paramPatternList.add(plist); this.body = def.getBody(); this.result = def.getResult(); this.precondition = precondition; this.postcondition = postcondition; this.freeVariables = freeVariables; this.checkInvariants = true; this.classdef = def.getClassDefinition(); if (Settings.measureChecks && def.getMeasureDef() != null) { measureName = def.getMeasureDef().getName(); measureValues = Collections.synchronizedMap(new HashMap<Long, Stack<Value>>()); } } public FunctionValue(IInterpreterAssistantFactory af, AImplicitFunctionDefinition fdef, PTypeList actualTypes, FunctionValue precondition, FunctionValue postcondition, Context freeVariables) { this(fdef, precondition, postcondition, freeVariables); this.typeValues = new NameValuePairList(); this.type = af.createAImplicitFunctionDefinitionAssistant().getType(fdef, actualTypes); Iterator<PType> ti = actualTypes.iterator(); for (ILexNameToken pname : fdef.getTypeParams()) { PType ptype = ti.next(); typeValues.add(new NameValuePair(pname, new ParameterValue(ptype))); } } public FunctionValue(IInterpreterAssistantFactory af, AExplicitFunctionDefinition fdef, PTypeList actualTypes, FunctionValue precondition, FunctionValue postcondition, Context freeVariables) { this(fdef, precondition, postcondition, freeVariables); this.typeValues = new NameValuePairList(); this.type = af.createAExplicitFunctionDefinitionAssistant().getType(fdef, actualTypes); Iterator<PType> ti = actualTypes.iterator(); for (ILexNameToken pname : fdef.getTypeParams()) { PType ptype = ti.next(); typeValues.add(new NameValuePair(pname, new ParameterValue(ptype))); } } // This constructor is used by IterFunctionValue and CompFunctionValue // The methods which matter are overridden in those classes. public FunctionValue(ILexLocation location, AFunctionType type, String name) { this.location = location; this.name = name; this.typeValues = null; this.type = type; this.paramPatternList = null; this.body = null; this.precondition = null; this.postcondition = null; this.freeVariables = null; this.checkInvariants = true; this.result = null; } @Override public String toString() { return type.toString(); } public Value eval(ILexLocation from, Value arg, Context ctxt) throws AnalysisException { ValueList args = new ValueList(arg); return eval(from, args, ctxt, null); } public Value eval(ILexLocation from, ValueList argValues, Context ctxt) throws AnalysisException { return eval(from, argValues, ctxt, null); } public void setSelf(ObjectValue self) { if (!isStatic) { this.self = self; if (measure != null) { measure.setSelf(self); } } } public void setClass(SClassDefinition classdef) { this.classdef = classdef; } public Value eval(ILexLocation from, ValueList argValues, Context ctxt, Context sctxt) throws AnalysisException { if (uninstantiated) { abort(3033, "Polymorphic function has not been instantiated: " + name, ctxt); } List<PPattern> paramPatterns = paramPatternList.get(0); RootContext evalContext = newContext(from, toTitle(), ctxt, sctxt); if (typeValues != null) { // Add any @T type values, for recursive polymorphic functions evalContext.putList(typeValues); } if (argValues.size() != paramPatterns.size()) { VdmRuntimeError.abort(type.getLocation(), 4052, "Wrong number of arguments passed to " + name, ctxt); } Iterator<Value> valIter = argValues.iterator(); Iterator<PType> typeIter = type.getParameters().iterator(); NameValuePairMap args = new NameValuePairMap(); for (PPattern p : paramPatterns) { Value pv = valIter.next(); if (checkInvariants) // Don't even convert invariant arg values { pv = pv.convertTo(typeIter.next(), ctxt); } try { for (NameValuePair nvp : ctxt.assistantFactory.createPPatternAssistant().getNamedValues(p, pv, ctxt)) { Value v = args.get(nvp.name); if (v == null) { args.put(nvp); } else // Names match, so values must also { if (!v.equals(nvp.value)) { abort(4053, "Parameter patterns do not match arguments", ctxt); } } } } catch (PatternMatchException e) { abort(e.number, e, ctxt); } } if (self != null) { evalContext.put(new LexNameToken(location.getModule(), "self", location), self); } evalContext.putAll(args); if (paramPatternList.size() == 1) { if (precondition != null && Settings.prechecks) { // Evaluate pre/post in evalContext as it includes the type // variables, if any. We disable the swapping and time (RT) // as precondition checks should be "free". try { evalContext.threadState.setAtomic(true); evalContext.setPrepost(4055, "Precondition failure: "); precondition.eval(from, argValues, evalContext); } finally { evalContext.setPrepost(0, null); evalContext.threadState.setAtomic(false); } } Long tid = Thread.currentThread().getId(); if (isMeasure) { if (measuringThreads.contains(tid)) // We are measuring on this thread { if (!callingThreads.add(tid)) // And we've been here already { abort(4148, "Measure function is called recursively: " + name, evalContext); } } } if (measureName != null) { if (measure == null) { measure = evalContext.lookup(measureName).functionValue(ctxt); if (typeValues != null) // Function is polymorphic, so measure copies type args { measure = (FunctionValue) measure.clone(); measure.uninstantiated = false; measure.typeValues = typeValues; } measure.measuringThreads = Collections.synchronizedSet(new HashSet<Long>()); measure.callingThreads = Collections.synchronizedSet(new HashSet<Long>()); measure.isMeasure = true; } // If this is a curried function, then the measure is called with all of the // previously applied argument values, in addition to the argValues. ValueList measureArgs = null; if (curriedArgs == null) { measureArgs = argValues; } else { measureArgs = new ValueList(); measureArgs.addAll(curriedArgs); // Previous args measureArgs.addAll(argValues); // Final args } // We disable the swapping and time (RT) as measure checks should be "free". Value mv; try { measure.measuringThreads.add(tid); evalContext.threadState.setAtomic(true); mv = measure.eval(measure.location, measureArgs, evalContext).deref(); } finally { evalContext.threadState.setAtomic(false); measure.measuringThreads.remove(tid); } Stack<Value> stack = measureValues.get(tid); if (stack == null) { stack = new Stack<Value>(); measureValues.put(tid, stack); } if (!stack.isEmpty()) { Value old = stack.peek(); // Previous value if (old != null && mv.compareTo(old) >= 0) // Not decreasing order { abort(4146, "Measure failure: " + name + Utils.listToString("(", argValues, ", ", ")") + ", measure " + measure.name + ", current " + mv + ", previous " + old, evalContext); } } stack.push(mv); } Value rv = null; if (body == null) { IConstraintSolver solver = SolverFactory.getSolver(Settings.dialect); if (solver != null) { rv = invokeSolver(ctxt, evalContext, args, solver); } else { abort(4051, "Cannot apply implicit function: " + name, ctxt); } } else { try { evalContext.threadState.setAtomic(true); evalContext.threadState.setPure(true); rv = body.apply(VdmRuntime.getExpressionEvaluator(), evalContext).convertTo(type.getResult(), evalContext); } catch (AnalysisException e) { if (e instanceof ValueException) { throw (ValueException) e; } e.printStackTrace(); } finally { evalContext.threadState.setAtomic(false); evalContext.threadState.setPure(false); } } if (ctxt.prepost > 0) // Note, caller's context is checked { if (!rv.boolValue(ctxt)) { // Note that this calls getLocation to find out where the body // wants to report its location for this error - this may be an // errs clause in some circumstances. throw new ContextException(ctxt.prepost, ctxt.prepostMsg + name, body.getLocation(), evalContext); } } if (postcondition != null && Settings.postchecks) { ValueList postArgs = new ValueList(argValues); postArgs.add(rv); // Evaluate pre/post in evalContext as it includes the type // variables, if any. We disable the swapping and time (RT) // as postcondition checks should be "free". try { evalContext.threadState.setAtomic(true); evalContext.setPrepost(4056, "Postcondition failure: "); postcondition.eval(from, postArgs, evalContext); } finally { evalContext.setPrepost(0, null); evalContext.threadState.setAtomic(false); } } if (measure != null) { measureValues.get(tid).pop(); } if (isMeasure) { callingThreads.remove(tid); } return rv; } else // This is a curried function { if (type.getResult() instanceof AFunctionType) { // If a curried function has a pre/postcondition, then the // result of a partial application has a pre/post condition // with its free variables taken from the environment (so // that parameters passed are fixed in subsequent applies). FunctionValue newpre = null; if (precondition != null) { newpre = precondition.curry(evalContext); } FunctionValue newpost = null; if (postcondition != null) { newpost = postcondition.curry(evalContext); } if (freeVariables != null) { evalContext.putAll(freeVariables); } // Curried arguments are collected so that we can invoke any measure functions // once we reach the final apply that does not return a function. ValueList argList = new ValueList(); if (curriedArgs != null) { argList.addAll(curriedArgs); } argList.addAll(argValues); FunctionValue rv = new FunctionValue(location, "curried", (AFunctionType) type.getResult(), paramPatternList.subList(1, paramPatternList.size()), body, newpre, newpost, evalContext, false, argList, measureName, measureValues, result); rv.setSelf(self); rv.typeValues = typeValues; return rv; } VdmRuntimeError.abort(type.getLocation(), 4057, "Curried function return type is not a function", ctxt); return null; } } public Value invokeSolver(Context ctxt, RootContext argContext, NameValuePairMap args, IConstraintSolver solver) throws ValueException { Value rv = null; try { Map<String, String> argExps = new HashMap<String, String>(); for (Entry<ILexNameToken, Value> argVal : args.entrySet()) { argExps.put(argVal.getKey().getName(), argVal.getValue().toString()); } Map<String, String> stateExps = new HashMap<String, String>(); Interpreter interpreter = Interpreter.getInstance(); List<PDefinition> allDefs = new Vector<PDefinition>(); if (interpreter instanceof ClassInterpreter) { for (SClassDefinition c : ((ClassInterpreter) interpreter).getClasses()) { allDefs.addAll(c.getDefinitions()); } } else if (interpreter instanceof ModuleInterpreter) { for (AModuleModules c : ((ModuleInterpreter) interpreter).getModules()) { allDefs.addAll(c.getDefs()); } } PExp res = solver.solve(allDefs, this.name, this.postcondition.body, result, stateExps, argExps, Console.out, Console.err); rv = res.apply(VdmRuntime.getExpressionEvaluator(), argContext); } catch (Exception e) { e.printStackTrace(); abort(4066, "Cannot call implicit operation: " + name, ctxt); } return rv; } private RootContext newContext(ILexLocation from, String title, Context ctxt, Context sctxt) { RootContext evalContext; if (self != null) { evalContext = new ObjectContext(Interpreter.getInstance().getAssistantFactory(), from, title, freeVariables, ctxt, self); } else if (classdef != null) { evalContext = new ClassContext(Interpreter.getInstance().getAssistantFactory(), from, title, freeVariables, ctxt, classdef); } else { evalContext = new StateContext(Interpreter.getInstance().getAssistantFactory(), from, title, freeVariables, ctxt, sctxt); } return evalContext; } @Override public boolean equals(Object other) { if (other instanceof Value) { Value val = ((Value) other).deref(); if (val instanceof CompFunctionValue || val instanceof IterFunctionValue) { return false; // Play safe - we can't really tell } else if (val instanceof FunctionValue) { FunctionValue ov = (FunctionValue) val; return ov.type.equals(type) && // Param and result types same ov.body.equals(body); // Not ideal - a string comparison in fact } } return false; } @Override public int hashCode() { return type.hashCode() + body.hashCode(); } @Override public String kind() { return "function"; } @Override public FunctionValue functionValue(Context ctxt) { return this; } @Override protected Value convertValueTo(PType to, Context ctxt, Set<PType> done) throws AnalysisException { PTypeAssistantInterpreter assistant = ctxt.assistantFactory.createPTypeAssistant(); if (assistant.isFunction(to)) { if (type.equals(to) || assistant.isUnknown(to)) { return this; } else { AFunctionType restrictedType = assistant.getFunction(to); if (type.equals(restrictedType)) { return this; } else { TypeComparator tc = ctxt.assistantFactory.getTypeComparator(); List<PType> domain = tc.narrowest(type.getParameters(), restrictedType.getParameters()); PType range = tc.narrowest(type.getResult(), restrictedType.getResult()); AFunctionType newType = AstFactory.newAFunctionType(location, true, domain, range); // Create a new function with the narrowest domain/range. FunctionValue restricted = new FunctionValue(location, name, newType, paramPatternList, body, precondition, postcondition, freeVariables, checkInvariants, curriedArgs, measureName, measureValues, result); restricted.typeValues = typeValues; return restricted; } } } else { return super.convertValueTo(to, ctxt, done); } } private FunctionValue curry(Context newFreeVariables) { // Remove first set of parameters, and set the free variables instead. // And adjust the return type to be the result type (a function). return new FunctionValue(location, name, (AFunctionType) type.getResult(), paramPatternList.subList(1, paramPatternList.size()), body, precondition, postcondition, newFreeVariables, false, null, null, null, result); } @Override public Object clone() { FunctionValue copy = new FunctionValue(location, name, type, paramPatternList, body, precondition, postcondition, freeVariables, checkInvariants, curriedArgs, measureName, measureValues, result); copy.typeValues = typeValues; return copy; } public String toTitle() { List<PPattern> paramPatterns = paramPatternList.get(0); return name + Utils.listToString("(", paramPatterns, ", ", ")"); } }