Первоначально я собирался называть это «микро-макросами с библиотекой идеально прагматичного препроцессора Марсио», но я не думал, что Бруно одобрит…
Я очень взволнован, когда разработчики получают возможность создавать новые инструменты и даже новые языки для решения своих проблем.
Видите ли, многие разработчики приходят на 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
их. Как это работает:
-
$many
соответствуетT_VARIABLE·A
-
4..8
соответствует···expression
- Внутри
eval
$lower
определяется как первый индекс массива после взрыва строки4..8
помощью..
-
$upper
определяется как второй индекс массива этого взрыва - Возвращаемое значение этой строки
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]
…
Если вы нашли это интересным или у вас есть интересные идеи о том, как использовать эту библиотеку, рассмотрите возможность оставить комментарий ниже.