最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 用PHP从零开始写一个编译器(四)

    正文概述 转载于:掘金(祁宏)   2021-05-14   461

    前面的链接:

    php从零开始写一个编译器(一)

    用PHP从零开始写一个编译器(二)

    用PHP从零开始写一个编译器(三)

    上一节中我们讲到了查询条件中的逻辑表达式节点:LogicNode。逻辑符写链接的是什么呢?

    除了not有可能与常量结合,其它的都是谓词表达式。而谓词表达式有一堆运算符。同样,我们将这些定义也放在Symbols类中了。

        /**
         * @var array
         */
        public static $operators = [
            'eq' => '=',
            'ne' => '<>',
            'gt' => '>',
            'ge' => '>=',
            'lt' => '<',
            'le' => '<=',
            'is' => 'is',
            'in' => 'in',
            'out' => 'not in',
            'like' => 'like',
            'between' => 'between',
            'contains' => 'contains'
        ];
    

    以上就是所有的谓词操作符。数组中的Key,就是RQL的函数名,可以说,真的相当简单。 比如,eq(a,b) 就是 a = b 。

    那么我们来看看谓词节点的类:

    declare(strict_types=1);
    /*
     * This file is part of the ByteFerry/Rql-Parser package.
     *
     * (c) BardoQi <67158925@qq.com>
     *
     * For the full copyright and license information, please view the LICENSE
     * file that was distributed with this source code.
     */
    
    namespace ByteFerry\RqlParser\AstBuilder;
    
    use ByteFerry\RqlParser\Lexer\Symbols;
    use ByteFerry\RqlParser\Exceptions\ParseException;
    
    /**
     * Class PredicateNode
     *
     * @package ByteFerry\RqlParser\Ast
     */
    class PredicateNode extends AstNode implements NodeInterface
    {
    
        /**
         * @return mixed
         * 这里是对 between谓词的特殊操作
         */
        public function between(){
            [$a, $b, $c] = $this->stage;
            $paramaterRegister =ParamaterRegister::getInstance();
            $paramaterRegister->add($a . '_from',$b);
            $paramaterRegister->add($a . '_to',$c);   // 我们拆成了两个变量, xx_from xx_to
            $this->output[0] = sprintf(' %s BETWEEN %s and %s ', $a, $b, $c);
            return $this->output[0];
        }
    
        /**
         * @return mixed
         */
        public function build(){
            $this->buildChildren();
            $operator = Symbols::$operators[$this->operator]??null;
            $paramaterRegister =ParamaterRegister::getInstance();
    
            /**
             * for extend other predicate
             * 目前会调用between,但未来想增加也是可以的
             */
            if(method_exists($this,$operator)){
                return $this->$operator();
            }
    
            [$a, $b] = $this->stage;  //取出预编译的结果
            if(null === $operator){
                throw new ParseException('The operators ' . $this->symbol . ' is not defined in the parser! ');
            }
            $paramaterRegister->add($a,$b);  // 添加变量到注册表
            $this->output[0] = sprintf(' %s %s %s ', $a,$operator,$b);   // 格式化成 a 谓词 b的形式。
            return $this->output[0];
        }
    
    }
    

    那么谓词节点的子节点是什么呢? 常量节点

    常量,实际上是基本不用编译的,常量有以下几类,变量名(字段名),常量值,还有特殊常量。我们来看源码:

    declare(strict_types=1);
    /*
     * This file is part of the ByteFerry/Rql-Parser package.
     *
     * (c) BardoQi <67158925@qq.com>
     *
     * For the full copyright and license information, please view the LICENSE
     * file that was distributed with this source code.
     */
    
    namespace ByteFerry\RqlParser\AstBuilder;
    
    use ByteFerry\RqlParser\Lexer\ListLexer;
    
    /**
     * Class ValueNode
     *
     * @package ByteFerry\RqlParser\AstBuilder
     */
    class ConstantNode extends AstNode implements NodeInterface
    {
    
        /**
         * @return int
         */
        protected function true(){
            return 1;
        }
    
        /**
         * @return int
         */
        protected function false(){
            return 0;
        }
    
        /**
         * @return string
         */
        protected function null(){
            return 'null';
        }
    
        /**
         * @return string
         */
        protected function empty(){
            return '""';
        }
    
        /**
         * @param \ByteFerry\RqlParser\Lexer\ListLexer $ListLexer
         *
         * @return int|void
         */
        public function load(ListLexer $ListLexer){
            return $ListLexer->getNextIndex();  //直接跳到下一个token
        }
    
        /**
         * @return mixed
         */
        public function build(){
            $symbol = $this->symbol;
            if(method_exists($this,$symbol)){
                $this->output[0] = $this->$symbol();
                return  $this->output[0];
            }
            $this->output[0] = $symbol;
            return  $this->output[0];
        }
    
    }
    

    可以看出,这里定义了true(), false(), null(), empty() ,其它都是原样返回

    谓词操作符中,有in和out,它们的第二个参数都是数组。比如:

    in(age,(13,25,37))

    这一情况下,我们需要有一个数组节点。代码如下:

    declare(strict_types=1);
    /*
     * This file is part of the ByteFerry/Rql-Parser package.
     *
     * (c) BardoQi <67158925@qq.com>
     *
     * For the full copyright and license information, please view the LICENSE
     * file that was distributed with this source code.
     */
    
    namespace ByteFerry\RqlParser\AstBuilder;
    
    
    /**
     * Class ArrayNode
     *
     * @package ByteFerry\RqlParser\AstBuilder
     */
    class ArrayNode extends AstNode implements NodeInterface
    {
    
        /**
         * @return mixed
         */
        public function build(){
            $this->buildChildren();
            $this->output[0] =  ' (' . implode(', ', $this->stage) . ') ';
            return $this->output[0];
        }
    
    }
    

    可以看出,它就是把子节点再变回在括号内的逗号分隔字符串。

    前面都是些基本查询,对于聚合查询,那一定有一些聚合函数。对于聚合函数,仍是按原样返回。请看代码:

    declare(strict_types=1);
    /*
     * This file is part of the ByteFerry/Rql-Parser package.
     *
     * (c) BardoQi <67158925@qq.com>
     *
     * For the full copyright and license information, please view the LICENSE
     * file that was distributed with this source code.
     */
    
    namespace ByteFerry\RqlParser\AstBuilder;
    
    
    /**
     * Class AggregateNode
     *
     * @package ByteFerry\RqlParser\AstBuilder
     */
    class AggregateNode extends AstNode implements NodeInterface
    {
        /**
         * @return mixed
         */
        public function build(){
            $this->buildChildren();
            $this->output[0] =  $this->operator . '(' . $this->stage[0]. ')';
            return $this->output[0];
        }
    
    }
    

    至于有哪些聚合函数,我们在文档中有说明,请看文档:

    byteferry.github.io/rql-parser/…

    到此,我们还剩下4个节点,

    • 用作排序的,SortNode,
    • 用于分页的, LimitNode,
    • 用作搜索的:SearchNode,
    • 用于写操作的:DataNode,

    排序节点:RQL示例:sort(+age, -score)

    这是指,age顺序,score倒序。可见,RQL相当简洁。

    以下是代码

    
    /*
     * This file is part of the ByteFerry/Rql-Parser package.
     *
     * (c) BardoQi <67158925@qq.com>
     *
     * For the full copyright and license information, please view the LICENSE
     * file that was distributed with this source code.
     */
    
    declare(strict_types=1);
    
    
    namespace ByteFerry\RqlParser\AstBuilder;
    
    use ByteFerry\RqlParser\Lexer\Symbols;
    use ByteFerry\RqlParser\Lexer\ListLexer;
    
    /**
     * Class SortNode
     *
     * @package ByteFerry\RqlParser\Ast
     */
    class SortNode extends AstNode implements NodeInterface
    {
    
        /**
         * @return string
         */
        public function getNodeType()
        {
            return 'sort';
        }
    
        /**
         * @param \ByteFerry\RqlParser\Lexer\ListLexer $ListLexer
         * 此函数是提供给load函数调用,返回要排序的哪个属性(列),以及方向。
         * @return \Generator|void
         */
        protected function getArguments(ListLexer $ListLexer){
    
            $token = $ListLexer->consume();  // 先消费一个token
            // the sort node will end with ')'
            // we only need consume the word token
            while(!$token->isClose()){
                $property = $token->getSymbol();
                $direction = 'ASC';  //默认给定 ASC
    
                // with prev type we could know the direction.
                if($token->getPrevType() === Symbols::T_MINUS){   前一个类型如果是T_MINUS,则就是DESC
                    $direction = 'DESC';
                }
                yield [$property, $direction]; ,返回一个控代器给LOAD
                $token = $ListLexer->consume(); 
    
            }
    
        }
    
        /**
         * @param \ByteFerry\RqlParser\Lexer\ListLexer $ListLexer
         *
         * @return int|void
         */
        public function load(ListLexer $ListLexer){
            /**
             * set resource_name value
             * 这里只负责写入  stage
             */
            foreach($this->getArguments($ListLexer) as $argument){
                $this->stage[] = $argument;
            }
            // return the $index, perhaps there are other queries still.
            return $ListLexer->getNextIndex();
        }
    
        /**
         * @return mixed
         */
        public function build(){
            $this->output = [$this->getNodeType() => $this->stage];
            return $this->output;
        }
    
    }
    

    可见,排序重载了load函数,这里又一次看到,Token中保存上一个类型,下一个类型的方便性了。

    分页节点 LimitNode,

    declare(strict_types=1);
    /*
     * This file is part of the ByteFerry/Rql-Parser package.
     *
     * (c) BardoQi <67158925@qq.com>
     *
     * For the full copyright and license information, please view the LICENSE
     * file that was distributed with this source code.
     */
    
    namespace ByteFerry\RqlParser\AstBuilder;
    
    
    /**
     * Class LimitNode
     *
     * @package ByteFerry\RqlParser\Ast
     */
    class LimitNode extends AstNode implements NodeInterface
    {
    
        /**
         * @return mixed
         */
        public function build(){
    
            $this->buildChildren();
            $this->output = ['limit'=>[$this->stage[0],$this->stage[1]??0]] ;
            return $this->output;
    
        }
    
    }
    

    分页本身也就上一到两个数字子节点,是常时子节点。至于这两个数字是offser和perPage还是page和perPage,我们不管,我们只把limit交出去就行。这样的好处是,自由度高一些。

    搜索节点:SearchNode, 为什么要这个节点呢?因为,有时搜索是在多个字段中同时找的。所以,用SearchNode,单独传查询参数是一个好想法。同样,像Laravel框架中一些插件,可以让你定义Searchable,即哪些字段要查询。这就大大减少了代码量。

       /*
     * This file is part of the ByteFerry/Rql-Parser package.
     *
     * (c) BardoQi <67158925@qq.com>
     *
     * For the full copyright and license information, please view the LICENSE
     * file that was distributed with this source code.
     */
    
    declare(strict_types=1);
    
    namespace ByteFerry\RqlParser\AstBuilder;
    
    
    /**
     * Class SearchNode
     *
     * @package ByteFerry\RqlParser\Ast
     */
    class SearchNode extends AstNode implements NodeInterface
    {
    
        /**
         * @return string
         */
        public function getNodeType()
        {
            return 'search';
        }
    
        /**
         * @return mixed
         */
        public function build(){
    
            $this->buildChildren();
            $query =  trim($this->stage[0],'\'"');  //常量节点会返回带引号的值,所以,变量名去掉引号
            if(trim($query,'%')===$query){
                $query .='%';  //没有通配符的,也加上
            }
            $this->output = [$this->getNodeType() =>$query];
            return $this->output;
        }
    
    }
    

    写操作用的数据节点,DataNode,目前流行的RQL并没有写操作,当然,我现在也不清楚oracle的RQL是否支持写操作。数据节点的表示方式示例:data(age:12,score:89)

    declare(strict_types=1);
    /*
     * This file is part of the ByteFerry/Rql-Parser package.
     *
     * (c) BardoQi <67158925@qq.com>
     *
     * For the full copyright and license information, please view the LICENSE
     * file that was distributed with this source code.
     */
    
    namespace ByteFerry\RqlParser\AstBuilder;
    
    
    /**
     * Class DataNode
     *
     * @package ByteFerry\RqlParser\Ast
     */
    class DataNode extends AstNode implements nodeInterface
    {
    
        /**
         * @return mixed
         */
        public function build(){
            $this->buildChildren();
            foreach($this->pair() as $item){  //使用父类的pair迭代器,一次取两个
                [$property, $value] = $item;
                $this->output[$property] = trim($value,'""');  //写成数组
            }
            return ['data' => $this->output];
        }
    
    }
    

    到目前为此,抽象语法树中的节点类我们都讲解完了。再复习一下程序的流程 1、拿到字符串以后,先用Lexer将其变成Token数组,放到ListLexer中,然后,Parser通过NodeVisitor创建节点,调用Load方法,实现数据加载为抽象语法树,同时也完成预编译。 接下来调用顶层build方法,完成编译,获得结果。build是深度优先的。所以,一定是最先编译叶节点,逐步向上返回。 下一节,我们要讲一些辅助的类,以及关于代码的单元测试了。(待续)


    起源地下载网 » 用PHP从零开始写一个编译器(四)

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元