/// <summary> /// Takes an array of string values, and converts it to the proper value type for /// this instance's Generic Type /// </summary> /// <param name="ValueParams">The string value's to convert, and set the /// Value of this instance to. /// </param> public override void SetValue(Token token, int objectLevel = 0) { Type PropertyType = typeof(T); // === // DONOT use this ObjectProperties TOKEN! breaks collections! TokenArgs tokenArgs = token.TokenArgs; // === // Check for ConFileObjects if (typeof(ConFileObject).IsAssignableFrom(PropertyType)) { // define vars ConFileObject obj = (ConFileObject)(object)Value; // Load a new object if we are undefined if (obj == null) { // Is this a defined object? string name = tokenArgs.Arguments.Last(); //var type = ScriptEngine.GetTemplateType(PropertyType); // Check for the object in this properties owner's Scope if (Token.File.Scope.ContainsObject(name, PropertyType)) { obj = Token.File.Scope.GetObject(name, PropertyType, Token); Argument = new ValueInfo <T>((T)(object)obj); } else { string error = $"ObjectProperty \"{Name}\" requires an existing object that is not defined!"; throw new Exception(error); } } else { // Let the child class parse itself obj.Parse(token, ++objectLevel); Argument = new ValueInfo <T>((T)(object)obj); } } else if (PropertyType.IsGenericType) { // Check for Generic types, we don't support these! throw new Exception($"Invalid Generic Type found \"{PropertyType}\""); } else { // Since we are not an array property, make sure we didnt get // an array passed in the con file if (tokenArgs.Arguments.Length > 1) { throw new Exception($"Expecting single value, but got an Array for \"{Name}\""); } // Since we are not an array, extract our only value Argument = new ValueInfo <T>(ConvertValue <T>(tokenArgs.Arguments[0], PropertyType)); } }
/// <summary> /// Creates a new instance of <see cref="ObjectProperty{T}"/> /// </summary> /// <param name="name">the property name</param> /// <param name="token">the token information</param> /// <param name="property">The PropertyInfo object for this instance</param> /// <param name="owner">the ConFileObject that owns this property instance</param> public ObjectProperty(string name, Token token, PropertyInfo property, ConFileObject owner) { Name = name; Token = token; Property = property; Owner = owner; }
/// <summary> /// Sets the active object in this scope, of its base template type /// </summary> /// <remarks>The object DOES NOT need to exist in this scope, but must in a parent scope</remarks> /// <param name="obj">The object we are activating</param> public void SetActiveObject(ConFileObject obj) { // Ensure that we aren't passing peoples problems CheckObjectToken(obj); ReferenceType type = obj.Token.TokenArgs.ReferenceType; ActiveObjects[type] = obj; }
/// <summary> /// Determines whether the specified object (not reference) exists in this scope. /// </summary> /// <param name="obj">The object we are searching for</param> /// <returns>true if the object exists in this scope, otherwise false</returns> public bool ContainsObject(ConFileObject obj) { // Ensure that we aren't passing peoples problems CheckObjectToken(obj); // Check for object existance var type = obj.Token.TokenArgs.ReferenceType; var key = new Tuple <string, ReferenceType>(obj.Name, type); return(ContainsObject(key)); }
/// <summary> /// Ensures that our token is valid /// </summary> /// <param name="obj"></param> private void CheckObjectToken(ConFileObject obj) { // Ensure that we aren't passing peoples problems if (obj?.Token?.TokenArgs == null) { throw new ArgumentNullException( "obj.Token.TokenArgs", $"TokenArgs are not defined on \"{obj.Name}\"!" ); } else if (obj.Token.TokenArgs.ReferenceType == null) { throw new ArgumentNullException( "obj.Token.TokenArgs.ReferenceType", $"ReferenceType is not defined on \"{obj.Name}\"!" ); } }
/// <summary> /// Adds a <see cref="ConFileObject"/> to this Scope /// </summary> /// <remarks> /// Used by the script engine to add objects, and set them as active /// </remarks> /// <param name="obj"></param> /// <param name="token"></param> internal void AddObject(ConFileObject obj, Token token) { // Ensure that we aren't passing peoples problems CheckObjectToken(obj); // Generate key var type = obj.Token.TokenArgs.ReferenceType; var key = new Tuple <string, ReferenceType>(obj.Name, type); // If object exists, then we fetch the existing reference if (ContainsObject(key)) { // Log warning string err = $"Object \"{obj.Name}\" is already defined, Setting existing one as active"; Logger.Warning(err, token.File, token.Position); // Get existing reference obj = GetObject(key, token); } else { // Add object Objects.Add(key, obj); // Register object? if (ParentScope != null && RegisterObjects) { if (ScopeType == ScopeType.Attached) { ParentScope.AddObject(obj, token); } else { ParentScope.AddObject(obj.Clone(), token); } } } // Set object as active SetActiveObject(obj); }
/// <summary> /// Adds a <see cref="ConFileObject"/> to this Scope /// </summary> /// <param name="obj">The object reference to add</param> /// <param name="setActive"> /// Set this as the active object of its <see cref="ReferenceType"/>? /// </param> public void AddObject(ConFileObject obj, bool setActive = true) { // Ensure that we aren't passing peoples problems CheckObjectToken(obj); // Generate key var type = obj.Token.TokenArgs.ReferenceType; var key = new Tuple <string, ReferenceType>(obj.Name, type); // If object exists, throw exception if (ContainsObject(key)) { throw new Exception("Object is already defined in this scope!"); } // Do we set as the active object? if (setActive) { SetActiveObject(obj); } // Register object? if (ParentScope != null && RegisterObjects) { if (ScopeType == ScopeType.Attached) { ParentScope.AddObject(obj, setActive); } else { ParentScope.AddObject(obj.Clone(), setActive); } } // Add object Objects.Add(key, obj); }
/// <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(); }