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 ec96758a..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,230 +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; - - actualParameters = actualParameters.stream().map(op -> op.copy()) - .collect(Collectors.toList()); - - 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 3ca92cb2..58e16140 100644 --- a/src/org/aavso/tools/vstar/vela/Operand.java +++ b/src/org/aavso/tools/vstar/vela/Operand.java @@ -32,466 +32,470 @@ */ public class Operand { - private Type type; - private long intVal; - private double doubleVal; - private String stringVal; - private boolean booleanVal; - private List listVal; - private FunctionExecutor functionVal; - - public static Operand EMPTY_LIST = new Operand(Type.LIST, Collections.emptyList()); - - public static Operand NO_VALUE = new Operand(Type.NONE, false); - - public Operand(Type type, long value) { - this.type = type; - intVal = value; - } - - public Operand(Type type, double value) { - this.type = type; - doubleVal = value; - } - - public Operand(Type type, String value) { - this.type = type; - stringVal = value; - } - - public Operand(Type type, boolean value) { - this.type = type; - booleanVal = value; - } - - public Operand(Type type, List value) { - this.type = type; - listVal = value; - } - - public Operand(Type type, FunctionExecutor value) { - this.type = type; - functionVal = value; - } - - // For object copy - private Operand() { - } - - // TODO: add a Property to Operand method - - /** - * Given a VeLa type and a Java object, return an Operand instance. - * - * @param type The VeLa type. - * @param obj The Java object. - * @return A corresponding Operand instance. - */ - public static Operand object2Operand(Type type, Object obj) { - Operand operand = null; - - switch (type) { - case INTEGER: - operand = new Operand(Type.INTEGER, (int) obj); - break; - case REAL: - operand = new Operand(Type.REAL, (double) obj); - break; - case STRING: - operand = new Operand(Type.STRING, (String) obj); - break; - case BOOLEAN: - operand = new Operand(Type.BOOLEAN, (boolean) obj); - break; - case LIST: - if (obj.getClass() == Type.DBL_ARR.getClass()) { - List arr = new ArrayList(); - for (double n : (double[]) obj) { - arr.add(new Operand(Type.REAL, n)); - } - obj = arr; - } else if (obj.getClass() == Type.DBL_CLASS_ARR.getClass()) { - List arr = new ArrayList(); - for (Double n : (Double[]) obj) { - arr.add(new Operand(Type.REAL, n)); - } - obj = arr; - } - operand = new Operand(Type.LIST, (List) obj); - break; - case FUNCTION: - operand = new Operand(Type.FUNCTION, (FunctionExecutor) obj); - break; - case NONE: - operand = NO_VALUE; - break; - case OBJECT: - // TODO + private Type type; + private long intVal; + private double doubleVal; + private String stringVal; + private boolean booleanVal; + private List listVal; + private FunctionExecutor functionVal; + + public static Operand EMPTY_LIST = new Operand(Type.LIST, Collections.emptyList()); + + public static Operand NO_VALUE = new Operand(Type.NONE, false); + + public Operand(Type type, long value) { + this.type = type; + intVal = value; + } + + public Operand(Type type, double value) { + this.type = type; + doubleVal = value; + } + + public Operand(Type type, String value) { + this.type = type; + stringVal = value; + } + + public Operand(Type type, boolean value) { + this.type = type; + booleanVal = value; + } + + public Operand(Type type, List value) { + this.type = type; + listVal = value; + } + + public Operand(Type type, FunctionExecutor value) { + this.type = type; + functionVal = value; + } + + // For object copy + private Operand() { + } + + // TODO: add a Property to Operand method + + /** + * Given a VeLa type and a Java object, return an Operand instance. + * + * @param type The VeLa type. + * @param obj The Java object. + * @return A corresponding Operand instance. + */ + public static Operand object2Operand(Type type, Object obj) { + Operand operand = null; + + switch (type) { + case INTEGER: + operand = new Operand(Type.INTEGER, (int) obj); + break; + case REAL: + operand = new Operand(Type.REAL, (double) obj); + break; + case STRING: + operand = new Operand(Type.STRING, (String) obj); + break; + case BOOLEAN: + operand = new Operand(Type.BOOLEAN, (boolean) obj); + break; + case LIST: + if (obj.getClass() == Type.DBL_ARR.getClass()) { + List arr = new ArrayList(); + for (double n : (double[]) obj) { + arr.add(new Operand(Type.REAL, n)); + } + obj = arr; + } else if (obj.getClass() == Type.DBL_CLASS_ARR.getClass()) { + List arr = new ArrayList(); + for (Double n : (Double[]) obj) { + arr.add(new Operand(Type.REAL, n)); + } + obj = arr; + } + operand = new Operand(Type.LIST, (List) obj); + break; + case FUNCTION: + operand = new Operand(Type.FUNCTION, (FunctionExecutor) obj); + break; + case NONE: + operand = NO_VALUE; + break; + case OBJECT: + // TODO // operand = new Operand(Type.OBJECT, obj); - } - - return operand; - } - - /** - * Return a Java object corresponding to this Operand instance. - * - * @return A corresponding Java object. - */ - public Object toObject() { - Object obj = null; - - switch (type) { - case INTEGER: - obj = intVal; - break; - case REAL: - obj = doubleVal; - break; - case STRING: - obj = stringVal; - break; - case BOOLEAN: - obj = booleanVal; - break; - case LIST: - for (Type type : Type.values()) { - if (listVal.stream().allMatch(op -> op.type == type)) { - int i = 0; - try { - switch (type) { - case INTEGER: - int[] ints = new int[listVal.size()]; - for (Operand op : listVal) { - ints[i++] = (int) op.intVal; - } - obj = ints; - break; - case REAL: - double[] reals = new double[listVal.size()]; - for (Operand op : listVal) { - reals[i++] = (double) op.doubleVal; - } - obj = reals; - break; - case STRING: - String[] strings = new String[listVal.size()]; - for (Operand op : listVal) { - strings[i++] = (String) op.stringVal; - } - obj = strings; - break; - case BOOLEAN: - boolean[] booleans = new boolean[listVal.size()]; - for (Operand op : listVal) { - booleans[i++] = (boolean) op.booleanVal; - } - obj = booleans; - break; - default: - throw new VeLaEvalError(""); - } - } catch (Throwable t) { - throw new VeLaEvalError("Cannot construct array from VeLa list"); - } - } - } - break; - case FUNCTION: - // TODO - break; - case NONE: - // TODO - break; - case OBJECT: - // TODO - break; - } - - return obj; - } - - /** - * Convert this operand to the required type, if possible. - * - * @param operand The operand to be converted. - * @param requiredType The required type. - * @return The converted type; will be unchanged if it matches the required type - * or can't be converted; TODO: consider returning Optional; if - * empty, then the type can't be converted - */ - public Type convert(Type requiredType) { - if (type != requiredType) { - // Integer to double - if (type == Type.INTEGER && requiredType == Type.REAL) { - setType(Type.REAL); - setDoubleVal((double) intVal); - } - } - - return type; - } - - /** - * Convert this operand's type to string. - */ - public void convertToString() { - assert type == Type.INTEGER || type == Type.REAL || type == Type.BOOLEAN; - - switch (type) { - case INTEGER: - setStringVal(Long.toString(intVal)); - setType(Type.STRING); - break; - case REAL: - setStringVal(NumericPrecisionPrefs.formatOther(doubleVal)); - setType(Type.STRING); - break; - case BOOLEAN: - setStringVal(Boolean.toString(booleanVal)); - setType(Type.STRING); - break; - default: - break; - } - } - - /** - * @return the type - */ - public Type getType() { - return type; - } - - /** - * @param type the type to set - */ - public void setType(Type type) { - this.type = type; - } - - /** - * @param intVal the intVal to set - */ - public void setIntegerVal(long intVal) { - this.intVal = intVal; - } - - /** - * @param doubleVal the doubleVal to set - */ - public void setDoubleVal(double doubleVal) { - this.doubleVal = doubleVal; - } - - /** - * @return the intVal - */ - public long intVal() { - return intVal; - } - - /** - * @return the doubleVal - */ - public double doubleVal() { - return doubleVal; - } - - /** - * @param stringVal the stringVal to set - */ - public void setStringVal(String stringVal) { - this.stringVal = stringVal; - } - - /** - * @return the stringVal - */ - public String stringVal() { - return stringVal; - } - - /** - * @param booleanVal the booleanVal to set - */ - public void setBooleanVal(boolean booleanVal) { - this.booleanVal = booleanVal; - } - - /** - * @return the booleanVal - */ - public boolean booleanVal() { - return booleanVal; - } - - /** - * @return the listVal - */ - public List listVal() { - return listVal; - } - - /** - * @param listVal the listVal to set - */ - public void setListVal(List listVal) { - this.listVal = listVal; - } - - /** - * @return the functionVal - */ - public FunctionExecutor functionVal() { - return functionVal; - } - - /** - * @param functionVal the functionVal to set - */ - public void setFunctionVal(FunctionExecutor functionVal) { - this.functionVal = functionVal; - } - - public String toHumanReadableString() { - String str = ""; - - switch (type) { - case INTEGER: - str = Long.toString(intVal); - break; - case REAL: - str = NumericPrecisionPrefs.formatOther(doubleVal); - break; - case BOOLEAN: - str = booleanVal ? "True" : "False"; - break; - case STRING: - str = stringVal; - break; - case LIST: - str = listVal.toString().replace(",", ""); - break; - case FUNCTION: - str = functionVal.toString(); - break; - } - - return str; - } - - @Override - public String toString() { - String str = ""; - - switch (type) { - case INTEGER: - str = Long.toString(intVal); - break; - case REAL: - str = NumericPrecisionPrefs.formatOther(doubleVal); - break; - case BOOLEAN: - str = booleanVal ? "True" : "False"; - break; - case STRING: - str = "\"" + stringVal + "\""; - break; - case LIST: - str = listVal.toString().replace(",", "").replace("[", "'(").replace("]", ")"); - break; - case FUNCTION: - str = functionVal.toString(); - break; - } - - // str += " (" + type + ")"; - - return str; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + (booleanVal ? 1231 : 1237); - long temp; - temp = Double.doubleToLongBits(doubleVal); - result = prime * result + (int) (temp ^ (temp >>> 32)); - result = prime * result + ((functionVal == null) ? 0 : functionVal.hashCode()); - result = prime * result + (int) (intVal ^ (intVal >>> 32)); - result = prime * result + ((listVal == null) ? 0 : listVal.hashCode()); - result = prime * result + ((stringVal == null) ? 0 : stringVal.hashCode()); - result = prime * result + ((type == null) ? 0 : type.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - Operand other = (Operand) obj; - if (booleanVal != other.booleanVal) - return false; - if (Double.doubleToLongBits(doubleVal) != Double.doubleToLongBits(other.doubleVal)) - return false; - if (functionVal == null) { - if (other.functionVal != null) - return false; - } else if (!functionVal.equals(other.functionVal)) - return false; - if (intVal != other.intVal) - return false; - if (listVal == null) { - if (other.listVal != null) - return false; - } else if (!listVal.equals(other.listVal)) - return false; - if (stringVal == null) { - if (other.stringVal != null) - return false; - } else if (!stringVal.equals(other.stringVal)) - return false; - if (type != other.type) - return false; - return true; - } - - public Operand copy() { - Operand operand = new Operand(); - - operand.type = type; - - switch (type) { - case INTEGER: - operand.intVal = intVal; - break; - case REAL: - operand.doubleVal = doubleVal; - break; - case BOOLEAN: - operand.booleanVal = booleanVal; - break; - case STRING: - operand.stringVal = stringVal; - break; - case LIST: - List list = new ArrayList(); - for (Operand op : listVal) { - list.add(op.copy()); - } - operand.listVal = list; - break; - case FUNCTION: - operand.functionVal = functionVal; - break; - } - - return operand; - } + } + + return operand; + } + + /** + * Return a Java object corresponding to this Operand instance. + * + * @return A corresponding Java object. + */ + public Object toObject() { + Object obj = null; + + switch (type) { + case INTEGER: + obj = intVal; + break; + case REAL: + obj = doubleVal; + break; + case STRING: + obj = stringVal; + break; + case BOOLEAN: + obj = booleanVal; + break; + case LIST: + for (Type type : Type.values()) { + if (listVal.stream().allMatch(op -> op.type == type)) { + int i = 0; + try { + switch (type) { + case INTEGER: + int[] ints = new int[listVal.size()]; + for (Operand op : listVal) { + ints[i++] = (int) op.intVal; + } + obj = ints; + break; + case REAL: + double[] reals = new double[listVal.size()]; + for (Operand op : listVal) { + reals[i++] = (double) op.doubleVal; + } + obj = reals; + break; + case STRING: + String[] strings = new String[listVal.size()]; + for (Operand op : listVal) { + strings[i++] = (String) op.stringVal; + } + obj = strings; + break; + case BOOLEAN: + boolean[] booleans = new boolean[listVal.size()]; + for (Operand op : listVal) { + booleans[i++] = (boolean) op.booleanVal; + } + obj = booleans; + break; + default: + throw new VeLaEvalError(""); + } + } catch (Throwable t) { + throw new VeLaEvalError("Cannot construct array from VeLa list"); + } + } + } + break; + case FUNCTION: + // TODO + break; + case NONE: + // TODO + break; + case OBJECT: + // TODO + break; + } + + return obj; + } + + /** + * 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 unchanged operand or a new operand that conforms to the required + * type. + */ + public Operand convert(Type requiredType) { + Operand operand = this; + + if (!type.isComposite()) { + if (type != requiredType) { + if (type == Type.INTEGER && requiredType == Type.REAL) { + operand = new Operand(Type.REAL, (double) intVal); + } else if (type != Type.STRING && requiredType == Type.STRING) { + operand = operand.convertToString(); + } + } + } + + return operand; + } + + /** + * Convert this operand's type to string. + */ + public Operand convertToString() { + assert type == Type.INTEGER || type == Type.REAL || type == Type.BOOLEAN; + + Operand operand = this; + + switch (type) { + case INTEGER: + operand = new Operand(Type.STRING, Long.toString(intVal)); + break; + case REAL: + operand = new Operand(Type.STRING, NumericPrecisionPrefs.formatOther(doubleVal)); + break; + case BOOLEAN: + operand = new Operand(Type.STRING, Boolean.toString(booleanVal)); + break; + default: + break; + } + + return operand; + } + + /** + * @return the type + */ + public Type getType() { + return type; + } + + /** + * @param type the type to set + */ + public void setType(Type type) { + this.type = type; + } + + /** + * @param intVal the intVal to set + */ + public void setIntegerVal(long intVal) { + this.intVal = intVal; + } + + /** + * @param doubleVal the doubleVal to set + */ + public void setDoubleVal(double doubleVal) { + this.doubleVal = doubleVal; + } + + /** + * @return the intVal + */ + public long intVal() { + return intVal; + } + + /** + * @return the doubleVal + */ + public double doubleVal() { + return doubleVal; + } + + /** + * @param stringVal the stringVal to set + */ + public void setStringVal(String stringVal) { + this.stringVal = stringVal; + } + + /** + * @return the stringVal + */ + public String stringVal() { + return stringVal; + } + + /** + * @param booleanVal the booleanVal to set + */ + public void setBooleanVal(boolean booleanVal) { + this.booleanVal = booleanVal; + } + + /** + * @return the booleanVal + */ + public boolean booleanVal() { + return booleanVal; + } + + /** + * @return the listVal + */ + public List listVal() { + return listVal; + } + + /** + * @param listVal the listVal to set + */ + public void setListVal(List listVal) { + this.listVal = listVal; + } + + /** + * @return the functionVal + */ + public FunctionExecutor functionVal() { + return functionVal; + } + + /** + * @param functionVal the functionVal to set + */ + public void setFunctionVal(FunctionExecutor functionVal) { + this.functionVal = functionVal; + } + + public String toHumanReadableString() { + String str = ""; + + switch (type) { + case INTEGER: + str = Long.toString(intVal); + break; + case REAL: + str = NumericPrecisionPrefs.formatOther(doubleVal); + break; + case BOOLEAN: + str = booleanVal ? "True" : "False"; + break; + case STRING: + str = stringVal; + break; + case LIST: + str = listVal.toString().replace(",", ""); + break; + case FUNCTION: + str = functionVal.toString(); + break; + } + + return str; + } + + @Override + public String toString() { + String str = ""; + + switch (type) { + case INTEGER: + str = Long.toString(intVal); + break; + case REAL: + str = NumericPrecisionPrefs.formatOther(doubleVal); + break; + case BOOLEAN: + str = booleanVal ? "True" : "False"; + break; + case STRING: + str = "\"" + stringVal + "\""; + break; + case LIST: + str = listVal.toString().replace(",", "").replace("[", "'(").replace("]", ")"); + break; + case FUNCTION: + str = functionVal.toString(); + break; + } + + // str += " (" + type + ")"; + + return str; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + (booleanVal ? 1231 : 1237); + long temp; + temp = Double.doubleToLongBits(doubleVal); + result = prime * result + (int) (temp ^ (temp >>> 32)); + result = prime * result + ((functionVal == null) ? 0 : functionVal.hashCode()); + result = prime * result + (int) (intVal ^ (intVal >>> 32)); + result = prime * result + ((listVal == null) ? 0 : listVal.hashCode()); + result = prime * result + ((stringVal == null) ? 0 : stringVal.hashCode()); + result = prime * result + ((type == null) ? 0 : type.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Operand other = (Operand) obj; + if (booleanVal != other.booleanVal) + return false; + if (Double.doubleToLongBits(doubleVal) != Double.doubleToLongBits(other.doubleVal)) + return false; + if (functionVal == null) { + if (other.functionVal != null) + return false; + } else if (!functionVal.equals(other.functionVal)) + return false; + if (intVal != other.intVal) + return false; + if (listVal == null) { + if (other.listVal != null) + return false; + } else if (!listVal.equals(other.listVal)) + return false; + if (stringVal == null) { + if (other.stringVal != null) + return false; + } else if (!stringVal.equals(other.stringVal)) + return false; + if (type != other.type) + return false; + return true; + } + + public Operand copy() { + Operand operand = new Operand(); + + operand.type = type; + + switch (type) { + case INTEGER: + operand.intVal = intVal; + break; + case REAL: + operand.doubleVal = doubleVal; + break; + case BOOLEAN: + operand.booleanVal = booleanVal; + break; + case STRING: + operand.stringVal = stringVal; + break; + case LIST: + List list = new ArrayList(); + for (Operand op : listVal) { + list.add(op.copy()); + } + operand.listVal = list; + break; + case FUNCTION: + operand.functionVal = functionVal; + break; + } + + return operand; + } } diff --git a/src/org/aavso/tools/vstar/vela/Type.java b/src/org/aavso/tools/vstar/vela/Type.java index 3cc26635..cb1f4666 100644 --- a/src/org/aavso/tools/vstar/vela/Type.java +++ b/src/org/aavso/tools/vstar/vela/Type.java @@ -28,7 +28,7 @@ */ public enum Type { - INTEGER, REAL, STRING, BOOLEAN, LIST, FUNCTION, OBJECT, NONE; + INTEGER, REAL, BOOLEAN, STRING, LIST, FUNCTION, OBJECT, NONE; public final static int[] INT_ARR = new int[0]; public final static double[] DBL_ARR = new double[0]; @@ -154,7 +154,7 @@ public static Type propertyToVela(Property prop) { } public boolean isComposite() { - return this == LIST || this == FUNCTION; + return this == LIST || this == FUNCTION || this == OBJECT; } public boolean oneOf(Type... types) { 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 c5db04bd..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() { @@ -1820,7 +1831,7 @@ public void testUserCode1() { File code = new File("test/org/aavso/tools/vstar/vela/code/sqr.vela"); Optional result = vela.program(code); assertTrue(result.isPresent()); - assertEquals(144, result.get().intVal()); + assertEquals(144.0, result.get().doubleVal()); } public void testUserCode2() { @@ -1830,7 +1841,7 @@ public void testUserCode2() { VeLaInterpreter vela = new VeLaInterpreter(VERBOSE, ADD_VSTAR_API, dirs); Optional result = vela.program("cube(2)"); assertTrue(result.isPresent()); - assertEquals(8, result.get().intVal()); + assertEquals(8.0, result.get().doubleVal()); } // Standard Library Functions: VeLa and intrinsic (Java)