Вы когда-нибудь задумывались о том, как расширить ядро PHP? Что нужно для того, чтобы создать новое ключевое слово или даже разработать новый синтаксис? Если у вас есть базовые знания языка C, то проблем с созданием небольших изменений возникнуть не должно. Да, я понимаю, что это может быть немного бессмысленно, но неважно — забавно ведь.
Давайте создадим альтернативный способ определения класса. Самый простой способ определения, разрешённый в PHP, выглядит следующим образом:
Мы можем упростить синтаксис и заменить фигурные скобки на точку с запятой.
Если вы попытаетесь выполнить этот код, то он, очевидно, выдаст ошибку. Не проблема, мы можем это исправить.
На первом шаге необходимо установить программное обеспечение.
PHP написан на C, однако парсер разработан с помощью Bison. Bison — это генератор синтаксических анализаторов. Официальный сайт определяет его, как генератор парсеров общего назначения, который преобразует помеченную контекстно-свободную грамматику в детерминированный LR или обобщенный LR (GLR) анализатор, применяя таблицы LALR-парсера (Look-Ahead LR parser — прим. пер.).
Это очень мощная часть программного обеспечения, о которой можно написать целую книгу. Если захотите узнать больше, я посоветовал бы вам ознакомиться с документацией. Она не из легких, зато содержит хорошие примеры. И если вы когда-нибудь захотите создать язык программирования, то она может стать хорошей стартовой площадкой.
Теперь перейдите на http://php.net и скачайте самые свежие исходники PHP.
Снимите шляпу, потому что перед вами ядро PHP. Код в этих файлах управляет подавляющим большинством веб-серверов. Давайте исследуем его.
По умолчанию для файлов генератора Bison используется расширение «y».
Мы не хотим возиться с синтаксисом «ini», поэтому остается только «zend_language_parser.y». Откройте его вашим любимым редактором.
Теперь, если поискать слово «class», то можно обнаружить следующее:
Парсер любит работать с токенами. Токен класса — это "T_CLASS". Если вы поищете в тексте "T_CLASS", то найдете нечто подобное:
Перед вами четыре разных способа определения класса.
В фигурных скобках вы можете видеть несколько присваиваний низкого уровня. Я могу только догадываться, зачем они нужны. Давайте не будем их трогать.
Мы на верном пути, но это не совсем то, что мы ищем. Поищите фразу «class_entry_type», которая объединяет те четыре определения класса. Она приведёт вас к пункту назначения. Разобраться в этом легко, но в первый раз читать сложновато.
Здесь есть два объявления. Одно для класса, другое для интерфейса. Нас интересует первое. Оно начинается с "class_entry_type", которое разрешает конструкции: class | abstract class | trait | final class. Следующий элемент — это токен T_STRING. В будущем на его месте будет имя класса. "extends_from" — это группа. Этот элемент может быть преобразован в «extends T_STRING» или оставаться пустым.
После этого парсер вызывает движок Zend, чтобы начать объявление класса.
Вы можете найти эту функцию в файле zend_compiler.c.
Первый аргумент здесь — это токен класса "class_entry_type", второй — имя класса "T_STRING", а последний — родительский класс "extends_from".
Ниже идёт группа «implements_list». Уверен, что вы знаете, зачем она нужна. Верно, для определения интерфейсов. Следующие строки образуют обязательное тело класса: открывающая фигурная скобка "{", группа "class_statement_list" и закрывающая фигурная скобка "}". Наконец, парсер сообщает движку Zend, что объявление класса окончено.
Нам необходимо продублировать этот код, но уже без тела класса.
Это было довольно просто, не так ли? Теперь вам остаётся лишь скомпилировать изменения.
Первая компиляция всегда занимает некоторое время.
Введите код для тестирования.
А теперь протестируйте его.
Отлично, у вас получилось!
Давайте сделаем еще кое-что. В PHP вы объявляете класс с помощью ключевого слова "class". Как насчет того, чтобы сделать его покороче? Думаю, "cls", подойдёт.
Ищем файлы лексера:
Файл Bison оперировал токенами. Лексер позволяет вам решать как преобразовывать код в токены. Откройте zend_language_scanner.l и поищите слово "class".
Продублируйте этот блок и измените class на cls.
Дело сделано. Скомпилируйте код и можете использовать ключевое слово "cls" вместо "class".
Не правда ли забавно? Надеюсь, вам было приятно также, как и мне. Интересуйтесь, исследуйте. И если вам это действительно понравилось, то стоит подумать о том, чтобы исправить некоторые ошибки на https://bugs.php.net/.
Давайте создадим альтернативный способ определения класса. Самый простой способ определения, разрешённый в 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; }
;
Перед вами четыре разных способа определения класса.
- класс (class)
- абстрактный класс (abstract class)
- трейт (trait)
- финальный (листовой, конечный) класс (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/.