/*
   +----------------------------------------------------------------------+
   | Zend Engine                                                          |
   +----------------------------------------------------------------------+
   | Copyright (c) 1998-2018 Zend Technologies Ltd. (http://www.zend.com) |
   +----------------------------------------------------------------------+
   | This source file is subject to version 2.00 of the Zend license,     |
   | that is bundled with this package in the file LICENSE, and is        |
   | available through the world-wide-web at the following url:           |
   | http://www.zend.com/license/2_00.txt.                                |
   | If you did not receive a copy of the Zend license and are unable to  |
   | obtain it through the world-wide-web, please send a note to          |
   | license@zend.com so we can mail you a copy immediately.              |
   +----------------------------------------------------------------------+
   | Authors: Bob Weinand <bwoebi@php.net>                                |
   |          Dmitry Stogov <dmitry@zend.com>                             |
   +----------------------------------------------------------------------+
*/

/* $Id$ */

#include "zend_ast.h"
#include "zend_API.h"
#include "zend_operators.h"
#include "zend_language_parser.h"
#include "zend_smart_str.h"
#include "zend_exceptions.h"

ZEND_API zend_ast_process_t zend_ast_process = NULL;

static inline void *zend_ast_alloc(size_t size) {
	return zend_arena_alloc(&CG(ast_arena), size);
}

static inline void *zend_ast_realloc(void *old, size_t old_size, size_t new_size) {
	void *new = zend_ast_alloc(new_size);
	memcpy(new, old, old_size);
	return new;
}

static inline size_t zend_ast_size(uint32_t children) {
	return sizeof(zend_ast) - sizeof(zend_ast *) + sizeof(zend_ast *) * children;
}

static inline size_t zend_ast_list_size(uint32_t children) {
	return sizeof(zend_ast_list) - sizeof(zend_ast *) + sizeof(zend_ast *) * children;
}

ZEND_API zend_ast *zend_ast_create_znode(znode *node) {
	zend_ast_znode *ast;

	ast = zend_ast_alloc(sizeof(zend_ast_znode));
	ast->kind = ZEND_AST_ZNODE;
	ast->attr = 0;
	ast->lineno = CG(zend_lineno);
	ast->node = *node;
	return (zend_ast *) ast;
}

ZEND_API zend_ast *zend_ast_create_zval_with_lineno(zval *zv, zend_ast_attr attr, uint32_t lineno) {
	zend_ast_zval *ast;

	ast = zend_ast_alloc(sizeof(zend_ast_zval));
	ast->kind = ZEND_AST_ZVAL;
	ast->attr = attr;
	ZVAL_COPY_VALUE(&ast->val, zv);
	ast->val.u2.lineno = lineno;
	return (zend_ast *) ast;
}

ZEND_API zend_ast *zend_ast_create_zval_ex(zval *zv, zend_ast_attr attr) {
	return zend_ast_create_zval_with_lineno(zv, attr, CG(zend_lineno));
}

ZEND_API zend_ast *zend_ast_create_decl(
	zend_ast_kind kind, uint32_t flags, uint32_t start_lineno, zend_string *doc_comment,
	zend_string *name, zend_ast *child0, zend_ast *child1, zend_ast *child2, zend_ast *child3
) {
	zend_ast_decl *ast;

	ast = zend_ast_alloc(sizeof(zend_ast_decl));
	ast->kind = kind;
	ast->attr = 0;
	ast->start_lineno = start_lineno;
	ast->end_lineno = CG(zend_lineno);
	ast->flags = flags;
	ast->lex_pos = LANG_SCNG(yy_text);
	ast->doc_comment = doc_comment;
	ast->name = name;
	ast->child[0] = child0;
	ast->child[1] = child1;
	ast->child[2] = child2;
	ast->child[3] = child3;

	return (zend_ast *) ast;
}

static zend_ast *zend_ast_create_from_va_list(zend_ast_kind kind, zend_ast_attr attr, va_list va) {
	uint32_t i, children = kind >> ZEND_AST_NUM_CHILDREN_SHIFT;
	zend_ast *ast;

	ast = zend_ast_alloc(zend_ast_size(children));
	ast->kind = kind;
	ast->attr = attr;
	ast->lineno = (uint32_t) -1;

	for (i = 0; i < children; ++i) {
		ast->child[i] = va_arg(va, zend_ast *);
		if (ast->child[i] != NULL) {
			uint32_t lineno = zend_ast_get_lineno(ast->child[i]);
			if (lineno < ast->lineno) {
				ast->lineno = lineno;
			}
		}
	}

	if (ast->lineno == UINT_MAX) {
		ast->lineno = CG(zend_lineno);
	}

	return ast;
}

ZEND_API zend_ast *zend_ast_create_ex(zend_ast_kind kind, zend_ast_attr attr, ...) {
	va_list va;
	zend_ast *ast;

	va_start(va, attr);
	ast = zend_ast_create_from_va_list(kind, attr, va);
	va_end(va);

	return ast;
}

ZEND_API zend_ast *zend_ast_create(zend_ast_kind kind, ...) {
	va_list va;
	zend_ast *ast;

	va_start(va, kind);
	ast = zend_ast_create_from_va_list(kind, 0, va);
	va_end(va);

	return ast;
}

ZEND_API zend_ast *zend_ast_create_list(uint32_t init_children, zend_ast_kind kind, ...) {
	zend_ast *ast;
	zend_ast_list *list;

	ast = zend_ast_alloc(zend_ast_list_size(4));
	list = (zend_ast_list *) ast;
	list->kind = kind;
	list->attr = 0;
	list->lineno = CG(zend_lineno);
	list->children = 0;

	{
		va_list va;
		uint32_t i;
		va_start(va, kind);
		for (i = 0; i < init_children; ++i) {
			zend_ast *child = va_arg(va, zend_ast *);
			ast = zend_ast_list_add(ast, child);
			if (child != NULL) {
				uint32_t lineno = zend_ast_get_lineno(child);
				if (lineno < ast->lineno) {
					ast->lineno = lineno;
				}
			}
		}
		va_end(va);
	}

	return ast;
}

static inline zend_bool is_power_of_two(uint32_t n) {
	return ((n != 0) && (n == (n & (~n + 1))));
}

ZEND_API zend_ast *zend_ast_list_add(zend_ast *ast, zend_ast *op) {
	zend_ast_list *list = zend_ast_get_list(ast);
	if (list->children >= 4 && is_power_of_two(list->children)) {
			list = zend_ast_realloc(list,
			zend_ast_list_size(list->children), zend_ast_list_size(list->children * 2));
	}
	list->child[list->children++] = op;
	return (zend_ast *) list;
}

static int zend_ast_add_array_element(zval *result, zval *offset, zval *expr)
{
	switch (Z_TYPE_P(offset)) {
		case IS_UNDEF:
			if (!zend_hash_next_index_insert(Z_ARRVAL_P(result), expr)) {
				zend_error(E_WARNING,
					"Cannot add element to the array as the next element is already occupied");
				zval_ptr_dtor(expr);
			}
			break;
		case IS_STRING:
			zend_symtable_update(Z_ARRVAL_P(result), Z_STR_P(offset), expr);
			zval_dtor(offset);
			break;
		case IS_NULL:
			zend_symtable_update(Z_ARRVAL_P(result), ZSTR_EMPTY_ALLOC(), expr);
			break;
		case IS_LONG:
			zend_hash_index_update(Z_ARRVAL_P(result), Z_LVAL_P(offset), expr);
			break;
		case IS_FALSE:
			zend_hash_index_update(Z_ARRVAL_P(result), 0, expr);
			break;
		case IS_TRUE:
			zend_hash_index_update(Z_ARRVAL_P(result), 1, expr);
			break;
		case IS_DOUBLE:
			zend_hash_index_update(Z_ARRVAL_P(result), zend_dval_to_lval(Z_DVAL_P(offset)), expr);
			break;
		case IS_RESOURCE:
			zend_error(E_NOTICE, "Resource ID#%d used as offset, casting to integer (%d)", Z_RES_HANDLE_P(offset), Z_RES_HANDLE_P(offset));
			zend_hash_index_update(Z_ARRVAL_P(result), Z_RES_HANDLE_P(offset), expr);
			break;
		default:
			zend_throw_error(NULL, "Illegal offset type");
			return FAILURE;
 	}
	return SUCCESS;
}

ZEND_API int zend_ast_evaluate(zval *result, zend_ast *ast, zend_class_entry *scope)
{
	zval op1, op2;
	int ret = SUCCESS;

	switch (ast->kind) {
		case ZEND_AST_BINARY_OP:
			if (UNEXPECTED(zend_ast_evaluate(&op1, ast->child[0], scope) != SUCCESS)) {
				ret = FAILURE;
			} else if (UNEXPECTED(zend_ast_evaluate(&op2, ast->child[1], scope) != SUCCESS)) {
				zval_dtor(&op1);
				ret = FAILURE;
			} else {
				binary_op_type op = get_binary_op(ast->attr);
				ret = op(result, &op1, &op2);
				zval_dtor(&op1);
				zval_dtor(&op2);
			}
			break;
		case ZEND_AST_GREATER:
		case ZEND_AST_GREATER_EQUAL:
			if (UNEXPECTED(zend_ast_evaluate(&op1, ast->child[0], scope) != SUCCESS)) {
				ret = FAILURE;
			} else if (UNEXPECTED(zend_ast_evaluate(&op2, ast->child[1], scope) != SUCCESS)) {
				zval_dtor(&op1);
				ret = FAILURE;
			} else {
				/* op1 > op2 is the same as op2 < op1 */
				binary_op_type op = ast->kind == ZEND_AST_GREATER
					? is_smaller_function : is_smaller_or_equal_function;
				ret = op(result, &op2, &op1);
				zval_dtor(&op1);
				zval_dtor(&op2);
			}
			break;
		case ZEND_AST_UNARY_OP:
			if (UNEXPECTED(zend_ast_evaluate(&op1, ast->child[0], scope) != SUCCESS)) {
				ret = FAILURE;
			} else {
				unary_op_type op = get_unary_op(ast->attr);
				ret = op(result, &op1);
				zval_dtor(&op1);
			}
			break;
		case ZEND_AST_ZVAL:
		{
			zval *zv = zend_ast_get_zval(ast);

			if (Z_OPT_CONSTANT_P(zv)) {
				if (!(Z_TYPE_FLAGS_P(zv) & IS_TYPE_IMMUTABLE)) {
					if (UNEXPECTED(zval_update_constant_ex(zv, scope) != SUCCESS)) {
						ret = FAILURE;
						break;
					}
					ZVAL_COPY(result, zv);
				} else {
					ZVAL_COPY_VALUE(result, zv);
					if (UNEXPECTED(zval_update_constant_ex(result, scope) != SUCCESS)) {
						ret = FAILURE;
						break;
					}
				}
			} else {
				ZVAL_COPY(result, zv);
			}
			break;
		}
		case ZEND_AST_AND:
			if (UNEXPECTED(zend_ast_evaluate(&op1, ast->child[0], scope) != SUCCESS)) {
				ret = FAILURE;
				break;
			}
			if (zend_is_true(&op1)) {
				if (UNEXPECTED(zend_ast_evaluate(&op2, ast->child[1], scope) != SUCCESS)) {
					zval_dtor(&op1);
					ret = FAILURE;
					break;
				}
				ZVAL_BOOL(result, zend_is_true(&op2));
				zval_dtor(&op2);
			} else {
				ZVAL_FALSE(result);
			}
			zval_dtor(&op1);
			break;
		case ZEND_AST_OR:
			if (UNEXPECTED(zend_ast_evaluate(&op1, ast->child[0], scope) != SUCCESS)) {
				ret = FAILURE;
				break;
			}
			if (zend_is_true(&op1)) {
				ZVAL_TRUE(result);
			} else {
				if (UNEXPECTED(zend_ast_evaluate(&op2, ast->child[1], scope) != SUCCESS)) {
					zval_dtor(&op1);
					ret = FAILURE;
					break;
				}
				ZVAL_BOOL(result, zend_is_true(&op2));
				zval_dtor(&op2);
			}
			zval_dtor(&op1);
			break;
		case ZEND_AST_CONDITIONAL:
			if (UNEXPECTED(zend_ast_evaluate(&op1, ast->child[0], scope) != SUCCESS)) {
				ret = FAILURE;
				break;
			}
			if (zend_is_true(&op1)) {
				if (!ast->child[1]) {
					*result = op1;
				} else {
					if (UNEXPECTED(zend_ast_evaluate(result, ast->child[1], scope) != SUCCESS)) {
						zval_dtor(&op1);
						ret = FAILURE;
						break;
					}
					zval_dtor(&op1);
				}
			} else {
				if (UNEXPECTED(zend_ast_evaluate(result, ast->child[2], scope) != SUCCESS)) {
					zval_dtor(&op1);
					ret = FAILURE;
					break;
				}
				zval_dtor(&op1);
			}
			break;
		case ZEND_AST_COALESCE:
			if (UNEXPECTED(zend_ast_evaluate(&op1, ast->child[0], scope) != SUCCESS)) {
				ret = FAILURE;
				break;
			}
			if (Z_TYPE(op1) > IS_NULL) {
				*result = op1;
			} else {
				if (UNEXPECTED(zend_ast_evaluate(result, ast->child[1], scope) != SUCCESS)) {
					zval_dtor(&op1);
					ret = FAILURE;
					break;
				}
				zval_dtor(&op1);
			}
			break;
		case ZEND_AST_UNARY_PLUS:
			if (UNEXPECTED(zend_ast_evaluate(&op2, ast->child[0], scope) != SUCCESS)) {
				ret = FAILURE;
			} else {
				ZVAL_LONG(&op1, 0);
				ret = add_function(result, &op1, &op2);
				zval_dtor(&op2);
			}
			break;
		case ZEND_AST_UNARY_MINUS:
			if (UNEXPECTED(zend_ast_evaluate(&op2, ast->child[0], scope) != SUCCESS)) {
				ret = FAILURE;
			} else {
				ZVAL_LONG(&op1, 0);
				ret = sub_function(result, &op1, &op2);
				zval_dtor(&op2);
			}
			break;
		case ZEND_AST_ARRAY:
			array_init(result);
			{
				uint32_t i;
				zend_ast_list *list = zend_ast_get_list(ast);
				for (i = 0; i < list->children; i++) {
					zend_ast *elem = list->child[i];
					if (elem->child[1]) {
						if (UNEXPECTED(zend_ast_evaluate(&op1, elem->child[1], scope) != SUCCESS)) {
							zval_dtor(result);
							return FAILURE;
						}
					} else {
						ZVAL_UNDEF(&op1);
					}
					if (UNEXPECTED(zend_ast_evaluate(&op2, elem->child[0], scope) != SUCCESS)) {
						zval_dtor(&op1);
						zval_dtor(result);
						return FAILURE;
					}
					if (UNEXPECTED(zend_ast_add_array_element(result, &op1, &op2) != SUCCESS)) {
						zval_dtor(&op1);
						zval_dtor(&op2);
						zval_dtor(result);
						return FAILURE;
					}
				}
			}
			break;
		case ZEND_AST_DIM:
			if (ast->child[1] == NULL) {
				zend_error_noreturn(E_COMPILE_ERROR, "Cannot use [] for reading");
			}

			if (UNEXPECTED(zend_ast_evaluate(&op1, ast->child[0], scope) != SUCCESS)) {
				ret = FAILURE;
			} else if (UNEXPECTED(zend_ast_evaluate(&op2, ast->child[1], scope) != SUCCESS)) {
				zval_dtor(&op1);
				ret = FAILURE;
			} else {
				zval tmp;

				if (ast->attr == ZEND_DIM_IS) {
					zend_fetch_dimension_by_zval_is(&tmp, &op1, &op2, IS_CONST);
				} else {
					zend_fetch_dimension_by_zval(&tmp, &op1, &op2);
				}

				if (UNEXPECTED(Z_ISREF(tmp))) {
					ZVAL_DUP(result, Z_REFVAL(tmp));
				} else {
					ZVAL_DUP(result, &tmp);
				}
				zval_ptr_dtor(&tmp);
				zval_dtor(&op1);
				zval_dtor(&op2);
			}
			break;
		default:
			zend_throw_error(NULL, "Unsupported constant expression");
			ret = FAILURE;
	}
	return ret;
}

ZEND_API zend_ast *zend_ast_copy(zend_ast *ast)
{
	if (ast == NULL) {
		return NULL;
	} else if (ast->kind == ZEND_AST_ZVAL) {
		zend_ast_zval *new = emalloc(sizeof(zend_ast_zval));
		new->kind = ZEND_AST_ZVAL;
		new->attr = ast->attr;
		ZVAL_COPY(&new->val, zend_ast_get_zval(ast));
		return (zend_ast *) new;
	} else if (zend_ast_is_list(ast)) {
		zend_ast_list *list = zend_ast_get_list(ast);
		zend_ast_list *new = emalloc(zend_ast_list_size(list->children));
		uint32_t i;
		new->kind = list->kind;
		new->attr = list->attr;
		new->children = list->children;
		for (i = 0; i < list->children; i++) {
			new->child[i] = zend_ast_copy(list->child[i]);
		}
		return (zend_ast *) new;
	} else {
		uint32_t i, children = zend_ast_get_num_children(ast);
		zend_ast *new = emalloc(zend_ast_size(children));
		new->kind = ast->kind;
		new->attr = ast->attr;
		for (i = 0; i < children; i++) {
			new->child[i] = zend_ast_copy(ast->child[i]);
		}
		return new;
	}
}

static void zend_ast_destroy_ex(zend_ast *ast, zend_bool free) {
	if (!ast) {
		return;
	}

	switch (ast->kind) {
		case ZEND_AST_ZVAL:
			/* Destroy value without using GC: When opcache moves arrays into SHM it will
			 * free the zend_array structure, so references to it from outside the op array
			 * become invalid. GC would cause such a reference in the root buffer. */
			zval_ptr_dtor_nogc(zend_ast_get_zval(ast));
			break;
		case ZEND_AST_FUNC_DECL:
		case ZEND_AST_CLOSURE:
		case ZEND_AST_METHOD:
		case ZEND_AST_CLASS:
		{
			zend_ast_decl *decl = (zend_ast_decl *) ast;
			if (decl->name) {
			    zend_string_release(decl->name);
			}
			if (decl->doc_comment) {
				zend_string_release(decl->doc_comment);
			}
			zend_ast_destroy_ex(decl->child[0], free);
			zend_ast_destroy_ex(decl->child[1], free);
			zend_ast_destroy_ex(decl->child[2], free);
			zend_ast_destroy_ex(decl->child[3], free);
			break;
		}
		default:
			if (zend_ast_is_list(ast)) {
				zend_ast_list *list = zend_ast_get_list(ast);
				uint32_t i;
				for (i = 0; i < list->children; i++) {
					zend_ast_destroy_ex(list->child[i], free);
				}
			} else {
				uint32_t i, children = zend_ast_get_num_children(ast);
				for (i = 0; i < children; i++) {
					zend_ast_destroy_ex(ast->child[i], free);
				}
			}
	}

	if (free) {
		efree(ast);
	}
}

ZEND_API void zend_ast_destroy(zend_ast *ast) {
	zend_ast_destroy_ex(ast, 0);
}
ZEND_API void zend_ast_destroy_and_free(zend_ast *ast) {
	zend_ast_destroy_ex(ast, 1);
}

ZEND_API void zend_ast_apply(zend_ast *ast, zend_ast_apply_func fn) {
	if (zend_ast_is_list(ast)) {
		zend_ast_list *list = zend_ast_get_list(ast);
		uint32_t i;
		for (i = 0; i < list->children; ++i) {
			fn(&list->child[i]);
		}
	} else {
		uint32_t i, children = zend_ast_get_num_children(ast);
		for (i = 0; i < children; ++i) {
			fn(&ast->child[i]);
		}
	}
}

/*
 * Operator Precendence
 * ====================
 * priority  associativity  operators
 * ----------------------------------
 *   10     left            include, include_once, eval, require, require_once
 *   20     left            ,
 *   30     left            or
 *   40     left            xor
 *   50     left            and
 *   60     right           print
 *   70     right           yield
 *   80     right           =>
 *   85     right           yield from
 *   90     right           = += -= *= /= .= %= &= |= ^= <<= >>= **=
 *  100     left            ? :
 *  110     right           ??
 *  120     left            ||
 *  130     left            &&
 *  140     left            |
 *  150     left            ^
 *  160     left            &
 *  170     non-associative == != === !==
 *  180     non-associative < <= > >= <=>
 *  190     left            << >>
 *  200     left            + - .
 *  210     left            * / %
 *  220     right           !
 *  230     non-associative instanceof
 *  240     right           + - ++ -- ~ (type) @
 *  250     right           **
 *  260     left            [
 *  270     non-associative clone new
 */

static void zend_ast_export_ex(smart_str *str, zend_ast *ast, int priority, int indent);

static void zend_ast_export_str(smart_str *str, zend_string *s)
{
	size_t i;

	for (i = 0; i < ZSTR_LEN(s); i++) {
		unsigned char c = ZSTR_VAL(s)[i];
		if (c == '\'' || c == '\\') {
			smart_str_appendc(str, '\\');
			smart_str_appendc(str, c);
		} else {
			smart_str_appendc(str, c);
		}
	}
}

static void zend_ast_export_qstr(smart_str *str, char quote, zend_string *s)
{
	size_t i;

	for (i = 0; i < ZSTR_LEN(s); i++) {
		unsigned char c = ZSTR_VAL(s)[i];
		if (c < ' ') {
			switch (c) {
				case '\n':
					smart_str_appends(str, "\\n");
					break;
				case '\r':
					smart_str_appends(str, "\\r");
					break;
				case '\t':
					smart_str_appends(str, "\\t");
					break;
				case '\f':
					smart_str_appends(str, "\\f");
					break;
				case '\v':
					smart_str_appends(str, "\\v");
					break;
#ifdef ZEND_WIN32
				case VK_ESCAPE:
#else
				case '\e':
#endif
					smart_str_appends(str, "\\e");
					break;
				default:
					smart_str_appends(str, "\\0");
					smart_str_appendc(str, '0' + (c / 8));
					smart_str_appendc(str, '0' + (c % 8));
					break;
			}
		} else {
			if (c == quote || c == '$' || c == '\\') {
				smart_str_appendc(str, '\\');
			}
			smart_str_appendc(str, c);
		}
	}
}

static void zend_ast_export_indent(smart_str *str, int indent)
{
	while (indent > 0) {
		smart_str_appends(str, "    ");
		indent--;
	}
}

static void zend_ast_export_name(smart_str *str, zend_ast *ast, int priority, int indent)
{
	if (ast->kind == ZEND_AST_ZVAL) {
		zval *zv = zend_ast_get_zval(ast);

		if (Z_TYPE_P(zv) == IS_STRING) {
			smart_str_append(str, Z_STR_P(zv));
			return;
		}
	}
	zend_ast_export_ex(str, ast, priority, indent);
}

static void zend_ast_export_ns_name(smart_str *str, zend_ast *ast, int priority, int indent)
{
	if (ast->kind == ZEND_AST_ZVAL) {
		zval *zv = zend_ast_get_zval(ast);

		if (Z_TYPE_P(zv) == IS_STRING) {
		    if (ast->attr == ZEND_NAME_FQ) {
				smart_str_appendc(str, '\\');
		    } else if (ast->attr == ZEND_NAME_RELATIVE) {
				smart_str_appends(str, "namespace\\");
		    }
			smart_str_append(str, Z_STR_P(zv));
			return;
		}
	}
	zend_ast_export_ex(str, ast, priority, indent);
}

static int zend_ast_valid_var_char(char ch)
{
	unsigned char c = (unsigned char)ch;

	if (c != '_' && c < 127 &&
	    (c < '0' || c > '9') &&
	    (c < 'A' || c > 'Z') &&
	    (c < 'a' || c > 'z')) {
		return 0;
	}
	return 1;
}

static int zend_ast_valid_var_name(const char *s, size_t len)
{
	unsigned char c;
	size_t i;

	if (len == 0) {
		return 0;
	}
	c = (unsigned char)s[0];
	if (c != '_' && c < 127 &&
	    (c < 'A' || c > 'Z') &&
	    (c < 'a' || c > 'z')) {
		return 0;
	}
	for (i = 1; i < len; i++) {
		c = (unsigned char)s[i];
		if (c != '_' && c < 127 &&
		    (c < '0' || c > '9') &&
		    (c < 'A' || c > 'Z') &&
		    (c < 'a' || c > 'z')) {
			return 0;
		}
	}
	return 1;
}

static void zend_ast_export_var(smart_str *str, zend_ast *ast, int priority, int indent)
{
	if (ast->kind == ZEND_AST_ZVAL) {
		zval *zv = zend_ast_get_zval(ast);
		if (Z_TYPE_P(zv) == IS_STRING &&
		    zend_ast_valid_var_name(Z_STRVAL_P(zv), Z_STRLEN_P(zv))) {
			smart_str_append(str, Z_STR_P(zv));
			return;
		}
	} else if (ast->kind == ZEND_AST_VAR) {
		zend_ast_export_ex(str, ast, 0, indent);
		return;
	}
	smart_str_appendc(str, '{');
	zend_ast_export_name(str, ast, 0, indent);
	smart_str_appendc(str, '}');
}

static void zend_ast_export_list(smart_str *str, zend_ast_list *list, int separator, int priority, int indent)
{
	uint32_t i = 0;

	while (i < list->children) {
		if (i != 0 && separator) {
			smart_str_appends(str, ", ");
		}
		zend_ast_export_ex(str, list->child[i], priority, indent);
		i++;
	}
}

static void zend_ast_export_encaps_list(smart_str *str, char quote, zend_ast_list *list, int indent)
{
	uint32_t i = 0;
	zend_ast *ast;

	while (i < list->children) {
		ast = list->child[i];
		if (ast->kind == ZEND_AST_ZVAL) {
			zval *zv = zend_ast_get_zval(ast);

			ZEND_ASSERT(Z_TYPE_P(zv) == IS_STRING);
			zend_ast_export_qstr(str, quote, Z_STR_P(zv));
		} else if (ast->kind == ZEND_AST_VAR &&
		           ast->child[0]->kind == ZEND_AST_ZVAL &&
		           (i + 1 == list->children ||
		            list->child[i + 1]->kind != ZEND_AST_ZVAL ||
		            !zend_ast_valid_var_char(
		                *Z_STRVAL_P(
		                    zend_ast_get_zval(list->child[i + 1]))))) {
			zend_ast_export_ex(str, ast, 0, indent);
		} else {
			smart_str_appendc(str, '{');
			zend_ast_export_ex(str, ast, 0, indent);
			smart_str_appendc(str, '}');
		}
		i++;
	}
}

static void zend_ast_export_name_list_ex(smart_str *str, zend_ast_list *list, int indent, const char *separator)
{
	uint32_t i = 0;

	while (i < list->children) {
		if (i != 0) {
			smart_str_appends(str, separator);
		}
		zend_ast_export_name(str, list->child[i], 0, indent);
		i++;
	}
}

#define zend_ast_export_name_list(s, l, i) zend_ast_export_name_list_ex(s, l, i, ", ")
#define zend_ast_export_catch_name_list(s, l, i) zend_ast_export_name_list_ex(s, l, i, "|")

static void zend_ast_export_var_list(smart_str *str, zend_ast_list *list, int indent)
{
	uint32_t i = 0;

	while (i < list->children) {
		if (i != 0) {
			smart_str_appends(str, ", ");
		}
		if (list->child[i]->attr) {
			smart_str_appendc(str, '&');
		}
		smart_str_appendc(str, '$');
		zend_ast_export_name(str, list->child[i], 20, indent);
		i++;
	}
}

static void zend_ast_export_stmt(smart_str *str, zend_ast *ast, int indent)
{
	if (!ast) {
		return;
	}

	if (ast->kind == ZEND_AST_STMT_LIST ||
	    ast->kind == ZEND_AST_TRAIT_ADAPTATIONS) {
		zend_ast_list *list = (zend_ast_list*)ast;
		uint32_t i = 0;

		while (i < list->children) {
			ast = list->child[i];
			zend_ast_export_stmt(str, ast, indent);
			i++;
		}
	} else {
		zend_ast_export_indent(str, indent);
		zend_ast_export_ex(str, ast, 0, indent);
		switch (ast->kind) {
			case ZEND_AST_LABEL:
			case ZEND_AST_IF:
			case ZEND_AST_SWITCH:
			case ZEND_AST_WHILE:
			case ZEND_AST_TRY:
			case ZEND_AST_FOR:
			case ZEND_AST_FOREACH:
			case ZEND_AST_FUNC_DECL:
			case ZEND_AST_METHOD:
			case ZEND_AST_CLASS:
			case ZEND_AST_USE_TRAIT:
			case ZEND_AST_NAMESPACE:
			case ZEND_AST_DECLARE:
				break;
			default:
				smart_str_appendc(str, ';');
				break;
		}
		smart_str_appendc(str, '\n');
	}
}

static void zend_ast_export_if_stmt(smart_str *str, zend_ast_list *list, int indent)
{
	uint32_t i;
	zend_ast *ast;

tail_call:
	i = 0;
	while (i < list->children) {
		ast = list->child[i];
		ZEND_ASSERT(ast->kind == ZEND_AST_IF_ELEM);
		if (ast->child[0]) {
			if (i == 0) {
				smart_str_appends(str, "if (");
			} else {
				zend_ast_export_indent(str, indent);
				smart_str_appends(str, "} elseif (");
			}
			zend_ast_export_ex(str, ast->child[0], 0, indent);
			smart_str_appends(str, ") {\n");
			zend_ast_export_stmt(str, ast->child[1], indent + 1);
		} else {
			zend_ast_export_indent(str, indent);
			smart_str_appends(str, "} else ");
			if (ast->child[1]->kind == ZEND_AST_IF) {
				list = (zend_ast_list*)ast->child[1];
				goto tail_call;
			} else {
				smart_str_appends(str, "{\n");
				zend_ast_export_stmt(str, ast->child[1], indent + 1);
			}
		}
		i++;
	}
	zend_ast_export_indent(str, indent);
	smart_str_appendc(str, '}');
}

static void zend_ast_export_zval(smart_str *str, zval *zv, int priority, int indent)
{
	zend_long idx;
	zend_string *key;
	zval *val;
	int first;

	ZVAL_DEREF(zv);
	switch (Z_TYPE_P(zv)) {
		case IS_NULL:
			smart_str_appends(str, "null");
			break;
		case IS_FALSE:
			smart_str_appends(str, "false");
			break;
		case IS_TRUE:
			smart_str_appends(str, "true");
			break;
		case IS_LONG:
			smart_str_append_long(str, Z_LVAL_P(zv));
			break;
		case IS_DOUBLE:
			key = zend_strpprintf(0, "%.*G", (int) EG(precision), Z_DVAL_P(zv));
			smart_str_appendl(str, ZSTR_VAL(key), ZSTR_LEN(key));
			zend_string_release(key);
			break;
		case IS_STRING:
			smart_str_appendc(str, '\'');
			zend_ast_export_str(str, Z_STR_P(zv));
			smart_str_appendc(str, '\'');
			break;
		case IS_ARRAY:
			smart_str_appendc(str, '[');
			first = 1;
			ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(zv), idx, key, val) {
				if (first) {
					first = 0;
				} else {
					smart_str_appends(str, ", ");
				}
				if (key) {
					smart_str_appendc(str, '\'');
					zend_ast_export_str(str, key);
					smart_str_appends(str, "' => ");
				} else {
					smart_str_append_long(str, idx);
					smart_str_appends(str, " => ");
				}
				zend_ast_export_zval(str, val, 0, indent);
			} ZEND_HASH_FOREACH_END();
			smart_str_appendc(str, ']');
			break;
		case IS_CONSTANT:
			smart_str_appendl(str, Z_STRVAL_P(zv), Z_STRLEN_P(zv));
			break;
		case IS_CONSTANT_AST:
			zend_ast_export_ex(str, Z_ASTVAL_P(zv), priority, indent);
			break;
		EMPTY_SWITCH_DEFAULT_CASE();
	}
}

static void zend_ast_export_class_no_header(smart_str *str, zend_ast_decl *decl, int indent) {
	if (decl->child[0]) {
		smart_str_appends(str, " extends ");
		zend_ast_export_ns_name(str, decl->child[0], 0, indent);
	}
	if (decl->child[1]) {
		smart_str_appends(str, " implements ");
		zend_ast_export_ex(str, decl->child[1], 0, indent);
	}
	smart_str_appends(str, " {\n");
	zend_ast_export_stmt(str, decl->child[2], indent + 1);
	zend_ast_export_indent(str, indent);
	smart_str_appends(str, "}");
}

#define BINARY_OP(_op, _p, _pl, _pr) do { \
		op = _op; \
		p = _p; \
		pl = _pl; \
		pr = _pr; \
		goto binary_op; \
	} while (0)

#define PREFIX_OP(_op, _p, _pl) do { \
		op = _op; \
		p = _p; \
		pl = _pl; \
		goto prefix_op; \
	} while (0)

#define FUNC_OP(_op) do { \
		op = _op; \
		goto func_op; \
	} while (0)

#define POSTFIX_OP(_op, _p, _pl) do { \
		op = _op; \
		p = _p; \
		pl = _pl; \
		goto postfix_op; \
	} while (0)

#define APPEND_NODE_1(_op) do { \
		op = _op; \
		goto append_node_1; \
	} while (0)

#define APPEND_STR(_op) do { \
		op = _op; \
		goto append_str; \
	} while (0)

#define APPEND_DEFAULT_VALUE(n) do { \
		p = n; \
		goto append_default_value; \
	} while (0)

static void zend_ast_export_ex(smart_str *str, zend_ast *ast, int priority, int indent)
{
	zend_ast_decl *decl;
	int p, pl, pr;
	const char *op;

tail_call:
	if (!ast) {
		return;
	}
	switch (ast->kind) {
		/* special nodes */
		case ZEND_AST_ZVAL:
			zend_ast_export_zval(str, zend_ast_get_zval(ast), priority, indent);
			break;
		case ZEND_AST_ZNODE:
			/* This AST kind is only used for temporary nodes during compilation */
			ZEND_ASSERT(0);
			break;

		/* declaration nodes */
		case ZEND_AST_FUNC_DECL:
		case ZEND_AST_CLOSURE:
		case ZEND_AST_METHOD:
			decl = (zend_ast_decl *) ast;
			if (decl->flags & ZEND_ACC_PUBLIC) {
				smart_str_appends(str, "public ");
			} else if (decl->flags & ZEND_ACC_PROTECTED) {
				smart_str_appends(str, "protected ");
			} else if (decl->flags & ZEND_ACC_PRIVATE) {
				smart_str_appends(str, "private ");
			}
			if (decl->flags & ZEND_ACC_STATIC) {
				smart_str_appends(str, "static ");
			}
			if (decl->flags & ZEND_ACC_ABSTRACT) {
				smart_str_appends(str, "abstract ");
			}
			if (decl->flags & ZEND_ACC_FINAL) {
				smart_str_appends(str, "final ");
			}
			smart_str_appends(str, "function ");
			if (decl->flags & ZEND_ACC_RETURN_REFERENCE) {
				smart_str_appendc(str, '&');
			}
			if (ast->kind != ZEND_AST_CLOSURE) {
				smart_str_appendl(str, ZSTR_VAL(decl->name), ZSTR_LEN(decl->name));
			}
			smart_str_appendc(str, '(');
			zend_ast_export_ex(str, decl->child[0], 0, indent);
			smart_str_appendc(str, ')');
			zend_ast_export_ex(str, decl->child[1], 0, indent);
			if (decl->child[3]) {
				smart_str_appends(str, ": ");
				if (decl->child[3]->attr & ZEND_TYPE_NULLABLE) {
					smart_str_appendc(str, '?');
				}
				zend_ast_export_ns_name(str, decl->child[3], 0, indent);
			}
			if (decl->child[2]) {
				smart_str_appends(str, " {\n");
				zend_ast_export_stmt(str, decl->child[2], indent + 1);
				zend_ast_export_indent(str, indent);
				smart_str_appendc(str, '}');
				if (ast->kind != ZEND_AST_CLOSURE) {
					smart_str_appendc(str, '\n');
				}
			} else {
				smart_str_appends(str, ";\n");
			}
			break;
		case ZEND_AST_CLASS:
			decl = (zend_ast_decl *) ast;
			if (decl->flags & ZEND_ACC_INTERFACE) {
				smart_str_appends(str, "interface ");
			} else if (decl->flags & ZEND_ACC_TRAIT) {
				smart_str_appends(str, "trait ");
			} else {
				if (decl->flags & ZEND_ACC_EXPLICIT_ABSTRACT_CLASS) {
					smart_str_appends(str, "abstract ");
				}
				if (decl->flags & ZEND_ACC_FINAL) {
					smart_str_appends(str, "final ");
				}
				smart_str_appends(str, "class ");
			}
			smart_str_appendl(str, ZSTR_VAL(decl->name), ZSTR_LEN(decl->name));
			zend_ast_export_class_no_header(str, decl, indent);
			smart_str_appendc(str, '\n');
			break;

		/* list nodes */
		case ZEND_AST_ARG_LIST:
		case ZEND_AST_EXPR_LIST:
		case ZEND_AST_PARAM_LIST:
simple_list:
			zend_ast_export_list(str, (zend_ast_list*)ast, 1, 20, indent);
			break;
		case ZEND_AST_ARRAY:
			smart_str_appendc(str, '[');
			zend_ast_export_list(str, (zend_ast_list*)ast, 1, 20, indent);
			smart_str_appendc(str, ']');
			break;
		case ZEND_AST_ENCAPS_LIST:
			smart_str_appendc(str, '"');
			zend_ast_export_encaps_list(str, '"', (zend_ast_list*)ast, indent);
			smart_str_appendc(str, '"');
			break;
		case ZEND_AST_STMT_LIST:
		case ZEND_AST_TRAIT_ADAPTATIONS:
			zend_ast_export_stmt(str, ast, indent);
			break;
		case ZEND_AST_IF:
			zend_ast_export_if_stmt(str, (zend_ast_list*)ast, indent);
			break;
		case ZEND_AST_SWITCH_LIST:
		case ZEND_AST_CATCH_LIST:
			zend_ast_export_list(str, (zend_ast_list*)ast, 0, 0, indent);
			break;
		case ZEND_AST_CLOSURE_USES:
			smart_str_appends(str, " use(");
			zend_ast_export_var_list(str, (zend_ast_list*)ast, indent);
			smart_str_appendc(str, ')');
			break;
		case ZEND_AST_PROP_DECL:
			if (ast->attr & ZEND_ACC_PUBLIC) {
				smart_str_appends(str, "public ");
			} else if (ast->attr & ZEND_ACC_PROTECTED) {
				smart_str_appends(str, "protected ");
			} else if (ast->attr & ZEND_ACC_PRIVATE) {
				smart_str_appends(str, "private ");
			}
			if (ast->attr & ZEND_ACC_STATIC) {
				smart_str_appends(str, "static ");
			}
			goto simple_list;
		case ZEND_AST_CONST_DECL:
		case ZEND_AST_CLASS_CONST_DECL:
			smart_str_appends(str, "const ");
			goto simple_list;
		case ZEND_AST_NAME_LIST:
			zend_ast_export_name_list(str, (zend_ast_list*)ast, indent);
			break;
		case ZEND_AST_USE:
			smart_str_appends(str, "use ");
			if (ast->attr == T_FUNCTION) {
				smart_str_appends(str, "function ");
			} else if (ast->attr == T_CONST) {
				smart_str_appends(str, "const ");
			}
			goto simple_list;

		/* 0 child nodes */
		case ZEND_AST_MAGIC_CONST:
			switch (ast->attr) {
				case T_LINE:     APPEND_STR("__LINE__");
				case T_FILE:     APPEND_STR("__FILE__");
				case T_DIR:      APPEND_STR("__DIR__");
				case T_TRAIT_C:  APPEND_STR("__TRAIT__");
				case T_METHOD_C: APPEND_STR("__METHOD__");
				case T_FUNC_C:   APPEND_STR("__FUNCTION__");
				case T_NS_C:     APPEND_STR("__NAMESPACE__");
				case T_CLASS_C:  APPEND_STR("__CLASS__");
				EMPTY_SWITCH_DEFAULT_CASE();
			}
			break;
		case ZEND_AST_TYPE:
			switch (ast->attr) {
				case IS_ARRAY:    APPEND_STR("array");
				case IS_CALLABLE: APPEND_STR("callable");
				EMPTY_SWITCH_DEFAULT_CASE();
			}
			break;

		/* 1 child node */
		case ZEND_AST_VAR:
			smart_str_appendc(str, '$');
			zend_ast_export_var(str, ast->child[0], 0, indent);
			break;
		case ZEND_AST_CONST:
			zend_ast_export_ns_name(str, ast->child[0], 0, indent);
			break;
		case ZEND_AST_UNPACK:
			smart_str_appends(str, "...");
			ast = ast->child[0];
			goto tail_call;
		case ZEND_AST_UNARY_PLUS:  PREFIX_OP("+", 240, 241);
		case ZEND_AST_UNARY_MINUS: PREFIX_OP("-", 240, 241);
		case ZEND_AST_CAST:
			switch (ast->attr) {
				case IS_NULL:      PREFIX_OP("(unset)",  240, 241);
				case _IS_BOOL:     PREFIX_OP("(bool)",   240, 241);
				case IS_LONG:      PREFIX_OP("(int)",    240, 241);
				case IS_DOUBLE:    PREFIX_OP("(double)", 240, 241);
				case IS_STRING:    PREFIX_OP("(string)", 240, 241);
				case IS_ARRAY:     PREFIX_OP("(array)",  240, 241);
				case IS_OBJECT:    PREFIX_OP("(object)", 240, 241);
				EMPTY_SWITCH_DEFAULT_CASE();
			}
			break;
		case ZEND_AST_EMPTY:
			FUNC_OP("empty");
		case ZEND_AST_ISSET:
			FUNC_OP("isset");
		case ZEND_AST_SILENCE:
			PREFIX_OP("@", 240, 241);
		case ZEND_AST_SHELL_EXEC:
			smart_str_appendc(str, '`');
			if (ast->child[0]->kind == ZEND_AST_ENCAPS_LIST) {
				zend_ast_export_encaps_list(str, '`', (zend_ast_list*)ast->child[0], indent);
			} else {
				zval *zv;
				ZEND_ASSERT(ast->child[0]->kind == ZEND_AST_ZVAL);
				zv = zend_ast_get_zval(ast->child[0]);
				ZEND_ASSERT(Z_TYPE_P(zv) == IS_STRING);
				zend_ast_export_qstr(str, '`', Z_STR_P(zv));
			}
			smart_str_appendc(str, '`');
			break;
		case ZEND_AST_CLONE:
			PREFIX_OP("clone ", 270, 271);
		case ZEND_AST_EXIT:
			if (ast->child[0]) {
				FUNC_OP("exit");
			} else {
				APPEND_STR("exit");
			}
			break;
		case ZEND_AST_PRINT:
			PREFIX_OP("print ", 60, 61);
		case ZEND_AST_INCLUDE_OR_EVAL:
			switch (ast->attr) {
				case ZEND_INCLUDE_ONCE: FUNC_OP("include_once");
				case ZEND_INCLUDE:      FUNC_OP("include");
				case ZEND_REQUIRE_ONCE: FUNC_OP("require_once");
				case ZEND_REQUIRE:      FUNC_OP("require");
				case ZEND_EVAL:         FUNC_OP("eval");
				EMPTY_SWITCH_DEFAULT_CASE();
			}
			break;
		case ZEND_AST_UNARY_OP:
			switch (ast->attr) {
				case ZEND_BW_NOT:   PREFIX_OP("~", 240, 241);
				case ZEND_BOOL_NOT: PREFIX_OP("!", 240, 241);
				EMPTY_SWITCH_DEFAULT_CASE();
			}
			break;
		case ZEND_AST_PRE_INC:
			PREFIX_OP("++", 240, 241);
		case ZEND_AST_PRE_DEC:
			PREFIX_OP("--", 240, 241);
		case ZEND_AST_POST_INC:
			POSTFIX_OP("++", 240, 241);
		case ZEND_AST_POST_DEC:
			POSTFIX_OP("--", 240, 241);

		case ZEND_AST_GLOBAL:
			APPEND_NODE_1("global");
		case ZEND_AST_UNSET:
			FUNC_OP("unset");
		case ZEND_AST_RETURN:
			APPEND_NODE_1("return");
		case ZEND_AST_LABEL:
			zend_ast_export_name(str, ast->child[0], 0, indent);
			smart_str_appendc(str, ':');
			break;
		case ZEND_AST_REF:
			smart_str_appendc(str, '&');
			ast = ast->child[0];
			goto tail_call;
		case ZEND_AST_HALT_COMPILER:
			APPEND_STR("__HALT_COMPILER()");
		case ZEND_AST_ECHO:
			APPEND_NODE_1("echo");
		case ZEND_AST_THROW:
			APPEND_NODE_1("throw");
		case ZEND_AST_GOTO:
			smart_str_appends(str, "goto ");
			zend_ast_export_name(str, ast->child[0], 0, indent);
			break;
		case ZEND_AST_BREAK:
			APPEND_NODE_1("break");
		case ZEND_AST_CONTINUE:
			APPEND_NODE_1("continue");

		/* 2 child nodes */
		case ZEND_AST_DIM:
			zend_ast_export_ex(str, ast->child[0], 260, indent);
			smart_str_appendc(str, '[');
			if (ast->child[1]) {
				zend_ast_export_ex(str, ast->child[1], 0, indent);
			}
			smart_str_appendc(str, ']');
			break;
		case ZEND_AST_PROP:
			zend_ast_export_ex(str, ast->child[0], 0, indent);
			smart_str_appends(str, "->");
			zend_ast_export_var(str, ast->child[1], 0, indent);
			break;
		case ZEND_AST_STATIC_PROP:
			zend_ast_export_ns_name(str, ast->child[0], 0, indent);
			smart_str_appends(str, "::$");
			zend_ast_export_var(str, ast->child[1], 0, indent);
			break;
		case ZEND_AST_CALL:
			zend_ast_export_ns_name(str, ast->child[0], 0, indent);
			smart_str_appendc(str, '(');
			zend_ast_export_ex(str, ast->child[1], 0, indent);
			smart_str_appendc(str, ')');
			break;
		case ZEND_AST_CLASS_CONST:
			zend_ast_export_ns_name(str, ast->child[0], 0, indent);
			smart_str_appends(str, "::");
			zend_ast_export_name(str, ast->child[1], 0, indent);
			break;
		case ZEND_AST_ASSIGN:            BINARY_OP(" = ",   90, 91, 90);
		case ZEND_AST_ASSIGN_REF:        BINARY_OP(" =& ",  90, 91, 90);
		case ZEND_AST_ASSIGN_OP:
			switch (ast->attr) {
				case ZEND_ASSIGN_ADD:    BINARY_OP(" += ",  90, 91, 90);
				case ZEND_ASSIGN_SUB:    BINARY_OP(" -= ",  90, 91, 90);
				case ZEND_ASSIGN_MUL:    BINARY_OP(" *= ",  90, 91, 90);
				case ZEND_ASSIGN_DIV:    BINARY_OP(" /= ",  90, 91, 90);
				case ZEND_ASSIGN_MOD:    BINARY_OP(" %= ",  90, 91, 90);
				case ZEND_ASSIGN_SL:     BINARY_OP(" <<= ", 90, 91, 90);
				case ZEND_ASSIGN_SR:     BINARY_OP(" >>= ", 90, 91, 90);
				case ZEND_ASSIGN_CONCAT: BINARY_OP(" .= ",  90, 91, 90);
				case ZEND_ASSIGN_BW_OR:  BINARY_OP(" |= ",  90, 91, 90);
				case ZEND_ASSIGN_BW_AND: BINARY_OP(" &= ",  90, 91, 90);
				case ZEND_ASSIGN_BW_XOR: BINARY_OP(" ^= ",  90, 91, 90);
				case ZEND_ASSIGN_POW:    BINARY_OP(" **= ", 90, 91, 90);
				EMPTY_SWITCH_DEFAULT_CASE();
			}
			break;
		case ZEND_AST_BINARY_OP:
			switch (ast->attr) {
				case ZEND_ADD:                 BINARY_OP(" + ",   200, 200, 201);
				case ZEND_SUB:                 BINARY_OP(" - ",   200, 200, 201);
				case ZEND_MUL:                 BINARY_OP(" * ",   210, 210, 211);
				case ZEND_DIV:                 BINARY_OP(" / ",   210, 210, 211);
				case ZEND_MOD:                 BINARY_OP(" % ",   210, 210, 211);
				case ZEND_SL:                  BINARY_OP(" << ",  190, 190, 191);
				case ZEND_SR:                  BINARY_OP(" >> ",  190, 190, 191);
				case ZEND_CONCAT:              BINARY_OP(" . ",   200, 200, 201);
				case ZEND_BW_OR:               BINARY_OP(" | ",   140, 140, 141);
				case ZEND_BW_AND:              BINARY_OP(" & ",   160, 160, 161);
				case ZEND_BW_XOR:              BINARY_OP(" ^ ",   150, 150, 151);
				case ZEND_IS_IDENTICAL:        BINARY_OP(" === ", 170, 171, 171);
				case ZEND_IS_NOT_IDENTICAL:    BINARY_OP(" !== ", 170, 171, 171);
				case ZEND_IS_EQUAL:            BINARY_OP(" == ",  170, 171, 171);
				case ZEND_IS_NOT_EQUAL:        BINARY_OP(" != ",  170, 171, 171);
				case ZEND_IS_SMALLER:          BINARY_OP(" < ",   180, 181, 181);
				case ZEND_IS_SMALLER_OR_EQUAL: BINARY_OP(" <= ",  180, 181, 181);
				case ZEND_POW:                 BINARY_OP(" ** ",  250, 251, 250);
				case ZEND_BOOL_XOR:            BINARY_OP(" xor ",  40,  40,  41);
				case ZEND_SPACESHIP:           BINARY_OP(" <=> ", 180, 181, 181);
				EMPTY_SWITCH_DEFAULT_CASE();
			}
			break;
		case ZEND_AST_GREATER:                 BINARY_OP(" > ",   180, 181, 181);
		case ZEND_AST_GREATER_EQUAL:           BINARY_OP(" >= ",  180, 181, 181);
		case ZEND_AST_AND:                     BINARY_OP(" && ",  130, 130, 131);
		case ZEND_AST_OR:                      BINARY_OP(" || ",  120, 120, 121);
		case ZEND_AST_ARRAY_ELEM:
			if (ast->child[1]) {
				zend_ast_export_ex(str, ast->child[1], 80, indent);
				smart_str_appends(str, " => ");
			}
			zend_ast_export_ex(str, ast->child[0], 80, indent);
			break;
		case ZEND_AST_NEW:
			smart_str_appends(str, "new ");
			if (ast->child[0]->kind == ZEND_AST_CLASS) {
				smart_str_appends(str, "class");
				if (zend_ast_get_list(ast->child[1])->children) {
					smart_str_appendc(str, '(');
					zend_ast_export_ex(str, ast->child[1], 0, indent);
					smart_str_appendc(str, ')');
				}
				zend_ast_export_class_no_header(str, (zend_ast_decl *) ast->child[0], indent);
			} else {
				zend_ast_export_ns_name(str, ast->child[0], 0, indent);
				smart_str_appendc(str, '(');
				zend_ast_export_ex(str, ast->child[1], 0, indent);
				smart_str_appendc(str, ')');
			}
			break;
		case ZEND_AST_INSTANCEOF:
			zend_ast_export_ex(str, ast->child[0], 0, indent);
			smart_str_appends(str, " instanceof ");
			zend_ast_export_ns_name(str, ast->child[1], 0, indent);
			break;
		case ZEND_AST_YIELD:
			if (priority > 70) smart_str_appendc(str, '(');
			smart_str_appends(str, "yield ");
			if (ast->child[0]) {
				if (ast->child[1]) {
					zend_ast_export_ex(str, ast->child[1], 70, indent);
					smart_str_appends(str, " => ");
				}
				zend_ast_export_ex(str, ast->child[0], 70, indent);
			}
			if (priority > 70) smart_str_appendc(str, ')');
			break;
		case ZEND_AST_YIELD_FROM:
			PREFIX_OP("yield from ", 85, 86);
		case ZEND_AST_COALESCE: BINARY_OP(" ?? ", 110, 111, 110);
		case ZEND_AST_STATIC:
			smart_str_appends(str, "static $");
			zend_ast_export_name(str, ast->child[0], 0, indent);
			APPEND_DEFAULT_VALUE(1);
		case ZEND_AST_WHILE:
			smart_str_appends(str, "while (");
			zend_ast_export_ex(str, ast->child[0], 0, indent);
			smart_str_appends(str, ") {\n");
			zend_ast_export_stmt(str, ast->child[1], indent + 1);
			zend_ast_export_indent(str, indent);
			smart_str_appendc(str, '}');
			break;
		case ZEND_AST_DO_WHILE:
			smart_str_appends(str, "do {\n");
			zend_ast_export_stmt(str, ast->child[0], indent + 1);
			zend_ast_export_indent(str, indent);
			smart_str_appends(str, "} while (");
			zend_ast_export_ex(str, ast->child[1], 0, indent);
			smart_str_appendc(str, ')');
			break;

		case ZEND_AST_IF_ELEM:
			if (ast->child[0]) {
				smart_str_appends(str, "if (");
				zend_ast_export_ex(str, ast->child[0], 0, indent);
				smart_str_appends(str, ") {\n");
				zend_ast_export_stmt(str, ast->child[1], indent + 1);
			} else {
				smart_str_appends(str, "else {\n");
				zend_ast_export_stmt(str, ast->child[1], indent + 1);
			}
			zend_ast_export_indent(str, indent);
			smart_str_appendc(str, '}');
			break;
		case ZEND_AST_SWITCH:
			smart_str_appends(str, "switch (");
			zend_ast_export_ex(str, ast->child[0], 0, indent);
			smart_str_appends(str, ") {\n");
			zend_ast_export_ex(str, ast->child[1], 0, indent + 1);
			zend_ast_export_indent(str, indent);
			smart_str_appendc(str, '}');
			break;
		case ZEND_AST_SWITCH_CASE:
			zend_ast_export_indent(str, indent);
			if (ast->child[0]) {
				smart_str_appends(str, "case ");
				zend_ast_export_ex(str, ast->child[0], 0, indent);
				smart_str_appends(str, ":\n");
			} else {
				smart_str_appends(str, "default:\n");
			}
			zend_ast_export_stmt(str, ast->child[1], indent + 1);
			break;
		case ZEND_AST_DECLARE:
			smart_str_appends(str, "declare(");
			ZEND_ASSERT(ast->child[0]->kind == ZEND_AST_CONST_DECL);
			zend_ast_export_list(str, (zend_ast_list*)ast->child[0], 1, 0, indent);
			smart_str_appendc(str, ')');
			if (ast->child[1]) {
				smart_str_appends(str, " {\n");
				zend_ast_export_stmt(str, ast->child[1], indent + 1);
				zend_ast_export_indent(str, indent);
				smart_str_appendc(str, '}');
			} else {
				smart_str_appendc(str, ';');
			}
			break;
		case ZEND_AST_PROP_ELEM:
			smart_str_appendc(str, '$');
			/* break missing intentionally */
		case ZEND_AST_CONST_ELEM:
			zend_ast_export_name(str, ast->child[0], 0, indent);
			APPEND_DEFAULT_VALUE(1);
		case ZEND_AST_USE_TRAIT:
			smart_str_appends(str, "use ");
			zend_ast_export_ex(str, ast->child[0], 0, indent);
			if (ast->child[1]) {
				smart_str_appends(str, " {\n");
				zend_ast_export_ex(str, ast->child[1], 0, indent + 1);
				zend_ast_export_indent(str, indent);
				smart_str_appends(str, "}");
			} else {
				smart_str_appends(str, ";");
			}
			break;
		case ZEND_AST_TRAIT_PRECEDENCE:
			zend_ast_export_ex(str, ast->child[0], 0, indent);
			smart_str_appends(str, " insteadof ");
			zend_ast_export_ex(str, ast->child[1], 0, indent);
			break;
		case ZEND_AST_METHOD_REFERENCE:
			if (ast->child[0]) {
				zend_ast_export_name(str, ast->child[0], 0, indent);
				smart_str_appends(str, "::");
			}
			zend_ast_export_name(str, ast->child[1], 0, indent);
			break;
		case ZEND_AST_NAMESPACE:
			smart_str_appends(str, "namespace");
			if (ast->child[0]) {
				smart_str_appendc(str, ' ');
				zend_ast_export_name(str, ast->child[0], 0, indent);
			}
			if (ast->child[1]) {
				smart_str_appends(str, " {\n");
				zend_ast_export_stmt(str, ast->child[1], indent + 1);
				zend_ast_export_indent(str, indent);
				smart_str_appends(str, "}\n");
			} else {
				smart_str_appendc(str, ';');
			}
			break;
		case ZEND_AST_USE_ELEM:
		case ZEND_AST_TRAIT_ALIAS:
			zend_ast_export_name(str, ast->child[0], 0, indent);
			if (ast->attr & ZEND_ACC_PUBLIC) {
				smart_str_appends(str, " as public");
			} else if (ast->attr & ZEND_ACC_PROTECTED) {
				smart_str_appends(str, " as protected");
			} else if (ast->attr & ZEND_ACC_PRIVATE) {
				smart_str_appends(str, " as private");
			} else if (ast->child[1]) {
				smart_str_appends(str, " as");
			}
			if (ast->child[1]) {
				smart_str_appendc(str, ' ');
				zend_ast_export_name(str, ast->child[1], 0, indent);
			}
			break;

		/* 3 child nodes */
		case ZEND_AST_METHOD_CALL:
			zend_ast_export_ex(str, ast->child[0], 0, indent);
			smart_str_appends(str, "->");
			zend_ast_export_var(str, ast->child[1], 0, indent);
			smart_str_appendc(str, '(');
			zend_ast_export_ex(str, ast->child[2], 0, indent);
			smart_str_appendc(str, ')');
			break;
		case ZEND_AST_STATIC_CALL:
			zend_ast_export_ns_name(str, ast->child[0], 0, indent);
			smart_str_appends(str, "::");
			zend_ast_export_var(str, ast->child[1], 0, indent);
			smart_str_appendc(str, '(');
			zend_ast_export_ex(str, ast->child[2], 0, indent);
			smart_str_appendc(str, ')');
			break;
		case ZEND_AST_CONDITIONAL:
			if (priority > 100) smart_str_appendc(str, '(');
			zend_ast_export_ex(str, ast->child[0], 100, indent);
			if (ast->child[1]) {
				smart_str_appends(str, " ? ");
				zend_ast_export_ex(str, ast->child[1], 101, indent);
				smart_str_appends(str, " : ");
			} else {
				smart_str_appends(str, " ?: ");
			}
			zend_ast_export_ex(str, ast->child[2], 101, indent);
			if (priority > 100) smart_str_appendc(str, ')');
			break;

		case ZEND_AST_TRY:
			smart_str_appends(str, "try {\n");
			zend_ast_export_stmt(str, ast->child[0], indent + 1);
			zend_ast_export_indent(str, indent);
			zend_ast_export_ex(str, ast->child[1], 0, indent);
			if (ast->child[2]) {
				smart_str_appends(str, "} finally {\n");
				zend_ast_export_stmt(str, ast->child[2], indent + 1);
				zend_ast_export_indent(str, indent);
			}
			smart_str_appendc(str, '}');
			break;
		case ZEND_AST_CATCH:
			smart_str_appends(str, "} catch (");
			zend_ast_export_catch_name_list(str, zend_ast_get_list(ast->child[0]), indent);
			smart_str_appends(str, " $");
			zend_ast_export_var(str, ast->child[1], 0, indent);
			smart_str_appends(str, ") {\n");
			zend_ast_export_stmt(str, ast->child[2], indent + 1);
			zend_ast_export_indent(str, indent);
			break;
		case ZEND_AST_PARAM:
			if (ast->child[0]) {
				if (ast->child[0]->attr & ZEND_TYPE_NULLABLE) {
					smart_str_appendc(str, '?');
				}
				zend_ast_export_ns_name(str, ast->child[0], 0, indent);
				smart_str_appendc(str, ' ');
			}
			if (ast->attr & ZEND_PARAM_REF) {
				smart_str_appendc(str, '&');
			}
			if (ast->attr & ZEND_PARAM_VARIADIC) {
				smart_str_appends(str, "...");
			}
			smart_str_appendc(str, '$');
			zend_ast_export_name(str, ast->child[1], 0, indent);
			APPEND_DEFAULT_VALUE(2);

		/* 4 child nodes */
		case ZEND_AST_FOR:
			smart_str_appends(str, "for (");
			zend_ast_export_ex(str, ast->child[0], 0, indent);
			smart_str_appendc(str, ';');
			if (ast->child[1]) {
				smart_str_appendc(str, ' ');
				zend_ast_export_ex(str, ast->child[1], 0, indent);
			}
			smart_str_appendc(str, ';');
			if (ast->child[2]) {
				smart_str_appendc(str, ' ');
				zend_ast_export_ex(str, ast->child[2], 0, indent);
			}
			smart_str_appends(str, ") {\n");
			zend_ast_export_stmt(str, ast->child[3], indent + 1);
			zend_ast_export_indent(str, indent);
			smart_str_appendc(str, '}');
			break;
		case ZEND_AST_FOREACH:
			smart_str_appends(str, "foreach (");
			zend_ast_export_ex(str, ast->child[0], 0, indent);
			smart_str_appends(str, " as ");
			if (ast->child[2]) {
				zend_ast_export_ex(str, ast->child[2], 0, indent);
				smart_str_appends(str, " => ");
			}
			zend_ast_export_ex(str, ast->child[1], 0, indent);
			smart_str_appends(str, ") {\n");
			zend_ast_export_stmt(str, ast->child[3], indent + 1);
			zend_ast_export_indent(str, indent);
			smart_str_appendc(str, '}');
			break;
		EMPTY_SWITCH_DEFAULT_CASE();
	}
	return;

binary_op:
	if (priority > p) smart_str_appendc(str, '(');
	zend_ast_export_ex(str, ast->child[0], pl, indent);
	smart_str_appends(str, op);
	zend_ast_export_ex(str, ast->child[1], pr, indent);
	if (priority > p) smart_str_appendc(str, ')');
	return;

prefix_op:
	if (priority > p) smart_str_appendc(str, '(');
	smart_str_appends(str, op);
	zend_ast_export_ex(str, ast->child[0], pl, indent);
	if (priority > p) smart_str_appendc(str, ')');
	return;

postfix_op:
	if (priority > p) smart_str_appendc(str, '(');
	zend_ast_export_ex(str, ast->child[0], pl, indent);
	smart_str_appends(str, op);
	if (priority > p) smart_str_appendc(str, ')');
	return;

func_op:
	smart_str_appends(str, op);
	smart_str_appendc(str, '(');
	zend_ast_export_ex(str, ast->child[0], 0, indent);
	smart_str_appendc(str, ')');
	return;

append_node_1:
	smart_str_appends(str, op);
	if (ast->child[0]) {
		smart_str_appendc(str, ' ');
		ast = ast->child[0];
		goto tail_call;
	}
	return;

append_str:
	smart_str_appends(str, op);
	return;

append_default_value:
	if (ast->child[p]) {
		smart_str_appends(str, " = ");
		ast = ast->child[p];
		goto tail_call;
	}
	return;
}

ZEND_API zend_string *zend_ast_export(const char *prefix, zend_ast *ast, const char *suffix)
{
	smart_str str = {0};

	smart_str_appends(&str, prefix);
	zend_ast_export_ex(&str, ast, 0, 0);
	smart_str_appends(&str, suffix);
	smart_str_0(&str);
	return str.s;
}
