/// <summary> /// Starting at the given node, gets a list of <see cref="Expression"/>s that include and follow this node's references. /// </summary> internal static IEnumerable<Expression> FlattenExpressions(VisualProgram context, StatementReference firstStatement) { var list = new List<Expression>(); var currentNode = firstStatement.ResolveNode(context); while (currentNode is VisualStatement currentStatement) { list.Add(currentStatement.CreateExpression(context)); currentNode = currentStatement.GetCompilerNextStatement(context)?.ResolveNode(context); } return list; }
/// <summary> /// Creates an expression that sets the variable to the given expression value. /// </summary> /// <param name="context">The program context whose variable dictionary will be used.</param> /// <param name="variable">A reference to the variable.</param> /// <param name="value">The expression that represents the new value of the variable.</param> internal static Expression CreateSetterExpression(VisualProgram context, IVariableReference variable, Expression value) { variable.Validate(context); return(Call( context.Variables.compiledInstanceParameter, setVariableMethod, Constant(variable.Name, typeof(string)), Convert(value, typeof(object)) )); }
public override Expression CreateExpression(VisualProgram context) { var branches = NodeUtils.FlattenExpressions(context, TrueBranch, FalseBranch); return(Expression.IfThenElse( Condition.ResolveRequiredExpression(context), Expression.Block(branches.branch1Flattened), Expression.Block(branches.branch2Flattened) )); }
public override Expression CreateExpression(VisualProgram context) { // If a start value is provided, set the target variable to that value. If not provided, do not var init = Start.HasValue ? Variable.ResolveRequiredSetterExpression(context, Start.ResolveRequiredExpression(context)) : Expression.Empty(); // Store some re-used expressions to prevent re-resolving them var variableExpr = Variable.ResolveRequiredGetterExpression(context); var changeExpr = Change.ResolveExpression(context) ?? Expression.Constant(1d, typeof(double)); var endExpr = End.ResolveRequiredExpression(context); var loop = LoopFactory.CreateLoop( // Loop body Expression.Block( // Run the given body Body.ResolveStatement(context), // Update the variable by 'Change' amount Variable.ResolveRequiredSetterExpression( context, Expression.Add(variableExpr, changeExpr) ) ), // Loop condition Expression.Condition( // If the 'Change' value is >= 0... Expression.GreaterThanOrEqual( changeExpr, Expression.Constant(0d, typeof(double)) ), // Then the condition is whether the variable is less than or equal to the end Expression.LessThanOrEqual(variableExpr, endExpr), // Otherwise (if 'Change' is < 0) the condition is whether variable is greater than or equal to the end Expression.GreaterThanOrEqual(variableExpr, endExpr) ) ); // Return a block that sets the initial value then executes the loop return(Expression.Block(init, loop)); }
/// <summary> /// Compiles the entries in the given Program into delegates. /// Returns the delegates themselves and the delegate signatures (excluding the context parameter). /// </summary> private FunctionStore CompileEntryDelegates(VisualProgram program) { var progEntryNodes = program.Nodes.OfType <VisualEntry>().ToDictionary(ve => ve.VisualEntryId, ve => ve); return(program.Environment.EntryDefinitions.ToDictionary( entry => entry.Key, entry => { // If the program does not contain this entry node OR it exists but is not connected to any statements, then return no delegate if (!progEntryNodes.TryGetValue(entry.Value.Id, out var startNode) || !startNode.FirstStatement.HasValue) { return null; } // Create a parameter for each expected/defined parameter var parameters = entry.Value.Parameters.Map(Expression.Parameter); // Compile and return the lambda return Expression.Lambda( Expression.Block( // Before doing the main body of the entry function, make expressions to copy the incoming parameters to variables startNode.ParameterMap .Where(mapping => !string.IsNullOrWhiteSpace(mapping.Value)) // Exclude any that don't actually map to anything .Select(p => VariableAccessorFactory.CreateSetterExpression( program, VariableReference.Create(entry.Value.Parameters[p.Key], p.Value), parameters[p.Key] ) ).Concat( // Then do the actual main body NodeUtils.FlattenExpressions(program, startNode.FirstStatement) ) ), // All lambdas will get a context parameter // We also pass the defined parameters to the lambda so it knows what types to expect and what signature to have. // This needs to happen regardless or not of whether the parameters are actually used by the entry function as this determines the delegate's signature. new[] { program.Variables.compiledInstanceParameter }.Concat(parameters) ).Compile(); }, StringComparer.OrdinalIgnoreCase )); }
internal CompiledProgramFactory(VisualProgram program) { this.program = program; functions = CompileEntryDelegates(program); vars = program.Variables.ToDictionary(v => v.Name, v => v.Clone(), StringComparer.OrdinalIgnoreCase); // Create a new type that extends/implements the TExtends type typeBuilder = moduleBuilder.DefineType("Dynamic_" + Guid.NewGuid().ToString("N"), TypeAttributes.Class | TypeAttributes.AutoClass | TypeAttributes.AnsiClass | TypeAttributes.BeforeFieldInit | TypeAttributes.AutoLayout); if (typeof(TExtends).IsInterface) { typeBuilder.AddInterfaceImplementation(typeof(TExtends)); } else { typeBuilder.SetParent(typeof(TExtends)); } // Add the ICompiledInstanceBase (so long as the TExtends wasn't that) if (typeof(TExtends) != typeof(ICompiledInstanceBase)) { typeBuilder.AddInterfaceImplementation(typeof(ICompiledInstanceBase)); } // Generate the fields that will hold our compiled functions and variables functionsFieldBuilder = typeBuilder.DefineField(FunctionsFieldName, typeof(FunctionStore), FieldAttributes.Private | FieldAttributes.Static); varsFieldBuilder = typeBuilder.DefineField(VariablesFieldName, typeof(VariableStore), FieldAttributes.Private); GenerateConstructors(); GenerateDefaultMethods(); GenerateDelegateBindings(); GeneratePropertyBindings(); // Create the type programType = typeBuilder.CreateType(); // Set the value of the static functions field programType.GetField(FunctionsFieldName, BindingFlags.NonPublic | BindingFlags.Static).SetValue(null, functions); varsFieldInfo = programType.GetField(VariablesFieldName, BindingFlags.NonPublic | BindingFlags.Instance); }
/// <summary> /// Finds the next node in common in the two branches. Can be used to help optimise the generation of branching flow statements /// such as if conditions, etc. Returns null if there is no common node. /// </summary> internal static StatementReference? FindNextSharedNode(VisualProgram context, StatementReference branch1, StatementReference branch2) { // Early return if either branch doesn't have any attached nodes - there cannot be a shared node if there are no nodes. if (!branch1.HasValue || !branch2.HasValue) return null; // Get a flat list of all Guids in the first branch var nodesIn1 = new List<Guid>(); StatementReference? currentRef = branch1; while (currentRef.HasValue && currentRef.Value.HasValue) { nodesIn1.Add(currentRef?.nodeId!.Value); // Note that we use GetNextStatement here instead of NextStatement since, if it is a branch, we want the next shared statement of that branch currentRef = (currentRef?.ResolveNode(context) as VisualStatement)?.GetCompilerNextStatement(context); } // Search the second branch until we find the first occurance of the same node in the first branch currentRef = branch2; while (currentRef.HasValue && currentRef.Value.HasValue) { if (nodesIn1.Contains(currentRef.Value.nodeId!.Value)) return currentRef.Value; currentRef = (currentRef?.ResolveNode(context) as VisualStatement)?.GetCompilerNextStatement(context); } return null; }
public MainWindowModel() { Program = new VisualProgram(env => env .ConfigureNodes(n => n .IncludeDefault() .Exclude <VisualProgrammer.Core.Nodes.Debug.Print>() .Include <TraceStmt>() ) .ConfigureEntries(e => e .Add("demoEntry", "Demo Entry") .Add("demoEntry2", "Demo Entry 2") ).ConfigureLockedVariables(v => v .Add <int>("someIntVar", 20) .Add <double>("somenewLockedDouble") ), new VisualNodeCollection(), new VariableCollection { { "someStringVar", typeof(string), "Hello from the someStringVar!" }, { "otherStrVar", typeof(string), "Greetings from the otherStrVar!" }, { "someIntVar", typeof(int), 10 } } ); }
public override Expression CreateExpression(VisualProgram context) => opMap[SelectedOperation]( LHS.ResolveRequiredExpression(context), RHS.ResolveRequiredExpression(context) );
public override Expression CreateExpression(VisualProgram context) => Expression.Condition( Condition.ResolveRequiredExpression(context), A.ResolveRequiredExpression(context), B.ResolveRequiredExpression(context) );
public override Expression CreateExpression(VisualProgram context) => Expression.Constant(Value, typeof(TValue));
public override Expression CreateExpression(VisualProgram context) => Variable.ResolveRequiredSetterExpression(context, opMap[SelectedOperation]( Variable.ResolveRequiredGetterExpression(context), Value.ResolveRequiredExpression(context) ) );
public override Expression CreateExpression(VisualProgram context) => Expression.Call(null, write, PrintValue.ResolveRequiredExpression(context));
public override Expression CreateExpression(VisualProgram context) => Variable.ResolveRequiredSetterExpression(context, Value.ResolveRequiredExpression(context));
public override Expression CreateExpression(VisualProgram context) => Expression.Call( concat, First.ResolveExpression(context) ?? Expression.Constant(""), Second.ResolveExpression(context) ?? Expression.Constant("") );
/// <summary> /// During compilation, this method will be used to get the next statement after this once. /// For most circumstances, the default implementation is fine, but for branching statements this will likely return /// the first shared node of the branches. /// </summary> public virtual StatementReference?GetCompilerNextStatement(VisualProgram context) => NextStatement;
public override Expression CreateExpression(VisualProgram context) => Expression.Call( Value.ResolveRequiredExpression(context), typeof(TSource).GetMethod("ToString", Array.Empty <Type>()) );
public override Expression CreateExpression(VisualProgram context) => LoopFactory.CreateLoop( Body.ResolveStatement(context), Condition.ResolveRequiredExpression(context) );
/// <summary> /// Starting at the given branch entry points, gets a list of <see cref="Expression"/>s that include and follow the branch, UNTIL a shared /// node is found. The shared node will not be included in the returned lists. /// </summary> internal static (IEnumerable<Expression> branch1Flattened, IEnumerable<Expression> branch2Flattened) FlattenExpressions(VisualProgram context, StatementReference branch1, StatementReference branch2) { var firstShared = FindNextSharedNode(context, branch1, branch2); // Early return if there is no shared node if (firstShared == null) return (FlattenExpressions(context, branch1), FlattenExpressions(context, branch2)); List<Expression> flatten(StatementReference start) { var list = new List<Expression>(); StatementReference? curRef = start; while (curRef.HasValue && curRef.Value.HasValue && curRef != firstShared && curRef.Value.ResolveNode(context) is VisualStatement statement) { list.Add(statement.CreateExpression(context)); curRef = statement.GetCompilerNextStatement(context); } return list; } return (flatten(branch1), flatten(branch2)); }