资讯详情

资讯详情

PHP-Parser 如何利用 Token 位置还原 AST 未保留的原始写法(如 var 与 public)?

PHP-Parser 如何利用 Token 位置还原 AST 未保留的原始写法如 var 与 public【免费下载链接】PHP-ParserA PHP parser written in PHP项目地址: https://gitcode.com/GitHub_Trending/ph/PHP-Parser当你用 PHP-Parsernikic/php-parser分析或重构 PHP 代码时会发现解析出的 AST 丢失了一部分“源码层面”的信息比如一个属性是用var $foo;还是public $foo;声明的在 AST 里完全一样。PHP-Parser 提供了一条官方文档明确给出的路径解析器会为每个节点附加 token 位置属性再用Parser::getTokens()拿到的 token 数组去查节点起始位置的 token 原文就能还原这类 AST 未保留的写法。完成本文后你可以写一段代码解析任意 PHP 文件遍历其中的属性节点判断每个属性当初是用var还是public声明的。适用前提是属性节点带有startTokenPos属性默认解析时会附加该属性。为什么 AST 本身无法区分 var 与 public属性节点PhpParser\Node\Stmt\Property只保存修饰符位掩码$flags。它的isPublic()实现是public function isPublic(): bool { return ($this-flags Modifiers::PUBLIC) ! 0 || ($this-flags Modifiers::VISIBILITY_MASK) 0; }也就是说var $foo;无可见性修饰符flags 为 0和public $foo;都会让isPublic()返回true两者在 AST 层面不可区分。官方文档 doc/component/Lexer.markdown 的 “Using token positions” 一节正是以这个问题为例说明如何用 token 位置找回原始写法。准备获取 token 数组与位置属性PHP-Parser 基于 PHP 的ext/tokenizer扩展做词法分析Lexer::tokenize()返回PhpParser\Token数组。文档列出的 token 接口核心字段是$idtoken IDT_*常量或单字符 token 的ord($char)$texttoken 的文本内容$linetoken 起始行号1 基未知时为 -1$postoken 起始偏移0 基未知时为 -1。与普通PhpToken::tokenize()的输出不同PHP-Parser 的 token 数组以一个 ID 为 0 的哨兵 token 结尾。解析后用Parser::getTokens()获取最近一次解析对应的 token$parser (new PhpParser\ParserFactory())-createForHostVersion(); $stmts $parser-parse($code); $tokens $parser-getTokens();与此同时解析器会为每个 AST 节点附加位置属性把节点映射回 token 数组或源码字符串start/end都是闭区间单 token 节点满足startTokenPos endTokenPos属性含义对应访问器startLine/endLine节点起止行getStartLine()/getEndLine()startTokenPos/endTokenPos节点首/尾 token 在 token 数组中的下标getStartTokenPos()/getEndTokenPos()startFilePos/endFilePos节点首/尾字符在代码字符串中的偏移getStartFilePos()/getEndFilePos()要还原原始写法用到的是startTokenPos它指向节点的第一个 token。执行步骤在访问器中结合 token 判断文档给出的判定逻辑是取属性节点的起始 token 下标看该 token 是不是T_VAR/** param PhpParser\Token[] $tokens */ function isDeclaredUsingVar(array $tokens, PhpParser\Node\Stmt\Property $prop) { $i $prop-getStartTokenPos(); return $tokens[$i]-id T_VAR; }要把 token 传入遍历过程需要写一个自定义访问器文档原文示例其中$lexerOptions为解析器可选的 lexer 选项不定制时可省略直接createForHostVersion()即可?php require path/to/vendor/autoload.php; // 实际项目中的 composer autoload 路径 class MyNodeVisitor extends PhpParser\NodeVisitorAbstract { private $tokens; public function setTokens(array $tokens) { $this-tokens $tokens; } public function leaveNode(PhpParser\Node $node) { if ($node instanceof PhpParser\Node\Stmt\Property) { var_dump(isDeclaredUsingVar($this-tokens, $node)); } } } $parser (new PhpParser\ParserFactory())-createForHostVersion(); $visitor new MyNodeVisitor(); $traverser new PhpParser\NodeTraverser($visitor); try { $stmts $parser-parse($code); $visitor-setTokens($parser-getTokens()); $stmts $traverser-traverse($stmts); } catch (PhpParser\Error $e) { echo Parse Error: , $e-getMessage(); }关键点在于setTokens()必须在traverse()之前调用这样leaveNode()处理每个属性节点时$this-tokens才可用。注意getTokens()返回的是“最后一次解析”的 token所以同一个 parser 实例上parse()之后、下一次parse()之前取 token 才对应当前这棵 AST。验证结果用如下代码验证文档示例$code CODE ?php class A { var $foo; public $bar; } CODE;按上述逻辑运行leaveNode()会按顺序遇到两个Stmt\Property节点并输出两个布尔值var $foo;的起始 token 是T_VAR输出bool(true)public $bar;的起始 token 是T_PUBLIC输出bool(false)。这个对比结果就是该方案的直接验证——同样返回isPublic() true的两个属性靠 token 才能区分出原始写法。也可以用 doc/2_Usage_of_basic_components.markdown 中提到的vendor/bin/php-parse file.php快速查看某段语法在 AST 中的形态辅助确认节点类型与属性名。限制与适用范围本例有 flags 捷径但思路更通用文档明确标注上面的var/public例子已过时——对可见性这一种情况直接检查($property-flags Class_::VISIBILITY_MODIFIER_MASK) 0就能区分varflags 为 0与显式public无需 token。但文档同时说明“the general idea behind the example still applies in other cases”即凡是 AST 只保留语义、不保留原始拼写或格式信息的场景注释风格之外的各类“同义写法”都可以用同样的 token 位置思路还原。token 下标是闭区间单 token 节点满足startTokenPos endTokenPos零长度节点满足startTokenPos - 1 endTokenPos。按此约定使用下标不要按左闭右开数组的习惯去取endTokenPos 1。token 数组末尾有哨兵 tokenID 为 0遍历时不要假设最后一个元素是真实词法单元。前提条件该方案要求节点带有startTokenPos属性。若你在 lexer 选项中关闭了 token 位置属性getStartTokenPos()会返回 -1此时无法做本文的判断应按 doc/component/Lexer.markdown 的说明确认属性是否启用。如果你的最终目标不是“检查”原始写法而是修改 AST 后重新生成代码并尽量保留未改动部分的原始格式下一步可以了解 Pretty printing 中的 formatting-preserving 模式printFormatPreserving()它同样依赖getTokens()拿到的 token 数组。【免费下载链接】PHP-ParserA PHP parser written in PHP项目地址: https://gitcode.com/GitHub_Trending/ph/PHP-Parser创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →