#region ================== Namespaces using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Text; using CodeImp.DoomBuilder.Compilers; using CodeImp.DoomBuilder.Data; using CodeImp.DoomBuilder.ZDoom; using CodeImp.DoomBuilder.GZBuilder.Data; using System.Windows.Forms; #endregion namespace CodeImp.DoomBuilder.SRB2 { internal sealed class LuaObjectParser : ZDTextParser { #region ================== Delegates public delegate void IncludeDelegate(LuaObjectParser parser, string includefile, bool clearerror); public IncludeDelegate OnInclude; #endregion #region ================== Variables private Dictionary objects; /*private Dictionary states; private List objectfreeslots; private List statefreeslots; private List spritefreeslots;*/ private IDictionary flagValues = new Dictionary { { "MF_SPECIAL", 0x1 }, { "MF_SOLID", 0x2 }, { "MF_SHOOTABLE", 0x4 }, { "MF_NOSECTOR", 0x8 }, { "MF_NOBLOCKMAP", 0x10 }, { "MF_PAPERCOLLISION", 0x20 }, { "MF_PUSHABLE", 0x40 }, { "MF_BOSS", 0x80 }, { "MF_SPAWNCEILING", 0x100 }, { "MF_NOGRAVITY", 0x200 }, { "MF_AMBIENT", 0x400 }, { "MF_SLIDEME", 0x800 }, { "MF_NOCLIP", 0x1000 }, { "MF_FLOAT", 0x2000 }, { "MF_BOXICON", 0x4000 }, { "MF_MISSILE", 0x8000 }, { "MF_SPRING", 0x10000 }, { "MF_BOUNCE", 0x20000 }, { "MF_MONITOR", 0x40000 }, { "MF_NOTHINK", 0x80000 }, { "MF_FIRE", 0x100000 }, { "MF_NOCLIPHEIGHT", 0x200000 }, { "MF_ENEMY", 0x400000 }, { "MF_SCENERY", 0x800000 }, { "MF_PAIN", 0x1000000 }, { "MF_STICKY", 0x2000000 }, { "MF_NIGHTSITEM", 0x4000000 }, { "MF_NOCLIPTHING", 0x8000000 }, { "MF_GRENADEBOUNCE", 0x10000000 }, { "MF_RUNSPAWNFUNC", 0x20000000 } }; #endregion #region ================== Properties public Dictionary Objects { get { return objects; } } #endregion #region ================== Constructor public LuaObjectParser() { // Syntax whitespace = "\n \t\r\u00A0"; specialtokens = "=\n"; objects = new Dictionary(); /*states = new Dictionary(); objectfreeslots = new List(); statefreeslots = new List(); spritefreeslots = new List();*/ } // Disposer public void Dispose() { objects = null; /*states = null; objectfreeslots = null; statefreeslots = null; spritefreeslots = null;*/ } #endregion #region ================== Parsing override public bool Parse(Stream stream, string sourcefilename, bool clearerrors) { if (!base.Parse(stream, sourcefilename, clearerrors)) return false; // Keep local data Stream localstream = datastream; string localsourcename = sourcename; BinaryReader localreader = datareader; string token; while (SkipWhitespace(true)) { token = ReadToken(); if (!string.IsNullOrEmpty(token)) { if (!token.StartsWith("mobjinfo[") || !token.EndsWith("]")) continue; string objname = token.Substring(9).TrimEnd(new char[] { ']' }); string name = objname; string sprite = DataManager.INTERNAL_PREFIX + "unknownthing"; string category = ""; string[] states = new string[8]; int mapThingNum = -1; int radius = 0; int height = 0; int flags = 0; Dictionary newflags = new Dictionary(); string angletext = ""; string parametertext = ""; string flagsvaluetext = ""; bool arrow = false; bool tagthing = false; SkipWhitespace(true); token = ReadToken(); if (token != "=") { continue; } SkipWhitespace(true); token = ReadToken(); if (token != "{") { ReportError("Invalid object definition, missing {"); return false; } SkipWhitespace(true); token = ReadToken(); bool finished = false; bool blockclosed = false; while (token != null) { if (finished) break; switch (token) { case "$Name": SkipWhitespace(true); token = ReadLine(); name = ZDTextParser.StripQuotes(token); break; case "$Sprite": SkipWhitespace(true); token = ReadToken(); if (((token.Length > DataManager.INTERNAL_PREFIX.Length) && token.ToLowerInvariant().StartsWith(DataManager.INTERNAL_PREFIX)) || General.Map.Data.GetSpriteExists(token)) { sprite = token; break; } LogWarning("The sprite \"" + token + "\" assigned by the \"$sprite\" property does not exist"); break; case "$Category": SkipWhitespace(true); token = ReadLine(); category = token; break; case "$Flags1Text": case "$Flags2Text": case "$Flags4Text": case "$Flags8Text": string index = token.Substring(6,1); SkipWhitespace(true); token = ReadLine(); newflags[index] = "[" + index + "] " + ZDTextParser.StripQuotes(token); break; case "$AngleText": SkipWhitespace(true); token = ReadLine(); angletext = ZDTextParser.StripQuotes(token); break; case "$ParameterText": SkipWhitespace(true); token = ReadLine(); parametertext = ZDTextParser.StripQuotes(token); break; case "$FlagsValueText": SkipWhitespace(true); token = ReadLine(); flagsvaluetext = ZDTextParser.StripQuotes(token); break; case "$Arrow": arrow = true; break; case "$TagThing": tagthing = true; break; case "doomednum": if (!ReadParameter(out token, out finished)) return false; if (!int.TryParse(token, NumberStyles.Integer, CultureInfo.InvariantCulture, out mapThingNum)) LogWarning("Could not parse map thing number"); break; case "radius": if (!ReadParameter(out token, out finished)) return false; if (!ParseWithArithmetic(token, out radius)) LogWarning("Could not parse radius"); radius /= 65536; break; case "height": if (!ReadParameter(out token, out finished)) return false; if (!ParseWithArithmetic(token, out height)) LogWarning("Could not parse height"); height /= 65536; break; case "spawnstate": if (!ReadParameter(out token, out finished)) return false; states[0] = token; break; case "seestate": if (!ReadParameter(out token, out finished)) return false; states[1] = token; break; case "painstate": if (!ReadParameter(out token, out finished)) return false; states[2] = token; break; case "meleestate": if (!ReadParameter(out token, out finished)) return false; states[3] = token; break; case "missilestate": if (!ReadParameter(out token, out finished)) return false; states[4] = token; break; case "deathstate": if (!ReadParameter(out token, out finished)) return false; states[5] = token; break; case "xdeathstate": if (!ReadParameter(out token, out finished)) return false; states[6] = token; break; case "raisestate": if (!ReadParameter(out token, out finished)) return false; states[7] = token; break; case "flags": if (!ReadParameter(out token, out finished)) return false; if (!ParseFlags(token, out flags)) LogWarning("Could not parse flags"); break; case "spawnhealth": case "seesound": case "reactiontime": case "attacksound": case "painchance": case "painsound": case "deathsound": case "speed": case "dispoffset": case "mass": case "damage": case "activesound": if (!ReadParameter(out token, out finished)) return false; break; case "}": finished = true; blockclosed = true; break; default: ReportError("Unknown object definition parameter " + token); return false; } if (!blockclosed) { SkipWhitespace(true); token = ReadToken(); } } if (token != "}") { ReportError("Invalid object definition, missing }"); return false; } if (mapThingNum > 0) { SRB2Object o = new SRB2Object(name, sprite, category, states, mapThingNum, radius, height, flags, newflags, angletext, parametertext, flagsvaluetext, arrow, tagthing); if (objects.ContainsKey(objname)) objects[objname] = o; else objects.Add(objname, o); } } } // All done return !this.HasError; } // 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 protected internal override bool SkipWhitespace(bool skipnewline) { int offset = skipnewline ? 0 : 1; char c; prevstreamposition = datastream.Position; //mxd do { if (datastream.Position == datastream.Length) return false; c = (char)datareader.ReadByte(); // Check if this is comment if (c == '/' || c == '-') { if (datastream.Position == datastream.Length) return false; char c2 = (char)datareader.ReadByte(); if (c2 == c) { // 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 = c4; } else { // Not a comment c = c3; } } else if (c2 == '*') { // Skip until */ char c4, c3 = '\0'; prevstreamposition = datastream.Position; //mxd do { if (datastream.Position == datastream.Length) //mxd { // ZDoom doesn't give even a warning message about this, so we shouldn't report error or fail parsing. General.ErrorLogger.Add(ErrorType.Warning, "Lua warning in '" + sourcename + "', line " + GetCurrentLineNumber() + ". Block comment is not closed."); return false; } c4 = c3; c3 = (char)datareader.ReadByte(); } while ((c4 != '*') || (c3 != '/')); c = ' '; } else { // Not a comment, rewind from reading c2 datastream.Seek(-1, SeekOrigin.Current); } } if (c == '-') { if (datastream.Position == datastream.Length) return false; char c2 = (char)datareader.ReadByte(); if (c2 == '-') { if (datastream.Position == datastream.Length) return false; char c3 = (char)datareader.ReadByte(); if (c3 == '[') { if (datastream.Position == datastream.Length) return false; char c4 = (char)datareader.ReadByte(); if (c4 == '[') { //Skip until ]] char c5 = '\0'; char c6 = '\0'; prevstreamposition = datastream.Position; //mxd do { if (datastream.Position == datastream.Length) //mxd { General.ErrorLogger.Add(ErrorType.Warning, "Lua warning in '" + sourcename + "', line " + GetCurrentLineNumber() + ". Block comment is not closed."); return false; } c5 = c6; c6 = (char)datareader.ReadByte(); } while ((c6 != ']') || (c5 != ']')); c = ' '; } else { // Not a multiline comment, rewind from reading c4 datastream.Seek(-1, SeekOrigin.Current); } } while ((c3 != '\n') && (datastream.Position < datastream.Length)) { c3 = (char)datareader.ReadByte(); } c = c3; } 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; } protected internal override string ReadToken(bool multiline) { //mxd. Return empty string when the end of the stream has been reached if (datastream.Position == datastream.Length) return string.Empty; //mxd. Store starting position prevstreamposition = datastream.Position; string token = ""; bool quotedstring = false; // Start reading char c = (char)datareader.ReadByte(); while (!IsWhitespace(c) || quotedstring || IsSpecialToken(c)) { //mxd. Break at newline? if (!multiline && c == '\r') { // Go one character back so line number is correct datastream.Seek(-1, SeekOrigin.Current); return token; } // 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 == '/') && !quotedstring) { // 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; } } // Potential comment? else if ((c == '-') && !quotedstring) { // Check the next byte if (datastream.Position == datastream.Length) return token; char c2 = (char)datareader.ReadByte(); if (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 a token (all sequential non-whitespace characters or a single character) using custom set of special tokens // Returns null when the end of the stream has been reached (mxd) protected internal override string ReadToken(string specialTokens) { // Return null when the end of the stream has been reached if (datastream.Position == datastream.Length) return null; //mxd. Store starting position prevstreamposition = datastream.Position; string token = ""; bool quotedstring = false; // Start reading char c = (char)datareader.ReadByte(); while (!IsWhitespace(c) || quotedstring || specialTokens.IndexOf(c) != -1) { // Special token? if (!quotedstring && specialTokens.IndexOf(c) != -1) { // Not reading a token yet? if (token.Length == 0) { // This is our whole token token += c; break; } // 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 == '/') && !quotedstring) { // 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; } } // Potential comment? else if ((c == '-') && !quotedstring) { // Check the next byte if (datastream.Position == datastream.Length) return token; char c2 = (char)datareader.ReadByte(); if (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; } #endregion #region ================== Methods private bool ReadParameter(out string output, out bool finished) { output = ""; finished = false; SkipWhitespace(true); string token = ReadToken(); if (token != "=") { ReportError("Invalid parameter definition, missing ="); return false; } SkipWhitespace(true); token = ReadToken(); if (!token.EndsWith(",")) { finished = true; } output = token.TrimEnd(new char[] { ',' }); return true; } private bool ParseWithArithmetic(string input, out int output) { output = 0; string[] tokens = input.Split(new char[] { '+' }); foreach (string t in tokens) { int val = 0; if (!ParseMultiplication(t, out val)) return false; output += val; } return true; } private bool ParseMultiplication(string input, out int output) { output = 1; string[] tokens = input.Split(new char[] { '*' }); foreach (string t in tokens) { string trimmed = t.Trim(); int val = 1; if (trimmed == "FRACUNIT") val = 65536; else if (!int.TryParse(trimmed, NumberStyles.Integer, CultureInfo.InvariantCulture, out val)) return false; output *= val; } return true; } private bool ParseFlags(string input, out int output) { output = 0; string[] tokens = input.Split(new char[] { '|' }); foreach (string token in tokens) { if (flagValues.ContainsKey(token)) { output |= flagValues[token]; } else { int val = 0; if (!int.TryParse(token, NumberStyles.Integer, CultureInfo.InvariantCulture, out val)) return false; output |= val; } } return true; } protected override string GetLanguageType() { return "Lua"; } #endregion } }