За последние 24 часа нас посетил 22691 программист и 1226 роботов. Сейчас ищет 731 программист ...

Посимвольная (Побайтовая) обработка PHP String Tokenizer introduction

Тема в разделе "Решения, алгоритмы", создана пользователем Kutuz, 26 апр 2016.

  1. Kutuz

    Kutuz Новичок

    С нами с:
    10 апр 2016
    Сообщения:
    15
    Симпатии:
    0
    Нужна была обработка / валидация кусков регулярных выражений, 1 вариант оказался самый быстрый, может кому пригодится.
    Данный подход дает начало написанию кастомного процесса проверки/обработки с любыми правилами на каждый токен или последовательность токенов - если подвязать сюда именованые контейнеры памяти, которые бы при необходимости записывали бы данные, чтобы использовать их в каждый следующих токенах. Конечно для PHP это жесть, многие скажут что на это есть PCRE, не всегда регулярки могут справиться с экзотикой, но данный поход как-раз и есть то с чего везде начинаются обработчики регулярных выражений, может кому пригодится для кастомизации.
    Песочница: http://sandbox.onlinephpfunctions.com/code/fbbd048387c03f4b56bb59128895e6670303d8d3
    Код (Text):
    1.  
    2. <?php
    3. /**
    4.  * Class Tokenizer
    5.  * @package string
    6.  */
    7. class Tokenizer implements \Iterator{
    8.  
    9.     /** @var  string */
    10.     protected $string;
    11.  
    12.     /** @var  int */
    13.     protected $position;
    14.  
    15.     /** @var  int */
    16.     protected $length;
    17.  
    18.     /**
    19.     * Tokenizer constructor.
    20.     * @param $string
    21.     */
    22.     public function __construct($string){
    23.         $this->length = strlen($string);
    24.         $this->string = $string;
    25.     }
    26.  
    27.     /**
    28.     * Считать впереди текущей позиции
    29.     * @param int $offset
    30.     * @param int $len
    31.     * @return string
    32.     */
    33.     public function after($len = 1,$offset = 0){
    34.         return substr($this->string,$this->position + $offset,$len);
    35.     }
    36.  
    37.     /**
    38.     * Считать позади текущей позиции
    39.     * @param int $offset
    40.     * @param int $len
    41.     * @return string
    42.     */
    43.     public function before($len = 1,$offset = 0){
    44.         $pos  = $this->position - $offset;
    45.         $start = $pos-$len;
    46.         if($start < 0){
    47.             $len+=$start;
    48.             if(!$len)return '';
    49.             $start = 0;
    50.         }
    51.         $e = substr($this->string,$start,$len);
    52.         return $e;
    53.     }
    54.  
    55.     /**
    56.     * Проверка впереди
    57.     * @param $needle
    58.     * @param bool|false $caseless
    59.     * @param int $offset
    60.     * @return bool
    61.     */
    62.     public function hasAfter($needle, $caseless = false, $offset = 0){
    63.         if(!is_array($needle)){
    64.             $needle = [$needle];
    65.         }
    66.         $ll = null;
    67.         foreach($needle as $item){
    68.             $l = strlen($item);
    69.             if(!isset($s) || $ll != $l){
    70.                 $s = $this->after($l,$offset);
    71.                 $ll = $l;
    72.             }
    73.             if(($caseless && strcasecmp($s,$item)) || (!$caseless && $s === $item)){
    74.                 return true;
    75.             }
    76.         }
    77.  
    78.         return false;
    79.     }
    80.  
    81.     /**
    82.     * Проверка позади
    83.     * @param $needle
    84.     * @param bool|false $caseless
    85.     * @param int $offset
    86.     * @return bool
    87.     */
    88.     public function hasBefore($needle, $caseless = false, $offset = 0){
    89.         if(!is_array($needle)){
    90.             $needle = [$needle];
    91.         }
    92.         $ll = null;
    93.         foreach($needle as $item){
    94.             $l = strlen($item);
    95.             if(!isset($s) || $ll != $l){
    96.                 $s = $this->before($l,$offset);
    97.                 $ll = $l;
    98.             }
    99.             if(($caseless && strcasecmp($s,$item)) || (!$caseless && $s === $item)){
    100.                 return true;
    101.             }
    102.         }
    103.  
    104.         return false;
    105.     }
    106.  
    107.     /**
    108.     * @param callable $handler
    109.     */
    110.     public function handle(callable $handler){
    111.         for($this->position = 0 ; $this->position < $this->length ; $this->position++){
    112.             if(call_user_func($handler,$this,$this->position,$this->length)===false){
    113.                 return;
    114.             }
    115.         }
    116.     }
    117.  
    118.  
    119.     /**
    120.     * @return mixed
    121.     */
    122.     public function current(){
    123.         return $this->string{$this->position};
    124.     }
    125.  
    126.     /**
    127.     *
    128.     */
    129.     public function next(){
    130.         $this->position++;
    131.     }
    132.  
    133.     /**
    134.     * @return int
    135.     */
    136.     public function key(){
    137.         return $this->position;
    138.     }
    139.  
    140.     /**
    141.     * @return bool
    142.     */
    143.     public function valid(){
    144.         return isset($this->string{$this->position});
    145.     }
    146.  
    147.     /**
    148.     *
    149.     */
    150.     public function rewind(){
    151.         $this->position = 0;
    152.     }
    153. }
    154.  
    155. /**
    156.  * Набор 1500 байтов
    157.  */
    158. $string = str_repeat('й(',500).'\\\\\\(';
    159.  
    160.  
    161. /**
    162.  * @Variant
    163.  * 1 Использование побайтовой выборки из источника
    164.  */
    165. echo '<h1>Variant 1.0 ( select char by pos )</h1>';
    166. $t = microtime(true);
    167. $len = strlen($string);
    168. $not_capturing_group_brackets = ['?#','?:','?>','?=','?!','?<=','?<!'];
    169. $opened = [];
    170. $groups_captured = [];
    171. $groups_not_captured = [];
    172. $groups_count = 0;
    173. for($i=0;$i<$len;$i++){
    174.     $token = $string{$i};
    175.     if($token === '('){
    176.         for($backslashes = 0; read_before($string, $i, 1,$backslashes) === '\\' ;$backslashes++){}
    177.         if($backslashes%2==0){
    178.             $capture = !has_after($string, $i,$not_capturing_group_brackets);
    179.             $opened[] = [$i,$capture];
    180.         }
    181.     }elseif($token===')'){
    182.         for($backslashes = 0; read_before($string, $i, 1,$backslashes) === '\\' ;$backslashes++){}
    183.         if($opened){
    184.             list($pos, $capture) = array_shift($opened);
    185.             if($capture){
    186.                 $groups_captured[] = [$pos,$i,$groups_count];
    187.             }else{
    188.                 $groups_not_captured[] = [$pos,$i,$groups_count];
    189.             }
    190.             $groups_count++;
    191.         }else{
    192.             throw new \LogicException('Error have not expected closed groups!');
    193.         }
    194.     }
    195. }
    196. /**
    197.  * Считывает определенное кол-во байтов начиная с $position + $offset на $len байтов (впереди позиции)
    198.  * @param $string
    199.  * @param $position
    200.  * @param int $len
    201.  * @param int $offset
    202.  * @return string
    203.  */
    204. function read_after($string, $position, $len = 1,$offset = 0){
    205.     return substr($string,$position+$offset,$len);
    206. }
    207.  
    208. /**
    209.  * Считывает определенное кол-во байтов начиная с $position + $offset на $len байтов (позади позиции)
    210.  * @param $string
    211.  * @param $position
    212.  * @param int $len
    213.  * @param int $offset
    214.  * @return string
    215.  */
    216. function read_before($string, $position, $len = 1,$offset = 0){
    217.     $pos  = $position - $offset;
    218.     $start = $pos-$len;
    219.     if($start < 0){
    220.         $len+=$start;
    221.         if(!$len)return '';
    222.         $start = 0;
    223.     }
    224.     return substr($string,$start,$len);
    225. }
    226.  
    227. /**
    228.  * Проверяет есть ли позади позиции одна из необходимой последовательности байтов
    229.  * @param $string
    230.  * @param $position
    231.  * @param $needle
    232.  * @param int $offset
    233.  * @return bool
    234.  */
    235. function has_before($string, $position, $needle, $offset=0){
    236.     if(!is_array($needle)){
    237.         $needle = [$needle];
    238.     }
    239.     $ll = null;
    240.     foreach($needle as $item){
    241.         $l = strlen($item);
    242.         if(!isset($s) || $ll != $l){
    243.             $s = read_before($string,$position,$l,$offset);
    244.             $ll = $l;
    245.         }
    246.         if($s === $item) return true;
    247.     }
    248.     return false;
    249. }
    250.  
    251. /**
    252.  * Проверяет есть ли позади позиции одна из необходимой последовательности байтов
    253.  * @param $string
    254.  * @param $position
    255.  * @param $needle
    256.  * @param int $offset
    257.  * @return bool
    258.  */
    259. function has_after($string, $position, $needle, $offset=0){
    260.     if(!is_array($needle)){
    261.         $needle = [$needle];
    262.     }
    263.     $ll = null;
    264.     foreach($needle as $item){
    265.         $l = strlen($item);
    266.         if(!isset($s) || $ll != $l){
    267.             $s = read_after($string,$position,$l,$offset);
    268.             $ll = $l;
    269.         }
    270.         if($s === $item) return true;
    271.     }
    272.     return false;
    273. }
    274. echo sprintf('%.4F',microtime(true) - $t).'<br/>';
    275.  
    276. /**
    277.  * @Variant
    278.  * 1.1 Обработка с использованием предыдущего варианта, только в реализованом классе @see Tokenizer
    279.  */
    280. echo '<h1>Variant 1.1 OO version </h1>';
    281. $tokenizer  = new Tokenizer($string);
    282. $t = microtime(true);
    283. $not_capturing_group_brackets = ['?#','?:','?>','?=','?!','?<=','?<!'];
    284. $opened = [];
    285. $groups_captured = [];
    286. $groups_not_captured = [];
    287. $groups_count = 0;
    288. foreach($tokenizer as $position => $token){
    289.     if($token === '('){
    290.         for($backslashes = 0; $tokenizer->before(1,$backslashes) === '\\' ;$backslashes++){}
    291.         if($backslashes%2==0){
    292.             $capture = !$tokenizer->hasAfter($not_capturing_group_brackets);
    293.             $opened[] = [$position,$capture];
    294.         }
    295.     }elseif($token===')'){
    296.         for($backslashes = 0; $tokenizer->before(1,$backslashes) === '\\' ;$backslashes++){}
    297.         if($opened){
    298.             list($pos, $capture) = array_shift($opened);
    299.             if($capture){
    300.                 $groups_captured[] = [$pos,$position,$groups_count];
    301.             }else{
    302.                 $groups_not_captured[] = [$pos,$position,$groups_count];
    303.             }
    304.             $groups_count++;
    305.         }else{
    306.             throw new \LogicException('Error have not expected closed groups!');
    307.         }
    308.     }
    309. }
    310. echo sprintf('%.4F',microtime(true) - $t).'<br/>';
    311.  
    312.  
    313. /**
    314.  * @Variant
    315.  * 2 Обработка с преобразованием в массив, и вытаскиванием каждого первого элемента (байта)
    316.  */
    317. echo '<h1>variant 2 (array, array_shift sequence)</h1>';
    318. $t = microtime(true);
    319. $tokens = str_split($string);
    320. while($tokens){
    321.     $token = array_shift($tokens);
    322.     //bla bla
    323. }
    324. echo sprintf('%.4F',microtime(true) - $t).'<br/>';
    325.  
    326.  
    327. /**
    328.  * @Variant
    329.  * 3 Обработка с substr с вытаскиванием каждого первого элемента (байта) (Без преобразования в массив)
    330.  */
    331.  
    332. echo '<h1>Variant 3 (char shifting sequence)</h1>';
    333. $t = microtime(true);
    334. while($string){
    335.     $token = shift($string);
    336.     //bla bla
    337. }
    338. echo sprintf('%.4F',microtime(true) - $t);
    339.  
    340.  
    341. /**
    342.  * Вытаскивает последний байт из строки, модифицируя аргумент $s @see array_pop
    343.  * @param $string
    344.  * @return string
    345.  */
    346. function pop(&$string){
    347.     $b = substr($string,-1);
    348.     $string = substr($string,0,-1);
    349.     return $b;
    350. }
    351.  
    352. /**
    353.  * Вытаскивает первый байт из строки, модифицируя аргумент $s @see array_shift
    354.  * @param $string
    355.  * @return string
    356.  */
    357. function shift(&$string){
    358.     $b = substr($string,0,1);
    359.     $string = substr($string,1);
    360.     return $b;
    361. }
     
  2. igordata

    igordata Суперстар
    Команда форума Модератор

    С нами с:
    18 мар 2010
    Сообщения:
    32.410
    Симпатии:
    1.768
    а что это?
     
  3. Kutuz

    Kutuz Новичок

    С нами с:
    10 апр 2016
    Сообщения:
    15
    Симпатии:
    0
    Код (Text):
    1. @(12345)@
    Equal to:
    Код (Text):
    1.  
    2. $rules = [
    3.   '12345'
    4. ];
    5. $rules_memory = [];
    6. $current_rule = 0;
    7. $current_rule_pos = 0;
    In process(cycle):
    Код (Text):
    1.  
    2. $current_rule_byte = $rules[$current_rule]{$current_rule_pos};
    3. if($token === $current_rule_byte){
    4.   $memories[$current_rule].= $token; // Добавляем в контейнер текущего правила, полученный токен
    5.   $current_rule_pos++;
    6.   if(strlen($rules[$current_rule]) === $current_rule_pos-1){
    7.         // next rule if exists or complete success matching
    8.         $current_rule++;
    9.   }
    10. }else{
    11.   //begin alternation or failure if have
    12. }
     
  4. igordata

    igordata Суперстар
    Команда форума Модератор

    С нами с:
    18 мар 2010
    Сообщения:
    32.410
    Симпатии:
    1.768
    если ты мне отвечал, то я всё равно не понял =(