PHP 词法分析/语法分析

作者: print("") 分类: 编译原理 发布时间: 2025-05-12 21:02

仓库代码 

https://github.com/aaPanel/aaWAF/tree/main/php_engine

PHP执行流程

参考:

https://github.com/php/php-src/blob/master/Zend/zend_language_scanner.l

https://github.com/php/php-src/blob/master/Zend/zend_language_parser.y

一、词法分析

1.1 Lexer结构体 以及实例化

// Lexer 结构体定义了PHP词法分析器的核心数据结构
type Lexer struct {
    data           []byte              // 待分析的源代码字节数组
    phpVersion     *version.Version    // PHP版本信息,用于兼容性处理
    errHandlerFunc func(*errors.Error) // 错误处理函数

    p, pe, cs   int   // p: 当前位置, pe: 数据结束位置, cs: 当前状态
    ts, te, act int   // ts: token开始位置, te: token结束位置, act: 当前动作
    stack       []int // 状态栈,用于处理嵌套结构
    top         int   // 栈顶指针

    heredocLabel []byte         // 当前heredoc标签
    tokenPool    *token.Pool    // token对象池,优化内存分配
    positionPool *position.Pool // 位置对象池,优化内存分配
    newLines     NewLines       // 跟踪源码中的新行位置
}


// NewLexer 创建并初始化一个新的词法分析器实例
func NewLexer(data []byte, config conf.Config) *Lexer {
    lex := &Lexer{
       data:           data,                    // 设置源代码
       phpVersion:     config.Version,          // 设置PHP版本
       errHandlerFunc: config.ErrorHandlerFunc, // 设置错误处理函数

       pe:    len(data),      // 设置数据结束位置
       stack: make([]int, 0), // 初始化状态栈

       // 初始化对象池和行号追踪
       tokenPool:    token.NewPool(position.DefaultBlockSize),
       positionPool: position.NewPool(token.DefaultBlockSize),
       newLines:     NewLines{make([]int, 0, 128)}, // 预分配空间以提高性能
    }
    initLexer(lex) // 初始化词法分析器状态机
    return lex
}

1.1 此法解析状态机

大概的一个状态机的图

1.2 解析一个简单的php文件

<?php phpinfo();?>

1.2.1 首先是进入到main的主状态

main := |*
            any => {
                fnext html; // 切换到html 状态 
                lex.ungetCnt(1) // 将刚读取的字符推回输入流
            };
        *|;

1.2.2 进入到html主状态

html := |*
            '<?php'i ( [ \t] | newline ) => {
                lex.ungetCnt(lex.te - lex.ts - 5) // 回退到空格这个地方。让下一个读取能获取到
                lex.addFreeFloatingToken(tkn, token.T_OPEN_TAG, lex.ts, lex.ts+5)
                fnext php;
            };

进入到了php的主状态

1.2.3 空格返回

whitespace_line*                   => {lex.addFreeFloatingToken(tkn, token.T_WHITESPACE, lex.ts, lex.te)};

1.2.4 Phpinfo 返回

varname => { lex.setTokenPosition(tkn); tok = token.T_STRING; fbreak; };

1.2.5 ( 返回

operators => {
    lex.setTokenPosition(tkn);
    tok = token.ID(int(lex.data[lex.ts]));
    fbreak;
};

1.2.6 ) 返回

operators => {
    lex.setTokenPosition(tkn);
    tok = token.ID(int(lex.data[lex.ts]));
    fbreak;
};

1.2.6 ;?>

';' whitespace_line* '?>' newline? => {lex.setTokenPosition(tkn); tok = token.ID(int(';')); fnext html; fbreak;};

1.2.7 汇总

Value: T_OPEN_TAG vlue: <?php
Value: T_WHITESPACE vlue:  
T_STRING phpinfo
ID(40) (
ID(41) )
ID(59) ;?>

二、词法分析

输入的如下举例
src := []byte(`
<?php 
phpinfo();?>`)
输入的Token为
T_INLINE_HTML vlu: 

T_STRING vlu: phpinfo
ID(40) vlu: (
ID(41) vlu: )
ID(59) vlu: ;?>
ID(0) vlu: ;?>

2.1 第一个T_INLINE_HTML

statement:
|   T_INLINE_HTML
        {
            $$ = &ast.StmtInlineHtml{
                Position: yylex.(*Parser).builder.NewTokenPosition($1),
                InlineHtmlTkn: $1,
                Value:         $1.Value,
            }
        }
规约到top_statement
top_statement:
        error
            {
                // error
                $$ = nil
            }
    |   statement
            {
                fmt.Println("规约到top_statement")
                $$ = $1
            }
规约到top_statement_list
top_statement_list:
        top_statement_list top_statement
            {
                fmt.Println("规约到top_statement_list")
                if $2 != nil {
                    $$ = append($1, $2)
                }
            }
    |   /* empty */
            {
                $$ = []ast.Vertex{}
            }
;

2.2 T_STRING phpinfo

到namespace_name 节点
namespace_name:
        T_STRING
            {
            fmt.Println("Identifier2:", $1.Value)
                $$ = &ParserSeparatedList{
                    Items: []ast.Vertex{
                        &ast.NamePart{
                            Position: yylex.(*Parser).builder.NewTokenPosition($1),
                            StringTkn: $1,
                            Value:     $1.Value,
                        },
                    },
                }
            }

2.3 ID(40) (

先规约后面操作
name:
        namespace_name
            {
                fmt.Println("规约到name")     
           }
规约到name 之后读入(
function_call:
        name argument_list
            {
                fmt.Println("规约到function_call")
                $$ = &ast.ExprFunctionCall{
                    Position: yylex.(*Parser).builder.NewNodesPosition($1, $2),
                    Function:            $1,
                    OpenParenthesisTkn:  $2.(*ArgumentList).OpenParenthesisTkn,
                    Args:                $2.(*ArgumentList).Arguments,
                    SeparatorTkns:       $2.(*ArgumentList).SeparatorTkns,
                    CloseParenthesisTkn: $2.(*ArgumentList).CloseParenthesisTkn,
                }
            }
刚好符合argument_list 但是这里差了一个( 等待后续输入
argument_list:
        '(' ')'
            {
                fmt.Println("argument_list1")
                $$ = &ArgumentList{
                    Position: yylex.(*Parser).builder.NewTokensPosition($1, $2),
                    OpenParenthesisTkn: $1,
                    CloseParenthesisTkn: $2,
                }
            }

2.4 ID(41) )

这里就符合了argument_list
argument_list:
        '(' ')'
            {
                fmt.Println("argument_list1")
                $$ = &ArgumentList{
                    Position: yylex.(*Parser).builder.NewTokensPosition($1, $2),
                    OpenParenthesisTkn: $1,
                    CloseParenthesisTkn: $2,
                }
            }
规约到function_call
function_call:
        name argument_list
            {
                xxx
                
            }
规约到callable_variable
callable_variable:
|   function_call
        {
            fmt.Println("function_call规约到callable_variable")
            $$ = $1
        }

2.5 ID(59) ;?>

先规约到variable
expr:
        variable
            {
                $$ = $1
            }
规约到expr
expr:
        variable
            {
                fmt.Println("规约到expr")
                $$ = $1
            }
读入; 符合statement
statement:
|   expr ';'
        {
            $$ = &ast.StmtExpression{
                Position: yylex.(*Parser).builder.NewNodeTokenPosition($1, $2),
                Expr:         $1,
                SemiColonTkn: $2,
            }
        }
规约到top_statement
top_statement:
        error
            {
                // error
                $$ = nil
            }
    |   statement
            {
                fmt.Println("规约到top_statement")
                $$ = $1
            }
规约到top_statement_list
top_statement_list:
        top_statement_list top_statement
            {
                fmt.Println("规约到top_statement_list")
                if $2 != nil {
                    $$ = append($1, $2)
                }
            }
    |   /* empty */
            {
                $$ = []ast.Vertex{}
            }
;

2.6 读入ID 0

先规约
start:
        top_statement_list
            {
            xxx
            
            }
这里符合start 的要求。这个语法通过

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注