/*************************************************************************/
/*  gdscript_disassembler.cpp                                            */
/*************************************************************************/
/*                       This file is part of:                           */
/*                           GODOT ENGINE                                */
/*                      https://godotengine.org                          */
/*************************************************************************/
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur.                 */
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md).   */
/*                                                                       */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the       */
/* "Software"), to deal in the Software without restriction, including   */
/* without limitation the rights to use, copy, modify, merge, publish,   */
/* distribute, sublicense, and/or sell copies of the Software, and to    */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions:                                             */
/*                                                                       */
/* The above copyright notice and this permission notice shall be        */
/* included in all copies or substantial portions of the Software.       */
/*                                                                       */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
/*************************************************************************/

#ifdef DEBUG_ENABLED

#include "gdscript_function.h"

#include "core/string/string_builder.h"
#include "gdscript.h"

static String _get_variant_string(const Variant &p_variant) {
	String txt;
	if (p_variant.get_type() == Variant::STRING) {
		txt = "\"" + String(p_variant) + "\"";
	} else if (p_variant.get_type() == Variant::STRING_NAME) {
		txt = "&\"" + String(p_variant) + "\"";
	} else if (p_variant.get_type() == Variant::NODE_PATH) {
		txt = "^\"" + String(p_variant) + "\"";
	} else if (p_variant.get_type() == Variant::OBJECT) {
		Object *obj = p_variant;
		if (!obj) {
			txt = "null";
		} else {
			GDScriptNativeClass *cls = Object::cast_to<GDScriptNativeClass>(obj);
			if (cls) {
				txt += cls->get_name();
				txt += " (class)";
			} else {
				txt = obj->get_class();
				if (obj->get_script_instance()) {
					txt += "(" + obj->get_script_instance()->get_script()->get_path() + ")";
				}
			}
		}
	} else {
		txt = p_variant;
	}
	return txt;
}

static String _disassemble_address(const GDScript *p_script, const GDScriptFunction &p_function, int p_address) {
	int addr = p_address & GDScriptFunction::ADDR_MASK;

	switch (p_address >> GDScriptFunction::ADDR_BITS) {
		case GDScriptFunction::ADDR_TYPE_MEMBER: {
			return "member(" + p_script->debug_get_member_by_index(addr) + ")";
		} break;
		case GDScriptFunction::ADDR_TYPE_CONSTANT: {
			return "const(" + _get_variant_string(p_function.get_constant(addr)) + ")";
		} break;
		case GDScriptFunction::ADDR_TYPE_STACK: {
			switch (addr) {
				case GDScriptFunction::ADDR_STACK_SELF:
					return "self";
				case GDScriptFunction::ADDR_STACK_CLASS:
					return "class";
				case GDScriptFunction::ADDR_STACK_NIL:
					return "nil";
				default:
					return "stack(" + itos(addr) + ")";
			}
		} break;
	}

	return "<err>";
}

void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
#define DADDR(m_ip) (_disassemble_address(_script, *this, _code_ptr[ip + m_ip]))

	for (int ip = 0; ip < _code_size;) {
		StringBuilder text;
		int incr = 0;

		text += " ";
		text += itos(ip);
		text += ": ";

		// This makes the compiler complain if some opcode is unchecked in the switch.
		Opcode code = Opcode(_code_ptr[ip] & INSTR_MASK);
		int instr_var_args = (_code_ptr[ip] & INSTR_ARGS_MASK) >> INSTR_BITS;

		switch (code) {
			case OPCODE_OPERATOR: {
				int operation = _code_ptr[ip + 4];

				text += "operator ";

				text += DADDR(3);
				text += " = ";
				text += DADDR(1);
				text += " ";
				text += Variant::get_operator_name(Variant::Operator(operation));
				text += " ";
				text += DADDR(2);

				incr += 5;
			} break;
			case OPCODE_OPERATOR_VALIDATED: {
				text += "validated operator ";

				text += DADDR(3);
				text += " = ";
				text += DADDR(1);
				text += " <operator function> ";
				text += DADDR(2);

				incr += 5;
			} break;
			case OPCODE_EXTENDS_TEST: {
				text += "is object ";
				text += DADDR(3);
				text += " = ";
				text += DADDR(1);
				text += " is ";
				text += DADDR(2);

				incr += 4;
			} break;
			case OPCODE_IS_BUILTIN: {
				text += "is builtin ";
				text += DADDR(2);
				text += " = ";
				text += DADDR(1);
				text += " is ";
				text += Variant::get_type_name(Variant::Type(_code_ptr[ip + 3]));

				incr += 4;
			} break;
			case OPCODE_SET_KEYED: {
				text += "set keyed ";
				text += DADDR(1);
				text += "[";
				text += DADDR(2);
				text += "] = ";
				text += DADDR(3);

				incr += 4;
			} break;
			case OPCODE_SET_KEYED_VALIDATED: {
				text += "set keyed validated ";
				text += DADDR(1);
				text += "[";
				text += DADDR(2);
				text += "] = ";
				text += DADDR(3);

				incr += 5;
			} break;
			case OPCODE_SET_INDEXED_VALIDATED: {
				text += "set indexed validated ";
				text += DADDR(1);
				text += "[";
				text += DADDR(2);
				text += "] = ";
				text += DADDR(3);

				incr += 5;
			} break;
			case OPCODE_GET_KEYED: {
				text += "get keyed ";
				text += DADDR(3);
				text += " = ";
				text += DADDR(1);
				text += "[";
				text += DADDR(2);
				text += "]";

				incr += 4;
			} break;
			case OPCODE_GET_KEYED_VALIDATED: {
				text += "get keyed validated ";
				text += DADDR(3);
				text += " = ";
				text += DADDR(1);
				text += "[";
				text += DADDR(2);
				text += "]";

				incr += 5;
			} break;
			case OPCODE_GET_INDEXED_VALIDATED: {
				text += "get indexed validated ";
				text += DADDR(3);
				text += " = ";
				text += DADDR(1);
				text += "[";
				text += DADDR(2);
				text += "]";

				incr += 5;
			} break;
			case OPCODE_SET_NAMED: {
				text += "set_named ";
				text += DADDR(1);
				text += "[\"";
				text += _global_names_ptr[_code_ptr[ip + 3]];
				text += "\"] = ";
				text += DADDR(2);

				incr += 4;
			} break;
			case OPCODE_SET_NAMED_VALIDATED: {
				text += "set_named validated ";
				text += DADDR(1);
				text += "[\"";
				text += "<unknown name>";
				text += "\"] = ";
				text += DADDR(2);

				incr += 4;
			} break;
			case OPCODE_GET_NAMED: {
				text += "get_named ";
				text += DADDR(2);
				text += " = ";
				text += DADDR(1);
				text += "[\"";
				text += _global_names_ptr[_code_ptr[ip + 3]];
				text += "\"]";

				incr += 4;
			} break;
			case OPCODE_GET_NAMED_VALIDATED: {
				text += "get_named validated ";
				text += DADDR(2);
				text += " = ";
				text += DADDR(1);
				text += "[\"";
				text += "<unknown name>";
				text += "\"]";

				incr += 4;
			} break;
			case OPCODE_SET_MEMBER: {
				text += "set_member ";
				text += "[\"";
				text += _global_names_ptr[_code_ptr[ip + 2]];
				text += "\"] = ";
				text += DADDR(1);

				incr += 3;
			} break;
			case OPCODE_GET_MEMBER: {
				text += "get_member ";
				text += DADDR(1);
				text += " = ";
				text += "[\"";
				text += _global_names_ptr[_code_ptr[ip + 2]];
				text += "\"]";

				incr += 3;
			} break;
			case OPCODE_ASSIGN: {
				text += "assign ";
				text += DADDR(1);
				text += " = ";
				text += DADDR(2);

				incr += 3;
			} break;
			case OPCODE_ASSIGN_TRUE: {
				text += "assign ";
				text += DADDR(1);
				text += " = true";

				incr += 2;
			} break;
			case OPCODE_ASSIGN_FALSE: {
				text += "assign ";
				text += DADDR(1);
				text += " = false";

				incr += 2;
			} break;
			case OPCODE_ASSIGN_TYPED_BUILTIN: {
				text += "assign typed builtin (";
				text += Variant::get_type_name((Variant::Type)_code_ptr[ip + 3]);
				text += ") ";
				text += DADDR(1);
				text += " = ";
				text += DADDR(2);

				incr += 4;
			} break;
			case OPCODE_ASSIGN_TYPED_ARRAY: {
				text += "assign typed array ";
				text += DADDR(1);
				text += " = ";
				text += DADDR(2);

				incr += 3;
			} break;
			case OPCODE_ASSIGN_TYPED_NATIVE: {
				text += "assign typed native (";
				text += DADDR(3);
				text += ") ";
				text += DADDR(1);
				text += " = ";
				text += DADDR(2);

				incr += 4;
			} break;
			case OPCODE_ASSIGN_TYPED_SCRIPT: {
				Variant script = _constants_ptr[_code_ptr[ip + 3]];
				Script *sc = Object::cast_to<Script>(script.operator Object *());

				text += "assign typed script (";
				text += sc->get_path();
				text += ") ";
				text += DADDR(1);
				text += " = ";
				text += DADDR(2);

				incr += 4;
			} break;
			case OPCODE_CAST_TO_BUILTIN: {
				text += "cast builtin ";
				text += DADDR(2);
				text += " = ";
				text += DADDR(1);
				text += " as ";
				text += Variant::get_type_name(Variant::Type(_code_ptr[ip + 1]));

				incr += 4;
			} break;
			case OPCODE_CAST_TO_NATIVE: {
				text += "cast native ";
				text += DADDR(2);
				text += " = ";
				text += DADDR(1);
				text += " as ";
				text += DADDR(3);

				incr += 4;
			} break;
			case OPCODE_CAST_TO_SCRIPT: {
				text += "cast ";
				text += DADDR(2);
				text += " = ";
				text += DADDR(1);
				text += " as ";
				text += DADDR(3);

				incr += 4;
			} break;
			case OPCODE_CONSTRUCT: {
				Variant::Type t = Variant::Type(_code_ptr[ip + 3 + instr_var_args]);
				int argc = _code_ptr[ip + 1 + instr_var_args];

				text += "construct ";
				text += DADDR(1 + argc);
				text += " = ";

				text += Variant::get_type_name(t) + "(";
				for (int i = 0; i < argc; i++) {
					if (i > 0) {
						text += ", ";
					}
					text += DADDR(i + 1);
				}
				text += ")";

				incr = 3 + instr_var_args;
			} break;
			case OPCODE_CONSTRUCT_VALIDATED: {
				int argc = _code_ptr[ip + 1 + instr_var_args];

				text += "construct validated ";
				text += DADDR(1 + argc);
				text += " = ";

				text += "<unknown type>(";
				for (int i = 0; i < argc; i++) {
					if (i > 0) {
						text += ", ";
					}
					text += DADDR(i + 1);
				}
				text += ")";

				incr = 3 + instr_var_args;
			} break;
			case OPCODE_CONSTRUCT_ARRAY: {
				int argc = _code_ptr[ip + 1 + instr_var_args];
				text += " make_array ";
				text += DADDR(1 + argc);
				text += " = [";

				for (int i = 0; i < argc; i++) {
					if (i > 0) {
						text += ", ";
					}
					text += DADDR(1 + i);
				}

				text += "]";

				incr += 3 + argc;
			} break;
			case OPCODE_CONSTRUCT_TYPED_ARRAY: {
				int argc = _code_ptr[ip + 1 + instr_var_args];

				Ref<Script> script_type = get_constant(_code_ptr[ip + argc + 2]);
				Variant::Type builtin_type = (Variant::Type)_code_ptr[ip + argc + 4];
				StringName native_type = get_global_name(_code_ptr[ip + argc + 5]);

				String type_name;
				if (script_type.is_valid() && script_type->is_valid()) {
					type_name = script_type->get_path();
				} else if (native_type != StringName()) {
					type_name = native_type;
				} else {
					type_name = Variant::get_type_name(builtin_type);
				}

				text += " make_typed_array (";
				text += type_name;
				text += ") ";

				text += DADDR(1 + argc);
				text += " = [";

				for (int i = 0; i < argc; i++) {
					if (i > 0) {
						text += ", ";
					}
					text += DADDR(1 + i);
				}

				text += "]";

				incr += 3 + argc;
			} break;
			case OPCODE_CONSTRUCT_DICTIONARY: {
				int argc = _code_ptr[ip + 1 + instr_var_args];
				text += "make_dict ";
				text += DADDR(1 + argc * 2);
				text += " = {";

				for (int i = 0; i < argc; i++) {
					if (i > 0) {
						text += ", ";
					}
					text += DADDR(1 + i * 2 + 0);
					text += ": ";
					text += DADDR(1 + i * 2 + 1);
				}

				text += "}";

				incr += 3 + argc * 2;
			} break;
			case OPCODE_CALL:
			case OPCODE_CALL_RETURN:
			case OPCODE_CALL_ASYNC: {
				bool ret = (_code_ptr[ip] & INSTR_MASK) == OPCODE_CALL_RETURN;
				bool async = (_code_ptr[ip] & INSTR_MASK) == OPCODE_CALL_ASYNC;

				if (ret) {
					text += "call-ret ";
				} else if (async) {
					text += "call-async ";
				} else {
					text += "call ";
				}

				int argc = _code_ptr[ip + 1 + instr_var_args];
				if (ret || async) {
					text += DADDR(2 + argc) + " = ";
				}

				text += DADDR(1 + argc) + ".";
				text += String(_global_names_ptr[_code_ptr[ip + 2 + instr_var_args]]);
				text += "(";

				for (int i = 0; i < argc; i++) {
					if (i > 0) {
						text += ", ";
					}
					text += DADDR(1 + i);
				}
				text += ")";

				incr = 5 + argc;
			} break;
			case OPCODE_CALL_METHOD_BIND:
			case OPCODE_CALL_METHOD_BIND_RET: {
				bool ret = (_code_ptr[ip] & INSTR_MASK) == OPCODE_CALL_METHOD_BIND_RET;

				if (ret) {
					text += "call-method_bind-ret ";
				} else {
					text += "call-method_bind ";
				}

				MethodBind *method = _methods_ptr[_code_ptr[ip + 2 + instr_var_args]];

				int argc = _code_ptr[ip + 1 + instr_var_args];
				if (ret) {
					text += DADDR(2 + argc) + " = ";
				}

				text += DADDR(1 + argc) + ".";
				text += method->get_name();
				text += "(";

				for (int i = 0; i < argc; i++) {
					if (i > 0) {
						text += ", ";
					}
					text += DADDR(1 + i);
				}
				text += ")";

				incr = 5 + argc;
			} break;
			case OPCODE_CALL_BUILTIN_STATIC: {
				Variant::Type type = (Variant::Type)_code_ptr[ip + 1 + instr_var_args];
				int argc = _code_ptr[ip + 3 + instr_var_args];

				text += "call built-in method static ";
				text += DADDR(1 + argc);
				text += " = ";
				text += Variant::get_type_name(type);
				text += ".";
				text += _global_names_ptr[_code_ptr[ip + 2 + instr_var_args]].operator String();
				text += "(";

				for (int i = 0; i < argc; i++) {
					if (i > 0) {
						text += ", ";
					}
					text += DADDR(1 + i);
				}
				text += ")";

				incr += 5 + argc;
			} break;
			case OPCODE_CALL_NATIVE_STATIC: {
				MethodBind *method = _methods_ptr[_code_ptr[ip + 1 + instr_var_args]];
				int argc = _code_ptr[ip + 2 + instr_var_args];

				text += "call native method static ";
				text += DADDR(1 + argc);
				text += " = ";
				text += method->get_instance_class();
				text += ".";
				text += method->get_name();
				text += "(";

				for (int i = 0; i < argc; i++) {
					if (i > 0) {
						text += ", ";
					}
					text += DADDR(1 + i);
				}
				text += ")";

				incr += 4 + argc;
			} break;
			case OPCODE_CALL_PTRCALL_NO_RETURN: {
				text += "call-ptrcall (no return) ";

				MethodBind *method = _methods_ptr[_code_ptr[ip + 2 + instr_var_args]];

				int argc = _code_ptr[ip + 1 + instr_var_args];

				text += DADDR(1 + argc) + ".";
				text += method->get_name();
				text += "(";

				for (int i = 0; i < argc; i++) {
					if (i > 0) {
						text += ", ";
					}
					text += DADDR(1 + i);
				}
				text += ")";

				incr = 5 + argc;
			} break;

#define DISASSEMBLE_PTRCALL(m_type)                                            \
	case OPCODE_CALL_PTRCALL_##m_type: {                                       \
		text += "call-ptrcall (return ";                                       \
		text += #m_type;                                                       \
		text += ") ";                                                          \
		MethodBind *method = _methods_ptr[_code_ptr[ip + 2 + instr_var_args]]; \
		int argc = _code_ptr[ip + 1 + instr_var_args];                         \
		text += DADDR(2 + argc) + " = ";                                       \
		text += DADDR(1 + argc) + ".";                                         \
		text += method->get_name();                                            \
		text += "(";                                                           \
		for (int i = 0; i < argc; i++) {                                       \
			if (i > 0)                                                         \
				text += ", ";                                                  \
			text += DADDR(1 + i);                                              \
		}                                                                      \
		text += ")";                                                           \
		incr = 5 + argc;                                                       \
	} break

				DISASSEMBLE_PTRCALL(BOOL);
				DISASSEMBLE_PTRCALL(INT);
				DISASSEMBLE_PTRCALL(FLOAT);
				DISASSEMBLE_PTRCALL(STRING);
				DISASSEMBLE_PTRCALL(VECTOR2);
				DISASSEMBLE_PTRCALL(VECTOR2I);
				DISASSEMBLE_PTRCALL(RECT2);
				DISASSEMBLE_PTRCALL(RECT2I);
				DISASSEMBLE_PTRCALL(VECTOR3);
				DISASSEMBLE_PTRCALL(VECTOR3I);
				DISASSEMBLE_PTRCALL(TRANSFORM2D);
				DISASSEMBLE_PTRCALL(VECTOR4);
				DISASSEMBLE_PTRCALL(VECTOR4I);
				DISASSEMBLE_PTRCALL(PLANE);
				DISASSEMBLE_PTRCALL(AABB);
				DISASSEMBLE_PTRCALL(BASIS);
				DISASSEMBLE_PTRCALL(TRANSFORM3D);
				DISASSEMBLE_PTRCALL(PROJECTION);
				DISASSEMBLE_PTRCALL(COLOR);
				DISASSEMBLE_PTRCALL(STRING_NAME);
				DISASSEMBLE_PTRCALL(NODE_PATH);
				DISASSEMBLE_PTRCALL(RID);
				DISASSEMBLE_PTRCALL(QUATERNION);
				DISASSEMBLE_PTRCALL(OBJECT);
				DISASSEMBLE_PTRCALL(CALLABLE);
				DISASSEMBLE_PTRCALL(SIGNAL);
				DISASSEMBLE_PTRCALL(DICTIONARY);
				DISASSEMBLE_PTRCALL(ARRAY);
				DISASSEMBLE_PTRCALL(PACKED_BYTE_ARRAY);
				DISASSEMBLE_PTRCALL(PACKED_INT32_ARRAY);
				DISASSEMBLE_PTRCALL(PACKED_INT64_ARRAY);
				DISASSEMBLE_PTRCALL(PACKED_FLOAT32_ARRAY);
				DISASSEMBLE_PTRCALL(PACKED_FLOAT64_ARRAY);
				DISASSEMBLE_PTRCALL(PACKED_STRING_ARRAY);
				DISASSEMBLE_PTRCALL(PACKED_VECTOR2_ARRAY);
				DISASSEMBLE_PTRCALL(PACKED_VECTOR3_ARRAY);
				DISASSEMBLE_PTRCALL(PACKED_COLOR_ARRAY);

			case OPCODE_CALL_BUILTIN_TYPE_VALIDATED: {
				int argc = _code_ptr[ip + 1 + instr_var_args];

				text += "call-builtin-method validated ";

				text += DADDR(2 + argc) + " = ";

				text += DADDR(1) + ".";
				text += "<unknown method>";

				text += "(";

				for (int i = 0; i < argc; i++) {
					if (i > 0) {
						text += ", ";
					}
					text += DADDR(1 + i);
				}
				text += ")";

				incr = 5 + argc;
			} break;
			case OPCODE_CALL_UTILITY: {
				text += "call-utility ";

				int argc = _code_ptr[ip + 1 + instr_var_args];
				text += DADDR(1 + argc) + " = ";

				text += _global_names_ptr[_code_ptr[ip + 2 + instr_var_args]];
				text += "(";

				for (int i = 0; i < argc; i++) {
					if (i > 0) {
						text += ", ";
					}
					text += DADDR(1 + i);
				}
				text += ")";

				incr = 4 + argc;
			} break;
			case OPCODE_CALL_UTILITY_VALIDATED: {
				text += "call-utility ";

				int argc = _code_ptr[ip + 1 + instr_var_args];
				text += DADDR(1 + argc) + " = ";

				text += "<unknown function>";
				text += "(";

				for (int i = 0; i < argc; i++) {
					if (i > 0) {
						text += ", ";
					}
					text += DADDR(1 + i);
				}
				text += ")";

				incr = 4 + argc;
			} break;
			case OPCODE_CALL_GDSCRIPT_UTILITY: {
				text += "call-gscript-utility ";

				int argc = _code_ptr[ip + 1 + instr_var_args];
				text += DADDR(1 + argc) + " = ";

				text += "<unknown function>";
				text += "(";

				for (int i = 0; i < argc; i++) {
					if (i > 0) {
						text += ", ";
					}
					text += DADDR(1 + i);
				}
				text += ")";

				incr = 4 + argc;
			} break;
			case OPCODE_CALL_SELF_BASE: {
				text += "call-self-base ";

				int argc = _code_ptr[ip + 1 + instr_var_args];
				text += DADDR(2 + argc) + " = ";

				text += _global_names_ptr[_code_ptr[ip + 2 + instr_var_args]];
				text += "(";

				for (int i = 0; i < argc; i++) {
					if (i > 0) {
						text += ", ";
					}
					text += DADDR(1 + i);
				}
				text += ")";

				incr = 4 + argc;
			} break;
			case OPCODE_AWAIT: {
				text += "await ";
				text += DADDR(1);

				incr = 2;
			} break;
			case OPCODE_AWAIT_RESUME: {
				text += "await resume ";
				text += DADDR(1);

				incr = 2;
			} break;
			case OPCODE_CREATE_LAMBDA: {
				int captures_count = _code_ptr[ip + 1 + instr_var_args];
				GDScriptFunction *lambda = _lambdas_ptr[_code_ptr[ip + 2 + instr_var_args]];

				text += DADDR(1 + captures_count);
				text += "create lambda from ";
				text += lambda->name.operator String();
				text += "function, captures (";

				for (int i = 0; i < captures_count; i++) {
					if (i > 0) {
						text += ", ";
					}
					text += DADDR(1 + i);
				}
				text += ")";

				incr = 3 + captures_count;
			} break;
			case OPCODE_CREATE_SELF_LAMBDA: {
				int captures_count = _code_ptr[ip + 1 + instr_var_args];
				GDScriptFunction *lambda = _lambdas_ptr[_code_ptr[ip + 2 + instr_var_args]];

				text += DADDR(1 + captures_count);
				text += "create self lambda from ";
				text += lambda->name.operator String();
				text += "function, captures (";

				for (int i = 0; i < captures_count; i++) {
					if (i > 0) {
						text += ", ";
					}
					text += DADDR(1 + i);
				}
				text += ")";

				incr = 3 + captures_count;
			} break;
			case OPCODE_JUMP: {
				text += "jump ";
				text += itos(_code_ptr[ip + 1]);

				incr = 2;
			} break;
			case OPCODE_JUMP_IF: {
				text += "jump-if ";
				text += DADDR(1);
				text += " to ";
				text += itos(_code_ptr[ip + 2]);

				incr = 3;
			} break;
			case OPCODE_JUMP_IF_NOT: {
				text += "jump-if-not ";
				text += DADDR(1);
				text += " to ";
				text += itos(_code_ptr[ip + 2]);

				incr = 3;
			} break;
			case OPCODE_JUMP_TO_DEF_ARGUMENT: {
				text += "jump-to-default-argument ";

				incr = 1;
			} break;
			case OPCODE_JUMP_IF_SHARED: {
				text += "jump-if-shared ";
				text += DADDR(1);
				text += " to ";
				text += itos(_code_ptr[ip + 2]);

				incr = 3;
			} break;
			case OPCODE_RETURN: {
				text += "return ";
				text += DADDR(1);

				incr = 2;
			} break;
			case OPCODE_RETURN_TYPED_BUILTIN: {
				text += "return typed builtin (";
				text += Variant::get_type_name((Variant::Type)_code_ptr[ip + 2]);
				text += ") ";
				text += DADDR(1);

				incr += 3;
			} break;
			case OPCODE_RETURN_TYPED_ARRAY: {
				text += "return typed array ";
				text += DADDR(1);

				incr += 5;
			} break;
			case OPCODE_RETURN_TYPED_NATIVE: {
				text += "return typed native (";
				text += DADDR(2);
				text += ") ";
				text += DADDR(1);

				incr += 3;
			} break;
			case OPCODE_RETURN_TYPED_SCRIPT: {
				Variant script = _constants_ptr[_code_ptr[ip + 2]];
				Script *sc = Object::cast_to<Script>(script.operator Object *());

				text += "return typed script (";
				text += sc->get_path();
				text += ") ";
				text += DADDR(1);

				incr += 3;
			} break;

#define DISASSEMBLE_ITERATE(m_type)      \
	case OPCODE_ITERATE_##m_type: {      \
		text += "for-loop (typed ";      \
		text += #m_type;                 \
		text += ") ";                    \
		text += DADDR(3);                \
		text += " in ";                  \
		text += DADDR(2);                \
		text += " counter ";             \
		text += DADDR(1);                \
		text += " end ";                 \
		text += itos(_code_ptr[ip + 4]); \
		incr += 5;                       \
	} break

#define DISASSEMBLE_ITERATE_BEGIN(m_type) \
	case OPCODE_ITERATE_BEGIN_##m_type: { \
		text += "for-init (typed ";       \
		text += #m_type;                  \
		text += ") ";                     \
		text += DADDR(3);                 \
		text += " in ";                   \
		text += DADDR(2);                 \
		text += " counter ";              \
		text += DADDR(1);                 \
		text += " end ";                  \
		text += itos(_code_ptr[ip + 4]);  \
		incr += 5;                        \
	} break

#define DISASSEMBLE_ITERATE_TYPES(m_macro) \
	m_macro(INT);                          \
	m_macro(FLOAT);                        \
	m_macro(VECTOR2);                      \
	m_macro(VECTOR2I);                     \
	m_macro(VECTOR3);                      \
	m_macro(VECTOR3I);                     \
	m_macro(STRING);                       \
	m_macro(DICTIONARY);                   \
	m_macro(ARRAY);                        \
	m_macro(PACKED_BYTE_ARRAY);            \
	m_macro(PACKED_INT32_ARRAY);           \
	m_macro(PACKED_INT64_ARRAY);           \
	m_macro(PACKED_FLOAT32_ARRAY);         \
	m_macro(PACKED_FLOAT64_ARRAY);         \
	m_macro(PACKED_STRING_ARRAY);          \
	m_macro(PACKED_VECTOR2_ARRAY);         \
	m_macro(PACKED_VECTOR3_ARRAY);         \
	m_macro(PACKED_COLOR_ARRAY);           \
	m_macro(OBJECT)

			case OPCODE_ITERATE_BEGIN: {
				text += "for-init ";
				text += DADDR(3);
				text += " in ";
				text += DADDR(2);
				text += " counter ";
				text += DADDR(1);
				text += " end ";
				text += itos(_code_ptr[ip + 4]);

				incr += 5;
			} break;
				DISASSEMBLE_ITERATE_TYPES(DISASSEMBLE_ITERATE_BEGIN);
			case OPCODE_ITERATE: {
				text += "for-loop ";
				text += DADDR(2);
				text += " in ";
				text += DADDR(2);
				text += " counter ";
				text += DADDR(1);
				text += " end ";
				text += itos(_code_ptr[ip + 4]);

				incr += 5;
			} break;
				DISASSEMBLE_ITERATE_TYPES(DISASSEMBLE_ITERATE);
			case OPCODE_STORE_GLOBAL: {
				text += "store global ";
				text += DADDR(1);
				text += " = ";
				text += String::num_int64(_code_ptr[ip + 2]);

				incr += 3;
			} break;
			case OPCODE_STORE_NAMED_GLOBAL: {
				text += "store named global ";
				text += DADDR(1);
				text += " = ";
				text += String(_global_names_ptr[_code_ptr[ip + 2]]);

				incr += 3;
			} break;
			case OPCODE_LINE: {
				int line = _code_ptr[ip + 1] - 1;
				if (line >= 0 && line < p_code_lines.size()) {
					text += "line ";
					text += itos(line + 1);
					text += ": ";
					text += p_code_lines[line];
				} else {
					text += "";
				}

				incr += 2;
			} break;

#define DISASSEMBLE_TYPE_ADJUST(m_v_type) \
	case OPCODE_TYPE_ADJUST_##m_v_type: { \
		text += "type adjust (";          \
		text += #m_v_type;                \
		text += ") ";                     \
		text += DADDR(1);                 \
		incr += 2;                        \
	} break

				DISASSEMBLE_TYPE_ADJUST(BOOL);
				DISASSEMBLE_TYPE_ADJUST(INT);
				DISASSEMBLE_TYPE_ADJUST(FLOAT);
				DISASSEMBLE_TYPE_ADJUST(STRING);
				DISASSEMBLE_TYPE_ADJUST(VECTOR2);
				DISASSEMBLE_TYPE_ADJUST(VECTOR2I);
				DISASSEMBLE_TYPE_ADJUST(RECT2);
				DISASSEMBLE_TYPE_ADJUST(RECT2I);
				DISASSEMBLE_TYPE_ADJUST(VECTOR3);
				DISASSEMBLE_TYPE_ADJUST(VECTOR3I);
				DISASSEMBLE_TYPE_ADJUST(TRANSFORM2D);
				DISASSEMBLE_TYPE_ADJUST(VECTOR4);
				DISASSEMBLE_TYPE_ADJUST(VECTOR4I);
				DISASSEMBLE_TYPE_ADJUST(PLANE);
				DISASSEMBLE_TYPE_ADJUST(QUATERNION);
				DISASSEMBLE_TYPE_ADJUST(AABB);
				DISASSEMBLE_TYPE_ADJUST(BASIS);
				DISASSEMBLE_TYPE_ADJUST(TRANSFORM3D);
				DISASSEMBLE_TYPE_ADJUST(PROJECTION);
				DISASSEMBLE_TYPE_ADJUST(COLOR);
				DISASSEMBLE_TYPE_ADJUST(STRING_NAME);
				DISASSEMBLE_TYPE_ADJUST(NODE_PATH);
				DISASSEMBLE_TYPE_ADJUST(RID);
				DISASSEMBLE_TYPE_ADJUST(OBJECT);
				DISASSEMBLE_TYPE_ADJUST(CALLABLE);
				DISASSEMBLE_TYPE_ADJUST(SIGNAL);
				DISASSEMBLE_TYPE_ADJUST(DICTIONARY);
				DISASSEMBLE_TYPE_ADJUST(ARRAY);
				DISASSEMBLE_TYPE_ADJUST(PACKED_BYTE_ARRAY);
				DISASSEMBLE_TYPE_ADJUST(PACKED_INT32_ARRAY);
				DISASSEMBLE_TYPE_ADJUST(PACKED_INT64_ARRAY);
				DISASSEMBLE_TYPE_ADJUST(PACKED_FLOAT32_ARRAY);
				DISASSEMBLE_TYPE_ADJUST(PACKED_FLOAT64_ARRAY);
				DISASSEMBLE_TYPE_ADJUST(PACKED_STRING_ARRAY);
				DISASSEMBLE_TYPE_ADJUST(PACKED_VECTOR2_ARRAY);
				DISASSEMBLE_TYPE_ADJUST(PACKED_VECTOR3_ARRAY);
				DISASSEMBLE_TYPE_ADJUST(PACKED_COLOR_ARRAY);

			case OPCODE_ASSERT: {
				text += "assert (";
				text += DADDR(1);
				text += ", ";
				text += DADDR(2);
				text += ")";

				incr += 3;
			} break;
			case OPCODE_BREAKPOINT: {
				text += "breakpoint";

				incr += 1;
			} break;
			case OPCODE_END: {
				text += "== END ==";

				incr += 1;
			} break;
		}

		ip += incr;
		if (text.get_string_length() > 0) {
			print_line(text.as_string());
		}
	}
}

#endif