mirror of
https://git.do.srb2.org/STJr/UltimateZoneBuilder.git
synced 2025-01-31 04:40:55 +00:00
Enabled prototype ZScript support
This commit is contained in:
parent
8afd01f6bd
commit
37a3eab150
11 changed files with 1751 additions and 1274 deletions
|
@ -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>
|
||||
|
|
432
Source/Core/ZDoom/DecorateActorStructure.cs
Executable file
432
Source/Core/ZDoom/DecorateActorStructure.cs
Executable file
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
167
Source/Core/ZDoom/DecorateStateGoto.cs
Executable file
167
Source/Core/ZDoom/DecorateStateGoto.cs
Executable file
|
@ -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
|
||||
}
|
||||
}
|
209
Source/Core/ZDoom/DecorateStateStructure.cs
Executable file
209
Source/Core/ZDoom/DecorateStateStructure.cs
Executable file
|
@ -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
|
||||
}
|
||||
}
|
|
@ -45,6 +45,11 @@ namespace CodeImp.DoomBuilder.ZDoom
|
|||
#region ================== Constructor / Disposer
|
||||
|
||||
// Constructor
|
||||
internal StateGoto()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
internal StateGoto(ActorStructure actor, ZDTextParser parser)
|
||||
{
|
||||
|
||||
|
|
499
Source/Core/ZDoom/ZScriptActorStructure.cs
Executable file
499
Source/Core/ZDoom/ZScriptActorStructure.cs
Executable file
|
@ -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));
|
||||
}*/
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
75
Source/Core/ZDoom/ZScriptStateGoto.cs
Executable file
75
Source/Core/ZDoom/ZScriptStateGoto.cs
Executable file
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
279
Source/Core/ZDoom/ZScriptStateStructure.cs
Executable file
279
Source/Core/ZDoom/ZScriptStateStructure.cs
Executable file
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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') ||
|
||||
|
|
Loading…
Reference in a new issue