Exemplo n.º 1
0
        /// <summary>
        /// Constructs a method which gets the parameters list for the specified property.
        /// </summary>
        /// <param name="sb">The class writer.</param>
        /// <param name="info">The property info.</param>
        /// <param name="additionParameterMethods">The list of methods which return addition parameter names.</param>
        /// <param name="classAddParams">True if the class has additional parameters method.</param>
        private static void GetParametersMethod(
            ClassWriter sb,
            PropertyInfo info,
            IReadOnlyCollection <string> additionParameterMethods,
            bool classAddParams)
        {
            string VarToString((string Name, string Type) var)
            {
                return($"new CompiledVariable({SurroundInQuotes(var.Name)}, typeof({var.Type}))");
            }

            string?matchingAdditionalParamtersMethod =
                additionParameterMethods.FirstOrDefault(x => x.Equals($"{ClassInfo.AdditionalParamtersMethodPrefix}{info.Name}"));

            using var braces = sb.AddMethod(
                      new MethodDefinition($"GetParameters{info.Name}")
            {
                SummaryDoc = $"Gets all parameters which should be presented to the {info.Name} expression.",
                ReturnsDoc = "All parameters needed by the expression.",
                Access     = AccessLevel.Public,
                Type       = "IList<CompiledVariable>",
            });

            sb.AppendLine($"List<CompiledVariable> parameters = new List<CompiledVariable> {{ {string.Join(", ", info.Variables.Select(VarToString))} }};");

            if (matchingAdditionalParamtersMethod != null)
            {
                sb.AppendLine($"parameters.AddRange(this.{matchingAdditionalParamtersMethod}());");
            }

            if (classAddParams)
            {
                sb.AppendLine($"parameters.AddRange(this.Combined{ClassInfo.AdditionalParamtersForClassMethod}());");
            }

            // Reassign the parameters to have the reassigned types if needed.
            sb.AppendLine("List<CompiledVariable> reassignedParameters = new List<CompiledVariable>();");
            sb.AppendLine("var reassignments = this.CombinedVariableTypeReassignments();");
            sb.AppendLine("foreach (var parm in parameters)");
            sb.StartBrace();
            sb.AppendLine("if (reassignments.TryGetValue(parm.Name, out Type? newType))");
            sb.StartBrace();
            sb.AppendLine("reassignedParameters.Add(new CompiledVariable(parm.Name, newType));");
            sb.EndBrace();
            sb.AppendLine("else");
            sb.StartBrace();
            sb.AppendLine("reassignedParameters.Add(parm);");
            sb.EndBrace();
            sb.EndBrace();

            sb.AppendLine("return reassignedParameters;");
        }
        /// <summary>
        /// Creates the method which activates all Lazy fields so compile everything.
        /// </summary>
        /// <param name="sb">The class writer.</param>
        /// <param name="classInfo">The class info.</param>
        private static void CompileMethod(ClassWriter sb, ClassInfo classInfo)
        {
            using var braces = sb.AddMethod(
                      new MethodDefinition("Compile")
            {
                Access   = AccessLevel.Public,
                Override = true,
            });

            sb.AppendLine("if (this.Compiler == null)");
            sb.StartBrace();
            sb.AppendLine("throw new InvalidOperationException(\"Definition must be attached before Compile is called.\");");
            sb.EndBrace();
            sb.AppendLine();

            sb.AppendLine("base.Compile();");

            foreach (PropertyInfo prop in classInfo.CompiledProps)
            {
                sb.AppendLine($"_ = this.compiledCondition{prop.Name}?.Value;");
            }

            foreach (PropertyInfo prop in classInfo.CompiledDictionaryProps)
            {
                sb.AppendLine($"this.compiledCondition{prop.Name}?.ToList().ForEach(x => _ = x.Value?.Value);");
            }

            foreach (var prop in classInfo.DefinitionProps)
            {
                sb.AppendLine($"this.{prop.Name}?.Compile();");
            }

            foreach (var prop in classInfo.DefinitionArrayProps)
            {
                sb.AppendLine($"foreach (var value in this.{prop.Name})");
                sb.StartBrace();
                sb.AppendLine($"value.Compile();");
                sb.EndBrace();
                sb.AppendLine();
            }

            foreach (var prop in classInfo.DefinitionDictionaryProps)
            {
                sb.AppendLine($"foreach (var value in this.{prop.Name})");
                sb.StartBrace();
                sb.AppendLine($"value.Value.Compile();");
                sb.EndBrace();
                sb.AppendLine();
            }
        }
        /// <summary>
        /// Creates the method which runs the condition evalulate method.
        /// </summary>
        /// <param name="sb">The class writer.</param>
        /// <param name="info">The property info.</param>
        /// <param name="additionParameterMethods">The list of additional parameter methods.</param>
        /// <param name="classAddParams">True if the class has additional parameters method.</param>
        private static void EvalDictionaryConditionMethod(
            ClassWriter sb,
            PropertyInfo info,
            IReadOnlyCollection <string> additionParameterMethods,
            bool classAddParams)
        {
            string?matchingAdditionalParamtersMethod =
                additionParameterMethods.FirstOrDefault(x => x.Equals($"{ClassInfo.AdditionalParamtersMethodPrefix}{info.Name}", StringComparison.Ordinal));

            List <ParamDef> allParameters = new List <ParamDef>()
            {
                new ParamDef("string", "key", "The key, in the condition dictionary, to evaluate."),
                new ParamDef("Random", "rdm", "The random number generator"),
            };

            allParameters.AddRange(info.Variables.Select(v =>
                                                         new ParamDef($"LegendsGenerator.Contracts.Things.BaseThing", v.Name, "One of the variables which will be passed to the compiled condition.")).ToList());

            if (matchingAdditionalParamtersMethod != null || classAddParams)
            {
                allParameters.Add(new ParamDef(
                                      "IDictionary<string, LegendsGenerator.Contracts.Things.BaseThing>",
                                      "additionalParameters",
                                      $"Additional parameters as defined by AdditionalParametersForClass and AdditionalParametersFor{info.Name} methods, or the upstream additional parameters."));
            }

            using var braces = sb.AddMethod(
                      new MethodDefinition($"Eval{info.Name}")
            {
                SummaryDoc = $"Evaluates the expressions in the {info.Name} property with the given parameters.",
                ReturnsDoc = "The result of evaluation.",
                Access     = info.Protected ? AccessLevel.Protected : AccessLevel.Public,
                Type       = info.ReturnType,
                Parameters = allParameters,
            });

            sb.AppendLine($"if (this.Compiler == null || this.compiledCondition{info.Name} == null)");
            sb.StartBrace();
            sb.AppendLine("throw new InvalidOperationException(\"Definition must be attached before any Eval method is called.\");");
            sb.EndBrace();
            sb.AppendLine();

            sb.AddDictionary(
                "Dictionary<string, LegendsGenerator.Contracts.Things.BaseThing>",
                "param",
                info.Variables.Select(x => x.Name).ToDictionary(x => x, x => x));

            if (matchingAdditionalParamtersMethod != null || classAddParams)
            {
                sb.AppendLine("foreach(var additionalParameter in additionalParameters)");
                sb.StartBrace();
                sb.AppendLine("param[additionalParameter.Key] = additionalParameter.Value;");
                sb.EndBrace();
                sb.AppendLine();
            }

            sb.AppendLine("try");
            sb.StartBrace();
            sb.AppendLine($"return this.compiledCondition{info.Name}[key].Value.Evaluate(rdm, param);");
            sb.EndBrace();
            sb.AppendLine("catch (Exception ex)");
            sb.StartBrace();
            sb.AppendLine($"throw this.GetConditionException($\"{info.Name}{{key}}\", ex);");
            sb.EndBrace();
        }
        /// <summary>
        /// Adds the attach override to the source file.
        /// </summary>
        /// <param name="sb">The class writer.</param>
        /// <param name="classInfo">The class info.</param>
        private static void AttachMethod(ClassWriter sb, ClassInfo classInfo)
        {
            using var braces = sb.AddMethod(
                      new MethodDefinition("Attach")
            {
                Access     = AccessLevel.Public,
                Override   = true,
                Parameters = new ParamDef[]
                {
                    new ParamDef("IConditionCompiler", "compiler", "The condition compiler."),
                    new ParamDef("BaseDefinition?", "upsteamDefinition", "null", "The upstream definition."),
                },
            });

            sb.AppendLine("base.Attach(compiler, upsteamDefinition);");
            foreach (var prop in classInfo.CompiledProps)
            {
                sb.AppendLine($"if (this.{prop.Name} != null)");
                sb.StartBrace();
                sb.AppendLine($"this.compiledCondition{prop.Name} = ");
                sb.AppendLine($"   new Lazy<ICompiledCondition<{prop.ReturnType}>>(() => this.CreateCondition<{prop.ReturnType}>(this.{prop.Name}, {prop.AsFormattedText.ToString().ToLower()}, this.{prop.Name}_IsComplex, this.GetParameters{prop.Name}()));");
                sb.EndBrace();
            }

            foreach (var prop in classInfo.CompiledDictionaryProps)
            {
                sb.AppendLine($"foreach (var entry in this.{prop.Name})");
                sb.StartBrace();
                sb.AppendLine($"if (this.{prop.Name}[entry.Key] != null)");
                sb.StartBrace();
                sb.AppendLine($"this.compiledCondition{prop.Name}[entry.Key] = ");
                sb.AppendLine($"   new Lazy<ICompiledCondition<{prop.ReturnType}>>(() => this.CreateCondition<{prop.ReturnType}>(this.{prop.Name}[entry.Key], {prop.AsFormattedText.ToString().ToLower()}, this.{prop.Name}_IsComplex, this.GetParameters{prop.Name}()));");
                sb.EndBrace();
                sb.EndBrace();
                sb.AppendLine();
            }

            foreach (var prop in classInfo.DefinitionProps)
            {
                sb.AppendLine($"this.{prop.Name}?.Attach(compiler, this);");
            }

            foreach (var prop in classInfo.DefinitionArrayProps)
            {
                sb.AppendLine($"foreach (var value in this.{prop.Name})");
                sb.StartBrace();
                sb.AppendLine($"value.Attach(compiler, this);");

                sb.EndBrace();
                sb.AppendLine();
            }

            foreach (var prop in classInfo.DefinitionDictionaryProps)
            {
                sb.AppendLine($"foreach (var value in this.{prop.Name})");
                sb.StartBrace();
                sb.AppendLine($"value.Value.Attach(compiler, this);");

                sb.EndBrace();
                sb.AppendLine();
            }
        }