/* base.c
 *
 * This file is part of the Phalcon Framework.
 *
 * (c) Phalcon Team <team@phalcon.io>
 *
 * For the full copyright and license information, please view the
 * LICENSE.txt file that was distributed with this source code.
 */

const phvolt_token_names phvolt_tokens[] =
{
  { SL("INTEGER"),       PHVOLT_T_INTEGER },
  { SL("DOUBLE"),        PHVOLT_T_DOUBLE },
  { SL("STRING"),        PHVOLT_T_STRING },
  { SL("IDENTIFIER"),    PHVOLT_T_IDENTIFIER },
  { SL("MINUS"),         PHVOLT_T_MINUS },
  { SL("+"),             PHVOLT_T_ADD },
  { SL("-"),             PHVOLT_T_SUB },
  { SL("*"),             PHVOLT_T_MUL },
  { SL("/"),             PHVOLT_T_DIV },
  { SL("%%"),            PHVOLT_T_MOD },
  { SL("!"),             PHVOLT_T_NOT },
  { SL("~"),             PHVOLT_T_CONCAT },
  { SL("AND"),           PHVOLT_T_AND },
  { SL("OR"),            PHVOLT_T_OR },
  { SL("DOT"),           PHVOLT_T_DOT },
  { SL("COMMA"),         PHVOLT_T_COMMA },
  { SL("EQUALS"),        PHVOLT_T_EQUALS },
  { SL("NOT EQUALS"),    PHVOLT_T_NOTEQUALS },
  { SL("IDENTICAL"),     PHVOLT_T_IDENTICAL },
  { SL("NOT IDENTICAL"), PHVOLT_T_NOTIDENTICAL },
  { SL("NOT"),           PHVOLT_T_NOT },
  { SL("RANGE"),         PHVOLT_T_RANGE },
  { SL("COLON"),         PHVOLT_T_COLON },
  { SL("QUESTION MARK"), PHVOLT_T_QUESTION },
  { SL("<"),             PHVOLT_T_LESS },
  { SL("<="),            PHVOLT_T_LESSEQUAL },
  { SL(">"),             PHVOLT_T_GREATER },
  { SL(">="),            PHVOLT_T_GREATEREQUAL },
  { SL("("),             PHVOLT_T_PARENTHESES_OPEN },
  { SL(")"),             PHVOLT_T_PARENTHESES_CLOSE },
  { SL("["),             PHVOLT_T_SBRACKET_OPEN },
  { SL("]"),             PHVOLT_T_SBRACKET_CLOSE },
  { SL("{"),             PHVOLT_T_CBRACKET_OPEN },
  { SL("}"),             PHVOLT_T_CBRACKET_CLOSE },
  { SL("{%"),            PHVOLT_T_OPEN_DELIMITER },
  { SL("%}"),            PHVOLT_T_CLOSE_DELIMITER },
  { SL("{{"),            PHVOLT_T_OPEN_EDELIMITER },
  { SL("}}"),            PHVOLT_T_CLOSE_EDELIMITER },
  { SL("IF"),            PHVOLT_T_IF },
  { SL("ELSE"),          PHVOLT_T_ELSE },
  { SL("ELSEIF"),        PHVOLT_T_ELSEIF },
  { SL("ELSEFOR"),       PHVOLT_T_ELSEFOR },
  { SL("ENDIF"),         PHVOLT_T_ENDIF },
  { SL("FOR"),           PHVOLT_T_FOR },
  { SL("SWITCH"),        PHVOLT_T_SWITCH },
  { SL("CASE"),          PHVOLT_T_CASE },
  { SL("DEFAULT"),       PHVOLT_T_DEFAULT },
  { SL("ENDSWITCH"),     PHVOLT_T_ENDSWITCH },
  { SL("IN"),            PHVOLT_T_IN },
  { SL("ENDFOR"),        PHVOLT_T_ENDFOR },
  { SL("SET"),           PHVOLT_T_SET },
  { SL("ASSIGN"),        PHVOLT_T_ASSIGN },
  { SL("+="),            PHVOLT_T_ADD_ASSIGN },
  { SL("-="),            PHVOLT_T_SUB_ASSIGN },
  { SL("*="),            PHVOLT_T_MUL_ASSIGN },
  { SL("/="),            PHVOLT_T_DIV_ASSIGN },
  { SL("++"),            PHVOLT_T_INCR },
  { SL("--"),            PHVOLT_T_DECR },
  { SL("BLOCK"),         PHVOLT_T_BLOCK },
  { SL("ENDBLOCK"),      PHVOLT_T_ENDBLOCK },
  { SL("CACHE"),         PHVOLT_T_CACHE },
  { SL("ENDCACHE"),      PHVOLT_T_ENDCACHE },
  { SL("EXTENDS"),       PHVOLT_T_EXTENDS },
  { SL("IS"),            PHVOLT_T_IS },
  { SL("DEFINED"),       PHVOLT_T_DEFINED },
  { SL("EMPTY"),         PHVOLT_T_EMPTY },
  { SL("EVEN"),          PHVOLT_T_EVEN },
  { SL("ODD"),           PHVOLT_T_ODD },
  { SL("NUMERIC"),       PHVOLT_T_NUMERIC },
  { SL("SCALAR"),        PHVOLT_T_SCALAR },
  { SL("ITERABLE"),      PHVOLT_T_ITERABLE },
  { SL("INCLUDE"),       PHVOLT_T_INCLUDE },
  { SL("DO"),            PHVOLT_T_DO },
  { SL("WHITESPACE"),    PHVOLT_T_IGNORE },
  { SL("AUTOESCAPE"),    PHVOLT_T_AUTOESCAPE },
  { SL("ENDAUTOESCAPE"), PHVOLT_T_ENDAUTOESCAPE },
  { SL("CONTINUE"),      PHVOLT_T_CONTINUE },
  { SL("BREAK"),         PHVOLT_T_BREAK },
  { SL("WITH"),          PHVOLT_T_WITH },
  { SL("RETURN"),        PHVOLT_T_RETURN },
  { SL("MACRO"),         PHVOLT_T_MACRO },
  { SL("ENDMACRO"),      PHVOLT_T_ENDMACRO },
  { SL("CALL"),          PHVOLT_T_CALL },
  { SL("WITH"),          PHVOLT_T_WITH },
  { NULL, 0, 0 }
};

/* {{{ phvolt_wrapper_alloc
   Wrapper to alloc memory within the parser. */
static void *phvolt_wrapper_alloc(size_t bytes)
{
	return emalloc(bytes);
}
/* }}} */

/* {{{ phvolt_wrapper_free
   Wrapper to free memory within the parser. */
static void phvolt_wrapper_free(void *pointer)
{
	efree(pointer);
}
/* }}} */

/* {{{ phvolt_parse_with_token
   Creates a parser_token to be passed to the parser. */
static void phvolt_parse_with_token(void* phvolt_parser, int opcode, int parsercode, phvolt_scanner_token *token,
	phvolt_parser_status *parser_status)
{
	phvolt_parser_token *pToken;

	pToken = emalloc(sizeof(phvolt_parser_token));
	pToken->opcode = opcode;
	pToken->token = token->value;
	pToken->token_len = token->len;
	pToken->free_flag = 1;

	phvolt_(phvolt_parser, parsercode, pToken, parser_status);

	token->value = NULL;
	token->len = 0;
}
/* }}} */

/* {{{ phvolt_create_error_msg
   Creates an error message. */
static void phvolt_create_error_msg(phvolt_parser_status *parser_status, char *message)
{
	unsigned int length = (128 + Z_STRLEN_P(parser_status->scanner_state->active_file));
	char *str = emalloc(sizeof(char) * length);

	snprintf(str, length, "%s in %s on line %d", message,
		Z_STRVAL_P(parser_status->scanner_state->active_file), parser_status->scanner_state->active_line);
	str[length - 1] = '\0';

	parser_status->syntax_error = estrndup(str, strlen(str));
	efree(str);
}
/* }}} */

/* {{{ phvolt_scanner_error_msg
   Creates an error message when it's triggered by the scanner. */
static void phvolt_scanner_error_msg(phvolt_parser_status *parser_status, zval **error_msg)
{
	char *error, *error_part;
	int length;
	phvolt_scanner_state *state = parser_status->scanner_state;

	ZVAL_NULL(*error_msg);

	if (state->start) {
		error = emalloc(sizeof(char) * 72 + state->start_length +  Z_STRLEN_P(state->active_file));
		if (state->start_length > 16) {
			length = 72 + Z_STRLEN_P(state->active_file);
			error_part = estrndup(state->start, 16);
			snprintf(error, length, "Scanning error before '%s...' in %s on line %d",
				error_part, Z_STRVAL_P(state->active_file), state->active_line);
			error[length - 1] = '\0';
			efree(error_part);
		} else {
			length = 48 + state->start_length + Z_STRLEN_P(state->active_file);
			snprintf(error, length, "Scanning error before '%s' in %s on line %d",
				state->start, Z_STRVAL_P(state->active_file), state->active_line);
		}
	} else {
		error = emalloc(sizeof(char) * (32 + Z_STRLEN_P(state->active_file)));
		length = 32 + Z_STRLEN_P(state->active_file);
		snprintf(error, length, "Scanning error near to EOF in %s", Z_STRVAL_P(state->active_file));
	}

	error[length - 1] = '\0';
	ZVAL_STRING(*error_msg, error);

	efree(error);
}
/* }}} */

/* {{{ phvolt_parse_view
   Receives the volt code tokenizes and parses it. */
int phvolt_parse_view(zval *result, zval *view_code, zval *template_path)
{
	zval em, *error_msg = &em;
	ZVAL_NULL(result);
	ZVAL_NULL(error_msg);

	if (Z_TYPE_P(view_code) != IS_STRING) {
		ZEPHIR_THROW_EXCEPTION_STRW(phalcon_mvc_view_exception_ce, "View code must be a string");
		return FAILURE;
	}

	if (phvolt_internal_parse_view(&result, view_code, template_path, &error_msg) == FAILURE) {
		ZEPHIR_THROW_EXCEPTION_STRW(phalcon_mvc_view_exception_ce, Z_STRVAL_P(error_msg));
		zval_dtor(error_msg);
		return FAILURE;
	}

	return SUCCESS;
}
/* }}} */

/* {{{ phvolt_parse_view
   Checks whether the token has only blank characters. */
int phvolt_is_blank_string(phvolt_scanner_token *token)
{
	char *marker = token->value;
	unsigned int ch, i;

	for (i = 0; i < token->len; i++) {
		ch = *marker;
		if (ch != ' ' && ch != '\t' && ch != '\n' && ch != '\r' && ch != 11) {
			return 0;
		}
		marker++;
	}

	return 1;
}
/* }}} */

/* {{{ Parses a volt template returning an intermediate array representation
   Checks whether the token has only blank characters. */
int phvolt_internal_parse_view(zval **result, zval *view_code, zval *template_path, zval **error_msg)
{
	char *error;
	phvolt_scanner_state *state;
	phvolt_scanner_token token;
	int scanner_status, status = SUCCESS;
	int prev_token = 0;
	phvolt_parser_status *parser_status = NULL;
	void* phvolt_parser;

	/** Check if the view has code */
	if (!Z_STRVAL_P(view_code)) {
		ZVAL_STRING(*error_msg, "View code cannot be null");
		return FAILURE;
	}

	if (!Z_STRLEN_P(view_code)) {
		array_init(*result);
		return SUCCESS;
	}

	/** Start the reentrant parser */
	phvolt_parser = phvolt_Alloc(phvolt_wrapper_alloc);
	if (unlikely(!phvolt_parser)) {
		ZVAL_STRING(*error_msg, "Memory allocation error");
		return FAILURE;
	}

	parser_status = emalloc(sizeof(phvolt_parser_status));
	state = emalloc(sizeof(phvolt_scanner_state));

	parser_status->status = PHVOLT_PARSING_OK;
	parser_status->scanner_state = state;
	ZVAL_UNDEF(&parser_status->ret);

	parser_status->token = &token;
	parser_status->syntax_error = NULL;

	/** Initialize the scanner state */
	state->active_token = 0;
	state->start = Z_STRVAL_P(view_code);
	state->mode = PHVOLT_MODE_RAW;
	state->raw_buffer = emalloc(sizeof(char) * PHVOLT_RAW_BUFFER_SIZE);
	state->raw_buffer_size = PHVOLT_RAW_BUFFER_SIZE;
	state->raw_buffer_cursor = 0;
	state->active_file = template_path;
	state->active_line = 1;
	state->statement_position = 0;
	state->extends_mode = 0;
	state->block_level = 0;
	state->macro_level = 0;
	state->start_length = 0;
	state->old_if_level = 0;
	state->if_level = 0;
	state->for_level = 0;
	state->switch_level = 0;
	state->whitespace_control = 0;
	state->verbatim = 0;

	state->end = state->start;

	token.value = NULL;
	token.len = 0;

	/* To enable parser tracing decalre "#undef NDEBUG" in the lemon file */
#ifndef NDEBUG
	phvolt_Trace(stderr, "[PARSER]  ");
#endif
	while (0 <= (scanner_status = phvolt_get_token(state, &token))) {

		state->active_token = token.opcode;

		state->start_length = (Z_STRVAL_P(view_code) + Z_STRLEN_P(view_code) - state->start);

		switch (token.opcode) {
			case PHVOLT_T_IGNORE:
				break;

			case PHVOLT_T_ADD:
				phvolt_(phvolt_parser, PHVOLT_PLUS, NULL, parser_status);
				break;

			case PHVOLT_T_SUB:
				phvolt_(phvolt_parser, PHVOLT_MINUS, NULL, parser_status);
				break;

			case PHVOLT_T_MUL:
				phvolt_(phvolt_parser, PHVOLT_TIMES, NULL, parser_status);
				break;

			case PHVOLT_T_DIV:
				phvolt_(phvolt_parser, PHVOLT_DIVIDE, NULL, parser_status);
				break;

			case PHVOLT_T_MOD:
				phvolt_(phvolt_parser, PHVOLT_MOD, NULL, parser_status);
				break;

			case PHVOLT_T_AND:
				phvolt_(phvolt_parser, PHVOLT_AND, NULL, parser_status);
				break;

			case PHVOLT_T_OR:
				phvolt_(phvolt_parser, PHVOLT_OR, NULL, parser_status);
				break;

			case PHVOLT_T_IS:
				phvolt_(phvolt_parser, PHVOLT_IS, NULL, parser_status);
				break;

			case PHVOLT_T_EQUALS:
				phvolt_(phvolt_parser, PHVOLT_EQUALS, NULL, parser_status);
				break;

			case PHVOLT_T_NOTEQUALS:
				phvolt_(phvolt_parser, PHVOLT_NOTEQUALS, NULL, parser_status);
				break;

			case PHVOLT_T_LESS:
				phvolt_(phvolt_parser, PHVOLT_LESS, NULL, parser_status);
				break;

			case PHVOLT_T_GREATER:
				phvolt_(phvolt_parser, PHVOLT_GREATER, NULL, parser_status);
				break;

			case PHVOLT_T_GREATEREQUAL:
				phvolt_(phvolt_parser, PHVOLT_GREATEREQUAL, NULL, parser_status);
				break;

			case PHVOLT_T_LESSEQUAL:
				phvolt_(phvolt_parser, PHVOLT_LESSEQUAL, NULL, parser_status);
				break;

			case PHVOLT_T_IDENTICAL:
				phvolt_(phvolt_parser, PHVOLT_IDENTICAL, NULL, parser_status);
				break;

			case PHVOLT_T_NOTIDENTICAL:
				phvolt_(phvolt_parser, PHVOLT_NOTIDENTICAL, NULL, parser_status);
				break;

			case PHVOLT_T_NOT:
				phvolt_(phvolt_parser, PHVOLT_NOT, NULL, parser_status);
				break;

			case PHVOLT_T_DOT:
				phvolt_(phvolt_parser, PHVOLT_DOT, NULL, parser_status);
				break;

			case PHVOLT_T_CONCAT:
				phvolt_(phvolt_parser, PHVOLT_CONCAT, NULL, parser_status);
				break;

			case PHVOLT_T_RANGE:
				phvolt_(phvolt_parser, PHVOLT_RANGE, NULL, parser_status);
				break;

			case PHVOLT_T_PIPE:
				phvolt_(phvolt_parser, PHVOLT_PIPE, NULL, parser_status);
				break;

			case PHVOLT_T_COMMA:
				phvolt_(phvolt_parser, PHVOLT_COMMA, NULL, parser_status);
				break;

			case PHVOLT_T_COLON:
				phvolt_(phvolt_parser, PHVOLT_COLON, NULL, parser_status);
				break;

			case PHVOLT_T_QUESTION:
				phvolt_(phvolt_parser, PHVOLT_QUESTION, NULL, parser_status);
				break;

			case PHVOLT_T_PARENTHESES_OPEN:
				phvolt_(phvolt_parser, PHVOLT_PARENTHESES_OPEN, NULL, parser_status);
				break;

			case PHVOLT_T_PARENTHESES_CLOSE:
				phvolt_(phvolt_parser, PHVOLT_PARENTHESES_CLOSE, NULL, parser_status);
				break;

			case PHVOLT_T_SBRACKET_OPEN:
				phvolt_(phvolt_parser, PHVOLT_SBRACKET_OPEN, NULL, parser_status);
				break;

			case PHVOLT_T_SBRACKET_CLOSE:
				phvolt_(phvolt_parser, PHVOLT_SBRACKET_CLOSE, NULL, parser_status);
				break;

			case PHVOLT_T_CBRACKET_OPEN:
				phvolt_(phvolt_parser, PHVOLT_CBRACKET_OPEN, NULL, parser_status);
				break;

			case PHVOLT_T_CBRACKET_CLOSE:
				phvolt_(phvolt_parser, PHVOLT_CBRACKET_CLOSE, NULL, parser_status);
				break;

			case PHVOLT_T_OPEN_DELIMITER:
				phvolt_(phvolt_parser, PHVOLT_OPEN_DELIMITER, NULL, parser_status);
				break;

			case PHVOLT_T_CLOSE_DELIMITER:
				phvolt_(phvolt_parser, PHVOLT_CLOSE_DELIMITER, NULL, parser_status);
				break;

			case PHVOLT_T_OPEN_EDELIMITER:
				if (state->extends_mode == 1 && state->block_level == 0) {
					phvolt_create_error_msg(parser_status, "Child templates only may contain blocks");
					parser_status->status = PHVOLT_PARSING_FAILED;
					break;
				}
				phvolt_(phvolt_parser, PHVOLT_OPEN_EDELIMITER, NULL, parser_status);
				break;

			case PHVOLT_T_CLOSE_EDELIMITER:
				phvolt_(phvolt_parser, PHVOLT_CLOSE_EDELIMITER, NULL, parser_status);
				break;

			case PHVOLT_T_NULL:
				phvolt_(phvolt_parser, PHVOLT_NULL, NULL, parser_status);
				break;

			case PHVOLT_T_TRUE:
				phvolt_(phvolt_parser, PHVOLT_TRUE, NULL, parser_status);
				break;

			case PHVOLT_T_FALSE:
				phvolt_(phvolt_parser, PHVOLT_FALSE, NULL, parser_status);
				break;

			case PHVOLT_T_INTEGER:
				phvolt_parse_with_token(phvolt_parser, PHVOLT_T_INTEGER, PHVOLT_INTEGER, &token, parser_status);
				break;

			case PHVOLT_T_DOUBLE:
				phvolt_parse_with_token(phvolt_parser, PHVOLT_T_DOUBLE, PHVOLT_DOUBLE, &token, parser_status);
				break;

			case PHVOLT_T_STRING:
				phvolt_parse_with_token(phvolt_parser, PHVOLT_T_STRING, PHVOLT_STRING, &token, parser_status);
				break;

			case PHVOLT_T_IDENTIFIER:
				phvolt_parse_with_token(phvolt_parser, PHVOLT_T_IDENTIFIER, PHVOLT_IDENTIFIER, &token, parser_status);
				break;

			case PHVOLT_T_IF:
				if (state->extends_mode == 1 && state->block_level == 0){
					phvolt_create_error_msg(parser_status, "Child templates only may contain blocks");
					parser_status->status = PHVOLT_PARSING_FAILED;
					break;
				} else {
					state->if_level++;
					state->block_level++;
				}
				phvolt_(phvolt_parser, PHVOLT_IF, NULL, parser_status);
				break;

			case PHVOLT_T_ELSE:
				if (state->if_level == 0 && state->for_level > 0) {
					phvolt_(phvolt_parser, PHVOLT_ELSEFOR, NULL, parser_status);
				} else {
					phvolt_(phvolt_parser, PHVOLT_ELSE, NULL, parser_status);
				}
				break;

			case PHVOLT_T_ELSEFOR:
				phvolt_(phvolt_parser, PHVOLT_ELSEFOR, NULL, parser_status);
				break;

			case PHVOLT_T_ELSEIF:
				if (state->if_level == 0) {
					phvolt_create_error_msg(parser_status, "Unexpected ENDIF");
					parser_status->status = PHVOLT_PARSING_FAILED;
					break;
				}
				phvolt_(phvolt_parser, PHVOLT_ELSEIF, NULL, parser_status);
				break;

			case PHVOLT_T_ENDIF:
				state->block_level--;
				state->if_level--;
				phvolt_(phvolt_parser, PHVOLT_ENDIF, NULL, parser_status);
				break;

			case PHVOLT_T_FOR:
				if (state->extends_mode == 1 && state->block_level == 0){
					phvolt_create_error_msg(parser_status, "Child templates only may contain blocks");
					parser_status->status = PHVOLT_PARSING_FAILED;
					break;
				} else {
					state->old_if_level = state->if_level;
					state->if_level = 0;
					state->for_level++;
					state->block_level++;
				}
				phvolt_(phvolt_parser, PHVOLT_FOR, NULL, parser_status);
				break;

			case PHVOLT_T_IN:
				phvolt_(phvolt_parser, PHVOLT_IN, NULL, parser_status);
				break;

			case PHVOLT_T_ENDFOR:
				state->block_level--;
				state->for_level--;
				state->if_level = state->old_if_level;
				phvolt_(phvolt_parser, PHVOLT_ENDFOR, NULL, parser_status);
				break;

			case PHVOLT_T_SWITCH:
				if (state->extends_mode == 1 && state->block_level == 0){
					phvolt_create_error_msg(parser_status, "Child templates only may contain blocks");
					parser_status->status = PHVOLT_PARSING_FAILED;
					break;
				} else if (state->switch_level > 0) {
					phvolt_create_error_msg(parser_status, "A nested switch detected. There is no nested switch-case statements support");
					parser_status->status = PHVOLT_PARSING_FAILED;
					break;
				}  else {
					state->switch_level = 1;
					state->block_level++;
				}
				phvolt_(phvolt_parser, PHVOLT_SWITCH, NULL, parser_status);
				break;

			case PHVOLT_T_CASE:
				if (state->switch_level == 0) {
					phvolt_create_error_msg(parser_status, "Unexpected CASE");
					parser_status->status = PHVOLT_PARSING_FAILED;
					break;
				}
				phvolt_(phvolt_parser, PHVOLT_CASE, NULL, parser_status);
				break;

			/*
			 * "default" is the {% default %} clause only when it is inside
			 * a switch and directly follows the opening delimiter; anywhere
			 * else (e.g. the |default() filter) it is a plain identifier
			 */
			case PHVOLT_T_DEFAULT:
				if (state->switch_level != 0 && prev_token == PHVOLT_T_OPEN_DELIMITER) {
					phvolt_(phvolt_parser, PHVOLT_DEFAULT, NULL, parser_status);
					efree(token.value);
				} else {
					phvolt_parse_with_token(phvolt_parser, PHVOLT_T_IDENTIFIER, PHVOLT_IDENTIFIER, &token, parser_status);
				}

				break;

			case PHVOLT_T_ENDSWITCH:
				if (state->switch_level == 0) {
					phvolt_create_error_msg(parser_status, "Unexpected ENDSWITCH");
					parser_status->status = PHVOLT_PARSING_FAILED;
					break;
				} else {
					state->switch_level = 0;
					state->block_level--;
				}

				phvolt_(phvolt_parser, PHVOLT_ENDSWITCH, NULL, parser_status);
				break;

			case PHVOLT_T_RAW_FRAGMENT:
				if (token.len > 0) {
					if (state->extends_mode == 1 && state->block_level == 0){
						if (!phvolt_is_blank_string(&token)) {
							phvolt_create_error_msg(parser_status, "Child templates only may contain blocks");
							parser_status->status = PHVOLT_PARSING_FAILED;
						}
						efree(token.value);
						break;
					} else {
						if (!phvolt_is_blank_string(&token)) {
							state->statement_position++;
						}
					}
					phvolt_parse_with_token(phvolt_parser, PHVOLT_T_RAW_FRAGMENT, PHVOLT_RAW_FRAGMENT, &token, parser_status);
				} else {
					efree(token.value);
				}
				break;

			case PHVOLT_T_SET:
				if (state->extends_mode == 1 && state->block_level == 0){
					phvolt_create_error_msg(parser_status, "Child templates only may contain blocks");
					parser_status->status = PHVOLT_PARSING_FAILED;
					break;
				}
				phvolt_(phvolt_parser, PHVOLT_SET, NULL, parser_status);
				break;

			case PHVOLT_T_ASSIGN:
				phvolt_(phvolt_parser, PHVOLT_ASSIGN, NULL, parser_status);
				break;

			case PHVOLT_T_ADD_ASSIGN:
				phvolt_(phvolt_parser, PHVOLT_ADD_ASSIGN, NULL, parser_status);
				break;

			case PHVOLT_T_SUB_ASSIGN:
				phvolt_(phvolt_parser, PHVOLT_SUB_ASSIGN, NULL, parser_status);
				break;

			case PHVOLT_T_MUL_ASSIGN:
				phvolt_(phvolt_parser, PHVOLT_MUL_ASSIGN, NULL, parser_status);
				break;

			case PHVOLT_T_DIV_ASSIGN:
				phvolt_(phvolt_parser, PHVOLT_DIV_ASSIGN, NULL, parser_status);
				break;

			case PHVOLT_T_INCR:
				phvolt_(phvolt_parser, PHVOLT_INCR, NULL, parser_status);
				break;

			case PHVOLT_T_DECR:
				phvolt_(phvolt_parser, PHVOLT_DECR, NULL, parser_status);
				break;

			case PHVOLT_T_BLOCK:
				if (state->block_level > 0) {
					phvolt_create_error_msg(parser_status, "Embedding blocks into other blocks is not supported");
					parser_status->status = PHVOLT_PARSING_FAILED;
					break;
				} else {
					state->block_level++;
				}
				phvolt_(phvolt_parser, PHVOLT_BLOCK, NULL, parser_status);
				break;

			case PHVOLT_T_ENDBLOCK:
				state->block_level--;
				phvolt_(phvolt_parser, PHVOLT_ENDBLOCK, NULL, parser_status);
				break;

			case PHVOLT_T_MACRO:
				if (state->macro_level > 0) {
					phvolt_create_error_msg(parser_status, "Embedding macros into other macros is not allowed");
					parser_status->status = PHVOLT_PARSING_FAILED;
					break;
				} else {
					state->macro_level++;
				}
				phvolt_(phvolt_parser, PHVOLT_MACRO, NULL, parser_status);
				break;
			case PHVOLT_T_ENDMACRO:
				state->macro_level--;
				phvolt_(phvolt_parser, PHVOLT_ENDMACRO, NULL, parser_status);
				break;

			case PHVOLT_T_CALL:
				phvolt_(phvolt_parser, PHVOLT_CALL, NULL, parser_status);
				break;
			case PHVOLT_T_ENDCALL:
				phvolt_(phvolt_parser, PHVOLT_ENDCALL, NULL, parser_status);
				break;

			case PHVOLT_T_CACHE:
				phvolt_(phvolt_parser, PHVOLT_CACHE, NULL, parser_status);
				break;
			case PHVOLT_T_ENDCACHE:
				phvolt_(phvolt_parser, PHVOLT_ENDCACHE, NULL, parser_status);
				break;

			case PHVOLT_T_VERBATIM:
				phvolt_(phvolt_parser, PHVOLT_VERBATIM, NULL, parser_status);
				break;
			case PHVOLT_T_ENDVERBATIM:
				phvolt_(phvolt_parser, PHVOLT_ENDVERBATIM, NULL, parser_status);
				break;

			case PHVOLT_T_INCLUDE:
				phvolt_(phvolt_parser, PHVOLT_INCLUDE, NULL, parser_status);
				break;

			case PHVOLT_T_WITH:
				phvolt_(phvolt_parser, PHVOLT_WITH, NULL, parser_status);
				break;

			case PHVOLT_T_DEFINED:
				phvolt_(phvolt_parser, PHVOLT_DEFINED, NULL, parser_status);
				break;

			case PHVOLT_T_EMPTY:
				phvolt_(phvolt_parser, PHVOLT_EMPTY, NULL, parser_status);
				break;

			case PHVOLT_T_EVEN:
				phvolt_(phvolt_parser, PHVOLT_EVEN, NULL, parser_status);
				break;

			case PHVOLT_T_ODD:
				phvolt_(phvolt_parser, PHVOLT_ODD, NULL, parser_status);
				break;

			case PHVOLT_T_NUMERIC:
				phvolt_(phvolt_parser, PHVOLT_NUMERIC, NULL, parser_status);
				break;

			case PHVOLT_T_SCALAR:
				phvolt_(phvolt_parser, PHVOLT_SCALAR, NULL, parser_status);
				break;

			case PHVOLT_T_ITERABLE:
				phvolt_(phvolt_parser, PHVOLT_ITERABLE, NULL, parser_status);
				break;

			case PHVOLT_T_DO:
				phvolt_(phvolt_parser, PHVOLT_DO, NULL, parser_status);
				break;
			case PHVOLT_T_RETURN:
				phvolt_(phvolt_parser, PHVOLT_RETURN, NULL, parser_status);
				break;

			case PHVOLT_T_AUTOESCAPE:
				phvolt_(phvolt_parser, PHVOLT_AUTOESCAPE, NULL, parser_status);
				break;

			case PHVOLT_T_ENDAUTOESCAPE:
				phvolt_(phvolt_parser, PHVOLT_ENDAUTOESCAPE, NULL, parser_status);
				break;

			case PHVOLT_T_BREAK:
				phvolt_(phvolt_parser, PHVOLT_BREAK, NULL, parser_status);
				break;

			case PHVOLT_T_CONTINUE:
				phvolt_(phvolt_parser, PHVOLT_CONTINUE, NULL, parser_status);
				break;

			case PHVOLT_T_EXTENDS:
				if (state->statement_position != 1) {
					phvolt_create_error_msg(parser_status, "Extends statement must be placed at the first line in the template");
					parser_status->status = PHVOLT_PARSING_FAILED;
					break;
				} else {
					state->extends_mode = 1;
				}
				phvolt_(phvolt_parser, PHVOLT_EXTENDS, NULL, parser_status);
				break;

			default:
				parser_status->status = PHVOLT_PARSING_FAILED;
				error = emalloc(sizeof(char) * (48 + Z_STRLEN_P(state->active_file)));
				snprintf(error, 48 + Z_STRLEN_P(state->active_file) + state->active_line,
					"Scanner: unknown opcode %d on in %s line %d", token.opcode,
					Z_STRVAL_P(state->active_file), state->active_line);

				if (Z_TYPE_P(*error_msg) == IS_NULL) {
					ZVAL_STRING((*error_msg), error);
				}

				efree(error);
				break;
		}

		if (parser_status->status != PHVOLT_PARSING_OK) {
			status = FAILURE;
			break;
		}

		/* whitespace inside delimiters arrives as IGNORE; skip it */
		if (token.opcode != PHVOLT_T_IGNORE) {
			prev_token = token.opcode;
		}

		state->end = state->start;
	}

	if (status != FAILURE) {
		switch (scanner_status) {
			case PHVOLT_SCANNER_RETCODE_ERR:
			case PHVOLT_SCANNER_RETCODE_IMPOSSIBLE:
				if (!*error_msg) {
					phvolt_scanner_error_msg(parser_status, error_msg);
				} else {
					if (Z_TYPE_P(*error_msg) == IS_NULL) {
						phvolt_scanner_error_msg(parser_status, error_msg);
					}
				}
				status = FAILURE;
				break;
			default:
				phvolt_(phvolt_parser, 0, NULL, parser_status);
		}
	}

	state->active_token = 0;
	state->start = NULL;
	efree(state->raw_buffer);

	if (parser_status->status != PHVOLT_PARSING_OK) {
		status = FAILURE;
		if (parser_status->syntax_error) {
			ZVAL_STRING(*error_msg, parser_status->syntax_error);
			efree(parser_status->syntax_error);
		}
	}

	if (status != FAILURE) {
		if (parser_status->status == PHVOLT_PARSING_OK) {
			if (Z_TYPE(parser_status->ret) != IS_UNDEF) {
				ZVAL_ZVAL(*result, &parser_status->ret, 1, 1);
			} else {
				array_init(*result);
			}
		}
	}

	phvolt_Free(phvolt_parser, phvolt_wrapper_free);

	efree(parser_status);
	efree(state);

	return status;

}
/* }}} */

/*
 * Local variables:
 * tab-width: 4
 * c-basic-offset: 4
 * End:
 * vim600: noet sw=4 ts=4 fdm=marker
 * vim<600: noet sw=4 ts=4
 */
