UltimateZoneBuilder/Source/Core/ZDoom/ZScriptStateStructure.cs

310 lines
14 KiB
C#
Executable file

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])))
{
if (outs.Length == 0 && (token.Type == ZScriptTokenType.String || token.Type == ZScriptTokenType.Name))
return token;
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)
{
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--;
break; // 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;
}
tokenizer.SkipWhitespace(); // Skip whitepace because there might be whitepsace between the state label and the colon. See https://github.com/jewalky/UltimateDoomBuilder/issues/631
}
// 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();
// this can be a function call, or a constant.
token = tokenizer.ExpectToken(ZScriptTokenType.Identifier);
if (token != null && token.IsValid)
{
duration = -1;
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;
}
}
}
else
{
if (!parser.ParseInteger(out duration))
return;
}
// now, it can also contain BRIGHT, LIGHT(), OFFSET()
string[] allspecials = new string[] { "bright", "light", "offset", "fast", "slow", "nodelay", "canraise" };
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("#")) // [ZZ] some actors have only TNT1 state and receive a random image because of this
{
info.Sprite = realspritename; //mxd
info.Duration = duration;
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.
tokenizer.SkipWhitespace();
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)
TrimLeft();
}
}
}