%require "3.0"
/*
 * phpdbg_parser.y
 * (from php-src root)
 */

%code requires {
#include "phpdbg.h"
#ifndef YY_TYPEDEF_YY_SCANNER_T
#define YY_TYPEDEF_YY_SCANNER_T
typedef void* yyscan_t;
#endif
}

%code {

#include "phpdbg_cmd.h"
#include "phpdbg_utils.h"
#include "phpdbg_prompt.h"

#include "phpdbg_parser.h"
#include "phpdbg_lexer.h"

#undef yyerror
static int yyerror(const char *msg);

ZEND_EXTERN_MODULE_GLOBALS(phpdbg)

#ifdef _MSC_VER
#define YYMALLOC malloc
#define YYFREE free
#endif

}

%define api.prefix {phpdbg_}
%define api.pure full
%define api.value.type {phpdbg_param_t}
%define parse.error verbose

%token END 0 "end of command"
%token T_EVAL       "eval"
%token T_RUN        "run"
%token T_SHELL      "shell"
%token T_IF         "if (condition)"
%token T_TRUTHY     "truthy (true, on, yes or enabled)"
%token T_FALSY      "falsy (false, off, no or disabled)"
%token T_STRING     "string (some input, perhaps)"
%token T_COLON      ": (colon)"
%token T_DCOLON     ":: (double colon)"
%token T_POUND      "# (pound sign followed by digits)"
%token T_SEPARATOR  "# (pound sign)"
%token T_PROTO      "protocol (file://)"
%token T_DIGITS     "digits (numbers)"
%token T_LITERAL    "literal (string)"
%token T_ADDR       "address"
%token T_OPCODE     "opcode"
%token T_ID         "identifier (command or function name)"
%token T_INPUT      "input (input string or data)"
%token T_UNEXPECTED "input"
%token T_REQ_ID     "request id (-r %d)"

%% /* Rules */

input
	: command { $$ = $1; }
	| input T_SEPARATOR command { phpdbg_stack_separate($1.top); $$ = $3; }
	| %empty { (void) phpdbg_nerrs; }
	;

command
	: parameters { $$.top = PHPDBG_G(parser_stack)->top; }
	| full_expression { phpdbg_stack_push(PHPDBG_G(parser_stack), &$1); $$.top = PHPDBG_G(parser_stack)->top; }
	;

parameters
	: parameter { phpdbg_stack_push(PHPDBG_G(parser_stack), &$1); $$.top = PHPDBG_G(parser_stack)->top; }
	| parameters parameter { phpdbg_stack_push(PHPDBG_G(parser_stack), &$2); $$.top = PHPDBG_G(parser_stack)->top; }
	| parameters T_REQ_ID { $$ = $1; PHPDBG_G(req_id) = $2.num; }
	;

parameter
	: T_ID T_COLON T_DIGITS {
		$$.type = FILE_PARAM;
		$$.file.name = $2.str;
		$$.file.line = $3.num;
	}
	| T_ID T_COLON T_POUND T_DIGITS {
		$$.type = NUMERIC_FILE_PARAM;
		$$.file.name = $1.str;
		$$.file.line = $4.num;
	}
	| T_PROTO T_ID T_COLON T_DIGITS {
		$$.type = FILE_PARAM;
		$$.file.name = malloc($1.len + $2.len + 1);
		if ($$.file.name) {
			memcpy(&$$.file.name[0], $1.str, $1.len);
			memcpy(&$$.file.name[$1.len], $2.str, $2.len);
			$$.file.name[$1.len + $2.len] = '\0';
		}
		$$.file.line = $4.num;
	}
	| T_PROTO T_ID T_COLON T_POUND T_DIGITS {
		$$.type = NUMERIC_FILE_PARAM;
		$$.file.name = malloc($1.len + $2.len + 1);
		if ($$.file.name) {
			memcpy(&$$.file.name[0], $1.str, $1.len);
			memcpy(&$$.file.name[$1.len], $2.str, $2.len);
			$$.file.name[$1.len + $2.len] = '\0';
		}
		$$.file.line = $5.num;
	}
	| T_ID T_DCOLON T_ID {
		$$.type = METHOD_PARAM;
		$$.method.class = $1.str;
		$$.method.name = $3.str;
	}
	| T_ID T_DCOLON T_ID T_POUND T_DIGITS {
		$$.type = NUMERIC_METHOD_PARAM;
		$$.method.class = $1.str;
		$$.method.name = $3.str;
		$$.num = $5.num;
	}
	| T_ID T_POUND T_DIGITS {
		$$.type = NUMERIC_FUNCTION_PARAM;
		$$.str = $1.str;
		$$.len = $1.len;
		$$.num = $3.num;
	}
	| T_IF T_INPUT {
		$$.type = COND_PARAM;
		$$.str = $2.str;
		$$.len = $2.len;
	}
	| T_OPCODE { $$ = $1; }
	| T_ADDR { $$ = $1; }
	| T_LITERAL { $$ = $1; }
	| T_TRUTHY { $$ = $1; }
	| T_FALSY { $$ = $1; }
	| T_DIGITS { $$ = $1; }
	| T_ID { $$ = $1; }
	;

req_id
	: T_REQ_ID { PHPDBG_G(req_id) = $1.num; }
	| %empty
;

full_expression
	: T_EVAL req_id T_INPUT {
		$$.type = EVAL_PARAM;
		$$.str = $3.str;
		$$.len = $3.len;
	}
	| T_SHELL req_id T_INPUT {
		$$.type = SHELL_PARAM;
		$$.str = $3.str;
		$$.len = $3.len;
	}
	| T_RUN req_id {
		$$.type = RUN_PARAM;
		$$.len = 0;
	}
	| T_RUN req_id T_INPUT {
		$$.type = RUN_PARAM;
		$$.str = $3.str;
		$$.len = $3.len;
	}
	;

%%

static int yyerror(const char *msg) {
	phpdbg_error("Parse Error: %s", msg);

	{
		const phpdbg_param_t *top = PHPDBG_G(parser_stack);

		while (top) {
			phpdbg_param_debug(top, "--> ");
			top = top->next;
		}
	}
	return 0;
}

int phpdbg_do_parse(phpdbg_param_t *stack, char *input) {
	if (!*input) {
		return 0;
	}

	if (PHPDBG_G(cur_command)) {
		free(PHPDBG_G(cur_command));
	}
	PHPDBG_G(cur_command) = strdup(input);

	phpdbg_init_lexer(stack, input);

	return yyparse();
}
