Статьи

Макросы PHP для удовольствия и прибыли!

Первоначально я собирался называть это «микро-макросами с библиотекой идеально прагматичного препроцессора Марсио», но я не думал, что Бруно одобрит…

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

Видите ли, многие разработчики приходят на PHP с других языков. И многие разработчики PHP могут кодировать на нескольких языках. Часто в этих языках есть вещи — небольшие синтаксические сахара — которые мы ценим и даже упускаем, когда создаем PHP-вещи.

Комплексный лабораторный лабиринт

Добавить их в язык на уровне компилятора сложно ( или это так? ). Это если вы не создали компилятор и / или не знаете, как он работает. Мы не собираемся делать что-то настолько техническое, но мы все еще будем уполномочены.

Когда я писал на Ruby (или языках, похожих в этом отношении), я использовал диапазоны индексов списков. Они выглядят примерно так:

few = many [ 1 . . 3 ] 

Мы можем ожидать, что подобный код возьмет второй, третий и четвертый элемент списка many , и назначит их переменной few . Мы можем сделать нечто подобное, используя array_slice :

 $few = array_slice ( $many , 1 , 3 ) ; 

Я думаю, что Ruby-эквивалент гораздо элегантнее. Мы также можем использовать переменные и отрицательные значения для нижней и верхней границ диапазона. Это великолепно!

Начало работы с макросами

Вдохновленный недавним сообщением SitePoint , я решил попробовать добавить этот синтаксис в мои приложения. Проблема в том, что я недостаточно хорошо понимаю интерпретатор PHP, чтобы его можно было добавить. Потом я вспомнил библиотеку Марсио Альмады Yay . Это добавляет макросы через предварительную обработку. Вот пример чего-то простого, что вы можете сделать с ним:

 macro { unless ( ···condition ) { ···body } } > > { if ( ! ( ···condition ) ) { ···body } } $condition = false ; unless ( $condition ) { print "look ma! macros..." ; } 

Этот пример прямо из readme (с некоторыми отличиями в стиле).

Для этого нам нужно скачать препроцессор:

 $ composer require yay/yay:dev-master 

После загрузки и сохранения приведенного выше PHP-подобного кода в файл (назовем его, unless.yphp не будет unless.yphp ), мы можем скомпилировать его в обычный PHP:

 $ vendor/bin/yay unless.yphp >> unless.php 

Результат будет выглядеть примерно так:

 $condition = false ; if ( ! ( $condition ) ) { print "look ma! macros..." ; } 

Как все это работает

Yay реализует парсер. Он разбивает строку кода на токены, создает абстрактное синтаксическое дерево и помещает код PHP обратно вместе со всеми макросами, замененными на настоящий код PHP.

Поначалу эти понятия трудно получить. Я предлагаю вам прочитать эту серию , которая объясняет некоторые из них с помощью инструментов, которые вы хорошо знаете.

Когда Yay совпадает с определенным нами шаблоном unless , он по существу заменяет строку вокруг condition и body . Мы можем использовать это с большим эффектом, для синтаксиса языка, который мы хотим добавить …

Добавляем наш синтаксис

Первое, что нам нужно сделать, это определить выражение, которое будет соответствовать индексу диапазона:

 macro { T_VARIABLE ·A [ T_VARIABLE ·B . . T_VARIABLE ·C ] } > > { array_slice ( T_VARIABLE ·A , T_VARIABLE ·B , T_VARIABLE ·C - T_VARIABLE ·B ) } 

Это выражение макроса ищет переменную, например $many и помечает ее как A Он будет соответствовать, только если он увидит эквивалент $many[$lower..$upper] , который он заменит на:

 array_slice ( $many , $lower , $upper - $lower ) ; 

array_slice функции array_slice — это исходный массив, начальное смещение и количество элементов, которые нужно получить. Это немного отличается от синтаксиса диапазона, потому что синтаксис диапазона определяет начальное смещение и конечное смещение. Мы переводим между ними, вычитая верхнюю границу из нижней.

T_VARIABLE — это тип токена AST, который идентифицирует переменные, определенные в строке кода. Существует довольно много разных типов токенов, но, к счастью, нам не нужно помнить их все …

Мы можем попробовать это с помощью некоторого Yay PHP:

 macro { T_VARIABLE ·A [ T_VARIABLE ·B . . T_VARIABLE ·C ] } > > { array_slice ( T_VARIABLE ·A , T_VARIABLE ·B , T_VARIABLE ·C - T_VARIABLE ·B ) } $many = [ "She walks in beauty" , "like the night" , "of cloudless climes" , "and starry skies" , "And all that's best" , "of dark and bright" , "meet in her aspect" , "and her eyes" , "..." , ] ; $lower = 4 ; $upper = 8 ; $few = $many [ $lower . . $upper ] ; 

Когда мы запускаем это через процесс преобразования, мы получаем что-то вроде:

 $many = [ "She walks in beauty" , "like the night" , "of cloudless climes" , "and starry skies" , "And all that's best" , "of dark and bright" , "meet in her aspect" , "and her eyes" , "..." , ] ; $lower = 4 ; $upper = 8 ; $few = array_slice ( $many , $lower , $upper - $lower ) ; 

Это хорошее начало. Мы все еще хотим разрешить простые целочисленные границы, что требует чуть более сложного макрокода:

 macro {( ···expression ) } > > { ·· stringify ( ···expression ) } macro { T_VARIABLE ·A [ ···range ] } > > { eval ( '$list = ' .( T_VARIABLE ·A ) . ';' . '$lower = ' . explode ( '..' ,( ···range ) ) [ 0 ] . ';' . '$upper = ' . explode ( '..' ,( ···range ) ) [ 1 ] . ';' . 'return array_slice($list, $lower, $upper - $lower);' ) } 

Давайте сделаем это один шаг за один раз. Мы начинаем с определения макроса для преобразования соответствия шаблонов в строки. ··stringify(...) — это расширение (что означает, что оно преобразует другие совпадения). Он берет совпадение и преобразует его непосредственно в строку. Мы можем видеть это в следующем примере:

 macro { convert ( ···expression ) } > > { ·· stringify ( ···expression ) } convert ( $a , $b , $c ) ; 

Если мы выполним этот код через Yay, мы увидим следующее:

 '$a, $b, $c' ; 

Я решил создать своего рода сокращенный макрос, преобразуя (...) в ··stringify(...) , чтобы сделать дальнейшие макросы более краткими.

Я столкнулся с несколькими проблемами, пытаясь захватить числа в синтаксисе диапазона индекса. К тому времени, когда Yay 4..8 AST, 4..8 маркируется как 4. и .8 ; вместо 4 , .. и 8 . Это приводит к ошибке синтаксического анализатора, прежде чем Yay сможет применить макросы. Обойти это можно, конвертировав диапазон индекса в строку и начав ее анализ.

Элегантный способ сделать это — передать строку диапазона индекса функции вместе с исходным массивом. К сожалению, правила области видимости PHP означают, что границы диапазона переменных не будут доступны внутри функции, что делает ее немного бесполезной.

Самый простой способ обойти эту проблему — ссылаться на переменные внутри строки и eval их. Как это работает:

  1. $many соответствует T_VARIABLE·A
  2. 4..8 соответствует ···expression
  3. Внутри eval $lower определяется как первый индекс массива после взрыва строки 4..8 помощью ..
  4. $upper определяется как второй индекс массива этого взрыва
  5. Возвращаемое значение этой строки eval — это исходный код array_slice мы использовали ранее

Любая из следующих строк будет соответствовать этому макросу:

 $lower = 4 ; $upper = 8 ; $few = $many [ $lower . . $upper ] ; $few = $many [ 4 . . 8 ] ; $few = $many [ 4 . . $upper ] ; $few = $many [ $lower . . 8 ] ; 

… и они сгенерируют код, похожий на следующий:

 $few = eval ( '$list = ' . '$many' . ';' . '$lower = ' . explode ( '..' , '$lower..$upper' ) [ 0 ] . ';' . '$upper = ' . explode ( '..' , '$lower..$upper' ) [ 1 ] . ';' . 'return array_slice($list, $lower, $upper - $lower);' ) ; $few = eval ( '$list = ' . '$many' . ';' . '$lower = ' . explode ( '..' , '4..8' ) [ 0 ] . ';' . '$upper = ' . explode ( '..' , '4..8' ) [ 1 ] . ';' . 'return array_slice($list, $lower, $upper - $lower);' ) ; $few = eval ( '$list = ' . '$many' . ';' . '$lower = ' . explode ( '..' , '4..$upper' ) [ 0 ] . ';' . '$upper = ' . explode ( '..' , '4..$upper' ) [ 1 ] . ';' . 'return array_slice($list, $lower, $upper - $lower);' ) ; $few = eval ( '$list = ' . '$many' . ';' . '$lower = ' . explode ( '..' , '$lower..8' ) [ 0 ] . ';' . '$upper = ' . explode ( '..' , '$lower..8' ) [ 1 ] . ';' . 'return array_slice($list, $lower, $upper - $lower);' ) ; 

Это, вероятно, настолько просто, насколько мы можем сделать это, учитывая ограничения области видимости переменной и анализатора. Это не самый приятный PHP, но он выполняет свою работу. Тем временем, мы можем нарезать массив с помощью $many[4..8]

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