/*
 * Copyright (C) 2012-2013 Robin Haberkorn
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see .
 */
#ifndef __PARSER_H
#define __PARSER_H
#include 
#include 
#include "undo.h"
#include "sciteco.h"
/* TECO uses only lower 7 bits for commands */
#define MAX_TRANSITIONS	127
/* thrown as exception, executed at cmdline macro level */
class ReplaceCmdline {
public:
	gchar *new_cmdline;
	gint pos;
	ReplaceCmdline();
};
class State {
public:
	class Error {
	public:
		Error(const gchar *fmt, ...) G_GNUC_PRINTF(2, 3);
	};
	class SyntaxError : public Error {
	public:
		SyntaxError(gchar chr)
			   : Error("Syntax error \"%c\" (%d)", chr, chr) {}
	};
	class MoveError : public Error {
	public:
		MoveError(const gchar *cmd)
			 : Error("Attempt to move pointer off page with <%s>",
				 cmd) {}
		MoveError(gchar cmd)
			 : Error("Attempt to move pointer off page with <%c>",
				 cmd) {}
	};
	class RangeError : public Error {
	public:
		RangeError(const gchar *cmd)
			  : Error("Invalid range specified for <%s>", cmd) {}
		RangeError(gchar cmd)
			  : Error("Invalid range specified for <%c>", cmd) {}
	};
	class InvalidQRegError : public Error {
	public:
		InvalidQRegError(const gchar *name, bool local = false)
				: Error("Invalid Q-Register \"%s%s\"",
					local ? "." : "", name) {}
		InvalidQRegError(gchar name, bool local = false)
				: Error("Invalid Q-Register \"%s%c\"",
					local ? "." : "", name) {}
	};
protected:
	/* static transitions */
	State *transitions[MAX_TRANSITIONS];
	inline void
	init(const gchar *chars, State &state)
	{
		while (*chars)
			transitions[(int)*chars++] = &state;
	}
	inline void
	init(const gchar *chars)
	{
		init(chars, *this);
	}
public:
	State();
	static void input(gchar chr) throw (Error, ReplaceCmdline);
	State *get_next_state(gchar chr) throw (Error, ReplaceCmdline);
protected:
	static bool eval_colon(void);
	virtual State *
	custom(gchar chr) throw (Error, ReplaceCmdline)
	{
		throw SyntaxError(chr);
		return NULL;
	}
};
template 
class MicroStateMachine {
protected:
	/* label pointers */
	typedef const void *MicroState;
	const MicroState StateStart;
#define MICROSTATE_START G_STMT_START {	\
	if (this->state != StateStart)		\
		goto *this->state;		\
} G_STMT_END
	MicroState state;
	inline void
	set(MicroState next)
	{
		if (next != state)
			undo.push_var(state) = next;
	}
public:
	MicroStateMachine() : StateStart(NULL), state(StateStart) {}
	virtual ~MicroStateMachine() {}
	virtual inline void
	reset(void)
	{
		set(StateStart);
	}
	virtual Type input(gchar chr) throw (State::Error) = 0;
};
/* avoid circular dependency on qregisters.h */
class QRegSpecMachine;
class StringBuildingMachine : public MicroStateMachine {
	QRegSpecMachine *qregspec_machine;
	enum Mode {
		MODE_NORMAL,
		MODE_UPPER,
		MODE_LOWER
	} mode;
	bool toctl;
public:
	StringBuildingMachine() : MicroStateMachine(),
				  qregspec_machine(NULL),
				  mode(MODE_NORMAL), toctl(false) {}
	~StringBuildingMachine();
	void reset(void);
	gchar *input(gchar chr) throw (State::Error);
};
/*
 * Super-class for states accepting string arguments
 * Opaquely cares about alternative-escape characters,
 * string building commands and accumulation into a string
 */
class StateExpectString : public State {
	StringBuildingMachine machine;
	gint nesting;
	bool string_building;
	bool last;
public:
	StateExpectString(bool _building = true, bool _last = true)
			 : State(), nesting(1),
			   string_building(_building), last(_last) {}
private:
	State *custom(gchar chr) throw (Error);
protected:
	virtual void initial(void) throw (Error) {}
	virtual void process(const gchar *str, gint new_chars) throw (Error) {}
	virtual State *done(const gchar *str) throw (Error) = 0;
};
class StateExpectFile : public StateExpectString {
public:
	StateExpectFile(bool _building = true, bool _last = true)
		       : StateExpectString(_building, _last) {}
};
class StateStart : public State {
public:
	StateStart();
private:
	void insert_integer(tecoInt v);
	tecoInt read_integer(void);
	tecoBool move_chars(tecoInt n);
	tecoBool move_lines(tecoInt n);
	tecoBool delete_words(tecoInt n);
	State *custom(gchar chr) throw (Error, ReplaceCmdline);
};
class StateControl : public State {
public:
	StateControl();
private:
	State *custom(gchar chr) throw (Error);
};
class StateASCII : public State {
public:
	StateASCII();
private:
	State *custom(gchar chr) throw (Error);
};
class StateFCommand : public State {
public:
	StateFCommand();
private:
	State *custom(gchar chr) throw (Error);
};
class StateCondCommand : public State {
public:
	StateCondCommand();
private:
	State *custom(gchar chr) throw (Error);
};
class StateECommand : public State {
public:
	StateECommand();
private:
	State *custom(gchar chr) throw (Error);
};
class StateScintilla_symbols : public StateExpectString {
public:
	StateScintilla_symbols() : StateExpectString(true, false) {}
private:
	State *done(const gchar *str) throw (Error);
};
class StateScintilla_lParam : public StateExpectString {
private:
	State *done(const gchar *str) throw (Error);
};
/*
 * also serves as base class for replace-insertion states
 */
class StateInsert : public StateExpectString {
protected:
	void initial(void) throw (Error);
	void process(const gchar *str, gint new_chars) throw (Error);
	State *done(const gchar *str) throw (Error);
};
namespace States {
	extern StateStart 		start;
	extern StateControl		control;
	extern StateASCII		ascii;
	extern StateFCommand		fcommand;
	extern StateCondCommand		condcommand;
	extern StateECommand		ecommand;
	extern StateScintilla_symbols	scintilla_symbols;
	extern StateScintilla_lParam	scintilla_lparam;
	extern StateInsert		insert;
	extern State *current;
	static inline bool
	is_string()
	{
		return dynamic_cast(current);
	}
	static inline bool
	is_file()
	{
		return dynamic_cast(current);
	}
}
extern enum Mode {
	MODE_NORMAL = 0,
	MODE_PARSE_ONLY_GOTO,
	MODE_PARSE_ONLY_LOOP,
	MODE_PARSE_ONLY_COND
} mode;
#define BEGIN_EXEC(STATE) G_STMT_START {	\
	if (mode > MODE_NORMAL)			\
		return STATE;			\
} G_STMT_END
extern gint macro_pc;
extern gchar *strings[2];
extern gchar escape_char;
namespace Execute {
	void step(const gchar *macro, gint stop_pos)
		 throw (State::Error, ReplaceCmdline);
	void macro(const gchar *macro, bool locals = true)
		  throw (State::Error, ReplaceCmdline);
	bool file(const gchar *filename, bool locals = true);
}
#endif