From 64b494464d7e0924eb6d9d0d38296d931a7259f5 Mon Sep 17 00:00:00 2001 From: Paul Griffith Date: Mon, 10 Oct 2022 15:47:53 -0700 Subject: [PATCH] Add system.util.evalExpression --- .../common/UtilitiesExtensions.java | 74 +++++++++++++++++++ .../common/UtilitiesExtensions.properties | 4 +- docker-compose.yml | 1 + 3 files changed, 78 insertions(+), 1 deletion(-) diff --git a/common/src/main/java/org/imdc/extensions/common/UtilitiesExtensions.java b/common/src/main/java/org/imdc/extensions/common/UtilitiesExtensions.java index 51d35cd..6287c5a 100644 --- a/common/src/main/java/org/imdc/extensions/common/UtilitiesExtensions.java +++ b/common/src/main/java/org/imdc/extensions/common/UtilitiesExtensions.java @@ -1,12 +1,29 @@ package org.imdc.extensions.common; +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.Optional; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import com.inductiveautomation.ignition.common.PyUtilities; +import com.inductiveautomation.ignition.common.TypeUtilities; +import com.inductiveautomation.ignition.common.expressions.ConstantExpression; +import com.inductiveautomation.ignition.common.expressions.Expression; +import com.inductiveautomation.ignition.common.expressions.ExpressionParseContext; +import com.inductiveautomation.ignition.common.expressions.FunctionFactory; +import com.inductiveautomation.ignition.common.expressions.parsing.ELParserHarness; import com.inductiveautomation.ignition.common.model.CommonContext; +import com.inductiveautomation.ignition.common.model.values.QualifiedValue; import com.inductiveautomation.ignition.common.script.PyArgParser; +import com.inductiveautomation.ignition.common.script.ScriptContext; import com.inductiveautomation.ignition.common.script.builtin.KeywordArgs; import com.inductiveautomation.ignition.common.script.hints.ScriptFunction; +import com.inductiveautomation.ignition.common.tags.model.TagPath; +import com.inductiveautomation.ignition.common.tags.paths.parser.TagPathParser; import org.apache.commons.lang3.tuple.Pair; import org.jetbrains.annotations.NotNull; import org.python.core.Py; @@ -17,6 +34,8 @@ public class UtilitiesExtensions { private final CommonContext context; + private static final ELParserHarness EXPRESSION_PARSER = new ELParserHarness(); + public UtilitiesExtensions(CommonContext context) { this.context = context; } @@ -55,4 +74,59 @@ private static PyObject recursiveConvert(@NotNull PyObject object) { } } } + + @ScriptFunction(docBundlePrefix = "UtilitiesExtensions") + @KeywordArgs(names = {"expression"}, types = {String.class}) + public QualifiedValue evalExpression(PyObject[] args, String[] keywords) throws Exception { + if (args.length == 0) { + throw Py.ValueError("Must supply at least one argument to evalExpression"); + } + + String expression = TypeUtilities.toString(TypeUtilities.pyToJava(args[0])); + + var keywordMap = new HashMap(); + for (int i = 0; i < keywords.length; i++) { + keywordMap.put(keywords[i], TypeUtilities.pyToJava(args[i + 1])); + } + ExpressionParseContext parseContext = new KeywordParseContext(keywordMap); + + Expression actualExpression = EXPRESSION_PARSER.parse(expression, parseContext); + try { + actualExpression.startup(); + return actualExpression.execute(); + } finally { + actualExpression.shutdown(); + } + } + + private class KeywordParseContext implements ExpressionParseContext { + private final Map keywords; + + private KeywordParseContext(Map keywords) { + this.keywords = keywords; + } + + @Override + public Expression createBoundExpression(String reference) throws RuntimeException { + if (reference == null || reference.isEmpty()) { + throw new IllegalArgumentException("Invalid path " + reference); + } + if (keywords.containsKey(reference)) { + return new ConstantExpression(keywords.get(reference)); + } else { + try { + TagPath path = TagPathParser.parse(ScriptContext.defaultTagProvider(), reference); + var tagValues = context.getTagManager().readAsync(List.of(path)).get(30, TimeUnit.SECONDS); + return new ConstantExpression(tagValues.get(0).getValue()); + } catch (IOException | InterruptedException | ExecutionException | TimeoutException e) { + throw new RuntimeException(e); + } + } + } + + @Override + public FunctionFactory getFunctionFactory() { + return context.getExpressionFunctionFactory(); + } + } } diff --git a/common/src/main/resources/org/imdc/extensions/common/UtilitiesExtensions.properties b/common/src/main/resources/org/imdc/extensions/common/UtilitiesExtensions.properties index b1f7cdb..4171733 100644 --- a/common/src/main/resources/org/imdc/extensions/common/UtilitiesExtensions.properties +++ b/common/src/main/resources/org/imdc/extensions/common/UtilitiesExtensions.properties @@ -1,6 +1,8 @@ getContext.desc=Returns the current scope's context object directly. getContext.returns=The current scope's context. - deepCopy.desc=Deep copies the inner object structure into plain Python lists, dictionaries, and primitives. deepCopy.param.object=The object to convert. deepCopy.returns=A plain Python primitive object. +evalExpression.desc=Evaluates the supplied expression. Provide keyword arguments to populate values to curly braces. +evalExpression.param.expression=The expression to evaluate. +evalExpression.returns=A QualifiedValue with the result of the provided expression. diff --git a/docker-compose.yml b/docker-compose.yml index 911d51b..165aa9f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,6 +3,7 @@ services: image: inductiveautomation/ignition:8.1.20 ports: - 18088:8088 + - 18000:8000 environment: GATEWAY_ADMIN_PASSWORD: password IGNITION_EDITION: standard