2017-02-08 21:55:38 +00:00
using CodeImp.DoomBuilder.Types ;
using System ;
2017-01-16 11:18:46 +00:00
using System.Collections.Generic ;
using System.IO ;
using System.Linq ;
using System.Text ;
namespace CodeImp.DoomBuilder.ZDoom
{
public sealed class ZScriptActorStructure : ActorStructure
{
2019-12-08 00:14:21 +00:00
#region = = = = = = = = = = = = = = = = = = Variables
private ZScriptParser parser ;
2017-01-17 04:41:33 +00:00
private Stream stream ;
private ZScriptTokenizer tokenizer ;
2019-12-08 00:14:21 +00:00
private List < string > mixins ;
#endregion
#region = = = = = = = = = = = = = = = = = = Properties
public List < string > Mixins { get { return mixins ; } }
2017-01-17 04:41:33 +00:00
2019-12-08 00:14:21 +00:00
#endregion
internal static bool ParseGZDBComment ( Dictionary < string , List < string > > props , string text )
2017-01-17 09:20:46 +00:00
{
2018-07-24 07:27:29 +00:00
if ( string . IsNullOrWhiteSpace ( text ) )
return false ;
2017-01-17 09:20:46 +00:00
text = text . Trim ( ) ;
// check if it's a GZDB comment
if ( text [ 0 ] ! = '$' )
return false ;
// check next occurrence of " \t\r\u00A0", then put everything else as property without parsing
int nextWhitespace = text . IndexOfAny ( new char [ ] { ' ' , '\t' , '\r' , ' \ u00A0 ' } ) ;
string propertyname = text ;
string propertyvalue = "" ;
if ( nextWhitespace > = 0 )
{
propertyname = propertyname . Substring ( 0 , nextWhitespace ) ;
2017-01-17 18:34:15 +00:00
propertyvalue = text . Substring ( nextWhitespace + 1 ) . Trim ( ) ;
2017-01-17 09:20:46 +00:00
}
2017-01-21 03:05:56 +00:00
props [ propertyname . ToLowerInvariant ( ) ] = new List < string > { propertyvalue } ;
2017-01-17 09:20:46 +00:00
return true ;
}
2017-01-17 04:41:33 +00:00
private bool ParseDefaultBlock ( )
2017-01-16 11:18:46 +00:00
{
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 )
{
long cpos = stream . Position ;
2017-01-17 09:20:46 +00:00
token = tokenizer . ExpectToken ( ZScriptTokenType . Whitespace , ZScriptTokenType . BlockComment , ZScriptTokenType . Newline , ZScriptTokenType . LineComment , ZScriptTokenType . OpAdd , ZScriptTokenType . OpSubtract , ZScriptTokenType . Identifier , ZScriptTokenType . CloseCurly , ZScriptTokenType . Semicolon ) ;
if ( token = = null | | ! token . IsValid )
2017-01-16 11:18:46 +00:00
{
2017-01-17 09:20:46 +00:00
parser . ReportError ( "Expected comment, flag, property, or }, got " + ( ( Object ) token ? ? "<null>" ) . ToString ( ) ) ;
2017-01-16 11:18:46 +00:00
return false ;
}
2017-01-17 09:20:46 +00:00
//if (ClassName == "Enforcer")
// parser.LogWarning(token.ToString());
2017-01-16 11:18:46 +00:00
if ( token . Type = = ZScriptTokenType . CloseCurly )
break ;
switch ( token . Type )
{
case ZScriptTokenType . Whitespace :
case ZScriptTokenType . BlockComment :
case ZScriptTokenType . Newline :
break ;
2017-01-17 09:20:46 +00:00
case ZScriptTokenType . LineComment :
2017-01-21 03:05:56 +00:00
ParseGZDBComment ( props , token . Value ) ;
2017-01-17 09:20:46 +00:00
break ;
2017-01-16 11:18:46 +00:00
// 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
2017-01-18 06:35:26 +00:00
// translate "scale" to x and y scale
if ( propertyname = = "scale" )
{
props [ "xscale" ] = props [ "yscale" ] = propertyvalues ;
}
else
{
props [ propertyname ] = propertyvalues ;
}
2017-01-16 11:18:46 +00:00
break ;
}
}
}
return true ;
}
2017-01-17 04:41:33 +00:00
private bool ParseStatesBlock ( )
2017-01-16 11:18:46 +00:00
{
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.
2017-01-17 04:53:20 +00:00
StateStructure st = new ZScriptStateStructure ( this , parser ) ;
2017-01-16 11:18:46 +00:00
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 ;
}
2017-01-17 04:41:33 +00:00
private string ParseTypeName ( )
2017-01-16 11:18:46 +00:00
{
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 ) // <
{
2017-04-17 09:40:54 +00:00
tokenizer . SkipWhitespace ( ) ;
2017-01-17 04:41:33 +00:00
string internal_type = ParseTypeName ( ) ;
2017-01-16 11:18:46 +00:00
if ( internal_type = = null )
return null ;
2017-04-17 09:40:54 +00:00
tokenizer . SkipWhitespace ( ) ;
2017-01-16 11:18:46 +00:00
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 ;
}
}
2017-02-11 15:45:41 +00:00
private List < int > ParseArrayDimensions ( )
{
List < int > dimensions = new List < int > ( ) ;
while ( true )
{
tokenizer . SkipWhitespace ( ) ;
ZScriptToken token = tokenizer . ExpectToken ( ZScriptTokenType . OpenSquare ) ;
if ( token = = null | | ! token . IsValid ) // array dimensions ended.
return dimensions ;
// parse identifier or int (identifier is a constant, we don't parse this yet)
tokenizer . SkipWhitespace ( ) ;
2017-12-25 00:45:21 +00:00
long cpos = stream . Position ;
token = tokenizer . ExpectToken ( ZScriptTokenType . Integer , ZScriptTokenType . Identifier , ZScriptTokenType . CloseSquare ) ;
2017-02-11 15:45:41 +00:00
if ( token = = null | | ! token . IsValid )
{
parser . ReportError ( "Expected integer or const, got " + ( ( Object ) token ? ? "<null>" ) . ToString ( ) ) ;
return null ;
}
int arraylen = - 1 ;
if ( token . Type = = ZScriptTokenType . Integer )
arraylen = token . ValueInt ;
2017-12-25 00:45:21 +00:00
else if ( token . Type = = ZScriptTokenType . CloseSquare )
{
/* todo determine this somehow... not for now */
stream . Position = cpos ; // code later expects close square
}
2017-09-06 15:57:25 +00:00
else
{
// we can have more identifiers (dotted)
while ( true )
{
2017-12-25 00:45:21 +00:00
cpos = stream . Position ;
2017-09-06 15:57:25 +00:00
token = tokenizer . ExpectToken ( ZScriptTokenType . Dot ) ;
if ( token = = null | | ! token . IsValid )
{
stream . Position = cpos ;
break ;
}
else
{
token = tokenizer . ExpectToken ( ZScriptTokenType . Identifier ) ;
if ( token = = null | | ! token . IsValid )
{
parser . ReportError ( "Expected identifier, got " + ( ( Object ) token ? ? "<null>" ) . ToString ( ) ) ;
return null ;
}
}
}
}
2017-02-11 15:45:41 +00:00
dimensions . Add ( arraylen ) ;
// closing square
tokenizer . SkipWhitespace ( ) ;
token = tokenizer . ExpectToken ( ZScriptTokenType . CloseSquare ) ;
if ( token = = null | | ! token . IsValid )
{
parser . ReportError ( "Expected ], got " + ( ( Object ) token ? ? "<null>" ) . ToString ( ) ) ;
return null ;
}
}
}
2018-12-02 15:51:54 +00:00
private bool ParseFlagdef ( )
{
// flagdef identifier: variable, bitnum;
tokenizer . SkipWhitespace ( ) ;
ZScriptToken token = tokenizer . ExpectToken ( ZScriptTokenType . Identifier ) ;
if ( token = = null | | ! token . IsValid )
{
parser . ReportError ( "Expected flag name, got " + ( ( Object ) token ? ? "<null>" ) . ToString ( ) ) ;
return false ;
}
tokenizer . SkipWhitespace ( ) ;
token = tokenizer . ExpectToken ( ZScriptTokenType . Colon ) ;
if ( token = = null | | ! token . IsValid )
{
parser . ReportError ( "Expected :, got " + ( ( Object ) token ? ? "<null>" ) . ToString ( ) ) ;
return false ;
}
tokenizer . SkipWhitespace ( ) ;
token = tokenizer . ExpectToken ( ZScriptTokenType . Identifier ) ;
if ( token = = null | | ! token . IsValid )
{
parser . ReportError ( "Expected flag base variable, got " + ( ( Object ) token ? ? "<null>" ) . ToString ( ) ) ;
return false ;
}
tokenizer . SkipWhitespace ( ) ;
token = tokenizer . ExpectToken ( ZScriptTokenType . Comma ) ;
if ( token = = null | | ! token . IsValid )
{
parser . ReportError ( "Expected comma, got " + ( ( Object ) token ? ? "<null>" ) . ToString ( ) ) ;
return false ;
}
tokenizer . SkipWhitespace ( ) ;
token = tokenizer . ExpectToken ( ZScriptTokenType . Integer ) ;
if ( token = = null | | ! token . IsValid )
{
parser . ReportError ( "Expected flag bit index, got " + ( ( Object ) token ? ? "<null>" ) . ToString ( ) ) ;
return false ;
}
tokenizer . SkipWhitespace ( ) ;
token = tokenizer . ExpectToken ( ZScriptTokenType . Semicolon ) ;
if ( token = = null | | ! token . IsValid )
{
parser . ReportError ( "Expected semicolon, got " + ( ( Object ) token ? ? "<null>" ) . ToString ( ) ) ;
return false ;
}
return true ;
}
2019-12-08 00:14:21 +00:00
private bool ParseMixin ( )
{
// mixin identifier;
tokenizer . SkipWhitespace ( ) ;
ZScriptToken token = tokenizer . ExpectToken ( ZScriptTokenType . Identifier ) ;
if ( token = = null | | ! token . IsValid )
{
parser . ReportError ( "Expected mixin class name, got " + ( ( Object ) token ? ? "<null>" ) . ToString ( ) ) ;
return false ;
}
mixins . Add ( token . Value . ToLowerInvariant ( ) ) ;
tokenizer . SkipWhitespace ( ) ;
token = tokenizer . ExpectToken ( ZScriptTokenType . Semicolon ) ;
if ( token = = null | | ! token . IsValid )
{
parser . ReportError ( "Expected semicolon, got " + ( ( Object ) token ? ? "<null>" ) . ToString ( ) ) ;
return false ;
}
return true ;
}
2017-01-17 04:41:33 +00:00
private bool ParseProperty ( )
{
// property identifier: identifier, identifier, identifier, ...;
tokenizer . SkipWhitespace ( ) ;
ZScriptToken token = tokenizer . ExpectToken ( ZScriptTokenType . Identifier ) ;
if ( token = = null | | ! token . IsValid )
{
parser . ReportError ( "Expected property name, got " + ( ( Object ) token ? ? "<null>" ) . ToString ( ) ) ;
return false ;
}
tokenizer . SkipWhitespace ( ) ;
token = tokenizer . ExpectToken ( ZScriptTokenType . Colon ) ;
if ( token = = null | | ! token . IsValid )
{
parser . ReportError ( "Expected :, got " + ( ( Object ) token ? ? "<null>" ) . ToString ( ) ) ;
return false ;
}
while ( true )
{
// expect identifier, then either a comma or a semicolon.
// semicolon means end of definition, comma means we parse next identifier.
tokenizer . SkipWhitespace ( ) ;
token = tokenizer . ExpectToken ( ZScriptTokenType . Identifier ) ;
if ( token = = null | | ! token . IsValid )
{
parser . ReportError ( "Expected variable, got " + ( ( Object ) token ? ? "<null>" ) . ToString ( ) ) ;
return false ;
}
tokenizer . SkipWhitespace ( ) ;
token = tokenizer . ExpectToken ( ZScriptTokenType . Semicolon , ZScriptTokenType . Comma ) ;
if ( token = = null | | ! token . IsValid )
{
parser . ReportError ( "Expected comma or ;, got " + ( ( Object ) token ? ? "<null>" ) . ToString ( ) ) ;
return false ;
}
if ( token . Type = = ZScriptTokenType . Semicolon )
break ;
}
return true ;
}
2017-03-06 04:07:19 +00:00
private string ParseVersion ( bool required )
{
// read in the version.
tokenizer . SkipWhitespace ( ) ;
ZScriptToken token = tokenizer . ExpectToken ( ZScriptTokenType . OpenParen ) ;
if ( token = = null | | ! token . IsValid )
{
if ( required )
parser . ReportError ( "Expected (, got " + ( ( Object ) token ? ? "<null>" ) . ToString ( ) ) ;
return null ;
}
tokenizer . SkipWhitespace ( ) ;
token = tokenizer . ExpectToken ( ZScriptTokenType . String ) ;
if ( token = = null | | ! token . IsValid )
{
parser . ReportError ( "Expected version, got " + ( ( Object ) token ? ? "<null>" ) . ToString ( ) ) ;
return null ;
}
string version = token . Value . Trim ( ) ;
tokenizer . SkipWhitespace ( ) ;
token = tokenizer . ExpectToken ( ZScriptTokenType . CloseParen ) ;
if ( token = = null | | ! token . IsValid )
{
parser . ReportError ( "Expected ), got " + ( ( Object ) token ? ? "<null>" ) . ToString ( ) ) ;
return null ;
}
return version ;
}
2017-01-17 09:40:58 +00:00
internal ZScriptActorStructure ( ZDTextParser zdparser , DecorateCategoryInfo catinfo , string _classname , string _replacesname , string _parentname )
2017-01-16 11:18:46 +00:00
{
2017-01-17 09:40:58 +00:00
this . catinfo = catinfo ; //mxd
2017-01-17 04:41:33 +00:00
parser = ( ZScriptParser ) zdparser ;
stream = parser . DataStream ;
tokenizer = new ZScriptTokenizer ( parser . DataReader ) ;
2017-01-16 11:18:46 +00:00
parser . tokenizer = tokenizer ;
classname = _classname ;
replaceclass = _replacesname ;
2019-12-08 00:14:21 +00:00
//baseclass = parser.GetArchivedActorByName(_parentname); // this is not guaranteed to work here
mixins = new List < string > ( ) ;
2017-01-16 11:18:46 +00:00
ZScriptToken cls_open = tokenizer . ExpectToken ( ZScriptTokenType . OpenCurly ) ;
if ( cls_open = = null | | ! cls_open . IsValid )
{
parser . ReportError ( "Expected {, got " + ( ( Object ) cls_open ? ? "<null>" ) . ToString ( ) ) ;
return ;
}
2018-07-24 07:27:29 +00:00
// this dict holds temporary user settings per field (function, etc)
Dictionary < string , List < string > > var_props = new Dictionary < string , List < string > > ( ) ;
2017-01-16 11:18:46 +00:00
// 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 )
{
2018-07-24 07:27:29 +00:00
var_props . Clear ( ) ;
while ( true )
{
ZScriptToken tt = tokenizer . ExpectToken ( ZScriptTokenType . Whitespace , ZScriptTokenType . BlockComment , ZScriptTokenType . LineComment , ZScriptTokenType . Newline ) ;
if ( tt = = null | | ! tt . IsValid )
break ;
if ( tt . Type = = ZScriptTokenType . LineComment )
ParseGZDBComment ( var_props , tt . Value ) ;
}
//tokenizer.SkipWhitespace();
2017-01-16 11:18:46 +00:00
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" :
2017-01-17 04:41:33 +00:00
if ( ! ParseDefaultBlock ( ) )
2017-01-16 11:18:46 +00:00
return ;
continue ;
case "states" :
2017-01-17 04:41:33 +00:00
if ( ! ParseStatesBlock ( ) )
2017-01-16 11:18:46 +00:00
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" :
2019-12-08 00:14:21 +00:00
if ( ! parser . ParseClassOrStruct ( true , false , false , null ) )
2017-01-16 11:18:46 +00:00
return ;
continue ;
2017-01-17 04:41:33 +00:00
// new properties syntax
case "property" :
if ( ! ParseProperty ( ) )
return ;
continue ;
2018-12-02 15:51:54 +00:00
// new flags syntax
case "flagdef" :
if ( ! ParseFlagdef ( ) )
return ;
continue ;
2019-12-08 00:14:21 +00:00
// mixins
case "mixin" :
if ( ! ParseMixin ( ) )
return ;
continue ;
2017-01-16 11:18:46 +00:00
default :
stream . Position = ocpos ;
break ;
}
// try to read in a variable/method.
bool bmethod = false ;
2017-12-25 00:45:21 +00:00
string [ ] availablemodifiers = new string [ ] { "static" , "native" , "action" , "readonly" , "protected" , "private" , "virtual" , "override" , "meta" , "transient" , "deprecated" , "final" , "play" , "ui" , "clearscope" , "virtualscope" , "version" , "const" } ;
2017-03-06 04:07:19 +00:00
string [ ] versionedmodifiers = new string [ ] { "version" , "deprecated" } ;
2017-01-16 11:18:46 +00:00
string [ ] methodmodifiers = new string [ ] { "action" , "virtual" , "override" , "final" } ;
HashSet < string > modifiers = new HashSet < string > ( ) ;
List < string > types = new List < string > ( ) ;
2017-02-11 15:45:41 +00:00
List < List < int > > typearraylens = new List < List < int > > ( ) ;
2017-01-16 11:18:46 +00:00
List < string > names = new List < string > ( ) ;
2017-02-11 15:45:41 +00:00
List < List < int > > arraylens = new List < List < int > > ( ) ;
2017-01-16 11:18:46 +00:00
List < ZScriptToken > args = null ; // this is for the future
2017-01-17 05:13:28 +00:00
//List<ZScriptToken> body = null;
2017-01-16 11:18:46 +00:00
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 ;
2017-03-06 04:07:19 +00:00
if ( versionedmodifiers . Contains ( b_lower ) )
{
string version = ParseVersion ( b_lower = = "version" ) ; // deprecated doesn't require version string for historical reasons. (compatibility with old gzdoom.pk3)
if ( version = = null & & b_lower = = "version" )
return ;
}
2017-01-16 11:18:46 +00:00
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 ( ) ;
2017-01-17 04:41:33 +00:00
string typename = ParseTypeName ( ) ;
2017-01-16 11:18:46 +00:00
if ( typename = = null )
return ;
2017-02-11 15:45:41 +00:00
2017-01-16 11:18:46 +00:00
types . Add ( typename . ToLowerInvariant ( ) ) ;
2017-02-11 15:45:41 +00:00
typearraylens . Add ( null ) ;
2017-01-16 11:18:46 +00:00
long cpos = stream . Position ;
tokenizer . SkipWhitespace ( ) ;
2017-02-11 15:45:41 +00:00
token = tokenizer . ExpectToken ( ZScriptTokenType . Comma , ZScriptTokenType . Identifier , ZScriptTokenType . OpenSquare ) ;
if ( token ! = null & & ! token . IsValid )
{
parser . ReportError ( "Expected comma, identifier or array dimensions, got " + ( ( Object ) token ? ? "<null>" ) . ToString ( ) ) ;
return ;
}
2017-01-16 11:18:46 +00:00
if ( token = = null | | token . Type ! = ZScriptTokenType . Comma )
{
stream . Position = cpos ;
2017-02-11 15:45:41 +00:00
if ( token . Type = = ZScriptTokenType . OpenSquare )
{
List < int > typelens = ParseArrayDimensions ( ) ;
if ( typelens = = null ) // error
return ;
typearraylens [ typearraylens . Count - 1 ] = typelens ;
}
2017-01-16 11:18:46 +00:00
break ;
}
}
while ( true )
{
string name = null ;
2017-02-11 15:45:41 +00:00
List < int > lens = null ;
2017-01-16 11:18:46 +00:00
// 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 ( ) ;
2017-02-11 15:45:41 +00:00
long cpos = stream . Position ;
2017-01-16 11:18:46 +00:00
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
2017-02-11 15:45:41 +00:00
if ( names . Count > 0 )
2017-01-16 11:18:46 +00:00
{
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 ( ) ;
2017-02-11 15:45:41 +00:00
cpos = stream . Position ;
2017-03-06 04:07:19 +00:00
token = tokenizer . ExpectToken ( ZScriptTokenType . Semicolon , ZScriptTokenType . OpenCurly , ZScriptTokenType . Identifier ) ;
2017-01-16 11:18:46 +00:00
if ( token = = null | | ! token . IsValid )
{
2017-03-06 04:07:19 +00:00
parser . ReportError ( "Expected 'const', ; or {, got " + ( ( Object ) token ? ? "<null>" ) . ToString ( ) ) ;
2017-01-16 11:18:46 +00:00
return ;
}
//
2017-03-06 04:07:19 +00:00
if ( token . Type = = ZScriptTokenType . Identifier )
{
if ( token . Value . ToLowerInvariant ( ) ! = "const" )
{
parser . ReportError ( "Expected 'const', got " + ( ( Object ) token ? ? "<null>" ) . ToString ( ) ) ;
return ;
}
tokenizer . SkipWhitespace ( ) ;
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 ;
}
}
2017-01-16 11:18:46 +00:00
if ( token . Type = = ZScriptTokenType . OpenCurly )
{
stream . Position = cpos ;
2017-01-17 02:51:12 +00:00
parser . SkipBlock ( ) ;
//body = parser.ParseBlock(false);
2017-01-16 11:18:46 +00:00
}
2017-02-11 15:45:41 +00:00
break ; // end method parsing
2017-01-16 11:18:46 +00:00
}
else
{
if ( bmethod )
{
parser . ReportError ( "Cannot have virtual, override or action fields" ) ;
return ;
}
// array
if ( token . Type = = ZScriptTokenType . OpenSquare )
{
2017-02-11 15:45:41 +00:00
stream . Position = cpos ;
lens = ParseArrayDimensions ( ) ;
if ( lens = = null ) // error
2017-01-16 11:18:46 +00:00
return ;
tokenizer . SkipWhitespace ( ) ;
2017-12-25 00:45:21 +00:00
ZScriptTokenType [ ] expectTokens ;
if ( modifiers . Contains ( "static" ) )
expectTokens = new ZScriptTokenType [ ] { ZScriptTokenType . Semicolon , ZScriptTokenType . Comma , ZScriptTokenType . OpAssign } ;
else expectTokens = new ZScriptTokenType [ ] { ZScriptTokenType . Semicolon , ZScriptTokenType . Comma } ;
token = tokenizer . ExpectToken ( expectTokens ) ;
2017-01-16 11:18:46 +00:00
if ( token = = null | | ! token . IsValid )
{
2017-12-25 00:45:21 +00:00
parser . ReportError ( "Expected ;, =, or comma, got " + ( ( Object ) token ? ? "<null>" ) . ToString ( ) ) ;
2017-01-16 11:18:46 +00:00
return ;
}
2017-12-25 00:45:21 +00:00
// "static int A[] = {1, 2, 3};"
if ( token . Type = = ZScriptTokenType . OpAssign )
{
// read in array data
tokenizer . SkipWhitespace ( ) ;
parser . SkipBlock ( false ) ;
tokenizer . SkipWhitespace ( ) ;
token = tokenizer . ExpectToken ( ZScriptTokenType . Semicolon , ZScriptTokenType . Comma ) ;
if ( token = = null | | ! token . IsValid )
{
parser . ReportError ( "Expected ; or comma, got " + ( ( Object ) token ? ? "<null>" ) . ToString ( ) ) ;
return ;
}
}
2017-01-16 11:18:46 +00:00
}
}
names . Add ( name ) ;
2017-02-11 15:45:41 +00:00
arraylens . Add ( lens ) ;
2017-01-16 11:18:46 +00:00
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 ) + ")" ;
2017-02-08 21:55:38 +00:00
else if ( arraylen ! = - 1 ) _args = " [" + arraylen . ToString ( ) + "]" ;
2017-01-16 11:18:46 +00:00
parser . LogWarning ( string . Format ( "{0} {1} {2}{3}" , string . Join ( " " , modifiers . ToArray ( ) ) , string . Join ( ", " , types . ToArray ( ) ) , name , _args ) ) ;
} * /
2017-02-08 21:55:38 +00:00
// update 08.02.17: add user variables from ZScript actors.
if ( args = = null & & types . Count = = 1 ) // it's a field
{
// we support:
// - float
// - int
// - double
// - bool
string type = types [ 0 ] ;
UniversalType utype ;
2018-07-24 07:27:29 +00:00
object udefault = null ;
2017-02-08 21:55:38 +00:00
switch ( type )
{
case "int" :
utype = UniversalType . Integer ;
break ;
2018-07-24 07:27:29 +00:00
case "float" :
2017-02-08 21:55:38 +00:00
case "double" :
utype = UniversalType . Float ;
break ;
case "bool" :
utype = UniversalType . Integer ;
break ;
case "string" :
utype = UniversalType . String ;
break ;
2018-07-24 07:27:29 +00:00
// todo test if class names and colors will work
2017-02-08 21:55:38 +00:00
default :
continue ; // go read next field
}
2018-07-24 08:35:52 +00:00
UniversalType utype_reinterpret = utype ;
if ( var_props . ContainsKey ( "$userreinterpret" ) )
{
string sp = var_props [ "$userreinterpret" ] [ 0 ] . Trim ( ) . ToLowerInvariant ( ) ;
switch ( sp )
{
case "color" :
if ( utype ! = UniversalType . Integer )
{
parser . LogWarning ( "Cannot use $UserReinterpret Color with non-integers" ) ;
break ;
}
utype_reinterpret = UniversalType . Color ;
break ;
}
}
2018-07-24 07:27:29 +00:00
if ( var_props . ContainsKey ( "$userdefaultvalue" ) )
{
string sp = var_props [ "$userdefaultvalue" ] [ 0 ] ;
switch ( utype )
{
case UniversalType . String :
if ( sp [ 0 ] = = '"' & & sp [ sp . Length - 1 ] = = '"' )
sp = sp . Substring ( 1 , sp . Length - 2 ) ;
udefault = sp ;
break ;
case UniversalType . Float :
float d ;
if ( ! float . TryParse ( sp , out d ) )
{
parser . LogWarning ( "Incorrect float default from string \"" + sp + "\"" ) ;
break ;
}
udefault = d ;
break ;
case UniversalType . Integer :
int i ;
if ( ! int . TryParse ( sp , out i ) )
{
2018-07-24 08:35:52 +00:00
if ( utype_reinterpret = = UniversalType . Color )
{
sp = sp . ToLowerInvariant ( ) ;
Rendering . PixelColor pc ;
if ( ! ZDTextParser . GetColorFromString ( sp , out pc ) )
{
parser . LogWarning ( "Incorrect color default from string \"" + sp + "\"" ) ;
break ;
}
udefault = pc . ToInt ( ) & 0xFFFFFF ;
break ;
}
2018-07-24 07:27:29 +00:00
if ( type = = "bool" )
{
sp = sp . ToLowerInvariant ( ) ;
if ( sp = = "true" )
udefault = true ;
else if ( sp = = "false" )
udefault = false ;
else parser . LogWarning ( "Incorrect boolean default from string \"" + sp + "\"" ) ;
break ;
}
parser . LogWarning ( "Incorrect integer default from string \"" + sp + "\"" ) ;
break ;
}
udefault = i ;
break ;
}
}
2017-02-08 21:55:38 +00:00
for ( int i = 0 ; i < names . Count ; i + + )
{
string name = names [ i ] ;
2017-02-11 15:45:41 +00:00
if ( arraylens [ i ] ! = null | | typearraylens [ 0 ] ! = null )
2017-02-08 21:55:38 +00:00
continue ; // we don't process arrays
if ( ! name . StartsWith ( "user_" ) )
continue ; // we don't process non-user_ fields (because ZScript won't pick them up anyway)
// parent class is not guaranteed to be loaded already, so handle collisions later
2018-07-24 08:35:52 +00:00
uservars . Add ( name , utype_reinterpret ) ;
2018-07-24 07:27:29 +00:00
if ( udefault ! = null )
uservar_defaults . Add ( name , udefault ) ;
2017-02-08 21:55:38 +00:00
}
}
2017-01-16 11:18:46 +00:00
}
2017-03-01 22:21:08 +00:00
// parsing done, process thing arguments
ParseCustomArguments ( ) ;
2017-01-16 11:18:46 +00:00
}
}
}