Enabled prototype ZScript support

This commit is contained in:
ZZYZX 2017-01-16 13:18:46 +02:00
parent 8afd01f6bd
commit 37a3eab150
11 changed files with 1751 additions and 1274 deletions

View file

@ -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>

View 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
}
}

View file

@ -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

View 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
}
}

View 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
}
}

View file

@ -45,6 +45,11 @@ namespace CodeImp.DoomBuilder.ZDoom
#region ================== Constructor / Disposer
// Constructor
internal StateGoto()
{
}
internal StateGoto(ActorStructure actor, ZDTextParser parser)
{

View 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));
}*/
}
}
}
}

View file

@ -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

View 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;
}
}
}

View 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)
}
}
}

View file

@ -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') ||