#ifndef TMPASM_H
#define TMPASM_H
#ifndef TMPASM_INSTR_MAXLEN
#define TMPASM_INSTR_MAXLEN 32
#endif

typedef struct tmpasm_s tmpasm_t;

typedef struct tmpasm_arg_s tmpasm_arg_t;

struct tmpasm_arg_s {
	tmpasm_arg_t *next;    /* block: the resulting string is a list of strings and addresses */
	char is_addr;          /* 1: arg is a node address; 0: arg is a string immediate */
	char data[1];          /* arg string - obviously longer than 1 char (but there's not special hack for that in C89), \0 terminated */
};

/* user specified instruction prototype */
typedef void tmpasm_instr(tmpasm_t *ctx, char *iname, int argc, tmpasm_arg_t *argv[]);


typedef struct tmpasm_cb_s {
	/* return the value of a node at addr - NULL is an error */
	const char *(*get)(tmpasm_t *ctx, const char *addr);

	/* set the value of a node at addr to data; data may be NULL */
	void (*set)(tmpasm_t *ctx, const char *addr, const char *data);

	/* return 1 if data is true, 0 otherwise; data may be NULL (if an unknown variable is referenced) */
	int (*is_true)(tmpasm_t *ctx, const char *data);

	/* return 1 if str matches pat, 0 otherwise; str and pat may be NULL */
	int (*match)(tmpasm_t *ctx, const char *str, const char *pat);

	/* first iteration over list; return the first element (or NULL to end); the string returned is not free'd by the caller */
	const char *(*first)(tmpasm_t *ctx, void **state, char *list);

	/* return next element of a list or NULL on end (in which case state shall be also free'd by the caller); the string returned is not free'd by the caller */
	const char *(*next)(tmpasm_t *ctx, void **state);

	/* resolve an instruction name to a function pointer */
	tmpasm_instr *(*resolve)(tmpasm_t *ctx, const char *name);

	/* optional: called once before execution of a context starts */
	void (*preexec)(tmpasm_t *ctx);

	/* optional: called once before execution of a context starts */
	void (*postexec)(tmpasm_t *ctx);

	/* optional: resolve the current runtime error, called only for negative
	   error codes; should return a format string with exactly one %s in it
	   or NULL. */
	const char *(*runtime_error_fmt)(tmpasm_t *ctx);
} tmpasm_cb_t;

int tmpasm_gotchar(tmpasm_t *ctx, char c);

tmpasm_t *tmpasm_init(tmpasm_cb_t *cb);
void tmpasm_uninit(tmpasm_t *ctx);

/* return the string version of an arg in a newly malloc()'d string
   if keep_addr is non-zero and a is a single address, no get() is run
   but the address is returned as a string */
char *tmpasm_arg2str(tmpasm_t *ctx, tmpasm_arg_t *a, int keep_addr);

/* execute the code recursively until it exits */
void tmpasm_execute(tmpasm_t *ctx);

void tmpasm_runtime_error(tmpasm_t *ctx, int code, char *data);
const char *tmpasm_runtime_error_fmt(tmpasm_t *ctx);

/* --- internals: not required for normal use --- */
typedef enum {
	ST_PRECMD,          /* waiting for a command to start - ignore whitespace */
	ST_CMD,
	ST_PREDATA,         /* waiting for data */
	ST_PREBLOCKSEP,     /* waiting for a block sep when opening a block */
	ST_BLOCKSEP,        /* found a block sep within the block - either an address or a termination follows */
	ST_BLOCK,           /* in [@ @] block, text part */
	ST_BLOCK_INLINE,    /* in [@ @] block, within inline @@ part */
	ST_STRING,          /* in {} string  */
	ST_STRING_ESCAPE,   /* in {} string, right after a \ */
	ST_ADDRESS,         /* shifting address bytes */
	ST_COMMENT          /* after #, until the next newline */
} tmpasm_state_t;

typedef enum {
	KW_none,
	KW_IF,
	KW_THEN,
	KW_ELSE,
	KW_END,
	KW_FOREACH,
	KW_IN,
	KW_SWITCH,
	KW_CASE,
	KW_DEFAULT,

	KW_NOP     /* virtual instruction */
} tmpasm_kw_t;

/* execution structs */
typedef struct tmpasm_exec_s tmpasm_exec_t;
typedef struct tmpasm_case_s tmpasm_case_t;

struct tmpasm_case_s {
	tmpasm_arg_t *data;
	tmpasm_exec_t *body;
	tmpasm_case_t *next;
};


struct tmpasm_exec_s {
	tmpasm_kw_t kw;        /* kw_none means a hook instruction */
	union {
		struct {      /* normal instruction */
			tmpasm_instr *call;
			char *call_name; /* temporary */
			int argc;
			tmpasm_arg_t **argv;
		} instr;
		struct {
			tmpasm_arg_t *cond;
			tmpasm_exec_t *code_then;
			tmpasm_exec_t *code_else;
		} fc_if;
		struct {
			char *loop_var;        /* must be a single address */
			tmpasm_arg_t *data;    /* what to loop in */
			tmpasm_exec_t *code_body;
		} fc_foreach;
		struct {
			tmpasm_arg_t *cond;
			tmpasm_case_t *first;
			tmpasm_case_t *last;
		} fc_switch;
	} payload;
	int line, col;
	tmpasm_exec_t *next;
};


/* parser structs */
typedef struct stack_s tmpasm_stack_t;
struct stack_s {
	tmpasm_state_t state;
/*	tmpasm_state_t kwstate; internal states of composite keywords like switch */
	char cmd_buff[TMPASM_INSTR_MAXLEN+1];
	unsigned int cmdi;
	tmpasm_kw_t kw, old_kw;
	char block_sep;
	int kwcol, kwline;

	int args_used, args_alloced; /* number of arguments in argv[] */

	tmpasm_arg_t **argv;   /* an array of linked lists */
	tmpasm_arg_t **argend; /* each argv[] is a linked list (for blocks); argend points to the tail */
	int *arg_alloced;      /* of argend */
	int *arg_used;         /* of argend */

	tmpasm_exec_t *last_code;     /* tail of the code list */

	tmpasm_stack_t *next;
};

struct tmpasm_s {
	tmpasm_stack_t *st;
	int dead;
	int col, line;
	tmpasm_exec_t *code;
	tmpasm_exec_t *executing; /* points to the code most recently executed (or being executed when in callbacks) */
	tmpasm_cb_t *cb;
	int halt;
	int runtime_error;
	char *runtime_error_data;
	int runtime_error_line;
	int runtime_error_col;
	void *user_data;
};
#endif
