/*
// Full copyright information is available in the file ../doc/CREDITS
*/

/*
// ------------------------------------------------------------
//
// C Declarations
//
*/

%{

#define _grammar_y_

#include "defs.h"

#include <stdarg.h>
#include "cdc_pcode.h"
#include "util.h"

int yyparse(void);
static void yyerror(char *s);

static Prog *prog;
static cList *errors;

extern Pile *compiler_pile;	/* We free this pile after compilation. */

%}

/*
// ------------------------------------------------------------
//
// yacc Declarations
//
*/

%union {
	Long			 num;
	Float			 fnum;
	char			*s;
	struct arguments	*args;
	struct stmt		*stmt;
	struct expr		*expr;
	struct id_list		*id_list;
	struct stmt_list	*stmt_list;
	struct expr_list	*expr_list;
	struct case_list	*case_list;
	struct case_entry	*case_entry;
};

%type	<num>		ovdecl
%type	<args>		argdecl
%type	<stmt>		stmt if
%type	<stmt_list>	compound stmts stmtlist
%type	<case_entry>	case_ent
%type	<case_list>	caselist cases
%type	<expr>		expr sexpr rexpr handler key_datum
%type	<expr_list>	args arglist cvals dict_args kd_list
%type	<s>		for map find filter hash
%type	<id_list>	vars idlist errors errlist

/* The following tokens are terminals for the parser. */

%token  FIRST_TOKEN

/* data */
%token	<num>	INTEGER OBJNUM
%token  <fnum>  FLOAT
%token	<s>	STRING SYMBOL OBJNAME T_ERROR
%token          LIST DICT BUFFER FROB

%token  DATA_END

/* not data */
%token  <s>     COMMENT IDENT
%token		DISALLOW_OVERRIDES ARG VAR
%token		IF FOR OP_IN UPTO WHILE SWITCH CASE DEFAULT
%token		BREAK CONTINUE RETURN
%token		CATCH ANY HANDLER
%token		FORK
%token		PASS CRITLEFT CRITRIGHT PROPLEFT PROPRIGHT DICT_EQ

%right	OP_ASSIGN
%right	MINUS_EQ DIV_EQ MULT_EQ PLUS_EQ OPTIONAL_ASSIGN
%left	TO
%right	OP_COND_IF ':' OP_COND_OTHER_ELSE
%right	OR
%right	AND
%left	OP_IN
%left	EQ NE '>' GE '<' LE
%left	'+' '-'
%left	'*' '/' '%'
%left   BW_OR BW_AND SHLEFT SHRIGHT
%left	'!'
%left	'[' ']' START_DICT START_BUFFER
%right	P_INCREMENT INCREMENT P_DECREMENT DECREMENT
%left	'.'

/* Declarations to shut up shift/reduce conflicts. */
%nonassoc LOWER_THAN_ELSE
%nonassoc ELSE
%nonassoc LOWER_THAN_WITH
%nonassoc WITH

/* Mapping expressions */
%token OP_MAP WHERE OP_FILTER OP_MAPHASH OP_FIND

/* The parser does not use the following tokens.  I define them here so
 * that I can use them, along with the above tokens, as statement and
 * expression types for the code generator, and as opcodes for the
 * interpreter. */

%token NOOP EXPR COMPOUND ASSIGN IF_ELSE FOR_RANGE FOR_LIST END RETURN_EXPR
%token DOEQ INDECR
%token CASE_VALUE CASE_RANGE LAST_CASE_VALUE LAST_CASE_RANGE END_CASE RANGE
%token FUNCTION_CALL CALL_METHOD EXPR_CALL_METHOD LIST DICT BUFFER FROB INDEX UNARY
%token BINARY CONDITIONAL SPLICE NEG SPLICE_ADD POP START_ARGS ZERO ONE
%token SET_LOCAL SET_OBJ_VAR GET_LOCAL GET_OBJ_VAR CATCH_END HANDLER_END
%token CRITICAL CRITICAL_END PROPAGATE PROPAGATE_END JUMP
%token OPTIONAL_END SCATTER_START SCATTER_END

%token OP_MAP_RANGE OP_MAPHASH_RANGE OP_FILTER_RANGE OP_FIND_RANGE 

%token FUNCTION_START
%token F_DEBUG_CALLERS F_CALL_TRACE
%token F_TYPE F_CLASS F_TOINT F_TOFLOAT F_TOSTR F_TOLITERAL F_FROMLITERAL
%token F_FROB_CLASS F_TOOBJNUM F_TOSYM F_TOERR F_VALID F_STRFMT F_STRLEN F_STRIDX
%token F_SUBSTR F_EXPLODE F_STRSED F_STRSUB F_PAD F_MATCH_BEGIN
%token F_MATCH_TEMPLATE F_STRGRAFT F_LISTGRAFT F_BUFGRAFT
%token F_MATCH_PATTERN F_MATCH_REGEXP F_REGEXP F_SPLIT F_CRYPT F_UPPERCASE
%token F_LOWERCASE F_STRCMP F_STRMEMCMP F_INTERNED F_ID F_RANGE F_GLOB F_LISTLEN F_SUBLIST F_INSERT F_JOIN F_REPLACE
%token F_LISTIDX F_DELETE F_SETADD F_SETREMOVE F_UNION F_MATCH_CRYPTED
%token F_DICT_KEYS F_DICT_VALUES F_DICT_ADD F_DICT_DEL F_DICT_UNION
%token F_DICT_CONTAINS F_BUFLEN F_BUFIDX F_BUF_REPLACE
%token F_BUF_TO_STRINGS F_STRINGS_TO_BUF F_STR_TO_BUF F_BUF_TO_STR
%token F_SUBBUF F_VERSION F_RANDOM F_TIME F_LOCALTIME F_MTIME
%token F_STRFTIME F_CTIME F_MIN F_MAX F_ABS F_LOOKUP F_METHOD_FLAGS
%token F_METHOD_ACCESS F_SET_METHOD_FLAGS F_SET_METHOD_ACCESS
%token F_METHODOP F_THIS F_DEFINER F_SENDER F_CALLER F_USER F_SET_USER
%token F_TASK_ID F_TICKS_LEFT F_ERROR_FUNC F_TRACEBACK F_ERROR_STR
%token F_ERROR_ARG F_THROW F_RETHROW F_CWRITE F_CWRITEF F_FSTAT
%token F_FREAD F_FCHMOD F_FMKDIR F_FRMDIR F_FILES F_FILE
%token F_FREMOVE F_FRENAME F_FOPEN F_FCLOSE F_FSEEK F_FEOF F_FWRITE
%token F_FFLUSH F_CLOSE_CONNECTION F_CONNECTION F_ADD_VAR F_VARIABLES
%token F_DEL_VAR F_SET_VAR F_GET_VAR F_INHERITED_VAR F_DEFAULT_VAR
%token F_CLEAR_VAR F_ADD_METHOD
%token F_RENAME_METHOD F_METHODS F_FIND_METHOD F_FIND_NEXT_METHOD
%token F_METHOD_BYTECODE F_LIST_METHOD F_DEL_METHOD F_PARENTS
%token F_CHILDREN F_ANCESTORS F_HAS_ANCESTOR F_SIZE
%token F_CREATE F_CHPARENTS F_DESTROY F_R_DBLOG F_DBLOG F_REASSIGN_CONNECTION
%token F_BACKUP F_BINARY_DUMP F_TEXT_DUMP F_EXECUTE F_SHUTDOWN F_HARD_EXIT_ERROR
%token F_BIND_PORT F_UNBIND_PORT F_OPEN_CONNECTION F_SET_HEARTBEAT
%token F_DATA F_SET_OBJNAME F_DEL_OBJNAME F_OBJNAME F_OBJNUM
%token F_NEXT_OBJNUM F_TICK F_HOSTNAME F_IP F_RESUME F_SUSPEND
%token F_TASKS F_TASK_INFO F_CANCEL F_PAUSE F_REFRESH F_STACK F_STATUS
%token F_CACHE_INFO F_BIND_FUNCTION F_UNBIND_FUNCTION F_ATOMIC
%token F_METHOD_INFO F_ENCODE F_DECODE F_SIN F_EXP F_LOG F_COS
%token F_TAN F_SQRT F_ASIN F_ACOS F_ATAN F_POW F_ATAN2 F_CONFIG F_ROUND
%token F_FLUSH OP_HANDLED_FROB F_VALUE F_HANDLER F_CALLINGMETHOD

/* Reserved for future use. */
/*%token FORK*/

/*
// LAST_TOKEN tells opcodes.c how much space to allocate
// for the opcodes table.
*/
%token LAST_TOKEN

/*
// ------------------------------------------------------------
//
// Grammar rules
//
*/

%%

method	: ovdecl argdecl vars stmtlist	{ prog = make_prog($1, $2, $3, $4); }
	;

ovdecl	: /* nothing */			{ $$ = 1; }
	| DISALLOW_OVERRIDES ';'	{ $$ = 0; }
	;

argdecl	: /* nothing */			{ $$ = arguments(NULL, NULL); }
	| ARG '[' IDENT ']' ';'		{ $$ = arguments(NULL, $3); }
	| ARG idlist ';'		{ $$ = arguments($2, NULL); }
	| ARG idlist ',' '[' IDENT ']' ';'
					{ $$ = arguments($2, $5); }
	| ARG '@' IDENT ';'		{ $$ = arguments(NULL, $3); }
	| ARG idlist ',' '@' IDENT  ';'
					{ $$ = arguments($2, $5); }
	;

vars	: /* nothing */			{ $$ = NULL; }
	| VAR idlist ';'		{ $$ = $2; }
	;

idlist	: IDENT				{ $$ = id_list($1, NULL); }
	| idlist ',' IDENT		{ $$ = id_list($3, $1); }
	;

errors	: ANY				{ $$ = NULL; }
	| errlist			{ $$ = $1; }
	;

errlist	: T_ERROR			{ $$ = id_list($1, NULL); }
	| errlist ',' T_ERROR		{ $$ = id_list($3, $1); }
	;

compound: '{' stmtlist '}'		{ $$ = $2; }
	;

stmtlist: /* nothing */			{ $$ = NULL; }
	| stmts				{ $$ = $1; }
	;

stmts	: stmt				{ $$ = stmt_list($1, NULL); }
	| stmts stmt			{ $$ = stmt_list($2, $1); }
	;

stmt	: COMMENT			{ $$ = comment_stmt($1); }
	| ';'				{ $$ = noop_stmt(); }
	| expr ';'			{ $$ = expr_stmt($1); }
	| compound			{ $$ = compound_stmt($1); }
	| if %prec LOWER_THAN_ELSE	{ $$ = $1; }
	| if ELSE stmt			{ $$ = if_else_stmt($1, $3); }
	| for '[' expr UPTO expr ']' stmt
					{ $$ = for_range_stmt($1, $3, $5,$7); }
	| for '(' expr ')' stmt		{ $$ = for_list_stmt($1, $3, $5); }
	| WHILE '(' expr ')' stmt	{ $$ = while_stmt($3, $5); }
	| SWITCH '(' expr ')' caselist	{ $$ = switch_stmt($3, $5); }
	| BREAK ';'			{ $$ = break_stmt(); }
	| CONTINUE ';'			{ $$ = continue_stmt(); }
	| RETURN ';'			{ $$ = return_stmt(); }
	| RETURN expr ';'		{ $$ = return_expr_stmt($2); }
	| CATCH errors stmt %prec LOWER_THAN_WITH
					{ $$ = catch_stmt($2, $3, NULL); }
	| CATCH errors stmt WITH HANDLER stmt
					{ $$ = catch_stmt($2, $3, $6); }
	| CATCH errors stmt WITH stmt
					{ $$ = catch_stmt($2, $3, $5); }
	| error ';'			{ yyerrok; $$ = NULL; }
	;

if	: IF '(' expr ')' stmt		{ $$ = if_stmt($3, $5); }
	;

for	: FOR IDENT OP_IN		{ $$ = $2; }
	;

caselist: '{' '}'			{ $$ = NULL; }
	| '{' cases '}'			{ $$ = $2; }
	;

cases	: case_ent			{ $$ = case_list($1, NULL); }
	| cases case_ent		{ $$ = case_list($2, $1); }
	;

case_ent: CASE cvals ':' stmtlist	{ $$ = case_entry($2, $4); }
	| DEFAULT ':' stmtlist		{ $$ = case_entry(NULL, $3); }
	;

handler : '>'                           { $$ = NULL; }
        | ',' expr '>'                  { $$ = $2; }
        ;

expr	: INTEGER			{ $$ = integer_expr($1); }
	| FLOAT				{ $$ = float_expr($1); }
	| STRING			{ $$ = string_expr($1); }
	| OBJNUM			{ $$ = objnum_expr($1); }
	| OBJNAME			{ $$ = objname_expr($1); }
	| SYMBOL			{ $$ = symbol_expr($1); }
	| T_ERROR				{ $$ = error_expr($1); }
	| IDENT				{ $$ = var_expr($1); }
	| IDENT '(' args ')'		{ $$ = function_call_expr($1, $3); }
	| PASS '(' args ')'		{ $$ = pass_expr($3); }
	| expr '.' IDENT '(' args ')'	{ $$ = message_expr($1, $3, $5); }
	| '.' IDENT '(' args ')'	{ $$ = message_expr(NULL, $2, $4); }
	| expr '.' '(' expr ')' '(' args ')'
					{ $$ = expr_message_expr($1, $4, $7); }
	| '.' '(' expr ')' '(' args ')'
					{ $$ = expr_message_expr(NULL, $3, $6);}
	| '[' args ']'			{ $$ = list_expr($2); }
	| START_DICT args ']'		{ $$ = dict_expr($2); }
	| START_BUFFER args ']'		{ $$ = buffer_expr($2); }
	| '<' expr ',' expr handler     { $$ = frob_expr($2, $4, $5); }
	| expr '[' expr ']'		{ $$ = index_expr($1, $3); }
	| INCREMENT IDENT               { $$ = indecr_expr(P_INCREMENT, $2); }
	| DECREMENT IDENT        	{ $$ = indecr_expr(P_DECREMENT, $2); }
	| IDENT INCREMENT               { $$ = indecr_expr(INCREMENT, $1); }
	| IDENT DECREMENT       	{ $$ = indecr_expr(DECREMENT, $1); }
	| '!' expr			{ $$ = unary_expr('!', $2); }
	| '-' expr %prec '!'		{ $$ = unary_expr(NEG, $2); }
	| '+' expr %prec '!'		{ $$ = $2; }
	| expr '*' expr                 { $$ = binary_expr('*', $1, $3); }
	| expr '/' expr			{ $$ = binary_expr('/', $1, $3); }
	| expr '%' expr			{ $$ = binary_expr('%', $1, $3); }
	| expr '+' expr			{ $$ = binary_expr('+', $1, $3); }
	| expr '-' expr			{ $$ = binary_expr('-', $1, $3); }
	| expr '&' expr			{ $$ = binary_expr('&', $1, $3); }
	| expr '|' expr			{ $$ = binary_expr('|', $1, $3); }
	| expr SHLEFT expr		{ $$ = binary_expr(SHLEFT, $1, $3); }
	| expr SHRIGHT expr		{ $$ = binary_expr(SHRIGHT, $1, $3); }
	| expr EQ expr			{ $$ = binary_expr(EQ, $1, $3); }
	| expr NE expr			{ $$ = binary_expr(NE, $1, $3); }
	| expr '>' expr			{ $$ = binary_expr('>', $1, $3); }
	| expr GE expr			{ $$ = binary_expr(GE, $1, $3); }
	| expr '<' expr			{ $$ = binary_expr('<', $1, $3); }
	| expr LE expr			{ $$ = binary_expr(LE, $1, $3); }
	| expr OP_IN expr		{ $$ = binary_expr(OP_IN, $1, $3); }
	| expr AND expr			{ $$ = and_expr($1, $3); }
	| expr OR expr			{ $$ = or_expr($1, $3); }
	| expr OP_COND_IF expr ':' expr	{ $$ = cond_expr($1, $3, $5); }
	| map '(' expr ')' TO '(' expr ')'
					{ $$ = map_expr($3,$1,$7,OP_MAP); }
	| map '[' expr UPTO expr ']' TO '(' expr ')'
					{ $$ = map_range_expr($3,$5,$1,$9,OP_MAP_RANGE); }
	| hash '(' expr ')' TO '(' expr ')'
					{ $$ = map_expr($3,$1,$7,OP_MAPHASH); }
	| hash '[' expr UPTO expr ']' TO '(' expr ')'
					{ $$ = map_range_expr($3,$5,$1,$9,OP_MAPHASH_RANGE); }
	| find '(' expr ')' WHERE '(' expr ')'
					{ $$ = map_expr($3,$1,$7,OP_FIND); }
	| find '[' expr UPTO expr ']' WHERE '(' expr ')'
					{ $$ = map_range_expr($3,$5,$1,$9,OP_FIND_RANGE); }
	| filter '(' expr ')' WHERE '(' expr ')'
					{ $$ = map_expr($3,$1,$7,OP_FILTER); }
	| filter '[' expr UPTO expr ']' WHERE '(' expr ')'
					{ $$ = map_range_expr($3,$5,$1,$9,OP_FILTER_RANGE); }
	| expr OP_COND_IF expr OP_COND_OTHER_ELSE expr { $$ = cond_expr($1, $3, $5); }
	| IDENT MULT_EQ expr		{ $$ = doeq_expr(MULT_EQ, $1, $3); }
	| IDENT DIV_EQ expr		{ $$ = doeq_expr(DIV_EQ, $1, $3); }
	| IDENT PLUS_EQ expr		{ $$ = doeq_expr(PLUS_EQ, $1, $3); }
	| IDENT MINUS_EQ expr		{ $$ = doeq_expr(MINUS_EQ, $1, $3); }
	| IDENT OPTIONAL_ASSIGN expr	{ $$ = opt_expr($1, $3); }
	| expr OP_ASSIGN expr		{ $$ = assign_expr($1, $3); }
	| '(' expr ')'			{ $$ = $2; }
	| CRITLEFT expr CRITRIGHT       { $$ = critical_expr($2); }
	| PROPLEFT expr PROPRIGHT	{ $$ = propagate_expr($2); }
	;

map     : OP_MAP IDENT OP_IN		{ $$ = $2; }
	;

find    : OP_FIND IDENT OP_IN		{ $$ = $2; }
	;

filter  : OP_FILTER IDENT OP_IN		{ $$ = $2; }
	;

hash	: OP_MAPHASH IDENT OP_IN	{ $$ = $2; }
	;

sexpr	: expr				{ $$ = $1; }
	| '@' expr			{ $$ = splice_expr($2); }
	;

args	: /* nothing */			{ $$ = NULL; }
	| arglist			{ $$ = $1; }
	;

dict_args : /* nothing */		{ $$ = NULL; }
	  | kd_list			{ $$ = $1; }
          ;

arglist	: sexpr				{ $$ = expr_list($1, NULL); }
	| arglist ',' sexpr		{ $$ = expr_list($3, $1); }
	;

kd_list : key_datum			{ $$ = expr_list($1, NULL); }
	| kd_list ',' key_datum		{ $$ = expr_list($3, $1); }
	;

key_datum : expr DICT_EQ expr		{ $$ = list_expr(expr_list($3, expr_list($1, NULL))); }
	  ;

rexpr	: expr				{ $$ = $1; }
	| expr UPTO expr		{ $$ = range_expr($1, $3); }
	;

cvals	: rexpr				{ $$ = expr_list($1, NULL); }
	| cvals ',' rexpr		{ $$ = expr_list($3, $1); }
	;

%%

/*
// ------------------------------------------------------------
//
// Additional code
//
*/

Method * compile(Obj * object, cList * code, cList ** error_ret) {
    Method * method = NULL;

    /* Initialize compiler globals. */
    errors = list_new(0);
    lex_start(code);

    /* Parse text.  This sets prog if successful. */
    yyparse();

    if (!errors->len) {
	/* No errors in parsing.  Compile to linear code.  method will be
	 * NULL if unsuccessful. */
    method = generate_method(prog, object);
    }

    /* Free up all temporary storage we allocated during compilation. */
    pfree(compiler_pile);

    /* error_ret gets reference count on errors. */
    *error_ret = errors;
    return method;
}

void compiler_error(Int lineno, char *fmt, ...)
{
    va_list arg;
    cStr * errstr, * line;
    cData d;

    va_start(arg, fmt);
    errstr = vformat(fmt, arg);

    if (lineno == -1) {
	line = errstr;
    } else {
	line = format("Line %d: %s", lineno, errstr->s);
	string_discard(errstr);
    }

    d.type = STRING;
    d.u.str = line;
    errors = list_add(errors, &d);

    string_discard(line);
    va_end(arg);
}

Int no_errors(void) {
    return (errors->len == 0);
}

static void yyerror(char * s) {
    compiler_error(cur_lineno(), s);
}

#undef _grammar_y_
