Хак синтаксиса PHP

Original author: Lukasz Kujawa
  • Translation
  • Tutorial
Вы когда-нибудь задумывались о том, как расширить ядро PHP? Что нужно для того, чтобы создать новое ключевое слово или даже разработать новый синтаксис? Если у вас есть базовые знания языка C, то проблем с созданием небольших изменений возникнуть не должно. Да, я понимаю, что это может быть немного бессмысленно, но неважно — забавно ведь.

Давайте создадим альтернативный способ определения класса. Самый простой способ определения, разрешённый в PHP, выглядит следующим образом:

<?php
class ClassName {}

Мы можем упростить синтаксис и заменить фигурные скобки на точку с запятой.

<?php
class ClassName;

Если вы попытаетесь выполнить этот код, то он, очевидно, выдаст ошибку. Не проблема, мы можем это исправить.

На первом шаге необходимо установить программное обеспечение.

$ sudo apt-get install bison re2c

PHP написан на C, однако парсер разработан с помощью Bison. Bison — это генератор синтаксических анализаторов. Официальный сайт определяет его, как генератор парсеров общего назначения, который преобразует помеченную контекстно-свободную грамматику в детерминированный LR или обобщенный LR (GLR) анализатор, применяя таблицы LALR-парсера (Look-Ahead LR parser — прим. пер.).

Это очень мощная часть программного обеспечения, о которой можно написать целую книгу. Если захотите узнать больше, я посоветовал бы вам ознакомиться с документацией. Она не из легких, зато содержит хорошие примеры. И если вы когда-нибудь захотите создать язык программирования, то она может стать хорошей стартовой площадкой.

Теперь перейдите на http://php.net и скачайте самые свежие исходники PHP.

$ tar xvjf php-5.4.14.tar.bz2
$ cd php-5.4.14
$ ./configure
$ cd Zend
$ ls

Снимите шляпу, потому что перед вами ядро PHP. Код в этих файлах управляет подавляющим большинством веб-серверов. Давайте исследуем его.

По умолчанию для файлов генератора Bison используется расширение «y».

$ ls *.y
zend_ini_parser.y zend_language_parser.y

Мы не хотим возиться с синтаксисом «ini», поэтому остается только «zend_language_parser.y». Откройте его вашим любимым редактором.

Теперь, если поискать слово «class», то можно обнаружить следующее:

%token T_CLASS      "class (T_CLASS)"

Парсер любит работать с токенами. Токен класса — это "T_CLASS". Если вы поищете в тексте "T_CLASS", то найдете нечто подобное:

class_entry_type:
    T_CLASS { $$.u.op.opline_num = CG(zend_lineno); $$.EA = 0; }
    | T_ABSTRACT T_CLASS { $$.u.op.opline_num = CG(zend_lineno); $$.EA = ZEND_ACC_EXPLICIT_ABSTRACT_CLASS; }
    | T_TRAIT { $$.u.op.opline_num = CG(zend_lineno); $$.EA = ZEND_ACC_TRAIT; }
    | T_FINAL T_CLASS { $$.u.op.opline_num = CG(zend_lineno); $$.EA = ZEND_ACC_FINAL_CLASS; }
    ;

Перед вами четыре разных способа определения класса.
  1. класс (class)
  2. абстрактный класс (abstract class)
  3. трейт (trait)
  4. финальный (листовой, конечный) класс (final class)

В фигурных скобках вы можете видеть несколько присваиваний низкого уровня. Я могу только догадываться, зачем они нужны. Давайте не будем их трогать.

Мы на верном пути, но это не совсем то, что мы ищем. Поищите фразу «class_entry_type», которая объединяет те четыре определения класса. Она приведёт вас к пункту назначения. Разобраться в этом легко, но в первый раз читать сложновато.

unticked_class_declaration_statement:
 
    class_entry_type T_STRING extends_from
            { zend_do_begin_class_declaration(&$1, &$2, &$3 TSRMLS_CC); }
            implements_list
            '{'
            class_statement_list
            '}' { zend_do_end_class_declaration(&$1, &$3 TSRMLS_CC); }
 
    | interface_entry T_STRING
            { zend_do_begin_class_declaration(&$1, &$2, NULL TSRMLS_CC); }
           interface_extends_list
           '{'
          class_statement_list
           '}' { zend_do_end_class_declaration(&$1, NULL TSRMLS_CC); }
    ;

Здесь есть два объявления. Одно для класса, другое для интерфейса. Нас интересует первое. Оно начинается с "class_entry_type", которое разрешает конструкции: class | abstract class | trait | final class. Следующий элемент — это токен T_STRING. В будущем на его месте будет имя класса. "extends_from" — это группа. Этот элемент может быть преобразован в «extends T_STRING» или оставаться пустым.

После этого парсер вызывает движок Zend, чтобы начать объявление класса.

{ zend_do_begin_class_declaration(&$1, &$2, &$3 TSRMLS_CC); }

Вы можете найти эту функцию в файле zend_compiler.c.

void zend_do_begin_class_declaration(const znode *class_token, znode *class_name, const znode *parent_class_name TSRMLS_DC)

Первый аргумент здесь — это токен класса "class_entry_type", второй — имя класса "T_STRING", а последний — родительский класс "extends_from".

Ниже идёт группа «implements_list». Уверен, что вы знаете, зачем она нужна. Верно, для определения интерфейсов. Следующие строки образуют обязательное тело класса: открывающая фигурная скобка "{", группа "class_statement_list" и закрывающая фигурная скобка "}". Наконец, парсер сообщает движку Zend, что объявление класса окончено.

{ zend_do_end_class_declaration(&$1, &$3 TSRMLS_CC); }

Нам необходимо продублировать этот код, но уже без тела класса.

unticked_class_declaration_statement:
 
    class_entry_type T_STRING extends_from
            { zend_do_begin_class_declaration(&$1, &$2, &$3 TSRMLS_CC); }
            ';' { zend_do_end_class_declaration(&$1, &$3 TSRMLS_CC); }
 
    | class_entry_type T_STRING extends_from
 
            { zend_do_begin_class_declaration(&$1, &$2, &$3 TSRMLS_CC); }
            implements_list
            '{'
            class_statement_list
            '}' { zend_do_end_class_declaration(&$1, &$3 TSRMLS_CC); }
 
    | interface_entry T_STRING
            { zend_do_begin_class_declaration(&$1, &$2, NULL TSRMLS_CC); }
           interface_extends_list
           '{'
          class_statement_list
           '}' { zend_do_end_class_declaration(&$1, NULL TSRMLS_CC); }
    ;

Это было довольно просто, не так ли? Теперь вам остаётся лишь скомпилировать изменения.

$ cd ..
$ make

Первая компиляция всегда занимает некоторое время.

$ vim test.php

Введите код для тестирования.

<?php
class FooBar;

$a = new FooBar;
$a->bar = 10;

print_r($a);

А теперь протестируйте его.

$ sapi/cli/php test.php 
FooBar Object
(
[bar] => 10
)

Отлично, у вас получилось!

Давайте сделаем еще кое-что. В PHP вы объявляете класс с помощью ключевого слова "class". Как насчет того, чтобы сделать его покороче? Думаю, "cls", подойдёт.

Ищем файлы лексера:

$ cd Zend/
$ ls *.l
zend_ini_scanner.l zend_language_scanner.l

Файл Bison оперировал токенами. Лексер позволяет вам решать как преобразовывать код в токены. Откройте zend_language_scanner.l и поищите слово "class".

<ST_IN_SCRIPTING>"class" {
return T_CLASS;
}

Продублируйте этот блок и измените class на cls.

<ST_IN_SCRIPTING>"cls" {
return T_CLASS;
}

<ST_IN_SCRIPTING>"class" {
return T_CLASS;
}

Дело сделано. Скомпилируйте код и можете использовать ключевое слово "cls" вместо "class".

Не правда ли забавно? Надеюсь, вам было приятно также, как и мне. Интересуйтесь, исследуйте. И если вам это действительно понравилось, то стоит подумать о том, чтобы исправить некоторые ошибки на https://bugs.php.net/.

Similar posts

Ads
AdBlock has stolen the banner, but banners are not teeth — they will be back

More

Comments 45

    +44
    Забавно, но так делать не надо, правда?
      +7
      Разве автор сказал, что так делать _надо_? :)
        +119
        Он просто положил гранату на стол… :)
          +5
          Она там давно лежала, он лишь пальчиком на нее показал.
            –3
            Он просто выдернул чеку и смотрит на окружающих.
              +3
              Он просто дал повод взглянуть на PHP более глубже, как он утроен изнутри. Возможно кто-то из прочитавших статью в будущем захочет внести свой вклад в развитие PHP
            +66
            image
              +6
              Он просто разместил объяву!
          +8
          Я могу только догадываться, зачем они нужны.
          Дальше стало как-то неуютно читать:)
            –3
            Для тех кто захочет поебаться с синтаксисом такая дружеская заметка — если вы захотели добавить новый токен, то нужно пересобрать сканнер с помощью re2c, но как это сделать — тоже проблема понять. Я сам недавно столкнулся с этим… Но к счастью на SO помогли: stackoverflow.com/questions/16032576/trying-to-regenerate-zend-language-scanner-c#answers
              –5
              Осталось родить синтаксический сахар в духе Ruby, Python, Haskell, Erlang и транслировать сразу в opcode кеш.
              Приверженцам этих языков останется хаить php только на тему производительности)
                +24
                о да. особенно боюсь охаяния со стороны рубистов
                  +1
                  Зря вы так. В прошлых топиках мелькала ссылка на benchmark'и — посмотрите.
                  Во многих вариантах ПХП выигрывает по скорости у Ruby, Python и т.п.
                    0
                    Дело не в выигрыше производительности, а в сокращении тем для холивара между приверженцами различных инструментов)

                    P.S. Я для большей части веб проектов использую PHP, если вас чем то обидело…
                      +1
                      Меня это нисколько не обидело :) Я просто сам был удивлен, ибо давно не интересовался темой производительности.
                      P.S. Сам использую PHP/Python
                        0
                        Аналогичные предпочтения)
                    0
                    Помнится даже начинал писать пост в таком духе, что бы хорошо PHP позаимствовать прежде всего от Ruby и Python, но потом понял, что он будет нещадно заминусован.
                      –1
                      несомненно заминусовали бы… ретроградов достаточно
                        –1
                        во-во и меня минусёры не обошли
                    0
                    А есть сборки php с особым синтаксисом? Вообще есть ли форки с интересными отклонениями?
                    +36
                    ?php
                    class ClassName {}

                    Мы можем упростить синтаксис

                    class ClassName
                    end

                    Если вы попытаетесь выполнить этот код, то он, очевидно, выдаст ошибку. Не проблема, мы можем это исправить.
                    На первом шаге необходимо установить программное обеспечение.

                    $ sudo apt-get install ruby

                    Автор, без обид, но таким образом можно исправить все что угодно
                      0
                      Я смеялся до слез. Хорошая шутка :)
                        0
                        Это упрощение века =)
                        +3
                        Рубикапец!
                          +7
                          А что если использовать эти возможности в несколько ином ключе — сделать инфернальный синтаксис, написать транслятор с обычного РНР на инфернальный (инмена переменных не забываем менять на случайное неиспользованое слово из большого словаря всяких интересных слов, например), поднять инфернальный РНР на сервере — и идеальный обфускатор готов, если кто-то теоретически сможет спи… воровать наш код, то разобраться хрен получится, а запустить — тем более.
                            +2
                            Люди с хорошим воображением, извините заранее.
                              –3
                              ну, теоретически можно сдампать байт-код, а потом его декомпилировать.
                                +6
                                Зачем ты тут со своей логикой и здравым смыслом?
                                0
                                typedef struct inferno_zval {
                                some_inferno * void;
                                some_zval * zval;
                                }

                                Так? :)
                                +6
                                Странно, что не предложено форкнуть реп на гитхабе, а потом сделать пулл реквест — забавы было бы больше :)
                                  –1
                                  Приходит в голову воспоминание о картинке с троллейбусом.
                                  или
                                  «Но зачем!? Во имя САТАНЫ, конечно!
                                    +3
                                    Перевод не адаптирован. Для детонирования наших должно быть:
                                    … и можете использовать ключевое слово «класс» вместо «class».
                                      +10
                                      нет, язык 1С не нужен
                                        +1
                                        Переманим всех 1Сников на PHP, вот тут-то ему и придёт конец :)
                                          0
                                          Которому, простите? :)

                                          P.S. Так и вижу новые названия — «PHP 5.5 Тонкий клиент», а также мелочи вроде «программная лицензия на PHP, слетающая при изменении сетевой платы», и прочие прелести )
                                            0
                                            Будем надеяться, что обоим :)
                                            Но вообще имелся ввиду 1С.
                                              0
                                              Думаю, обоим никак… Но, да:

                                              image

                                              Должен остаться только один!
                                        +2
                                        И знак доллара заменить на букву Р
                                      –1
                                      Можно взять исходник любого opensource — проекта и внести в него изменения, скомпилировать, запустить, а в чем смысл?
                                      Код написанный для вашей версии PHP не будет работать на всех других серверах…
                                      Хотя это можно как раз использовать что бы не запускали Ваш код, где не попадя =)))
                                        +9
                                        Интересно спровоцирует ли статья неделю нескучных php7 на хабре…
                                          0
                                          Лично для меня изменение синтаксиса языка (например, PHP) всегда упиралось в то, что среды разработки поддерживают только стандарт, и добавление в них соответствующих изменений может обернуться отдельным весёлым и сказочным геморройчиком.
                                            0
                                            Покажите, как заменить if на русское если

                                            Only users with full accounts can post comments. Log in, please.