/// <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(); }