#region ================== Copyright (c) 2007 Pascal vd Heiden /* * Copyright (c) 2007 Pascal vd Heiden, www.codeimp.com * This program is released under GNU General Public License * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * */ #endregion #region ================== Namespaces using System; using System.Collections; using System.Collections.Generic; using System.Globalization; using System.Text; using CodeImp.DoomBuilder.IO; using CodeImp.DoomBuilder.Data; using System.IO; using System.Diagnostics; using CodeImp.DoomBuilder.Compilers; #endregion namespace CodeImp.DoomBuilder.Decorate { public sealed class DecorateParser { #region ================== Delegates public delegate void IncludeDelegate(DecorateParser parser, string includefile); public IncludeDelegate OnInclude; #endregion #region ================== Constants // Parsing private const string WHITESPACE = "\n \t\r"; private const string SPECIALTOKEN = ":{}+-\n;"; #endregion #region ================== Variables // These are actors we want to keep private Dictionary actors; // These are all parsed actors, also those from other games private Dictionary archivedactors; // Input data stream private Stream datastream; private BinaryReader datareader; private string sourcename; // Error report private int errorline; private string errordesc; private string errorsource; #endregion #region ================== Properties internal Stream DataStream { get { return datastream; } } internal BinaryReader DataReader { get { return datareader; } } public ICollection Actors { get { return actors.Values; } } public int ErrorLine { get { return errorline; } } public string ErrorDescription { get { return errordesc; } } public string ErrorSource { get { return errorsource; } } public bool HasError { get { return (errordesc != null); } } #endregion #region ================== Constructor / Disposer // Constructor public DecorateParser() { // Initialize actors = new Dictionary(); archivedactors = new Dictionary(); errordesc = null; } #endregion #region ================== Parsing // This parses the given decorate stream // Returns false on errors public bool Parse(Stream stream, string sourcefilename) { Stream localstream = stream; string localsourcename = sourcefilename; BinaryReader localreader = new BinaryReader(localstream, Encoding.ASCII); datastream = localstream; datareader = localreader; sourcename = localsourcename; datastream.Seek(0, SeekOrigin.Begin); // Continue until at the end of the stream while(SkipWhitespace(true)) { // Read a token string objdeclaration = ReadToken(); if(objdeclaration != null) { objdeclaration = objdeclaration.ToLowerInvariant(); if(objdeclaration == "actor") { // Read actor structure ActorStructure actor = new ActorStructure(this); if(this.HasError) break; // Add the actor archivedactors[actor.ClassName.ToLowerInvariant()] = actor; if(actor.CheckActorSupported()) actors[actor.ClassName.ToLowerInvariant()] = actor; // Replace an actor? if(actor.ReplacesClass != null) { if(GetArchivedActorByName(actor.ReplacesClass) != null) archivedactors[actor.ReplacesClass.ToLowerInvariant()] = actor; else General.WriteLogLine("WARNING: Unable to find the DECORATE class '" + actor.ReplacesClass + "' to replace, while parsing '" + actor.ClassName + "'"); if(actor.CheckActorSupported()) { if(GetActorByName(actor.ReplacesClass) != null) actors[actor.ReplacesClass.ToLowerInvariant()] = actor; } } } else if(objdeclaration == "#include") { // Include a file SkipWhitespace(true); string filename = ReadToken(); if(!string.IsNullOrEmpty(filename)) { // Strip the quotes filename = filename.Replace("\"", ""); // Callback to parse this file now if(OnInclude != null) OnInclude(this, filename); // Set our buffers back to continue parsing datastream = localstream; datareader = localreader; sourcename = localsourcename; if(HasError) break; } else { ReportError("Expected file name to include"); break; } } else if((objdeclaration == "const") || (objdeclaration == "native")) { // We don't need this, ignore up to the first next ; while(SkipWhitespace(true)) { string t = ReadToken(); if((t == ";") || (t == null)) break; } } else { // Unknown structure! // Best we can do now is just find the first { and then // follow the scopes until the matching } is found string token2; do { if(!SkipWhitespace(true)) break; token2 = ReadToken(); if(token2 == null) break; } while(token2 != "{"); int scopelevel = 1; do { if(!SkipWhitespace(true)) break; token2 = ReadToken(); if(token2 == null) break; if(token2 == "{") scopelevel++; if(token2 == "}") scopelevel--; } while(scopelevel > 0); } } } // Return true when no errors occurred return (errordesc == null); } // This returns true if the given character is whitespace internal bool IsWhitespace(char c) { return (WHITESPACE.IndexOf(c) > -1); } // This returns true if the given character is a special token internal bool IsSpecialToken(char c) { return (SPECIALTOKEN.IndexOf(c) > -1); } // This returns true if the given character is a special token internal bool IsSpecialToken(string s) { if(s.Length > 0) return (SPECIALTOKEN.IndexOf(s[0]) > -1); else return false; } // This skips whitespace on the stream, placing the read // position right before the first non-whitespace character // Returns false when the end of the stream is reached internal bool SkipWhitespace(bool skipnewline) { int offset = skipnewline ? 0 : 1; char c; do { if(datastream.Position == datastream.Length) return false; c = (char)datareader.ReadByte(); // Check if this is comment if(c == '/') { if(datastream.Position == datastream.Length) return false; char c2 = (char)datareader.ReadByte(); if(c2 == '/') { // Check if not a special comment with a token if(datastream.Position == datastream.Length) return false; char c3 = (char)datareader.ReadByte(); if(c3 != '$') { // Skip entire line char c4 = ' '; while((c4 != '\n') && (datastream.Position < datastream.Length)) { c4 = (char)datareader.ReadByte(); } c = ' '; } else { // Not a comment c = c3; } } else if(c2 == '*') { // Skip until */ char c4, c3 = '\0'; do { c4 = c3; c3 = (char)datareader.ReadByte(); } while((c4 != '*') || (c3 != '/')); c = ' '; } else { // Not a comment, rewind from reading c2 datastream.Seek(-1, SeekOrigin.Current); } } } while(WHITESPACE.IndexOf(c, offset) > -1); // Go one character back so we can read this non-whitespace character again datastream.Seek(-1, SeekOrigin.Current); return true; } // This reads a token (all sequential non-whitespace characters or a single character) // Returns null when the end of the stream has been reached internal string ReadToken() { string token = ""; bool quotedstring = false; // Return null when the end of the stream has been reached if(datastream.Position == datastream.Length) return null; // Start reading char c = (char)datareader.ReadByte(); while(!IsWhitespace(c) || quotedstring || IsSpecialToken(c)) { // Special token? if(!quotedstring && IsSpecialToken(c)) { // Not reading a token yet? if(token.Length == 0) { // This is our whole token token += c; break; } else { // This is a new token and shouldn't be read now // Go one character back so we can read this token again datastream.Seek(-1, SeekOrigin.Current); break; } } else { // Quote? if(c == '"') { // Quote to end the string? if(quotedstring) quotedstring = false; // First character is a quote? if(token.Length == 0) quotedstring = true; token += c; } // Potential comment? else if(c == '/') { // Check the next byte if(datastream.Position == datastream.Length) return token; char c2 = (char)datareader.ReadByte(); if((c2 == '/') || (c2 == '*')) { // This is a comment start, so the token ends here // Go two characters back so we can read this comment again datastream.Seek(-2, SeekOrigin.Current); break; } else { // Not a comment // Go one character back so we can read this char again datastream.Seek(-1, SeekOrigin.Current); token += c; } } else { token += c; } } // Next character if(datastream.Position < datastream.Length) c = (char)datareader.Read(); else break; } return token; } // This reads the rest of the line // Returns null when the end of the stream has been reached internal string ReadLine() { string token = ""; // Return null when the end of the stream has been reached if(datastream.Position == datastream.Length) return null; // Start reading char c = (char)datareader.ReadByte(); while(c != '\n') { token += c; // Next character if(datastream.Position < datastream.Length) c = (char)datareader.Read(); else break; } return token.Trim(); } // This reports an error internal void ReportError(string message) { long position = datastream.Position; long readpos = 0; int linenumber = 1; // Find the line on which we found this error datastream.Seek(0, SeekOrigin.Begin); StreamReader textreader = new StreamReader(datastream, Encoding.ASCII); while(readpos < position) { string line = textreader.ReadLine(); if(line == null) break; readpos += line.Length + 2; linenumber++; } // Return to original position datastream.Seek(position, SeekOrigin.Begin); // Set error information errordesc = message; errorline = linenumber; errorsource = sourcename; } #endregion #region ================== Methods // This returns an actor by name // Returns null when actor cannot be found public ActorStructure GetActorByName(string name) { name = name.ToLowerInvariant(); if(actors.ContainsKey(name)) return actors[name]; else return null; } // This returns an actor by name // Returns null when actor cannot be found internal ActorStructure GetArchivedActorByName(string name) { name = name.ToLowerInvariant(); if(archivedactors.ContainsKey(name)) return archivedactors[name]; else return null; } #endregion } }