diff --git a/plugin/src/org/aavso/tools/vstar/external/plugin/VeLaObservationTransformer.java b/plugin/src/org/aavso/tools/vstar/external/plugin/VeLaObservationTransformer.java index 3c9fb336..9376579c 100644 --- a/plugin/src/org/aavso/tools/vstar/external/plugin/VeLaObservationTransformer.java +++ b/plugin/src/org/aavso/tools/vstar/external/plugin/VeLaObservationTransformer.java @@ -45,189 +45,171 @@ /** * This plugin allows a VeLa function to be used for observation transformation. */ -public class VeLaObservationTransformer extends - ObservationTransformerPluginBase { - - private VeLaInterpreter vela; - private boolean shouldInvokeDialog; - private boolean firstInvocation; - - @Override - public String getDisplayName() { - return "VeLa Observation Transformer"; - } - - @Override - public String getDescription() { - return "VeLa observation transformation tool"; - } - - /** - * @see org.aavso.tools.vstar.plugin.IPlugin#getDocName() - */ - @Override - public String getDocName() { - return "VeLa Observation Transformer Plug-In.pdf"; - } - - @Override - public IUndoableAction createAction(ISeriesInfoProvider seriesInfo, - Set series) { - return new IUndoableAction() { - - private Map> mags = new HashMap>(); - private Map> errs = new HashMap>(); - - @Override - public boolean execute(UndoableActionType type) { - boolean ok = true; - - if (firstInvocation) { - Mediator.getInstance().getNewStarNotifier() - .addListener(getNewStarListener()); - - firstInvocation = false; - } - - switch (type) { - case DO: - vela = new VeLaInterpreter(); - - Pair pair = invokeDialog(vela); - - ok = pair.first; - - if (ok && pair.second.trim().length() != 0) { - vela.program(pair.second); - } else { - break; - } - // Note: there being no unconditional break here is on purpose! - case REDO: - for (SeriesType seriesType : series) { - for (ValidObservation ob : seriesInfo - .getObservations(seriesType)) { - // Store old magnitude for undo - Magnitude magnitude = ob.getMagnitude(); - - if (mags.get(seriesType) == null) { - mags.put(seriesType, new ArrayList()); - } - mags.get(seriesType).add(magnitude.getMagValue()); - - if (errs.get(seriesType) == null) { - errs.put(seriesType, new ArrayList()); - } - errs.get(seriesType) - .add(magnitude.getUncertainty()); - - // Push an environment that makes the - // observation available to VeLa code... - vela.pushEnvironment(new VeLaValidObservationEnvironment( - ob)); - - // ...and call the function with the current - // observation's magnitude and error values. - String funCall = String.format("do()", - magnitude.getMagValue(), - magnitude.getUncertainty()); - - Optional result = vela.program(funCall); - - if (result.isPresent() - && result.get().getType() == Type.LIST) { - Operand op = result.get(); - - if (op.listVal().size() == 2) { - boolean bothReal = op - .listVal() - .stream() - .allMatch( - x -> x.convert(Type.REAL) == Type.REAL); - - if (bothReal) { - double mag = op.listVal().get(0) - .doubleVal(); - double err = op.listVal().get(1) - .doubleVal(); - ob.setMagnitude(new Magnitude(mag, err)); - } else { - // Need two real numbers - ok = false; - } - } else { - // Need two real numbers - ok = false; - } - } else { - // Need (two real numbers in) a list - ok = false; - } - - if (!ok) { - ok = false; - MessageBox.showErrorDialog("VeLa Error", - "Expected a 2 element result list"); - } - - // Push current observation's environment. - vela.popEnvironment(); - } - } - break; - case UNDO: - // Undo by restoring magnitude and error values. - for (SeriesType seriesType : series) { - for (int i = 0; i < seriesInfo.getObservations( - seriesType).size(); i++) { - double mag = mags.get(seriesType).get(i); - double err = errs.get(seriesType).get(i); - ValidObservation ob = seriesInfo.getObservations( - seriesType).get(i); - ob.setMagnitude(new Magnitude(mag, err)); - } - } - break; - } - - return ok; - } - - @Override - public String getDisplayString() { - return "VeLa observation transformation"; - } - }; - } - - /** - * Get the new star listener for this plugin. - */ - protected Listener getNewStarListener() { - return new Listener() { - public void update(NewStarMessage info) { - shouldInvokeDialog = true; - } - - public boolean canBeRemoved() { - return false; - } - }; - } - - /** - * Invoke dialog to request magnitude shift value. - * - * @return A pair containing a Boolean and string: whether the dialog's OK - * button was clicked and the VeLa code string. - */ - private Pair invokeDialog(VeLaInterpreter vela) { - VeLaDialog velaDialog = new VeLaDialog( - "Define VeLa function: do():list"); - - boolean ok = !velaDialog.isCancelled(); - String code = velaDialog.getCode(); - - return new Pair(ok, code); - } +public class VeLaObservationTransformer extends ObservationTransformerPluginBase { + + private VeLaInterpreter vela; + private boolean shouldInvokeDialog; + private boolean firstInvocation; + + @Override + public String getDisplayName() { + return "VeLa Observation Transformer"; + } + + @Override + public String getDescription() { + return "VeLa observation transformation tool"; + } + + /** + * @see org.aavso.tools.vstar.plugin.IPlugin#getDocName() + */ + @Override + public String getDocName() { + return "VeLa Observation Transformer Plug-In.pdf"; + } + + @Override + public IUndoableAction createAction(ISeriesInfoProvider seriesInfo, Set series) { + return new IUndoableAction() { + + private Map> mags = new HashMap>(); + private Map> errs = new HashMap>(); + + @Override + public boolean execute(UndoableActionType type) { + boolean ok = true; + + if (firstInvocation) { + Mediator.getInstance().getNewStarNotifier().addListener(getNewStarListener()); + + firstInvocation = false; + } + + switch (type) { + case DO: + vela = new VeLaInterpreter(); + + Pair pair = invokeDialog(vela); + + ok = pair.first; + + if (ok && pair.second.trim().length() != 0) { + vela.program(pair.second); + } else { + break; + } + // Note: there being no unconditional break here is on purpose! + case REDO: + for (SeriesType seriesType : series) { + for (ValidObservation ob : seriesInfo.getObservations(seriesType)) { + // Store old magnitude for undo + Magnitude magnitude = ob.getMagnitude(); + + if (mags.get(seriesType) == null) { + mags.put(seriesType, new ArrayList()); + } + mags.get(seriesType).add(magnitude.getMagValue()); + + if (errs.get(seriesType) == null) { + errs.put(seriesType, new ArrayList()); + } + errs.get(seriesType).add(magnitude.getUncertainty()); + + // Push an environment that makes the + // observation available to VeLa code... + vela.pushEnvironment(new VeLaValidObservationEnvironment(ob)); + + // ...and call the function with the current + // observation's magnitude and error values. + String funCall = String.format("do()", magnitude.getMagValue(), magnitude.getUncertainty()); + + Optional result = vela.program(funCall); + + if (result.isPresent() && result.get().getType() == Type.LIST) { + Operand op = result.get(); + + if (op.listVal().size() == 2) { + boolean bothReal = op.listVal().stream() + .allMatch(x -> x.convert(Type.REAL).getType() == Type.REAL); + + if (bothReal) { + double mag = op.listVal().get(0).doubleVal(); + double err = op.listVal().get(1).doubleVal(); + ob.setMagnitude(new Magnitude(mag, err)); + } else { + // Need two real numbers + ok = false; + } + } else { + // Need two real numbers + ok = false; + } + } else { + // Need (two real numbers in) a list + ok = false; + } + + if (!ok) { + ok = false; + MessageBox.showErrorDialog("VeLa Error", "Expected a 2 element result list"); + } + + // Push current observation's environment. + vela.popEnvironment(); + } + } + break; + case UNDO: + // Undo by restoring magnitude and error values. + for (SeriesType seriesType : series) { + for (int i = 0; i < seriesInfo.getObservations(seriesType).size(); i++) { + double mag = mags.get(seriesType).get(i); + double err = errs.get(seriesType).get(i); + ValidObservation ob = seriesInfo.getObservations(seriesType).get(i); + ob.setMagnitude(new Magnitude(mag, err)); + } + } + break; + } + + return ok; + } + + @Override + public String getDisplayString() { + return "VeLa observation transformation"; + } + }; + } + + /** + * Get the new star listener for this plugin. + */ + protected Listener getNewStarListener() { + return new Listener() { + public void update(NewStarMessage info) { + shouldInvokeDialog = true; + } + + public boolean canBeRemoved() { + return false; + } + }; + } + + /** + * Invoke dialog to request magnitude shift value. + * + * @return A pair containing a Boolean and string: whether the dialog's OK + * button was clicked and the VeLa code string. + */ + private Pair invokeDialog(VeLaInterpreter vela) { + VeLaDialog velaDialog = new VeLaDialog("Define VeLa function: do():list"); + + boolean ok = !velaDialog.isCancelled(); + String code = velaDialog.getCode(); + + return new Pair(ok, code); + } } diff --git a/src/org/aavso/tools/vstar/vela/FunctionExecutor.java b/src/org/aavso/tools/vstar/vela/FunctionExecutor.java index 8332d50f..bf973066 100644 --- a/src/org/aavso/tools/vstar/vela/FunctionExecutor.java +++ b/src/org/aavso/tools/vstar/vela/FunctionExecutor.java @@ -22,7 +22,6 @@ import java.util.Collections; import java.util.List; import java.util.Optional; -import java.util.stream.Collectors; /** * The base class for all function executors. Each subclass must implement @@ -30,227 +29,206 @@ */ public abstract class FunctionExecutor { - public static final List ANY_FORMALS = Collections.emptyList(); - public static final List NO_FORMALS = Collections.emptyList(); - public static final List NO_ACTUALS = new ArrayList(); - - protected Optional funcName; - protected List parameterTypes; - protected Optional returnType; - - protected Method method; - - /** - * Apply the function to the specified operands and return a result of the - * specified type. - * - * @param operands - * A list if operands to which the function is to be applied. - * @return The optional return value. - * @throws A - * VeLaEvalError if an error occurred during function - * evaluation. - */ - public abstract Optional apply(List operands) - throws VeLaEvalError; - - /** - * Constructor for functions with a corresponding Java method to invoke. - * - * @param funcName - * The function's name. - * @param method - * The corresponding Java method object. - * @param parameterTypes - * The function's parameter types. - * @param returnType - * The function's optional return type. - */ - public FunctionExecutor(Optional funcName, Method method, - List parameterTypes, Optional returnType) { - this.funcName = funcName; - this.method = method; - this.parameterTypes = parameterTypes; - this.returnType = returnType; - } - - /** - * Constructor - * - * @param funcName - * The function's name. - * @param parameterTypes - * The function's parameter types. - * @param returnType - * The function's return type. - */ - public FunctionExecutor(Optional funcName, - List parameterTypes, Optional returnType) { - this(funcName, null, parameterTypes, returnType); - } - - /** - * Constructor for zero-arity functions with a corresponding Java method to - * invoke. - * - * @param funcName - * The function's name. - * @param method - * The corresponding Java method object. - * @param parameterTypes - * The function's parameter types. - * @param returnType - * The function's return type. - */ - public FunctionExecutor(Optional funcName, Method method, - Optional returnType) { - this(funcName, method, NO_FORMALS, returnType); - } - - /** - * Constructor for zero-arity functions. - * - * @param funcName - * The function's name. - * @param parameterTypes - * The function's parameter types. - * @param returnType - * The function's return type. - */ - public FunctionExecutor(Optional funcName, Optional returnType) { - this(funcName, null, NO_FORMALS, returnType); - } - - /** - * Do the specified actual parameters conform to the function's formal - * parameter list? - * - * @param actualParameters - * A list of actual parameters (Operands). - * @return Whether or not the actuals conform to the formals. - */ - public boolean conforms(List actualParameters) { - boolean result = true; - - if (parameterTypes == ANY_FORMALS) { - result = true; - } else if (actualParameters.size() != parameterTypes.size()) { - result = false; - } else { - for (int i = 0; i < actualParameters.size(); i++) { - Type requiredType = parameterTypes.get(i); - if (actualParameters.get(i).convert(requiredType) != requiredType) { - result = false; - break; - } - } - } - - return result; - } - - /** - * @return the funcName - */ - public Optional getFuncName() { - return funcName; - } - - /** - * @return the method - */ - public Method getMethod() { - return method; - } - - /** - * @return the parameterTypes - */ - public List getParameterTypes() { - return parameterTypes; - } - - /** - * @return the returnType - */ - public Optional getReturnType() { - return returnType; - } - - /** - * @param returnType - * the returnType to set - */ - public void setReturnType(Optional returnType) { - this.returnType = returnType; - } - - @Override - public String toString() { - StringBuffer buf = new StringBuffer(); - - buf.append(String.format("%s (%s)", - funcName.isPresent() ? funcName.get() : "anonymous", - parameterTypes.toString().replace("[", "").replace("]", "") - .replace(",", ""))); - - if (returnType.isPresent()) { - buf.append(String.format(" : %s", returnType.get())); - } - - return buf.toString(); - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result - + ((funcName == null) ? 0 : funcName.hashCode()); - result = prime * result + ((method == null) ? 0 : method.hashCode()); - result = prime * result - + ((parameterTypes == null) ? 0 : parameterTypes.hashCode()); - result = prime * result - + ((returnType == null) ? 0 : returnType.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (!(obj instanceof FunctionExecutor)) { - return false; - } - FunctionExecutor other = (FunctionExecutor) obj; - if (funcName == null) { - if (other.funcName != null) { - return false; - } - } else if (!funcName.equals(other.funcName)) { - return false; - } - if (method == null) { - if (other.method != null) { - return false; - } - } else if (!method.equals(other.method)) { - return false; - } - if (parameterTypes == null) { - if (other.parameterTypes != null) { - return false; - } - } else if (!parameterTypes.equals(other.parameterTypes)) { - return false; - } - if (returnType != other.returnType) { - return false; - } - return true; - } + public static final List ANY_FORMALS = Collections.emptyList(); + public static final List NO_FORMALS = Collections.emptyList(); + public static final List NO_ACTUALS = new ArrayList(); + + protected Optional funcName; + protected List parameterTypes; + protected Optional returnType; + + protected Method method; + + /** + * Apply the function to the specified operands and return a result of the + * specified type. + * + * @param operands A list if operands to which the function is to be applied. + * @return The optional return value. + * @throws A VeLaEvalError if an error occurred during function evaluation. + */ + public abstract Optional apply(List operands) throws VeLaEvalError; + + /** + * Constructor for functions with a corresponding Java method to invoke. + * + * @param funcName The function's name. + * @param method The corresponding Java method object. + * @param parameterTypes The function's parameter types. + * @param returnType The function's optional return type. + */ + public FunctionExecutor(Optional funcName, Method method, List parameterTypes, + Optional returnType) { + this.funcName = funcName; + this.method = method; + this.parameterTypes = parameterTypes; + this.returnType = returnType; + } + + /** + * Constructor + * + * @param funcName The function's name. + * @param parameterTypes The function's parameter types. + * @param returnType The function's return type. + */ + public FunctionExecutor(Optional funcName, List parameterTypes, Optional returnType) { + this(funcName, null, parameterTypes, returnType); + } + + /** + * Constructor for zero-arity functions with a corresponding Java method to + * invoke. + * + * @param funcName The function's name. + * @param method The corresponding Java method object. + * @param parameterTypes The function's parameter types. + * @param returnType The function's return type. + */ + public FunctionExecutor(Optional funcName, Method method, Optional returnType) { + this(funcName, method, NO_FORMALS, returnType); + } + + /** + * Constructor for zero-arity functions. + * + * @param funcName The function's name. + * @param parameterTypes The function's parameter types. + * @param returnType The function's return type. + */ + public FunctionExecutor(Optional funcName, Optional returnType) { + this(funcName, null, NO_FORMALS, returnType); + } + + /** + * Do the specified actual parameters conform to the function's formal parameter + * list? If any operands are converted to a different type, they will be + * replaced with a new operand in the actual parameter list to prevent side + * effects. + * + * @param actualParameters A list of actual parameters (Operands). + * @return Whether or not the actuals conform to the formals. + */ + public boolean conforms(List actualParameters) { + boolean result = true; + + if (parameterTypes == ANY_FORMALS) { + result = true; + } else if (actualParameters.size() != parameterTypes.size()) { + result = false; + } else { + for (int i = 0; i < actualParameters.size(); i++) { + Type requiredType = parameterTypes.get(i); + Operand originalVal = actualParameters.get(i); + Operand convertedVal = originalVal.convert(requiredType); + if (convertedVal.getType() != requiredType) { + result = false; + break; + } else if (convertedVal != originalVal) { + actualParameters.set(i, convertedVal); + } + } + } + + return result; + } + + /** + * @return the funcName + */ + public Optional getFuncName() { + return funcName; + } + + /** + * @return the method + */ + public Method getMethod() { + return method; + } + + /** + * @return the parameterTypes + */ + public List getParameterTypes() { + return parameterTypes; + } + + /** + * @return the returnType + */ + public Optional getReturnType() { + return returnType; + } + + /** + * @param returnType the returnType to set + */ + public void setReturnType(Optional returnType) { + this.returnType = returnType; + } + + @Override + public String toString() { + StringBuffer buf = new StringBuffer(); + + buf.append(String.format("%s (%s)", funcName.isPresent() ? funcName.get() : "anonymous", + parameterTypes.toString().replace("[", "").replace("]", "").replace(",", ""))); + + if (returnType.isPresent()) { + buf.append(String.format(" : %s", returnType.get())); + } + + return buf.toString(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((funcName == null) ? 0 : funcName.hashCode()); + result = prime * result + ((method == null) ? 0 : method.hashCode()); + result = prime * result + ((parameterTypes == null) ? 0 : parameterTypes.hashCode()); + result = prime * result + ((returnType == null) ? 0 : returnType.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof FunctionExecutor)) { + return false; + } + FunctionExecutor other = (FunctionExecutor) obj; + if (funcName == null) { + if (other.funcName != null) { + return false; + } + } else if (!funcName.equals(other.funcName)) { + return false; + } + if (method == null) { + if (other.method != null) { + return false; + } + } else if (!method.equals(other.method)) { + return false; + } + if (parameterTypes == null) { + if (other.parameterTypes != null) { + return false; + } + } else if (!parameterTypes.equals(other.parameterTypes)) { + return false; + } + if (returnType != other.returnType) { + return false; + } + return true; + } } diff --git a/src/org/aavso/tools/vstar/vela/Operand.java b/src/org/aavso/tools/vstar/vela/Operand.java index 7f6a9cec..58e16140 100644 --- a/src/org/aavso/tools/vstar/vela/Operand.java +++ b/src/org/aavso/tools/vstar/vela/Operand.java @@ -212,50 +212,52 @@ public Object toObject() { } /** - * Convert this operand to the required type, if possible. + * Convert this operand to the required type, if possible, first making a copy + * if the required type is different from the current type. * * @param requiredType The required type. - * @return The converted type; will be unchanged if it matches the required + * @return The unchanged operand or a new operand that conforms to the required * type. - * @throws VeLaEvalError if the type cannot be converted. */ - public Type convert(Type requiredType) { + public Operand convert(Type requiredType) { + Operand operand = this; + if (!type.isComposite()) { if (type != requiredType) { if (type == Type.INTEGER && requiredType == Type.REAL) { - setType(Type.REAL); - setDoubleVal((double) intVal); - } else if (type.oneOf(Type.INTEGER, Type.REAL, Type.BOOLEAN) && requiredType == Type.STRING) { - convertToString(); + operand = new Operand(Type.REAL, (double) intVal); + } else if (type != Type.STRING && requiredType == Type.STRING) { + operand = operand.convertToString(); } } } - return type; + return operand; } /** * Convert this operand's type to string. */ - public void convertToString() { + public Operand convertToString() { assert type == Type.INTEGER || type == Type.REAL || type == Type.BOOLEAN; + Operand operand = this; + switch (type) { case INTEGER: - setStringVal(Long.toString(intVal)); - setType(Type.STRING); + operand = new Operand(Type.STRING, Long.toString(intVal)); break; case REAL: - setStringVal(NumericPrecisionPrefs.formatOther(doubleVal)); - setType(Type.STRING); + operand = new Operand(Type.STRING, NumericPrecisionPrefs.formatOther(doubleVal)); break; case BOOLEAN: - setStringVal(Boolean.toString(booleanVal)); - setType(Type.STRING); + operand = new Operand(Type.STRING, Boolean.toString(booleanVal)); break; default: break; } + + return operand; } /** diff --git a/src/org/aavso/tools/vstar/vela/VeLaInterpreter.java b/src/org/aavso/tools/vstar/vela/VeLaInterpreter.java index 8e1c5741..2b982ee8 100644 --- a/src/org/aavso/tools/vstar/vela/VeLaInterpreter.java +++ b/src/org/aavso/tools/vstar/vela/VeLaInterpreter.java @@ -708,9 +708,8 @@ private void specialForm(AST ast) { * @param op The operation to be applied. */ private void applyBinaryOperation(Operation op) { - // TODO: copy() needed? - Operand operand2 = stack.pop().copy(); - Operand operand1 = stack.pop().copy(); + Operand operand2 = stack.pop(); + Operand operand1 = stack.pop(); if (operand1.getType() == Type.LIST || operand2.getType() == Type.LIST) { applyBinaryListOperation(op, operand1, operand2); @@ -721,7 +720,13 @@ private void applyBinaryOperation(Operation op) { // https://stackoverflow.com/questions/13604703/how-do-i-define-a-method-which-takes-a-lambda-as-a-parameter-in-java-8 // type unification is not relevant to all operations, e.g. IN; - Type type = unifyTypes(operand1, operand2); + // Unify the operand types if possible. + Pair operands = unifyTypes(operand1, operand2); + operand1 = operands.first; + operand2 = operands.second; + + // Arbitrarily use the type of the first operand. + Type type = operands.first.getType(); switch (op) { case ADD: @@ -968,12 +973,19 @@ private void applyBinaryOperation(Operation op) { private void applyBinaryListOperation(Operation op, Operand operand1, Operand operand2) { switch (op) { case IN: + Pair operands = unifyTypes(operand1, operand2); + operand1 = operands.first; + operand2 = operands.second; + if (operand2.getType() == Type.LIST) { // Is a value contained within a list? stack.push(new Operand(Type.BOOLEAN, operand2.listVal().contains(operand1))); - } else if (unifyTypes(operand1, operand2) == Type.STRING) { + } else if (operand2.getType() == Type.STRING) { // Is one string contained within another? stack.push(new Operand(Type.BOOLEAN, operand2.stringVal().contains(operand1.stringVal()))); + } else { + String msg = String.format("The second operand must be of type list or string for 'IN' operation", op); + throw new VeLaEvalError(msg); } break; @@ -1029,36 +1041,29 @@ private void binaryOpError(Operation op, Type type) { /** * Unify operand types by converting both operands to strings if only one is a * string or both operands to double if only one is an integer. We change - * nothing if either type is composite or Boolean. Note that this method is - * intended to be used for expressions, not for variable bindings where much - * less conversion/coercion is possible. + * nothing if either type is composite or Boolean. * * @param a The first operand. * @param b The second operand. - * @return The final type of the unified operands. + * @return The unified operands. */ - private Type unifyTypes(Operand a, Operand b) { - Type type = a.getType(); + private Pair unifyTypes(Operand a, Operand b) { + Operand converted_a = a; + Operand converted_b = b; if (!a.getType().isComposite() && !b.getType().isComposite()) { if (a.getType() != Type.STRING && b.getType() == Type.STRING) { - a.convertToString(); - type = Type.STRING; + converted_a = a.convertToString(); } else if (a.getType() == Type.STRING && b.getType() != Type.STRING) { - b.convertToString(); - type = Type.STRING; + converted_b = b.convertToString(); } else if (a.getType() == Type.INTEGER && b.getType() == Type.REAL) { - a.setDoubleVal(a.intVal()); - a.setType(Type.REAL); - type = Type.REAL; + converted_a = new Operand(Type.REAL, (double) a.intVal()); } else if (a.getType() == Type.REAL && b.getType() == Type.INTEGER) { - b.setDoubleVal(b.intVal()); - b.setType(Type.REAL); - type = Type.REAL; + converted_b = new Operand(Type.REAL, (double) b.intVal()); } } - return type; + return new Pair(converted_a, converted_b); } // ** Variable related methods ** @@ -1081,10 +1086,10 @@ public void bind(String name, Operand value, boolean isConstant) { Optional possibleBinding = environments.get(i).lookup(name); if (possibleBinding.isPresent()) { Operand existingBinding = possibleBinding.get(); - Type convertedType = value.convert(existingBinding.getType()); - if (convertedType == existingBinding.getType()) { + Operand convertedVal = value.convert(existingBinding.getType()); + if (convertedVal.getType() == existingBinding.getType()) { // bind value to existing variable... - environments.get(i).bind(name, value, isConstant); + environments.get(i).bind(name, convertedVal, isConstant); bound = true; } else { throw new VeLaEvalError(String.format( @@ -1258,11 +1263,11 @@ private boolean applyFunction(FunctionExecutor function, List params) t // Does the function have a return type defined? if (function.returnType.isPresent()) { // Attempt to convert to return type if necessary. - result.get().convert(function.getReturnType().get()); - if (result.get().getType() == function.returnType.get()) { - // The returned result was of the expected type. - stack.push(result.get()); + Operand convertedResult = result.get().convert(function.getReturnType().get()); + if (convertedResult.getType() == function.returnType.get()) { + // The returned result was of the expected type or was converted to it. + stack.push(convertedResult); } else { // The returned result was not of the expected type. throw new VeLaEvalError(String.format( diff --git a/test/org/aavso/tools/vstar/vela/VeLaTest.java b/test/org/aavso/tools/vstar/vela/VeLaTest.java index 848ec1f8..470918af 100644 --- a/test/org/aavso/tools/vstar/vela/VeLaTest.java +++ b/test/org/aavso/tools/vstar/vela/VeLaTest.java @@ -1238,6 +1238,17 @@ public void testNamedFunRecursiveLoop() { vela.program(prog); } + public void testParamConversionDoesNotChangeActualParam() { + String prog = ""; + prog += "a <- 42\n" + "f(n:real):real{n/5}\n" + "f(a)\n" + "a\n"; + + Optional result = vela.program(prog); + + assertTrue(result.isPresent()); + assertEquals(42, result.get().intVal()); + assertEquals(Type.INTEGER, result.get().getType()); + } + // Turing Completeness attained: Dec 20 2018, 18:55 :) public void testNamedFunRecursiveFactorial() {