Actualmente, como parte de un par de proyectos que estoy desarrollando, estoy trabajando en una especie de libreria/framework de PHP (no me acaba de gustar el termino de framework y trato de que sea más libreria que framework).
El caso es que para algunos de los componentes necesitaba hacer un parser (también llamado analizador sintáctico), y para el parser necesitaba un tokenizador en PHP (tokenizador = analizador léxico o lexer), y claro está, cuanto más rápido funcione todo mejor (que PHP es interpretado y ya se sabe).
Total, que había dos opciones: o hacerlo desde 0, usando las funciones de string que trae PHP (una opción que se plantea lenta); o bien usar la función token_get_all que está disponible desde la versión 4.2.0.
Personalmente, he preferido usar token_get_all, aunque esta funcion sea, en si misma, un parser de código de PHP. Lo que he hecho es una clase que encapsula la llamada a esta función y devuelve una lista de token ids vs texto.
El truco para llamar a token_get_all es añadir “<?php “ y “?>” al principio y final de la cadena que se desea tokenizar (probablemente el “?>” del final no haga falta).
Si los tokens necesarios para el parser es un subconjunto de los tokens que soporta PHP, entonces no hay ningún problema con usar este método, si no es así, habrá que currarselo un poco más (y como en mi caso sí se trata de un subconjunto, dejo ese tema zanjado en este punto).
El problema de la función token_get_all es que devuelve un array no demasiado normalizado. A veces devuelve un único item (el texto propio del token), mientras que otras veces devuelve un par de elementos en un array (el primero es el token id y el segundo el texto).
Al tema:
<?php class jtokenizer { const TK_UNKNOWN = 0x0000; const TK_ID = 0x0001; const TK_STRING = 0x0002; const TK_PLUS = 0x0003; const TK_MINUS = 0x0004; // ... public static function tokenize ($string) { $tokens = array(); $phptokens = token_get_all ('<?php ' . $string . '?>'); foreach ($phptokens as $ptoken) { $id = self::TK_UNKNOWN; if (is_string ($ptoken)) { $text = $ptoken; switch ($ptoken) { case '+': $id = self::TK_PLUS; break; case '-': $id = self::TK_MINUS; break; //////////////////////////////////////// // Add more tokens here! // E.g: '.', ',', ';', ':', '=', ... //////////////////////////////////////// default: /** handle error here! */ break; } } else { // this should be an array (tokenid, text) list ($tokenid, $text) = $ptoken; switch ($tokenid) { // ignore opening/closing tag case T_OPEN_TAG: $id = NULL; break; case T_CLOSE_TAG: $id = NULL; break; // ignore white spaces case T_WHITESPACE: $id = NULL; break; case T_CONSTANT_ENCAPSED_STRING: $id = self::TK_STRING; // remove ' or " at the beginning and at the end $text = trim($text, $text[0]); break; case T_STRING: $id = self::TK_ID; break; /////////////////////////////////////////// // Add more tokens here! // Get a complete list from: // http://uk3.php.net/manual/en/tokens.php /////////////////////////////////////////// default: /** handle error here! */ break; } } // append the token if ($id !== NULL) { array_push ($tokens, array ($id, $text)); } } return $tokens; } }
Básicamente esto es un esqueleto que podría servir para ejemplificar un poco cómo hacer el parser, aunque faltarían muchas cosas que añadir. Luego tokenizar una cadena sería simplemente hacer una llamada a jtokenizer::tokenize ($string).
En el array devuelto por esta nueva función estarán normalizados todos los tokens con nuestros propios identificadores. Por lo que si nos llega un jtokenizer::T_STRING, sabremos que es un string, y en la posición 1 del array estará el propio texto de la cadena. Y lo mismo si nos llega un jtokenizer::T_PLUS.
Creo que con este ejemplo ya es suficiente para que alguien interesado en el tema pueda hacerse el suyo propio, y si no le gusta devolver un array de arrays de 2 elementos, pues puede devolver lo que quiera :p
Para más información, me remito a la documentación de PHP:
Nota: Si se queire que sea aún más rápido es mejor usar arrays asociativos en vez de switches
English
23/01/2009 at 5:38 am Permalink
You could take a look at the tokenizer used by PHP_CodeSniffer.
It works the same way your class do.
23/01/2009 at 6:26 am Permalink
Jean-Marc thanks for the information, I’ll take a look
22/10/2009 at 8:40 am Permalink
Now, if you or I tried to get this E. ,