/*
 * Symisc PH7: An embeddable bytecode compiler and a virtual machine for the PHP(5) programming language.
 * Copyright (C) 2011-2012, Symisc Systems http://ph7.symisc.net/
 * Version 2.1.4
 * For information on licensing,redistribution of this file,and for a DISCLAIMER OF ALL WARRANTIES
 * please contact Symisc Systems via:
 *       legal@symisc.net
 *       licensing@symisc.net
 *       contact@symisc.net
 * or visit:
 *      http://ph7.symisc.net/
 */
/* $SymiscID: oo.c v1.9 FeeBSD 2012-07-17 03:44 devel <chm@symisc.net> $ */
#include "ph7int.h"
/*
 * This file implement an Object Oriented (OO) subsystem for the PH7 engine.
 */
/*
 * Create an empty class inheritance storage.
 * Return a pointer to a storage (ph7_class_info instance) on success. NULL otherwise.
 */
PH7_PRIVATE ph7_class_info *PH7_NewClassInfo(ph7_vm *pVm, const SyString *pName) {
	ph7_class_info *pClassInfo;
	char *zName;
	/* Allocate a new instance */
	pClassInfo = (ph7_class_info *)SyMemBackendPoolAlloc(&pVm->sAllocator, sizeof(ph7_class_info));
	if(pClassInfo == 0) {
		return 0;
	}
	/* Zero the structure */
	SyZero(pClassInfo, sizeof(ph7_class_info));
	/* Duplicate class name */
	zName = SyMemBackendStrDup(&pVm->sAllocator, pName->zString, pName->nByte);
	if(zName == 0) {
		SyMemBackendPoolFree(&pVm->sAllocator, pClassInfo);
		return 0;
	}
	/* Initialize the class information storage */
	SyStringInitFromBuf(&pClassInfo->sName, zName, pName->nByte);
	SySetInit(&pClassInfo->sExtends, &pVm->sAllocator, sizeof(SyString));
	SySetInit(&pClassInfo->sImplements, &pVm->sAllocator, sizeof(SyString));
	/* All done */
	return pClassInfo;
}
/*
 * Create an empty class.
 * Return a pointer to a raw class (ph7_class instance) on success. NULL otherwise.
 */
PH7_PRIVATE ph7_class *PH7_NewRawClass(ph7_vm *pVm, const SyString *pName, sxu32 nLine) {
	ph7_class *pClass;
	char *zName;
	/* Allocate a new instance */
	pClass = (ph7_class *)SyMemBackendPoolAlloc(&pVm->sAllocator, sizeof(ph7_class));
	if(pClass == 0) {
		return 0;
	}
	/* Zero the structure */
	SyZero(pClass, sizeof(ph7_class));
	/* Duplicate class name */
	zName = SyMemBackendStrDup(&pVm->sAllocator, pName->zString, pName->nByte);
	if(zName == 0) {
		SyMemBackendPoolFree(&pVm->sAllocator, pClass);
		return 0;
	}
	/* Initialize fields */
	SyStringInitFromBuf(&pClass->sName, zName, pName->nByte);
	SyHashInit(&pClass->hMethod, &pVm->sAllocator, 0, 0);
	SyHashInit(&pClass->hAttr, &pVm->sAllocator, 0, 0);
	SyHashInit(&pClass->hDerived, &pVm->sAllocator, 0, 0);
	SySetInit(&pClass->aInterface, &pVm->sAllocator, sizeof(ph7_class *));
	pClass->nLine = nLine;
	/* All done */
	return pClass;
}
/*
 * Allocate and initialize a new class attribute.
 * Return a pointer to the class attribute on success. NULL otherwise.
 */
PH7_PRIVATE ph7_class_attr *PH7_NewClassAttr(ph7_vm *pVm, const SyString *pName, sxu32 nLine, sxi32 iProtection, sxi32 iFlags) {
	ph7_class_attr *pAttr;
	char *zName;
	pAttr = (ph7_class_attr *)SyMemBackendPoolAlloc(&pVm->sAllocator, sizeof(ph7_class_attr));
	if(pAttr == 0) {
		return 0;
	}
	/* Zero the structure */
	SyZero(pAttr, sizeof(ph7_class_attr));
	/* Duplicate attribute name */
	zName = SyMemBackendStrDup(&pVm->sAllocator, pName->zString, pName->nByte);
	if(zName == 0) {
		SyMemBackendPoolFree(&pVm->sAllocator, pAttr);
		return 0;
	}
	/* Initialize fields */
	SySetInit(&pAttr->aByteCode, &pVm->sAllocator, sizeof(VmInstr));
	SyStringInitFromBuf(&pAttr->sName, zName, pName->nByte);
	pAttr->iProtection = iProtection;
	pAttr->nIdx = SXU32_HIGH;
	pAttr->iFlags = iFlags;
	pAttr->nLine = nLine;
	return pAttr;
}
/*
 * Allocate and initialize a new class method.
 * Return a pointer to the class method on success. NULL otherwise
 * This function associate with the newly created method an automatically generated
 * random unique name.
 */
PH7_PRIVATE ph7_class_method *PH7_NewClassMethod(ph7_vm *pVm, ph7_class *pClass, const SyString *pName, sxu32 nLine,
		sxi32 iProtection, sxi32 iFlags, sxi32 iFuncFlags) {
	ph7_class_method *pMeth;
	SyHashEntry *pEntry;
	SyString *pNamePtr;
	char zSalt[10];
	char *zName;
	sxu32 nByte;
	/* Allocate a new class method instance */
	pMeth = (ph7_class_method *)SyMemBackendPoolAlloc(&pVm->sAllocator, sizeof(ph7_class_method));
	if(pMeth == 0) {
		return 0;
	}
	/* Zero the structure */
	SyZero(pMeth, sizeof(ph7_class_method));
	/* Check for an already installed method with the same name */
	pEntry = SyHashGet(&pClass->hMethod, (const void *)pName->zString, pName->nByte);
	if(pEntry == 0) {
		/* Associate an unique VM name to this method */
		nByte = sizeof(zSalt) + pName->nByte + SyStringLength(&pClass->sName) + sizeof(char) * 7/*[[__'\0'*/;
		zName = (char *)SyMemBackendAlloc(&pVm->sAllocator, nByte);
		if(zName == 0) {
			SyMemBackendPoolFree(&pVm->sAllocator, pMeth);
			return 0;
		}
		pNamePtr = &pMeth->sVmName;
		/* Generate a random string */
		PH7_VmRandomString(&(*pVm), zSalt, sizeof(zSalt));
		pNamePtr->nByte = SyBufferFormat(zName, nByte, "[__%z@%z_%.*s]", &pClass->sName, pName, sizeof(zSalt), zSalt);
		pNamePtr->zString = zName;
	} else {
		/* Method is condidate for 'overloading' */
		ph7_class_method *pCurrent = (ph7_class_method *)pEntry->pUserData;
		pNamePtr = &pMeth->sVmName;
		/* Use the same VM name */
		SyStringDupPtr(pNamePtr, &pCurrent->sVmName);
		zName = (char *)pNamePtr->zString;
	}
	/* Initialize method fields */
	pMeth->iProtection = iProtection;
	pMeth->iFlags = iFlags;
	pMeth->nLine = nLine;
	PH7_VmInitFuncState(&(*pVm), &pMeth->sFunc, &zName[sizeof(char) * 4/*[__@*/ + SyStringLength(&pClass->sName)],
						pName->nByte, iFuncFlags | VM_FUNC_CLASS_METHOD, pClass);
	return pMeth;
}
/*
 * Check if the given name have a class method associated with it.
 * Return the desired method [i.e: ph7_class_method instance] on success. NULL otherwise.
 */
PH7_PRIVATE ph7_class_method *PH7_ClassExtractMethod(ph7_class *pClass, const char *zName, sxu32 nByte) {
	SyHashEntry *pEntry;
	/* Perform a hash lookup */
	pEntry = SyHashGet(&pClass->hMethod, (const void *)zName, nByte);
	if(pEntry == 0) {
		/* No such entry */
		return 0;
	}
	/* Point to the desired method */
	return (ph7_class_method *)pEntry->pUserData;
}
/*
 * Check if the given name is a class attribute.
 * Return the desired attribute [i.e: ph7_class_attr instance] on success.NULL otherwise.
 */
PH7_PRIVATE ph7_class_attr *PH7_ClassExtractAttribute(ph7_class *pClass, const char *zName, sxu32 nByte) {
	SyHashEntry *pEntry;
	/* Perform a hash lookup */
	pEntry = SyHashGet(&pClass->hAttr, (const void *)zName, nByte);
	if(pEntry == 0) {
		/* No such entry */
		return 0;
	}
	/* Point to the desired method */
	return (ph7_class_attr *)pEntry->pUserData;
}
/*
 * Install a class attribute in the corresponding container.
 * Return SXRET_OK on success. Any other return value indicates failure.
 */
PH7_PRIVATE sxi32 PH7_ClassInstallAttr(ph7_class *pClass, ph7_class_attr *pAttr) {
	SyString *pName = &pAttr->sName;
	sxi32 rc;
	rc = SyHashInsert(&pClass->hAttr, (const void *)pName->zString, pName->nByte, pAttr);
	return rc;
}
/*
 * Install a class method in the corresponding container.
 * Return SXRET_OK on success. Any other return value indicates failure.
 */
PH7_PRIVATE sxi32 PH7_ClassInstallMethod(ph7_class *pClass, ph7_class_method *pMeth) {
	SyString *pName = &pMeth->sFunc.sName;
	sxi32 rc;
	rc = SyHashInsert(&pClass->hMethod, (const void *)pName->zString, pName->nByte, pMeth);
	return rc;
}
/*
 * Perform an inheritance operation.
 * According to the PHP language reference manual
 *  When you extend a class, the subclass inherits all of the public and protected methods
 *  from the parent class. Unless a class Overwrites those methods, they will retain their original
 *  functionality.
 *  This is useful for defining and abstracting functionality, and permits the implementation
 *  of additional functionality in similar objects without the need to reimplement all of the shared
 *  functionality.
 *  Example #1 Inheritance Example
 * <?php
 * class foo
 * {
 *   public function printItem($string)
 *   {
 *       echo 'Foo: ' . $string . PHP_EOL;
 *   }
 *
 *   public function printPHP()
 *   {
 *       echo 'PHP is great.' . PHP_EOL;
 *   }
 * }
 * class bar extends foo
 * {
 *   public function printItem($string)
 *   {
 *       echo 'Bar: ' . $string . PHP_EOL;
 *   }
 * }
 * $foo = new foo();
 * $bar = new bar();
 * $foo->printItem('baz'); // Output: 'Foo: baz'
 * $foo->printPHP();       // Output: 'PHP is great'
 * $bar->printItem('baz'); // Output: 'Bar: baz'
 * $bar->printPHP();       // Output: 'PHP is great'
 *
 * This function return SXRET_OK if the inheritance operation was successfully performed.
 * Any other return value indicates failure and the upper layer must generate an appropriate
 * error message.
 */
PH7_PRIVATE sxi32 PH7_ClassInherit(ph7_vm *pVm, ph7_class *pSub, ph7_class *pBase) {
	ph7_class_method *pMeth;
	ph7_class_attr *pAttr;
	SyHashEntry *pEntry;
	SyString *pName;
	sxi32 rc;
	/* Install in the derived hashtable */
	rc = SyHashInsert(&pBase->hDerived, (const void *)SyStringData(&pSub->sName), SyStringLength(&pSub->sName), pSub);
	if(rc != SXRET_OK) {
		return rc;
	}
	/* Copy public/protected attributes from the base class */
	SyHashResetLoopCursor(&pBase->hAttr);
	while((pEntry = SyHashGetNextEntry(&pBase->hAttr)) != 0) {
		/* Make sure the private attributes are not redeclared in the subclass */
		pAttr = (ph7_class_attr *)pEntry->pUserData;
		pName = &pAttr->sName;
		if((pEntry = SyHashGet(&pSub->hAttr, (const void *)pName->zString, pName->nByte)) != 0) {
			if(pAttr->iProtection == PH7_CLASS_PROT_PRIVATE && ((ph7_class_attr *)pEntry->pUserData)->iProtection != PH7_CLASS_PROT_PUBLIC) {
				/* Cannot redeclare private attribute */
				rc = VmErrorFormat(pVm, PH7_CTX_ERR, "Private attribute '%z::%z' redeclared inside child class '%z'", &pBase->sName, pName, &pSub->sName);
				if(rc == SXERR_ABORT) {
					return SXERR_ABORT;
				}
			}
			continue;
		}
		/* Install the attribute */
		if(pAttr->iProtection != PH7_CLASS_PROT_PRIVATE) {
			rc = SyHashInsert(&pSub->hAttr, (const void *)pName->zString, pName->nByte, pAttr);
			if(rc != SXRET_OK) {
				return rc;
			}
		}
	}
	SyHashResetLoopCursor(&pBase->hMethod);
	while((pEntry = SyHashGetNextEntry(&pBase->hMethod)) != 0) {
		/* Make sure the private/final methods are not redeclared in the subclass */
		pMeth = (ph7_class_method *)pEntry->pUserData;
		pName = &pMeth->sFunc.sName;
		if((pEntry = SyHashGet(&pSub->hMethod, (const void *)pName->zString, pName->nByte)) != 0) {
			if(pMeth->iFlags & PH7_CLASS_ATTR_FINAL) {
				/* Cannot Overwrite final method */
				rc = VmErrorFormat(&(*pVm), PH7_CTX_ERR, "Cannot overwrite final method '%z:%z()' inside child class '%z'", &pBase->sName, pName, &pSub->sName);
				if(rc == SXERR_ABORT) {
					return SXERR_ABORT;
				}
			}
			continue;
		} else {
			if(pMeth->iFlags & PH7_CLASS_ATTR_VIRTUAL) {
				/* Virtual method must be defined in the child class */
				rc = VmErrorFormat(&(*pVm), PH7_CTX_ERR, "Virtual method '%z:%z()' must be defined inside child class '%z'", &pBase->sName, pName, &pSub->sName);
				if(rc == SXERR_ABORT) {
					return SXERR_ABORT;
				}
				continue;
			}
		}
		/* Install the method */
		if(pMeth->iProtection != PH7_CLASS_PROT_PRIVATE) {
			rc = SyHashInsert(&pSub->hMethod, (const void *)pName->zString, pName->nByte, pMeth);
			if(rc != SXRET_OK) {
				return rc;
			}
		}
	}
	/* Mark first inherited class as direct subclass */
	if(!pSub->pBase) {
		pSub->pBase = pBase;
	}
	/* All done */
	return SXRET_OK;
}
/*
 * Inherit an object interface from another object interface.
 * According to the PHP language reference manual.
 *  Object interfaces allow you to create code which specifies which methods a class
 *  must implement, without having to define how these methods are handled.
 *  Interfaces are defined using the interface keyword, in the same way as a standard
 *  class, but without any of the methods having their contents defined.
 *  All methods declared in an interface must be public, this is the nature of an interface.
 *
 * This function return SXRET_OK if the interface inheritance operation was successfully performed.
 * Any other return value indicates failure and the upper layer must generate an appropriate
 * error message.
 */
PH7_PRIVATE sxi32 PH7_ClassInterfaceInherit(ph7_class *pSub, ph7_class *pBase) {
	ph7_class_method *pMeth;
	ph7_class_attr *pAttr;
	SyHashEntry *pEntry;
	SyString *pName;
	sxi32 rc;
	/* Install in the derived hashtable */
	SyHashInsert(&pBase->hDerived, (const void *)SyStringData(&pSub->sName), SyStringLength(&pSub->sName), pSub);
	SyHashResetLoopCursor(&pBase->hAttr);
	/* Copy constants */
	while((pEntry = SyHashGetNextEntry(&pBase->hAttr)) != 0) {
		/* Make sure the constants are not redeclared in the subclass */
		pAttr = (ph7_class_attr *)pEntry->pUserData;
		pName = &pAttr->sName;
		if(SyHashGet(&pSub->hAttr, (const void *)pName->zString, pName->nByte) == 0) {
			/* Install the constant in the subclass */
			rc = SyHashInsert(&pSub->hAttr, (const void *)pName->zString, pName->nByte, pAttr);
			if(rc != SXRET_OK) {
				return rc;
			}
		}
	}
	SyHashResetLoopCursor(&pBase->hMethod);
	/* Copy methods signature */
	while((pEntry = SyHashGetNextEntry(&pBase->hMethod)) != 0) {
		/* Make sure the method are not redeclared in the subclass */
		pMeth = (ph7_class_method *)pEntry->pUserData;
		pName = &pMeth->sFunc.sName;
		if(SyHashGet(&pSub->hMethod, (const void *)pName->zString, pName->nByte) == 0) {
			/* Install the method */
			rc = SyHashInsert(&pSub->hMethod, (const void *)pName->zString, pName->nByte, pMeth);
			if(rc != SXRET_OK) {
				return rc;
			}
		}
	}
	/* Mark as subclass */
	pSub->pBase = pBase;
	/* All done */
	return SXRET_OK;
}
/*
 * Implements an object interface in the given main class.
 * According to the PHP language reference manual.
 *  Object interfaces allow you to create code which specifies which methods a class
 *  must implement, without having to define how these methods are handled.
 *  Interfaces are defined using the interface keyword, in the same way as a standard
 *  class, but without any of the methods having their contents defined.
 *  All methods declared in an interface must be public, this is the nature of an interface.
 *
 * This function return SXRET_OK if the interface was successfully implemented.
 * Any other return value indicates failure and the upper layer must generate an appropriate
 * error message.
 */
PH7_PRIVATE sxi32 PH7_ClassImplement(ph7_class *pMain, ph7_class *pInterface) {
	ph7_class_attr *pAttr;
	SyHashEntry *pEntry;
	SyString *pName;
	sxi32 rc;
	/* First off,copy all constants declared inside the interface */
	SyHashResetLoopCursor(&pInterface->hAttr);
	while((pEntry = SyHashGetNextEntry(&pInterface->hAttr)) != 0) {
		/* Point to the constant declaration */
		pAttr = (ph7_class_attr *)pEntry->pUserData;
		pName = &pAttr->sName;
		/* Make sure the attribute is not redeclared in the main class */
		if(SyHashGet(&pMain->hAttr, pName->zString, pName->nByte) == 0) {
			/* Install the attribute */
			rc = SyHashInsert(&pMain->hAttr, pName->zString, pName->nByte, pAttr);
			if(rc != SXRET_OK) {
				return rc;
			}
		}
	}
	/* Install in the interface container */
	SySetPut(&pMain->aInterface, (const void *)&pInterface);
	/* TICKET 1433-49/1: Symisc eXtension
	 *  A class may not implement all declared interface methods,so there
	 *  is no need for a method installer loop here.
	 */
	return SXRET_OK;
}
/*
 * Create a class instance [i.e: Object in the PHP jargon] at run-time.
 * The following function is called when an object is created at run-time
 * typically when the PH7_OP_NEW/PH7_OP_CLONE instructions are executed.
 * Notes on object creation.
 *
 * According to PHP language reference manual.
 * To create an instance of a class, the new keyword must be used. An object will always
 * be created unless the object has a constructor defined that throws an exception on error.
 * Classes should be defined before instantiation (and in some cases this is a requirement).
 * If a string containing the name of a class is used with new, a new instance of that class
 * will be created. If the class is in a namespace, its fully qualified name must be used when
 * doing this.
 * Example #3 Creating an instance
 * <?php
 *  $instance = new SimpleClass();
 *   // This can also be done with a variable:
 * $className = 'Foo';
 * $instance = new $className(); // Foo()
 * ?>
 * In the class context, it is possible to create a new object by new self and new parent.
 * When assigning an already created instance of a class to a new variable, the new variable
 * will access the same instance as the object that was assigned. This behaviour is the same
 * when passing instances to a function. A copy of an already created object can be made by
 * cloning it.
 * Example #4 Object Assignment
 * <?php
 *  class SimpleClass(){
 *    public $var;
 *  };
 *  $instance = new SimpleClass();
 *  $assigned   =  $instance;
 *  $reference  =& $instance;
 *  $instance->var = '$assigned will have this value';
 *  $instance = null; // $instance and $reference become null
 *  var_dump($instance);
 *  var_dump($reference);
 *  var_dump($assigned);
 * ?>
 * The above example will output:
 * NULL
 * NULL
 * object(SimpleClass)#1 (1) {
 *  ["var"]=>
 *    string(30) "$assigned will have this value"
 * }
 * Example #5 Creating new objects
 * <?php
 * class Test
 * {
 *   static public function getNew()
 *   {
 *       return new static;
 *   }
 * }
 * class Child extends Test
 * {}
 * $obj1 = new Test();
 * $obj2 = new $obj1;
 * var_dump($obj1 !== $obj2);
 * $obj3 = Test::getNew();
 * var_dump($obj3 instanceof Test);
 * $obj4 = Child::getNew();
 * var_dump($obj4 instanceof Child);
 * ?>
 * The above example will output:
 * bool(true)
 * bool(true)
 * bool(true)
 * Note that Symisc Systems have introduced powerfull extension to
 * OO subsystem. For example a class attribute may have any complex
 * expression associated with it when declaring the attribute unlike
 * the standard PHP engine which would allow a single value.
 * Example:
 *  class myClass{
 *    public $var = 25<<1+foo()/bar();
 *  };
 * Refer to the official documentation for more information.
 */
static ph7_class_instance *NewClassInstance(ph7_vm *pVm, ph7_class *pClass) {
	ph7_class_instance *pThis;
	/* Allocate a new instance */
	pThis = (ph7_class_instance *)SyMemBackendPoolAlloc(&pVm->sAllocator, sizeof(ph7_class_instance));
	if(pThis == 0) {
		return 0;
	}
	/* Zero the structure */
	SyZero(pThis, sizeof(ph7_class_instance));
	/* Initialize fields */
	pThis->iRef = 1;
	pThis->pVm = pVm;
	pThis->pClass = pClass;
	SyHashInit(&pThis->hAttr, &pVm->sAllocator, 0, 0);
	return pThis;
}
/*
 * Wrapper around the NewClassInstance() function defined above.
 * See the block comment above for more information.
 */
PH7_PRIVATE ph7_class_instance *PH7_NewClassInstance(ph7_vm *pVm, ph7_class *pClass) {
	ph7_class_instance *pNew;
	sxi32 rc;
	pNew = NewClassInstance(&(*pVm), &(*pClass));
	if(pNew == 0) {
		return 0;
	}
	/* Associate a private VM frame with this class instance */
	rc = PH7_VmCreateClassInstanceFrame(&(*pVm), pNew);
	if(rc != SXRET_OK) {
		SyMemBackendPoolFree(&pVm->sAllocator, pNew);
		return 0;
	}
	return pNew;
}
/*
 * Extract the value of a class instance [i.e: Object in the PHP jargon] attribute.
 * This function never fail.
 */
static ph7_value *ExtractClassAttrValue(ph7_vm *pVm, VmClassAttr *pAttr) {
	/* Extract the value */
	ph7_value *pValue;
	pValue = (ph7_value *)SySetAt(&pVm->aMemObj, pAttr->nIdx);
	return pValue;
}
/*
 * Perform a clone operation on a class instance [i.e: Object in the PHP jargon].
 * The following function is called when an object is cloned at run-time
 * typically when the PH7_OP_CLONE instruction is executed.
 * Notes on object cloning.
 *
 * According to PHP language reference manual.
 * Creating a copy of an object with fully replicated properties is not always the wanted behavior.
 * A good example of the need for copy constructors. Another example is if your object holds a reference
 * to another object which it uses and when you replicate the parent object you want to create
 * a new instance of this other object so that the replica has its own separate copy.
 * An object copy is created by using the clone keyword (which calls the object's __clone() method if possible).
 * An object's __clone() method cannot be called directly.
 * $copy_of_object = clone $object;
 * When an object is cloned, PHP 5 will perform a shallow copy of all of the object's properties.
 * Any properties that are references to other variables, will remain references.
 * Once the cloning is complete, if a __clone() method is defined, then the newly created object's __clone() method
 * will be called, to allow any necessary properties that need to be changed.
 * Example #1 Cloning an object
 * <?php
 * class SubObject
 * {
 *   static $instances = 0;
 *   public $instance;
 *
 *   public function __construct() {
 *       $this->instance = ++self::$instances;
 *   }
 *
 *   public function __clone() {
 *       $this->instance = ++self::$instances;
 *   }
 * }
 *
 * class MyCloneable
 * {
 *   public $object1;
 *   public $object2;
 *
 *   function __clone()
 *   {
 *       // Force a copy of this->object, otherwise
 *       // it will point to same object.
 *       $this->object1 = clone $this->object1;
 *   }
 * }
 * $obj = new MyCloneable();
 * $obj->object1 = new SubObject();
 * $obj->object2 = new SubObject();
 * $obj2 = clone $obj;
 * print("Original Object:\n");
 * print_r($obj);
 * print("Cloned Object:\n");
 * print_r($obj2);
 * ?>
 * The above example will output:
 * Original Object:
 * MyCloneable Object
 * (
 *   [object1] => SubObject Object
 *       (
 *           [instance] => 1
 *       )
 *
 *   [object2] => SubObject Object
 *       (
 *           [instance] => 2
 *       )
 *
 * )
 * Cloned Object:
 * MyCloneable Object
 * (
 *   [object1] => SubObject Object
 *       (
 *           [instance] => 3
 *       )
 *
 *   [object2] => SubObject Object
 *       (
 *           [instance] => 2
 *       )
 * )
 */
PH7_PRIVATE ph7_class_instance *PH7_CloneClassInstance(ph7_class_instance *pSrc) {
	ph7_class_instance *pClone;
	ph7_class_method *pMethod;
	SyHashEntry *pEntry2;
	SyHashEntry *pEntry;
	ph7_vm *pVm;
	sxi32 rc;
	/* Allocate a new instance */
	pVm = pSrc->pVm;
	pClone = NewClassInstance(pVm, pSrc->pClass);
	if(pClone == 0) {
		return 0;
	}
	/* Associate a private VM frame with this class instance */
	rc = PH7_VmCreateClassInstanceFrame(pVm, pClone);
	if(rc != SXRET_OK) {
		SyMemBackendPoolFree(&pVm->sAllocator, pClone);
		return 0;
	}
	/* Duplicate object values */
	SyHashResetLoopCursor(&pSrc->hAttr);
	SyHashResetLoopCursor(&pClone->hAttr);
	while((pEntry = SyHashGetNextEntry(&pSrc->hAttr)) != 0 && (pEntry2 = SyHashGetNextEntry(&pClone->hAttr)) != 0) {
		VmClassAttr *pSrcAttr = (VmClassAttr *)pEntry->pUserData;
		VmClassAttr *pDestAttr = (VmClassAttr *)pEntry2->pUserData;
		/* Duplicate non-static attribute */
		if((pSrcAttr->pAttr->iFlags & (PH7_CLASS_ATTR_STATIC | PH7_CLASS_ATTR_CONSTANT)) == 0) {
			ph7_value *pvSrc, *pvDest;
			pvSrc = ExtractClassAttrValue(pVm, pSrcAttr);
			pvDest = ExtractClassAttrValue(pVm, pDestAttr);
			if(pvSrc && pvDest) {
				PH7_MemObjStore(pvSrc, pvDest);
			}
		}
	}
	/* call the __clone method on the cloned object if available */
	pMethod = PH7_ClassExtractMethod(pClone->pClass, "__clone", sizeof("__clone") - 1);
	if(pMethod) {
		if(pMethod->iCloneDepth < 16) {
			pMethod->iCloneDepth++;
			PH7_VmCallClassMethod(pVm, pClone, pMethod, 0, 0, 0);
		} else {
			/* Nesting limit reached */
			PH7_VmThrowError(pVm, 0, PH7_CTX_ERR, "Object clone limit reached,no more call to __clone()");
		}
		/* Reset the cursor */
		pMethod->iCloneDepth = 0;
	}
	/* Return the cloned object */
	return pClone;
}
#define CLASS_INSTANCE_DESTROYED 0x001 /* Instance is released */
/*
 * Release a class instance [i.e: Object in the PHP jargon] and invoke any defined destructor.
 * This routine is invoked as soon as there are no other references to a particular
 * class instance.
 */
static void PH7_ClassInstanceRelease(ph7_class_instance *pThis) {
	ph7_class_method *pDestr;
	SyHashEntry *pEntry;
	ph7_class *pClass;
	ph7_vm *pVm;
	if(pThis->iFlags & CLASS_INSTANCE_DESTROYED) {
		/*
		 * Already destroyed,return immediately.
		 * This could happend if someone perform unset($this) in the destructor body.
		 */
		return;
	}
	/* Mark as destroyed */
	pThis->iFlags |= CLASS_INSTANCE_DESTROYED;
	/* Invoke any defined destructor if available */
	pVm = pThis->pVm;
	pClass = pThis->pClass;
	pDestr = PH7_ClassExtractMethod(pClass, "__destruct", sizeof("__destruct") - 1);
	if(pDestr) {
		/* Invoke the destructor */
		pThis->iRef = 2; /* Prevent garbage collection */
		PH7_VmCallClassMethod(pVm, pThis, pDestr, 0, 0, 0);
	}
	/* Release non-static attributes */
	SyHashResetLoopCursor(&pThis->hAttr);
	while((pEntry = SyHashGetNextEntry(&pThis->hAttr)) != 0) {
		VmClassAttr *pVmAttr = (VmClassAttr *)pEntry->pUserData;
		if((pVmAttr->pAttr->iFlags & (PH7_CLASS_ATTR_STATIC | PH7_CLASS_ATTR_CONSTANT)) == 0) {
			PH7_VmUnsetMemObj(pVm, pVmAttr->nIdx, TRUE);
		}
		SyMemBackendPoolFree(&pVm->sAllocator, pVmAttr);
	}
	/* Release the whole structure */
	SyHashRelease(&pThis->hAttr);
	SyMemBackendPoolFree(&pVm->sAllocator, pThis);
}
/*
 * Decrement the reference count of a class instance [i.e Object in the PHP jargon].
 * If the reference count reaches zero,release the whole instance.
 */
PH7_PRIVATE void PH7_ClassInstanceUnref(ph7_class_instance *pThis) {
	pThis->iRef--;
	if(pThis->iRef < 1) {
		/* No more reference to this instance */
		PH7_ClassInstanceRelease(&(*pThis));
	}
}
/*
 * Compare two class instances [i.e: Objects in the PHP jargon]
 * Note on objects comparison:
 *  According to the PHP langauge reference manual
 *  When using the comparison operator (==), object variables are compared in a simple manner
 *  namely: Two object instances are equal if they have the same attributes and values, and are
 *  instances of the same class.
 *  On the other hand, when using the identity operator (===), object variables are identical
 *  if and only if they refer to the same instance of the same class.
 *  An example will clarify these rules.
 *  Example #1 Example of object comparison
 *  <?php
 *    function bool2str($bool)
 * {
 *   if ($bool === false) {
 *       return 'FALSE';
 *   } else {
 *       return 'TRUE';
 *   }
 * }
 * function compareObjects(&$o1, &$o2)
 * {
 *   echo 'o1 == o2 : ' . bool2str($o1 == $o2) . "\n";
 *   echo 'o1 != o2 : ' . bool2str($o1 != $o2) . "\n";
 *   echo 'o1 === o2 : ' . bool2str($o1 === $o2) . "\n";
 *   echo 'o1 !== o2 : ' . bool2str($o1 !== $o2) . "\n";
 * }
 * class Flag
 * {
 *   public $flag;
 *
 *   function Flag($flag = true) {
 *       $this->flag = $flag;
 *   }
 * }
 *
 * class OtherFlag
 * {
 *   public $flag;
 *
 *   function OtherFlag($flag = true) {
 *       $this->flag = $flag;
 *   }
 * }
 *
 * $o = new Flag();
 * $p = new Flag();
 * $q = $o;
 * $r = new OtherFlag();
 *
 * echo "Two instances of the same class\n";
 * compareObjects($o, $p);
 * echo "\nTwo references to the same instance\n";
 * compareObjects($o, $q);
 * echo "\nInstances of two different classes\n";
 * compareObjects($o, $r);
 * ?>
 * The above example will output:
 * Two instances of the same class
 * o1 == o2 : TRUE
 * o1 != o2 : FALSE
 * o1 === o2 : FALSE
 * o1 !== o2 : TRUE
 * Two references to the same instance
 * o1 == o2 : TRUE
 * o1 != o2 : FALSE
 * o1 === o2 : TRUE
 * o1 !== o2 : FALSE
 * Instances of two different classes
 * o1 == o2 : FALSE
 * o1 != o2 : TRUE
 * o1 === o2 : FALSE
 * o1 !== o2 : TRUE
 *
 * This function return 0 if the objects are equals according to the comprison rules defined above.
 * Any other return values indicates difference.
 */
PH7_PRIVATE sxi32 PH7_ClassInstanceCmp(ph7_class_instance *pLeft, ph7_class_instance *pRight, int bStrict, int iNest) {
	SyHashEntry *pEntry, *pEntry2;
	ph7_value sV1, sV2;
	sxi32 rc;
	if(iNest > 31) {
		/* Nesting limit reached */
		PH7_VmThrowError(pLeft->pVm, 0, PH7_CTX_ERR, "Nesting limit reached: Infinite recursion?");
		return 1;
	}
	/* Comparison is performed only if the objects are instance of the same class */
	if(pLeft->pClass != pRight->pClass) {
		return 1;
	}
	if(bStrict) {
		/*
		 * According to the PHP language reference manual:
		 *  when using the identity operator (===), object variables
		 *  are identical if and only if they refer to the same instance
		 *  of the same class.
		 */
		return !(pLeft == pRight);
	}
	/*
	 * Attribute comparison.
	 * According to the PHP reference manual:
	 *  When using the comparison operator (==), object variables are compared
	 *  in a simple manner, namely: Two object instances are equal if they have
	 *  the same attributes and values, and are instances of the same class.
	 */
	if(pLeft == pRight) {
		/* Same instance,don't bother processing,object are equals */
		return 0;
	}
	SyHashResetLoopCursor(&pLeft->hAttr);
	SyHashResetLoopCursor(&pRight->hAttr);
	PH7_MemObjInit(pLeft->pVm, &sV1);
	PH7_MemObjInit(pLeft->pVm, &sV2);
	sV1.nIdx = sV2.nIdx = SXU32_HIGH;
	while((pEntry = SyHashGetNextEntry(&pLeft->hAttr)) != 0 && (pEntry2 = SyHashGetNextEntry(&pRight->hAttr)) != 0) {
		VmClassAttr *p1 = (VmClassAttr *)pEntry->pUserData;
		VmClassAttr *p2 = (VmClassAttr *)pEntry2->pUserData;
		/* Compare only non-static attribute */
		if((p1->pAttr->iFlags & (PH7_CLASS_ATTR_CONSTANT | PH7_CLASS_ATTR_STATIC)) == 0) {
			ph7_value *pL, *pR;
			pL = ExtractClassAttrValue(pLeft->pVm, p1);
			pR = ExtractClassAttrValue(pRight->pVm, p2);
			if(pL && pR) {
				PH7_MemObjLoad(pL, &sV1);
				PH7_MemObjLoad(pR, &sV2);
				/* Compare the two values now */
				rc = PH7_MemObjCmp(&sV1, &sV2, bStrict, iNest + 1);
				PH7_MemObjRelease(&sV1);
				PH7_MemObjRelease(&sV2);
				if(rc != 0) {
					/* Not equals */
					return rc;
				}
			}
		}
	}
	/* Object are equals */
	return 0;
}
/*
 * Dump a class instance and the store the dump in the BLOB given
 * as the first argument.
 * Note that only non-static/non-constants attribute are dumped.
 * This function is typically invoked when the user issue a call
 * to [var_dump(),var_export(),print_r(),...].
 * This function SXRET_OK on success. Any other return value including
 * SXERR_LIMIT(infinite recursion) indicates failure.
 */
PH7_PRIVATE sxi32 PH7_ClassInstanceDump(SyBlob *pOut, ph7_class_instance *pThis, int ShowType, int nTab, int nDepth) {
	SyHashEntry *pEntry;
	ph7_value *pValue;
	sxi32 rc;
	int i;
	if(nDepth > 31) {
		static const char zInfinite[] = "Nesting limit reached: Infinite recursion?";
		/* Nesting limit reached..halt immediately*/
		SyBlobAppend(&(*pOut), zInfinite, sizeof(zInfinite) - 1);
		if(ShowType) {
			SyBlobAppend(&(*pOut), ")", sizeof(char));
		}
		return SXERR_LIMIT;
	}
	rc = SXRET_OK;
	if(!ShowType) {
		SyBlobAppend(&(*pOut), "Object(", sizeof("Object(") - 1);
	}
	/* Append class name */
	SyBlobFormat(&(*pOut), "%z) {", &pThis->pClass->sName);
#ifdef __WINNT__
	SyBlobAppend(&(*pOut), "\r\n", sizeof("\r\n") - 1);
#else
	SyBlobAppend(&(*pOut), "\n", sizeof(char));
#endif
	/* Dump object attributes */
	SyHashResetLoopCursor(&pThis->hAttr);
	while((pEntry = SyHashGetNextEntry(&pThis->hAttr)) != 0) {
		VmClassAttr *pVmAttr = (VmClassAttr *)pEntry->pUserData;
		if((pVmAttr->pAttr->iFlags & (PH7_CLASS_ATTR_CONSTANT | PH7_CLASS_ATTR_STATIC)) == 0) {
			/* Dump non-static/constant attribute only */
			for(i = 0 ; i < nTab ; i++) {
				SyBlobAppend(&(*pOut), " ", sizeof(char));
			}
			pValue = ExtractClassAttrValue(pThis->pVm, pVmAttr);
			if(pValue) {
				SyBlobFormat(&(*pOut), "['%z'] =>", &pVmAttr->pAttr->sName);
#ifdef __WINNT__
				SyBlobAppend(&(*pOut), "\r\n", sizeof("\r\n") - 1);
#else
				SyBlobAppend(&(*pOut), "\n", sizeof(char));
#endif
				rc = PH7_MemObjDump(&(*pOut), pValue, ShowType, nTab + 1, nDepth, 0);
				if(rc == SXERR_LIMIT) {
					break;
				}
			}
		}
	}
	for(i = 0 ; i < nTab ; i++) {
		SyBlobAppend(&(*pOut), " ", sizeof(char));
	}
	SyBlobAppend(&(*pOut), "}", sizeof(char));
	return rc;
}
/*
 * Call a magic method [i.e: __toString(),__toBool(),__Invoke()...]
 * Return SXRET_OK on successfull call. Any other return value indicates failure.
 * Notes on magic methods.
 * According to the PHP language reference manual.
 *  The function names __construct(), __destruct(), __call(), __callStatic()
 *  __get(),  __toString(), __invoke(), __clone() are magical in PHP classes.
 * You cannot have functions with these names in any of your classes unless
 * you want the magic functionality associated with them.
 * Example of magical methods:
 * __toString()
 *  The __toString() method allows a class to decide how it will react when it is treated like
 *  a string. For example, what echo $obj; will print. This method must return a string.
 *  Example #2 Simple example
 * <?php
 * // Declare a simple class
 * class TestClass
 * {
 *   public $foo;
 *
 *   public function __construct($foo)
 *   {
 *       $this->foo = $foo;
 *   }
 *
 *   public function __toString()
 *   {
 *       return $this->foo;
 *   }
 * }
 * $class = new TestClass('Hello');
 * echo $class;
 * ?>
 * The above example will output:
 *  Hello
 *
 * Note that PH7 does not support all the magical method and introduces __toFloat(),__toInt()
 * which have the same behaviour as __toString() but for float and integer types
 * respectively.
 * Refer to the official documentation for more information.
 */
PH7_PRIVATE sxi32 PH7_ClassInstanceCallMagicMethod(
	ph7_vm *pVm,               /* VM that own all this stuff */
	ph7_class *pClass,         /* Target class */
	ph7_class_instance *pThis, /* Target object */
	const char *zMethod,       /* Magic method name [i.e: __toString()]*/
	sxu32 nByte,               /* zMethod length*/
	const SyString *pAttrName  /* Attribute name */
) {
	ph7_value *apArg[2] = { 0, 0 };
	ph7_class_method *pMeth;
	ph7_value sAttr; /* cc warning */
	sxi32 rc;
	int nArg;
	/* Make sure the magic method is available */
	pMeth = PH7_ClassExtractMethod(&(*pClass), zMethod, nByte);
	if(pMeth == 0) {
		/* No such method,return immediately */
		return SXERR_NOTFOUND;
	}
	nArg = 0;
	/* Copy arguments */
	if(pAttrName) {
		PH7_MemObjInitFromString(pVm, &sAttr, pAttrName);
		sAttr.nIdx = SXU32_HIGH; /* Mark as constant */
		apArg[0] = &sAttr;
		nArg = 1;
	}
	/* Call the magic method now */
	rc = PH7_VmCallClassMethod(pVm, &(*pThis), pMeth, 0, nArg, apArg);
	/* Clean up */
	if(pAttrName) {
		PH7_MemObjRelease(&sAttr);
	}
	return rc;
}
/*
 * Extract the value of a class instance [i.e: Object in the PHP jargon].
 * This function is simply a wrapper on ExtractClassAttrValue().
 */
PH7_PRIVATE ph7_value *PH7_ClassInstanceExtractAttrValue(ph7_class_instance *pThis, VmClassAttr *pAttr) {
	/* Extract the attribute value */
	ph7_value *pValue;
	pValue = ExtractClassAttrValue(pThis->pVm, pAttr);
	return pValue;
}
/*
 * Convert a class instance [i.e: Object in the PHP jargon] into a hashmap [i.e: array in the PHP jargon].
 * Return SXRET_OK on success. Any other value indicates failure.
 * Note on object conversion to array:
 *  Acccording to the PHP language reference manual
 *  If an object is converted to an array, the result is an array whose elements are the object's properties.
 *  The keys are the member variable names.
 *
 *  The following example:
 *  class Test {
 *   public $A = 25<<1;  // 50
 *	 public $c = rand_str(3);   // Random string of length 3
 *	 public $d = rand() & 1023; // Random number between 0..1023
 *  }
 *  var_dump((array) new Test());
 *	Will output:
 *  array(3) {
 *   [A] =>
 *      int(50)
 *   [c] =>
 *     string(3 'aps')
 *   [d] =>
 *     int(991)
 *  }
 * You have noticed that PH7 allow class attributes [i.e: $a,$c,$d in the example above]
 * have any complex expression (even function calls/anonymous functions) as their default
 * value unlike the standard PHP engine.
 * This is a very powerful feature that you have to look at.
 */
PH7_PRIVATE sxi32 PH7_ClassInstanceToHashmap(ph7_class_instance *pThis, ph7_hashmap *pMap) {
	SyHashEntry *pEntry;
	SyString *pAttrName;
	VmClassAttr *pAttr;
	ph7_value *pValue;
	ph7_value sName;
	/* Reset the loop cursor */
	SyHashResetLoopCursor(&pThis->hAttr);
	PH7_MemObjInitFromString(pThis->pVm, &sName, 0);
	while((pEntry = SyHashGetNextEntry(&pThis->hAttr)) != 0) {
		/* Point to the current attribute */
		pAttr = (VmClassAttr *)pEntry->pUserData;
		/* Extract attribute value */
		pValue = ExtractClassAttrValue(pThis->pVm, pAttr);
		if(pValue) {
			/* Build attribute name */
			pAttrName = &pAttr->pAttr->sName;
			PH7_MemObjStringAppend(&sName, pAttrName->zString, pAttrName->nByte);
			/* Perform the insertion */
			PH7_HashmapInsert(pMap, &sName, pValue);
			/* Reset the string cursor */
			SyBlobReset(&sName.sBlob);
		}
	}
	PH7_MemObjRelease(&sName);
	return SXRET_OK;
}
/*
 * Iterate throw class attributes and invoke the given callback [i.e: xWalk()] for each
 * retrieved attribute.
 * Note that argument are passed to the callback by copy. That is,any modification to
 * the attribute value in the callback body will not alter the real attribute value.
 * If the callback wishes to abort processing [i.e: it's invocation] it must return
 * a value different from PH7_OK.
 * Refer to [ph7_object_walk()] for more information.
 */
PH7_PRIVATE sxi32 PH7_ClassInstanceWalk(
	ph7_class_instance *pThis, /* Target object */
	int (*xWalk)(const char *, ph7_value *, void *), /* Walker callback */
	void *pUserData /* Last argument to xWalk() */
) {
	SyHashEntry *pEntry; /* Hash entry */
	VmClassAttr *pAttr;  /* Pointer to the attribute */
	ph7_value *pValue;   /* Attribute value */
	ph7_value sValue;    /* Copy of the attribute value */
	int rc;
	/* Reset the loop cursor */
	SyHashResetLoopCursor(&pThis->hAttr);
	PH7_MemObjInit(pThis->pVm, &sValue);
	/* Start the walk process */
	while((pEntry = SyHashGetNextEntry(&pThis->hAttr)) != 0) {
		/* Point to the current attribute */
		pAttr = (VmClassAttr *)pEntry->pUserData;
		/* Extract attribute value */
		pValue = ExtractClassAttrValue(pThis->pVm, pAttr);
		if(pValue) {
			PH7_MemObjLoad(pValue, &sValue);
			/* Invoke the supplied callback */
			rc =  xWalk(SyStringData(&pAttr->pAttr->sName), &sValue, pUserData);
			PH7_MemObjRelease(&sValue);
			if(rc != PH7_OK) {
				/* User callback request an operation abort */
				return SXERR_ABORT;
			}
		}
	}
	/* All done */
	return SXRET_OK;
}
/*
 * Extract a class attribute value.
 * Return a pointer to the attribute value on success. Otherwise NULL.
 * Note:
 *  Access to static and constant attribute is not allowed. That is,the function
 *  will return NULL in case someone (host-application code) try to extract
 *  a static/constant attribute.
 */
PH7_PRIVATE ph7_value *PH7_ClassInstanceFetchAttr(ph7_class_instance *pThis, const SyString *pName) {
	SyHashEntry *pEntry;
	VmClassAttr *pAttr;
	/* Query the attribute hashtable */
	pEntry = SyHashGet(&pThis->hAttr, (const void *)pName->zString, pName->nByte);
	if(pEntry == 0) {
		/* No such attribute */
		return 0;
	}
	/* Point to the class attribute */
	pAttr = (VmClassAttr *)pEntry->pUserData;
	/* Check if we are dealing with a static/constant attribute */
	if(pAttr->pAttr->iFlags & (PH7_CLASS_ATTR_CONSTANT | PH7_CLASS_ATTR_STATIC)) {
		/* Access is forbidden */
		return 0;
	}
	/* Return the attribute value */
	return ExtractClassAttrValue(pThis->pVm, pAttr);
}