Expression Parsing?

I was looking through the SDK docs (.common.expressions.) and was trying to make sense of it in order to figure out what needs to get called to evaluate expressions with the same ability as that in an expression binding? This would include the ability to have the parser resolve any valid functions, tag paths, and potentially any property bindings. By the look of it there are a variety of classes but I am uncertain what ones are applied to what functionality. Anyone have an example of doing this in a general-purpose kind of way?

No?! Got so far as using the ELParserHarness to evaluate mathematical expressions, but nothing that references a tag or otherwise (e.g. function, etc). I am assuming it is because I am using a null parameter for the ExpressionParseContext. But that I don’t get since those classes appear to be a gateway-scoped resource!?

I’m posting this in the hopes that I come back tomorrow and try to answer your question! :laughing:

…and I this in hopes you remember you posted in order to remember to post… My scope is client/designer and want to be able to evaluate expressions similarly to that of an expression binding within a component and/or tag. I understand that there is likely a deeper complexity to providing the context appropriate to resolving property bindings since one would have to provide the context for which to resolve them from, but understanding that part of it would be tremendously useful as it is not described very well (at all) in the docs/javadoc. Thanks in advance!

Here is what we have done.

  1. Create an object that implements ExpressionParseContext. I am not sure if this is required or not.
public class MyParserContext implements ExpressionParseContext {

	@Override
	public Expression createBoundExpression(String path)
			throws RuntimeException {
		TagPath tagPath;
		try {
			tagPath = TagPathParser.parse(path);
			BoundTagExpression exp = new BoundTagExpression(tagPath);
			exp.setTagListenerDelegate(new TagListener(
					tagPath, null, DataQuality.UNKNOWN));
			return exp;
		} catch (IOException e) {
			return null;
		}
	}

	@Override
	public FunctionFactory getFunctionFactory() {
        return new ClientFunctionFactory(DefaultFunctionFactory.getSharedInstance());
	}

}

After having a reference to the context, try something like this:

Parser parser = new ELParserHarness();
MyParserContext mpc = new MyParserContext();
try {
                LOGGER.debug("This is my lblExpStr: " + lblExpStr);
                lblExp = parser.parse(lblExpStr,mpc);

                lblExp.connect(ctx, new InteractionListener() {
                    @Override
                    public void childInteractionUpdated() {
                        try {
                            LOGGER.debug("updating");
                            String value = lblExp.execute().getValue()
                                    .toString();
                            system.out.println(value);
                        } catch (ExpressionException e) {
                            LOGGER.warn("Error evaluating lblExpStr", e);
                        }
                    }
                });
                //Start the expression, then use it to print the initial text.
                lblExp.startup();
                system.out.println(lblExp.execute().getValue().toString());
            } catch (Exception e) {
                LOGGER.error("Error Starting ELParser", e);
            }

Let me know how well this works for you, I know my source works in my application, but I changed it to be a little more generic for you.

1 Like

Kyle,

Thanks for the input. I did give the snippet a shot but it was basically still returning nothing when I give it a tag path like ‘{Simulator/Ramp/Ramp0}’ (and yes, that is a valid path in the tag tree and evaluates in a standard expression elsewhere. It did resolve the issue of functions as originally I could evaluate ‘1+2+3’ but not ‘toString(1+2+3)’ (which I now can).

Have you found if the connect/startup part of this is required? I want to simply evaluate on demand, not tie into the change/update notification piece.

Kev, any input on this?

This may be all you need:

public class ExampleParseContext implements ExpressionParseContext {

    private final TagManagerBase tagManagerBase;
    private final FunctionFactory functionFactory;

    public ExampleParseContext(TagManagerBase tagManagerBase, FunctionFactory functionFactory) {
        this.tagManagerBase = tagManagerBase;
        this.functionFactory = functionFactory;
    }

    @Override
    public Expression createBoundExpression(String path) throws RuntimeException {
        try {
            TagPath tagPath = TagPathParser.parse(path);
            Tag tag = tagManagerBase.getTag(tagPath);

            if (tag == null) throw new TagNotFoundException(tagPath);

            TagValue tagValue = tag.getAttribute(tagPath.getProperty());

            TagListener listener = new TagListener(
                    tagPath,
                    tagValue.getValue(),
                    tagValue.getQuality()
            );

            Class type = TagProp.Value.equals(tagPath.getProperty()) ?
                    tag.getDataType().getJavaType() :
                    tagPath.getProperty().getType();

            BoundTagExpression expression = new BoundTagExpression(tagPath);
            expression.setType(type);
            expression.setTagListenerDelegate(listener);

            return expression;
        } catch (IllegalArgumentException | IOException | TagNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public FunctionFactory getFunctionFactory() {
        return functionFactory;
    }

}

You can get references to the FunctionFactory and to the SQLTagsManager from GatewayContext. See GatewayContext#getExpressionFunctionFactory() and GatewayContext#getTagManager().

1 Like

Thanks Kev, I’ll try this later today. I assume I can use the analogous context functions within the Client scope?

Yeah, you should find the same methods on ClientContext.

That was the ticket!

Let me go out on a limb a little further… Now if I wanted to resolve a property binding, what has to get added to the mix? I only can pass in one ExpressionParseContext which appears to receive each ‘{whatever}’ on the expression. It is clear where the function resolution happens – but in what way do you get a context tied to the current scope of properties?