2009-04-19 18:07:22 +00:00
#region = = = = = = = = = = = = = = = = = = Copyright ( c ) 2007 Pascal vd Heiden
/ *
* Copyright ( c ) 2007 Pascal vd Heiden , www . codeimp . com
* This program is released under GNU General Public License
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* /
#endregion
#region = = = = = = = = = = = = = = = = = = Namespaces
using System.Globalization ;
using System.Text ;
using System.IO ;
#endregion
namespace CodeImp.DoomBuilder.ZDoom
{
public abstract class ZDTextParser
{
#region = = = = = = = = = = = = = = = = = = Constants
#endregion
#region = = = = = = = = = = = = = = = = = = Variables
// Parsing
2013-11-18 12:36:42 +00:00
protected string whitespace = "\n \t\r\u00A0\0" ; //mxd. non-breaking space is also space :)
2009-04-19 18:07:22 +00:00
protected string specialtokens = ":{}+-\n;" ;
// Input data stream
protected Stream datastream ;
protected BinaryReader datareader ;
protected string sourcename ;
// Error report
private int errorline ;
private string errordesc ;
private string errorsource ;
#endregion
#region = = = = = = = = = = = = = = = = = = Properties
internal Stream DataStream { get { return datastream ; } }
internal BinaryReader DataReader { get { return datareader ; } }
public int ErrorLine { get { return errorline ; } }
public string ErrorDescription { get { return errordesc ; } }
public string ErrorSource { get { return errorsource ; } }
public bool HasError { get { return ( errordesc ! = null ) ; } }
#endregion
#region = = = = = = = = = = = = = = = = = = Constructor / Disposer
// Constructor
protected ZDTextParser ( )
{
// Initialize
errordesc = null ;
}
#endregion
#region = = = = = = = = = = = = = = = = = = Parsing
// This parses the given decorate stream
// Returns false on errors
public virtual bool Parse ( Stream stream , string sourcefilename )
{
2014-10-23 12:48:31 +00:00
return Parse ( stream , sourcefilename , false ) ;
}
// This parses the given decorate stream (mxd)
// Returns false on errors
public virtual bool Parse ( Stream stream , string sourcefilename , bool clearerrors )
{
// Clear error status (mxd)
if ( clearerrors )
{
errordesc = null ;
errorsource = null ;
errorline = - 1 ;
}
2009-04-19 18:07:22 +00:00
datastream = stream ;
datareader = new BinaryReader ( stream , Encoding . ASCII ) ;
sourcename = sourcefilename ;
datastream . Seek ( 0 , SeekOrigin . Begin ) ;
return true ;
}
// This returns true if the given character is whitespace
2015-02-04 18:02:03 +00:00
private bool IsWhitespace ( char c )
2009-04-19 18:07:22 +00:00
{
return ( whitespace . IndexOf ( c ) > - 1 ) ;
}
// This returns true if the given character is a special token
2015-02-04 18:02:03 +00:00
private bool IsSpecialToken ( char c )
2009-04-19 18:07:22 +00:00
{
return ( specialtokens . IndexOf ( c ) > - 1 ) ;
}
// This returns true if the given character is a special token
2009-08-15 08:41:43 +00:00
protected internal bool IsSpecialToken ( string s )
2009-04-19 18:07:22 +00:00
{
2014-01-03 10:33:45 +00:00
if ( s . Length > 0 ) return ( specialtokens . IndexOf ( s [ 0 ] ) > - 1 ) ;
return false ;
2009-04-19 18:07:22 +00:00
}
2015-02-04 18:02:03 +00:00
//mxd. This removes beginning and ending quotes from a token
protected internal string StripTokenQuotes ( string token )
{
return StripQuotes ( token ) ;
}
2009-04-19 18:07:22 +00:00
// This removes beginning and ending quotes from a token
2015-02-04 18:02:03 +00:00
internal static string StripQuotes ( string token )
2009-04-19 18:07:22 +00:00
{
// Remove first character, if it is a quote
if ( ! string . IsNullOrEmpty ( token ) & & ( token [ 0 ] = = '"' ) )
token = token . Substring ( 1 ) ;
// Remove last character, if it is a quote
if ( ! string . IsNullOrEmpty ( token ) & & ( token [ token . Length - 1 ] = = '"' ) )
token = token . Substring ( 0 , token . Length - 1 ) ;
return token ;
}
// This skips whitespace on the stream, placing the read
// position right before the first non-whitespace character
// Returns false when the end of the stream is reached
2009-08-15 08:41:43 +00:00
protected internal bool SkipWhitespace ( bool skipnewline )
2009-04-19 18:07:22 +00:00
{
int offset = skipnewline ? 0 : 1 ;
char c ;
do
{
if ( datastream . Position = = datastream . Length ) return false ;
c = ( char ) datareader . ReadByte ( ) ;
// Check if this is comment
if ( c = = '/' )
{
if ( datastream . Position = = datastream . Length ) return false ;
char c2 = ( char ) datareader . ReadByte ( ) ;
if ( c2 = = '/' )
{
// Check if not a special comment with a token
if ( datastream . Position = = datastream . Length ) return false ;
char c3 = ( char ) datareader . ReadByte ( ) ;
if ( c3 ! = '$' )
{
// Skip entire line
char c4 = ' ' ;
while ( ( c4 ! = '\n' ) & & ( datastream . Position < datastream . Length ) ) { c4 = ( char ) datareader . ReadByte ( ) ; }
2010-08-01 20:08:29 +00:00
c = c4 ;
2009-04-19 18:07:22 +00:00
}
else
{
// Not a comment
c = c3 ;
}
}
else if ( c2 = = '*' )
{
// Skip until */
char c4 , c3 = '\0' ;
do
{
c4 = c3 ;
c3 = ( char ) datareader . ReadByte ( ) ;
}
while ( ( c4 ! = '*' ) | | ( c3 ! = '/' ) ) ;
c = ' ' ;
}
else
{
// Not a comment, rewind from reading c2
datastream . Seek ( - 1 , SeekOrigin . Current ) ;
}
}
}
while ( whitespace . IndexOf ( c , offset ) > - 1 ) ;
// Go one character back so we can read this non-whitespace character again
datastream . Seek ( - 1 , SeekOrigin . Current ) ;
return true ;
}
// This reads a token (all sequential non-whitespace characters or a single character)
// Returns null when the end of the stream has been reached
2009-08-15 08:41:43 +00:00
protected internal string ReadToken ( )
2009-04-19 18:07:22 +00:00
{
// Return null when the end of the stream has been reached
if ( datastream . Position = = datastream . Length ) return null ;
2014-01-03 10:33:45 +00:00
string token = "" ;
bool quotedstring = false ;
2009-04-19 18:07:22 +00:00
// Start reading
char c = ( char ) datareader . ReadByte ( ) ;
while ( ! IsWhitespace ( c ) | | quotedstring | | IsSpecialToken ( c ) )
{
// Special token?
if ( ! quotedstring & & IsSpecialToken ( c ) )
{
// Not reading a token yet?
if ( token . Length = = 0 )
{
// This is our whole token
token + = c ;
break ;
}
else
{
// This is a new token and shouldn't be read now
// Go one character back so we can read this token again
datastream . Seek ( - 1 , SeekOrigin . Current ) ;
break ;
}
}
else
{
// Quote?
if ( c = = '"' )
{
// Quote to end the string?
if ( quotedstring ) quotedstring = false ;
// First character is a quote?
if ( token . Length = = 0 ) quotedstring = true ;
token + = c ;
}
// Potential comment?
2010-08-17 10:02:07 +00:00
else if ( ( c = = '/' ) & & ! quotedstring )
2009-04-19 18:07:22 +00:00
{
// Check the next byte
if ( datastream . Position = = datastream . Length ) return token ;
char c2 = ( char ) datareader . ReadByte ( ) ;
if ( ( c2 = = '/' ) | | ( c2 = = '*' ) )
{
// This is a comment start, so the token ends here
// Go two characters back so we can read this comment again
datastream . Seek ( - 2 , SeekOrigin . Current ) ;
break ;
}
else
{
// Not a comment
// Go one character back so we can read this char again
datastream . Seek ( - 1 , SeekOrigin . Current ) ;
token + = c ;
}
}
else
{
token + = c ;
}
}
// Next character
if ( datastream . Position < datastream . Length )
c = ( char ) datareader . Read ( ) ;
else
break ;
}
return token ;
}
2014-01-03 10:33:45 +00:00
// This reads a token (all sequential non-whitespace characters or a single character) using custom set of special tokens
// Returns null when the end of the stream has been reached (mxd)
2014-12-03 23:15:26 +00:00
protected internal string ReadToken ( string specialTokens )
{
2014-01-03 10:33:45 +00:00
// Return null when the end of the stream has been reached
if ( datastream . Position = = datastream . Length ) return null ;
string token = "" ;
bool quotedstring = false ;
// Start reading
char c = ( char ) datareader . ReadByte ( ) ;
2014-12-03 23:15:26 +00:00
while ( ! IsWhitespace ( c ) | | quotedstring | | specialTokens . IndexOf ( c ) ! = - 1 )
{
2014-01-03 10:33:45 +00:00
// Special token?
2014-12-03 23:15:26 +00:00
if ( ! quotedstring & & specialTokens . IndexOf ( c ) ! = - 1 )
{
2014-01-03 10:33:45 +00:00
// Not reading a token yet?
2014-12-03 23:15:26 +00:00
if ( token . Length = = 0 )
{
2014-01-03 10:33:45 +00:00
// This is our whole token
token + = c ;
break ;
}
// This is a new token and shouldn't be read now
// Go one character back so we can read this token again
datastream . Seek ( - 1 , SeekOrigin . Current ) ;
break ;
2014-12-03 23:15:26 +00:00
}
else
{
2014-01-03 10:33:45 +00:00
// Quote?
2014-12-03 23:15:26 +00:00
if ( c = = '"' )
{
2014-01-03 10:33:45 +00:00
// Quote to end the string?
if ( quotedstring ) quotedstring = false ;
// First character is a quote?
if ( token . Length = = 0 ) quotedstring = true ;
token + = c ;
}
2014-12-03 23:15:26 +00:00
// Potential comment?
else if ( ( c = = '/' ) & & ! quotedstring )
{
2014-01-03 10:33:45 +00:00
// Check the next byte
if ( datastream . Position = = datastream . Length ) return token ;
char c2 = ( char ) datareader . ReadByte ( ) ;
2014-12-03 23:15:26 +00:00
if ( ( c2 = = '/' ) | | ( c2 = = '*' ) )
{
2014-01-03 10:33:45 +00:00
// This is a comment start, so the token ends here
// Go two characters back so we can read this comment again
datastream . Seek ( - 2 , SeekOrigin . Current ) ;
break ;
2014-12-03 23:15:26 +00:00
}
else
{
2014-01-03 10:33:45 +00:00
// Not a comment
// Go one character back so we can read this char again
datastream . Seek ( - 1 , SeekOrigin . Current ) ;
token + = c ;
}
2014-12-03 23:15:26 +00:00
}
else
{
2014-01-03 10:33:45 +00:00
token + = c ;
}
}
// Next character
if ( datastream . Position < datastream . Length )
c = ( char ) datareader . Read ( ) ;
else
break ;
}
return token ;
}
2009-04-19 18:07:22 +00:00
// This reads the rest of the line
// Returns null when the end of the stream has been reached
2009-08-15 08:41:43 +00:00
protected internal string ReadLine ( )
2009-04-19 18:07:22 +00:00
{
string token = "" ;
// Return null when the end of the stream has been reached
if ( datastream . Position = = datastream . Length ) return null ;
// Start reading
char c = ( char ) datareader . ReadByte ( ) ;
while ( c ! = '\n' )
{
token + = c ;
// Next character
if ( datastream . Position < datastream . Length )
c = ( char ) datareader . Read ( ) ;
else
break ;
}
return token . Trim ( ) ;
}
2012-06-01 10:17:47 +00:00
2015-04-13 09:50:17 +00:00
//mxd
protected bool NextTokenIs ( string expectedtoken )
{
return NextTokenIs ( expectedtoken , true ) ;
}
//mxd
protected bool NextTokenIs ( string expectedtoken , bool reporterror )
{
2015-04-29 08:36:10 +00:00
if ( ! SkipWhitespace ( true ) ) return false ;
2015-04-13 09:50:17 +00:00
string token = ReadToken ( ) ;
if ( token ! = expectedtoken )
{
if ( reporterror ) General . ErrorLogger . Add ( ErrorType . Error , "Error in '" + sourcename + "' at line " + GetCurrentLineNumber ( ) + ": expected '" + expectedtoken + "', but got '" + token + "'" ) ;
// Rewind so this structure can be read again
DataStream . Seek ( - token . Length - 1 , SeekOrigin . Current ) ;
return false ;
}
return true ;
}
2013-09-11 09:47:53 +00:00
//mxd
2014-12-03 23:15:26 +00:00
protected internal bool ReadSignedFloat ( string token , ref float value )
{
2013-09-11 09:47:53 +00:00
int sign = 1 ;
2014-12-03 23:15:26 +00:00
if ( token = = "-" )
{
2013-09-11 09:47:53 +00:00
sign = - 1 ;
token = StripTokenQuotes ( ReadToken ( ) ) ;
}
2012-06-01 10:17:47 +00:00
2013-09-11 09:47:53 +00:00
float val ;
bool success = float . TryParse ( token , NumberStyles . Float , CultureInfo . InvariantCulture , out val ) ;
2013-12-18 09:11:04 +00:00
if ( success ) value = val * sign ;
2013-09-11 09:47:53 +00:00
return success ;
}
2012-06-01 10:17:47 +00:00
2013-09-11 09:47:53 +00:00
//mxd
2015-04-13 09:50:17 +00:00
protected internal bool ReadSignedInt ( string token , ref int value )
2014-12-03 23:15:26 +00:00
{
2013-09-11 09:47:53 +00:00
int sign = 1 ;
2014-12-03 23:15:26 +00:00
if ( token = = "-" )
{
2013-09-11 09:47:53 +00:00
sign = - 1 ;
token = StripTokenQuotes ( ReadToken ( ) ) ;
}
2012-06-01 10:17:47 +00:00
2013-09-11 09:47:53 +00:00
int val ;
bool success = int . TryParse ( token , NumberStyles . Integer , CultureInfo . InvariantCulture , out val ) ;
2013-12-18 09:11:04 +00:00
if ( success ) value = val * sign ;
2013-09-11 09:47:53 +00:00
return success ;
}
2009-04-19 18:07:22 +00:00
// This reports an error
2009-08-15 08:41:43 +00:00
protected internal void ReportError ( string message )
2009-04-19 18:07:22 +00:00
{
// Set error information
errordesc = message ;
2013-09-11 09:47:53 +00:00
errorline = GetCurrentLineNumber ( ) ;
2009-04-19 18:07:22 +00:00
errorsource = sourcename ;
}
2012-05-21 23:51:32 +00:00
2013-09-11 09:47:53 +00:00
//mxd
2014-12-03 23:15:26 +00:00
protected internal int GetCurrentLineNumber ( )
{
2013-09-11 09:47:53 +00:00
long position = datastream . Position ;
long readpos = 0 ;
int linenumber = 1 ;
2012-05-21 23:51:32 +00:00
2013-09-11 09:47:53 +00:00
// Find the line on which we found this error
datastream . Seek ( 0 , SeekOrigin . Begin ) ;
StreamReader textreader = new StreamReader ( datastream , Encoding . ASCII ) ;
2014-12-03 23:15:26 +00:00
while ( readpos < position )
{
2013-09-11 09:47:53 +00:00
string line = textreader . ReadLine ( ) ;
if ( line = = null ) break ;
readpos + = line . Length + 2 ;
linenumber + + ;
}
2012-05-21 23:51:32 +00:00
2013-09-11 09:47:53 +00:00
// Return to original position
datastream . Seek ( position , SeekOrigin . Begin ) ;
2012-05-21 23:51:32 +00:00
2013-09-11 09:47:53 +00:00
return linenumber ;
}
2009-04-19 18:07:22 +00:00
#endregion
}
}