Skip to content

Commit 8c2e541

Browse files
Merge pull request #7 from delegateas/thygesteffensen/dotIndexAndBool
Thygesteffensen/dot index and bool
2 parents 5efb090 + df2677b commit 8c2e541

7 files changed

Lines changed: 197 additions & 26 deletions

File tree

ExpressionEngine/ExpressionGrammar.cs

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ public ExpressionGrammar(IEnumerable<IFunction> functions)
1717

1818
#region BasicAuxParsers
1919

20+
Parser<IRule> boolean = Parse.String("true").Select(b => new ConstantRule(new ValueContainer(true)))
21+
.Or(Parse.String("false").Select(b => new ConstantRule(new ValueContainer(false))));
22+
2023
Parser<IRule> integer =
2124
Parse.Digit.AtLeastOnce().Text()
2225
.Select(
@@ -26,10 +29,6 @@ public ExpressionGrammar(IEnumerable<IFunction> functions)
2629
Parser<string> simpleString =
2730
Parse.AnyChar.Except(Parse.Char('@')).AtLeastOnce().Text();
2831

29-
Parser<ConstantRule> simpleStringRule =
30-
Parse.AnyChar.Except(Parse.Char('@')).Except(Parse.Char('(').Except(Parse.Char(')'))).AtLeastOnce()
31-
.Text().Select(x => new ConstantRule(new ValueContainer(x)));
32-
3332
Parser<char> escapedCharacters =
3433
from c in
3534
Parse.String("''").Select(n => '\'')
@@ -49,27 +48,34 @@ from content in Parse.CharExcept('\'').Or(escapedCharacters).Many().Text()
4948

5049
#endregion
5150

52-
5351
var lBracket = Parse.Char('[');
5452
var rBracket = Parse.Char(']');
5553
var lParenthesis = Parse.Char('(');
5654
var rParenthesis = Parse.Char(')');
5755

5856
Parser<bool> nullConditional = Parse.Char('?').Optional().Select(nC => !nC.IsEmpty);
5957

60-
Parser<IRule> indices =
58+
Parser<IRule> bracketIndices =
6159
from nll in nullConditional
6260
from index in _method.Or(stringLiteral).Or(integer).Contained(lBracket, rBracket)
6361
select new IndexRule(index, nll);
62+
63+
Parser<IRule> dotIndices =
64+
from nll in nullConditional
65+
from dot in Parse.Char('.')
66+
from index in Parse.AnyChar.Except(
67+
lBracket
68+
.Or(rBracket)
69+
.Or(lParenthesis)
70+
.Or(rParenthesis)
71+
.Or(Parse.Char('@'))
72+
).Many().Text()
73+
select new IndexRule(new StringLiteralRule(new ValueContainer(index)), nll);
6474

6575
Parser<IRule> argument =
66-
from arg in Parse.Ref(() => _method.Or(stringLiteral).Or(integer))
76+
from arg in Parse.Ref(() => _method.Or(stringLiteral).Or(integer).Or(boolean))
6777
select arg;
6878

69-
Parser<IRule[]> emptyArgument =
70-
from empty in Parse.String("()")
71-
select new IRule[0];
72-
7379
Parser<IOption<IEnumerable<IRule>>> arguments =
7480
from args in argument.Token().DelimitedBy(Parse.Char(',')).Optional()
7581
select args;
@@ -86,9 +92,8 @@ from args in arguments.Contained(lParenthesis, rParenthesis)
8692
_method =
8793
Parse.Ref(() =>
8894
from func in function
89-
from indexes in indices.Many()
95+
from indexes in bracketIndices.Or(dotIndices).Many()
9096
select (IRule) new AccessValueRule(func, indexes));
91-
// .Or(simpleStringRule);
9297

9398
Parser<ValueContainer> enclosedExpression =
9499
_method.Contained(
@@ -100,8 +105,7 @@ from indexes in indices.Many()
100105
from at in Parse.Char('@')
101106
from method in _method
102107
select method.Evaluate();
103-
104-
108+
105109
Parser<string> allowedString =
106110
from t in simpleString.Or(allowedCharacters).Many()
107111
select string.Concat(t);

ExpressionEngine/FlowRunnerDependencyExtension.cs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,18 @@
1-
using ExpressionEngine;
2-
using ExpressionEngine.Functions.Base;
1+
using ExpressionEngine.Functions.Base;
32
using ExpressionEngine.Functions.Implementations.CollectionFunctions;
43
using ExpressionEngine.Functions.Implementations.ConversionFunctions;
54
using ExpressionEngine.Functions.Implementations.LogicalComparisonFunctions;
65
using ExpressionEngine.Functions.Implementations.StringFunctions;
76
using Microsoft.Extensions.DependencyInjection;
8-
using Parser.ExpressionParser.Functions.Implementations.StringFunctions;
97

10-
namespace Parser
8+
namespace ExpressionEngine
119
{
1210
public static class FlowRunnerDependencyExtension
1311
{
1412
public static void AddExpressionEngine(this IServiceCollection services)
1513
{
16-
services.AddScoped<IExpressionEngine, ExpressionEngine.ExpressionEngine>();
17-
services.AddScoped<ExpressionGrammar>();
14+
services.AddScoped<IExpressionEngine, ExpressionEngine>();
15+
services.AddSingleton<ExpressionGrammar>();
1816

1917
AddStringFunctions(services);
2018
AddCollectionFunction(services);

ExpressionEngine/Functions/Implementations/StringFunctions/GuidFunction.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
using System;
22
using System.Linq;
3-
using ExpressionEngine;
43
using ExpressionEngine.Functions.Base;
54
using ExpressionEngine.Functions.CustomException;
6-
using ExpressionEngine.Functions.Implementations.StringFunctions;
75

8-
namespace Parser.ExpressionParser.Functions.Implementations.StringFunctions
6+
namespace ExpressionEngine.Functions.Implementations.StringFunctions
97
{
108
public class GuidFunction : Function
119
{

ExpressionEngine/ReadMe.md

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# Parser
2+
3+
This is the readme for the parser project which is a part of the FlowRunner.
4+
5+
6+
## BNF
7+
8+
This is the BNF, [Backus-Nuar form](https://da.wikipedia.org/wiki/Backus-Naur_form), of the language used in Power Automate / Logic Apps expressions.
9+
Their JSON definition can be found [here](https://docs.microsoft.com/en-us/azure/logic-apps/workflow-definition-language-functions-reference) and [here](https://docs.microsoft.com/en-us/azure/logic-apps/logic-apps-workflow-definition-language#functions) and the [json](https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json).
10+
11+
### Examples
12+
`@triggerOutputs()`
13+
14+
`@toLower('TEXT')`
15+
16+
`@toLower(toUpper('text'))`
17+
18+
`@variables('var')`
19+
20+
`@variables('array')[0]`
21+
22+
`@variables('object')['user']`
23+
24+
`@variables('object')[toLower('USER')]`
25+
26+
`@{toLower('USER')}@{toUpper('user')}`
27+
28+
`Name: @{variables('object')[toLower('USER')]}`
29+
30+
31+
```xml
32+
<input> ::= <expression> | <joinedString> | <atPrefixedString>
33+
<atPrefixedString> ::= <string> <!-- NO ( ) -->
34+
35+
<joinedString> ::= *(*<allowedString> *<enclosedExpression>)
36+
<allowedString> ::= *([A-Za-z] | [0-9] | [\_]) <!-- Add the correct characters -->
37+
38+
<expression> ::= "@" <method>
39+
<enclosedExpression> ::= "@{" <method> "}"
40+
41+
<method> ::= <function> *<operators>
42+
43+
<function> ::= <string> "(" [ <arguments> ] ")"
44+
<arguments> ::= <argument> *("," <argument>)
45+
<argument> ::= <method> | <string>
46+
47+
<operators> ::= *( [ <nullconditional> ] "[" <index> "]") | <dot>
48+
<nullconditional> ::= "?"
49+
<index> ::= <int> | <string> | <method>
50+
<dot> ::= "." <string>
51+
```
52+
53+
`string` contains a valid string including characters according to Azure Workflow definition language.
54+
55+
56+
## Special expressions
57+
Some expressions the user can define itself, since it is idempotent and does not use the engine state.
58+
59+
Some expression should not be user dependent, such as
60+
61+
* compose
62+
* outputs
63+
* triggerOutputs
64+
* variables
65+
66+
These are all expression which interact with data stored in the state.
67+
68+
69+
70+
Eventuelt skriv om hvordan det var sværet bare at gå igang, men efter du læste en artikel og lavede en BNF var det nemt. Dette var måske også fordi du vidste hvordan Sprache virkede og hvad der skete under moteren og aritklen gav dig en dybere forståelse af hvad man kan! :D
71+
72+
73+
## Extra observations
74+
### Null conditional
75+
We'll handle this, of course, and I've created a flow to figure out how to handle this :D
76+
77+
As seen in the currect version of owner grammer, we'll accept null conditional after a function or after a indexer. If the part before the null conditional is null, we'll stop executing and return??
78+
79+
The flow used to test is called: `null condtional` and is located in DG Lab 6; [link to flow](https://emea.flow.microsoft.com/manage/environments/934e6690-2c92-4a37-ab81-31be17f7724a/solutions/86491904-39dd-ea11-a813-000d3ab11761/flows/d40eaddf-55a2-4b98-87e3-c21c4fcfc57a).
80+
81+
Error (with out null condition):
82+
> InvalidTemplate. Unable to process template language expressions in action 'Respond_to_a_PowerApp_or_flow_-_Without_null_conditional' inputs at line '1' and column '2357': 'The template language expression 'variables('NullVariable')['first']' cannot be evaluated because property 'first' cannot be selected. Please see https://aka.ms/logicexpressions for usage details.'.
83+
84+
With null condtion, the answer is null and read as an empty string in some cases?

Test/Expression/StringFunctionTests.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
using ExpressionEngine;
22
using ExpressionEngine.Functions.Implementations.StringFunctions;
33
using NUnit.Framework;
4-
using Parser.ExpressionParser.Functions.Implementations.StringFunctions;
54

65
namespace Test.Expression
76
{

Test/ExpressionGrammarTest.cs

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
using System.Collections.Generic;
2+
using ExpressionEngine;
3+
using ExpressionEngine.Functions.Base;
4+
using NUnit.Framework;
5+
6+
namespace Test
7+
{
8+
public class ExpressionGrammarTest
9+
{
10+
private ExpressionGrammar _expressionGrammar;
11+
private DummyFunction _dummyFunction;
12+
13+
[SetUp]
14+
public void SetUp()
15+
{
16+
_dummyFunction = new DummyFunction(new ValueContainer(new Dictionary<string, ValueContainer>
17+
{
18+
{
19+
"prop1", new ValueContainer(new Dictionary<string, ValueContainer>
20+
{
21+
{
22+
"prop2", new ValueContainer(new Dictionary<string, ValueContainer>
23+
{
24+
{"prop3", new ValueContainer("value")}
25+
})
26+
}
27+
})
28+
}
29+
}));
30+
31+
var functions = new List<IFunction>
32+
{
33+
_dummyFunction
34+
};
35+
36+
_expressionGrammar = new ExpressionGrammar(functions);
37+
}
38+
39+
[Test]
40+
public void IndicesTest()
41+
{
42+
const string expressionString = "@dummyFunction()['prop1'].prop2['prop3']";
43+
44+
var res = _expressionGrammar.EvaluateToString(expressionString);
45+
46+
Assert.AreEqual("value", res);
47+
}
48+
49+
[Test]
50+
public void BooleanTest()
51+
{
52+
const string expressionString = "@dummyFunction(true, false)";
53+
54+
_expressionGrammar.EvaluateToValueContainer(expressionString);
55+
56+
Assert.NotNull(_dummyFunction._parameters);
57+
Assert.AreEqual(2, _dummyFunction._parameters.Length);
58+
var param1 = _dummyFunction._parameters[0];
59+
var param2 = _dummyFunction._parameters[1];
60+
Assert.NotNull(param1);
61+
Assert.NotNull(param2);
62+
Assert.AreEqual(ValueContainer.ValueType.Boolean, param1.Type());
63+
Assert.AreEqual(ValueContainer.ValueType.Boolean, param2.Type());
64+
Assert.AreEqual(true, param1.GetValue<bool>());
65+
Assert.AreEqual(false, param2.GetValue<bool>());
66+
}
67+
}
68+
69+
public class DummyFunction : Function
70+
{
71+
private readonly ValueContainer _valueContainer;
72+
internal ValueContainer[] _parameters;
73+
74+
public DummyFunction() : base("dummyFunction")
75+
{
76+
}
77+
78+
public DummyFunction(ValueContainer valueContainer) : base("dummyFunction")
79+
{
80+
_valueContainer = valueContainer;
81+
}
82+
83+
public override ValueContainer ExecuteFunction(params ValueContainer[] parameters)
84+
{
85+
_parameters = parameters;
86+
return _valueContainer;
87+
}
88+
}
89+
}

Test/ParserTest.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
using ExpressionEngine.Functions.CustomException;
44
using Microsoft.Extensions.DependencyInjection;
55
using NUnit.Framework;
6-
using Parser;
76

87

98
namespace Test

0 commit comments

Comments
 (0)