package com.sap.runlet.abstractinterpreter; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; import org.eclipse.emf.ecore.EObject; import org.omg.CORBA.NamedValue; import com.sap.runlet.abstractinterpreter.objects.ClassTypedObject; import com.sap.runlet.abstractinterpreter.objects.RunletObject; /** * Holds one frame of a call stack of an execution thread corresponding to a {@link Block} * execution. This stack contains all variables that are declared in the block and are * already in scope (variables are in scope only after their declaration in the block).<p> * * Stack frames that represent executions of nested blocks (such as that of a loop * or an if/else statement) know their parent frame which is then also used to * resolve variables that are not in scope for the current innermost block. The * outermost block's frame that corresponds to a method or function invocation does * not have a parent as no named value from outside such a scope is mapped into the * method's or function's scope.<p> * * The {@link BlockInterpreter} works in lock-step with this class to announce * block entries and exits.<p> * * TODO give stack frames names; creators then can specify them, e.g., when invoking something or creating a thread * * @author Axel Uhl (D043530) * */ public class StackFrame<LinkEndMetaObject, TypeUsage, ClassUsage extends TypeUsage, SignatureImplementationType> { private StackFrame<LinkEndMetaObject, TypeUsage, ClassUsage, SignatureImplementationType> scopeParent; private Map<String, RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage>> variableValues; /** * In debug mode, holds the signature whose implementation is currently being executed. */ private SignatureImplementationType currentlyExecutingImplementationOf; /** * In debug mode, the {@link DebugSession} will maintain the "instruction pointer" which is the * model element currently being evaluated */ private EObject currentlyEvaluating; /** * The <tt>this</tt> pointer of the stack frame in element 0; the array is <tt>null</tt> * if this frame is not for a method call but, e.g., a function call, or if this frame is for a nested * block. This is different from a valid array holding the value <tt>null</tt> in element 0 * which encodes <tt>this==null</tt>. */ private ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage>[] thiz; /** * Creates a new stack frame for a method or function invocation with no parent stack * for name resolution. */ public StackFrame() { variableValues = new HashMap<String, RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage>>(); } /** * Create a stack frame that has <tt>parent</tt> as its parent stack frame. This constructor * is expected to be used when a nested block inside a method or function execution begins * to execute. */ public StackFrame(StackFrame<LinkEndMetaObject, TypeUsage, ClassUsage, SignatureImplementationType> parent) { this(); this.scopeParent = parent; } @SuppressWarnings("unchecked") public StackFrame<LinkEndMetaObject, TypeUsage, ClassUsage, SignatureImplementationType> clone() { StackFrame<LinkEndMetaObject, TypeUsage, ClassUsage, SignatureImplementationType> result = new StackFrame<LinkEndMetaObject, TypeUsage, ClassUsage, SignatureImplementationType>(); result.scopeParent = getScopeParent(); result.variableValues = getVariableValues(); if (getThis() != null) { result.thiz = (ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage>[]) new ClassTypedObject<?, ?, ?>[] { getThis() }; } return result; } public void setCurrentlyEvaluating(EObject element) { currentlyEvaluating = element; } public EObject getCurrentlyEvaluating() { return currentlyEvaluating; } /** * Tries to locate the variable in this and the connected scope parent frames. If the entry is found, * it is updated by the <tt>value</tt> provided. Otherwise, the variable is added to this frame and * set to <tt>value</tt>. */ public void setValue(String variableName, RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage> value) { if (variableName == null) { throw new RuntimeException("Cannot create unnamed variable on StackFrame"); } if (value == null) { throw new RuntimeException("Cannot create variable with value <null> on StackFrame"); } StackFrame<LinkEndMetaObject, TypeUsage, ClassUsage, SignatureImplementationType> container = this; while (container != null && !container.getVariableValues().containsKey(variableName)) { container = container.getScopeParent(); } if (container == null) { // not found in this or parent frames; add to this frame getVariableValues().put(variableName, value); } else { container.getVariableValues().put(variableName, value); } } /** * Stores the variable into this particular frame. This will hide any definitions under the same * key in any parent frame. If a definition by that key is already known by this very frame * (not ascending to parent frames), an exception will be thrown. */ public void enterValue(String variableName, RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage> value) { if (variableName == null) { throw new RuntimeException("Cannot create unnamed variable on StackFrame"); } if (value == null) { throw new RuntimeException("Cannot create variable with value <null> on StackFrame"); } if (getVariableValues().containsKey(variableName)) { throw new RuntimeException("Cannot enter the same variable twice into the same StackFrame"); } setOrEnterValue(variableName, value); } /** * Stores the variable into this particular frame. This will hide any definitions under the same * key in any parent frame. If a definition by that key is already known by this very frame * (not ascending to parent frames), its value will be overwritten and no exception will be thrown * (as opposed to the behavior of {@link #enterValue}, which would throw an exception in this case). */ public void setOrEnterValue(String variableName, RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage> value) { if (variableName == null) { throw new RuntimeException("Cannot create unnamed variable on StackFrame"); } if (value == null) { throw new RuntimeException("Cannot create variable with value <null> on StackFrame"); } getVariableValues().put(variableName, value); } /** * Retrieves the value for <tt>variable</tt> from this stack frame. If the variable * is not in scope in the current frame, the parent frame stack is checked one after the * other until a value is found or the end of the stack has been reached. In that case, * a {@link RuntimeException} is thrown because the value is undefined. */ public RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage> getValue(String variableName) { RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage> result; if (variableName != null && getVariableValues().containsKey(variableName)) { result = getVariableValues().get(variableName); } else { if (variableName != null && getScopeParent() != null) { result = getScopeParent().getValue(variableName); } else { throw new RuntimeException("No value defined for "+((variableName==null)?"<null> variable":variableName)); } } return result; } /** * For all variables for which {@link #getValue(NamedValue)} will return a non-<tt>null</tt> * result, either from this frame or from one of its scope parents, adds the variable name and * its value to the result map. Variables from scope parents that have an equally-named definition * in a lower stack will not be added. */ public Map<String, RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage>> getAllVisibleVariableValues() { Map<String, RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage>> result = new LinkedHashMap<String, RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage>>(); if (hasThis()) { result.put("this", getThis()); } result.putAll(getVariableValues()); if (getScopeParent() != null) { Map<String, RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage>> parentScopeVariableValues = getScopeParent().getAllVisibleVariableValues(); for (String parentVariableName : parentScopeVariableValues.keySet()) { if (!result.containsKey(parentVariableName)) { result.put(parentVariableName, parentScopeVariableValues.get(parentVariableName)); } } } return result; } /** * For all variables for which {@link #getValue(NamedValue)} will return a non-<tt>null</tt> * result, either from this frame or from one of its scope parents, adds the variable name to * the result. Variables from scope parents that have an equally-named definition in a lower * stack will not be added. */ public Set<String> getAllVisibleVariableNames() { Set<String> result = new LinkedHashSet<String>(); if (hasThis()) { result.add("this"); } result.addAll(getVariableValues().keySet()); if (getScopeParent() != null) { result.addAll(getScopeParent().getAllVisibleVariableNames()); } return result; } private Map<String, RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage>> getVariableValues() { return variableValues; } public ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage> getThis() { ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage> result; if (thiz == null) { if (getScopeParent() != null) { result = getScopeParent().getThis(); } else { throw new RuntimeException("No value defined for this"); } } else { result = thiz[0]; } return result; } /** * Checks if a <tt>this</tt> value is defined for this frame, including looking at this frame's * scope parents. */ private boolean hasThis() { boolean result = false; if (thiz == null) { if (getScopeParent() != null) { result = getScopeParent().hasThis(); } } else { result = true; } return result; } @SuppressWarnings("unchecked") public void setThis(ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage> thiz) { this.thiz = (ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage>[]) new ClassTypedObject<?, ?, ?>[] { thiz }; } /** * Tells if this frame resorts to a parent frame recursively to resolve elements it does not * hold itself. This is used for language constructs where scopes can be nested and still see * elements from their enclosing scopes. * <p> * * Both, this frame and the scope parent will be on the interpreter's * {@link RiverInterpreter#getCallstack() callstack}. */ public StackFrame<LinkEndMetaObject, TypeUsage, ClassUsage, SignatureImplementationType> getScopeParent() { return scopeParent; } public String toString() { StringBuilder result = new StringBuilder(); if (thiz != null) { result.append("this="); result.append(thiz[0]); result.append('\n'); } for (String variableName : variableValues.keySet()) { assert variableName != null; result.append(variableName); result.append('='); result.append(variableValues.get(variableName)); result.append('\n'); } return result.toString(); } public String renderAsString() { StringBuilder str = new StringBuilder(); str.append("Frame:\n"); str.append(this); if (scopeParent != null) { str.append("Parent:\n"); str.append(getScopeParent().toString()); } return str.toString(); } /** * Checks if the {@link #currentlyExecutingImplementationOf} is set to a non-<tt>null</tt> * value here and if so, returns it. If not, checks up the {@link #scopeParent scope parent} * hierarchy. */ public SignatureImplementationType getCurrentlyExecutingImplementation() { SignatureImplementationType result = currentlyExecutingImplementationOf; if (result == null && getScopeParent() != null) { result = getScopeParent().getCurrentlyExecutingImplementation(); } return result; } public void setCurrentlyExecutingImplementation(SignatureImplementationType currentlyExecutingImplementation) { this.currentlyExecutingImplementationOf = currentlyExecutingImplementation; } }