Skip to content

Commit

Permalink
Merge pull request #19 from thygesteffensen/issue/18/apply
Browse files Browse the repository at this point in the history
Issue/18/apply
  • Loading branch information
thygesteffensen authored Dec 28, 2020
2 parents ce8a97e + 6fc1095 commit a9d1da4
Show file tree
Hide file tree
Showing 19 changed files with 662 additions and 35 deletions.
8 changes: 7 additions & 1 deletion PowerAutomateMockUp/ExpressionParser/ExpressionEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
public interface IExpressionEngine
{
string Parse(string input);
ValueContainer ParseToValueContainer(string input);
}

public class ExpressionEngine : IExpressionEngine
Expand All @@ -16,7 +17,12 @@ public ExpressionEngine(ExpressionGrammar grammar)

public string Parse(string input)
{
return _expressionGrammar.Evaluate(input);
return _expressionGrammar.EvaluateToString(input);
}

public ValueContainer ParseToValueContainer(string input)
{
return _expressionGrammar.EvaluateToValueContainer(input);
}
}
}
27 changes: 16 additions & 11 deletions PowerAutomateMockUp/ExpressionParser/ExpressionGrammar.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace Parser.ExpressionParser
public class ExpressionGrammar
{
private readonly Parser<IRule> _method;
private readonly Parser<string> _input;
private readonly Parser<ValueContainer> _input;

public ExpressionGrammar(IEnumerable<IFunction> functions)
{
Expand Down Expand Up @@ -95,43 +95,48 @@ from indexes in indices.Many()
select (IRule) new AccessValueRule(func, indexes));
// .Or(simpleStringRule);

Parser<string> enclosedExpression =
Parser<ValueContainer> enclosedExpression =
_method.Contained(
Parse.String("@{"),
Parse.Char('}'))
.Select(x => x.Evaluate().GetValue<string>());
.Select(x => x.Evaluate());

Parser<string> expression =
Parser<ValueContainer> expression =
from at in Parse.Char('@')
from method in _method
select method.Evaluate().GetValue<string>();
select method.Evaluate();


Parser<string> allowedString =
from t in simpleString.Or(allowedCharacters).Many()
select string.Concat(t);

Parser<string> joinedString =
Parser<ValueContainer> joinedString =
from e in (
from preFix in allowedString
from exp in enclosedExpression.Optional()
select exp.IsEmpty ? preFix : preFix + exp.Get())
.Many()
select string.Concat(e);
select new ValueContainer(string.Concat(e));

Parser<string> charPrefixedString =
Parser<ValueContainer> charPrefixedString =
from at in Parse.Char('@')
from str in Parse.LetterOrDigit.Many().Text().Except(Parse.Chars('{', '@'))
select str;
select new ValueContainer(str);

_input = expression.Or(charPrefixedString).Or(joinedString);
}

public string Evaluate(string input)
public string EvaluateToString(string input)
{
var output = _input.Parse(input);

return output;
return output.GetValue<string>();
}

public ValueContainer EvaluateToValueContainer(string input)
{
return _input.Parse(input);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public static void AddFlowRunner(this IServiceCollection services)
services.AddSingleton<FlowRunner>();

services.AddSingleton<ActionExecutorFactory>();
services.AddSingleton<ScopeDepthManager>();
services.AddSingleton<IScopeDepthManager, ScopeDepthManager>();

services.AddScoped<IState, State>();
services.AddScoped<IVariableRetriever>(x => x.GetRequiredService<IState>());
Expand All @@ -28,15 +28,19 @@ public static void AddFlowRunner(this IServiceCollection services)
services.AddTransient<IFunction, ConcatFunction>();
services.AddTransient<IFunction, ToLower>();
services.AddTransient<IFunction, ToUpperFunction>();

services.AddTransient<IFunction, VariablesFunction>();
services.AddTransient<IFunction, OutputsFunction>();
services.AddTransient<IFunction, TriggerOutputsFunctions>();
services.AddTransient<IFunction, ItemsFunction>();

services.AddTransient<IFunction, TrimFunction>();
services.AddTransient<IFunction, LengthFunction>();

services.AddFlowActionByFlowType<IfActionExecutor>("If");
services.AddFlowActionByFlowType<ScopeActionExecutor>("Scope");
services.AddFlowActionByFlowType<TerminateActionExecutor>("Terminate");
services.AddFlowActionByFlowType<ForEachActionExecutor>("Foreach");

services.AddLogging();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System;
using Parser.ExpressionParser.Functions.Base;
using Parser.ExpressionParser.Functions.CustomException;

namespace Parser.ExpressionParser.Functions.Storage
{
public class ItemsFunction : Function
{
private readonly IState _variableRetriever;

public ItemsFunction(IState variableRetriever) : base("items")
{
_variableRetriever = variableRetriever ?? throw new ArgumentNullException(nameof(variableRetriever));
}

public override ValueContainer ExecuteFunction(params ValueContainer[] parameters)
{
if (parameters.Length != 1)
{
throw new ArgumentError(parameters.Length > 1 ? "Too many arguments" : "Too few arguments");
}

var variableName = parameters[0].GetValue<string>();
// TODO: Maybe implement another storage option?
var value = _variableRetriever.GetOutputs($"item_{variableName}");

return value;
}
}
}
49 changes: 48 additions & 1 deletion PowerAutomateMockUp/ExpressionParser/ValueContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@ public ValueContainer(float floatValue)
_type = ValueType.Float;
}

public ValueContainer(double floatValue)
{
_value = floatValue;
_type = ValueType.Float;
}

public ValueContainer(int intValue)
{
_value = intValue;
Expand Down Expand Up @@ -211,12 +217,13 @@ public Dictionary<string, ValueContainer> AsDict()
{
return GetValue<Dictionary<string, ValueContainer>>();
}

throw new PowerAutomateMockUpException("Can't get none object value container as dict.");
}

private ValueContainer JsonToValueContainer(JToken json)
{
if (json.GetType() == typeof(JObject))
if (json is JObject jObject)
{
var dictionary = json.ToDictionary(pair => ((JProperty) pair).Name, token =>
{
Expand All @@ -236,9 +243,49 @@ private ValueContainer JsonToValueContainer(JToken json)
return new ValueContainer(dictionary);
}

if (json is JArray jArray)
{
return jArray.Count > 0 ? new ValueContainer() : JArrayToValueContainer(jArray);
}

throw new Exception();
}

private ValueContainer JArrayToValueContainer(JArray json)
{
var list = new List<ValueContainer>();

foreach (var jToken in json)
{
if (jToken.GetType() != typeof(JValue))
{
throw new PowerAutomateMockUpException("Json can only contain arrays of primitive types.");
}

var t = (JValue) jToken;
switch (t.Value)
{
case int i:
list.Add(new ValueContainer(i));
break;
case string s:
list.Add(new ValueContainer(s));
break;
case bool b:
list.Add(new ValueContainer(b));
break;
case double d:
list.Add(new ValueContainer(d));
break;
default:
throw new PowerAutomateMockUpException(
$"Type {t.Value.GetType()} is not recognized when converting Json to ValueContainer.");
}
}

return new ValueContainer(list);
}


public override string ToString()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System.Threading.Tasks;

namespace Parser.FlowParser.ActionExecutors
{
public interface IScopeActionExecutor
{
public Task<ActionResult> ExitScope(ActionStatus scopeStatus);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json.Linq;
using Parser.ExpressionParser;

namespace Parser.FlowParser.ActionExecutors.Implementations
{
public class ForEachActionExecutor : DefaultBaseActionExecutor, IScopeActionExecutor
{
private readonly IState _state;
private readonly IScopeDepthManager _scopeDepthManager;
private readonly ILogger<ForEachActionExecutor> _logger;
private readonly IExpressionEngine _expressionEngine;

private JProperty[] _actionDescriptions;
private string _firstScopeActionName;

private List<ValueContainer> _items;

public ForEachActionExecutor(
IState state,
IScopeDepthManager scopeDepthManager,
ILogger<ForEachActionExecutor> logger,
IExpressionEngine expressionEngine)
{
_state = state ?? throw new ArgumentNullException(nameof(state));
_scopeDepthManager = scopeDepthManager ?? throw new ArgumentNullException(nameof(scopeDepthManager));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_expressionEngine = expressionEngine ?? throw new ArgumentNullException(nameof(expressionEngine));

_items = new List<ValueContainer>();
}

public override Task<ActionResult> Execute()
{
_logger.LogInformation("Entered foreach...");

if (Json == null)
{
throw new PowerAutomateMockUpException($"Json cannot be null - cannot execute {ActionName}.");
}

var runOn = (Json.SelectToken("$..foreach") ??
throw new InvalidOperationException("Json must contain foreach token.")).Value<string>();
var values = _expressionEngine.ParseToValueContainer(runOn);

if (values.Type() != ValueContainer.ValueType.Array)
return Task.FromResult(new ActionResult
{
// TODO: Figure out what happens when you apply for each on non array values
ActionStatus = ActionStatus.Failed
});

SetupForEach(values);

UpdateScopeAndSetItemValue();

return Task.FromResult(new ActionResult {NextAction = _firstScopeActionName});
}

private void SetupForEach(ValueContainer values)
{
var scopeActionDescriptions = (Json.SelectToken("$.actions") ??
throw new InvalidOperationException("Json must contain actions token."))
.OfType<JProperty>();
_actionDescriptions = scopeActionDescriptions as JProperty[] ?? scopeActionDescriptions.ToArray();
_firstScopeActionName = _actionDescriptions.First(ad =>
!(ad.Value.SelectToken("$.runAfter") ??
throw new InvalidOperationException("Json must contain runAfter token.")).Any()).Name;

// TODO: Add scope relevant storage to store stuff like this, which cannot interfere with the state.
_items = values.GetValue<IEnumerable<ValueContainer>>().ToList();
}

private void UpdateScopeAndSetItemValue()
{
_scopeDepthManager.Push(ActionName, _actionDescriptions, this);

_state.AddOutputs($"item_{ActionName}", _items.First());
_items = _items.Skip(1).ToList();
}


public Task<ActionResult> ExitScope(ActionStatus scopeStatus)
{
if (_items.Count > 0)
{
_logger.LogInformation("Continuing foreach.");

UpdateScopeAndSetItemValue();

return Task.FromResult(new ActionResult {NextAction = _firstScopeActionName});
}
else
{
_logger.LogInformation("Exited foreach...");
return Task.FromResult(new ActionResult());
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ namespace Parser.FlowParser.ActionExecutors.Implementations
{
public class ScopeActionExecutor : DefaultBaseActionExecutor
{
private readonly ScopeDepthManager _scopeDepthManager;
private readonly IScopeDepthManager _scopeDepthManager;

public ScopeActionExecutor(ScopeDepthManager scopeDepthManager)
public ScopeActionExecutor(IScopeDepthManager scopeDepthManager)
{
_scopeDepthManager = scopeDepthManager ?? throw new ArgumentNullException(nameof(scopeDepthManager));
}
Expand All @@ -26,9 +26,4 @@ public override Task<ActionResult> Execute()
return Task.FromResult(new ActionResult {NextAction = firstScopeAction.Name});
}
}

public interface IScopeActionExecutor
{
public Task<ActionResult> ExitScope(ActionStatus scopeStatus);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,7 @@ protected override void ProcessJson()
Parameters = new ValueContainer(new Dictionary<string, ValueContainer>());
foreach (var keyValuePar in content.Parameters)
{
// TODO: Here is an use case where the engine could have just returned a Value Container instead!
Parameters[keyValuePar.Key] = new ValueContainer(_expressionEngine.Parse(keyValuePar.Value), true);
Parameters[keyValuePar.Key] = _expressionEngine.ParseToValueContainer(keyValuePar.Value);
}
}

Expand Down
4 changes: 2 additions & 2 deletions PowerAutomateMockUp/FlowParser/FlowRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ public class FlowRunner
{
private readonly IState _state;
private readonly FlowSettings _flowRunnerSettings;
private readonly ScopeDepthManager _scopeManager;
private readonly IScopeDepthManager _scopeManager;
private readonly ActionExecutorFactory _actionExecutorFactory;
private JProperty _trigger;

public FlowRunner(
IState state,
ScopeDepthManager scopeDepthManager,
IScopeDepthManager scopeDepthManager,
IOptions<FlowSettings> flowRunnerSettings,
ActionExecutorFactory actionExecutorFactory)
{
Expand Down
Loading

0 comments on commit a9d1da4

Please sign in to comment.