/// <summary> /// Stores an ERROR message to be logged /// </summary> /// <param name="error">The message text</param> /// <param name="file">The ConFile being processed for this message</param> /// <param name="line">The ConFile line number being processed for this message</param> public static void Error(string error, ConFile file = null, int line = 0, Exception exception = null) { if (Enabled) { LogEntry entry = new LogEntry() { Type = LogEntryType.Error, Message = error, File = file, Line = line, ExceptionObj = exception }; lock (_syncObj) { Messages.Add(entry); Errors.Add(entry); if (OnMessageLogged != null) { OnMessageLogged(null, entry); } } } }
/// <summary> /// Invokes the specified script command /// </summary> /// <param name="input">The ConFile formated command to execute in this scope</param> /// <param name="file">If a file is specified, the result of this command will also be saved /// in the <see cref="ConFile"/> specified here, so it can be stored when saved.</param> /// <example> /// input => "ObjectTemplate.activeSafe GenericFireArm chrif_type95" /// </example> public void Execute(string input, ConFile file = null) { // === Create Token Token token = Tokenizer.Tokenize(input); token.File = file; // === Execute on Scope ScriptEngine.ExecuteInScope(token, this); }
/// <summary> /// Stores an INFO message to be logged /// </summary> /// <param name="Message">The message text</param> /// <param name="WorkingFile">The ConFile being processed for this message</param> /// <param name="LineNumber">The ConFile line number being processed for this message</param> public static void Info(string Message, ConFile WorkingFile = null, int LineNumber = 0) { if (Enabled) { LogEntry entry = new LogEntry() { Type = LogEntryType.Info, Message = Message, File = WorkingFile, Line = LineNumber }; lock (_syncObj) { Messages.Add(entry); if (OnMessageLogged != null) OnMessageLogged(null, entry); } } }
/// <summary> /// Loads a .con or .ai file, and converts the script objects into C# objects /// </summary> /// <param name="filePath">The path to the .con / .ai file</param> /// <param name="scope"> /// Sets the scope in which the objects from this <see cref="ConFile"/> /// will be loaded into. /// </param> /// <param name="runInstruction"> /// Defines how the script engine should handle nested file includes /// </param> /// <exception cref="Exception"> /// Thrown if there is a problem loading the script file in any way. /// </exception> /// <returns>Returns the parsed bf2 script file.</returns> public static async Task <ConFile> LoadFileAsync(string filePath, Scope scope = null, ExecuteInstruction runInstruction = ExecuteInstruction.Skip) { // Create new ConFile contents ConFile cFile = new ConFile(filePath, scope); Dictionary <int, string> fileContents = new Dictionary <int, string>(); int lineNum = 1; // Create Log Logger.Info("Loading file: " + filePath); // Open the confile, and read its contents using (FileStream fStream = new FileStream(filePath, FileMode.Open, FileAccess.Read)) using (StreamReader reader = new StreamReader(fStream)) { // While we can keep reading while (!reader.EndOfStream) { string line = await reader.ReadLineAsync(); fileContents.Add(lineNum++, line); } } // Parse the contents of the Con / Ai file try { await ParseFileLines(fileContents, cFile, runInstruction); } catch { // Pass the exception up throw; } // Return the file return(cFile); }
/// <summary> /// Stores an INFO message to be logged /// </summary> /// <param name="Message">The message text</param> /// <param name="WorkingFile">The ConFile being processed for this message</param> /// <param name="LineNumber">The ConFile line number being processed for this message</param> public static void Info(string Message, ConFile WorkingFile = null, int LineNumber = 0) { if (Enabled) { LogEntry entry = new LogEntry() { Type = LogEntryType.Info, Message = Message, File = WorkingFile, Line = LineNumber }; lock (_syncObj) { Messages.Add(entry); if (OnMessageLogged != null) { OnMessageLogged(null, entry); } } } }
/// <summary> /// Loads a .con or .ai file, and converts the script objects into C# objects /// </summary> /// <param name="filePath">The path to the .con / .ai file</param> /// <param name="scope"> /// Sets the scope in which the objects from this <see cref="ConFile"/> /// will be loaded into. /// </param> /// <param name="runInstruction"> /// Defines how the script engine should handle nested file includes /// </param> /// <exception cref="Exception"> /// Thrown if there is a problem loading the script file in any way. /// </exception> /// <returns>Returns the parsed bf2 script file.</returns> public static async Task<ConFile> LoadFileAsync(string filePath, Scope scope = null, ExecuteInstruction runInstruction = ExecuteInstruction.Skip) { // Create new ConFile contents ConFile cFile = new ConFile(filePath, scope); Dictionary<int, string> fileContents = new Dictionary<int, string>(); int lineNum = 1; // Create Log Logger.Info("Loading file: " + filePath); // Open the confile, and read its contents using (FileStream fStream = new FileStream(filePath, FileMode.Open, FileAccess.Read)) using (StreamReader reader = new StreamReader(fStream)) { // While we can keep reading while (!reader.EndOfStream) { string line = await reader.ReadLineAsync(); fileContents.Add(lineNum++, line); } } // Parse the contents of the Con / Ai file try { await ParseFileLines(fileContents, cFile, runInstruction); } catch { // Pass the exception up throw; } // Return the file return cFile; }
/// <summary> /// Stores an ERROR message to be logged /// </summary> /// <param name="error">The message text</param> /// <param name="file">The ConFile being processed for this message</param> /// <param name="line">The ConFile line number being processed for this message</param> public static void Error(string error, ConFile file = null, int line = 0, Exception exception = null) { if (Enabled) { LogEntry entry = new LogEntry() { Type = LogEntryType.Error, Message = error, File = file, Line = line, ExceptionObj = exception }; lock (_syncObj) { Messages.Add(entry); Errors.Add(entry); if (OnMessageLogged != null) OnMessageLogged(null, entry); } } }
/// <summary> /// Parses the contents of a .con or .ai file, and converts the contents into C# objects /// </summary> /// <param name="fileContents">A dictionary of file contents [LineNumber => LineContents]</param> /// <param name="workingFile">A reference to the ConFile that contains the contents</param> internal static async Task ParseFileLines( Dictionary<int, string> fileContents, ConFile workingFile, ExecuteInstruction run) { // ============ // First we convert our confile lines into parsed tokens // ============ Token[] fileTokens = Tokenizer.Tokenize(workingFile, ref fileContents); TokenArgs tokenArgs; Scope currentScope = workingFile.Scope; // ============ // Now we create an object reference of all objects in the Confile // before parsing the object properties, which **can** reference an object // in the same file before its defined // NOTE: Do not create object references for .Active and .safeActive // ============ foreach (Token token in fileTokens.Where(x => x.Kind == TokenType.ObjectStart).OrderBy(x => x.Position)) { // Create the object var Method = token.TokenArgs.ReferenceType.GetMethod(token.TokenArgs.PropertyName); ConFileObject template = Method.Invoke(token); // Finally, register the object with the ObjectManager currentScope.AddObject(template, token); Logger.Info($"Created {token.TokenArgs.ReferenceType} \"{template.Name}\"", workingFile, token.Position); } // ============ // Finally, we load all of the object properties, and assign them to their // respective objects // ============ // Create our needed objects RemComment comment = null; ConFileObject currentObj = null; ReferenceType type; var builder = new StringBuilder(); // We use a for loop here so we can skip rem blocks and statements for (int i = 0; i < fileTokens.Length; i++) { // Grab token value Token token = fileTokens[i]; try { switch (token.Kind) { case TokenType.ObjectStart: case TokenType.ActiveSwitch: // NOTE: the object was created before this loop! currentObj = currentScope.GetObject(token); currentScope.SetActiveObject(currentObj); // === Objects are already added to the working file before hand === // // Add object reference to file workingFile.AddEntry(currentObj, token); // Reset comment comment = null; // Log Logger.Info($"Loading object properties for \"{currentObj.Name}\"", workingFile, token.Position ); break; case TokenType.ObjectProperty: // Convert args to an object tokenArgs = token.TokenArgs; // Get the last used object type = tokenArgs.ReferenceType; currentObj = currentScope.GetActiveObject(type); // Make sure we have an object to work with and the object // reference matches our current working object if (currentObj == null) { // If we are here, we have an issue... string error = $"Failed to set property \"{token.TokenArgs.ReferenceType.Name}.\"" + $"{token.TokenArgs.PropertyName}. No object reference set!"; throw new ParseException(error, token); } // Let the object parse its own lines... try { currentObj.Parse(token); // Ensure comment is null comment = null; } catch (Exception e) { Logger.Error(e.Message, workingFile, token.Position, e); throw; } break; case TokenType.RemComment: // Create a new comment if we need to if (comment == null) { comment = new RemComment(token); } // Add comment to the current string comment.AppendLine(token.Value); break; case TokenType.BeginRem: RemComment rem = new RemComment(token); rem.IsRemBlock = true; // Skip every line until we get to the endRem builder.AppendLine(token.Value); i = ScopeUntil(TokenType.EndRem, fileTokens, i, builder); // Set rem value rem.Value = builder.ToString().TrimEnd(); workingFile.AddEntry(rem, rem.Token); // Clear the string builder builder.Clear(); break; case TokenType.IfStart: Statement statement = new Statement(token); if (token.Kind == TokenType.IfStart) { // Skip every line until we get to the endIf builder.AppendLine(token.Value); i = ScopeUntil(TokenType.EndIf, fileTokens, i, builder); // Clear the string builder statement.Token.Value = builder.ToString().TrimEnd(); builder.Clear(); } // Add entry workingFile.AddEntry(statement, statement.Token); break; case TokenType.Run: case TokenType.Include: // Just add to as a string RunStatement stmt = new RunStatement(token); workingFile.AddEntry(stmt, stmt.Token); // Do we execute the statement? if (run == ExecuteInstruction.Skip) continue; // Create new scope for execution Scope runScope = currentScope; // Are we executing in a new scope? if (run == ExecuteInstruction.ExecuteInNewScope) { // For now, we just inherit the parent scope type runScope = new Scope(currentScope, currentScope.ScopeType); runScope.MissingObjectHandling = MissingObjectHandling.CheckParent; } // Get the filepath string filePath = Path.GetDirectoryName(workingFile.FilePath); string fileName = Path.Combine(filePath, stmt.FileName); // Define file arguments runScope.SetArguments(stmt.Arguments); // Load the file try { ConFile include = await LoadFileAsync(fileName, runScope, run); workingFile.ExecutedIncludes.Add(include); } catch (FileNotFoundException) // Only acceptable exception { fileName = Path.GetFileName(fileName); Logger.Warning($"Failed to run file \"{fileName}\". File Not Found", workingFile, token.Position); } break; case TokenType.Constant: case TokenType.Variable: // Set the new expression reference in Scope Expression exp = new Expression(token); currentScope.Expressions[exp.Name] = exp; // Add expression to the confile as well workingFile.AddEntry(exp, exp.Token); break; case TokenType.None: // Dont attach comment to a property if we have an empty line here if (comment != null) { workingFile.AddEntry(comment, comment.Token); comment = null; } // Throw error if the line is not empty if (!String.IsNullOrWhiteSpace(token.Value)) { string message = $"Unable to parse file entry \"{token.Value}\" on line {token.Position}"; throw new ParseException(message, token); } break; } } catch (Exception e) { Logger.Error(e.Message, token.File, token.Position, e); throw; } } // Finalize this confile workingFile.Finish(); }
/// <summary> /// Parses the contents of a .con or .ai file, and converts the contents into C# objects /// </summary> /// <param name="fileContents">A dictionary of file contents [LineNumber => LineContents]</param> /// <param name="workingFile">A reference to the ConFile that contains the contents</param> internal static async Task ParseFileLines( Dictionary <int, string> fileContents, ConFile workingFile, ExecuteInstruction run) { // ============ // First we convert our confile lines into parsed tokens // ============ Token[] fileTokens = Tokenizer.Tokenize(workingFile, ref fileContents); TokenArgs tokenArgs; Scope currentScope = workingFile.Scope; // ============ // Now we create an object reference of all objects in the Confile // before parsing the object properties, which **can** reference an object // in the same file before its defined // NOTE: Do not create object references for .Active and .safeActive // ============ foreach (Token token in fileTokens.Where(x => x.Kind == TokenType.ObjectStart).OrderBy(x => x.Position)) { // Create the object var Method = token.TokenArgs.ReferenceType.GetMethod(token.TokenArgs.PropertyName); ConFileObject template = Method.Invoke(token); // Finally, register the object with the ObjectManager currentScope.AddObject(template, token); Logger.Info($"Created {token.TokenArgs.ReferenceType} \"{template.Name}\"", workingFile, token.Position); } // ============ // Finally, we load all of the object properties, and assign them to their // respective objects // ============ // Create our needed objects RemComment comment = null; ConFileObject currentObj = null; ReferenceType type; var builder = new StringBuilder(); // We use a for loop here so we can skip rem blocks and statements for (int i = 0; i < fileTokens.Length; i++) { // Grab token value Token token = fileTokens[i]; try { switch (token.Kind) { case TokenType.ObjectStart: case TokenType.ActiveSwitch: // NOTE: the object was created before this loop! currentObj = currentScope.GetObject(token); currentScope.SetActiveObject(currentObj); // === Objects are already added to the working file before hand === // // Add object reference to file workingFile.AddEntry(currentObj, token); // Reset comment comment = null; // Log Logger.Info($"Loading object properties for \"{currentObj.Name}\"", workingFile, token.Position ); break; case TokenType.ObjectProperty: // Convert args to an object tokenArgs = token.TokenArgs; // Get the last used object type = tokenArgs.ReferenceType; currentObj = currentScope.GetActiveObject(type); // Make sure we have an object to work with and the object // reference matches our current working object if (currentObj == null) { // If we are here, we have an issue... string error = $"Failed to set property \"{token.TokenArgs.ReferenceType.Name}.\"" + $"{token.TokenArgs.PropertyName}. No object reference set!"; throw new ParseException(error, token); } // Let the object parse its own lines... try { currentObj.Parse(token); // Ensure comment is null comment = null; } catch (Exception e) { Logger.Error(e.Message, workingFile, token.Position, e); throw; } break; case TokenType.RemComment: // Create a new comment if we need to if (comment == null) { comment = new RemComment(token); } // Add comment to the current string comment.AppendLine(token.Value); break; case TokenType.BeginRem: RemComment rem = new RemComment(token); rem.IsRemBlock = true; // Skip every line until we get to the endRem builder.AppendLine(token.Value); i = ScopeUntil(TokenType.EndRem, fileTokens, i, builder); // Set rem value rem.Value = builder.ToString().TrimEnd(); workingFile.AddEntry(rem, rem.Token); // Clear the string builder builder.Clear(); break; case TokenType.IfStart: Statement statement = new Statement(token); if (token.Kind == TokenType.IfStart) { // Skip every line until we get to the endIf builder.AppendLine(token.Value); i = ScopeUntil(TokenType.EndIf, fileTokens, i, builder); // Clear the string builder statement.Token.Value = builder.ToString().TrimEnd(); builder.Clear(); } // Add entry workingFile.AddEntry(statement, statement.Token); break; case TokenType.Run: case TokenType.Include: // Just add to as a string RunStatement stmt = new RunStatement(token); workingFile.AddEntry(stmt, stmt.Token); // Do we execute the statement? if (run == ExecuteInstruction.Skip) { continue; } // Create new scope for execution Scope runScope = currentScope; // Are we executing in a new scope? if (run == ExecuteInstruction.ExecuteInNewScope) { // For now, we just inherit the parent scope type runScope = new Scope(currentScope, currentScope.ScopeType); runScope.MissingObjectHandling = MissingObjectHandling.CheckParent; } // Get the filepath string filePath = Path.GetDirectoryName(workingFile.FilePath); string fileName = Path.Combine(filePath, stmt.FileName); // Define file arguments runScope.SetArguments(stmt.Arguments); // Load the file try { ConFile include = await LoadFileAsync(fileName, runScope, run); workingFile.ExecutedIncludes.Add(include); } catch (FileNotFoundException) // Only acceptable exception { fileName = Path.GetFileName(fileName); Logger.Warning($"Failed to run file \"{fileName}\". File Not Found", workingFile, token.Position); } break; case TokenType.Constant: case TokenType.Variable: // Set the new expression reference in Scope Expression exp = new Expression(token); currentScope.Expressions[exp.Name] = exp; // Add expression to the confile as well workingFile.AddEntry(exp, exp.Token); break; case TokenType.None: // Dont attach comment to a property if we have an empty line here if (comment != null) { workingFile.AddEntry(comment, comment.Token); comment = null; } // Throw error if the line is not empty if (!String.IsNullOrWhiteSpace(token.Value)) { string message = $"Unable to parse file entry \"{token.Value}\" on line {token.Position}"; throw new ParseException(message, token); } break; } } catch (Exception e) { Logger.Error(e.Message, token.File, token.Position, e); throw; } } // Finalize this confile workingFile.Finish(); }