public void Load() { Log.Info("Loading rules"); using (var progress = Display.StartProgress("Loading rules")) { long total = context.Plugins.Count; long current = 0; foreach (var pluginFileName in context.Plugins.Select(p => p.FileName)) { bool retry; do { retry = false; try { var compiler = new RuleCompiler(this, pluginFileName); string path = Path.Combine(Program.ProgramFolder, Program.ProgramRulesFolder, RulesFolder, pluginFileName); var files = Context.DataFileProvider.FindDataFiles(path, "*.rules").ToArray(); total += files.Length; foreach (var file in files) { using (var stream = file.Open()) { bool isDebugModeEnabled = DebugAll || pluginFileName.Equals(DebugPluginFileName, StringComparison.OrdinalIgnoreCase) && (DebugRuleFileName == null || Path.GetFileName(file.Name).Equals(DebugRuleFileName, StringComparison.OrdinalIgnoreCase)); int count = 0; using (RuleReader reader = new RuleReader(stream)) { foreach (var entry in reader.ReadRules()) { if (entry.Select == null && entry.Update == null && entry.Inserts.Count() == 0) { Log.Warning("Rule {0} in file {1} ignored because it lacks any operation", entry.Name, pluginFileName); continue; } var metadata = new RuleMetadata() { PluginFileName = pluginFileName, RuleFileName = Path.GetFileName(file.Name), Name = entry.Name, Description = entry.Description, }; Log.Fine("Loading rule {0}\\{1}@{2}", metadata.PluginFileName, metadata.RuleFileName, metadata.Name); try { compiler.Add(entry, metadata, isDebugModeEnabled); } catch (IllegalTokenException ex) { Display.ShowProblems("Illegal Token", ex.ToString(), new Problem() { Message = ex.Message, File = file.GetRelativePath(), Solution = string.Format("Please avoid using the following tokens in rule code: {0}", string.Join(", ", RuleCompiler.GetIllegalCodeTokens())) }); throw; } count++; } } Log.Fine("Loaded {0} rule(s) from file {1}", count, stream.Name); } progress.Update(current, total, string.Format("{0}\\{1}", pluginFileName, Path.GetFileName(file.Name))); } if (compiler.HasRules) { try { compiler.CompileAll(); } catch (CompilerException ex) { StringBuilder text = new StringBuilder(); List <Problem> problems = new List <Problem>(); foreach (System.CodeDom.Compiler.CompilerError error in ex.Errors) { if (!error.IsWarning) { text.AppendLine(error.ToString()); problems.Add(new Problem() { Message = string.Format("{0}: {1}", error.ErrorNumber, error.ErrorText), File = DataFile.GetRelativePath(error.FileName), Line = error.Line, Column = error.Column, Solution = RuleCompiler.GetCompilerErrorHint(error) }); } } text.Append(ex.ToString()); Display.ShowProblems("Compiler Error(s)", text.ToString(), problems.ToArray()); throw ex; } rules.Add(pluginFileName, new List <IRule>(compiler.CompiledRules)); } } catch (Exception ex) { Log.Error("Error occured while loading rules for plugin {0} with message: {1}", pluginFileName, ex.Message); Log.Fine(ex.ToString()); // Depending on the kind of error, offer a choice to reload rules for this plugin // Rules can be reloaded only if illegal tokens have been detected or when compilation has failed ChoiceOption result = ChoiceOption.Cancel; if (ex.GetType() == typeof(IllegalTokenException) || ex.GetType() == typeof(CompilerException)) { result = Display.Choice(string.Format("Try loading rules for plugin {0} again?", pluginFileName), ChoiceOption.Yes, ChoiceOption.Continue, ChoiceOption.Cancel); } else { result = Display.Choice("Continue loading rules?", ChoiceOption.Continue, ChoiceOption.Cancel); } if (result == ChoiceOption.Yes) { Log.Info("Rules for plugin {0} will be reloaded.", pluginFileName); retry = true; } else if (result == ChoiceOption.Continue) { Log.Warning("Rules for plugin {0} skipped because an error occured: {1} ", pluginFileName, ex.Message); } else { Log.Warning("Rule loading has been aborted."); throw new UserAbortException("Rule loading has been aborted by the user."); } } finally { Display.ClearProblems(); } } while (retry); } current++; } }
public void Load() { Log.Info("Loading rules"); using (var progress = Display.StartProgress("Loading rules")) { long total = context.Plugins.Count; long current = 0; foreach (var pluginFileName in context.Plugins.Select(p => p.FileName)) { bool retry; do { retry = false; try { var compiler = new RuleCompiler(this, pluginFileName); string path = Path.Combine(Program.ProgramFolder, Program.ProgramRulesFolder, RulesFolder, pluginFileName); var files = Context.DataFileProvider.FindDataFiles(path, "*.rules").ToArray(); total += files.Length; foreach (var file in files) { using (var stream = file.Open()) { bool isDebugModeEnabled = DebugAll || pluginFileName.Equals(DebugPluginFileName, StringComparison.OrdinalIgnoreCase) && (DebugRuleFileName == null || Path.GetFileName(file.Name).Equals(DebugRuleFileName, StringComparison.OrdinalIgnoreCase)); int count = 0; using (RuleReader reader = new RuleReader(stream)) { foreach (var entry in reader.ReadRules()) { if (entry.Select == null && entry.Update == null && entry.Inserts.Count() == 0) { Log.Warning("Rule {0} in file {1} ignored because it lacks any operation", entry.Name, pluginFileName); continue; } var metadata = new RuleMetadata() { PluginFileName = pluginFileName, RuleFileName = Path.GetFileName(file.Name), Name = entry.Name, Description = entry.Description, }; Log.Fine("Loading rule {0}\\{1}@{2}", metadata.PluginFileName, metadata.RuleFileName, metadata.Name); try { compiler.Add(entry, metadata, isDebugModeEnabled); } catch (IllegalTokenException ex) { Display.ShowProblems("Illegal Token", ex.ToString(), new Problem() { Message = ex.Message, File = file.GetRelativePath(), Solution = string.Format("Please avoid using the following tokens in rule code: {0}", string.Join(", ", RuleCompiler.GetIllegalCodeTokens())) }); throw; } count++; } } Log.Fine("Loaded {0} rule(s) from file {1}", count, stream.Name); } progress.Update(current, total, string.Format("{0}\\{1}", pluginFileName, Path.GetFileName(file.Name))); } if (compiler.HasRules) { try { compiler.CompileAll(); } catch (CompilerException ex) { StringBuilder text = new StringBuilder(); List<Problem> problems = new List<Problem>(); foreach (System.CodeDom.Compiler.CompilerError error in ex.Errors) { if (!error.IsWarning) { text.AppendLine(error.ToString()); problems.Add(new Problem() { Message = string.Format("{0}: {1}", error.ErrorNumber, error.ErrorText), File = DataFile.GetRelativePath(error.FileName), Line = error.Line, Column = error.Column, Solution = RuleCompiler.GetCompilerErrorHint(error) }); } } text.Append(ex.ToString()); Display.ShowProblems("Compiler Error(s)", text.ToString(), problems.ToArray()); throw ex; } rules.Add(pluginFileName, new List<IRule>(compiler.CompiledRules)); } } catch (Exception ex) { Log.Error("Error occured while loading rules for plugin {0} with message: {1}", pluginFileName, ex.Message); Log.Fine(ex.ToString()); // Depending on the kind of error, offer a choice to reload rules for this plugin // Rules can be reloaded only if illegal tokens have been detected or when compilation has failed ChoiceOption result = ChoiceOption.Cancel; if (ex.GetType() == typeof(IllegalTokenException) || ex.GetType() == typeof(CompilerException)) { result = Display.Choice(string.Format("Try loading rules for plugin {0} again?", pluginFileName), ChoiceOption.Yes, ChoiceOption.Continue, ChoiceOption.Cancel); } else { result = Display.Choice("Continue loading rules?", ChoiceOption.Continue, ChoiceOption.Cancel); } if (result == ChoiceOption.Yes) { Log.Info("Rules for plugin {0} will be reloaded.", pluginFileName); retry = true; } else if (result == ChoiceOption.Continue) { Log.Warning("Rules for plugin {0} skipped because an error occured: {1} ", pluginFileName, ex.Message); } else { Log.Warning("Rule loading has been aborted."); throw new UserAbortException("Rule loading has been aborted by the user."); } } finally { Display.ClearProblems(); } } while (retry); } current++; } }
public void Add(RuleEntry entry, RuleMetadata metadata, bool debug) { string className = "Rule_" + ruleClassCounter++; var unit = new RuleCompilationUnit() { ClassName = className, ClassFullName = string.Format("{0}.{1}", NamespaceName, className), IsDebugModeEnabled = debug, Rule = new CompiledRule(engine, metadata) { From = entry.From == null ? FormKind.Any : (FormKind)entry.From.FormKind }, }; string comment = string.Format("//\n// Source file for rule {0}\\{1}@{2} by {3}\n//\n", metadata.PluginFileName, metadata.RuleFileName, entry.Name, Program.GetProgramFullVersionInfo()); CodeBuilder builder = new CodeBuilder(NamespaceName, className, comment); builder.Usings.Add("Patcher.Rules.Compiled.Helpers"); builder.Usings.Add("Patcher.Rules.Compiled.Helpers." + engine.Context.GameTitle); builder.Usings.Add("Patcher.Rules.Compiled.Extensions"); builder.Usings.Add("Patcher.Rules.Compiled.Extensions." + engine.Context.GameTitle); builder.Usings.Add("Patcher.Rules.Compiled.Constants"); builder.Usings.Add("Patcher.Rules.Compiled.Constants." + engine.Context.GameTitle); Type sourceProxyType = entry.From != null ? engine.ProxyProvider.GetInterface((FormKind)entry.From.FormKind) : typeof(object); if (entry.Where != null) { string code = PreprocessCode(entry.Where.Code); if (StripComments(code).Length > 0) { // TODO: optimize HasTag() with index similarly var match = Regex.Match(code, @"^\s*Source\.EditorId\s*==\s*""([^""]*)""\s*$"); if (match.Success) { unit.Rule.WhereEditorId = match.Groups[1].Value; Log.Fine("Where criteria EditorID == '" + match.Groups[1].Value + "' will use an index."); } else { builder.BeginMethod("Where", sourceProxyType); builder.WriteCodeAsReturn(code); builder.EndMethod(); unit.Rule.Where = new WhereMethod(); } } } if (entry.Select != null) { string code = PreprocessCode(entry.Select.Code); if (StripComments(code).Length > 0) { builder.BeginMethod("Select", sourceProxyType); builder.WriteCode(code); builder.ReturnTrue(); builder.EndMethod(); unit.Rule.Select = new SelectMethod(); } } if (entry.Update != null) { string code = PreprocessCode(entry.Update.Code); if (StripComments(code).Length > 0) { builder.BeginMethod("Update", sourceProxyType, sourceProxyType); builder.WriteCode(code); builder.ReturnTrue(); builder.EndMethod(); unit.Rule.Update = new UpdateMethod(); } } if (entry.Inserts != null) { List<InsertMethod> methods = new List<InsertMethod>(); foreach (var insert in entry.Inserts) { string code = PreprocessCode(insert.Code); if (StripComments(code).Length > 0) { Type targetProxyType = engine.ProxyProvider.GetInterface((FormKind)insert.InsertedFormKind); builder.BeginMethod("Insert_" + methods.Count, sourceProxyType, targetProxyType); builder.WriteCode(code); builder.ReturnTrue(); builder.EndMethod(); } methods.Add(new InsertMethod() { InsertedFormId = insert.InsertedFormId, InsertedFormKind = (FormKind)insert.InsertedFormKind, Copy = insert.Copy }); } unit.Rule.Inserts = methods.ToArray(); } // Declare static helper fields foreach (var helper in engine.HelperProvider.Helpers) { // Skip declaration of helpers that are not used in debug mode if debug mode is disabled if (helper.DebugModeOnly && !debug) continue; builder.WriteCode(string.Format("static readonly {0} {1} = null;", helper.InterfaceType.FullName, helper.Name)); } string finalCode = builder.ToString(); if (!debug) { finalCode = StripDebug(finalCode); } // Write to memory stream using (var memoryStream = new MemoryStream(finalCode.Length)) { var writer = new StreamWriter(memoryStream); writer.Write(finalCode); writer.Flush(); memoryStream.Position = 0; // Find available file name for the new source file. int ruleNumber = 0; string sourceFilePath; do { sourceFilePath = string.Format("{0}@{1:00}.cs", Path.Combine(cachePath, unit.Rule.Metadata.RuleFileName), ruleNumber++); } while (units.Any(u => u.Source == sourceFilePath)); // Update source if necessary var sourceFile = engine.Context.DataFileProvider.GetDataFile(FileMode.Create, sourceFilePath); bool updated = sourceFile.CopyFrom(memoryStream, true); unit.Source = sourceFilePath; unit.Updated = updated; } units.Add(unit); }
public void Add(RuleEntry entry, RuleMetadata metadata, bool debug) { string className = "Rule_" + ruleClassCounter++; var unit = new RuleCompilationUnit() { ClassName = className, ClassFullName = string.Format("{0}.{1}", NamespaceName, className), IsDebugModeEnabled = debug, Rule = new CompiledRule(engine, metadata) { From = entry.From == null ? FormKind.Any : (FormKind)entry.From.FormKind }, }; string comment = string.Format("//\n// Source file for rule {0}\\{1}@{2} by {3}\n//\n", metadata.PluginFileName, metadata.RuleFileName, entry.Name, Program.GetProgramFullVersionInfo()); CodeBuilder builder = new CodeBuilder(NamespaceName, className, comment); builder.Usings.Add("Patcher.Rules.Compiled.Helpers"); builder.Usings.Add("Patcher.Rules.Compiled.Helpers." + engine.Context.GameTitle); builder.Usings.Add("Patcher.Rules.Compiled.Extensions"); builder.Usings.Add("Patcher.Rules.Compiled.Extensions." + engine.Context.GameTitle); builder.Usings.Add("Patcher.Rules.Compiled.Constants"); builder.Usings.Add("Patcher.Rules.Compiled.Constants." + engine.Context.GameTitle); Type sourceProxyType = entry.From != null?engine.ProxyProvider.GetInterface((FormKind)entry.From.FormKind) : typeof(object); if (entry.Where != null) { string code = PreprocessCode(entry.Where.Code); if (StripComments(code).Length > 0) { // TODO: optimize HasTag() with index similarly var match = Regex.Match(code, @"^\s*Source\.EditorId\s*==\s*""([^""]*)""\s*$"); if (match.Success) { unit.Rule.WhereEditorId = match.Groups[1].Value; Log.Fine("Where criteria EditorID == '" + match.Groups[1].Value + "' will use an index."); } else { builder.BeginMethod("Where", sourceProxyType); builder.WriteCodeAsReturn(code); builder.EndMethod(); unit.Rule.Where = new WhereMethod(); } } } if (entry.Select != null) { string code = PreprocessCode(entry.Select.Code); if (StripComments(code).Length > 0) { builder.BeginMethod("Select", sourceProxyType); builder.WriteCode(code); builder.ReturnTrue(); builder.EndMethod(); unit.Rule.Select = new SelectMethod(); } } if (entry.Update != null) { string code = PreprocessCode(entry.Update.Code); if (StripComments(code).Length > 0) { builder.BeginMethod("Update", sourceProxyType, sourceProxyType); builder.WriteCode(code); builder.ReturnTrue(); builder.EndMethod(); unit.Rule.Update = new UpdateMethod(); } } if (entry.Inserts != null) { List <InsertMethod> methods = new List <InsertMethod>(); foreach (var insert in entry.Inserts) { string code = PreprocessCode(insert.Code); if (StripComments(code).Length > 0) { Type targetProxyType = engine.ProxyProvider.GetInterface((FormKind)insert.InsertedFormKind); builder.BeginMethod("Insert_" + methods.Count, sourceProxyType, targetProxyType); builder.WriteCode(code); builder.ReturnTrue(); builder.EndMethod(); } methods.Add(new InsertMethod() { InsertedFormId = insert.InsertedFormId, InsertedFormKind = (FormKind)insert.InsertedFormKind, Copy = insert.Copy }); } unit.Rule.Inserts = methods.ToArray(); } // Declare static helper fields foreach (var helper in engine.HelperProvider.Helpers) { // Skip declaration of helpers that are not used in debug mode if debug mode is disabled if (helper.DebugModeOnly && !debug) { continue; } builder.WriteCode(string.Format("static readonly {0} {1} = null;", helper.InterfaceType.FullName, helper.Name)); } string finalCode = builder.ToString(); if (!debug) { finalCode = StripDebug(finalCode); } // Write to memory stream using (var memoryStream = new MemoryStream(finalCode.Length)) { var writer = new StreamWriter(memoryStream); writer.Write(finalCode); writer.Flush(); memoryStream.Position = 0; // Find available file name for the new source file. int ruleNumber = 0; string sourceFilePath; do { sourceFilePath = string.Format("{0}@{1:00}.cs", Path.Combine(cachePath, unit.Rule.Metadata.RuleFileName), ruleNumber++); } while (units.Any(u => u.Source == sourceFilePath)); // Update source if necessary var sourceFile = engine.Context.DataFileProvider.GetDataFile(FileMode.Create, sourceFilePath); bool updated = sourceFile.CopyFrom(memoryStream, true); unit.Source = sourceFilePath; unit.Updated = updated; } units.Add(unit); }