From f0d576d0032535323ed27f89211aae9f8b5e760f Mon Sep 17 00:00:00 2001 From: miettinen Date: Fri, 31 Jan 2014 12:25:35 +0000 Subject: [PATCH] Unit validation for Sysdyn functions: matching argument units to the input (template) units (refs #4320). Lacks the correct behavior of scalar vs unit="1". git-svn-id: https://www.simantics.org/svn/simantics/sysdyn/trunk@28743 ac1ea38d-2e2b-0410-8846-a27921b304fc --- org.simantics.sysdyn.ontology/graph.tg | Bin 174033 -> 174034 bytes .../graph/ModelicaFunctions.pgraph | 6 +- .../unitParser/UnitCheckingNodeFactory.java | 2 + .../sysdyn/unitParser/UnitParser.jjt | 4 +- .../sysdyn/unitParser/nodes/FunctionCall.java | 161 ++++++++++++++---- .../unitParser/nodes/NamedArguments.java | 27 +++ .../sysdyn/unitParser/nodes/UnitResult.java | 5 +- 7 files changed, 168 insertions(+), 37 deletions(-) create mode 100644 org.simantics.sysdyn/src/org/simantics/sysdyn/unitParser/nodes/NamedArguments.java diff --git a/org.simantics.sysdyn.ontology/graph.tg b/org.simantics.sysdyn.ontology/graph.tg index 4d3bfdfbea7b39b108038552e995d7bd957dc9cd..8f3e514e9f973f9c460afcf9f99032183af58a8b 100644 GIT binary patch delta 42 vcmcb3p6k+iu7(!IEllgo8JV|lFlYKC!ouw67dd^Y50fI8wf&?I6Gt=vS||=$ delta 41 vcmca~p6lXyu7(!IEllgonOM}fuQzA ( "in" expression() )? } -/* Removed by Teemu. Refactored in function_arguments) void named_arguments() : { } { named_argument() ( "," named_arguments() )? } -*/ void named_argument() : { } { diff --git a/org.simantics.sysdyn/src/org/simantics/sysdyn/unitParser/nodes/FunctionCall.java b/org.simantics.sysdyn/src/org/simantics/sysdyn/unitParser/nodes/FunctionCall.java index a82b1c52..4f73e165 100644 --- a/org.simantics.sysdyn/src/org/simantics/sysdyn/unitParser/nodes/FunctionCall.java +++ b/org.simantics.sysdyn/src/org/simantics/sysdyn/unitParser/nodes/FunctionCall.java @@ -25,6 +25,7 @@ import org.simantics.sysdyn.utils.Function; import org.simantics.sysdyn.utils.Function.Input; import org.simantics.sysdyn.utils.Function.Output; import org.simantics.sysdyn.utils.Function.Parameter; +import org.simantics.utils.datastructures.Pair; /** * See UnitCheckingNodeFactory for mapping @@ -54,11 +55,14 @@ public class FunctionCall extends UnitCheckingNode { // Get the expected inputs ArrayList inputs = f.getInputList(); // Get arguments from parser. - ArrayList argumentUnits = getArgumentsOf(functionArgumentsNode, units, functions, allowEquivalents); + ArrayList> argumentUnits = getArgumentsOf(functionArgumentsNode, units, functions, allowEquivalents); + //HashMap correspondencies = getReplacementUnitCorrespondencies(inputs, argumentUnits); + // If the function input parameter units are OK, set the output unit accordingly. - if (matchInputs(inputs, argumentUnits)) { - result = setUnitResult(units, result, inputs, argumentUnits, f, functions, allowEquivalents); + if (matchInputs(inputs, argumentUnits, f, functions, allowEquivalents, units)) { + Output o = f.getOutputList().get(0); // Support only single return value functions. + result = getUnitResult(units, result, inputs, argumentUnits, f, functions, allowEquivalents, o); return result; } @@ -79,24 +83,24 @@ public class FunctionCall extends UnitCheckingNode { throw u; } - private UnitResult setUnitResult( + private static UnitResult getUnitResult( HashMap units, UnitResult result, ArrayList inputs, - ArrayList argumentUnits, + ArrayList> argumentUnits, Function f, ArrayList functions, - boolean allowEquivalents) throws UnitCheckingException { - Output o = f.getOutputList().get(0); // Support only single return value functions. + boolean allowEquivalents, + Parameter p) throws UnitCheckingException { - if (Parameter.ANY.equals(o.unit)) { + if (Parameter.ANY.equals(p.unit)) { result.setUnitType(UnitType.ANY); - } else if ("1".equals(o.unit)) { // TODO: see if this should be changed to something else + } else if ("1".equals(p.unit)) { // TODO: see if this should be changed to something else result.setUnitType(UnitType.SCALAR); } else { // Replace TIME with actual time unit. String timeUnit = units.get("time"); - String timeReplaced = o.unit.replace(Parameter.TIME, timeUnit); + String timeReplaced = p.unit.replace(Parameter.TIME, timeUnit); // Replace 'p, 'q, etc. in output with units from actual inputs. HashMap correspondencies = getReplacementUnitCorrespondencies(inputs, argumentUnits); @@ -121,7 +125,7 @@ public class FunctionCall extends UnitCheckingNode { return result; } - private String replaceCorrespondencies(Function f, String original, + private static String replaceCorrespondencies(Function f, String original, HashMap correspondencies) throws UnitCheckingException { int index; String ret = new String(original); @@ -138,23 +142,97 @@ public class FunctionCall extends UnitCheckingNode { return ret; } - private HashMap getReplacementUnitCorrespondencies( - ArrayList inputs, ArrayList argumentUnits) { + private static HashMap getReplacementUnitCorrespondencies( + ArrayList inputs, ArrayList> argumentUnits) { // TODO: General case. Currently each 'p, 'q etc. must exist as an atomic unit. HashMap correspondencies = new HashMap(); - for (int i = 0; i < inputs.size(); ++i) { - if (inputs.get(i).unit.matches("'[a-zA-Z]")) { - if (argumentUnits.size() > i) { // Skip optional - correspondencies.put(inputs.get(i).unit, argumentUnits.get(i).getCleanFullUnit()); + for (int i = 0; i < argumentUnits.size(); ++i) { + if (argumentUnits.get(i).second != null) { // named arguments + for (Input input : inputs) { + if (input.name.equals(argumentUnits.get(i).second)) { + addPossibleCorrespondence(correspondencies, input, argumentUnits.get(i).first); + } } + } else if (inputs.size() > i) { + addPossibleCorrespondence(correspondencies, inputs.get(i), argumentUnits.get(i).first); } } + // If there is no mapping between the actual and defined inputs (e.g. 'p refers to both m and s), + // then return the latest correspondence. The users of this functions currently assume such behavior. return correspondencies; } + private static void addPossibleCorrespondence(HashMap correspondencies, + Input input, UnitResult argumentUnit) { + if (input.unit.matches("'[a-zA-Z]")) { + String fullUnit = argumentUnit.getCleanFullUnit(); + if ("".equals(fullUnit)) + fullUnit = "1"; + correspondencies.put(input.unit, fullUnit); + } + } + private boolean matchInputs(ArrayList inputs, - ArrayList argumentUnits) { - // TODO Auto-generated method stub + ArrayList> argumentUnits, Function f, ArrayList functions, boolean allowEquivalents, HashMap units) throws UnitCheckingException { + ArrayList inputMatches = new ArrayList(); + for (int i = 0; i < inputs.size(); ++i) + inputMatches.add(Boolean.FALSE); + + for (int i = 0; i < argumentUnits.size(); ++i) { // Go through all _arguments_ + if (inputs.size() == 0) + return false; // No inputs expected but got at least one + + if (argumentUnits.get(i).second != null) { // Named argument + boolean found = false; + for (int j = 0; j < inputs.size(); ++j) { + Input namedInput = inputs.get(j); + if (namedInput.name.equals(argumentUnits.get(i).second)) { + // Match input unit to argument unit + UnitResult inputUnit = new UnitResult(allowEquivalents); + inputUnit = getUnitResult(units, inputUnit, inputs, argumentUnits, f, functions, allowEquivalents, namedInput); + if (!inputUnit.equals(argumentUnits.get(i).first)) + return false; + inputMatches.set(j, Boolean.TRUE); + found = true; + break; + } + } + if (!found) { + throw new UnitCheckingException("Undefined input argument " + argumentUnits.get(i).second + + " used in function " + f.getName() + "."); + } + } else { // Position argument + if (i >= inputs.size()) { // Test for variable length argument + // Assume there can be only one variable length input and its in the end. + // And that there cannot be any optional arguments before. + if (inputs.get(inputs.size() - 1).variableLength) { + // Match input unit to argument unit + UnitResult inputUnit = new UnitResult(allowEquivalents); + inputUnit = getUnitResult(units, inputUnit, inputs, argumentUnits, f, functions, allowEquivalents, inputs.get(inputs.size() - 1)); + if (!inputUnit.equals(argumentUnits.get(i).first)) + return false; + // The corresponding _input_ has already been gone through, no need to set true. + } else { + return false; + } + } else { + // Match input unit to argument unit + UnitResult inputUnit = new UnitResult(allowEquivalents); + inputUnit = getUnitResult(units, inputUnit, inputs, argumentUnits, f, functions, allowEquivalents, inputs.get(i)); + if (!inputUnit.equals(argumentUnits.get(i).first)) + return false; + inputMatches.set(i, Boolean.TRUE); + } + } + } + + // See if some of the required inputs has not been defined. + for (int i = 0; i < inputs.size(); ++i) { + if (!inputMatches.get(i) && !inputs.get(i).optional) { + return false; + } + } + return true; } @@ -165,7 +243,7 @@ public class FunctionCall extends UnitCheckingNode { * @param argumentUnits * @return */ - private UnitCheckingException getException(UnitCheckingNode functionLabelNode, ArrayList inputs, ArrayList argumentUnits) { + private UnitCheckingException getException(UnitCheckingNode functionLabelNode, ArrayList inputs, ArrayList> argumentUnits) { String exception = new String("Function arguments do not have correct units. Expected " + functionLabelNode.printNode() + "("); for (int i = 0; i < inputs.size(); ++i) { @@ -182,12 +260,12 @@ public class FunctionCall extends UnitCheckingNode { } exception += " ), got " + functionLabelNode.printNode() + "("; for (int i = 0; i < argumentUnits.size(); ++i) { - UnitResult argumentUnit = argumentUnits.get(i); + UnitResult argumentUnit = argumentUnits.get(i).first; if (i == 0) exception += " "; exception += "[" + argumentUnit.getCleanFullUnit() + "]"; - if (i != inputs.size() - 1) + if (i != argumentUnits.size() - 1) exception += ", "; } @@ -196,21 +274,46 @@ public class FunctionCall extends UnitCheckingNode { return new UnitCheckingException(exception); } - private ArrayList getArgumentsOf( + private ArrayList> getArgumentsOf( UnitCheckingNode functionArgumentsNode, HashMap units, ArrayList functions, boolean allowEquivalents) throws UnitCheckingException { // Arguments are defined recursively in OpenModelica - ArrayList argumentUnits = new ArrayList(); + ArrayList> argumentUnits = new ArrayList>(); if (functionArgumentsNode instanceof FunctionArguments) { UnitCheckingNode firstArg = ((UnitCheckingNode) functionArgumentsNode.jjtGetChild(0)); - argumentUnits.add(firstArg.getUnits(units, functions, allowEquivalents)); - if (functionArgumentsNode.jjtGetNumChildren() > 1) { - UnitCheckingNode next = ((UnitCheckingNode) functionArgumentsNode.jjtGetChild(1)); - argumentUnits.addAll(getArgumentsOf(next, units, functions, allowEquivalents)); + if (firstArg instanceof NamedArguments) { + // Found all position arguments, continue with named arguments + argumentUnits.addAll(getNamedArgumentsOf(firstArg, units, functions, allowEquivalents)); + } else { + argumentUnits.add(new Pair(firstArg.getUnits(units, functions, allowEquivalents), null)); + if (functionArgumentsNode.jjtGetNumChildren() > 1) { + // Continue to the next argument (within the following function_arguments) + UnitCheckingNode next = ((UnitCheckingNode) functionArgumentsNode.jjtGetChild(1)); + argumentUnits.addAll(getArgumentsOf(next, units, functions, allowEquivalents)); + } + } + } + return argumentUnits; + } + + private ArrayList> getNamedArgumentsOf( + UnitCheckingNode namedArgumentsNode, + HashMap units, + ArrayList functions, + boolean allowEquivalents) throws UnitCheckingException { + // Named arguments are defined recursively in OpenModelica + ArrayList> argumentUnits = new ArrayList>(); + if (namedArgumentsNode instanceof NamedArguments) { + UnitCheckingNode firstArg = ((UnitCheckingNode) namedArgumentsNode.jjtGetChild(0)); + String argumentName = firstArg.jjtGetFirstToken().image; + argumentUnits.add(new Pair(firstArg.getUnits(units, functions, allowEquivalents), argumentName)); + if (namedArgumentsNode.jjtGetNumChildren() > 1) { + // Continue to the next argument (within the following function_arguments) + UnitCheckingNode next = ((UnitCheckingNode) namedArgumentsNode.jjtGetChild(1)); + argumentUnits.addAll(getNamedArgumentsOf(next, units, functions, allowEquivalents)); } - } return argumentUnits; } diff --git a/org.simantics.sysdyn/src/org/simantics/sysdyn/unitParser/nodes/NamedArguments.java b/org.simantics.sysdyn/src/org/simantics/sysdyn/unitParser/nodes/NamedArguments.java new file mode 100644 index 00000000..7afcf98e --- /dev/null +++ b/org.simantics.sysdyn/src/org/simantics/sysdyn/unitParser/nodes/NamedArguments.java @@ -0,0 +1,27 @@ +/******************************************************************************* + * Copyright (c) 2014 Association for Decentralized Information Management in + * Industry THTH ry. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * VTT Technical Research Centre of Finland - initial API and implementation + *******************************************************************************/ +package org.simantics.sysdyn.unitParser.nodes; + +import org.simantics.sysdyn.unitParser.UnitCheckingNode; + +/** + * See UnitCheckingNodeFactory for mapping + * @author Tuomas Miettinen + * + */ +public class NamedArguments extends UnitCheckingNode { + + public NamedArguments(int id) { + super(id); + } + +} diff --git a/org.simantics.sysdyn/src/org/simantics/sysdyn/unitParser/nodes/UnitResult.java b/org.simantics.sysdyn/src/org/simantics/sysdyn/unitParser/nodes/UnitResult.java index 74cf4dd9..7daf08ac 100644 --- a/org.simantics.sysdyn/src/org/simantics/sysdyn/unitParser/nodes/UnitResult.java +++ b/org.simantics.sysdyn/src/org/simantics/sysdyn/unitParser/nodes/UnitResult.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2013 Association for Decentralized Information Management in + * Copyright (c) 2013-2014 Association for Decentralized Information Management in * Industry THTH ry. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 @@ -8,7 +8,8 @@ * * Contributors: * Semantum Oy - initial API and implementation - *******************************************************************************/ + * VTT Technical Research Centre of Finland + ********************************************************************************/ package org.simantics.sysdyn.unitParser.nodes; import java.util.ArrayList; -- 2.47.1