精华内容
下载资源
问答
  • 语法解析及Antlr

    千次阅读 2020-02-11 19:28:00
    1 语法解析 1.1 语法解析器 1.1.1 执行流程 1.1.2 语法树好处 1.1.3 解析方法LL与LR 1.1.4 抽象语法树(AST) 1.2 语法规则文件 2 Antlr 2.1 解析方法 2.1.1 递归下降语法分析器 2.1.2 ALL(*)解析器 2.2 ...

    目录

     

    1 语法解析

    1.1 语法解析器

    1.1.1 执行流程

    1.1.2  语法树好处

    1.1.3 解析方法LL与LR

    1.1.4 抽象语法树(AST)

    1.2  语法规则文件

    2 Antlr

    2.1  解析方法

    2.1.1  递归下降语法分析器

    2.1.2 ALL(*)解析器

    2.2  语法解析树

    2.3 使用方法

    2.3.1 使用流程

    3 Listener 和 Visitor 机制

    3.1 Listener模式

    3.2 Visitor模式

    3.3 相关问题释疑

    3.9 实践问题

    4 规则命中策略

    5 antlr的应用

    5.1 在Twitter中的应用

    5.2  大数据产品中的应用 

    9 参考资料


    1 语法解析

    1.1 语法解析器

    1.1.1 执行流程

    第一个阶段是词法分析,主要负责将符号文本分组成符号类tokens

    第二个阶段就是真正的语法解析,目标就是构建一个语法解析树。

     

    语法解析的输入是tokens,输出就是一颗语法解析树。

    1.1.2  语法树好处

    1. 树形结构易于遍历和处理,并且容易被程序员理解,方便了应用代码做进一步处理。

    2. 多种解释或翻译的应用代码都可以重用一个解析器。但 ANTLR 也支持像传统解析器生成器那样,将应用处理代码直接嵌入到语法中。

    3. 对于因为计算依赖而需要多趟处理的翻译器来说,比起多次调用解析器去解析,遍历语法树多次更加高效。

    1.1.3 解析方法LL与LR

    根据推导策略的不同,语法解析分为LL与LR两种

    LR自低向上(bottom-up)的语法分析方法

    LL是自顶向下(top-down)的语法分析方法

    举个例子,比如如何描述一个Json字符串?

    Json字符串是一个key:Value字符串,其中key是字符串,value可以是String、数字、或者又是一个Json。csv也是这样,类似一种递归定义。

    exp1 = x1;

    exp2 = x2;

    result = exp1 'operator' exp2

    两类分析器各有其优势,适用不同的场景,很难说谁要更好一些。普遍的说法是LR可以解析的语法形式更多LL的语法定义更简单易懂

    1.1.4 抽象语法树(AST)

     抽象语法树是将源代码根据其语法结构,省略一些细节(比如:括号没有生成节点),抽象成树形表达。

     抽象语法树在计算机科学中有很多应用,比如编译器、IDE、压缩优化代码等。

     如配置文件读取器,遗留代码转换器,wiki 标记渲染器和 JSON 解析器

     [抽象语法树] https://tech.meituan.com/abstract-syntax-tree.html 该文中也举了一个遗留代码转换器的例子

     Json解析器:Twitter

     note:一些比较简单的其实可以用正则来处理,但是规则容易误中,表达能力也非常受限。

    源文件--->抽象,结构化------->parse

    1.2  语法规则文件

    编写语法规则文件的同时,其实就是一个抽象的过程。示例如下:

     

    从上图可以看出主要语法文件分为四块,第二块一般可以不写,最重要的就是语法规则。

    语法规则是以小写字母组成。如prog,stat。词法规则由大写字母组成。如ID:[a-zA-Z]+。

    通过使用 | 运算符来将不同的规则分割,还可以使用括号构成子规则。例如(‘*’ | ‘/’)会匹配多个乘号或除号。注释: // /* */其他空格连接符: 表示顺序连接| 选择符: 表示或者的关系重复次数: + *可选: ?

     

    https://github.com/antlr/grammars-v4 这类有各种语法文件示例。

    FAQ

    Q: 为啥大家都是换行写分号?这个是

    A: 【TODO】

    Q: stat+ 是必须的表示语句一次可以有多个吗?

    A:表示可以一次解析多个语句。比如 ANTLRInputStream inputStream = new ANTLRInputStream("ord=(3*4)/2 price=(4*3)/2"); 这两个stat就可以解析。当然如果我们是每次只解析一个,也可以不写+。

    Q: 那么多规则,规则内还有分支,谁先谁后?具体的逻辑。

    A: 这个谁先谁后还是看具体场景的逻辑,整体就是希望先被识别成哪种模式,就把该标签放在前面。

    2 Antlr

    2.1  解析方法

    2.1.1  递归下降语法分析器

    是自顶向下语法解析器的一种。

    顾名思义,递归下降指的就是解析过程是从语法树的根开始,向叶子(token)递归。还是以前面的赋值表达式解析为例,其递归下降语法分析器的代码大概是下面这个样子:

     

     

    stat()、assign()、expr()等方法调用所形成的调用栈能与语法分析树的内部节点一一对应。match()的调用对应树的叶子,而assign()方法直接顺序读取输入字符,而不用做任何选择。相比之下,stat()方法要复杂一些,因为在解析时,它需要向前看一些字符才能确认走哪个代码分支,有时甚至要读取完所有输入才能得出预测结果。

    2.1.2 ALL(*)解析器

    ANTLR从4.0开始生成的是ALL(*)解析器,其中A是自适应(Adaptive)的意思。对传统的LL(*)解析器有很大的改进,ANTLR是目前唯一可以生成ALL(*)解析器的工具。

    2.2  语法解析树

    在ANTLR4中已经不再提供生成AST的功能(在ANTLR3中可以通过Options指定output=AST来生成AST),而是生成了分析树(Parse Tree)。

    2.3 使用方法

    2.3.1 使用流程

    http://www.antlr.org/

    自定义文法--->词法分析器(Lexical)------>语法分析器(Parse)—→Grammer

    draw.io evaluation version

    Lexical

    文法文件

    Parse

    处理后的文件

    输入

    g4文件

    Parse

    输出

    antlr

    1. 自定义词法分析器 (g4后缀)文件

    2. 用工具生成Java文件

    3. 继承xxBaseVisitor类实现自己的Visitor

     

    在整个Antlr的使用过程中,最重要的就是编写规则文件。

    规则文件的编写:

    Grammer文件:

    grammar Cal;

    prog: stat+;  //一个程序由至少一条语句组成

    /*为了以后的运算的方便性,我们需要给每一步规则打上标签,标签以”#”开头,出现在每一条规则的右边。打上标签后,antlr会为每一个规则都生成一个事件;在没有标签的情况下,每个规则会生成一个Context以及一个事件。 如下述语句,如果没有标签,那么只会生成一个StatContext和一个visitStat方法。 */

    stat: ID '=' expr ';' #Assign //变量赋值语句

    | 'print' '(' expr ')' ';' #printExpr   //输出语句

    ;

    expr: expr op=('*'|'/') expr #MulDiv  //表达式可以是表达式之间乘除

    | expr op=('+'|'-') expr #AddSub  //表达式可以是表达式之间加减

    | NUM #NUM    //表达式可以是一个数字

    | ID #ID  //表达式可以是一个变脸

    | '(' expr ')' #parens    //表达式可以被括号括起来

    ;

    MUL:'*';

    DIV:'/';

    ADD:'+';

    SUB:'-';

    ID: [a-zA-Z][a-zA-Z0-9]*; //变量可以是数字和字母,但必须以字母开头

    //负数必须要用"()"括起来

    NUM: [0-9]+   //正整数

    | '(' '-' [0-9]+ ')'  //负整数

    | [0-9]+'.'[0-9]+   //正浮点数

    | '(' '-' [0-9]+'.'[0-9]+ ')'   //负浮点数

    ;

    WS: [ \t\r\n] -> skip;    //跳过空格、制表符、回车、换行

    生成命令:

    编辑完 Antlr 文件后,我们在安装有 Antlr plugin 的 Intellij 上,可以通过右键语法规则对语法规则进行测试,并可以在配置生成中间代码的包名、路径等选项后,直接生成中间代码。

     

    生成的文件说明:

     

     

    • Hello.tokens和HelloLexver.tokens为文法中用到的各种符号做了数字化编号,对于我们定义的每个 token,ANTLR 分配了一个 token 类型码并将这些值保存在 ArrayInit.tokens。因为这个文件的存在,当我们将较大规模的语法分割为各种小型的语法表达时,ANTLR 能够使同种 token 的类型码保持一致。

    • HelloLexer.java 包含专用的词法分析程序(lexer)类的定义

    • HelloParse.java 包含了专用于 ArrayInit 语法的解析器(parser)类的定义。

    • HelloVisitor 和 HelloBaseVisitor 分别是语法解析树的vistor的接口和类,用于遍历整个语法树。一般情况下,我们通过继承 HelloBaseVisitor 来实现自己对于语法树遍历的处理。

    • HelloListener和HelloBaseListener是用listen方式来遍历树的解析方法。

     

    HelloParse说明:

    ANTLR为每个子规则创建一个同名函数,因此可以方便地取到其子规则的context。即每个规则对应一个Context。

     

    Context对象包含以下内容:

    语法分析时生成

    • 起始Token,终止Token

    • children: 可以得到子语法规则中的内容。

    • 异常信息: 可以得到解析失败的信息。

     

     

     

    查看语法解析树:

     

    3 Listener 和 Visitor 机制

    在ANTLR 4以前,有两种开发方式:一是将目标语言的代码直接硬编码到语法定义文件中,在生成分析器时会插入这些代码到生成文件中,这也是大多数语法分析器生成工具的做法。

    在上边的语法判定与通道的例子中,就有将Java代码硬编码到语法定义的情况。将目标代码和语法定义耦合在了一起,当需要生成不同目标语言的分析器时,就需要维护多份语法定义文件,

    ‘增加了维护成本,同时在编写复杂逻辑时,由于IDE没有对目标语言的支持,开发和测试都很幸苦。另一种方式是让ANTLR生成语法分析树,然后写程序遍历语法树,对语法树的遍历是一个很复杂的工作。

    ANTLR 4开始会生成监听器(Listener)与访问者(Visitor),将语法定义与目标代码完全的解耦。监听器可以被动的接受语法分析树遍历的事件,对于每一个语法节点,

    都会生成进入enterSomeNodeName与退出exitSomeNodeName两个方法。访问者机制生成对语法节点的访问方法visitSomeNodeName,在访问方法中需要手动调用visit方法来对子节点进行遍历,

    使用访问者模式可以控制语法树的遍历,略过某些的分枝。ANTLR默认只生成监听器,生成访问者类需要在生成时添加-visitor选项。

     Visitor和Listener是antlr提供的两种树遍历机制。Listener是默认的机制,可以被antlr提供的遍历器对象调用;如果要用Visitor机制,在生成识别器时就需要显式说明 

    antlr4 -no-listener -visitor Calc.g4,并且必须显示的调用visitor方法遍历它们的子节点,在一个节点的子节点上如果忘记调用visit方法,就意味着那些子数没有得到访问

    核心区别:两者各有优劣,Listener模式适合全局查找,默认是深度优先遍历,而Visitor模式适合指定某个节点作遍历。

    listener默认是遍历每个节点,对于每个节点都有enter和exit事件。对于visitor则是需要自己手动用写代码去进行指定节点遍历。

    3.1 Listener模式

    示例如下

    public class WlgsParseListener extends MerakDslBaseListener{
        private ParseTreeProperty<String> property = new ParseTreeProperty<>();
    
        @Override
        public void exitArithmeticBinary(MerakDslParser.ArithmeticBinaryContext ctx) {
            MerakDslParser.ExpressionContext right = ctx.right;
            MerakDslParser.ExpressionContext left = ctx.left;
            String formula = left.getText() +  ctx.operator.getText() + right.getText();
            property.put(ctx,formula);
            super.exitArithmeticBinary(ctx);
        }
    }

    Listener模式的exitXX方法的返回值都是空,在遍历过程所有有临时数据需要保存,可以存近一个自定义数据结构中或者使用antrl的ParseTreeProperty中或者自定义一个数据结果保存都是OK的。

    3.2 Visitor模式

    从上面可以看出,即使我们不使用super调用父类的同名方法,也不调用自定义baseListener中的其他方法,其实listen也是可以完成遍历的。因此listener是被动进行遍历的。

    visitor不一样,是手动进行遍历,必须手动使用visit调用其他的visitor方法,或者使用super,父类中该方法是调用所有的子类visitor。

    从以下源码可以看出,super.visitXX都是一样的,都是visit 参数Context的 Children。

    @Override public T visitAssignment(MerakDslParser.AssignmentContext ctx) { return visitChildren(ctx); }
    	/**
    	 * {@inheritDoc}
    	 *
    	 * <p>The default implementation returns the result of calling
    	 * {@link #visitChildren} on {@code ctx}.</p>
    	 */
    	@Override public T visitCaseWhen(MerakDslParser.CaseWhenContext ctx) { return visitChildren(ctx); }
    	/**

    整体流程就是,

    visitProg(){
    
    super.visitProg(ctx)  //visit children就是所有的stat.accept(visitor)
      stat.accept(visitor) ==> visitor.visitStat()
      visitStat(ctx){super.visitStat} // visitStat的child
      以此类推。。
    }

    Q:如果Prog下有多个stat,那么用super visit所有的children的结果如何合并为一个结果(该结果是visitStat的返回值)。

    A: visitChildren源码可以看出,多个child的结果是,后续的结果会覆盖前面的结果,也就是visitProg的返回值就是最后一个visitStat的结果。

        public T visitChildren(RuleNode node) {
            T result = this.defaultResult();
            int n = node.getChildCount();
    
            for(int i = 0; i < n && this.shouldVisitNextChild(node, result); ++i) {
                ParseTree c = node.getChild(i);
                T childResult = c.accept(this);
                result = this.aggregateResult(result, childResult);
            }
    
            return result;
        }

    比如对于(3*4)/2 ;方法visitParens,如果方法实现是使用super.visitParens,则会返回")"。 Parens的children数组元素有三个,(、exp、);最后的“)”会覆盖前面两个。这个时候我们自定义visit的作用就体现出来了,一般对于四则运算的visitParens我们会重写如下

    visit(ctx.expression());

    重写的visitParens中,我们就手动指定了节点的访问。因此可以认为visitor模式就是主动遍历的模式。

    接下来还需要明确下 Context的层级结构,比如

     

     

    3.3 相关问题释疑

    1 语法文件中的#意味着什么?和生成的Context结构如何对应?

    #号的含义在上文语法文件说明部分已经解释了,没有#规则名对应一个Context,有#能以更细的分支来管理规则,#后面的别名对应着一个Context,也对应着一个访问方法。

    2 大部分时候都是有children,到底什么节点下有children?

     

    目前这个Expression的children下面没有数组,而是left 和right的属性。

    因此猜测,凡是有 left = xx;有这种描述语句的时候都是没有数组的。这个时候不能使用 super.xxx进行遍历

     

    3.9 实践问题

    其他细节:因为 字符串和null用+号拼接的时候,就相当于字符串 + “null”因此可能会出现 (null)的现象。有两种办法,一种在遍历括号节点的时候加判断,或者解析公式之前就加个校验,保证不出出现这种问题,

    天璇V3Manager工程 物理公式解析就是前端控件进行了校验,保证了不会出现这个问题。

    if (ctx.expression() != null){ 
      return visitExpression(ctx.expression());             
    } else{ 
      return Double.parseDouble(ctx.INT().getText());             
    }

    使用 Maven

    Antlr4 提供了 Maven Plugin,可以通过配置来进行编译。语法文件 g4 放置在 src/main/antlr4 目录下即可,配置依赖的 antlr4 和 plugin 即可。生成 visitor 在 plugin 配置 visitor 参数为 true 即可。

    注意:antlr4 的库版本要与 plugin 版本对应,antlr4 对生成文件用的版本与库本身的版本会进行对照,不匹配会报错。

    ...
    <properties>
        <antlr4.version>4.7.2</antlr4.version>
    </properties>
    
    <dependencies>
        <dependency>
            <groupId>org.antlr</groupId>
            <artifactId>antlr4</artifactId>
            <version>${antlr4.version}</version>
        </dependency>
    </dependencies>
    ...
    <build>
        <plugins>
            <plugin>
                <groupId>org.antlr</groupId>
                <artifactId>antlr4-maven-plugin</artifactId>
                <version>${antlr4.version}</version>
                <configuration>
                    <visitor>true</visitor>
                </configuration>
                <executions>
                    <execution>
                        <id>antlr</id>
                        <goals>
                            <goal>antlr4</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
    ...

     

    4 规则命中策略

    规则这么多,也许一个表达式能够命中多个规则分支,最终命中哪个是什么策略决定的。以下进行说明。

    【TODO】

    5 antlr的应用

    一般的处理对象有:

    • SQL语句

    • 算数表达式

    • 编程语言的源文件

    可以看出来其实编写grammar文件的过程其实就是对某种场景的抽象,定义一些基础的元素,然后Parse能够针对输入生成某种输入的树结构,然后遍历这种树结构,在遍历的过程中可以做一些处理,比如翻译。

     

    note:

    [各种语言的antlr解析] https://blog.csdn.net/xiyue_ruoxi/article/details/38925091 

    关于SQL的antlr解析

    5.1 在Twitter中的应用

    将查询参数解析成一个语法解析树

    5.2  大数据产品中的应用 

    antlr在hibernate、presto以及SparkSQL中都是使用了的。

    antlr在presto及SparkSQL中作用是相似的,都是就是生成了生成一个SQL语法解析树(一个未处理的逻辑执行计划)。

     

     

    后续是对逻辑执行计划是进行了优化的。

    也就是说presto和spark是从SQL到逻辑执行计划;天璇的逻辑执行计划是从查询参数构建出来,也就是手动完成了antlr的过程。

    9 参考资料

    1. [官网 看 antlr 4 reference手册,权威 必看http://www.antlr.org/

    2. [进阶] https://liangshuang.name/2017/08/20/antlr/

    3. [基础] http://kyonhuang.top/ANTLR-learning-notes-1/

    4.  [visitor] https://dohkoos.gitbooks.io/antlr4-short-course/content/calculator-visitor.html

    5. https://toutiao.io/posts/2a6f21/preview

    6. [使用语法错误异常做补全] https://www.liangshuang.name/2017/08/20/antlr/

    7. [各种语法文件] https://github.com/antlr/grammars-v4

    展开全文
  • postgresql内核语法解析器详解

    千次阅读 2017-01-24 19:34:34
    postgresql内核语法解析器详解概述前面博文中谈过parser语法解析模块,但没深入介绍,本文相对详细的介绍下,对postgresql语法解析模块初步揭密。

    概述

    前面博文中谈过parser语法解析模块,但没深入介绍,本文相对详细的介绍下。
    当PostgreSQL的后台进程Postgres接收到查询语句后,首先将其传递给查询分析模块,进行词法、语法和语义分析。若是功能性命令(例如建表、创建用户、备份等)则将其分配到功能性命令处理模块;对于查询命令(SELECT/INSERT/DELETE/UPDATE)则要为其构建查询树(Query结构体),然后交给查询重写模块。

    下面用两图来说明查询分析过程:

    这里写图片描述
    在raw_parser中,将通过Lex和Yacc(GNU下Flex和Bison)生成的代码来进行词法和语法分析并生成分析树。

    关于Flex和Bison的基础知识不在本文介绍,但是想继续阅读本文需要去提前学习,至少需要使用这两个工具完成简易计算器的编写并大概理解其中原理。

    一天之内不再畏惧lex&yacc之必备参考资料

    flex and bison :做个计算器
    更多postgresql内核开始相关文章请点击

    几个核心文件简介

    源文件 说明
    gram.y 定义语法结构,bison编译后生成gram.y和gram.h
    scan.l 定义词法结构,flex编译后生成scan.c
    kwlist.h 关键字列表,需要按序排列
    check_keywords.pl linux下会调用其进行关键字检查(顺序、合法性等)

    移进/规约(shift/reduce)冲突

    本文主要介绍移进/规约冲突。

    以ALTER TABLE语法中的alter_table_cmd为例

    alter_table_cmd:
                /* ALTER TABLE <name> ADD <coldef> */
                ADD_P columnDef
                    {
                        AlterTableCmd *n = makeNode(AlterTableCmd);
                        n->subtype = AT_AddColumn;
                        n->def = $2;
                        $$ = (Node *)n;
    				}
    			/* ALTER TABLE <name> ADD COLUMN <coldef> */
    			| ADD_P COLUMN columnDef
    				{
    					AlterTableCmd *n = makeNode(AlterTableCmd);
    					n->subtype = AT_AddColumn;
    					n->def = $3;
    					$$ = (Node *)n;
    				}
    			/* ALTER TABLE <name> ALTER [COLUMN] <colname> {SET DEFAULT <expr>|DROP DEFAULT} */
    			| ALTER opt_column ColId alter_column_default
    				{
    					AlterTableCmd *n = makeNode(AlterTableCmd);
    					n->subtype = AT_ColumnDefault;
    					n->name = $3;
    					n->def = $4;
    					$$ = (Node *)n;
                    }
                ......

    对于上述语法,细心地读者可能有疑问,ADD_P columnDef与ADD_P COLUMN columnDef两个分支语法,是否可以合并为ADD_P opt_column columnDef一条语句呢,因为下面有很多地方也使用了opt_column,其中opt_column定义如下:

    opt_column: COLUMN                                  { $$ = COLUMN; }
    			| /*EMPTY*/								{ $$ = 0; }
            ;

    好,我们可以尝试一下更改之后,单独对gram.y进行编译。

    alter_table_cmd:
                ADD_P opt_column columnDef
                    {
                        AlterTableCmd *n = makeNode(AlterTableCmd);
                        n->subtype = AT_AddColumn;
                        n->def = $3;
                        $$ = (Node *)n;
                    }
                ......

    编译结果:

    [postgres@localhost postgresql-9.4.1]$ bison -v src/backend/parser/gram.y
    src/backend/parser/gram.y: conflicts: 1 shift/reduce
    src/backend/parser/gram.y: error: expected 0 shift/reduce conflicts

    可知,修改之后产生了1个移进规约冲突(pg961的版本支持了IF NOT EXISTS语法,会产生更过的移进/规约冲突,使用961以后版本的读者请知悉)。

    何为移进/规约冲突呢?简而言之,就是在一个语法分支中,当bison读到一个token之后,有两条路可以选择:一条是继续往后移进一个token,以期匹配一条规则;另一条则是认为前面已经读到的token已经匹配了一条规则,可以进行规约了。还是不明白?那就继续结合上面的例子,继续深入一下。

    读者可以自行思考何谓规约/规约冲突。

    调试冲突

    调试移进/规约冲突时,gram.output(就叫报告文件吧)是非常有用的。bison加上 -v选项可以生成此文件。

    Terminals unused in grammar
    
       DOT_DOT
    
    
    State 1269 conflicts: 1 shift/reduce
    
    ......
    
    State 1269
    
      267 alter_table_cmd: ADD_P . opt_column columnDef
      279                | ADD_P . TableConstraint
    
        CHECK       shift, and go to state 1917
        COLUMN      shift, and go to state 1990
        CONSTRAINT  shift, and go to state 1918
        EXCLUDE     shift, and go to state 1919
        FOREIGN     shift, and go to state 1920
        PRIMARY     shift, and go to state 1921
        UNIQUE      shift, and go to state 1922
    
        EXCLUDE   [reduce using rule 1105 (opt_column)]
        $default  reduce using rule 1105 (opt_column)
    
        TableConstraint  go to state 1991
        ConstraintElem   go to state 1924
        opt_column       go to state 1992

    可以看出,在一条ALTER TABLE命令中,当读到一个ADD后,假如下一个token是EXCLUDE的话,有移进和规约两条路可以走。 结果文件中的“.”代表“我们在哪儿”,方括号标志不可达的动作(后面说明含义)。

    State 1919
    
      448 ConstraintElem: EXCLUDE . access_method_clause '(' ExclusionConstraintList ')' opt_definition OptConsTableSpace ExclusionWhereClause ConstraintAttributeSpec
    
        USING  shift, and go to state 2568

    可以看出,state-1919说明EXCLUDE是ConstraintElem开头的token,这就是移进。

    opt_column: COLUMN
               | /* empty */

    rule-1105则是opt_column的空选项。在这里,bison可以将EXCLUDE理解为一个ColumnDef的开始,这就是规约。

    知道了问题所在之后,该怎么解决呢?其实这个问题,是我用来说明问移进/规约冲突构造出来的。解决方案大家都知道了,就是将ADD_P opt_column columnDef这个规则扩展为两个:ADD_P columnDef与ADD_P COLUMN columnDef。那为什么扩展为两个之后就没有移进/规约冲突了呢?

    其实要是大家看懂了上面的内容,答案就呼之欲出了。扩展为两条语法分支之后,在ADD之后,读到EXCLUDE这个token,bison不需要去做移进还是规约的选择了,因为没有可以用来规约的语法了。这么说吧,读到EXCLUDE这个关键字后,不管走ConstraintElem还是columnDef两条路,要做的都是移进。

    请读者再深入考虑一下这个例子(alter_table_cmd的全部代码请查看PostgreSQL源码):

    alter_table_cmd:
                /* ALTER TABLE <name> ADD <coldef> */
                ADD_P columnDef
                    {
                        AlterTableCmd *n = makeNode(AlterTableCmd);
                        n->subtype = AT_AddColumn;
                        n->def = $2;
                        $$ = (Node *)n;
    				}
    			/* ALTER TABLE <name> ADD COLUMN <coldef> */
    			| ADD_P COLUMN columnDef
    				{
    					AlterTableCmd *n = makeNode(AlterTableCmd);
    					n->subtype = AT_AddColumn;
    					n->def = $3;
    					$$ = (Node *)n;
    				}
    			/* ALTER TABLE <name> ALTER [COLUMN] <colname> {SET DEFAULT <expr>|DROP DEFAULT} */
    			| ALTER opt_column ColId alter_column_default
    				{
    					AlterTableCmd *n = makeNode(AlterTableCmd);
    					n->subtype = AT_ColumnDefault;
    					n->name = $3;
    					n->def = $4;
    					$$ = (Node *)n;
                    }
                ......

    这里的ALTER句中为什么就可以使用opt_column来整合两条规则?

    使用优先级解决冲突

    在PostgreSQL源码的语法分析中,经常用到使用优先级来解决冲突的办法,本节结合PG源码进行举例说明。

    先看例子的表象

    对于update语法,查看官网可以看到语法如下:

    [ WITH [ RECURSIVE ] with_query [, ...] ]
    UPDATE [ ONLY ] table_name [ * ] [ [ AS ] alias ]
        SET { column_name = { expression | DEFAULT } |
              ( column_name [, ...] ) = ( { expression | DEFAULT } [, ...] ) } [, ...]
        [ FROM from_list ]
        [ WHERE condition | WHERE CURRENT OF cursor_name ]
        [ RETURNING * | output_expression [ [ AS ] output_name ] [, ...] ]

    也就是说在update的时候,可以对表名取别名(官网上提到,对于去了别名的表,在update句的其它地方想引用这个表,都必须用别名,本来的表名被隐藏,此处不是重点暂且不表)。

    如以下测试用例:

    drop table test;
    create table test(id int);
    insert into test values (1);
    insert into test values (2);
    insert into test values (3);
    
    update test t set id = 4 where t.id = 2; --success
    update test set set id = 4 where id = 2; --error

    为什么将test取别名为set,会出现错误?

    再看例子的内在

    src/backend/parser/gram.y

    中,优先级定义是如下代码段:

    /* Precedence: lowest to highest */
    %nonassoc   SET             /* see relation_expr_opt_alias */
    %left       UNION EXCEPT
    %left       INTERSECT
    %left       OR
    %left       AND
    %right      NOT
    %nonassoc   IS ISNULL NOTNULL   /* IS sets precedence for IS NULL, etc */     
    %nonassoc   '<' '>' '=' LESS_EQUALS GREATER_EQUALS NOT_EQUALS
    %nonassoc   BETWEEN IN_P LIKE ILIKE SIMILAR NOT_LA
    %nonassoc   ESCAPE          /* ESCAPE must be just above LIKE/ILIKE/SIMILAR */
    %left       POSTFIXOP       /* dummy for postfix Op rules */
    
    %nonassoc   UNBOUNDED       /* ideally should have same precedence as IDENT */   
    %nonassoc   IDENT NULL_P PARTITION RANGE ROWS PRECEDING FOLLOWING CUBE ROLLUP    
    %left       Op OPERATOR     /* multi-character ops and user-defined operators */ 
    %left       '+' '-'                                                              
    %left       '*' '/' '%'                                                          
    %left       '^'                                                                  
    /* Unary Operators */                                                            
    %left       AT              /* sets precedence for AT TIME ZONE */               
    %left       COLLATE                                                              
    %right      UMINUS                                                               
    %left       '[' ']'                                                              
    %left       '(' ')'                                                              
    %left       TYPECAST                                                             
    %left       '.'                                                                  
    
    %left       JOIN CROSS LEFT FULL RIGHT INNER_P NATURAL                           
    /* kluge to keep xml_whitespace_option from causing shift/reduce conflicts */    
    %right      PRESERVE STRIP_P                                                     

    语法规则定义如下:

    UpdateStmt: opt_with_clause UPDATE relation_expr_opt_alias
                SET set_clause_list
                from_clause
                where_or_current_clause 
                returning_clause
                    {   
                        UpdateStmt *n = makeNode(UpdateStmt);
                        n->relation = $3;
                        n->targetList = $5;
                        n->fromClause = $6;
                        n->whereClause = $7;
                        n->returningList = $8;
                        n->withClause = $1;
                        $$ = (Node *)n;
                    }
            ;
    
    relation_expr_opt_alias: relation_expr                  %prec UMINUS 
                    {                                                    
                        $$ = $1;                                         
                    }                                                    
                | relation_expr ColId                                    
                    {                                                    
                        Alias *alias = makeNode(Alias);                  
                        alias->aliasname = $2;                           
                        $1->alias = alias;                               
                        $$ = $1;                                         
                    }                                                    
                | relation_expr AS ColId                                 
                    {                                                    
                        Alias *alias = makeNode(Alias);                  
                        alias->aliasname = $3;                           
                        $1->alias = alias;                               
                        $$ = $1;                                         
                    }                                                    
            ;                                                            

    在这里,请重点关注relation_expr_opt_alias: relation_expr %prec UMINUS这一行中的%prec UMINUS.

    首先我们考虑将%prec UMINUS去除,会产生什么后果?当然,想一想之后可以去尝试一下。

    [blog@localhost postgresql-9.6.1]$ bison -v src/backend/parser/gram.y 
    src/backend/parser/gram.y: conflicts: 1 shift/reduce
    src/backend/parser/gram.y: error: expected 0 shift/reduce conflicts

    果然,产生了1处移进规约的冲突。

    再读报告文件:

    State 1315 conflicts: 1 shift/reduce
    
    State 1315
    
      1618 relation_expr_opt_alias: relation_expr .
      1619                        | relation_expr . ColId
      1620                        | relation_expr . AS ColId
    
        IDENT            shift, and go to state 222
        ......
        SET              shift, and go to state 464
        ......
    
        SET       [reduce using rule 1618 (relation_expr_opt_alias)]
        $default  reduce using rule 1618 (relation_expr_opt_alias)

    之所以有移进/规约冲突,是对于“UPDATE foo set set…”的句式产生的歧义(源码中有注释,请读者自行阅读并理解)。

    很明显,对relation_expr_opt_alias规则的第一个分支relation_expr加上%prec UMINUS之后,冲突可以得到解决。是何原因?

    回到优先级描述的那段代码,首先我们知道,对于优先级的高低,规则就是从上到下一次从低到高,可以看到SET这个token的优先级是明显低于UMINUS的,而%prec UMINUS则定义这个语法分支的优先级与UMINUS这个虚拟符号的优先级一致,所以当表名后读到一个set,解析器将表名规约到relation_expr_opt_alias这个语法规则中,将SET当成关键字而非表名。这也就可以解释,上面的用例update test set set id = 4 where id = 2;会报错的原因,第一个set已经匹配到了语法规则中的SET关键字,之后无法匹配另一个SET,报语法错。

    对于优先级使用源码中还可以找到许多用例,读者假如可以自己找一个分析一下,然后通过修改代码构造出冲突,再去理解使用优先级为何能解决这个冲突的话,应该就算是理解了。

     expect值

    对于PostgreSQL中的语法冲突,使用上面所说的两种方法

    • 调整语法分支结构,构造“冗余”–给出明确路径
    • 使用优先级(有时考虑结核性)

    应该可以解决大部分的问题(解决的时候尽量使用调整语法结构的方法,实在不行再考虑优先级)。PostgreSQL中也提供了妥协的方法,就是%expect这个变量。

    %pure-parser
    %expect 0

    expect的含义就是接受你有几个冲突,譬如将expect置为1,那你有一个移进/规约冲突,编译的时候是不会报错的。

    将relation_expr_opt_alias规则的第一个分支relation_expr的%prec UMINUS去掉,再将下述两个文件中的expect改为1(各自产生一个移进/规约冲突),可正常编译安装。

    src/backend/parser/gram.y

    src/interfaces/ecpg/preproc/preproc.y

    重启数据库之后可以发现,update test set set id = 4 where id = 2;可用。

    postgres=# update test as set set id = 4 where id = 2;
    UPDATE 1

    这里有涉及一个问题,有两条路径的时候,会默认走哪一条呢?答案是一般会默认走移进的规则(从支持update test set set id = 4 where id = 2;可看出)。上面有提到方括号表示不可达的路径,可以看到一般reduce(规约)都在方括号内。

    一般为了保证“纯净性”,不建议更改expect值,有冲突需要使用上面的方法进行解决,这样能保证语法是按照你的想法执行的。

    调试PG语法解析器

    在wiki上看到一个patch,可以对解析状态进行调试。

    diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
    index 6f43b85..5038ea5 100644
    --- a/src/backend/parser/gram.y
    +++ b/src/backend/parser/gram.y
    @@ -665,6 +665,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
    
    
     /* Precedence: lowest to highest */
    +%debug
     %nonassoc  SET             /* see relation_expr_opt_alias */
     %left      UNION EXCEPT
     %left      INTERSECT
    diff --git a/src/backend/parser/parser.c b/src/backend/parser/parser.c
    index 61d24e1..ce21f82 100644
    --- a/src/backend/parser/parser.c
    +++ b/src/backend/parser/parser.c
    @@ -24,6 +24,8 @@
     #include "parser/gramparse.h"
     #include "parser/parser.h"
    
    +extern int base_yydebug;
    +
    
     /*
      * raw_parser
    @@ -48,6 +50,9 @@ raw_parser(const char *str)
        /* initialize the bison parser */
        parser_init(&yyextra);
    
    +   /* enable parser debug */
    +   base_yydebug = 1;
    +
        /* Parse! */
        yyresult = base_yyparse(yyscanner);
    

    重新编译安装,可以看到解析器每个状态:

    postgres=# select 1;
    Starting parse
    Entering state 0
    Reading a token: Next token is token SELECT (: )
    Shifting token SELECT (: )
    Entering state 39
    Reading a token: Next token is token ICONST (: )
    Reducing stack by rule 1515 (line 10346):
    -> $$ = nterm opt_all_clause (: )
    Stack now 0 39
    Entering state 722
    Next token is token ICONST (: )
    Shifting token ICONST (: )
    Entering state 654
    Reducing stack by rule 2065 (line 13596):
       $1 = token ICONST (: )
    -> $$ = nterm Iconst (: )
    Stack now 0 39 722
    Entering state 1234
    Reducing stack by rule 2052 (line 13501):
       $1 = nterm Iconst (: )
    -> $$ = nterm AexprConst (: )
    Stack now 0 39 722
    Entering state 1233
    Reducing stack by rule 1815 (line 12068):
       $1 = nterm AexprConst (: )
    -> $$ = nterm c_expr (: )
    Stack now 0 39 722
    Entering state 1220
    Reducing stack by rule 1726 (line 11584):
       $1 = nterm c_expr (: )
    -> $$ = nterm a_expr (: )
    Stack now 0 39 722
    Entering state 1219
    Reading a token: Next token is token ';' (: )
    Reducing stack by rule 2036 (line 13386):
       $1 = nterm a_expr (: )
    -> $$ = nterm target_el (: )
    Stack now 0 39 722
    Entering state 1231
    Reducing stack by rule 2032 (line 13358):
       $1 = nterm target_el (: )
    -> $$ = nterm target_list (: )
    Stack now 0 39 722
    Entering state 1238
    Next token is token ';' (: )
    Reducing stack by rule 2030 (line 13353):
       $1 = nterm target_list (: )
    -> $$ = nterm opt_target_list (: )
    Stack now 0 39 722
    Entering state 1237
    Next token is token ';' (: )
    Reducing stack by rule 1497 (line 10265):
    -> $$ = nterm into_clause (: )
    Stack now 0 39 722 1237
    Entering state 1836
    Next token is token ';' (: )
    Reducing stack by rule 1576 (line 10606):
    -> $$ = nterm from_clause (: )
    Stack now 0 39 722 1237 1836
    Entering state 2646
    Next token is token ';' (: )
    Reducing stack by rule 1634 (line 11030):
    -> $$ = nterm where_clause (: )
    Stack now 0 39 722 1237 1836 2646
    Entering state 3321
    Next token is token ';' (: )
    Reducing stack by rule 1546 (line 10486):
    -> $$ = nterm group_clause (: )
    Stack now 0 39 722 1237 1836 2646 3321
    Entering state 3863
    Next token is token ';' (: )
    Reducing stack by rule 1559 (line 10538):
    -> $$ = nterm having_clause (: )
    Stack now 0 39 722 1237 1836 2646 3321 3863
    Entering state 4231
    Next token is token ';' (: )
    Reducing stack by rule 1906 (line 12725):
    -> $$ = nterm window_clause (: )
    Stack now 0 39 722 1237 1836 2646 3321 3863 4231
    Entering state 4477
    Reducing stack by rule 1481 (line 10134):
       $1 = token SELECT (: )
       $2 = nterm opt_all_clause (: )
       $3 = nterm opt_target_list (: )
       $4 = nterm into_clause (: )
       $5 = nterm from_clause (: )
       $6 = nterm where_clause (: )
       $7 = nterm group_clause (: )
       $8 = nterm having_clause (: )
       $9 = nterm window_clause (: )
    -> $$ = nterm simple_select (: )
    Stack now 0
    Entering state 183
    Next token is token ';' (: )
    Reducing stack by rule 1471 (line 10047):
       $1 = nterm simple_select (: )
    -> $$ = nterm select_no_parens (: )
    Stack now 0
    Entering state 181
    Reducing stack by rule 1467 (line 10026):
       $1 = nterm select_no_parens (: )
    -> $$ = nterm SelectStmt (: )
    Stack now 0
    Entering state 179
    Reducing stack by rule 119 (line 874):
       $1 = nterm SelectStmt (: )
    -> $$ = nterm stmt (: )
    Stack now 0
    Entering state 53
    Reducing stack by rule 3 (line 749):
       $1 = nterm stmt (: )
    -> $$ = nterm stmtmulti (: )
    Stack now 0
    Entering state 52
    Next token is token ';' (: )
    Shifting token ';' (: )
    Entering state 764
    Reading a token: Now at end of input.
    Reducing stack by rule 129 (line 885):
    -> $$ = nterm stmt (: )
    Stack now 0 52 764
    Entering state 1292
    Reducing stack by rule 2 (line 742):
       $1 = nterm stmtmulti (: )
       $2 = token ';' (: )
       $3 = nterm stmt (: )
    -> $$ = nterm stmtmulti (: )
    Stack now 0
    Entering state 52
    Now at end of input.
    Reducing stack by rule 1 (line 735):
       $1 = nterm stmtmulti (: )
    -> $$ = nterm stmtblock (: )
    Stack now 0
    Entering state 51
    Now at end of input.
    Shifting token $end (: )
    Entering state 763
    Stack now 0 51 763
    Cleanup: popping token $end (: )
    Cleanup: popping nterm stmtblock (: )
     ?column? 
    ----------
            1
    (1 row)

    有兴趣的读者请自行研究。

    参考

    解决移进/规约冲突

    bison使能debug
    更多postgresql内核开始相关文章请点击

    展开全文
  • 使用Calcite做Sql语法解析

    千次阅读 2020-02-05 21:24:01
    点击箭头处“蓝色字”,关注我们哦!!Flink SQL中使用Calcite作为sql语法解析、校验、优化工具,本篇是实操篇,介绍一下calcite做sql语法解析使用方式。sql经过ca...


    点击箭头处“蓝色字”,关注我们哦!!

    Flink SQL中使用Calcite作为sql语法解析、校验、优化工具,本篇是实操篇,介绍一下calcite做sql语法解析使用方式。

    sql经过calcite解析之后,得到一棵抽象语法树,也就是我们说的AST,这语法树是由不同的节点组成,节点称之为SqlNode,根据不同类型的dml、ddl得到不同的类型的SqlNode,例如select语句转换为SqlSelect,delete语句转换为SqlDelete,join语句转换为SqlJoin。

    使用方式:

    SqlParser.Config config = SqlParser.configBuilder()
             .setLex(Lex.MYSQL) //使用mysql 语法
             .build();
    //SqlParser 语法解析器         
    SqlParser sqlParser = SqlParser
           .create("select id,name,age FROM stu where age<20", config);
    SqlNode sqlNode = null;
    try {
       sqlNode = sqlParser.parseStmt();
    } catch (SqlParseException e) {
      throw new RuntimeException("", e);
    }
    

    这里解析了一个select的语句,那么得到的sqlNode就是一个SqlSelect。

    if(SqlKind.SELECT.equals(sqlNode.getKind())){
    
    
       SqlSelect sqlSelect = (SqlSelect) sqlNode;
       SqlNode from=sqlSelect.getFrom();
       SqlNode where=sqlSelect.getWhere();
       SqlNodeList selectList=sqlSelect.getSelectList();
       //标识符
       if(SqlKind.IDENTIFIER.equals(from.getKind())){
           System.out.println(from.toString());
        }
    
    
        if(SqlKind.LESS_THAN.equals(where.getKind())){
            SqlBasicCall sqlBasicCall=(SqlBasicCall)where;
            for(SqlNode sqlNode1: sqlBasicCall.operands){
              if(SqlKind.LITERAL.equals(sqlNode1.getKind())){
                  System.out.println(sqlNode1.toString());
                }
              }
           }
    
    
        selectList.getList().forEach(x->{
          if(SqlKind.IDENTIFIER.equals(x.getKind())){
              System.out.println(x.toString());
             }
         });
     }
    

    一个select语句包含from部分、where部分、select部分等,每一部分都表示一个SqlNode。SqlKind是一个枚举类型,包含了各种SqlNode类型:SqlSelect、SqlIdentifier、SqlLiteral等。SqlIdentifier表示标识符,例如表名称、字段名;SqlLiteral表示字面常量,一些具体的数字、字符。

    SqlBasicCall对比SqlSelect/SqlDelete而言,可以理解为表示的是一些基本的、简单的调用,例如聚合函数、比较函数等,接下来看一下其如何解析sum操作:

    select sum(amount) FROM orders //解析的sql
    //解析select部分
    selectList.getList().forEach(x->{
        if(SqlKind.SUM.equals(x.getKind())){
          SqlBasicCall sqlBasicCall=(SqlBasicCall)x;
          System.out.println(sqlBasicCall.operands[0]);
       }
     });
    

    其内部主要就是operands,也是SqlNode节点,但是都是一些基本的SqlNode,例如SqlIdentifierSqlLiteral

    SqlSelect/SqlDelete/SqlBasicCall 都称之为SqlCall,差别是SqlSelect是复杂的SqlCall,内部可以包含其他节点,而SqlBasicCall表示简单的SqlCall。另外两种SqlNode:SqlDataTypeSpec与SqlNodeList,SqlDataTypeSpec代表数据类型节点,例如CHAR/VARCHAR/DOUBLE, SqlNodeList表示包含多个同级别的SqlNode,在上面select中已经展示过,看下SqlDataTypeSpec使用实例:

    select cast(amount as CHAR) FROM orders//解析的sql
    //解析select部分
    selectList.getList().forEach(x->{
       if(SqlKind.CAST.equals(x.getKind())){
            SqlBasicCall sqlBasicCall=(SqlBasicCall)x;
            System.out.println(sqlBasicCall.operands[0]); //amount
            SqlDataTypeSpec charType=(SqlDataTypeSpec)sqlBasicCall.operands[1];
            System.out.println(charType.getTypeName()); //CHAR
      }
    });
    

    另外一种节点SqlOperator,可以代表函数、运算符、语法(select)结构,例如sum解析为SqlAggFunction、select解析为SqlSelectOperator,as 作为SqlAsOperator。SqlOperator是被嵌入在SqlNode中,作为其属性,通过SqlOperator的createCall方法可以创建对应的SqlNode,使用方式:

    SqlOperator operator = new SqlAsOperator();
    SqlParserPos sqlParserPos = new SqlParserPos(1, 1);
    SqlIdentifier name = new SqlIdentifier("orders", null, sqlParserPos);
    SqlIdentifier alias = new SqlIdentifier("o", null, sqlParserPos);
    SqlNode[] sqlNodes = new SqlNode[2];
    sqlNodes[0] = name;
    sqlNodes[1] = alias;
    SqlBasicCall sqlBasicCall = (SqlBasicCall)operator.createCall(sqlParserPos,sqlNodes);
    System.out.println(sqlBasicCall); //得到的就是 Order as o
    

    SqlParsePos表示对应解析的节点在sql位置,起止行与起止列。

    以上介绍了一下calcite解析sql的简单使用方式,我们可以使用Calcite来做血缘分析、flink sql维表关联等。

    —END—

    关注回复Flink

    获取更多系列

    原创不易,好看,就点个"在看"

    展开全文
  • Logos语法解析

    千次阅读 2016-05-31 13:38:04
    Logos语法解析 1.%hook 指定需要hook的class,必须以%end结尾。 // hook SpringBoard类里面的_menuButtonDown函数,先打印一句话,再之子那个函数原始的操作 %hook SpringBorad - (void)_menuButtonDown:(id)down { ...

    Logos语法解析

    • 1.%hook

      指定需要hook的class,必须以%end结尾。

      // hook SpringBoard类里面的_menuButtonDown函数,先打印一句话,再之子那个函数原始的操作
      %hook SpringBorad
      - (void)_menuButtonDown:(id)down
      {
          NSLog(@"111111");
         %orig; // 调用原始的_menuButtonDown函数
      }
      %end
    • 2.%log

      该指令在%hook内部使用,将函数的类名、参数等信息写入syslog,可以%log([(),…..])的格式追加其他打印信息。

      %hook SpringBorad
      - (void)_menuButtonDown:(id)down
      {
          %log((NSString *)@"iosre",(NSString *)@"Debug");
          %orig; // 调用原始的_menuButtonDown方法
      }
      %end
    • 3.%orig

      该指令在%hook内部使用,执行被hook的函数的原始代码;也可以用%orig更改原始函数的参数。

      %hook SpringBorad
      - (void)setCustomSubtitleText:(id)arg1 withColor:   (id)arg2
      {
          %orig(@"change arg2",arg2);// 将arg2的参数修 改为"change arg2"
      }
      %end
    • 4.%group

      该指令用于将%hook分组,便于代码管理及按条件初始化分组,必须以%end结尾。
      一个%group可以包含多个%hook,所有不属于某个自定义group的%hook会被隐式归类到%group_ungrouped中。

      /*
      在%group iOS7Hook中钩住iOS7Class的iOS7Method,在%group iOS8Class中钩住iOS8Method函数,然后在%group _ungroup中钩住SpringBoard类的powerDown函数.
      */
      %group iOS7Hook
      %hook iOS7Class
      - (id)ios7Method
      {
          id result = %orig;
          NSLog(@"这个方法只有iOS7适用");
          return result;
      }
      %end
      %end // iOS7Method
      
      %group iOS8Hook
      %hook iOS8Class
      - (id)ios8Method
      {
          id result = %orig;
          NSLog(@"这个方法只有iOS7适用");
          return result;
      }
      %end
      %end // iOS8Method
      
      %hook SpringBoard
      - (void)powerDown
      {
           %orig;
      }
      %end
    • 5.%init

      该指令用于初始化某个%group,必须在%hook或%ctor内调用;如果带参数,则初始化指定的group,如果不带参数,则初始化_ungrouped.
      注:
      切记,只有调用了%init,对应的%group才能起作用!

      
      #ifndef KCFCoreFoundationVersionNumber_iOS_8_0
      
      
      #define KCFCoreFoundationVersionNumber_iOS_8_0      1140.10
      
      
      #endif
      
      
      - (void)applicationDidFinishLaunching:(UIApplication    *)application
      {
          %orig;
      
          %init; // Equals to init(_ungrouped)
      
          if (KCFCoreFoundationVersionNumber >=   KCFCoreFoundationVersionNumber_iOS_7_0 &&   KCFCoreFoundationVersionNumber >    KCFCoreFoundationVersionNumber_iOS_8_0)
              %init(iOS7Hook);
          if (KCFCoreFoundationVersionNumber >= KCFCoreFoundationVersionNumber_iOS_8_0)
              %init(iOS8Hook);
      }
      %end
    • 6.%ctor

      tweak的constructor,完成初始化工作;如果不显示定义,Theos会自动生成一个%ctor,并在其中调用%init(_ungrouped)。%ctor一般可以用来初始化%group,以及进行MSHookFunction等操作,如下:

      
      #ifndef KCFCoreFoundationVersionNumber_iOS_8_0
      
      
      #define KCFCoreFoundationVersionNumber_iOS_8_0      1140.10
      
      
      #endif
      
      
      %ctor
      {
          %init;
      
          if (KCFCoreFoundationVersionNumber >= KCFCoreFoundationVersionNumber_iOS_7_0 && KCFCoreFoundationVersionNumber > KCFCoreFoundationVersionNumber_iOS_8_0)
          %init(iOS7Hook);
       if (KCFCoreFoundationVersionNumber >= KCFCoreFoundationVersionNumber_iOS_8_0)
          %init(iOS8Hook);
      MSHookFunction((void *)&AudioServicesPlaySystemSound,(void *)&replaced_AudioServerPlaySystemSound,(void **)&orginal_AudioServicesPlaySystemSound);
      }
    • 7.%new

      在%hook内部使用,给一个现有class添加新函数,功能与class_addMethod相同.

      注:
      Objective-C的category与class_addMethod的区别:
      前者是静态的而后者是动态的。使用%new添加,而不需要向.h文件中添加函数声明,如果使用category,可能与遇到这样那样的错误.

      %hook SpringBoard
      %new
      - (void)addNewMethod
      {
          NSLog(@"动态添加一个方法到SpringBoard");
      }
      %end
    • 8.%c

      该指令的作用等同于objc_getClass或NSClassFromString,即动态获取一个类的定义,在%hook或%ctor内使用 。

    logos语法链接
    github链接
    展开全文
  • Java实现 LeetCode 736 Lisp 语法解析(递归)

    千次阅读 多人点赞 2020-04-11 10:46:27
    736. Lisp 语法解析 给定一个类似 Lisp 语句的表达式 expression,求出其计算结果。 表达式语法如下所示: 表达式可以为整数,let 语法,add 语法,mult 语法,或赋值的变量。表达式的结果总是一个整数。 (整数可以是...
  • hive源码解析之语法解析

    千次阅读 2013-09-26 09:04:33
    Hive语法解析器是根据 词法分析 > 生成的语法树为基础,进行语法解析。根据语法token的情况实现了五个具体的语法解析器。 + 在你生成语法器的时候, SemanticAnalyzerFactory分别针对不同的情况生成对应的某个...
  • JavaScript的语法解析与抽象语法树

    千次阅读 2019-05-16 11:45:26
    抽象语法树(Abstract Syntax Tree)也称为AST语法树,指的是源代码语法所...JavaScript语法解析 什么是语法树 可以通过一个简单的例子来看语法树具体长什么样子。有如下代码: var AST = "is Tree"; 我们...
  • 文章目录目录前文列表语法解析mpc 语法解析库 前文列表 《用 C 语言开发一门编程语言 — 交互式 Shell》 《用 C 语言开发一门编程语言 — 跨平台》 语法解析 当我们学习一门自然语言的时候,我们往往从语法开始。当...
  • 自顶向下的语法解析中,可以通过属性传递的方式去实现代码生成,属性传递的方式主要是在父函数调用子函数时,将属性作为参数进行传递。但是,就如上节我们看到的,LALR语法解析过程是通过栈而不是函数调用的方式来...
  • 自底向上的语法解析,依赖于一种语法格式,我们可称之为LALR(1),跟LL(1)语法类似,LALR语法有以下特点,第二个L表示在解析语法时,从左向右读取语法文本。R表示right most, 也就是在做语法解析时,我们从推导表达式最...
  • 后来在仔细研究下,发现是因为vim受到.swp交换文件的影响导致 语法解析失效. 我之前的解决方法是 复制一个文件,然后把原来的删了,后来发现有更简单的做法,直接在vim底层命令行模式注入下面命令,重新解析该文件...
  • 写C语言其实就是写一个个函数,因此对函数实现的语法解析是C语言编译器语法解析中,最复杂的,我们以前解析的各种结构体,枚举类型,变量定义等,都会间套到函数实现中,于此函数实现的解析要能够对所有C语言的数据...
  • 同样Jasper对JSP语法解析后也会生成一棵树,这棵树各个节点包含了不同的信息,但对于JSP来说解析后的语法树比较简单,只有一个父节点和n个子节点。例如node1是表示形如字符串 -->的注释节点,节点里面包含了一个表示...
  • 转JavaScript的语法解析与抽象语法树

    千次阅读 2015-07-10 11:57:24
    JavaScript的语法解析与抽象语法树 Jul 10, 2015 抽象语法树(Abstract Syntax Tree)也称为AST语法树,指的是源代码语法所对应的树状结构。也就是说,对于一种具体编程语言下的源代码,通过构建语法树的形式将源...
  • 语法解析(rs.next())

    千次阅读 2017-01-12 12:14:25
    语法解析(rs.next()) ResultSet.next()方法将指针从当前位置下移一行。ResultSet指针最初位于第一行之前;第一次调用next方法使第一行成为当前行;第二次调用使第二行成为当前行,依此类推。如果如果新的...
  • 举个例子看看,语法解析的过程。句子:“我看到刘德华唱歌”。在计算机里,怎么用程序解析它呢。从语法上看,句子的组成是由主语,动词,和谓语从句组成,主语是“我”,动词是“看见”, 谓语从句是”刘德华唱歌...
  • oracle中的SAVEPOINT和ROLLBACK TO SAVEPOINT语法解析
  • 使用Python语言编写简单的HTML5语法解析器 摘要:通过使用Python语言编写一个简单的HTML5语法解析器作为例子,探讨了在设计手写的递归下降语法... 如何编写一个语法解析器(Parser)呢?在C/C++语言领域,我们有le
  • DSL语法解析器生成器:dropincc.java

    千次阅读 2018-07-18 16:01:39
    一个简单、好用的语法解析器生成器; 专为java语言环境下,实施DSL方案而设计; 特点:使用纯java语法(Fluent Interface)制定用户的词法、语法规则;jdk1.6 compiler API动态编译为字节码;自动管理字节码、用户...
  • 从这节开始,我们看看解析器如何对逻辑控制语句,例如if else, for, while , do...while, goto 等语句进行相应的语法解析
  • boost spirit ——编译器,语法解析

    千次阅读 2015-08-20 14:13:23
    灵活,伸缩性好,可以用来搭建小的语法解析器也可以用来开发大型编译器等等。 boost::spirit 目前主要有三部分: Boost.Qi (for writing parsers), Boost.Karma (for generators) and Boost.Lex (for le
  • FreeMarker语法解析

    千次阅读 2016-06-27 09:47:06
    输出Map对象时,可以使用点语法或中括号语法,如下面的几种写法的效果是一样的: book.author.name book.author["name"] book["author"].name book["author"]["name"] 使用点语法时,变量...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 456,741
精华内容 182,696
关键字:

语法解析