/* A simple interpreter class, by Ludovic Brenta
 * First released with YACL 1.7 Beta - May 1997.
 *
 * See <contrib/brenta/readme.txt> for info on how to contact me.
 */


#ifndef Interpreter_H
#define Interpreter_H

#include <fstream.h>

#include <base/string.h>

#include "Exception.h"

/*
 * The CL_Interpreter class encapsulates a number of low-level methods to parse
 * a source file.  Its sole purpose is to make it easier to parse a source
 * file.
 *
 * The methods recognise comments in the source file.  Both end-of-line com-
 * ments and delimited comments are supported.	The delimiters are specified at
 * run-time via the EolComment(), CommentBegin() and CommentEnd() methods.  By
 * default, only end-of-line comments beginning with a pound (#) sign are
 * supported.
 *
 * Since a CL_Interpreter acts more or less like an "iterator" over a file,
 * it should be noted that most methods have side effects on the position
 * of the file pointer.  Therefore a CL_Interpreter should be construed as a
 * NON-REENTRANT class.  However, you may modify the position of the file
 * pointer between (but not during) calls to CL_Interpreter methods, provided
 * that you know pretty well what you're doing :)
 *
 * This class is derived from C code I wrote some years ago, and I used it
 * in my Menu Editor application.  Now I consider this class to be solid,
 * battle-tested code and I'm pretty confident you can use it reliably.  If,
 * however, you do find bugs, please let me know.
 */

class CL_InterpreterException : public CL_Exception  // the class of exceptions thrown by CL_Interpreter
{
public:
	CL_InterpreterException(const char *, const char *, const char *, long = 0, const char * = 0);
	virtual const char *ClassName() const { return "CL_InterpreterException"; }
};

inline CL_InterpreterException::CL_InterpreterException(const char *a, const char *b,
	const char *c, long d, const char *e)
: CL_Exception(a, b, c, d, e)
{
}

class CL_Interpreter
{
public: // Construction
	CL_Interpreter(ifstream& stream, bool caseSensitive = FALSE);
	// Constructs an Interpreter object bound to the given file.  The file
	// will neither be opened nor closed by any Interpreter method.  It is up
	// to the caller method to ensure the file is opened as long as an Inter-
	// preter is active on it.
	// Whether the ReadKeyWord() method is case sensitive is determined by
	// the "caseSensitive" parameter.

public: // Constants
	enum Base { RN_BIN = 2, RN_OCT = 8, RN_DEC = 10, RN_HEX = 16 };
	// for ReadNumber()

public:
	CL_String& EolComment()       { return _eolComment; }
	CL_String& CommentBegin()		{ return _commentBegin; }
	CL_String& CommentEnd()       { return _commentEnd; }
	// These three methods allow to customize the comment delimiters.  All the
	// Interpreter methods recognize both end-of-line and delimited comments.
	// If EolComment() is the empty string, end-of-line comments are not suppor-
	// ted.	If either Comment...() method returns the empty string, delimited
	// comments are not supported.
	//
	// Examples of comment delimiters are:
	// #				in Makefiles, Unix shell scripts, X-Window resource files
	// ;				in Assembler, Windows or OS/2 definition files, Windows INI
	//                files
	// //          in C++, Windows and OS/2 resource files
	// /* */       in C, Windows and OS/2 resource files
	// (* *)       in Pascal
	// {	 }       in Pascal
	// --          in Ada
	// REM			in DOS or OS/2 batch files or CONFIG.SYS files
	//
	// WARNING! The Interpreter object is notified whenever one of these strings
	// is modified; it may then raise a CL_InterpreterException if EolComment
	// is the same as CommentBegin.
	// Therefore you should enclose any assignments to these strings in a try
	// / catch block.

	bool& CaseSensitive()		{ return _caseSensitive; }

public:
	bool ReadKeyWord(const CL_String& keyWord)
		throw(CL_InterpreterException);
	// Reads a keyword in the file.	Returns true if the keyword is identical
	// to the given keyword, or false otherwise.
	// If the word read from the file is not "keyWord", this method returns to
	// its original position in the file, so that a subsequent call to Read-
	// Word() is unaffected.

	CL_String ReadStringBetweenQuotes()
		throw(CL_InterpreterException);
	// Reads and returns a character string.	A character string is any
	// sequence of characters appearing between doublequote (") characters
	// in the input file.

	unsigned long ReadNumber(CL_Interpreter::Base base)
		throw(CL_InterpreterException);
	// Reads a number from the file.  The number is coded as an ASCII string
	// in the file, in the base given as "base".  For now, only the bases 10
	// (RN_DEC) and 16 (RN_HEX) are supported, but provision is made for bases
	// 2 (RN_BIN) and 8 (RN_OCT).  The reading of the number stops when any
	// non-digit character is encountered (that is, any character not in
	// [0-9A-Fa-f]).
	//
	// Limitations:
	// - since the returned number is an unsigned long integer, numbers may
	//   not have more than 32 ASCII digits (that is for base 2).
	// - in bases other than 2, 32-digit numbers may yield numbers too large to
	//   fit in an unsigned long integer.	In this case, an exception is thrown.
	// - negative numbers are not supported.
	// - floating-points numbers are not supported either.  In both cases, an
	//   exception (unrecognized character) is thrown.

	CL_String ReadWord()
		throw(CL_InterpreterException);
	// Reads a word in the input file.	A word is a sequence of non-blank
	// characters delimited by blank characters or punctuations.
	// If the first character is a punctuation, then the read word consists
	// solely of a sequence of punctuations.	Otherwise, the word consists
	// solely of a sequence of non-blank, non-punctuation characters.  For
	// example, consider the following input file:
	//
	// identifier ::= sequence(char);
	//
	// Successive calls to ReadWord will yield:
	// 1. "identifier"
	// 2. "::="
	// 3. "sequence"
	// 4. "("
	// 5. "char"
	// 6. ");"
	//
	// See NextPunctuation() for a list of supported punctuation characters.

	char NextPunctuation(char = ' ')
		throw(CL_InterpreterException);
	// Call this method at a point where you expect a punctuation to occur
	// in your input file.	It will skip any blank characters and return when
	// it encounters the first non-blank character.
	//
	// Punctuation characters are : ,;:.(){}[]!?$+-*/=%|~
	//
	// Return value:
	// - if the parameter has its default value (' '), the method returns the
	//   punctuation character, or 0 if another non-blank character is encoun-
	//   tered first.
	// - if the parameter is a punctuation, the method returns the given
	//   punctuation if it is found, and 0 otherwise.
	//
	// Thus, passing this method a character other than one of the above will
	// always cause it to return zero.
	//
	// Side effect:
	// if the method returns 0, it goes back in the input file so that a sub-
	// sequent call to e.g. ReadWord() is unaffected.

protected:
	void _Check(const char *methodName) const
		throw(CL_InterpreterException);
	// Performs various checks and throws an exception if this Interpreter has
	// a bad state.  The parameter specifies the name of the method that calls
	// _Check(); this name will be given to the CL_Exception ctor to provide
	// info about the conditions under which the exception was raised.

	bool _CommentDelimitersChanged()
		throw(CL_InterpreterException);
	// Updates some internal flags that speed up processing.

	int _NextChar() const
		throw(CL_InterpreterException);
	// Returns the next character in the stream, ignoring comments.  This is
	// essentially a filter.
	// Warning! Carriage Return ('\r' or 0xD) and Form Feed (0xC) characters
	// will be returned as ' '.  Therefore you'll never get them unless they
	// appear in a string (see ReadStringBetweenQuotes()).  This is fortunate,
	// in fact, since these characters just get in the way if you're reading
	// a text file (which I hope you're doing, otherwise don't use this class).

protected: // Data
	ifstream*	_stream;
	bool			_caseSensitive;
	CL_String	_commentBegin, _commentEnd;	// Comment delimiters
	CL_String	_eolComment;						// end-of-line comment delimiter
	bool			_delimCommentsSupported;		// there are comment delimiters
	bool			_eolCommentsSupported;			// there is an eol comment delim
};

#endif /* Interpreter_H */

