]> Raphaël G. Git Repositories - bbcode/commitdiff
Remove stdbool, seems a deprecaded c99 feature master
authorRaphaël Gertz <git@rapsys.eu>
Sun, 10 Feb 2019 05:39:19 +0000 (06:39 +0100)
committerRaphaël Gertz <git@rapsys.eu>
Sun, 10 Feb 2019 05:39:19 +0000 (06:39 +0100)
Add dump feature header
Add pcre header
Add root zend_string in bbcode_object
Fix type of flag to zend_long
Disable definition of not yet implemented flags
Fix accordingly default flag
Fix bbcode_destroy and bbcode_free prototypes to match php7 definitions
Reimplement bbcode_free to correctly free ressources of custom bbcode_object
Reimplement bbcode_destroy to trigger the destructor on the zend_object
Fix bbcode_clone to correctly duplicate object
Add comment and default members values in bbcode_create function
Allow tag arg to be null (we may use that to implement a default tag array later)
Implement bbcode_gen_child and bbcode_gen_parent functions (used in bbcode_check_tag)
Finish implementation of bbcode_check_tag
Check that tag don't contains a special chars =[]()/\\ which may trigger errors in pcre search
Sanitize warning messages
Add warning for empty parent array
Fix immutable array detection
Disable useless immutable empty array copy
Add open, close and default keys generation for root and multi types
Add child generation for root and multi types
Add parent generation for multi and single types
Add arg and img automatic support in single type
Compute opattern and cpattern, that we may use later for child detection
Duplicate root string to avoid any trouble with refcount
Use latest style in parameters parsing in constructor
Enable exception throwing to prevent the possibility to have an incomplete object returned
Comment useless code in destructor
Add work in progress bbcode_parse implementation
Add work in progress BBCode::parse method
Add various cleanup and comments

bbcode.c

index 5810aae38435468b394260e30b05e66298035a6a..9271a960895afa81d83f46a97eebeb537e5f0d2e 100644 (file)
--- a/bbcode.c
+++ b/bbcode.c
@@ -9,7 +9,11 @@
 #include "Zend/zend_exceptions.h"
 #include "php_bbcode.h"
 
-#include <stdbool.h>
+/* XXX: only required for php_debug_zval_dump */
+#include "ext/standard/php_var.h"
+
+/* XXX: required for pcre_cache_entry */
+#include "ext/pcre/php_pcre.h"
 
 /* If you declare any globals in php_bbcode.h uncomment this:
 ZEND_DECLARE_MODULE_GLOBALS(bbcode)
@@ -18,7 +22,8 @@ ZEND_DECLARE_MODULE_GLOBALS(bbcode)
 typedef struct _bbcode_object {
        HashTable *tag;
        HashTable *smiley;
-       zval flag;
+       zend_string *root;
+       zend_long flag;
        zend_object std;
 } bbcode_object;
 
@@ -44,7 +49,9 @@ zend_class_entry *bbcode_exception_ce;
        GEN(TYPE_ROOT) /*BBCODE_TYPE_ROOT*/\
        GEN(TYPE_MULTI) /*BBCODE_TYPE_ARG|BBCODE_TYPE_OPTARG|BBCODE_TYPE_NOARG*/\
        GEN(TYPE_SINGLE) /*BBCODE_TYPE_SINGLE*/\
-       \
+
+/* TODO: add again each feature when implemented (before beta) */
+#if 0
        /* Quotes */ \
        GEN(QUOTE_DOUBLE) /*BBCODE_ARG_DOUBLE_QUOTE*/\
        GEN(QUOTE_SIMPLE) /*BBCODE_ARG_SINGLE_QUOTE*/\
@@ -65,6 +72,7 @@ zend_class_entry *bbcode_exception_ce;
        GEN(REMOVE_EMPTY) /*BBCODE_FLAGS_REMOVE_IF_EMPTY*/\
        GEN(REMOVE_CONTENT) /*BBCODE_FLAGS_CDATA_NOT_ALLOWED*/\
        GEN(REMOVE_REOPEN) /*BBCODE_FLAGS_DENY_REOPEN_CHILD*/
+#endif
 
 
 /* Generate enum version */
@@ -104,12 +112,18 @@ enum {
 };
 
 /* Set default flag 
- * TODO: Set it in an ini ? */
+ * TODO: Implement it in an ini file ? (later maybe) */
+#define BBCODE_DEFAULT_FLAG 0
+#if 0
 #define BBCODE_DEFAULT_FLAG BBCODE_QUOTE_DOUBLE|BBCODE_QUOTE_SIMPLE|BBCODE_QUOTE_ESCAPE|BBCODE_CORRECT_AUTO|BBCODE_CORRECT_REOPEN|BBCODE_SMILEY_ON|BBCODE_CLOSE_AUTO|BBCODE_REMOVE_EMPTY
+#endif
+
+/* Set debug flag */
+#define BBCODE_DEBUG 0
 
 /* {{{ Function prototypes (bbcode_destroy|bbcode_free|bbcode_clone|bbcode_create) */
-static void bbcode_destroy(bbcode_object *obj TSRMLS_DC);
-static void bbcode_free(bbcode_object *obj TSRMLS_DC);
+static void bbcode_destroy(zend_object *obj TSRMLS_DC);
+static void bbcode_free(zend_object *obj TSRMLS_DC);
 static zend_object *bbcode_clone(zval *obj TSRMLS_DC);
 static zend_object *bbcode_create(zend_class_entry *ce TSRMLS_DC);
 /* }}} */
@@ -126,80 +140,137 @@ static inline bbcode_object* bbcode_fetch(zend_object *obj TSRMLS_DC) {
 #define Z_BBCODE_P(zv) bbcode_fetch(Z_OBJ_P((zv)))
 /* }}} */
 
-/* {{{ static void bbcode_destroy(bbcode_object *obj TSRMLS_DC) {
+/* {{{ static void bbcode_destroy(zend_object *obj TSRMLS_DC) {
  * BBCode object destroy call */
-static void bbcode_destroy(bbcode_object *obj TSRMLS_DC) {
-       //zend_objects_destroy_object(&obj->std);
-       zend_objects_destroy_object((zend_object *)obj);
-
-       //zend_object_std_dtor(&obj->std TSRMLS_CC);
-
-       /*if (obj->flag) {
-               efree(obj->flag);
-       }*/
-
-       //efree(obj);
+static void bbcode_destroy(zend_object *obj TSRMLS_DC) {
+       /* Trigger user land destructor on obj */
+       zend_objects_destroy_object(obj);
 }
 /* }}} */
 
-/* {{{ static void bbcode_free(bbcode_object *obj TSRMLS_DC) {
+/* {{{ static void bbcode_free(zend_object *obj TSRMLS_DC) {
  * BBCode object free call */
-static void bbcode_free(bbcode_object *obj TSRMLS_DC) {
+static void bbcode_free(zend_object *obj TSRMLS_DC) {
+       /* Retrieve bbcode object from zend object */
+       bbcode_object *bbobj = bbcode_fetch(obj);
+
+       /* Check if root is present */
+       if (bbobj->root) {
+               /* Release the root string */
+               zend_string_release(bbobj->root);
+       }
 
-       /*if (obj->flag) {
-               efree(obj->flag);
-       }*/
+       /* Check if tag is present */
+       if (bbobj->tag) {
+               /* Release the tag hash */
+               zend_array_destroy(bbobj->tag);
+       }
+
+       /* Check if smiley is present */
+       if (bbobj->smiley) {
+               /* Release the smiley hash */
+               zend_array_destroy(bbobj->smiley);
+       }
 
-       //efree(obj);
-       zend_object_std_dtor((zend_object *)obj);
+       /* Trigger zend object std destructor on obj */
+       zend_object_std_dtor(obj);
+
+       /* Free the bbobj */
+       efree(bbobj);
 }
 /* }}} */
 
 /* {{{ static zend_object *bbcode_clone(zval *obj TSRMLS_DC) {
  * BBCode object clone call */
 static zend_object *bbcode_clone(zval *obj TSRMLS_DC) {
+       /* Fetch pointer to old bbcode object */
+       bbcode_object *oldbbobj = Z_BBCODE_P(obj);
 
-       /* Fetch pointer on old obj */
-       zend_object *oldobj = Z_OBJ_P(obj);
-       /* Create pointer on nex obj */
-       zend_object *newobj = bbcode_create(oldobj->ce);
+       /* Create pointer on new obj */
+       //bbcode_object *newbbobj = bbcode_fetch(bbcode_create(oldbbobj->std.ce));
+       bbcode_object *newbbobj = bbcode_fetch(bbcode_create(Z_OBJ_P(obj)->ce));
 
        /* Call members cloning function */
-       zend_objects_clone_members(newobj, oldobj);
+       zend_objects_clone_members(&newbbobj->std, &oldbbobj->std);
+
+       /* Duplicate flag value */
+       newbbobj->flag = oldbbobj->flag;
+
+       /* Set root as null */
+       newbbobj->root = NULL;
+
+       /* Check if root is available */
+       if (oldbbobj->root) {
+               /* Duplicate root zend string */
+               newbbobj->root = zend_string_copy(oldbbobj->root);
+       }
+
+       /* Set tag as null */
+       newbbobj->tag = NULL;
 
-       /* Retrieve from zend object old bbcode */
-       bbcode_object *bboldobj = Z_BBCODE_P(obj);
+       /* Check if tag is available */
+       if (oldbbobj->tag) {
+               /* Duplicate tag zend array */
+               newbbobj->tag = zend_array_dup(oldbbobj->tag);
+       }
 
-       /* Retrieve from zend object new bbcode */
-       bbcode_object *bbnewobj = bbcode_fetch(newobj);
+       /* Set smiley as null */
+       newbbobj->smiley = NULL;
 
-       /* Restore object member values */
-       //TODO: deal with the other one (tag|smiley)
-       Z_LVAL(bbnewobj->flag) = Z_LVAL(bboldobj->flag);
+       /* Check if smiley is available */
+       if (oldbbobj->smiley) {
+               /* Duplicate smiley zend array */
+               newbbobj->smiley = zend_array_dup(oldbbobj->smiley);
+       }
 
        /* Return the new object */
-       return newobj;
+       return &newbbobj->std;
 }
 /* }}} */
 
 /* {{{ static zend_object *bbcode_create(zend_class_entry *ce TSRMLS_DC) {
  * BBCode object create call */
 static zend_object *bbcode_create(zend_class_entry *ce TSRMLS_DC) {
-       bbcode_object *obj = (bbcode_object *)ecalloc(1, sizeof(bbcode_object) + zend_object_properties_size(ce));//emalloc(sizeof(bbcode_object));
+       /* Allocate object */
+       bbcode_object *obj = (bbcode_object *)ecalloc(1, sizeof(bbcode_object) + zend_object_properties_size(ce));
 
+       /* Init std member */
        zend_object_std_init(&obj->std, ce TSRMLS_CC);
+
+       /* Set object properties */
        object_properties_init(&obj->std, ce TSRMLS_CC);
 
+       /* Duplicate standard object handler */
        memcpy(&bbcode_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers));
+
+       /* Set object offset */
        bbcode_handlers.offset = XtOffsetOf(bbcode_object, std);
+
+       /* Set destructor function */
        bbcode_handlers.dtor_obj = (zend_object_dtor_obj_t) bbcode_destroy;
+
+       /* Set free function */
        bbcode_handlers.free_obj = (zend_object_free_obj_t) bbcode_free;
+
+       /* Set clone function */
        bbcode_handlers.clone_obj = (zend_object_clone_obj_t) bbcode_clone;
 
+       /* Assign custom handler */
        obj->std.handlers = &bbcode_handlers;
 
-       Z_LVAL(obj->flag) = 0;
+       /* Init tag */
+       obj->tag = NULL;
+
+       /* Init smiley */
+       obj->smiley = NULL;
+
+       /* Init root */
+       obj->root = NULL;
+
+       /* Init flag */
+       obj->flag = BBCODE_DEFAULT_FLAG;
 
+       /* Return the std member address */
        return &obj->std;
 }
 /* }}} */
@@ -208,7 +279,7 @@ static zend_object *bbcode_create(zend_class_entry *ce TSRMLS_DC) {
 // ZEND_BEGIN_ARG_INFO_EX(name, _unused, return_reference, required_num_args)
 ZEND_BEGIN_ARG_INFO_EX(bbcode_construct_arginfo, 0, 0, 1)
        // ZEND_ARG_ARRAY_INFO(pass_by_ref, name, allow_null)
-       ZEND_ARG_ARRAY_INFO(0, tag, 1)
+       ZEND_ARG_ARRAY_INFO(0, tag, 0)
        ZEND_ARG_ARRAY_INFO(0, smiley, 1)
        // ZEND_ARG_INFO(pass_by_ref, name)
        ZEND_ARG_INFO(0, flag)
@@ -223,13 +294,157 @@ ZEND_BEGIN_ARG_INFO_EX(bbcode_parse_arginfo, 0, 0, 1)
 ZEND_END_ARG_INFO()
 /* }}} */
 
-/* { { { static zend_bool *bbcode_check_tag(HashTable *tag TSRMLS_DC) {
+/* {{{ static HashTable *bbcode_gen_child(HashTable *ret, HashTable *tag, zend_string *cur TSRMLS_DC) { */
+static HashTable *bbcode_gen_child(HashTable *ret, HashTable *tag, zend_string *cur TSRMLS_DC) {
+       /* Key string */
+       zend_string *key;
+       /* Value */
+       zval *val;
+
+       /* Iterate on each key val until hash end is reached */
+       ZEND_HASH_FOREACH_STR_KEY_VAL(tag, key, val) {
+               /* Check if key is not a string, value is null or not an array */
+               if (key == NULL || val == NULL || Z_TYPE_P(val) != IS_ARRAY) {
+                       /* Return failure, useless to generate a hash when we will trigger an error later */
+                       return NULL;
+               }
+
+               /* Init type val */
+               zval *type;
+
+               /* Check that we have no type or that it's not root type */
+               /* XXX: it's not possible to have in child the root tag */
+               if ((type = zend_hash_str_find(Z_ARR_P(val), "type", strlen("type"))) == NULL || Z_LVAL_P(type) != BBCODE_TYPE_ROOT) {
+                       /* Check that we are not on cur key */
+                       /* XXX: not really required, but it's stupid to allow same tag in child */
+                       //if (strcmp(ZSTR_VAL(cur), ZSTR_VAL(key)) != 0) {
+                               /* Init parent val */
+                               zval *parent;
+
+                               /* Init toInsert bool */
+                               unsigned char toInsert = 0;
+
+                               /* Check if we find parent key */
+                               if ((parent = zend_hash_str_find(Z_ARR_P(val), "parent", strlen("parent"))) != NULL) {
+                                       /* Parent value */
+                                       zval *pval;
+
+                                       /* Iterate on each val until hash end is reached */
+                                       ZEND_HASH_FOREACH_VAL(Z_ARR_P(parent), pval) {
+                                               /* Check that parent val is a string */
+                                               if (Z_TYPE_P(pval) != IS_STRING) {
+                                                       return NULL;
+                                               }
+
+                                               /* Check that tkey is a string and is identical */
+                                               if (strcmp(Z_STRVAL_P(pval), ZSTR_VAL(cur)) == 0) {
+                                                       toInsert = 1;
+                                               }
+                                       } ZEND_HASH_FOREACH_END();
+                               /* No parent key */
+                               } else {
+                                       toInsert = 1;
+                               }
+
+                               /* Add the key */
+                               if (toInsert) {
+                                       /* Init child val */
+                                       zval cval;
+
+                                       /* Set child val */
+                                       ZVAL_STR(&cval, key);
+
+                                       /* Insert the child val in return array */
+                                       ZEND_ASSERT(zend_hash_next_index_insert_new(ret, &cval) != NULL);
+                               }
+                       //}
+               }
+       } ZEND_HASH_FOREACH_END();
+
+       /* Return value */
+       return ret;
+}
+/* }}} */
+
+/* {{{ static HashTable *bbcode_gen_parent(HashTable *ret, HashTable *tag, zend_string *cur TSRMLS_DC) { */
+static HashTable *bbcode_gen_parent(HashTable *ret, HashTable *tag, zend_string *cur TSRMLS_DC) {
+       /* Key string */
+       zend_string *key;
+       /* Value */
+       zval *val;
+
+       /* Iterate on each key val until hash end is reached */
+       ZEND_HASH_FOREACH_STR_KEY_VAL(tag, key, val) {
+               /* Check if key is not a string, value is null or not an array */
+               if (key == NULL || val == NULL || Z_TYPE_P(val) != IS_ARRAY) {
+                       /* Return failure, useless to generate a hash when we will trigger an error later */
+                       return NULL;
+               }
+
+               /* Init type val */
+               zval *type;
+
+               /* Check that we have no type or that it's not single type */
+               /* XXX: it's not possible to have in child the root tag */
+               if ((type = zend_hash_str_find(Z_ARR_P(val), "type", strlen("type"))) == NULL || Z_LVAL_P(type) != BBCODE_TYPE_SINGLE) {
+                       /* Check that we are not on cur key */
+                       /* XXX: not really required, but it's stupid to allow same tag in child */
+                       //if (strcmp(ZSTR_VAL(cur), ZSTR_VAL(key)) != 0) {
+                               /* Init child val */
+                               zval *child;
+
+                               /* Init toInsert bool */
+                               unsigned char toInsert = 0;
+
+                               /* Check if we find child key */
+                               if ((child = zend_hash_str_find(Z_ARR_P(val), "child", strlen("child"))) != NULL) {
+                                       /* Child value */
+                                       zval *cval;
+
+                                       /* Iterate on each val until hash end is reached */
+                                       ZEND_HASH_FOREACH_VAL(Z_ARR_P(child), cval) {
+                                               /* Check that child val is a string */
+                                               if (Z_TYPE_P(cval) != IS_STRING) {
+                                                       return NULL;
+                                               }
+
+                                               /* Check that tkey is a string and is identical */
+                                               if (strcmp(Z_STRVAL_P(cval), ZSTR_VAL(cur)) == 0) {
+                                                       toInsert = 1;
+                                               }
+                                       } ZEND_HASH_FOREACH_END();
+                               /* No child key */
+                               } else {
+                                       toInsert = 1;
+                               }
+
+                               /* Add the key */
+                               if (toInsert) {
+                                       /* Init parent val */
+                                       zval pval;
+
+                                       /* Set parent val */
+                                       ZVAL_STR(&pval, key);
+
+                                       /* Insert the parent val in return array */
+                                       ZEND_ASSERT(zend_hash_next_index_insert_new(ret, &pval) != NULL);
+                               }
+                       //}
+               }
+       } ZEND_HASH_FOREACH_END();
+
+       /* Return value */
+       return ret;
+}
+/* }}} */
+
+/* {{{ static zend_bool *bbcode_check_tag(HashTable *tag TSRMLS_DC) {
  * Check that tag hash is valid */
-static zend_bool bbcode_check_tag(HashTable *tag TSRMLS_DC) {
+static zend_string *bbcode_check_tag(HashTable *tag TSRMLS_DC) {
        /* Key numeric value */
        zend_long idx;
-       /* Key string */
-       zend_string *key;
+       /* Key and root string */
+       zend_string *key, *root = NULL;
        /* Value */
        zval *val;
 
@@ -241,30 +456,36 @@ static zend_bool bbcode_check_tag(HashTable *tag TSRMLS_DC) {
 
        /* Iterate on each key val until hash end is reached */
        ZEND_HASH_FOREACH_KEY_VAL(tag, idx, key, val) {
-               /* Check if key is a string */
-               if (!key) {
+               /* Check if key is not a string */
+               if (key == NULL) {
                        /* Display error about long key */
-                       php_error_docref(NULL TSRMLS_CC, E_ERROR, "Key %ld[%d] from argument tag is not an expected string", idx, pos);
+                       php_error_docref(NULL TSRMLS_CC, E_WARNING, "Key %ld[%d] from argument tag is not an expected string", idx, pos);
                        /* Return failure */
-                       return 0;
+                       return NULL;
+               /* Check that key do not contains a special character */
+               } else if (strchr(ZSTR_VAL(key), '=') != NULL || strchr(ZSTR_VAL(key), '[') != NULL || strchr(ZSTR_VAL(key), ']') != NULL || strchr(ZSTR_VAL(key), '(') != NULL || strchr(ZSTR_VAL(key), ')') != NULL || strchr(ZSTR_VAL(key), '/') != NULL || strchr(ZSTR_VAL(key), '\\') != NULL) {
+                       /* Display error about key containing a special char */
+                       php_error_docref(NULL TSRMLS_CC, E_WARNING, "Key %s[%d] from argument tag contains a special forbidden character: =[]()/\\", ZSTR_VAL(key), pos);
+                       /* Return failure */
+                       return NULL;
                }
 
                /* Check that value exists */
                if (val == NULL) {
                        /* Display error about long key */
-                       php_error_docref(NULL TSRMLS_CC, E_ERROR, "Value for key %s[%d] from argument tag is NULL instead of expected array", ZSTR_VAL(key), pos);
+                       php_error_docref(NULL TSRMLS_CC, E_WARNING, "Value for key %s[%d] from argument tag is NULL instead of expected array", ZSTR_VAL(key), pos);
                        /* Return failure */
-                       return 0;
+                       return NULL;
                /* Check that value is an array */
                } else if (Z_TYPE_P(val) != IS_ARRAY) {
                        /* Display error about long key */
-                       php_error_docref(NULL TSRMLS_CC, E_ERROR, "Value for key %s[%d] from argument tag is %s[%d] instead of expected array", ZSTR_VAL(key), pos, BBCODE_GET_TYPE(Z_TYPE_P(val)), Z_TYPE_P(val));
+                       php_error_docref(NULL TSRMLS_CC, E_WARNING, "Value for key %s[%d] from argument tag is %s[%d] instead of expected array", ZSTR_VAL(key), pos, BBCODE_GET_TYPE(Z_TYPE_P(val)), Z_TYPE_P(val));
                        /* Return failure */
-                       return 0;
+                       return NULL;
                }
 
                /* Store child tag array */
-               HashTable *ctag = Z_ARRVAL_P(val);
+               HashTable *ctag = Z_ARR_P(val);
                /* Child key numeric value */
                zend_long cidx;
                /* Child key string */
@@ -272,28 +493,31 @@ static zend_bool bbcode_check_tag(HashTable *tag TSRMLS_DC) {
                /* Child value */
                zval *cval;
 
+               /* Buffer */
+               char *buf;
+
                /* Child key position */
-               int cpos = 0, type;
+               int cpos = 0, type, len;
 
                /* Detect if entry has a type, parent, child, open, close, default or arg key to display error if mandatory field is missing */
-               bool hasType = false, hasParent = false, hasChild = false, hasOpen = false, hasClose = false, hasDefault = false, hasArg = false;
+               unsigned char hasType = 0, hasParent = 0, hasChild = 0, hasOpen = 0, hasClose = 0, hasDefault = 0, hasArg = 0;
 
                /* Iterate on each ckey cval until hash end is reached */
                ZEND_HASH_FOREACH_KEY_VAL(ctag, cidx, ckey, cval) {
                        /* Check if ckey is a string */
                        if (!ckey) {
                                /* Display error about long ckey */
-                               php_error_docref(NULL TSRMLS_CC, E_ERROR, "Key %s[%d]/%ld[%d] from argument tag is not an expected string", ZSTR_VAL(key), pos, cidx, cpos);
+                               php_error_docref(NULL TSRMLS_CC, E_WARNING, "Key %s[%d]/%ld[%d] from argument tag is not an expected string", ZSTR_VAL(key), pos, cidx, cpos);
                                /* Return failure */
-                               return 0;
+                               return NULL;
                        }
 
                        /* Check if ckey is empty */
                        if (ZSTR_LEN(ckey) == 0) {
                                /* Display error about long key */
-                               php_error_docref(NULL TSRMLS_CC, E_ERROR, "Key %s[%d]/%s[%d] from argument tag is empty not one of expected type|parent|child|open|close|default string", ZSTR_VAL(key), pos, ZSTR_VAL(ckey), cpos);
+                               php_error_docref(NULL TSRMLS_CC, E_WARNING, "Key %s[%d]/%s[%d] from argument tag is empty not one of expected type|parent|child|open|close|default string", ZSTR_VAL(key), pos, ZSTR_VAL(ckey), cpos);
                                /* Return failure */
-                               return 0;
+                               return NULL;
                        /* Check if current ckey is type */
                        } else if (strcmp("type", ZSTR_VAL(ckey)) == 0) {
                                /* Extract type */
@@ -306,9 +530,9 @@ static zend_bool bbcode_check_tag(HashTable *tag TSRMLS_DC) {
                                        type != BBCODE_TYPE_SINGLE
                                ) {
                                        /* Display error about unexpected type value */
-                                       php_error_docref(NULL TSRMLS_CC, E_ERROR, "Value '%d' for key %s[%d]/%s[%d] from argument tag is not one of expected BBCODE::TYPE_(ROOT|MULTI|SINGLE) constant", type, ZSTR_VAL(key), pos, ZSTR_VAL(ckey), cpos);
+                                       php_error_docref(NULL TSRMLS_CC, E_WARNING, "Value '%d' for key %s[%d]/%s[%d] from argument tag is not one of expected BBCODE::TYPE_(ROOT|MULTI|SINGLE) constant", type, ZSTR_VAL(key), pos, ZSTR_VAL(ckey), cpos);
                                        /* Return failure */
-                                       return 0;
+                                       return NULL;
                                /* Check if root */
                                } else if (type == BBCODE_TYPE_ROOT) {
                                        /* Grow hasRoot */
@@ -316,21 +540,27 @@ static zend_bool bbcode_check_tag(HashTable *tag TSRMLS_DC) {
                                }
 
                                /* Set hasType */
-                               hasType = true;
+                               hasType = 1;
                        /* Check if current key is parent */
                        } else if (strcmp("parent", ZSTR_VAL(ckey)) == 0) {
                                /* Check that value exists */
                                if (cval == NULL) {
                                        /* Display error about long key */
-                                       php_error_docref(NULL TSRMLS_CC, E_ERROR, "Value for key %s[%d]/%s[%d] from argument tag is NULL instead of expected array", ZSTR_VAL(key), pos, ZSTR_VAL(ckey), cpos);
+                                       php_error_docref(NULL TSRMLS_CC, E_WARNING, "Value for key %s[%d]/%s[%d] from argument tag is NULL instead of expected array", ZSTR_VAL(key), pos, ZSTR_VAL(ckey), cpos);
                                        /* Return failure */
-                                       return 0;
+                                       return NULL;
                                /* Check that value is an array */
                                } else if (Z_TYPE_P(cval) != IS_ARRAY) {
                                        /* Display error about long key */
-                                       php_error_docref(NULL TSRMLS_CC, E_ERROR, "Value for key %s[%d]/%s[%d] from argument tag is %s[%d] instead of expected array", ZSTR_VAL(key), pos, ZSTR_VAL(ckey), cpos, BBCODE_GET_TYPE(Z_TYPE_P(cval)), Z_TYPE_P(cval));
+                                       php_error_docref(NULL TSRMLS_CC, E_WARNING, "Value for key %s[%d]/%s[%d] from argument tag is %s[%d] instead of expected array", ZSTR_VAL(key), pos, ZSTR_VAL(ckey), cpos, BBCODE_GET_TYPE(Z_TYPE_P(cval)), Z_TYPE_P(cval));
+                                       /* Return failure */
+                                       return NULL;
+                               /* Check that value is an non empty array */
+                               } else if (zend_array_count(Z_ARR_P(cval)) == 0) {
+                                       /* Display error about empty parent array */
+                                       php_error_docref(NULL TSRMLS_CC, E_WARNING, "Value for key %s[%d]/%s[%d] from argument tag is an empty array instead of expected filled array", ZSTR_VAL(key), pos, ZSTR_VAL(ckey), cpos);
                                        /* Return failure */
-                                       return 0;
+                                       return NULL;
                                }
 
                                /* Parent value */
@@ -340,36 +570,37 @@ static zend_bool bbcode_check_tag(HashTable *tag TSRMLS_DC) {
                                int ppos = 0;
 
                                /* Iterate on each val until hash end is reached */
-                               ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(cval), pval) {
+                               ZEND_HASH_FOREACH_VAL(Z_ARR_P(cval), pval) {
                                        /* Check that parent val is a string */
+                                       /* XXX: pval == NULL case is catched here too */
                                        if (Z_TYPE_P(pval) != IS_STRING) {
                                                /* Display error about not string */
-                                               php_error_docref(NULL TSRMLS_CC, E_ERROR, "Parent value %s[%d] for key %s[%d]/%s[%d] is not an expected string", Z_STRVAL_P(pval), ppos, ZSTR_VAL(key), pos, ZSTR_VAL(ckey), cpos);
+                                               php_error_docref(NULL TSRMLS_CC, E_WARNING, "Value for key %s[%d]/%s[%d]/[%d] from argument tag is %s[%d] instead of expected string", ZSTR_VAL(key), pos, ZSTR_VAL(ckey), cpos, ppos, BBCODE_GET_TYPE(Z_TYPE_P(pval)), Z_TYPE_P(pval));
                                                /* Return failure */
-                                               return 0;
+                                               return NULL;
                                        }
 
                                        /* Tag key */
                                        zend_string *tkey;
 
                                        /* Reset found flag */
-                                       bool found = false;
+                                       unsigned char found = 0;
 
                                        /* Iterate on each key until hash end is reached */
                                        ZEND_HASH_FOREACH_STR_KEY(tag, tkey) {
                                                /* Check that tkey is a string and is identical */
                                                if (tkey != NULL && strcmp(Z_STRVAL_P(pval), ZSTR_VAL(tkey)) == 0) {
                                                        /* We found parent value in tag keys*/
-                                                       found = true;
+                                                       found = 1;
                                                }
                                        } ZEND_HASH_FOREACH_END();
 
                                        /* Check if we found the key */
                                        if (!found) {
                                                /* Display error about long key */
-                                               php_error_docref(NULL TSRMLS_CC, E_ERROR, "Parent value %s for key %s[%d]/%s[%d] is not present in tag keys", Z_STRVAL_P(pval), ZSTR_VAL(key), pos, ZSTR_VAL(ckey), cpos);
+                                               php_error_docref(NULL TSRMLS_CC, E_WARNING, "Value for key %s[%d]/%s[%d]/%s[%d] from argument tag is not present in tag keys", ZSTR_VAL(key), pos, ZSTR_VAL(ckey), cpos, Z_STRVAL_P(pval), ppos);
                                                /* Return failure */
-                                               return 0;
+                                               return NULL;
                                        }
 
                                        /* Grow ppos */
@@ -377,21 +608,21 @@ static zend_bool bbcode_check_tag(HashTable *tag TSRMLS_DC) {
                                } ZEND_HASH_FOREACH_END();
 
                                /* Set hasParent */
-                               hasParent = true;
+                               hasParent = 1;
                        /* Check if current key is child */
                        } else if (strcmp("child", ZSTR_VAL(ckey)) == 0) {
                                /* Check that value exists */
                                if (cval == NULL) {
                                        /* Display error about long key */
-                                       php_error_docref(NULL TSRMLS_CC, E_ERROR, "Value for key %s[%d]/%s[%d] from argument tag is NULL instead of expected array", ZSTR_VAL(key), pos, ZSTR_VAL(ckey), cpos);
+                                       php_error_docref(NULL TSRMLS_CC, E_WARNING, "Value for key %s[%d]/%s[%d] from argument tag is NULL instead of expected array", ZSTR_VAL(key), pos, ZSTR_VAL(ckey), cpos);
                                        /* Return failure */
-                                       return 0;
+                                       return NULL;
                                /* Check that value is an array */
                                } else if (Z_TYPE_P(cval) != IS_ARRAY) {
                                        /* Display error about long key */
-                                       php_error_docref(NULL TSRMLS_CC, E_ERROR, "Value for key %s[%d]/%s[%d] from argument tag is %s[%d] instead of expected array", ZSTR_VAL(key), pos, ZSTR_VAL(ckey), cpos, BBCODE_GET_TYPE(Z_TYPE_P(cval)), Z_TYPE_P(cval));
+                                       php_error_docref(NULL TSRMLS_CC, E_WARNING, "Value for key %s[%d]/%s[%d] from argument tag is %s[%d] instead of expected array", ZSTR_VAL(key), pos, ZSTR_VAL(ckey), cpos, BBCODE_GET_TYPE(Z_TYPE_P(cval)), Z_TYPE_P(cval));
                                        /* Return failure */
-                                       return 0;
+                                       return NULL;
                                }
 
                                /* Child value */
@@ -401,36 +632,37 @@ static zend_bool bbcode_check_tag(HashTable *tag TSRMLS_DC) {
                                int ccpos = 0;
 
                                /* Iterate on each val until hash end is reached */
-                               ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(cval), ccval) {
+                               ZEND_HASH_FOREACH_VAL(Z_ARR_P(cval), ccval) {
                                        /* Check that child val is a string */
+                                       /* XXX: ccval == NULL case is catched here too */
                                        if (Z_TYPE_P(ccval) != IS_STRING) {
                                                /* Display error about not string */
-                                               php_error_docref(NULL TSRMLS_CC, E_ERROR, "Child value %s[%d] for key %s[%d]/%s[%d] is not an expected string", Z_STRVAL_P(ccval), ccpos, ZSTR_VAL(key), pos, ZSTR_VAL(ckey), cpos);
+                                               php_error_docref(NULL TSRMLS_CC, E_WARNING, "Value for key %s[%d]/%s[%d]/[%d] from argument tag is %s[%d] instead of expected string", ZSTR_VAL(key), pos, ZSTR_VAL(ckey), cpos, ccpos, BBCODE_GET_TYPE(Z_TYPE_P(ccval)), Z_TYPE_P(ccval));
                                                /* Return failure */
-                                               return 0;
+                                               return NULL;
                                        }
 
                                        /* Tag key */
                                        zend_string *tkey;
 
                                        /* Reset found flag */
-                                       bool found = false;
+                                       unsigned char found = 0;
 
                                        /* Iterate on each key until hash end is reached */
                                        ZEND_HASH_FOREACH_STR_KEY(tag, tkey) {
                                                /* Check that tkey is a string and is identical */
                                                if (tkey != NULL && strcmp(Z_STRVAL_P(ccval), ZSTR_VAL(tkey)) == 0) {
                                                        /* We found child value in tag keys*/
-                                                       found = true;
+                                                       found = 1;
                                                }
                                        } ZEND_HASH_FOREACH_END();
 
                                        /* Check if we found the key */
                                        if (!found) {
                                                /* Display error about long key */
-                                               php_error_docref(NULL TSRMLS_CC, E_ERROR, "Child value %s for key %s[%d]/%s[%d] is not present in tag keys", Z_STRVAL_P(ccval), ZSTR_VAL(key), pos, ZSTR_VAL(ckey), cpos);
+                                               php_error_docref(NULL TSRMLS_CC, E_WARNING, "Child value %s for key %s[%d]/%s[%d] is not present in tag keys", Z_STRVAL_P(ccval), ZSTR_VAL(key), pos, ZSTR_VAL(ckey), cpos);
                                                /* Return failure */
-                                               return 0;
+                                               return NULL;
                                        }
 
                                        /* Grow ccpos */
@@ -438,54 +670,59 @@ static zend_bool bbcode_check_tag(HashTable *tag TSRMLS_DC) {
                                } ZEND_HASH_FOREACH_END();
 
                                /* Set hasChild */
-                               hasChild = true;
+                               hasChild = 1;
                        /* Check if current key is open */
                        } else if (strcmp("open", ZSTR_VAL(ckey)) == 0) {
                                /* Set hasOpen */
-                               hasOpen = true;
+                               hasOpen = 1;
                        /* Check if current key is close */
                        } else if (strcmp("close", ZSTR_VAL(ckey)) == 0) {
                                /* Set hasClose */
-                               hasClose = true;
+                               hasClose = 1;
                        /* Check if current key is default */
                        } else if (strcmp("default", ZSTR_VAL(ckey)) == 0) {
                                /* Set hasDefault */
-                               hasDefault = true;
+                               hasDefault = 1;
                        /* Check if current key is arg */
                        } else if (strcmp("arg", ZSTR_VAL(ckey)) == 0) {
                                /* Set hasArg */
-                               hasArg = true;
+                               hasArg = 1;
                        /* Check if current key is unknown */
                        } else {
                                /* Display error about long key */
-                               php_error_docref(NULL TSRMLS_CC, E_ERROR, "Key %s[%d]/%s[%d] from argument tag is not one of expected type|parent|child|open|close|default string", ZSTR_VAL(key), pos, ZSTR_VAL(ckey), cpos);
+                               php_error_docref(NULL TSRMLS_CC, E_WARNING, "Key %s[%d]/%s[%d] from argument tag is not one of expected type|parent|child|open|close|default string", ZSTR_VAL(key), pos, ZSTR_VAL(ckey), cpos);
                                /* Return failure */
-                               return 0;
+                               return NULL;
                        }
 
                        /* Grow cpos */
                        cpos++;
                } ZEND_HASH_FOREACH_END();
 
-               /* Check if val array is initialized */
-               /*XXX: this step is required to workaround zend engine half initialized hashtable when val array is empty that trigger a segfault*/
-               if ((GC_FLAGS(Z_ARR_P(val)) & HASH_FLAG_INITIALIZED) != HASH_FLAG_INITIALIZED) {
+               /* Check if val array is immutable */
+               /* XXX: this strange case happen with empty val array, the zend engine don't initialize a usable hashtable, to avoid triggering a segfault later when using the array, we fix it here */
+               if ((Z_GC_FLAGS_P(val) & GC_IMMUTABLE) == GC_IMMUTABLE) {
                        /* Allocate hash table */
-                       /* XXX: this is required to avoid segfault in zend_hash_add when the array is empty */
+                       /* XXX: this is required to avoid segfault in zend_hash_add when the array is immutable */
                        ALLOC_HASHTABLE(Z_ARR_P(val));
+
                        /* Init hash table */
                        zend_hash_init(Z_ARR_P(val), 0, NULL, ZVAL_PTR_DTOR, 0);
-                       zend_hash_copy(Z_ARR_P(val), ctag, (copy_ctor_func_t) zval_add_ref);
+
+                       /* Copy old values in new array */
+                       //XXX: disabled for now, array shoudln't need to be copied, immutable flag seems only present on empty arrays
+                       //zend_hash_copy(Z_ARR_P(val), ctag, (copy_ctor_func_t) zval_add_ref);
                }
 
                /* Check if entry has no type */
+               /* TODO: make depend this code on a parse flag ? */
                if (!hasType) {
-                       /* Type zval */
-                       zval tval;
-
-                       /* Type key string */
+                       /* Type key */
                        zend_string *tkey = zend_string_init("type", strlen("type"), 0);
 
+                       /* Type value */
+                       zval tval;
+
                        /* Check if key is '' */
                        if (ZSTR_LEN(key) == 0) {
                                /* Set tval value to root */
@@ -510,25 +747,148 @@ static zend_bool bbcode_check_tag(HashTable *tag TSRMLS_DC) {
                        zend_string_release(tkey);
 
                        /* Set hasType */
-                       hasType = true;
+                       hasType = 1;
                }
 
                /* Check for root type */
                if (type == BBCODE_TYPE_ROOT) {
+                       /* Set root */
+                       root = key;
+
                        /* Check if has parent */
                        if (hasParent) {
                                /* Display error about parent entry */
-                               php_error_docref(NULL TSRMLS_CC, E_ERROR, "Key %s[%d] from argument tag of type BBCODE::TYPE_ROOT cannot have a parent entry", ZSTR_VAL(key), pos);
+                               php_error_docref(NULL TSRMLS_CC, E_WARNING, "Key %s[%d] from argument tag of type BBCODE::TYPE_ROOT cannot have a parent entry", ZSTR_VAL(key), pos);
                                /* Return failure */
-                               return 0;
+                               return NULL;
+                       }
+
+                       /* Check if has open entry */
+                       if (!hasOpen) {
+                               /* Key string */
+                               zend_string *mkey = zend_string_init("open", strlen("open"), 0);
+
+                               /* Value */
+                               zval mval;
+
+                               /* Check if tag is empty */
+                               if (ZSTR_LEN(key) == 0) {
+                                       /* Init new value */
+                                       ZVAL_EMPTY_STRING(&mval);
+                               /* Non empty tag */
+                               } else {
+                                       /* Init buffer and length */
+                                       buf = (char *)malloc((len = strlen("<>") + ZSTR_LEN(key) + (hasArg?strlen("%s"):0))*sizeof(char));
+
+                                       /* Generate string in  */
+                                       ZEND_ASSERT(sprintf(buf, "<%s%s>", ZSTR_VAL(key), hasArg?"%s":"") == len);
+
+                                       /* Init new value */
+                                       ZVAL_STRINGL(&mval, buf, len);
+
+                                       /* Free buffer */
+                                       free(buf);
+                               }
+
+                               /* Add new key */
+                               ZEND_ASSERT(zend_hash_add(Z_ARR_P(val), mkey, &mval) != NULL);
+
+                               /* Free key */
+                               zend_string_release(mkey);
+
+                               /* Set hasOpen */
+                               hasOpen = 1;
+                       }
+
+                       /* Check if has close entry */
+                       if (!hasClose) {
+                               /* Key string */
+                               zend_string *mkey = zend_string_init("close", strlen("close"), 0);
+
+                               /* Value */
+                               zval mval;
+
+                               /* Check if tag is empty */
+                               if (ZSTR_LEN(key) == 0) {
+                                       /* Init new value */
+                                       ZVAL_EMPTY_STRING(&mval);
+                               /* Non empty tag */
+                               } else {
+                                       /* Init buffer and length */
+                                       buf = (char *)malloc((len = strlen("</>") + ZSTR_LEN(key))*sizeof(char));
+
+                                       /* Generate string in  */
+                                       ZEND_ASSERT(sprintf(buf, "</%s>", ZSTR_VAL(key)) == len);
+
+                                       /* Init new value */
+                                       ZVAL_STRINGL(&mval, buf, len);
+
+                                       /* Free buffer */
+                                       free(buf);
+                               }
+
+                               /* Add new key */
+                               ZEND_ASSERT(zend_hash_add(Z_ARR_P(val), mkey, &mval) != NULL);
+
+                               /* Free key */
+                               zend_string_release(mkey);
+
+                               /* Set hasClose */
+                               hasClose = 1;
+                       }
+
+                       /* Check if has not default */
+                       if (!hasDefault) {
+                               /* Key string */
+                               zend_string *mkey = zend_string_init("default", strlen("default"), 0);
+
+                               /* Value */
+                               zval mval;
+
+                               /* Set value */
+                               ZVAL_STRING(&mval, "%s");
+
+                               /* Add new key */
+                               ZEND_ASSERT(zend_hash_add(Z_ARR_P(val), mkey, &mval) != NULL);
+
+                               /* Free key */
+                               zend_string_release(mkey);
+
+                               /* Set hasDefault */
+                               hasDefault = 1;
                        }
 
-                       /* TODO: Compute child here */
-                       //TODO: set child if not provided for ROOT (has to loop on each possible type and check if a parent array is present with this tag inside, if none set to all possible tag except '' one)
-                       //TODO: display error if type=root and child was not provided| XXX: we can compute that shit here for lazy morons
+                       /* Compute child here */
                        if (!hasChild) {
-                               //TODO compute child by looping on each non root element and element with no parent or with root in parent
-                               //XXX: it seems that a better approach would be to save root element en push each element one by one ?
+                               /* Child value */
+                               zval cval;
+
+                               /* Init new child val array */
+                               ZVAL_NEW_ARR(&cval);
+
+                               /* Init hash table */
+                               zend_hash_init(Z_ARR(cval), 0, NULL, ZVAL_PTR_DTOR, 0);
+
+                               /* Compute and set child here */
+                               /* XXX: we don't check or trigger error inside this function as these tests are done in current loop */
+                               if (bbcode_gen_child(Z_ARR(cval), tag, key) != NULL) {
+                                       /* Child key string */
+                                       zend_string *ckey = zend_string_init("child", strlen("child"), 0);
+
+                                       /* Add new child key */
+                                       ZEND_ASSERT(zend_hash_add(Z_ARR_P(val), ckey, &cval) != NULL);
+
+                                       /* Free type key */
+                                       zend_string_release(ckey);
+
+                                       /* Set hasChild */
+                                       hasChild = 1;
+                               /* Child generation reached an error case */
+                               /* XXX: it will trigger error later */
+                               } else {
+                                       /* Release hash table */
+                                       zend_array_destroy(Z_ARR(cval));
+                               }
                        }
                /* Check for multi type */
                } else if (type == BBCODE_TYPE_MULTI) {
@@ -540,26 +900,33 @@ static zend_bool bbcode_check_tag(HashTable *tag TSRMLS_DC) {
                                /* Value */
                                zval mval;
 
-                               /* Tag */
-                               zend_string *mtag = zend_string_init("<>", strlen("<>") + ZSTR_LEN(key) + (hasArg?strlen("%s"):0), 0);
-                               memcpy(ZSTR_VAL(mtag)+sizeof(char), ZSTR_VAL(key), ZSTR_LEN(key));
-                               if (hasArg) {
-                                       memcpy(ZSTR_VAL(mtag)+(1+ZSTR_LEN(key))*sizeof(char), "%s>", strlen("%s>"));
+                               /* Check if tag is empty */
+                               if (ZSTR_LEN(key) == 0) {
+                                       /* Init new value */
+                                       ZVAL_EMPTY_STRING(&mval);
+                               /* Non empty tag */
                                } else {
-                                       memcpy(ZSTR_VAL(mtag)+(1+ZSTR_LEN(key))*sizeof(char), ">", strlen(">"));
-                               }
+                                       /* Init buffer and length */
+                                       buf = (char *)malloc((len = strlen("<>") + ZSTR_LEN(key) + (hasArg?strlen("%s"):0))*sizeof(char));
 
-                               /* Set value */
-                               ZVAL_STRING(&mval, ZSTR_VAL(mtag));
+                                       /* Generate string in  */
+                                       ZEND_ASSERT(sprintf(buf, "<%s%s>", ZSTR_VAL(key), hasArg?"%s":"") == len);
+
+                                       /* Init new value */
+                                       ZVAL_STRINGL(&mval, buf, len);
+
+                                       /* Free buffer */
+                                       free(buf);
+                               }
 
                                /* Add new key */
                                ZEND_ASSERT(zend_hash_add(Z_ARR_P(val), mkey, &mval) != NULL);
 
-                               /* Free tag */
-                               zend_string_release(mtag);
-
                                /* Free key */
                                zend_string_release(mkey);
+
+                               /* Set hasOpen */
+                               hasOpen = 1;
                        }
 
                        /* Check if has close entry */
@@ -570,22 +937,33 @@ static zend_bool bbcode_check_tag(HashTable *tag TSRMLS_DC) {
                                /* Value */
                                zval mval;
 
-                               /* Tag */
-                               zend_string *mtag = zend_string_init("</>", strlen("</>") + ZSTR_LEN(key), 0);
-                               memcpy(ZSTR_VAL(mtag)+2*sizeof(char), ZSTR_VAL(key), ZSTR_LEN(key));
-                               memcpy(ZSTR_VAL(mtag)+(2+ZSTR_LEN(key))*sizeof(char), ">", strlen(">"));
+                               /* Check if tag is empty */
+                               if (ZSTR_LEN(key) == 0) {
+                                       /* Init new value */
+                                       ZVAL_EMPTY_STRING(&mval);
+                               /* Non empty tag */
+                               } else {
+                                       /* Init buffer and length */
+                                       buf = (char *)malloc((len = strlen("</>") + ZSTR_LEN(key))*sizeof(char));
 
-                               /* Set value */
-                               ZVAL_STRING(&mval, ZSTR_VAL(mtag));
+                                       /* Generate string in  */
+                                       ZEND_ASSERT(sprintf(buf, "</%s>", ZSTR_VAL(key)) == len);
+
+                                       /* Init new value */
+                                       ZVAL_STRINGL(&mval, buf, len);
+
+                                       /* Free buffer */
+                                       free(buf);
+                               }
 
                                /* Add new key */
                                ZEND_ASSERT(zend_hash_add(Z_ARR_P(val), mkey, &mval) != NULL);
 
-                               /* Free tag */
-                               zend_string_release(mtag);
-
                                /* Free key */
                                zend_string_release(mkey);
+
+                               /* Set hasClose */
+                               hasClose = 1;
                        }
 
                        /* Check if has not default */
@@ -604,26 +982,88 @@ static zend_bool bbcode_check_tag(HashTable *tag TSRMLS_DC) {
 
                                /* Free key */
                                zend_string_release(mkey);
+
+                               /* Set hasDefault */
+                               hasDefault = 1;
                        }
 
-                       //TODO: set parent&child if not provided for ARG|OPTARG|NOARG|SINGLE (has to loop on each possible type and check if no child array or present in child array if available)
+                       /* Compute parent here */
+                       if (!hasParent) {
+                               /* Parent key string */
+                               zend_string *pkey = zend_string_init("parent", strlen("parent"), 0);
+
+                               /* Parent value */
+                               zval pval;
+
+                               /* Init new parent val array */
+                               ZVAL_NEW_ARR(&pval);
+
+                               /* Init hash table */
+                               zend_hash_init(Z_ARR(pval), 0, NULL, ZVAL_PTR_DTOR, 0);
+
+                               /* Compute and set parent here */
+                               /* XXX: we don't check or trigger error inside this function as these tests are done in current loop */
+                               /* TODO: change this assert to a if() and free pval on error, error will trigger in later loop (code coverage tests to do) */
+                               ZEND_ASSERT(bbcode_gen_parent(Z_ARR(pval), tag, key) != NULL);
+
+                               /* Add new parent key */
+                               ZEND_ASSERT(zend_hash_add(Z_ARR_P(val), pkey, &pval) != NULL);
+
+                               /* Free type key */
+                               zend_string_release(pkey);
+
+                               /* Set hasParent */
+                               hasParent = 1;
+                       }
+
+                       /* Compute child here */
+                       if (!hasChild) {
+                               /* Child value */
+                               zval cval;
+
+                               /* Init new child val array */
+                               ZVAL_NEW_ARR(&cval);
+
+                               /* Init hash table */
+                               zend_hash_init(Z_ARR(cval), 0, NULL, ZVAL_PTR_DTOR, 0);
+
+                               /* Compute and set child here */
+                               /* XXX: we don't check or trigger error inside this function as these tests are done in current loop */
+                               if (bbcode_gen_child(Z_ARR(cval), tag, key) != NULL) {
+                                       /* Child key string */
+                                       zend_string *ckey = zend_string_init("child", strlen("child"), 0);
 
+                                       /* Add new child key */
+                                       ZEND_ASSERT(zend_hash_add(Z_ARR_P(val), ckey, &cval) != NULL);
+
+                                       /* Free type key */
+                                       zend_string_release(ckey);
+
+                                       /* Set hasChild */
+                                       hasChild = 1;
+                               /* Child generation reached an error case */
+                               /* XXX: it will trigger error later */
+                               } else {
+                                       /* Release hash table */
+                                       zend_array_destroy(Z_ARR(cval));
+                               }
+                       }
                /* Check for single type */
                } else if (type == BBCODE_TYPE_SINGLE) {
                        /* Check if has open entry */
                        if (hasOpen) {
                                /* Display error about open entry */
-                               php_error_docref(NULL TSRMLS_CC, E_ERROR, "Key %s[%d] from argument tag of type BBCODE::TYPE_SINGLE cannot have an open entry", ZSTR_VAL(key), pos);
+                               php_error_docref(NULL TSRMLS_CC, E_WARNING, "Key %s[%d] from argument tag of type BBCODE::TYPE_SINGLE cannot have an open entry", ZSTR_VAL(key), pos);
                                /* Return failure */
-                               return 0;
+                               return NULL;
                        }
 
                        /* Check if has close entry */
                        if (hasClose) {
                                /* Display error about close entry */
-                               php_error_docref(NULL TSRMLS_CC, E_ERROR, "Key %s[%d] from argument tag of type BBCODE::TYPE_SINGLE cannot have a close entry", ZSTR_VAL(key), pos);
+                               php_error_docref(NULL TSRMLS_CC, E_WARNING, "Key %s[%d] from argument tag of type BBCODE::TYPE_SINGLE cannot have a close entry", ZSTR_VAL(key), pos);
                                /* Return failure */
-                               return 0;
+                               return NULL;
                        }
 
                        /* Check if has not default */
@@ -634,57 +1074,238 @@ static zend_bool bbcode_check_tag(HashTable *tag TSRMLS_DC) {
                                /* Value */
                                zval sval;
 
-                               /* Tag */
-                               zend_string *stag;
-
-                               /* Check if jey is img */
+                               /* Check if key is img */
                                if (strcmp("img", ZSTR_VAL(key)) == 0) {
-                                       stag = zend_string_init("<img src=\"%s\"/>", strlen("<img src=\"%s\"/>") + (hasArg?strlen("%s"):0), 0);
-                                       if (hasArg) {
-                                               memcpy(ZSTR_VAL(stag)+strlen("<img src=\"%s\"")*sizeof(char), "%s/>", strlen("%s/>"));
-                                       } else {
-                                               memcpy(ZSTR_VAL(stag)+strlen("<img src=\"%s\"")*sizeof(char), "/>", strlen("/>"));
-                                       }
+                                       /* Init buffer and length */
+                                       buf = (char *)malloc((len = strlen("<img src=\"%s\"/>") + (hasArg?strlen("%s"):0))*sizeof(char));
+
+                                       /* Generate string in  */
+                                       ZEND_ASSERT(sprintf(buf, "<img src=\"%%s\"%s/>", hasArg?"%s":"") == len);
                                } else {
-                                       stag = zend_string_init("</>", strlen("</>") + ZSTR_LEN(key) + (hasArg?strlen("%s"):0), 0);
-                                       memcpy(ZSTR_VAL(stag)+sizeof(char), ZSTR_VAL(key), ZSTR_LEN(key));
-                                       if (hasArg) {
-                                               memcpy(ZSTR_VAL(stag)+(1+ZSTR_LEN(key))*sizeof(char), "%s/>", strlen("%s/>"));
-                                       } else {
-                                               memcpy(ZSTR_VAL(stag)+(1+ZSTR_LEN(key))*sizeof(char), "/>", strlen("/>"));
-                                       }
+                                       /* Init buffer and length */
+                                       buf = (char *)malloc((len = strlen("</>") + ZSTR_LEN(key) + (hasArg?strlen("%s"):0))*sizeof(char));
+
+                                       /* Generate string in  */
+                                       ZEND_ASSERT(sprintf(buf, "<%s%s/>", ZSTR_VAL(key), hasArg?"%s":"") == len);
                                }
 
-                               /* Set value */
-                               ZVAL_STRING(&sval, ZSTR_VAL(stag));
+                               /* Init new value */
+                               ZVAL_STRINGL(&sval, buf, len);
+
+                               /* Free buffer */
+                               free(buf);
 
                                /* Add new key */
                                ZEND_ASSERT(zend_hash_add(Z_ARR_P(val), skey, &sval) != NULL);
 
-                               /* Free tag */
-                               zend_string_release(stag);
-
                                /* Free key */
                                zend_string_release(skey);
+
+                               /* Set hasDefault */
+                               hasDefault = 1;
                        }
 
                        /* Check if has child */
                        if (hasChild) {
                                /* Display error about child entry */
-                               php_error_docref(NULL TSRMLS_CC, E_ERROR, "Key %s[%d] from argument tag of type BBCODE::TYPE_SINGLE cannot have a child entry", ZSTR_VAL(key), pos);
+                               php_error_docref(NULL TSRMLS_CC, E_WARNING, "Key %s[%d] from argument tag of type BBCODE::TYPE_SINGLE cannot have a child entry", ZSTR_VAL(key), pos);
                                /* Return failure */
-                               return 0;
+                               return NULL;
                        }
 
-                       //TODO: set parent if not provided for ARG|OPTARG|NOARG|SINGLE (has to loop on each possible type and check if no child array or present in child array if available)
+                       /* Compute parent here */
+                       if (!hasParent) {
+                               /* Parent key string */
+                               zend_string *pkey = zend_string_init("parent", strlen("parent"), 0);
+
+                               /* Parent value */
+                               zval pval;
+
+                               /* Init new parent val array */
+                               ZVAL_NEW_ARR(&pval);
+
+                               /* Init hash table */
+                               zend_hash_init(Z_ARR(pval), 0, NULL, ZVAL_PTR_DTOR, 0);
+
+                               /* Compute and set parent here */
+                               /* XXX: we don't check or trigger error inside this function as these tests are done in current loop */
+                               /* TODO: change this asser to a if() and free pval on error, error will trigger in later loop (code coverage tests to do) */
+                               ZEND_ASSERT(bbcode_gen_parent(Z_ARR(pval), tag, key) != NULL);
+
+                               /* Add new parent key */
+                               ZEND_ASSERT(zend_hash_add(Z_ARR_P(val), pkey, &pval) != NULL);
+
+                               /* Free type key */
+                               zend_string_release(pkey);
+
+                               /* Set hasParent */
+                               hasParent = 1;
+                       }
                }
 
-#ifndef DEBUG
-               printf("key=>%s\n", ZSTR_VAL(key));
-               fflush(NULL);
-               php_debug_zval_dump(val, 0);
-               fflush(NULL);
-#endif
+               /* Generate open and close pattern for tag with child */
+               if (hasChild) {
+                       /* Child array and value */
+                       zval *child, *ccval;
+
+                       /* Close pattern length */
+                       int len2, cpos2;
+
+                       /* Close pattern buf */
+                       char *buf2;
+
+                       /* Retrieve child array */
+                       /* XXX: child should exists here and be an array */
+                       ZEND_ASSERT(((child = zend_hash_str_find(Z_ARR_P(val), "child", strlen("child"))) != NULL) && Z_TYPE_P(child) == IS_ARRAY);
+
+                       /* Init open pattern buffer and length */
+                       /* XXX: lagest bbcode tag length seems to be 7, we guess that 10 is enough to contain every tag and pipe */
+                       buf = (char *)malloc((len = strlen("/\\[()(?:=([^\\]]*))?\\]/") + zend_array_count(Z_ARR_P(child))*10)*sizeof(char));
+
+                       /* Init close pattern buffer and length */
+                       /* XXX: lagest bbcode tag length seems to be 7, we guess that 10 is enough to contain every tag and pipe */
+                       buf2 = (char *)malloc((len2 = strlen("/\\[\\/()\\]/") + zend_array_count(Z_ARR_P(child))*10)*sizeof(char));
+
+                       /* Init cpos and cpos2 */
+                       cpos = 4; cpos2 = 6;
+
+                       /* Assign open pattern begin */
+                       ZEND_ASSERT(memcpy(buf, &"/\\[(", 4) != NULL);
+
+                       /* Assign close pattern begin */
+                       ZEND_ASSERT(memcpy(buf2, &"/\\[\\/(", 6) != NULL);
+
+                       /* Iterate on each val until hash end is reached */
+                       ZEND_HASH_FOREACH_VAL(Z_ARR_P(child), ccval) {
+                               /* Check that child val is a string */
+                               /* XXX: ccval should be an array here */
+                               ZEND_ASSERT(Z_TYPE_P(ccval) == IS_STRING);
+
+                               /* Init zval node and type */
+                               zval *znode, *ztype;
+
+                               /* Init zend_long type */
+                               zend_long ltype;
+
+                               /* Retrieve the node */
+                               ZEND_ASSERT((znode = zend_hash_find(tag, Z_STR_P(ccval))) != NULL && Z_TYPE_P(znode) == IS_ARRAY);
+
+                               /* Try to retrieve the type */
+                               //ZEND_ASSERT((ztype = zend_hash_str_find(Z_ARR_P(znode), "type", strlen("type"))) != NULL && Z_TYPE_P(ztype) == IS_LONG);
+                               /* XXX: the type may not yet available here, it is fixed when checking the tag itself later */
+                               if ((ztype = zend_hash_str_find(Z_ARR_P(znode), "type", strlen("type"))) == NULL || Z_TYPE_P(ztype) != IS_LONG) {
+                                       /* Check if empty tag */
+                                       if (Z_STRLEN_P(ccval) == 0) {
+                                               /* Set as root */
+                                               ltype = BBCODE_TYPE_ROOT;
+                                       } else if (strcmp("img", Z_STRVAL_P(ccval)) == 0) {
+                                               /* Set as single */
+                                               ltype = BBCODE_TYPE_SINGLE;
+                                       } else {
+                                               /* Set as multi */
+                                               ltype = BBCODE_TYPE_MULTI;
+                                       }
+                               /* Type zval available and usable */
+                               } else {
+                                       /* Set type from zval value */
+                                       ltype = Z_LVAL_P(ztype);
+                               }
+
+                               /* Check if not first string to add */
+                               if (cpos > 4) {
+                                       /* Set pipe before next tag */
+                                       *(buf+cpos) = '|';
+                                       /* Move position */
+                                       cpos++;
+                                       /* Check if not single type */
+                                       if (ltype != BBCODE_TYPE_SINGLE) {
+                                               /* Set pipe before next tag */
+                                               *(buf2+cpos2) = '|';
+                                               /* Move position */
+                                               cpos2++;
+                                       }
+                               }
+
+                               /* Check if not single type */
+                               if (ltype != BBCODE_TYPE_SINGLE) {
+                                       /* Grow buf if necessary */
+                                       if (cpos + Z_STRLEN_P(ccval) + 18 > len) {
+                                               /* We resize buf to current length(cpos), the string size plus eighteen char for trailing pattern */
+                                               buf = (char *)realloc(buf, (len = cpos + Z_STRLEN_P(ccval) + 18)*sizeof(char));
+                                       }
+
+                                       /* Copy string in buf */
+                                       ZEND_ASSERT(memcpy(buf+cpos, Z_STRVAL_P(ccval), Z_STRLEN_P(ccval)) != NULL);
+
+                                       /* Move cpos */
+                                       cpos += Z_STRLEN_P(ccval);
+
+                                       /* Grow buf2 if necessary */
+                                       if (cpos2 + Z_STRLEN_P(ccval) + 4 > len2) {
+                                               /* We resize buf2 to current length(cpos2), the string size plus four char for trailing pattern */
+                                               buf2 = (char *)realloc(buf2, (len2 = cpos2 + Z_STRLEN_P(ccval) + 4)*sizeof(char));
+                                       }
+
+                                       /* Copy string in buf2 */
+                                       ZEND_ASSERT(memcpy(buf2+cpos2, Z_STRVAL_P(ccval), Z_STRLEN_P(ccval)) != NULL);
+
+                                       /* Move cpos2 */
+                                       cpos2 += Z_STRLEN_P(ccval);
+                               /* Single type case */
+                               } else {
+                                       /* Grow buf if necessary */
+                                       if (cpos + Z_STRLEN_P(ccval) + strlen("(?:\\/)?") + 18 > len) {
+                                               /* We resize buf to current length(cpos), the string size plus three plus eighteen char for trailing pattern */
+                                               buf = (char *)realloc(buf, (len = cpos + Z_STRLEN_P(ccval) + strlen("(?:\\/)?") + 18)*sizeof(char));
+                                       }
+
+                                       /* Copy string in buf */
+                                       ZEND_ASSERT(memcpy(buf+cpos, Z_STRVAL_P(ccval), Z_STRLEN_P(ccval)) != NULL);
+
+                                       /* Append pattern in buf */
+                                       ZEND_ASSERT(memcpy(buf+cpos+Z_STRLEN_P(ccval), &"(?:\\/)?", strlen("(?:\\/)?")) != NULL);
+
+                                       /* Move cpos */
+                                       cpos += Z_STRLEN_P(ccval) + strlen("(?:\\/)?");
+                               }
+                       } ZEND_HASH_FOREACH_END();
+
+                       /* Assign open pattern begin */
+                       ZEND_ASSERT(memcpy(buf+cpos, &")(?:=([^\\]]*))?\\]/", 18) != NULL);
+
+                       /* Assign open pattern begin */
+                       ZEND_ASSERT(memcpy(buf2+cpos2, &")\\]/", 4) != NULL);
+
+                       /* Open key string */
+                       zend_string *okey = zend_string_init("opattern", strlen("opattern"), 0);
+
+                       /* Close key string */
+                       zend_string *ckey = zend_string_init("cpattern", strlen("cpattern"), 0);
+
+                       /* Open and close value */
+                       zval oval, cval;
+
+                       /* Set open value */
+                       ZVAL_STRINGL(&oval, buf, cpos + 18);
+
+                       /* Set close value */
+                       ZVAL_STRINGL(&cval, buf2, cpos2 + 4);
+
+                       /* Free buffers */
+                       free(buf); free(buf2);
+
+                       /* Add open key */
+                       ZEND_ASSERT(zend_hash_add(Z_ARR_P(val), okey, &oval) != NULL);
+
+                       /* Add close key */
+                       ZEND_ASSERT(zend_hash_add(Z_ARR_P(val), ckey, &cval) != NULL);
+
+                       /* Free open key */
+                       zend_string_release(okey);
+
+                       /* Free close key */
+                       zend_string_release(ckey);
+               }
 
                /* Grow pos */
                pos++;
@@ -693,13 +1314,19 @@ static zend_bool bbcode_check_tag(HashTable *tag TSRMLS_DC) {
        /* Check if not one unique root */
        if (hasRoot != 1) {
                        /* Display error about multiple root */
-                       php_error_docref(NULL TSRMLS_CC, E_ERROR, "Has %d BBCODE::TYPE_ROOT entry, require one unique", hasRoot);
+                       php_error_docref(NULL TSRMLS_CC, E_WARNING, "Tag array has %d BBCODE::TYPE_ROOT entry instead of expected unique one", hasRoot);
                        /* Return failure */
-                       return 0;
+                       return NULL;
        }
 
+       /* Duplicate root zend string to avoid it beeing freed */
+       return zend_string_copy(root);
+
+#if 0
+
        /* Return success */
-       return 1;
+       return root;
+#endif
 }
 /* }}} */
 
@@ -721,7 +1348,7 @@ static zend_bool bbcode_check_smiley(HashTable *smiley TSRMLS_DC) {
                /* Check that key is a string */
                if (key == NULL) {
                        /* Display error about long key */
-                       php_error_docref(NULL TSRMLS_CC, E_ERROR, "Key %ld[%d] from argument smiley is not an expected string", idx, pos);
+                       php_error_docref(NULL TSRMLS_CC, E_WARNING, "Key %ld[%d] from argument smiley is not an expected string", idx, pos);
                        /* Return failure */
                        return 0;
                }
@@ -729,13 +1356,13 @@ static zend_bool bbcode_check_smiley(HashTable *smiley TSRMLS_DC) {
                /* Check that value exists */
                if (val == NULL) {
                        /* Display error about long key */
-                       php_error_docref(NULL TSRMLS_CC, E_ERROR, "Value for key %s[%d] from argument smiley is NULL instead of expected array", ZSTR_VAL(key), pos);
+                       php_error_docref(NULL TSRMLS_CC, E_WARNING, "Value for key %s[%d] from argument smiley is NULL instead of expected array", ZSTR_VAL(key), pos);
                        /* Return failure */
                        return 0;
                /* Check that value is not an array */
                } else if (Z_TYPE_P(val) == IS_ARRAY) {
                        /* Display error about long key */
-                       php_error_docref(NULL TSRMLS_CC, E_ERROR, "Value for key %s[%d] from argument smiley is an array instead of expected string", ZSTR_VAL(key), pos);
+                       php_error_docref(NULL TSRMLS_CC, E_WARNING, "Value for key %s[%d] from argument smiley is an array instead of expected string", ZSTR_VAL(key), pos);
                        /* Return failure */
                        return 0;
                }
@@ -749,109 +1376,420 @@ static zend_bool bbcode_check_smiley(HashTable *smiley TSRMLS_DC) {
 }
 /* }}} */
 
-/* {{{ PHP_METHOD(BBCode, __construct) {
- */
+/* {{{ PHP_METHOD(BBCode, __construct) { */
 PHP_METHOD(BBCode, __construct) {
+       /* Init tag hash */
        HashTable *tag = NULL;
+
+       /* Init smiley hash */
        HashTable *smiley = NULL;
-       zend_long flag = BBCODE_DEFAULT_FLAG;
 
-/*TODO: init tag with [
-       '' => [ ? ]
-]*/
-/* TODO: init smiley with default list
- * TODO: use prefix in an ini parameter ? ini_setable ?
- * TODO: make sure it's not really a global and per-app param ?
- * */
+       /* Init flag long */
+       zend_long flag = BBCODE_DEFAULT_FLAG;
 
-       /* Retrieve the pointer to this object */
-       bbcode_object *obj = Z_BBCODE_P(getThis());
+       /* Zend error handling */
+       zend_error_handling error_handling;
 
-       /* Verify that the pointer is leading somewhere */
-       assert(obj != NULL);
+       /* TODO: set tag array to a default value like this in case of missing/empty tag array :
+        * [ '' => [ ? ] ]
+        * (maybe later)
+        */
 
-       /* Set default values */
-       obj->tag = tag;
-       obj->smiley = smiley;
-       Z_LVAL(obj->flag) = flag;
+       /* TODO: init smiley with default list ? what about img storage ?
+        * TODO: use prefix in an ini parameter ? ini_setable ?
+        * TODO: make sure it's not really a global and per-app param ?
+        * (maybe later)
+        */
 
+       zend_replace_error_handling(EH_THROW, bbcode_exception_ce, &error_handling);
        /* Parse parameters */
+       /*if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "h|hl", &tag, &smiley, &flag) == FAILURE) { return; }*/
        ZEND_PARSE_PARAMETERS_START(1, 3)
+               /* Tag arg is required */
                Z_PARAM_ARRAY_HT(tag)
-               Z_PARAM_ARRAY_HT(smiley)
+               /* We switch to optional args */
+               Z_PARAM_OPTIONAL
+               /* Smiley arg is optional and may be null */
+               Z_PARAM_ARRAY_HT_EX(smiley, 1, 0)
+               /* Flag arg is optional and may be null */
                Z_PARAM_LONG(flag)
+       /* End parameters parse */
        ZEND_PARSE_PARAMETERS_END();
 
-       /*if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "h|hl", &tag, &smiley, &flag) == FAILURE) {
-               return;
-       }*/
+       /* Retrieve the pointer to this object */
+       bbcode_object *obj = Z_BBCODE_P(getThis());
+
+       /* Verify that the pointer is leading somewhere */
+       assert(obj != NULL);
+
+       /* Set flag value */
+       obj->flag = flag;
+
+       /* Duplicate hash to avoid modifications on original argument */
+       obj->tag = zend_array_dup(tag);
 
        /* Save tag argument hash in current object */
-       //TODO: check tag structure
-       if (bbcode_check_tag(tag)) {
-               obj->tag = tag;
-       } else {
-               zend_hash_destroy(tag);
-               zend_hash_destroy(smiley);
+       if ((obj->root = bbcode_check_tag(obj->tag)) == NULL) {
+               /* Restore error handler */
+               zend_restore_error_handling(&error_handling);
+
+               /* Release the tag hash */
+               zend_array_destroy(obj->tag);
+
+               /* Free the zend object */
+               //XXX: seems useless, it don't seems possible to null the contructor return value...
                ZVAL_NULL(getThis() TSRMLS_CC);
+
+               /* Free the object */
+               efree(obj);
+
+               /* Stop here */
                return;
        }
 
-       /* Save smiley argument hash in current object if provided */
-       //TODO: check smiley structure
+       /* Check if smiley argument is available */
        if (smiley) {
-               if (bbcode_check_smiley(smiley)) {
-                       obj->smiley = smiley;
-               } else {
-                       zend_hash_destroy(tag);
-                       zend_hash_destroy(smiley);
+               /* Duplicate hash to avoid modifications on original argument */
+               obj->smiley = zend_array_dup(smiley);
+
+               /* Save smiley argument hash in current object */
+               if (!bbcode_check_smiley(obj->smiley)) {
+                       /* Restore error handler */
+                       zend_restore_error_handling(&error_handling);
+
+                       /* Release the root string */
+                       zend_string_release(obj->root);
+
+                       /* Release the tag hash */
+                       zend_array_destroy(obj->tag);
+
+                       /* Release the smiley hash */
+                       zend_array_destroy(obj->smiley);
+
+                       /* Free the object */
+                       //XXX: seems useless, it don't seems possible to null the contructor return value...
                        ZVAL_NULL(getThis() TSRMLS_CC);
+
+                       /* Free the object */
+                       efree(obj);
+
+                       /* Stop here */
                        return;
                }
        }
 
-       /* Save flag argument long in current object if provided */
-       if (flag) {
-               Z_LVAL(obj->flag) = flag;
-       }
+       /* Restore error handler */
+       zend_restore_error_handling(&error_handling);
 }
 /* }}} */
 
 /* {{{ PHP_METHOD(BBCode, __destruct) {
+ * XXX: this function is called by bbcode_destroy function
  */
 PHP_METHOD(BBCode, __destruct) {
+//XXX: disable this code, we don't need to retrieve obj as we do nothing with userland destructor for now
+#if 0
        /* Retrieve the pointer to this object */
        bbcode_object *obj = Z_BBCODE_P(getThis());
 
        /* Verify that the pointer is leading somewhere */
        assert(obj != NULL);
+#endif
+}
+/* }}} */
+
+/*  */
+//[HashTable *ret][zend_string *current][char *str][int offset][int length]
+static char *bbcode_parse(HashTable *tag, zend_string *cur, char *rs, int *ros, int *rlen, char *str, int len TSRMLS_DC) {
+       /* Check if return string is not initialized or shorter than wanted length */
+       if (rs == NULL || *rlen - *ros <= 1024) {
+               /* Compute new length */
+               *rlen = (rs == NULL) ? 2048 : *rlen + 1024;
+
+               /* Grow the return string */
+               ZEND_ASSERT((rs = (char *)realloc(rs, (*rlen)*sizeof(char))) != NULL);
+       }
+
+       //DEBUG: for reminder
+       //str=skip0[r]string0[a=arg0]string1[/a]string2[b]string3[/b][/r]skip1
+       //len=64
+
+       /* Init zval for current node and type */
+       zval *znode, *ztype;
+
+       /* Retrieve current tag hash as zval */
+       ZEND_ASSERT((znode = zend_hash_find(tag, cur)) != NULL && Z_TYPE_P(znode) == IS_ARRAY);
+
+       /* Retrieve current tag type */
+       ZEND_ASSERT((ztype = zend_hash_str_find(Z_ARR_P(znode), "type", strlen("type"))) != NULL && Z_TYPE_P(ztype) == IS_LONG);
+
+       /* Retrieve current tag open */
+       /* XXX: may be NULL if not present */
+       zval *zopen = zend_hash_str_find(Z_ARR_P(znode), "open", strlen("open"));
+
+       /* Retrieve current tag close */
+       /* XXX: may be NULL if not present */
+       zval *zclose = zend_hash_str_find(Z_ARR_P(znode), "close", strlen("close"));
+
+       /* Retrieve current tag default */
+       /* XXX: may be NULL if not present */
+       zval *zdefault = zend_hash_str_find(Z_ARR_P(znode), "default", strlen("default"));
+
+       /* Retrieve current tag arg */
+       /* XXX: may be NULL if not present */
+       zval *zarg = zend_hash_str_find(Z_ARR_P(znode), "arg", strlen("arg"));
+
+       /* The arg zend string */
+       zend_string *argstr = NULL;
+
+       /* Check if we have a not root and not empty tag */
+       if (Z_LVAL_P(ztype) != BBCODE_TYPE_ROOT && strcmp(ZSTR_VAL(cur), "") != 0) {
+               /* Regular expression */
+               zend_string *regex;
+
+               /* Open and close pattern */
+               char *openstr, *closestr, *found;
+
+               /* Open and close pattern length */
+               int openlen, closelen;
+
+               /* Alloc open pattern buffer and set length */
+               openstr = (char *)malloc(((openlen = strlen("/\\[\\]/") + (zarg == NULL ? strlen("(?:=[^\\]]*)?") : strlen("(?:=([^\\]]*))?")) + ZSTR_LEN(cur)) + 1)*sizeof(char));
+
+               /* Alloc close pattern buffer and set length */
+               closestr = (char *)malloc(((closelen = strlen("[/]") + ZSTR_LEN(cur)) + 1)*sizeof(char));
+
+               /* Create open tag string */
+               ZEND_ASSERT(snprintf(openstr, openlen + 1, "/\\[%s%s\\]/", ZSTR_VAL(cur), (zarg == NULL ? "(?:=[^\\]]*)?" : "(?:=([^\\]]*))?")) == openlen);
+
+               /* Duplicate open tag in regex */
+               regex = zend_string_init(openstr, openlen, 0);
+
+               /* Free the close string */
+               free(closestr);
+
+               /* Compiled regular expression */
+               pcre_cache_entry *pce;
+
+               /* Array for subpatterns and return value */
+               zval rv, subpats;
+
+               /* Init subpats array */
+               /* XXX: if we don't initialize the array first, some strange memory corruption happen inside php_pcre_match_impl call */
+               array_init(&subpats);
+
+               /* Compile regex or get it from cache. */
+               /* TODO: see if we handle the error here ? (maybe later)
+                * XXX: it seems unlikely any error could be recoverable if pcre function throw something
+                * XXX: special char in tag is already protected earlier
+                * XXX: maybe just check that the pcre error trigger an exception or something uncatchable in all case
+                * XXX: see function pcre_get_compiled_regex_cache in ext/pcre/php_pcre.c +530 */
+               ZEND_ASSERT((pce = pcre_get_compiled_regex_cache(regex)) != NULL);
+
+               /* Increase pce refcount */
+               /* XXX: we use this instead of pce->refcount++; to avoid dereferencing compile error  */
+               php_pcre_pce_incref(pce);
+
+               /* Call zend pcre implementation */
+               php_pcre_match_impl(pce, str, len, &rv, &subpats, 0, 0, 0, 0);
+
+               /* Decrease pce refcount */
+               /* XXX: we use this instead of pce->refcount--; to avoid dereferencing compile error  */
+               php_pcre_pce_decref(pce);
+
+               /* Check that we found an occurence */
+               if (Z_TYPE(rv) == IS_LONG && Z_LVAL(rv) == 1) {
+                       /* Init match zval */
+                       zval *match;
+
+                       /* Init the matched tag string */
+                       //char *tstr;
+
+                       /* Test that subpats is an array */
+                       ZEND_ASSERT(Z_TYPE(subpats) == IS_ARRAY);
+
+                       /* Test that subpats members count is right */
+                       ZEND_ASSERT(zend_array_count(Z_ARR(subpats)) == (zarg == NULL ? 1 : 2));
 
-       /* Free tag hashtable */
-       if (obj->tag) {
-               efree(obj->tag);
+                       /* Retrieve the matched full tag string */
+                       ZEND_ASSERT((match = zend_hash_index_find(Z_ARR(subpats), 0)) != NULL);
+
+                       /* Test that match is a string */
+                       ZEND_ASSERT(Z_TYPE_P(match) == IS_STRING);
+
+                       /* Find the tag in string */
+                       if ((found = (char *)zend_memnstr(str, Z_STRVAL_P(match), Z_STRLEN_P(match), str+len))) {
+                               /* Move str to found position + matched tag string */
+                               str = found + Z_STRLEN_P(match);
+                       }
+
+                       /* Check if have and arg to retrieve */
+                       if (zarg != NULL) {
+                               /* Retrieve the matched arg string */
+                               ZEND_ASSERT((match = zend_hash_index_find(Z_ARR(subpats), 1)) != NULL);
+
+                               /* Test that match is a string */
+                               ZEND_ASSERT(Z_TYPE_P(match) == IS_STRING);
+
+                               /* Save arg string */
+                               argstr = Z_STR_P(match);
+                       }
+
+                       //XXX: code unfinished, these todo are normal, work start here :)
+                       //TODO: set the new search position to the current offset plus found subpats[0] string length
+                       //TODO: save argument string for appending in return string if zarg is not NULL
+                       //TODO: see what happen if we find start tag and not end
+                       //TODO: see what happen if we find no start tag and no end
+                       //TODO: see what happen if we find no start tag and no end
+
+               /* No occurence found */
+               } else {
+                       /* XXX: nothing to do ? skip end tag search ??? return to parent level ? */
+               }
+
+               /* Destroy subpats array */
+               zend_array_destroy(Z_ARR(subpats));
+
+               /* Create close tag string */
+               //ZEND_ASSERT(snprintf(closestr, closelen + 1, "/\\[\\/%s\\]/", ZSTR_VAL(cur)) == closelen);
+               ZEND_ASSERT(snprintf(closestr, closelen + 1, "[/%s]", ZSTR_VAL(cur)) == closelen);
+
+               /* Look for close tag */
+               /* XXX: watch out, this is a maximum length to consider, the close tag may be present earlier for current leaf */
+               if ((found = (char *)zend_memnrstr(str, closestr, closelen, str+len))) {
+                       /* Reduce len by the size of trailing string since close tag */
+                       len -= len + str - found;
+               }
+
+               /* Free openstr and closestr */
+               free(openstr);
+       }
+
+       printf("str=%.*s\n", len, str);
+       fflush(NULL);
+
+       return NULL;
+
+#if 0
+       fflush(NULL);
+       printf("str=%s\n", str);
+#endif
+
+       /* XXX: debug */
+       zval ztag;
+       ZVAL_ARR(&ztag, tag);
+       php_debug_zval_dump(&ztag, 0);
+       fflush(NULL);
+       exit(1);
+
+       php_debug_zval_dump(zopen, 0);
+       php_debug_zval_dump(zclose, 0);
+       php_debug_zval_dump(zdefault, 0);
+       fflush(NULL);
+       exit(1);
+
+       if (argstr != NULL) {
+               printf("arg=%s[%ld]\n", ZSTR_VAL(argstr), ZSTR_LEN(argstr));
+               fflush(NULL);
+               exit(1);
        }
 
-       /* Free smiley hashtable */
-       if (obj->smiley) {
-               efree(obj->smiley);
+       //XXX: same here unfinished, code start here :)
+       //TODO: check what happen when we have something like that : [t]s[/t][t]p[/t]
+       //1: compute open and close tag
+       //2: extract end of open tag position and begin of close tag reverse searched in current space
+       //(if we don't find end tag, consider positionning to end of string or rise error ? parse flag involved here)
+       //3: append to return string open tag
+       //4: loop for child tags in subspace and trigger recursion on space between current open and close tags
+       //(loop involved here)
+       //5: duplicate plain strings between each child in return string
+       //6: append the close tag
+
+#if 0
+       /* Check if non empty string (other tag that empty root) */
+       if (strcmp(ZSTR_VAL(cur), "") != 0) {
+               //TODO: generate search patterns for opening tag and closing tag
+               //TODO: search position of opening tag
+               //TODO: search close tag
+               //TODO: search for child tag
+       /* Non root tag */
+       } else {
+               //Nothing to do, search offset for child will be 0 and length of search to len
+               //TODO: search current end tag here ?
        }
+
+       /* Search next child node */
+
+       /**/
+       //bbcode_search_node(ret,
+#endif
+
+       /* Return string by default */
+       return rs;
 }
-/* }}} */
 
 /* { { { PHP_METHOD(BBCode, parse) {
  */
 PHP_METHOD(BBCode, parse) {
+       /* Init string to parse */
        zend_string *str = NULL;
 
-       if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "S", &str) == FAILURE) {
-               return;
-       }
+       /* Return string */
+       //TODO: voir si on crée pas une struct pour éviter de trainer 3 arguments
+       char *rs = NULL;
+
+       /* Return offset and length */
+       int ros = 0, rlen = 0;
+
+       /* Parse parameters */
+       /*if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "S", &str) == FAILURE) { return; }*/
+       ZEND_PARSE_PARAMETERS_START(1, 1)
+               Z_PARAM_STR(str)
+       ZEND_PARSE_PARAMETERS_END();
+
+       /* Retrieve the pointer to this object */
+       bbcode_object *obj = Z_BBCODE_P(getThis());
+
+       /* Init return string and set length to string * 2 plus last null char */
+       //ZEND_ASSERT((rs = (char *)malloc((rlen = (2*Z_STRLEN(str)) + 1)*sizeof(char)) != NULL);
+
+       /* Go compute the tree */
+       bbcode_parse(obj->tag, obj->root, rs, &ros, &rlen, ZSTR_VAL(str), ZSTR_LEN(str));
+
+       /* Free return string */
+       free(rs);
+
+#if 0
+       /* Retrieve root node */
+       zval *root = zend_hash_find(obj->tag, obj->root);
+
+       bbcode_
+
+                               /* Retrieve child array */
+                               /* XXX: child should exists here and be an array */
+                               ZEND_ASSERT(((child = zend_hash_str_find(Z_ARR_P(val), "child", strlen("child"))) != NULL) && Z_TYPE_P(child) == IS_ARRAY);
+#endif
+
+#if BBCODE_DEBUG
+       /* Tag */
+       zval tag;
+
+       /* Init new child val array */
+       ZVAL_ARR(&tag, obj->tag);
+
+       php_debug_zval_dump(&tag, 0);
+       fflush(NULL);
+#endif
+
+       //printf("flag=%ld\n", obj->flag);
+       //printf("die ici: %s +%d\n", __FILE__, __LINE__);
 
        //TODO: create a tree for storing result
        //TODO: apply recursively search of next possible tags (or a closing parent tag ?) regexp and store result inside
        //TODO: d
        //TODO: apply the smiley parsing on string
 
+       /* TODO: the return string */
        RETURN_STR(str);
 }
 /* }}} */
@@ -870,8 +1808,7 @@ const zend_function_entry bbcode_methods[] = {
 
 /* {{{ PHP_MINIT_FUNCTION
  */
-PHP_MINIT_FUNCTION(bbcode)
-{
+PHP_MINIT_FUNCTION(bbcode) {
        /* If you have INI entries, uncomment these lines
        REGISTER_INI_ENTRIES();
        */
@@ -895,14 +1832,14 @@ PHP_MINIT_FUNCTION(bbcode)
        /* Register BBCodeException class entry */
        bbcode_exception_ce = zend_register_internal_class_ex(&tmp_ce, zend_exception_get_default(TSRMLS_C) TSRMLS_CC);
 
+       /* Send success */
        return SUCCESS;
 }
 /* }}} */
 
 /* {{{ PHP_MSHUTDOWN_FUNCTION
  */
-PHP_MSHUTDOWN_FUNCTION(bbcode)
-{
+PHP_MSHUTDOWN_FUNCTION(bbcode) {
        /* uncomment this line if you have INI entries
        UNREGISTER_INI_ENTRIES();
        */
@@ -912,8 +1849,7 @@ PHP_MSHUTDOWN_FUNCTION(bbcode)
 
 /* {{{ PHP_MINFO_FUNCTION
  */
-PHP_MINFO_FUNCTION(bbcode)
-{
+PHP_MINFO_FUNCTION(bbcode) {
        php_info_print_table_start();
        php_info_print_table_header(2, PHP_BBCODE_NAME " support", "enabled");
        php_info_print_table_row(2, "Extension version", PHP_BBCODE_VERSION);
@@ -929,14 +1865,14 @@ PHP_MINFO_FUNCTION(bbcode)
  */
 zend_module_entry bbcode_module_entry = {
        STANDARD_MODULE_HEADER,
-       PHP_BBCODE_NAME,
-       NULL, /*bbcode_functions,*/
-       PHP_MINIT(bbcode),
-       PHP_MSHUTDOWN(bbcode),
-       NULL,
-       NULL,
-       PHP_MINFO(bbcode),
-       PHP_BBCODE_VERSION,
+       PHP_BBCODE_NAME,                        /* extension name */
+       NULL,                                           /* function list */
+       PHP_MINIT(bbcode),                      /* process startup */
+       PHP_MSHUTDOWN(bbcode),          /* process shutdown */
+       NULL,                                           /* request startup */
+       NULL,                                           /* request shutdown */
+       PHP_MINFO(bbcode),                      /* extension info */
+       PHP_BBCODE_VERSION,                     /* extension version */
        STANDARD_MODULE_PROPERTIES
 };
 /* }}} */