From 37a3eab15006a8398b7aef427b2b3d7ff12f5606 Mon Sep 17 00:00:00 2001 From: ZZYZX <zzyzx@virtual> Date: Mon, 16 Jan 2017 13:18:46 +0200 Subject: [PATCH] Enabled prototype ZScript support --- Source/Core/Builder.csproj | 6 + Source/Core/ZDoom/DecorateActorStructure.cs | 432 +++++++++++ Source/Core/ZDoom/DecorateParser.cs | 779 -------------------- Source/Core/ZDoom/DecorateStateGoto.cs | 167 +++++ Source/Core/ZDoom/DecorateStateStructure.cs | 209 ++++++ Source/Core/ZDoom/StateGoto.cs | 5 + Source/Core/ZDoom/ZScriptActorStructure.cs | 499 +++++++++++++ Source/Core/ZDoom/ZScriptParser.cs | 535 ++------------ Source/Core/ZDoom/ZScriptStateGoto.cs | 75 ++ Source/Core/ZDoom/ZScriptStateStructure.cs | 279 +++++++ Source/Core/ZDoom/ZScriptTokenizer.cs | 39 +- 11 files changed, 1751 insertions(+), 1274 deletions(-) create mode 100755 Source/Core/ZDoom/DecorateActorStructure.cs create mode 100755 Source/Core/ZDoom/DecorateStateGoto.cs create mode 100755 Source/Core/ZDoom/DecorateStateStructure.cs create mode 100755 Source/Core/ZDoom/ZScriptActorStructure.cs create mode 100755 Source/Core/ZDoom/ZScriptStateGoto.cs create mode 100755 Source/Core/ZDoom/ZScriptStateStructure.cs diff --git a/Source/Core/Builder.csproj b/Source/Core/Builder.csproj index 1a87cb30..11330636 100644 --- a/Source/Core/Builder.csproj +++ b/Source/Core/Builder.csproj @@ -520,7 +520,13 @@ <Compile Include="VisualModes\VisualSector.cs" /> <Compile Include="Rendering\World3DShader.cs" /> <Compile Include="Rendering\WorldVertex.cs" /> + <Compile Include="ZDoom\DecorateActorStructure.cs" /> + <Compile Include="ZDoom\DecorateStateGoto.cs" /> + <Compile Include="ZDoom\DecorateStateStructure.cs" /> + <Compile Include="ZDoom\ZScriptActorStructure.cs" /> <Compile Include="ZDoom\ZScriptParser.cs" /> + <Compile Include="ZDoom\ZScriptStateGoto.cs" /> + <Compile Include="ZDoom\ZScriptStateStructure.cs" /> <Compile Include="ZDoom\ZScriptTokenizer.cs" /> </ItemGroup> <ItemGroup> diff --git a/Source/Core/ZDoom/DecorateActorStructure.cs b/Source/Core/ZDoom/DecorateActorStructure.cs new file mode 100755 index 00000000..e5487b94 --- /dev/null +++ b/Source/Core/ZDoom/DecorateActorStructure.cs @@ -0,0 +1,432 @@ +using CodeImp.DoomBuilder.Config; +using CodeImp.DoomBuilder.Types; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; + +namespace CodeImp.DoomBuilder.ZDoom +{ + + public sealed class DecorateActorStructure : ActorStructure + { + #region ================== DECORATE Actor Structure parsing + + internal DecorateActorStructure(ZDTextParser zdparser, DecorateCategoryInfo catinfo) : base(zdparser, catinfo) + { + DecorateParser parser = (DecorateParser)zdparser; + bool done = false; //mxd + + // First next token is the class name + parser.SkipWhitespace(true); + classname = parser.StripTokenQuotes(parser.ReadToken(ACTOR_CLASS_SPECIAL_TOKENS)); + + if (string.IsNullOrEmpty(classname)) + { + parser.ReportError("Expected actor class name"); + return; + } + + //mxd. Fail on duplicates + if (parser.ActorsByClass.ContainsKey(classname.ToLowerInvariant())) + { + parser.ReportError("Actor \"" + classname + "\" is double-defined"); + return; + } + + // Parse tokens before entering the actor scope + while (parser.SkipWhitespace(true)) + { + string token = parser.ReadToken(); + if (!string.IsNullOrEmpty(token)) + { + token = token.ToLowerInvariant(); + + switch (token) + { + case ":": + // The next token must be the class to inherit from + parser.SkipWhitespace(true); + inheritclass = parser.StripTokenQuotes(parser.ReadToken()); + if (string.IsNullOrEmpty(inheritclass)) + { + parser.ReportError("Expected class name to inherit from"); + return; + } + + // Find the actor to inherit from + baseclass = parser.GetArchivedActorByName(inheritclass); + break; + + case "replaces": + // The next token must be the class to replace + parser.SkipWhitespace(true); + replaceclass = parser.StripTokenQuotes(parser.ReadToken()); + if (string.IsNullOrEmpty(replaceclass)) + { + parser.ReportError("Expected class name to replace"); + return; + } + break; + + case "native": + // Igore this token + break; + + case "{": + // Actor scope begins here, + // break out of this parse loop + done = true; + break; + + case "-": + // This could be a negative doomednum (but our parser sees the - as separate token) + // So read whatever is after this token and ignore it (negative doomednum indicates no doomednum) + parser.ReadToken(); + break; + + default: + //mxd. Property begins with $? Then the whole line is a single value + if (token.StartsWith("$")) + { + // This is for editor-only properties such as $sprite and $category + props[token] = new List<string> { (parser.SkipWhitespace(false) ? parser.ReadLine() : "") }; + continue; + } + + if (!int.TryParse(token, NumberStyles.Integer, CultureInfo.InvariantCulture, out doomednum)) // Check if numeric + { + // Not numeric! + parser.ReportError("Expected editor number or start of actor scope while parsing \"" + classname + "\""); + return; + } + + //mxd. Range check + if ((doomednum < General.Map.FormatInterface.MinThingType) || (doomednum > General.Map.FormatInterface.MaxThingType)) + { + // Out of bounds! + parser.ReportError("Actor \"" + classname + "\" has invalid editor number. Editor number must be between " + + General.Map.FormatInterface.MinThingType + " and " + General.Map.FormatInterface.MaxThingType); + return; + } + break; + } + + if (done) break; //mxd + } + else + { + parser.ReportError("Unexpected end of structure"); + return; + } + } + + // Now parse the contents of actor structure + string previoustoken = ""; + done = false; //mxd + while (parser.SkipWhitespace(true)) + { + string token = parser.ReadToken(); + token = token.ToLowerInvariant(); + + switch (token) + { + case "+": + case "-": + // Next token is a flag (option) to set or remove + bool flagvalue = (token == "+"); + parser.SkipWhitespace(true); + string flagname = parser.ReadToken(); + if (!string.IsNullOrEmpty(flagname)) + { + // Add the flag with its value + flagname = flagname.ToLowerInvariant(); + flags[flagname] = flagvalue; + } + else + { + parser.ReportError("Expected flag name"); + return; + } + break; + + case "action": + case "native": + // We don't need this, ignore up to the first next ; + while (parser.SkipWhitespace(true)) + { + string t = parser.ReadToken(); + if (string.IsNullOrEmpty(t) || t == ";") break; + } + break; + + case "skip_super": + skipsuper = true; + break; + + case "states": + // Now parse actor states until we reach the end of the states structure + while (parser.SkipWhitespace(true)) + { + string statetoken = parser.ReadToken(); + if (!string.IsNullOrEmpty(statetoken)) + { + // Start of scope? + if (statetoken == "{") + { + // This is fine + } + // End of scope? + else if (statetoken == "}") + { + // Done with the states, + // break out of this parse loop + break; + } + // State label? + else if (statetoken == ":") + { + if (!string.IsNullOrEmpty(previoustoken)) + { + // Parse actor state + StateStructure st = new DecorateStateStructure(this, parser, parser.DataManager); + if (parser.HasError) return; + states[previoustoken.ToLowerInvariant()] = st; + } + else + { + parser.ReportError("Expected actor state name"); + return; + } + } + else + { + // Keep token + previoustoken = statetoken; + } + } + else + { + parser.ReportError("Unexpected end of structure"); + return; + } + } + break; + + case "var": //mxd + // Type + parser.SkipWhitespace(true); + string typestr = parser.ReadToken().ToUpperInvariant(); + UniversalType type = UniversalType.EnumOption; // There is no Unknown type, so let's use something impossiburu... + switch (typestr) + { + case "INT": type = UniversalType.Integer; break; + case "FLOAT": type = UniversalType.Float; break; + default: parser.LogWarning("Unknown user variable type"); break; + } + + // Name + parser.SkipWhitespace(true); + string name = parser.ReadToken(); + if (string.IsNullOrEmpty(name)) + { + parser.ReportError("Expected User Variable name"); + return; + } + if (!name.StartsWith("user_", StringComparison.OrdinalIgnoreCase)) + { + parser.ReportError("User Variable name must start with \"user_\" prefix"); + return; + } + if (uservars.ContainsKey(name)) + { + parser.ReportError("User Variable \"" + name + "\" is double defined"); + return; + } + if (!skipsuper && baseclass != null && baseclass.uservars.ContainsKey(name)) + { + parser.ReportError("User variable \"" + name + "\" is already defined in one of the parent classes"); + return; + } + + // Rest + parser.SkipWhitespace(true); + string next = parser.ReadToken(); + if (next == "[") // that's User Array. Let's skip it... + { + int arrlen = -1; + if (!parser.ReadSignedInt(ref arrlen)) + { + parser.ReportError("Expected User Array length"); + return; + } + if (arrlen < 1) + { + parser.ReportError("User Array length must be a positive value"); + return; + } + if (!parser.NextTokenIs("]") || !parser.NextTokenIs(";")) + { + return; + } + } + else if (next != ";") + { + parser.ReportError("Expected \";\", but got \"" + next + "\""); + return; + } + else + { + // Add to collection + uservars.Add(name, type); + } + break; + + case "}": + //mxd. Get user vars from the BaseClass, if we have one + if (!skipsuper && baseclass != null && baseclass.uservars.Count > 0) + { + foreach (var group in baseclass.uservars) + uservars.Add(group.Key, group.Value); + } + + // Actor scope ends here, break out of this parse loop + done = true; + break; + + // Monster property? + case "monster": + // This sets certain flags we are interested in + flags["shootable"] = true; + flags["countkill"] = true; + flags["solid"] = true; + flags["canpushwalls"] = true; + flags["canusewalls"] = true; + flags["activatemcross"] = true; + flags["canpass"] = true; + flags["ismonster"] = true; + break; + + // Projectile property? + case "projectile": + // This sets certain flags we are interested in + flags["noblockmap"] = true; + flags["nogravity"] = true; + flags["dropoff"] = true; + flags["missile"] = true; + flags["activateimpact"] = true; + flags["activatepcross"] = true; + flags["noteleport"] = true; + break; + + // Clearflags property? + case "clearflags": + // Clear all flags + flags.Clear(); + break; + + // Game property? + case "game": + // Include all tokens on the same line + List<string> games = new List<string>(); + while (parser.SkipWhitespace(false)) + { + string v = parser.ReadToken(); + if (string.IsNullOrEmpty(v)) + { + parser.ReportError("Expected \"Game\" property value"); + return; + } + if (v == "\n") break; + if (v == "}") return; //mxd + if (v != ",") games.Add(v.ToLowerInvariant()); + } + props[token] = games; + break; + + // Property + default: + // Property begins with $? Then the whole line is a single value + if (token.StartsWith("$")) + { + // This is for editor-only properties such as $sprite and $category + props[token] = new List<string> { (parser.SkipWhitespace(false) ? parser.ReadLine() : "") }; + } + else + { + // Next tokens up until the next newline are values + List<string> values = new List<string>(); + while (parser.SkipWhitespace(false)) + { + string v = parser.ReadToken(); + if (string.IsNullOrEmpty(v)) + { + parser.ReportError("Unexpected end of structure"); + return; + } + if (v == "\n") break; + if (v == "}") return; //mxd + if (v != ",") values.Add(v); + } + + //mxd. Translate scale to xscale and yscale + if (token == "scale") + { + props["xscale"] = values; + props["yscale"] = values; + } + else + { + props[token] = values; + } + } + break; + } + + if (done) break; //mxd + + // Keep token + previoustoken = token; + } + + //mxd. Check if baseclass is valid + if (inheritclass.ToLowerInvariant() != "actor" && doomednum > -1 && baseclass == null) + { + //check if this class inherits from a class defined in game configuration + Dictionary<int, ThingTypeInfo> things = General.Map.Config.GetThingTypes(); + string inheritclasscheck = inheritclass.ToLowerInvariant(); + + foreach (KeyValuePair<int, ThingTypeInfo> ti in things) + { + if (!string.IsNullOrEmpty(ti.Value.ClassName) && ti.Value.ClassName.ToLowerInvariant() == inheritclasscheck) + { + //states + if (states.Count == 0 && !string.IsNullOrEmpty(ti.Value.Sprite)) + states.Add("spawn", new StateStructure(ti.Value.Sprite.Substring(0, 5))); + + //flags + if (ti.Value.Hangs && !flags.ContainsKey("spawnceiling")) + flags["spawnceiling"] = true; + + if (ti.Value.Blocking > 0 && !flags.ContainsKey("solid")) + flags["solid"] = true; + + //properties + if (!props.ContainsKey("height")) + props["height"] = new List<string> { ti.Value.Height.ToString() }; + + if (!props.ContainsKey("radius")) + props["radius"] = new List<string> { ti.Value.Radius.ToString() }; + + return; + } + } + + parser.LogWarning("Unable to find \"" + inheritclass + "\" class to inherit from, while parsing \"" + classname + ":" + doomednum + "\""); + } + } + + #endregion + } +} diff --git a/Source/Core/ZDoom/DecorateParser.cs b/Source/Core/ZDoom/DecorateParser.cs index 4fcb9c15..7e76f586 100755 --- a/Source/Core/ZDoom/DecorateParser.cs +++ b/Source/Core/ZDoom/DecorateParser.cs @@ -28,785 +28,6 @@ using CodeImp.DoomBuilder.Types; namespace CodeImp.DoomBuilder.ZDoom { - internal sealed class DecorateStateGoto : StateGoto - { - #region ================== DECORATE State Goto parsing - - internal DecorateStateGoto(ActorStructure actor, ZDTextParser zdparser) : base(actor, zdparser) - { - DecorateParser parser = (DecorateParser)zdparser; - - string firsttarget = ""; - string secondtarget = ""; - bool commentreached = false; - bool offsetreached = false; - string offsetstr = ""; - int cindex = 0; - - // This is a bitch to parse because for some bizarre reason someone thought it - // was funny to allow quotes here. Read the whole line and start parsing this manually. - string line = parser.ReadLine(); - - // Skip whitespace - while ((cindex < line.Length) && ((line[cindex] == ' ') || (line[cindex] == '\t'))) - cindex++; - - // Parse first target - while ((cindex < line.Length) && (line[cindex] != ':')) - { - // When a comment is reached, we're done here - if (line[cindex] == '/') - { - if ((cindex + 1 < line.Length) && ((line[cindex + 1] == '/') || (line[cindex + 1] == '*'))) - { - commentreached = true; - break; - } - } - - // Whitespace ends the string - if ((line[cindex] == ' ') || (line[cindex] == '\t')) - break; - - // + sign indicates offset start - if (line[cindex] == '+') - { - cindex++; - offsetreached = true; - break; - } - - // Ignore quotes - if (line[cindex] != '"') - firsttarget += line[cindex]; - - cindex++; - } - - if (!commentreached && !offsetreached) - { - // Skip whitespace - while ((cindex < line.Length) && ((line[cindex] == ' ') || (line[cindex] == '\t'))) - cindex++; - - // Parse second target - while (cindex < line.Length) - { - // When a comment is reached, we're done here - if (line[cindex] == '/') - { - if ((cindex + 1 < line.Length) && ((line[cindex + 1] == '/') || (line[cindex + 1] == '*'))) - { - commentreached = true; - break; - } - } - - // Whitespace ends the string - if ((line[cindex] == ' ') || (line[cindex] == '\t')) - break; - - // + sign indicates offset start - if (line[cindex] == '+') - { - cindex++; - offsetreached = true; - break; - } - - // Ignore quotes and semicolons - if ((line[cindex] != '"') && (line[cindex] != ':')) - secondtarget += line[cindex]; - - cindex++; - } - } - - // Try to find the offset if we still haven't found it yet - if (!offsetreached) - { - // Skip whitespace - while ((cindex < line.Length) && ((line[cindex] == ' ') || (line[cindex] == '\t'))) - cindex++; - - if ((cindex < line.Length) && (line[cindex] == '+')) - { - cindex++; - offsetreached = true; - } - } - - if (offsetreached) - { - // Parse offset - while (cindex < line.Length) - { - // When a comment is reached, we're done here - if (line[cindex] == '/') - { - if ((cindex + 1 < line.Length) && ((line[cindex + 1] == '/') || (line[cindex + 1] == '*'))) - { - commentreached = true; - break; - } - } - - // Whitespace ends the string - if ((line[cindex] == ' ') || (line[cindex] == '\t')) - break; - - // Ignore quotes and semicolons - if ((line[cindex] != '"') && (line[cindex] != ':')) - offsetstr += line[cindex]; - - cindex++; - } - } - - // We should now have a first target, optionally a second target and optionally a sprite offset - - // Check if we don't have the class specified - if (string.IsNullOrEmpty(secondtarget)) - { - // First target is the state to go to - classname = actor.ClassName; - statename = firsttarget.ToLowerInvariant().Trim(); - } - else - { - // First target is the base class to use - // Second target is the state to go to - classname = firsttarget.ToLowerInvariant().Trim(); - statename = secondtarget.ToLowerInvariant().Trim(); - } - - if (offsetstr.Length > 0) - int.TryParse(offsetstr, out spriteoffset); - - if ((classname == "super") && (actor.BaseClass != null)) - classname = actor.BaseClass.ClassName; - } - - #endregion - } - - public sealed class DecorateStateStructure : StateStructure - { - #region ================== DECORATE State Structure parsing - - internal DecorateStateStructure(ActorStructure actor, ZDTextParser zdparser, DataManager dataman) : base(actor, zdparser, dataman) - { - DecorateParser parser = (DecorateParser)zdparser; - string lasttoken = ""; - - // Skip whitespace - while (parser.SkipWhitespace(true)) - { - // Read first token - string token = parser.ReadToken().ToLowerInvariant(); - - // One of the flow control statements? - if ((token == "loop") || (token == "stop") || (token == "wait") || (token == "fail")) - { - // Ignore flow control - } - // Goto? - else if (token == "goto") - { - gotostate = new DecorateStateGoto(actor, parser); - if (parser.HasError) return; - } - // Label? - else if (token == ":") - { - // Rewind so that this label can be read again - if (!string.IsNullOrEmpty(lasttoken)) - parser.DataStream.Seek(-(lasttoken.Length + 1), SeekOrigin.Current); - - // Done here - return; - } - //mxd. Start of inner scope? - else if (token == "{") - { - int bracelevel = 1; - while (!string.IsNullOrEmpty(token) && bracelevel > 0) - { - parser.SkipWhitespace(false); - token = parser.ReadToken(); - switch (token) - { - case "{": bracelevel++; break; - case "}": bracelevel--; break; - } - } - } - // End of scope? - else if (token == "}") - { - // Rewind so that this scope end can be read again - parser.DataStream.Seek(-1, SeekOrigin.Current); - - // Done here - return; - } - else - { - // First part of the sprite name - token = parser.StripTokenQuotes(token); //mxd. First part of the sprite name can be quoted - if (string.IsNullOrEmpty(token)) - { - parser.ReportError("Expected sprite name"); - return; - } - - // Frames of the sprite name - parser.SkipWhitespace(true); - string spriteframes = parser.StripTokenQuotes(parser.ReadToken()); //mxd. Frames can be quoted - if (string.IsNullOrEmpty(spriteframes)) - { - parser.ReportError("Expected sprite frame"); - return; - } - - // Label? - if (spriteframes == ":") - { - // Rewind so that this label can be read again - parser.DataStream.Seek(-(token.Length + 1), SeekOrigin.Current); - - // Done here - return; - } - - // No first sprite yet? - FrameInfo info = new FrameInfo(); //mxd - if (spriteframes.Length > 0) - { - //mxd. I'm not even 50% sure the parser handles all bizzare cases without shifting sprite name / frame blocks, - // so let's log it as a warning, not an error... - if (token.Length != 4) - { - parser.LogWarning("Invalid sprite name \"" + token.ToUpperInvariant() + "\". Sprite names must be exactly 4 characters long"); - } - else - { - // Make the sprite name - string spritename = (token + spriteframes[0]).ToUpperInvariant(); - - // Ignore some odd ZDoom things - if (!spritename.StartsWith("TNT1") && !spritename.StartsWith("----") && !spritename.Contains("#")) - { - info.Sprite = spritename; //mxd - sprites.Add(info); - } - } - } - - // Continue until the end of the line - parser.SkipWhitespace(false); - string t = parser.ReadToken(); - while (!string.IsNullOrEmpty(t) && t != "\n") - { - //mxd. Bright keyword support... - if (t == "bright") - { - info.Bright = true; - } - //mxd. Light() expression support... - else if (t == "light") - { - if (!parser.NextTokenIs("(")) return; - - if (!parser.SkipWhitespace(true)) - { - parser.ReportError("Unexpected end of the structure"); - return; - } - - info.LightName = parser.StripTokenQuotes(parser.ReadToken()); - if (string.IsNullOrEmpty(info.LightName)) - { - parser.ReportError("Expected dynamic light name"); - return; - } - - if (!parser.SkipWhitespace(true)) - { - parser.ReportError("Unexpected end of the structure"); - return; - } - - if (!parser.NextTokenIs(")")) return; - } - //mxd. Inner scope start. Step back and reparse using parent loop - else if (t == "{") - { - // Rewind so that this scope end can be read again - parser.DataStream.Seek(-1, SeekOrigin.Current); - - // Break out of this loop - break; - } - //mxd. Function params start (those can span multiple lines) - else if (t == "(") - { - int bracelevel = 1; - while (!string.IsNullOrEmpty(token) && bracelevel > 0) - { - parser.SkipWhitespace(true); - token = parser.ReadToken(); - switch (token) - { - case "(": bracelevel++; break; - case ")": bracelevel--; break; - } - } - } - //mxd. Because stuff like this is also valid: "Actor Oneliner { States { Spawn: WOOT A 1 A_FadeOut(0.1) Loop }}" - else if (t == "}") - { - // Rewind so that this scope end can be read again - parser.DataStream.Seek(-1, SeekOrigin.Current); - - // Done here - return; - } - - // Read next token - parser.SkipWhitespace(false); - t = parser.ReadToken().ToLowerInvariant(); - } - } - - lasttoken = token; - } - } - - #endregion - } - - public sealed class DecorateActorStructure : ActorStructure - { - #region ================== DECORATE Actor Structure parsing - - internal DecorateActorStructure(ZDTextParser zdparser, DecorateCategoryInfo catinfo) : base(zdparser, catinfo) - { - DecorateParser parser = (DecorateParser)zdparser; - bool done = false; //mxd - - // First next token is the class name - parser.SkipWhitespace(true); - classname = parser.StripTokenQuotes(parser.ReadToken(ACTOR_CLASS_SPECIAL_TOKENS)); - - if (string.IsNullOrEmpty(classname)) - { - parser.ReportError("Expected actor class name"); - return; - } - - //mxd. Fail on duplicates - if (parser.ActorsByClass.ContainsKey(classname.ToLowerInvariant())) - { - parser.ReportError("Actor \"" + classname + "\" is double-defined"); - return; - } - - // Parse tokens before entering the actor scope - while (parser.SkipWhitespace(true)) - { - string token = parser.ReadToken(); - if (!string.IsNullOrEmpty(token)) - { - token = token.ToLowerInvariant(); - - switch (token) - { - case ":": - // The next token must be the class to inherit from - parser.SkipWhitespace(true); - inheritclass = parser.StripTokenQuotes(parser.ReadToken()); - if (string.IsNullOrEmpty(inheritclass)) - { - parser.ReportError("Expected class name to inherit from"); - return; - } - - // Find the actor to inherit from - baseclass = parser.GetArchivedActorByName(inheritclass); - break; - - case "replaces": - // The next token must be the class to replace - parser.SkipWhitespace(true); - replaceclass = parser.StripTokenQuotes(parser.ReadToken()); - if (string.IsNullOrEmpty(replaceclass)) - { - parser.ReportError("Expected class name to replace"); - return; - } - break; - - case "native": - // Igore this token - break; - - case "{": - // Actor scope begins here, - // break out of this parse loop - done = true; - break; - - case "-": - // This could be a negative doomednum (but our parser sees the - as separate token) - // So read whatever is after this token and ignore it (negative doomednum indicates no doomednum) - parser.ReadToken(); - break; - - default: - //mxd. Property begins with $? Then the whole line is a single value - if (token.StartsWith("$")) - { - // This is for editor-only properties such as $sprite and $category - props[token] = new List<string> { (parser.SkipWhitespace(false) ? parser.ReadLine() : "") }; - continue; - } - - if (!int.TryParse(token, NumberStyles.Integer, CultureInfo.InvariantCulture, out doomednum)) // Check if numeric - { - // Not numeric! - parser.ReportError("Expected editor number or start of actor scope while parsing \"" + classname + "\""); - return; - } - - //mxd. Range check - if ((doomednum < General.Map.FormatInterface.MinThingType) || (doomednum > General.Map.FormatInterface.MaxThingType)) - { - // Out of bounds! - parser.ReportError("Actor \"" + classname + "\" has invalid editor number. Editor number must be between " - + General.Map.FormatInterface.MinThingType + " and " + General.Map.FormatInterface.MaxThingType); - return; - } - break; - } - - if (done) break; //mxd - } - else - { - parser.ReportError("Unexpected end of structure"); - return; - } - } - - // Now parse the contents of actor structure - string previoustoken = ""; - done = false; //mxd - while (parser.SkipWhitespace(true)) - { - string token = parser.ReadToken(); - token = token.ToLowerInvariant(); - - switch (token) - { - case "+": - case "-": - // Next token is a flag (option) to set or remove - bool flagvalue = (token == "+"); - parser.SkipWhitespace(true); - string flagname = parser.ReadToken(); - if (!string.IsNullOrEmpty(flagname)) - { - // Add the flag with its value - flagname = flagname.ToLowerInvariant(); - flags[flagname] = flagvalue; - } - else - { - parser.ReportError("Expected flag name"); - return; - } - break; - - case "action": - case "native": - // We don't need this, ignore up to the first next ; - while (parser.SkipWhitespace(true)) - { - string t = parser.ReadToken(); - if (string.IsNullOrEmpty(t) || t == ";") break; - } - break; - - case "skip_super": - skipsuper = true; - break; - - case "states": - // Now parse actor states until we reach the end of the states structure - while (parser.SkipWhitespace(true)) - { - string statetoken = parser.ReadToken(); - if (!string.IsNullOrEmpty(statetoken)) - { - // Start of scope? - if (statetoken == "{") - { - // This is fine - } - // End of scope? - else if (statetoken == "}") - { - // Done with the states, - // break out of this parse loop - break; - } - // State label? - else if (statetoken == ":") - { - if (!string.IsNullOrEmpty(previoustoken)) - { - // Parse actor state - StateStructure st = new DecorateStateStructure(this, parser, parser.DataManager); - if (parser.HasError) return; - states[previoustoken.ToLowerInvariant()] = st; - } - else - { - parser.ReportError("Expected actor state name"); - return; - } - } - else - { - // Keep token - previoustoken = statetoken; - } - } - else - { - parser.ReportError("Unexpected end of structure"); - return; - } - } - break; - - case "var": //mxd - // Type - parser.SkipWhitespace(true); - string typestr = parser.ReadToken().ToUpperInvariant(); - UniversalType type = UniversalType.EnumOption; // There is no Unknown type, so let's use something impossiburu... - switch (typestr) - { - case "INT": type = UniversalType.Integer; break; - case "FLOAT": type = UniversalType.Float; break; - default: parser.LogWarning("Unknown user variable type"); break; - } - - // Name - parser.SkipWhitespace(true); - string name = parser.ReadToken(); - if (string.IsNullOrEmpty(name)) - { - parser.ReportError("Expected User Variable name"); - return; - } - if (!name.StartsWith("user_", StringComparison.OrdinalIgnoreCase)) - { - parser.ReportError("User Variable name must start with \"user_\" prefix"); - return; - } - if (uservars.ContainsKey(name)) - { - parser.ReportError("User Variable \"" + name + "\" is double defined"); - return; - } - if (!skipsuper && baseclass != null && baseclass.uservars.ContainsKey(name)) - { - parser.ReportError("User variable \"" + name + "\" is already defined in one of the parent classes"); - return; - } - - // Rest - parser.SkipWhitespace(true); - string next = parser.ReadToken(); - if (next == "[") // that's User Array. Let's skip it... - { - int arrlen = -1; - if (!parser.ReadSignedInt(ref arrlen)) - { - parser.ReportError("Expected User Array length"); - return; - } - if (arrlen < 1) - { - parser.ReportError("User Array length must be a positive value"); - return; - } - if (!parser.NextTokenIs("]") || !parser.NextTokenIs(";")) - { - return; - } - } - else if (next != ";") - { - parser.ReportError("Expected \";\", but got \"" + next + "\""); - return; - } - else - { - // Add to collection - uservars.Add(name, type); - } - break; - - case "}": - //mxd. Get user vars from the BaseClass, if we have one - if (!skipsuper && baseclass != null && baseclass.uservars.Count > 0) - { - foreach (var group in baseclass.uservars) - uservars.Add(group.Key, group.Value); - } - - // Actor scope ends here, break out of this parse loop - done = true; - break; - - // Monster property? - case "monster": - // This sets certain flags we are interested in - flags["shootable"] = true; - flags["countkill"] = true; - flags["solid"] = true; - flags["canpushwalls"] = true; - flags["canusewalls"] = true; - flags["activatemcross"] = true; - flags["canpass"] = true; - flags["ismonster"] = true; - break; - - // Projectile property? - case "projectile": - // This sets certain flags we are interested in - flags["noblockmap"] = true; - flags["nogravity"] = true; - flags["dropoff"] = true; - flags["missile"] = true; - flags["activateimpact"] = true; - flags["activatepcross"] = true; - flags["noteleport"] = true; - break; - - // Clearflags property? - case "clearflags": - // Clear all flags - flags.Clear(); - break; - - // Game property? - case "game": - // Include all tokens on the same line - List<string> games = new List<string>(); - while (parser.SkipWhitespace(false)) - { - string v = parser.ReadToken(); - if (string.IsNullOrEmpty(v)) - { - parser.ReportError("Expected \"Game\" property value"); - return; - } - if (v == "\n") break; - if (v == "}") return; //mxd - if (v != ",") games.Add(v.ToLowerInvariant()); - } - props[token] = games; - break; - - // Property - default: - // Property begins with $? Then the whole line is a single value - if (token.StartsWith("$")) - { - // This is for editor-only properties such as $sprite and $category - props[token] = new List<string> { (parser.SkipWhitespace(false) ? parser.ReadLine() : "") }; - } - else - { - // Next tokens up until the next newline are values - List<string> values = new List<string>(); - while (parser.SkipWhitespace(false)) - { - string v = parser.ReadToken(); - if (string.IsNullOrEmpty(v)) - { - parser.ReportError("Unexpected end of structure"); - return; - } - if (v == "\n") break; - if (v == "}") return; //mxd - if (v != ",") values.Add(v); - } - - //mxd. Translate scale to xscale and yscale - if (token == "scale") - { - props["xscale"] = values; - props["yscale"] = values; - } - else - { - props[token] = values; - } - } - break; - } - - if (done) break; //mxd - - // Keep token - previoustoken = token; - } - - //mxd. Check if baseclass is valid - if (inheritclass.ToLowerInvariant() != "actor" && doomednum > -1 && baseclass == null) - { - //check if this class inherits from a class defined in game configuration - Dictionary<int, ThingTypeInfo> things = General.Map.Config.GetThingTypes(); - string inheritclasscheck = inheritclass.ToLowerInvariant(); - - foreach (KeyValuePair<int, ThingTypeInfo> ti in things) - { - if (!string.IsNullOrEmpty(ti.Value.ClassName) && ti.Value.ClassName.ToLowerInvariant() == inheritclasscheck) - { - //states - if (states.Count == 0 && !string.IsNullOrEmpty(ti.Value.Sprite)) - states.Add("spawn", new StateStructure(ti.Value.Sprite.Substring(0, 5))); - - //flags - if (ti.Value.Hangs && !flags.ContainsKey("spawnceiling")) - flags["spawnceiling"] = true; - - if (ti.Value.Blocking > 0 && !flags.ContainsKey("solid")) - flags["solid"] = true; - - //properties - if (!props.ContainsKey("height")) - props["height"] = new List<string> { ti.Value.Height.ToString() }; - - if (!props.ContainsKey("radius")) - props["radius"] = new List<string> { ti.Value.Radius.ToString() }; - - return; - } - } - - parser.LogWarning("Unable to find \"" + inheritclass + "\" class to inherit from, while parsing \"" + classname + ":" + doomednum + "\""); - } - } - - #endregion - } - public sealed class DecorateParser : ZDTextParser { #region ================== Delegates diff --git a/Source/Core/ZDoom/DecorateStateGoto.cs b/Source/Core/ZDoom/DecorateStateGoto.cs new file mode 100755 index 00000000..df44747a --- /dev/null +++ b/Source/Core/ZDoom/DecorateStateGoto.cs @@ -0,0 +1,167 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace CodeImp.DoomBuilder.ZDoom +{ + internal sealed class DecorateStateGoto : StateGoto + { + #region ================== DECORATE State Goto parsing + + internal DecorateStateGoto(ActorStructure actor, ZDTextParser parser) : base(actor, parser) + { + string firsttarget = ""; + string secondtarget = ""; + bool commentreached = false; + bool offsetreached = false; + string offsetstr = ""; + int cindex = 0; + + // This is a bitch to parse because for some bizarre reason someone thought it + // was funny to allow quotes here. Read the whole line and start parsing this manually. + string line = parser.ReadLine(); + + // Skip whitespace + while ((cindex < line.Length) && ((line[cindex] == ' ') || (line[cindex] == '\t'))) + cindex++; + + // Parse first target + while ((cindex < line.Length) && (line[cindex] != ':')) + { + // When a comment is reached, we're done here + if (line[cindex] == '/') + { + if ((cindex + 1 < line.Length) && ((line[cindex + 1] == '/') || (line[cindex + 1] == '*'))) + { + commentreached = true; + break; + } + } + + // Whitespace ends the string + if ((line[cindex] == ' ') || (line[cindex] == '\t')) + break; + + // + sign indicates offset start + if (line[cindex] == '+') + { + cindex++; + offsetreached = true; + break; + } + + // Ignore quotes + if (line[cindex] != '"') + firsttarget += line[cindex]; + + cindex++; + } + + if (!commentreached && !offsetreached) + { + // Skip whitespace + while ((cindex < line.Length) && ((line[cindex] == ' ') || (line[cindex] == '\t'))) + cindex++; + + // Parse second target + while (cindex < line.Length) + { + // When a comment is reached, we're done here + if (line[cindex] == '/') + { + if ((cindex + 1 < line.Length) && ((line[cindex + 1] == '/') || (line[cindex + 1] == '*'))) + { + commentreached = true; + break; + } + } + + // Whitespace ends the string + if ((line[cindex] == ' ') || (line[cindex] == '\t')) + break; + + // + sign indicates offset start + if (line[cindex] == '+') + { + cindex++; + offsetreached = true; + break; + } + + // Ignore quotes and semicolons + if ((line[cindex] != '"') && (line[cindex] != ':')) + secondtarget += line[cindex]; + + cindex++; + } + } + + // Try to find the offset if we still haven't found it yet + if (!offsetreached) + { + // Skip whitespace + while ((cindex < line.Length) && ((line[cindex] == ' ') || (line[cindex] == '\t'))) + cindex++; + + if ((cindex < line.Length) && (line[cindex] == '+')) + { + cindex++; + offsetreached = true; + } + } + + if (offsetreached) + { + // Parse offset + while (cindex < line.Length) + { + // When a comment is reached, we're done here + if (line[cindex] == '/') + { + if ((cindex + 1 < line.Length) && ((line[cindex + 1] == '/') || (line[cindex + 1] == '*'))) + { + commentreached = true; + break; + } + } + + // Whitespace ends the string + if ((line[cindex] == ' ') || (line[cindex] == '\t')) + break; + + // Ignore quotes and semicolons + if ((line[cindex] != '"') && (line[cindex] != ':')) + offsetstr += line[cindex]; + + cindex++; + } + } + + // We should now have a first target, optionally a second target and optionally a sprite offset + + // Check if we don't have the class specified + if (string.IsNullOrEmpty(secondtarget)) + { + // First target is the state to go to + classname = actor.ClassName; + statename = firsttarget.ToLowerInvariant().Trim(); + } + else + { + // First target is the base class to use + // Second target is the state to go to + classname = firsttarget.ToLowerInvariant().Trim(); + statename = secondtarget.ToLowerInvariant().Trim(); + } + + if (offsetstr.Length > 0) + int.TryParse(offsetstr, out spriteoffset); + + if ((classname == "super") && (actor.BaseClass != null)) + classname = actor.BaseClass.ClassName; + } + + #endregion + } +} diff --git a/Source/Core/ZDoom/DecorateStateStructure.cs b/Source/Core/ZDoom/DecorateStateStructure.cs new file mode 100755 index 00000000..79899ab4 --- /dev/null +++ b/Source/Core/ZDoom/DecorateStateStructure.cs @@ -0,0 +1,209 @@ +using CodeImp.DoomBuilder.Config; +using CodeImp.DoomBuilder.Data; +using CodeImp.DoomBuilder.Types; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; + +namespace CodeImp.DoomBuilder.ZDoom +{ + + public sealed class DecorateStateStructure : StateStructure + { + #region ================== DECORATE State Structure parsing + + internal DecorateStateStructure(ActorStructure actor, ZDTextParser zdparser, DataManager dataman) : base(actor, zdparser, dataman) + { + DecorateParser parser = (DecorateParser)zdparser; + string lasttoken = ""; + + // Skip whitespace + while (parser.SkipWhitespace(true)) + { + // Read first token + string token = parser.ReadToken().ToLowerInvariant(); + + // One of the flow control statements? + if ((token == "loop") || (token == "stop") || (token == "wait") || (token == "fail")) + { + // Ignore flow control + } + // Goto? + else if (token == "goto") + { + gotostate = new DecorateStateGoto(actor, parser); + if (parser.HasError) return; + } + // Label? + else if (token == ":") + { + // Rewind so that this label can be read again + if (!string.IsNullOrEmpty(lasttoken)) + parser.DataStream.Seek(-(lasttoken.Length + 1), SeekOrigin.Current); + + // Done here + return; + } + //mxd. Start of inner scope? + else if (token == "{") + { + int bracelevel = 1; + while (!string.IsNullOrEmpty(token) && bracelevel > 0) + { + parser.SkipWhitespace(false); + token = parser.ReadToken(); + switch (token) + { + case "{": bracelevel++; break; + case "}": bracelevel--; break; + } + } + } + // End of scope? + else if (token == "}") + { + // Rewind so that this scope end can be read again + parser.DataStream.Seek(-1, SeekOrigin.Current); + + // Done here + return; + } + else + { + // First part of the sprite name + token = parser.StripTokenQuotes(token); //mxd. First part of the sprite name can be quoted + if (string.IsNullOrEmpty(token)) + { + parser.ReportError("Expected sprite name"); + return; + } + + // Frames of the sprite name + parser.SkipWhitespace(true); + string spriteframes = parser.StripTokenQuotes(parser.ReadToken()); //mxd. Frames can be quoted + if (string.IsNullOrEmpty(spriteframes)) + { + parser.ReportError("Expected sprite frame"); + return; + } + + // Label? + if (spriteframes == ":") + { + // Rewind so that this label can be read again + parser.DataStream.Seek(-(token.Length + 1), SeekOrigin.Current); + + // Done here + return; + } + + // No first sprite yet? + FrameInfo info = new FrameInfo(); //mxd + if (spriteframes.Length > 0) + { + //mxd. I'm not even 50% sure the parser handles all bizzare cases without shifting sprite name / frame blocks, + // so let's log it as a warning, not an error... + if (token.Length != 4) + { + parser.LogWarning("Invalid sprite name \"" + token.ToUpperInvariant() + "\". Sprite names must be exactly 4 characters long"); + } + else + { + // Make the sprite name + string spritename = (token + spriteframes[0]).ToUpperInvariant(); + + // Ignore some odd ZDoom things + if (!spritename.StartsWith("TNT1") && !spritename.StartsWith("----") && !spritename.Contains("#")) + { + info.Sprite = spritename; //mxd + sprites.Add(info); + } + } + } + + // Continue until the end of the line + parser.SkipWhitespace(false); + string t = parser.ReadToken(); + while (!string.IsNullOrEmpty(t) && t != "\n") + { + //mxd. Bright keyword support... + if (t == "bright") + { + info.Bright = true; + } + //mxd. Light() expression support... + else if (t == "light") + { + if (!parser.NextTokenIs("(")) return; + + if (!parser.SkipWhitespace(true)) + { + parser.ReportError("Unexpected end of the structure"); + return; + } + + info.LightName = parser.StripTokenQuotes(parser.ReadToken()); + if (string.IsNullOrEmpty(info.LightName)) + { + parser.ReportError("Expected dynamic light name"); + return; + } + + if (!parser.SkipWhitespace(true)) + { + parser.ReportError("Unexpected end of the structure"); + return; + } + + if (!parser.NextTokenIs(")")) return; + } + //mxd. Inner scope start. Step back and reparse using parent loop + else if (t == "{") + { + // Rewind so that this scope end can be read again + parser.DataStream.Seek(-1, SeekOrigin.Current); + + // Break out of this loop + break; + } + //mxd. Function params start (those can span multiple lines) + else if (t == "(") + { + int bracelevel = 1; + while (!string.IsNullOrEmpty(token) && bracelevel > 0) + { + parser.SkipWhitespace(true); + token = parser.ReadToken(); + switch (token) + { + case "(": bracelevel++; break; + case ")": bracelevel--; break; + } + } + } + //mxd. Because stuff like this is also valid: "Actor Oneliner { States { Spawn: WOOT A 1 A_FadeOut(0.1) Loop }}" + else if (t == "}") + { + // Rewind so that this scope end can be read again + parser.DataStream.Seek(-1, SeekOrigin.Current); + + // Done here + return; + } + + // Read next token + parser.SkipWhitespace(false); + t = parser.ReadToken().ToLowerInvariant(); + } + } + + lasttoken = token; + } + } + + #endregion + } +} diff --git a/Source/Core/ZDoom/StateGoto.cs b/Source/Core/ZDoom/StateGoto.cs index f12e7e16..5c3a3964 100755 --- a/Source/Core/ZDoom/StateGoto.cs +++ b/Source/Core/ZDoom/StateGoto.cs @@ -45,6 +45,11 @@ namespace CodeImp.DoomBuilder.ZDoom #region ================== Constructor / Disposer // Constructor + internal StateGoto() + { + + } + internal StateGoto(ActorStructure actor, ZDTextParser parser) { diff --git a/Source/Core/ZDoom/ZScriptActorStructure.cs b/Source/Core/ZDoom/ZScriptActorStructure.cs new file mode 100755 index 00000000..4836b844 --- /dev/null +++ b/Source/Core/ZDoom/ZScriptActorStructure.cs @@ -0,0 +1,499 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; + +namespace CodeImp.DoomBuilder.ZDoom +{ + public sealed class ZScriptActorStructure : ActorStructure + { + private bool ParseDefaultBlock(ZScriptParser parser, Stream stream, ZScriptTokenizer tokenizer) + { + tokenizer.SkipWhitespace(); + ZScriptToken token = tokenizer.ExpectToken(ZScriptTokenType.OpenCurly); + if (token == null || !token.IsValid) + { + parser.ReportError("Expected {, got " + ((Object)token ?? "<null>").ToString()); + return false; + } + + ZScriptTokenType[] whitespacetypes = new ZScriptTokenType[] { ZScriptTokenType.Newline, ZScriptTokenType.Whitespace, ZScriptTokenType.BlockComment, ZScriptTokenType.LineComment }; + + // todo parse defaults block + while (true) + { + tokenizer.SkipWhitespace(); + long cpos = stream.Position; + token = tokenizer.ReadToken(); + if (token == null) + { + parser.ReportError("Expected a token"); + return false; + } + + if (token.Type == ZScriptTokenType.CloseCurly) + break; + + switch (token.Type) + { + case ZScriptTokenType.Whitespace: + case ZScriptTokenType.BlockComment: + case ZScriptTokenType.Newline: + break; + + // flag definition (+/-) + case ZScriptTokenType.OpAdd: + case ZScriptTokenType.OpSubtract: + { + bool flagset = (token.Type == ZScriptTokenType.OpAdd); + string flagname = parser.ParseDottedIdentifier(); + if (flagname == null) return false; + + //parser.LogWarning(string.Format("{0}{1}", (flagset ? '+' : '-'), flagname)); + // set flag + flags[flagname] = flagset; + break; + } + + // property or combo definition + case ZScriptTokenType.Identifier: + { + stream.Position = cpos; + string propertyname = parser.ParseDottedIdentifier(); + if (propertyname == null) return false; + List<string> propertyvalues = new List<string>(); + + // read in property values, until semicolon reached + while (true) + { + tokenizer.SkipWhitespace(); + List<ZScriptToken> expr = parser.ParseExpression(); + string exprstring = ZScriptTokenizer.TokensToString(expr); + + token = tokenizer.ExpectToken(ZScriptTokenType.Comma, ZScriptTokenType.Semicolon); + if (token == null || !token.IsValid) + { + parser.ReportError("Expected comma or ;, got " + ((Object)token ?? "<null>").ToString()); + return false; + } + + propertyvalues.Add(exprstring); + if (token.Type == ZScriptTokenType.Semicolon) + break; + } + + //parser.LogWarning(string.Format("{0} = [{1}]", propertyname, string.Join(", ", propertyvalues.ToArray()))); + // set property + props[propertyname] = propertyvalues; + break; + } + } + + } + + return true; + } + + private bool ParseStatesBlock(ZScriptParser parser, Stream stream, ZScriptTokenizer tokenizer) + { + tokenizer.SkipWhitespace(); + ZScriptToken token = tokenizer.ExpectToken(ZScriptTokenType.OpenParen, ZScriptTokenType.OpenCurly); + if (token == null || !token.IsValid) + { + parser.ReportError("Expected ( or {, got " + ((Object)token ?? "<null>").ToString()); + return false; + } + + // we can have some weirdass class name list after States keyword. handle that here. + if (token.Type == ZScriptTokenType.OpenParen) + { + parser.ParseExpression(true); + tokenizer.SkipWhitespace(); + token = tokenizer.ExpectToken(ZScriptTokenType.CloseParen); + if (token == null || !token.IsValid) + { + parser.ReportError("Expected ), got " + ((Object)token ?? "<null>").ToString()); + return false; + } + + tokenizer.SkipWhitespace(); + token = tokenizer.ExpectToken(ZScriptTokenType.OpenCurly); + if (token == null || !token.IsValid) + { + parser.ReportError("Expected {, got " + ((Object)token ?? "<null>").ToString()); + return false; + } + } + + // todo parse states block + stream.Position--; + token = tokenizer.ExpectToken(ZScriptTokenType.OpenCurly); + if (token == null || !token.IsValid) + { + parser.ReportError("Expected {, got " + ((Object)token ?? "<null>").ToString()); + return false; + } + + string statelabel = ""; + while (true) + { + // parse a state block. + // this is a seriously broken approach, but let it be for now. + StateStructure st = new ZScriptStateStructure(this, parser, parser.DataManager); + parser.tokenizer = tokenizer; + if (parser.HasError) return false; + states[statelabel] = st; + + tokenizer.SkipWhitespace(); + long cpos = stream.Position; + token = tokenizer.ExpectToken(ZScriptTokenType.Identifier, ZScriptTokenType.CloseCurly); + if (token == null || !token.IsValid) + { + parser.ReportError("Expected state label or }, got " + ((Object)token ?? "<null>").ToString()); + return false; + } + + if (token.Type == ZScriptTokenType.CloseCurly) + break; + + stream.Position = cpos; + statelabel = parser.ParseDottedIdentifier(); + if (statelabel == null) + return false; + + // otherwise expect a colon + token = tokenizer.ExpectToken(ZScriptTokenType.Colon); + if (token == null || !token.IsValid) + { + parser.ReportError("Expected :, got " + ((Object)token ?? "<null>").ToString()); + return false; + } + } + + return true; + } + + private string ParseTypeName(ZScriptParser parser, Stream stream, ZScriptTokenizer tokenizer) + { + ZScriptToken token = tokenizer.ExpectToken(ZScriptTokenType.Identifier); + if (token == null || !token.IsValid) + { + parser.ReportError("Expected type name, got " + ((Object)token ?? "<null>").ToString()); + return null; + } + + string outs = token.Value; + + long cpos = stream.Position; + tokenizer.SkipWhitespace(); + token = tokenizer.ReadToken(); + if (token != null && token.Type == ZScriptTokenType.OpLessThan) // < + { + string internal_type = ParseTypeName(parser, stream, tokenizer); + if (internal_type == null) + return null; + token = tokenizer.ExpectToken(ZScriptTokenType.OpGreaterThan); + if (token == null || !token.IsValid) + { + parser.ReportError("Expected >, got " + ((Object)token ?? "<null>").ToString()); + return null; + } + return outs + "<" + internal_type + ">"; + } + else + { + stream.Position = cpos; + return outs; + } + } + + internal ZScriptActorStructure(ZDTextParser zdparser, DecorateCategoryInfo catinfo, string _classname, string _replacesname, string _parentname) : base(zdparser, catinfo) + { + ZScriptParser parser = (ZScriptParser)zdparser; + Stream stream = parser.DataStream; + ZScriptTokenizer tokenizer = new ZScriptTokenizer(parser.DataReader); + parser.tokenizer = tokenizer; + + classname = _classname; + replaceclass = _replacesname; + baseclass = parser.GetArchivedActorByName(_parentname); + + ZScriptToken cls_open = tokenizer.ExpectToken(ZScriptTokenType.OpenCurly); + if (cls_open == null || !cls_open.IsValid) + { + parser.ReportError("Expected {, got " + ((Object)cls_open ?? "<null>").ToString()); + return; + } + + // in the class definition, we can have the following: + // - Defaults block + // - States block + // - method signature: [native] [action] <type [, type [...]]> <name> (<arguments>); + // - method: <method signature (except native)> <block> + // - field declaration: [native] <type> <name>; + // - enum definition: enum <name> <block>; + // we are skipping everything, except Defaults and States. + while (true) + { + tokenizer.SkipWhitespace(); + long ocpos = stream.Position; + ZScriptToken token = tokenizer.ExpectToken(ZScriptTokenType.Identifier, ZScriptTokenType.CloseCurly); + if (token == null || !token.IsValid) + { + parser.ReportError("Expected identifier, got " + ((Object)cls_open ?? "<null>").ToString()); + return; + } + if (token.Type == ZScriptTokenType.CloseCurly) // end of class + break; + + string b_lower = token.Value.ToLowerInvariant(); + switch (b_lower) + { + case "default": + if (!ParseDefaultBlock(parser, stream, tokenizer)) + return; + continue; + + case "states": + if (!ParseStatesBlock(parser, stream, tokenizer)) + return; + continue; + + case "enum": + if (!parser.ParseEnum()) + return; + continue; + + case "const": + if (!parser.ParseConst()) + return; + continue; + + // apparently we can have a struct inside a class, but not another class. + case "struct": + if (!parser.ParseClassOrStruct(true, false)) + return; + continue; + + default: + stream.Position = ocpos; + break; + } + + // try to read in a variable/method. + bool bmethod = false; + string[] availablemodifiers = new string[] { "static", "native", "action", "readonly", "protected", "private", "virtual", "override", "meta", "deprecated", "final" }; + string[] methodmodifiers = new string[] { "action", "virtual", "override", "final" }; + HashSet<string> modifiers = new HashSet<string>(); + List<string> types = new List<string>(); + List<string> names = new List<string>(); + List<int> arraylens = new List<int>(); + List<ZScriptToken> args = null; // this is for the future + List<ZScriptToken> body = null; + + while (true) + { + tokenizer.SkipWhitespace(); + long cpos = stream.Position; + token = tokenizer.ExpectToken(ZScriptTokenType.Identifier); + if (token == null || !token.IsValid) + { + parser.ReportError("Expected modifier or name, got " + ((Object)cls_open ?? "<null>").ToString()); + return; + } + + b_lower = token.Value.ToLowerInvariant(); + if (availablemodifiers.Contains(b_lower)) + { + if (modifiers.Contains(b_lower)) + { + parser.ReportError("Field/method modifier '" + b_lower + "' was specified twice"); + return; + } + + if (methodmodifiers.Contains(b_lower)) + bmethod = true; + + modifiers.Add(b_lower); + } + else + { + stream.Position = cpos; + break; + } + } + + // read in the type name(s) + // type name can be: + // - identifier + // - identifier<identifier> + while (true) + { + tokenizer.SkipWhitespace(); + string typename = ParseTypeName(parser, stream, tokenizer); + if (typename == null) + return; + types.Add(typename.ToLowerInvariant()); + long cpos = stream.Position; + tokenizer.SkipWhitespace(); + token = tokenizer.ReadToken(); + if (token == null || token.Type != ZScriptTokenType.Comma) + { + stream.Position = cpos; + break; + } + } + + while (true) + { + string name = null; + int arraylen = 0; + + // read in the method/field name + tokenizer.SkipWhitespace(); + token = tokenizer.ExpectToken(ZScriptTokenType.Identifier); + if (token == null || !token.IsValid) + { + parser.ReportError("Expected field/method name, got " + ((Object)token ?? "<null>").ToString()); + return; + } + name = token.Value.ToLowerInvariant(); + + // check the token. if it's a (, then it's a method. if it's a ;, then it's a field, if it's a [ it's an array field. + // if it's a field and bmethod=true, report error. + tokenizer.SkipWhitespace(); + token = tokenizer.ExpectToken(ZScriptTokenType.Comma, ZScriptTokenType.OpenParen, ZScriptTokenType.OpenSquare, ZScriptTokenType.Semicolon); + if (token == null || !token.IsValid) + { + parser.ReportError("Expected comma, ;, [, or argument list, got " + ((Object)token ?? "<null>").ToString()); + return; + } + + if (token.Type == ZScriptTokenType.OpenParen) + { + // if we have multiple names + if (names.Count > 1) + { + parser.ReportError("Cannot have multiple names in a method"); + return; + } + + bmethod = true; + // now, I could parse this properly, but it won't be used anyway, so I'll do it as a fake expression. + args = parser.ParseExpression(true); + token = tokenizer.ExpectToken(ZScriptTokenType.CloseParen); + if (token == null || !token.IsValid) + { + parser.ReportError("Expected ), got " + ((Object)token ?? "<null>").ToString()); + return; + } + + // also get the body block, if any. + tokenizer.SkipWhitespace(); + long cpos = stream.Position; + token = tokenizer.ExpectToken(ZScriptTokenType.Semicolon, ZScriptTokenType.OpenCurly); + if (token == null || !token.IsValid) + { + parser.ReportError("Expected ; or {, got " + ((Object)token ?? "<null>").ToString()); + return; + } + + // + if (token.Type == ZScriptTokenType.OpenCurly) + { + stream.Position = cpos; + body = parser.ParseBlock(false); + } + } + else + { + if (bmethod) + { + parser.ReportError("Cannot have virtual, override or action fields"); + return; + } + + // array + if (token.Type == ZScriptTokenType.OpenSquare) + { + // read in the size or a constant. + tokenizer.SkipWhitespace(); + token = tokenizer.ExpectToken(ZScriptTokenType.Integer, ZScriptTokenType.Identifier); + if (token == null || !token.IsValid) + { + parser.ReportError("Expected integer or constant, got " + ((Object)token ?? "<null>").ToString()); + return; + } + + // + if (token.Type == ZScriptTokenType.Integer) + arraylen = token.ValueInt; + else arraylen = -1; + + tokenizer.SkipWhitespace(); + token = tokenizer.ExpectToken(ZScriptTokenType.CloseSquare); + if (token == null || !token.IsValid) + { + parser.ReportError("Expected ], got " + ((Object)token ?? "<null>").ToString()); + return; + } + + tokenizer.SkipWhitespace(); + token = tokenizer.ExpectToken(ZScriptTokenType.Semicolon, ZScriptTokenType.Comma); + if (token == null || !token.IsValid) + { + parser.ReportError("Expected ;, got " + ((Object)token ?? "<null>").ToString()); + return; + } + } + } + + names.Add(name); + arraylens.Add(arraylen); + + if (token.Type != ZScriptTokenType.Comma) // next name + break; + } + + // validate modifiers here. + // protected and private cannot be combined. + if (bmethod) + { + if (modifiers.Contains("protected") && modifiers.Contains("private")) + { + parser.ReportError("Cannot have protected and private on the same method"); + return; + } + // virtual and override cannot be combined. + int cvirtual = modifiers.Contains("virtual") ? 1 : 0; + cvirtual += modifiers.Contains("override") ? 1 : 0; + cvirtual += modifiers.Contains("final") ? 1 : 0; + if (cvirtual > 1) + { + parser.ReportError("Cannot have virtual, override and final on the same method"); + return; + } + // meta (what the fuck is that?) probably cant be on a method + if (modifiers.Contains("meta")) + { + parser.ReportError("Cannot have meta on a method"); + return; + } + } + + // finished method or field parsing. + /*for (int i = 0; i < names.Count; i++) + { + string name = names[i]; + int arraylen = arraylens[i]; + + string _args = ""; + if (args != null) _args = " (" + ZScriptTokenizer.TokensToString(args) + ")"; + else if (arraylen != 0) _args = " [" + arraylen.ToString() + "]"; + parser.LogWarning(string.Format("{0} {1} {2}{3}", string.Join(" ", modifiers.ToArray()), string.Join(", ", types.ToArray()), name, _args)); + }*/ + } + } + } +} diff --git a/Source/Core/ZDoom/ZScriptParser.cs b/Source/Core/ZDoom/ZScriptParser.cs index 9c66117a..695abc1f 100755 --- a/Source/Core/ZDoom/ZScriptParser.cs +++ b/Source/Core/ZDoom/ZScriptParser.cs @@ -9,482 +9,6 @@ using System.Text; namespace CodeImp.DoomBuilder.ZDoom { - public sealed class ZScriptActorStructure : ActorStructure - { - private string ParseDottedIdentifier(ZScriptParser parser, Stream stream, ZScriptTokenizer tokenizer) - { - string name = ""; - while (true) - { - ZScriptToken token = tokenizer.ExpectToken(ZScriptTokenType.Identifier); - if (token == null || !token.IsValid) - { - parser.ReportError("Expected identifier, got " + ((Object)token ?? "<null>").ToString()); - return null; - } - - if (name.Length > 0) name += '.'; - name += token.Value.ToLowerInvariant(); - - long cpos = stream.Position; - token = tokenizer.ReadToken(); - if (token.Type != ZScriptTokenType.Dot) - { - stream.Position = cpos; - break; - } - } - - return name; - } - - private bool ParseDefaultBlock(ZScriptParser parser, Stream stream, ZScriptTokenizer tokenizer) - { - tokenizer.SkipWhitespace(); - ZScriptToken token = tokenizer.ExpectToken(ZScriptTokenType.OpenCurly); - if (token == null || !token.IsValid) - { - parser.ReportError("Expected {, got " + ((Object)token ?? "<null>").ToString()); - return false; - } - - ZScriptTokenType[] whitespacetypes = new ZScriptTokenType[] {ZScriptTokenType.Newline, ZScriptTokenType.Whitespace, ZScriptTokenType.BlockComment, ZScriptTokenType.LineComment}; - - // todo parse defaults block - while (true) - { - tokenizer.SkipWhitespace(); - long cpos = stream.Position; - token = tokenizer.ReadToken(); - if (token == null) - { - parser.ReportError("Expected a token"); - return false; - } - - if (token.Type == ZScriptTokenType.CloseCurly) - break; - - switch (token.Type) - { - case ZScriptTokenType.Whitespace: - case ZScriptTokenType.BlockComment: - case ZScriptTokenType.Newline: - break; - - // flag definition (+/-) - case ZScriptTokenType.OpAdd: - case ZScriptTokenType.OpSubtract: - { - bool flagset = (token.Type == ZScriptTokenType.OpAdd); - string flagname = ParseDottedIdentifier(parser, stream, tokenizer); - if (flagname == null) return false; - - //parser.LogWarning(string.Format("{0}{1}", (flagset ? '+' : '-'), flagname)); - // set flag - flags[flagname] = flagset; - break; - } - - // property or combo definition - case ZScriptTokenType.Identifier: - { - stream.Position = cpos; - string propertyname = ParseDottedIdentifier(parser, stream, tokenizer); - if (propertyname == null) return false; - List<string> propertyvalues = new List<string>(); - - // read in property values, until semicolon reached - while (true) - { - tokenizer.SkipWhitespace(); - List<ZScriptToken> expr = parser.ParseExpression(); - string exprstring = ZScriptTokenizer.TokensToString(expr); - - token = tokenizer.ExpectToken(ZScriptTokenType.Comma, ZScriptTokenType.Semicolon); - if (token == null || !token.IsValid) - { - parser.ReportError("Expected comma or ;, got " + ((Object)token ?? "<null>").ToString()); - return false; - } - - propertyvalues.Add(exprstring); - if (token.Type == ZScriptTokenType.Semicolon) - break; - } - - //parser.LogWarning(string.Format("{0} = [{1}]", propertyname, string.Join(", ", propertyvalues.ToArray()))); - // set property - props[propertyname] = propertyvalues; - break; - } - } - - } - - return true; - } - - private bool ParseStatesBlock(ZScriptParser parser, Stream stream, ZScriptTokenizer tokenizer) - { - tokenizer.SkipWhitespace(); - ZScriptToken token = tokenizer.ExpectToken(ZScriptTokenType.OpenParen, ZScriptTokenType.OpenCurly); - if (token == null || !token.IsValid) - { - parser.ReportError("Expected ( or {, got " + ((Object)token ?? "<null>").ToString()); - return false; - } - - // we can have some weirdass class name list after States keyword. handle that here. - if (token.Type == ZScriptTokenType.OpenParen) - { - parser.ParseExpression(true); - tokenizer.SkipWhitespace(); - token = tokenizer.ExpectToken(ZScriptTokenType.CloseParen); - if (token == null || !token.IsValid) - { - parser.ReportError("Expected ), got " + ((Object)token ?? "<null>").ToString()); - return false; - } - - tokenizer.SkipWhitespace(); - token = tokenizer.ExpectToken(ZScriptTokenType.OpenCurly); - if (token == null || !token.IsValid) - { - parser.ReportError("Expected {, got " + ((Object)token ?? "<null>").ToString()); - return false; - } - } - - // todo parse states block - stream.Position--; - List<ZScriptToken> tokens = parser.ParseBlock(false); // do nothing for now - - return (tokens != null); - } - - private string ParseTypeName(ZScriptParser parser, Stream stream, ZScriptTokenizer tokenizer) - { - ZScriptToken token = tokenizer.ExpectToken(ZScriptTokenType.Identifier); - if (token == null || !token.IsValid) - { - parser.ReportError("Expected type name, got " + ((Object)token ?? "<null>").ToString()); - return null; - } - - string outs = token.Value; - - long cpos = stream.Position; - tokenizer.SkipWhitespace(); - token = tokenizer.ReadToken(); - if (token != null && token.Type == ZScriptTokenType.OpLessThan) // < - { - string internal_type = ParseTypeName(parser, stream, tokenizer); - if (internal_type == null) - return null; - token = tokenizer.ExpectToken(ZScriptTokenType.OpGreaterThan); - if (token == null || !token.IsValid) - { - parser.ReportError("Expected >, got " + ((Object)token ?? "<null>").ToString()); - return null; - } - return outs + "<" + internal_type + ">"; - } - else - { - stream.Position = cpos; - return outs; - } - } - - internal ZScriptActorStructure(ZDTextParser zdparser, DecorateCategoryInfo catinfo, string _classname, string _replacesname, string _parentname) : base(zdparser, catinfo) - { - ZScriptParser parser = (ZScriptParser)zdparser; - Stream stream = parser.DataStream; - ZScriptTokenizer tokenizer = new ZScriptTokenizer(parser.DataReader); - parser.tokenizer = tokenizer; - - classname = _classname; - replaceclass = _replacesname; - - ZScriptToken cls_open = tokenizer.ExpectToken(ZScriptTokenType.OpenCurly); - if (cls_open == null || !cls_open.IsValid) - { - parser.ReportError("Expected {, got " + ((Object)cls_open ?? "<null>").ToString()); - return; - } - - // in the class definition, we can have the following: - // - Defaults block - // - States block - // - method signature: [native] [action] <type [, type [...]]> <name> (<arguments>); - // - method: <method signature (except native)> <block> - // - field declaration: [native] <type> <name>; - // - enum definition: enum <name> <block>; - // we are skipping everything, except Defaults and States. - while (true) - { - tokenizer.SkipWhitespace(); - long ocpos = stream.Position; - ZScriptToken token = tokenizer.ExpectToken(ZScriptTokenType.Identifier, ZScriptTokenType.CloseCurly); - if (token == null || !token.IsValid) - { - parser.ReportError("Expected identifier, got " + ((Object)cls_open ?? "<null>").ToString()); - return; - } - if (token.Type == ZScriptTokenType.CloseCurly) // end of class - break; - - string b_lower = token.Value.ToLowerInvariant(); - switch (b_lower) - { - case "default": - if (!ParseDefaultBlock(parser, stream, tokenizer)) - return; - continue; - - case "states": - if (!ParseStatesBlock(parser, stream, tokenizer)) - return; - continue; - - case "enum": - if (!parser.ParseEnum()) - return; - continue; - - case "const": - if (!parser.ParseConst()) - return; - continue; - - // apparently we can have a struct inside a class, but not another class. - case "struct": - if (!parser.ParseClassOrStruct(true, false)) - return; - continue; - - default: - stream.Position = ocpos; - break; - } - - // try to read in a variable/method. - bool bmethod = false; - string[] availablemodifiers = new string[] { "static", "native", "action", "readonly", "protected", "private", "virtual", "override", "meta", "deprecated", "final" }; - string[] methodmodifiers = new string[] { "action", "virtual", "override", "final" }; - HashSet<string> modifiers = new HashSet<string>(); - List<string> types = new List<string>(); - List<string> names = new List<string>(); - List<int> arraylens = new List<int>(); - List<ZScriptToken> args = null; // this is for the future - List<ZScriptToken> body = null; - - while (true) - { - tokenizer.SkipWhitespace(); - long cpos = stream.Position; - token = tokenizer.ExpectToken(ZScriptTokenType.Identifier); - if (token == null || !token.IsValid) - { - parser.ReportError("Expected modifier or name, got " + ((Object)cls_open ?? "<null>").ToString()); - return; - } - - b_lower = token.Value.ToLowerInvariant(); - if (availablemodifiers.Contains(b_lower)) - { - if (modifiers.Contains(b_lower)) - { - parser.ReportError("Field/method modifier '" + b_lower + "' was specified twice"); - return; - } - - if (methodmodifiers.Contains(b_lower)) - bmethod = true; - - modifiers.Add(b_lower); - } - else - { - stream.Position = cpos; - break; - } - } - - // read in the type name(s) - // type name can be: - // - identifier - // - identifier<identifier> - while (true) - { - tokenizer.SkipWhitespace(); - string typename = ParseTypeName(parser, stream, tokenizer); - if (typename == null) - return; - types.Add(typename.ToLowerInvariant()); - long cpos = stream.Position; - tokenizer.SkipWhitespace(); - token = tokenizer.ReadToken(); - if (token == null || token.Type != ZScriptTokenType.Comma) - { - stream.Position = cpos; - break; - } - } - - while (true) - { - string name = null; - int arraylen = 0; - - // read in the method/field name - tokenizer.SkipWhitespace(); - token = tokenizer.ExpectToken(ZScriptTokenType.Identifier); - if (token == null || !token.IsValid) - { - parser.ReportError("Expected field/method name, got " + ((Object)token ?? "<null>").ToString()); - return; - } - name = token.Value.ToLowerInvariant(); - - // check the token. if it's a (, then it's a method. if it's a ;, then it's a field, if it's a [ it's an array field. - // if it's a field and bmethod=true, report error. - tokenizer.SkipWhitespace(); - token = tokenizer.ExpectToken(ZScriptTokenType.Comma, ZScriptTokenType.OpenParen, ZScriptTokenType.OpenSquare, ZScriptTokenType.Semicolon); - if (token == null || !token.IsValid) - { - parser.ReportError("Expected comma, ;, [, or argument list, got " + ((Object)token ?? "<null>").ToString()); - return; - } - - if (token.Type == ZScriptTokenType.OpenParen) - { - // if we have multiple names - if (names.Count > 1) - { - parser.ReportError("Cannot have multiple names in a method"); - return; - } - - bmethod = true; - // now, I could parse this properly, but it won't be used anyway, so I'll do it as a fake expression. - args = parser.ParseExpression(true); - token = tokenizer.ExpectToken(ZScriptTokenType.CloseParen); - if (token == null || !token.IsValid) - { - parser.ReportError("Expected ), got " + ((Object)token ?? "<null>").ToString()); - return; - } - - // also get the body block, if any. - tokenizer.SkipWhitespace(); - long cpos = stream.Position; - token = tokenizer.ExpectToken(ZScriptTokenType.Semicolon, ZScriptTokenType.OpenCurly); - if (token == null || !token.IsValid) - { - parser.ReportError("Expected ; or {, got " + ((Object)token ?? "<null>").ToString()); - return; - } - - // - if (token.Type == ZScriptTokenType.OpenCurly) - { - stream.Position = cpos; - body = parser.ParseBlock(false); - } - } - else - { - if (bmethod) - { - parser.ReportError("Cannot have virtual, override or action fields"); - return; - } - - // array - if (token.Type == ZScriptTokenType.OpenSquare) - { - // read in the size or a constant. - tokenizer.SkipWhitespace(); - token = tokenizer.ExpectToken(ZScriptTokenType.Integer, ZScriptTokenType.Identifier); - if (token == null || !token.IsValid) - { - parser.ReportError("Expected integer or constant, got " + ((Object)token ?? "<null>").ToString()); - return; - } - - // - if (token.Type == ZScriptTokenType.Integer) - arraylen = token.ValueInt; - else arraylen = -1; - - tokenizer.SkipWhitespace(); - token = tokenizer.ExpectToken(ZScriptTokenType.CloseSquare); - if (token == null || !token.IsValid) - { - parser.ReportError("Expected ], got " + ((Object)token ?? "<null>").ToString()); - return; - } - - tokenizer.SkipWhitespace(); - token = tokenizer.ExpectToken(ZScriptTokenType.Semicolon, ZScriptTokenType.Comma); - if (token == null || !token.IsValid) - { - parser.ReportError("Expected ;, got " + ((Object)token ?? "<null>").ToString()); - return; - } - } - } - - names.Add(name); - arraylens.Add(arraylen); - - if (token.Type != ZScriptTokenType.Comma) // next name - break; - } - - // validate modifiers here. - // protected and private cannot be combined. - if (bmethod) - { - if (modifiers.Contains("protected") && modifiers.Contains("private")) - { - parser.ReportError("Cannot have protected and private on the same method"); - return; - } - // virtual and override cannot be combined. - int cvirtual = modifiers.Contains("virtual")?1:0; - cvirtual += modifiers.Contains("override")?1:0; - cvirtual += modifiers.Contains("final")?1:0; - if (cvirtual > 1) - { - parser.ReportError("Cannot have virtual, override and final on the same method"); - return; - } - // meta (what the fuck is that?) probably cant be on a method - if (modifiers.Contains("meta")) - { - parser.ReportError("Cannot have meta on a method"); - return; - } - } - - // finished method or field parsing. - /*for (int i = 0; i < names.Count; i++) - { - string name = names[i]; - int arraylen = arraylens[i]; - - string _args = ""; - if (args != null) _args = " (" + ZScriptTokenizer.TokensToString(args) + ")"; - else if (arraylen != 0) _args = " [" + arraylen.ToString() + "]"; - parser.LogWarning(string.Format("{0} {1} {2}{3}", string.Join(" ", modifiers.ToArray()), string.Join(", ", types.ToArray()), name, _args)); - }*/ - } - } - } - public sealed class ZScriptParser : ZDTextParser { #region ================== Internal classes @@ -982,6 +506,65 @@ namespace CodeImp.DoomBuilder.ZDoom return true; } + internal bool ParseInteger(out int value) + { + value = 1; + + ZScriptToken token = tokenizer.ExpectToken(ZScriptTokenType.Integer, ZScriptTokenType.OpAdd, ZScriptTokenType.OpSubtract); + if (token == null || !token.IsValid) + { + ReportError("Expected integer, got " + ((Object)token ?? "<null>").ToString()); + return false; + } + + if (token.Type == ZScriptTokenType.OpSubtract) value = -1; + + if (token.Type != ZScriptTokenType.Integer) + { + token = tokenizer.ExpectToken(ZScriptTokenType.Integer); + if (token == null || !token.IsValid) + { + ReportError("Expected integer, got " + ((Object)token ?? "<null>").ToString()); + return false; + } + + value *= token.ValueInt; + return true; + } + else + { + value = token.ValueInt; + return true; + } + } + + internal string ParseDottedIdentifier() + { + string name = ""; + while (true) + { + ZScriptToken token = tokenizer.ExpectToken(ZScriptTokenType.Identifier); + if (token == null || !token.IsValid) + { + ReportError("Expected identifier, got " + ((Object)token ?? "<null>").ToString()); + return null; + } + + if (name.Length > 0) name += '.'; + name += token.Value.ToLowerInvariant(); + + long cpos = datastream.Position; + token = tokenizer.ReadToken(); + if (token.Type != ZScriptTokenType.Dot) + { + datastream.Position = cpos; + break; + } + } + + return name; + } + internal bool ParseClassOrStruct(bool isstruct, bool extend) { // 'class' keyword is already parsed diff --git a/Source/Core/ZDoom/ZScriptStateGoto.cs b/Source/Core/ZDoom/ZScriptStateGoto.cs new file mode 100755 index 00000000..7394eda0 --- /dev/null +++ b/Source/Core/ZDoom/ZScriptStateGoto.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; + +namespace CodeImp.DoomBuilder.ZDoom +{ + internal sealed class ZScriptStateGoto : StateGoto + { + internal ZScriptStateGoto(ActorStructure actor, ZDTextParser zdparser) : base(actor, zdparser) + { + // goto syntax that is accepted by GZDB is [classname::]statename[+offset] + + ZScriptParser parser = (ZScriptParser)zdparser; + Stream stream = parser.DataStream; + ZScriptTokenizer tokenizer = new ZScriptTokenizer(parser.DataReader); + parser.tokenizer = tokenizer; + + tokenizer.SkipWhitespace(); + string firsttarget = parser.ParseDottedIdentifier(); + if (firsttarget == null) + return; + + ZScriptToken token; + + string secondtarget = null; + int offset = 0; + + tokenizer.SkipWhitespace(); + token = tokenizer.ExpectToken(ZScriptTokenType.DoubleColon); + if (token != null && token.IsValid) + { + secondtarget = parser.ParseDottedIdentifier(); + if (secondtarget == null) + return; + } + + tokenizer.SkipWhitespace(); + token = tokenizer.ExpectToken(ZScriptTokenType.OpAdd); + if (token != null && token.IsValid) + { + tokenizer.SkipWhitespace(); + token = tokenizer.ExpectToken(ZScriptTokenType.Integer); + if (token == null || !token.IsValid) + { + parser.ReportError("Expected state offset, got " + ((Object)token ?? "<null>").ToString()); + return; + } + + offset = token.ValueInt; + } + + // Check if we don't have the class specified + if (string.IsNullOrEmpty(secondtarget)) + { + // First target is the state to go to + classname = actor.ClassName; + statename = firsttarget.ToLowerInvariant().Trim(); + } + else + { + // First target is the base class to use + // Second target is the state to go to + classname = firsttarget.ToLowerInvariant().Trim(); + statename = secondtarget.ToLowerInvariant().Trim(); + } + + spriteoffset = offset; + + if ((classname == "super") && (actor.BaseClass != null)) + classname = actor.BaseClass.ClassName; + } + } +} diff --git a/Source/Core/ZDoom/ZScriptStateStructure.cs b/Source/Core/ZDoom/ZScriptStateStructure.cs new file mode 100755 index 00000000..836f334a --- /dev/null +++ b/Source/Core/ZDoom/ZScriptStateStructure.cs @@ -0,0 +1,279 @@ +using CodeImp.DoomBuilder.Data; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; + +namespace CodeImp.DoomBuilder.ZDoom +{ + public sealed class ZScriptStateStructure : StateStructure + { + ZScriptToken TryReadSprite(ZScriptParser parser, Stream stream, ZScriptTokenizer tokenizer) + { + // this is a hack to read #### and ----. + // also [\] + string specialinvalid = "-#"; + string outs = ""; + long cpos = stream.Position; + for (int i = 0; ; i++) + { + cpos = stream.Position; + ZScriptToken token = tokenizer.ReadToken(true); + + if ((token == null) || + (token.Type != ZScriptTokenType.Invalid) || + (token.Value == ";") || + ((outs.Length > 0 && token.Value[0] != outs[0]) && specialinvalid.Contains(token.Value[0]))) + { + stream.Position = cpos; + break; + } + + outs += token.Value[0]; + } + + if (outs.Length > 0) + { + ZScriptToken tok = new ZScriptToken(); + tok.Type = ZScriptTokenType.String; + tok.Value = outs; + return tok; + } + + stream.Position = cpos; + return null; + } + + internal ZScriptStateStructure(ActorStructure actor, ZDTextParser zdparser, DataManager dataman) : base(actor, zdparser, dataman) + { + ZScriptParser parser = (ZScriptParser)zdparser; + Stream stream = parser.DataStream; + ZScriptTokenizer tokenizer = new ZScriptTokenizer(parser.DataReader); + parser.tokenizer = tokenizer; + + // todo: parse stuff + // + string[] control_keywords = new string[] { "goto", "loop", "wait", "fail", "stop" }; + + while (true) + { + // expect identifier or string. + // if it's an identifier, it can be goto/loop/wait/fail/stop. + // if it's a string, it's always a sprite name. + tokenizer.SkipWhitespace(); + long cpos = stream.Position; + ZScriptToken token = tokenizer.ExpectToken(ZScriptTokenType.Identifier, ZScriptTokenType.String, ZScriptTokenType.CloseCurly); + if (token == null || !token.IsValid) + { + ZScriptToken _token = TryReadSprite(parser, stream, tokenizer); + if (_token == null) + { + parser.ReportError("Expected identifier or string, got " + ((Object)token ?? "<null>").ToString()); + return; + } + + token = _token; + } + + if (token.Type == ZScriptTokenType.CloseCurly) + { + stream.Position--; + return; // done + } + else if (token.Type == ZScriptTokenType.Identifier) + { + string s_keyword = token.Value.ToLowerInvariant(); + if (control_keywords.Contains(s_keyword)) + { + if (s_keyword == "goto") // just use decorate goto here. should work. but check for semicolon! + { + gotostate = new ZScriptStateGoto(actor, parser); + parser.tokenizer = tokenizer; + } + + //parser.LogWarning(string.Format("keyword {0}", s_keyword)); + + tokenizer.SkipWhitespace(); + token = tokenizer.ExpectToken(ZScriptTokenType.Semicolon); + if (token == null || !token.IsValid) + { + parser.ReportError("Expected ;, got " + ((Object)token ?? "<null>").ToString()); + return; + } + + continue; + } + } + + // make sure it's not a label of the next state. if it is, break out. + long cpos2 = stream.Position; // local rewind point + ZScriptToken token2 = tokenizer.ExpectToken(ZScriptTokenType.Colon, ZScriptTokenType.Dot); + bool nextstate = (token2 != null && token2.IsValid); + stream.Position = cpos2; // rewind to before state label read + if (nextstate) + { + stream.Position = cpos; + break; + } + + // it's a frame definition. read it. + string spritename = token.Value.ToLowerInvariant(); + if (spritename.Length != 4) + { + parser.ReportError("Sprite name should be exactly 4 characters long (got " + spritename + ")"); + return; + } + + tokenizer.SkipWhitespace(); + + token = TryReadSprite(parser, stream, tokenizer); + if (token == null) + { + parser.ReportError("Expected sprite frame(s)"); + return; + } + + string spriteframes = token.Value; + + //parser.LogWarning(string.Format("sprite {0} {1}", spritename, spriteframes)); + + // duration + int duration; + tokenizer.SkipWhitespace(); + if (!parser.ParseInteger(out duration)) + return; + + // now, it can also contain BRIGHT, LIGHT(), OFFSET() + string[] allspecials = new string[] { "bright", "light", "offset", "fast", "slow" }; + HashSet<string> specials = new HashSet<string>(); + // maybe something else. I don't know. + FrameInfo info = new FrameInfo(); + + // Make the sprite name + string realspritename = (spritename + spriteframes[0]).ToUpperInvariant(); + + // Ignore some odd ZDoom things + if (!realspritename.StartsWith("TNT1") && !realspritename.StartsWith("----") && !realspritename.Contains("#")) + { + info.Sprite = realspritename; //mxd + sprites.Add(info); + } + + while (true) + { + tokenizer.SkipWhitespace(); + cpos2 = stream.Position; + token = tokenizer.ExpectToken(ZScriptTokenType.Identifier, ZScriptTokenType.Semicolon, ZScriptTokenType.OpenCurly); + if (token == null || !token.IsValid) + { + parser.ReportError("Expected identifier, ;, or {, got " + ((Object)token ?? "<null>").ToString()); + return; + } + + // if it's opencurly, it means that everything else is an anonymous block. + // skip/parse that. + // if it's semicolon, it means end of the frame. + // if it's BRIGHT, LIGHT() or OFFSET(), it should be processed. + // if it's something else (but identifier), then it's an action function call, process it. + if (token.Type == ZScriptTokenType.OpenCurly) + { + stream.Position--; + if (!parser.SkipBlock()) + return; + break; // this block is done + } + else if (token.Type == ZScriptTokenType.Semicolon) + { + break; // done + } + else // identifier + { + string special = token.Value.ToLowerInvariant(); + if (allspecials.Contains(special)) + { + if (specials.Contains(special)) + { + parser.ReportError("'" + special + "' cannot be used twice"); + return; + } + + specials.Add(special); + + if (special == "bright") + { + info.Bright = true; + } + else if (special == "light" || special == "offset") + { + tokenizer.SkipWhitespace(); + token = tokenizer.ExpectToken(ZScriptTokenType.OpenParen); + if (token == null || !token.IsValid) + { + parser.ReportError("Expected (, got " + ((Object)token ?? "<null>").ToString()); + return; + } + + List<ZScriptToken> tokens = parser.ParseExpression(true); + tokenizer.SkipWhitespace(); + token = tokenizer.ExpectToken(ZScriptTokenType.CloseParen); + if (token == null || !token.IsValid) + { + parser.ReportError("Expected ), got " + ((Object)token ?? "<null>").ToString()); + return; + } + + // parse the light expression. + if (special == "light") + { + if (tokens.Count != 1 || (tokens[0].Type != ZScriptTokenType.String && tokens[0].Type != ZScriptTokenType.Identifier)) + { + parser.ReportError("Light() special takes one string argument"); + return; + } + + info.LightName = tokens[0].Value; + } + } + } + else + { + // + stream.Position = cpos2; + string actionfunction = parser.ParseDottedIdentifier(); + //parser.LogWarning("actionfunction = " + actionfunction); + if (actionfunction == null) + return; + // + tokenizer.SkipWhitespace(); + token = tokenizer.ExpectToken(ZScriptTokenType.OpenParen); + if (token != null && token.IsValid) + { + List<ZScriptToken> tokens = parser.ParseExpression(true); + tokenizer.SkipWhitespace(); + token = tokenizer.ExpectToken(ZScriptTokenType.CloseParen); + if (token == null || !token.IsValid) + { + parser.ReportError("Expected ), got " + ((Object)token ?? "<null>").ToString()); + return; + } + + // possibly do something with the arguments? not now though. + } + + // expect semicolon and break. + token = tokenizer.ExpectToken(ZScriptTokenType.Semicolon); + if (token == null || !token.IsValid) + { + parser.ReportError("Expected ;, got " + ((Object)token ?? "<null>").ToString()); + return; + } + + break; + } // if not special + } // if identifier + } // frame parsing loop (inner) + } // state parsing loop (outer) + } + } +} diff --git a/Source/Core/ZDoom/ZScriptTokenizer.cs b/Source/Core/ZDoom/ZScriptTokenizer.cs index e8906bd6..06d22b52 100755 --- a/Source/Core/ZDoom/ZScriptTokenizer.cs +++ b/Source/Core/ZDoom/ZScriptTokenizer.cs @@ -60,6 +60,7 @@ namespace CodeImp.DoomBuilder.ZDoom // ternary operator (x ? y : z), also the colon after state labels [ZScriptTokenString("?")] Questionmark, [ZScriptTokenString(":")] Colon, + [ZScriptTokenString("::")] DoubleColon, // + - * / << >> ~ ^ & | [ZScriptTokenString("+")] OpAdd, @@ -183,32 +184,32 @@ namespace CodeImp.DoomBuilder.ZDoom // string whitespace = " \r\t\u00A0"; - if (!short_circuit) + // check whitespace + if (whitespace.Contains(c)) { - // check whitespace - if (whitespace.Contains(c)) + string ws_content = ""; + ws_content += c; + while (true) { - string ws_content = ""; - ws_content += c; - while (true) + char cnext = reader.ReadChar(); + if (whitespace.Contains(cnext)) { - char cnext = reader.ReadChar(); - if (whitespace.Contains(cnext)) - { - ws_content += cnext; - continue; - } - - reader.BaseStream.Position--; - break; + ws_content += cnext; + continue; } - tok = new ZScriptToken(); - tok.Type = ZScriptTokenType.Whitespace; - tok.Value = ws_content; - return tok; + reader.BaseStream.Position--; + break; } + tok = new ZScriptToken(); + tok.Type = ZScriptTokenType.Whitespace; + tok.Value = ws_content; + return tok; + } + + if (!short_circuit) + { // check identifier if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||