yay / yay
高级PHP预处理器
Requires
- php: 7.*
- ext-mbstring: *
- ext-tokenizer: *
- nikic/php-parser: ^2.1|^3.0|^4.0
Requires (Dev)
- phpbench/phpbench: @dev
- phpunit/phpunit: ~6.5
This package is auto-updated.
Last update: 2024-09-20 23:52:59 UTC
README
YAY! 是一个高级解析组合器PHP预处理器,允许任何人用PHP 💥 扩充PHP。
这意味着语言功能可以作为composer包分发(只要基于宏的实现可以用纯PHP代码表达,并且实现足够快)。
路线图.
设置
composer require yay/yay:dev-master
使用方法
命令行
yay some/file/with/macros.php >> target/file.php
运行时模式
“运行时”模式是W.I.P,将使用流封装以及与composer的集成来预处理每个包含的文件。它可能有一些opcache/cache支持,因此文件将只预处理/展开一次,并在需要时进行。
查看功能进度在问题#11。
它是如何工作的
一个非常简单的例子
每个宏由一个匹配器和扩展器组成,当执行时,允许你扩充PHP。考虑最简单的例子
$(macro :unsafe) { $ } >> { $this } // this shorthand
该宏基本上将字面量$
标记扩展为$this
。以下代码将扩展为
// source | // expansion class Foo { | class Foo { protected $a = 1, $b = 2, $c = 3; | protected $a = 1, $b = 2, $c = 3; | function getProduct(): int { | function getProduct(): int { return $->a * $->b * $->c; | return $this->a * $this->b *$this->c; } | } } | }
请注意,在
$this
扩展中需要:unsafe
标记来避免宏卫生。
这个宏实际上非常简单,一个更成熟的版本会是
$(macro :unsafe){ $ // litterally matches '$' // but not followed by: $(not(token(T_VARIABLE))) // avoids var var false positives such as '$$foo' $(not(token('{'))) // avoids false positives such as '${foo}' } >> { $this }
简单示例
除了字面字符序列之外,还可以使用$(TOKEN_TYPE as label)
形式的token匹配器来匹配特定的标记类型。
以下宏匹配像__swap($x, $y)
或__swap($foo, $bar)
这样的标记序列
$(macro) { __swap ( $(T_VARIABLE as A) , $(T_VARIABLE as B) ) } >> { (list($(A), $(B)) = [$(B), $(A)]) }
扩展应该是显而易见的
// source | // expansion __swap($foo, $bar); | (list($foo, $bar) = [$bar, $foo]);
另一个简单示例
要实现unless
,我们需要匹配字面量unless
关键字后跟括号内的标记层(...)
和代码块{...}
。幸运的是,宏DSL有一个非常直接的层匹配构造
$(macro) { unless ($(layer() as expression)) { $(layer() as body) } } >> { if (! ($(expression))) { $(body) } }
宏的实际作用
// source | // expansion unless ($x === 1) { | if (! ($x === 1)) { echo "\$x is not 1"; | echo "\$x is not 1"; } | }
PS:请勿实现“unless”。这里只是为了教学目的。
高级示例
一个更复杂的例子可能是将枚举从未来迁移到PHP,语法如下
enum Fruits { Apple, Orange } var_dump(\Fruits::Orange <=> \Fruits::Apple);
因此,从语法上讲,枚举是用字面量enum
单词声明的,后跟一个T_STRING
和一个用逗号分隔的标识符列表,例如{A, B, C}
。
YAY内部使用解析组合器进行一切操作,并且这些更高级的解析器在宏声明中完全暴露。我们的枚举宏将需要像ls()
和label()
这样的高级匹配器组合来匹配所需的语法,如下所示
$(macro) { enum $(T_STRING as name) { $( // ls() matches a delimited list // in this case a list of label() delimited by ',' such as `foo, bar, baz` ls ( label() as field , token(',') ) as fields ) } } >> { "it works"; }
该宏已经能够匹配枚举语法
// source // expansion enum Order {ASC, DESC}; | "it works";
我不会解释枚举是如何实现的,如果您愿意,可以阅读RFC,然后查看以下扩展是如何工作的
// things here would normally be under a namespace, but since we want a concise example... interface Enum { } function enum_field_or_class_constant(string $class, string $field) { return (\in_array(\Enum::class, \class_implements($class)) ? $class::$field() : \constant("{$class}::{$field}")); } $(macro :unsafe) { // the enum declaration enum $(T_STRING as name) { $( ls ( label() as field , token(',') ) as fields ) } } >> { class $(name) implements Enum { private static $registry; private function __construct() {} static function __callStatic(string $type, array $args) : self { if(! self::$registry) { self::$registry = new \stdclass; $(fields ... { self::$registry->$(field) = new class extends $(name) {}; }) } if (isset(self::$registry->$type)) return self::$registry->$type; throw new \Exception(sprintf('Undefined enum type %s->%s', __CLASS__, $type)); } } } $(macro) { $( // sequence that matches the enum field access syntax: chain( ns() as class, // matches a namespace token(T_DOUBLE_COLON), // matches T_DOUBLE_COLON used for static access not(class), // avoids matching `Foo::class`, class resolution syntax label() as field, // matches the enum field name not(token('(')) // avoids matching static method calls such as `Foo::bar()` ) ) } >> { \enum_field_or_class_constant($(class)::class, $$(stringify($(field)))) }
更多示例在phpt测试文件夹中https://github.com/marcioAlmada/yay/tree/master/tests/phpt
常见问题解答
为什么叫“YAY!”?
- PHP具有功能“x”:yay还是nay?😉
文档在哪里?
正在制作食谱
你为什么在制作这个?
因为它很有趣。它可能变得有用。 因为我们能™。
结论
目前这是一个关于如何使用解析器组合器在类似PHP这样的语言上构建高级预处理器领域特定语言(DSL)的实验。为什么?
PHP离同构性非常远,因此需要复杂的确定性解析和大的AST实现,以及节点访问者API来修改源代码——最终,你甚至无法轻松处理未知语法 ¯\_(⊙_ʖ⊙)_/¯
。
因此产生了这个项目。它也是挑战的一部分
- 创建一个简约的架构,将预处理程序本身的内部组件子集暴露给用户DSL。
- 创建具有良好错误报告和语法无效化功能的解析器组合器,因为1
版权
版权(c)2015-* Márcio Almada。在MIT风格的许可下分发。有关详细信息,请参阅LICENSE。