SQLite3源程序分析之分析器的生成
1、概述
Lemon是一个LALR(1)文法分析器生成工具,与bison和yacc类似,是一个可以独立于SQLite使用的开源的分析器生成工具。而且它使用与yacc(bison)不同的语法规则,可以减少编程时出现错误的机会。Lemon比yacc和bison更精致、更快,而且是可重入的,也是线程安全的——这对于支持多线程的程序是非常重要的。
Lemon的主要功能就是根据上下文无关文法(CFG),生成支持该文法的分析器。程序的输入文件有两个:
(1) 语法规则文件;
(2) 分析器模板文件。
一般来说,语法规则是由程序员定义的;Lemon有一个适用于大多数应用程序的默认分析器模板。
根据命令行选项,Lemon会生成以下一些文件:
(1) 分析器的C代码;
(2) 一个为每个终结符定义一个整型ID的头文件;
(3) 一个描述分析器状态的文件。
语法规范文件以”.y”为后缀,如果语法规范文件为”gram.y”,则可以使用该命令生成分析器:lemon gram.y。
1.1、分析器接口
Lemon不会生成一个完整的、可以运行的程序。它仅仅生成一些实现分析器的子例程,然后由用户程序在适当的地方调用这些子例程,从而生成一个完整的分析器。
1.1.1、ParseAlloc
程序在使用Lemon生成的分析器之前,必须创建一个分析器。如下:
void *pParser = ParseAlloc( malloc );
ParseAlloc为分析器分配空间,然后初始化它,返回一个指向分析器的指针。SQLite对应的函数为:
void *sqlite3ParserAlloc(void *(*mallocProc)(size_t))
函数的参数为一个函数指针,并在函数内调用该指针指向的函数。如:
void *sqlite3ParserAlloc(void *(*mallocProc)(size_t)){ yyParser *pParser; pParser = (yyParser*)(*mallocProc)( (size_t)sizeof(yyParser) ); if( pParser ){ pParser->yyidx = -; #ifdef YYTRACKMAXSTACKDEPTH pParser->yyidxMax = ; #endif #if YYSTACKDEPTH<=0 pParser->yystack = NULL; pParser->yystksz = ; yyGrowStack(pParser); #endif } return pParser; }
1.1.2、ParseFree
当程序不再使用分析器时,应该回收为其分配的内存。如下:
ParseFree(pParser, free);
SQLite对应的函数如下:
void sqlite3ParserFree( void *p, /* The parser to be deleted */ void (*freeProc)(void*) /* Function used to reclaim memory */ ){ yyParser *pParser = (yyParser*)p; ) return; ) yy_pop_parser_stack(pParser); #if YYSTACKDEPTH<=0 free(pParser->yystack); #endif (*freeProc)((void*)pParser); }
1.1.3、Parse
Parse是Lemon生成的分析器的核心例程。在分析器调用ParseAlloc后,分词器就可以将切分的词传递给Parse,进行语法分析。SQLite对应的函数如下:
void sqlite3Parser( void *yyp, /* The parser */ int yymajor, /* The major token code number */ sqlite3ParserTOKENTYPE yyminor /* The value for the token */ sqlite3ParserARG_PDECL /* Optional %extra_argument parameter */ )
该函数由sqlite3RunParser调用:
int sqlite3RunParser(Parse *pParse, const char *zSql, char **pzErrMsg)
sqlite3RunParser位于token.c文件中,它是进行SQL语句分析的入口,它调用sqlite3GetToken对SQL语句zSql进行分词,然后调用sqlite3Parser进行语法分析。而sqlite3Parser在语法规则发生规约时调用相应的opcode生成子例程,生成opcode。
1.2、与yacc和bison的不同之处
Lemon与yacc和bison有一些不同的地方:
(1)在yacc和bison中,是分析器调用分词器。在Lemon,分词器调用分析器;
(2)Lemon不会使用全局变量。而yacc与bison在分析器与分词器之间使用全局变量;
(3)Lemon允许多个分析器同时运行,因为它是可重入的。而yacc与bison却不行。
2、输入文件语法
Lemon的语法规则文件(grammar specification file)主要用于为分析器定义语法规则。此外,输入文件还包括其它一些有用的信息,使用的Lemon的大部分工作就是写语法文件。
2.1、终结符与非终结符(Terminals and Nonterminals)
终结符通常是大写的字符串(数字或字母)。非终结符是小写的字符串(数字或字母)。
2.2、语法规则(Grammar Rules)
每个语法规则由三部分构成:以非终结符开始,随后紧接着为“::=”,然后是终结符(或非终结)列表,规则以英文语句“.”结尾。如下:
expr ::= expr PLUS expr. expr ::= expr TIMES expr. expr ::= LPAREN expr RPAREN. expr ::= VALUE.
上例中,有一个非终结符“expr”,和5个终结符:“PLUS”、“TIMES”、“LPAREN”、“RPAREN”、和“VALUE”。
与yacc和bison一样,Lemon允许为规则添加C代码块,并由分析器进行规则规约时调用。如下:
expr ::= expr PLUS expr. { printf("Doing an addition...\n"); }
为了使规则有用,语法动作(grammar actions)必须与相应的语法规则联系起来。在yacc和bison中,动作(action)中的“$$”代表左值,而“$1”、“$2”等则代表“::=”右边的位置相应为1、2等的终结符或非终结符的值。这是非常有用的,但是,却非常容易出错。例如:
expr -> expr PLUS expr { $$ = $ + $; };
而Lemon通过为规则中的每个符号指定一个额外的符号名字(symbolic)达到相同的目的,然后在动作中使用这些符号名字。如下:
expr(A) ::= expr(B) PLUS expr(C). { A = B+C; }
2.3、优先级规则(Precedence Rules)
Lemon采用与yacc和bison相同的方法处理歧义性问题。移进—规约冲突,则选择移进;规约——规约冲突,则选择先出现的规则。
同样,Lemon也允许通过优先级规则来解决冲突。如下:
%left AND. %left OR. %nonassoc EQ NE GT GE LT LE. %left PLUS MINUS. %left TIMES DIVIDE MOD. %right EXP NOT.
2.4、特殊指示符(Special Directives)
Lemon支持如下一些特殊指示符:
%code %default_destructor %default_type %destructor %extra_argument %include %left %name %nonassoc %parse_accept %parse_failure %right %stack_overflow %stack_size %start_symbol %syntax_error %token_destructor %token_prefix %token_type %type
%code
%code表示将一段C/C++代码添加到输出文件的尾部。它主要用于包含一些动作例程(action routines)或者词法器的部分代码。
%default_type
如果没有在%type中为非终结符指定数据类型,则default_type指定非终结符的数据类型。
%destructor
为非终结符指定一个资源释放器(destructor)(%token_destructor为终结符指定资源释放器)。当非终结符从分析栈中弹出时,释放器就会被调用,以释放其占用的资源。包括如下情况:
(1)一个规则发生规约,而非终结符的右边却没有C代码;
(2)在错误处理中,出栈操作;
(3)ParseFree函数返回。
释放器可以做任何操作,但是它主要用来释放非终结符占用的内存或其它资源。例:
%type nt {void*} %destructor nt { free($$); } nt(A) ::= ID NUM. { A = ); }
在例子中,“nt”的数据类型为“void *”。当“nt”的规则发生规约时,为非终结符通过malloc分配空间。之后,当非终结符从栈中弹出时,释放器会被调用,以释放malloc申请的内存。这可以避免内存泄漏。
注意,除非非终结符会在C动作代码中使用,否则,当非终结符从栈中弹出时,就会调用释放器释放资源。如果C代码使用非终结符,则由C代码保证资源的释放。
%token_prefix
Lemon的生成文件会为每个终结符定义一个整数值。如下:
#define AND 1 #define MINUS 2 #define OR 3 #define PLUS 4
如果愿意,可以通过该指示符为#define的预处理符号加一个前缀。例如,可以在规则文件加上如下:
%token_prefix TOKEN_
则生成的文件的输出如下:
#define TOKEN_AND 1 #define TOKEN_MINUS 2 #define TOKEN_OR 3 #define TOKEN_PLUS 4
%include
由该指示符指定的C代码会包含到生成的分析的顶部。你可以包含任意代码,Lemon会完全拷贝过去。
%extra_argument
指示Parse函数中第四个参数。 Lemon本身不会做任何处理,但是相应的C代码可以使用该参数。
%parse_accept
分析器进行语法分析成功时,执行的C代码。如:
%parse_accept { printf("parsing complete!\n"); }
%stack_overflow
当分析器执行发生内部栈溢出时,会执行相应的动作。通常,可以输出错误消息,分析器不能继续执行,而必须重置。例如:
%stack_overflow { fprintf(stderr,"Giving up. Parser stack overflow\n"); }
%name
默认情况下,Lemon生成的函数都以“Parse”开始,可以通过该指示符修改。例如:
%name Abcde
这会导致Lemon生成的函数的名字如下:
AbcdeAlloc(), AbcdeFree(), AbcdeTrace(), and Abcde().
%token_type与%type
这些指示符用于为分析器的栈中的终结符或非终结指定数据类型。所有终结符都必须是相同的类型,而与应该与Lemon生成的输出文件中的Parse()的第3个参数的类型一致。通常,可以将一个结构指针赋给终结符,如下:
%token_type {Token*}
如果终结符的数据类型没有指定,默认为“int”。通常,每个非终结符都有各自的数据类型。例如,通常非终结符为指向的分析树的根结点的数据类型指针,该根结点包含非终结符的所有信息。例如:
%type expr {Expr*}
3、SQLite语法规则分析
下面以SELECT语句简要的概述一下SQLite的语法规则。
3.1、SELECT语法
SELECT语法是SQL语句中最复杂的部分之一。而其它SQL语句,比如CREATE(DROP) TABLE、CREATE(DROP)INDEX、INSERT、DELETE、UPDATE相对来说比较简单。
3.1.1、select-stmt
select的语法全图:
3.1.2、select-core
相应的语法规则:
cmd ::= select(X). { SelectDest dest = {SRT_Output, , , , }; sqlite3Select(pParse, X, &dest); sqlite3SelectDelete(pParse->db, X); } %type select {Select*} //select语句对应的结构体 %destructor select {sqlite3SelectDelete(pParse->db, $$);} %type oneselect {Select*} %destructor oneselect {sqlite3SelectDelete(pParse->db, $$);} select(A) ::= oneselect(X). {A = X;} //... //简单SQL语句,可以分成以下几部分:输出列、from子句、where子句、group子句、having子句 oneselect(A) ::= SELECT distinct(D) selcollist(W) from(X) where_opt(Y) groupby_opt(P) having_opt(Q) orderby_opt(Z) limit_opt(L). { A = sqlite3SelectNew(pParse,W,X,Y,P,Q,Z,D,L.pLimit,L.pOffset); }
distinct
%type distinct {int} distinct(A) ::= DISTINCT. {A = ;} distinct(A) ::= ALL. {A = ;} distinct(A) ::= . {A = ;}
selcollist(输出结果列)
%type selcollist {ExprList*} //输出列对应的结构体 %destructor selcollist {sqlite3ExprListDelete(pParse->db, $$);} %type sclp {ExprList*} %destructor sclp {sqlite3ExprListDelete(pParse->db, $$);} sclp(A) ::= selcollist(X) COMMA. {A = X;} sclp(A) ::= . {A = ;} selcollist(A) ::= sclp(P) expr(X) as(Y). { A = sqlite3ExprListAppend(pParse, P, X.pExpr); ) sqlite3ExprListSetName(pParse, A, &Y, ); sqlite3ExprListSetSpan(pParse,A,&X); } selcollist(A) ::= sclp(P) STAR. { Expr *p = sqlite3Expr(pParse->db, TK_ALL, ); A = sqlite3ExprListAppend(pParse, P, p); } selcollist(A) ::= sclp(P) nm(X) DOT STAR(Y). { Expr *pRight = sqlite3PExpr(pParse, TK_ALL, , , &Y); Expr *pLeft = sqlite3PExpr(pParse, TK_ID, , , &X); Expr *pDot = sqlite3PExpr(pParse, TK_DOT, pLeft, pRight, ); A = sqlite3ExprListAppend(pParse,P, pDot); } // An option "AS <id>" phrase that can follow one of the expressions that // define the result set, or one of the tables in the FROM clause. // AS语句 %type as {Token} as(X) ::= AS nm(Y). {X = Y;} as(X) ::= ids(Y). {X = Y;} ;}
from
from子句分以下几部分:
join-source:
single-source:
join-op:
join-constraint:
语法规则:
%type seltablist {SrcList*} //from子语对应的数据结构 %destructor seltablist {sqlite3SrcListDelete(pParse->db, $$);} %type stl_prefix {SrcList*} %destructor stl_prefix {sqlite3SrcListDelete(pParse->db, $$);} %type from {SrcList*} %destructor from {sqlite3SrcListDelete(pParse->db, $$);} // A complete FROM clause. FROM子句 // from(A) ::= . {A = sqlite3DbMallocZero(pParse->db, sizeof(*A));} from(A) ::= FROM seltablist(X). { A = X; sqlite3SrcListShiftJoinType(A); } // "seltablist" is a "Select Table List" - the content of the FROM clause // in a SELECT statement. "stl_prefix" is a prefix of this list. // stl_prefix(A) ::= seltablist(X) joinop(Y). { A = X; ) ) A->a[A->nSrc-].jointype = (u8)Y; } stl_prefix(A) ::= . {A = ;} //from后面的语句 seltablist(A) ::= stl_prefix(X) nm(Y) dbnm(D) as(Z) indexed_opt(I) on_opt(N) using_opt(U). { A = sqlite3SrcListAppendFromTerm(pParse,X,&Y,&D,&Z,,N,U); sqlite3SrcListIndexedBy(pParse, A, &I); } //数据库名 %type dbnm {Token} dbnm(A) ::= . {A.z=; A.n=;} dbnm(A) ::= DOT nm(X). {A = X;} //全名 %type fullname {SrcList*} %destructor fullname {sqlite3SrcListDelete(pParse->db, $$);} fullname(A) ::= nm(X) dbnm(Y). {A = sqlite3SrcListAppend(pParse->db,,&X,&Y);} //join语句 %type joinop {int} %type joinop2 {int} joinop(X) ::= COMMA|JOIN. { X = JT_INNER; } joinop(X) ::= JOIN_KW(A) JOIN. { X = sqlite3JoinType(pParse,&A,,); } joinop(X) ::= JOIN_KW(A) nm(B) JOIN. { X = sqlite3JoinType(pParse,&A,&B,); } joinop(X) ::= JOIN_KW(A) nm(B) nm(C) JOIN. { X = sqlite3JoinType(pParse,&A,&B,&C); } //on语句 %type on_opt {Expr*} %destructor on_opt {sqlite3ExprDelete(pParse->db, $$);} on_opt(N) ::= ON expr(E). {N = E.pExpr;} on_opt(N) ::= . {N = ;} // Note that this block abuses the Token type just a little. If there is // no "INDEXED BY" clause, the returned token is empty (z==0 && n==0). If // there is an INDEXED BY clause, then the token is populated as per normal, // with z pointing to the token data and n containing the number of bytes // in the token. // // If there is a "NOT INDEXED" clause, then (z==0 && n==1), which is // normally illegal. The sqlite3SrcListIndexedBy() function // recognizes and interprets this as a special case. //index by语句(似乎不属于SQL92标准) %type indexed_opt {Token} indexed_opt(A) ::= . {A.z=; A.n=;} indexed_opt(A) ::= INDEXED BY nm(X). {A = X;} indexed_opt(A) ::= NOT INDEXED. {A.z=; A.n=;} //using语句 %type using_opt {IdList*} %destructor using_opt {sqlite3IdListDelete(pParse->db, $$);} using_opt(U) ::= USING LP inscollist(L) RP. {U = L;} using_opt(U) ::= . {U = ;}
order by
%type orderby_opt {ExprList*} %destructor orderby_opt {sqlite3ExprListDelete(pParse->db, $$);} %type sortlist {ExprList*} %destructor sortlist {sqlite3ExprListDelete(pParse->db, $$);} %type sortitem {Expr*} %destructor sortitem {sqlite3ExprDelete(pParse->db, $$);} //order by语句 orderby_opt(A) ::= . {A = ;} orderby_opt(A) ::= ORDER BY sortlist(X). {A = X;} sortlist(A) ::= sortlist(X) COMMA sortitem(Y) sortorder(Z). { A = sqlite3ExprListAppend(pParse,X,Y); ].sortOrder = (u8)Z; } sortlist(A) ::= sortitem(Y) sortorder(Z). { A = sqlite3ExprListAppend(pParse,,Y); ].sortOrder = (u8)Z; } sortitem(A) ::= expr(X). {A = X.pExpr;} //顺序 %type sortorder {int} sortorder(A) ::= ASC. {A = SQLITE_SO_ASC;} sortorder(A) ::= DESC. {A = SQLITE_SO_DESC;} sortorder(A) ::= . {A = SQLITE_SO_ASC;}
group by
%type groupby_opt {ExprList*} %destructor groupby_opt {sqlite3ExprListDelete(pParse->db, $$);} groupby_opt(A) ::= . {A = ;} groupby_opt(A) ::= GROUP BY nexprlist(X). {A = X;}
having
%type having_opt {Expr*} %destructor having_opt {sqlite3ExprDelete(pParse->db, $$);} having_opt(A) ::= . {A = ;} having_opt(A) ::= HAVING expr(X). {A = X.pExpr;}
limit
%type limit_opt {struct LimitVal} // The destructor for limit_opt will never fire in the current grammar. // The limit_opt non-terminal only occurs at the end of a single production // rule for SELECT statements. As soon as the rule that create the // limit_opt non-terminal reduces, the SELECT statement rule will also // reduce. So there is never a limit_opt non-terminal on the stack // except as a transient. So there is never anything to destroy. // //%destructor limit_opt { // sqlite3ExprDelete(pParse->db, $$.pLimit); // sqlite3ExprDelete(pParse->db, $$.pOffset); //} limit_opt(A) ::= . {A.pLimit = ; A.pOffset = ;} limit_opt(A) ::= LIMIT expr(X). {A.pLimit = X.pExpr; A.pOffset = ;} limit_opt(A) ::= LIMIT expr(X) OFFSET expr(Y). {A.pLimit = X.pExpr; A.pOffset = Y.pExpr;} limit_opt(A) ::= LIMIT expr(X) COMMA expr(Y). {A.pOffset = X.pExpr; A.pLimit = Y.pExpr;}
SQLite3源程序分析之分析器的生成的更多相关文章
- SQLite3源程序分析之查询处理及优化
前言 查询处理及优化是关系数据库得以流行的根本原因,也是关系数据库系统最核心的技术之一.SQLite的查询处理模块很精致,而且很容易移植到不支持SQL的存储引擎(Berkeley DB最新的版本已经将 ...
- SQLite3源程序分析之虚拟机
前言 最早的虚拟机可追溯到IBM的VM/370,到上个世纪90年代,在计算机程序设计语言领域又出现一件革命性的事情——Java语言的出现,它与c++最大的不同在于它必须在Java虚拟机上运行.Java ...
- LR(1)语法分析器生成器(生成Action表和Goto表)java实现(一)
序言 : 在看过<自己实现编译器链接器>源码之后,最近在看<编译器设计>,但感觉伪代码还是有点太浮空.没有掌握的感觉,也因为内网几乎没有LR(1)语法分析器生成器的内容,于是我 ...
- SQLite源程序分析之sqlite3.c
/****************************************************************************** ** This file is an a ...
- LR(1)语法分析器生成器(生成Action表和Goto表)java实现(二)
本来这次想好好写一下博客的...结果耐心有限,又想着烂尾总比断更好些.于是还是把后续代码贴上.不过后续代码是继续贴在BNF容器里面的...可能会显得有些臃肿.但目前管不了那么多了.先贴上来吧hhh.说 ...
- linux性能监控分析及通过nmon_analyse生成分析报表
nmon是一款分析 AIX 和 Linux 性能的免费工具 nmon 工具还可以将相同的数据捕获到一个文本文件,便于以后对报告进行分析和绘制图形.输出文件采用电子表格的格式 (.csv). 性能介绍 ...
- 【转载】linux性能监控分析及通过nmon_analyse生成分析报表
转载地址:http://www.cnblogs.com/Lam7/p/6604832.html nmon是一款分析 AIX 和 Linux 性能的免费工具 nmon 工具还可以将相同的数据捕获到一个文 ...
- ES中的分析和分析器
在ES存储的文档,进行存储时,会对文档的内容进行分析和分词 分析的过程: 首先,将一块文本分成适合于倒排索引的独立的 词条 , 之后,将这些词条统一化为标准格式以提高它们的“可搜索性”,或者 reca ...
- python 一键登录微信分析好友性别 地址 生成结果
# -*- coding:utf- -*- """ author:Mr Yang data:// """ import itchat imp ...
随机推荐
- 项目积累(三)CSS
公司不是专门做网站的,偶尔会接到客户让修改前端,有时候和让头疼,自己浏览器兼容问题处理不好. 慢慢积累吧. 先贴出来一些前端代码吧,如下: <div class="test" ...
- Asp.Net Core 项目实战之权限管理系统(7) 组织机构、角色、用户权限
0 Asp.Net Core 项目实战之权限管理系统(0) 无中生有 1 Asp.Net Core 项目实战之权限管理系统(1) 使用AdminLTE搭建前端 2 Asp.Net Core 项目实战之 ...
- dicom网络通讯入门(1)
看标准 越看越糊,根本原因:dicom抽象得非常严重,是“专家”弄的.没办法. 又是什么服务类 又是什么sop,相信你把dicom标准看到头大 都不知如何下手. 不就是 socket么 这有何难. 首 ...
- 导出BOM表
1.Report->Bill of Materials for Project 将Value拖上左上角的Grouped Columns 2.在Excel表中全选器件,右键设置"设置单元 ...
- JSON转化为JAVABEAN集合
String str = "[{'id':'392','type':'jpg'},{'id':'393','type':'jpg'},{'id':'377','type':'jpg'}]&q ...
- 高性能 TCP/UDP/HTTP 通信框架 HP-Socket v4.1.2
HP-Socket 是一套通用的高性能 TCP/UDP/HTTP 通信框架,包含服务端组件.客户端组件和 Agent 组件,广泛适用于各种不同应用场景的 TCP/UDP/HTTP 通信系统,提供 C/ ...
- jq制作圣诞主题页面
今天制作的是有飘雪效果的圣诞主题页面,个人灰常喜欢. 首先还是放张效果图: 当看到这这页面的时候我们要注意四点: 1.图片的轮播 2.文字的滚动效果 3.音乐播放 4.飘雪效果 那我们就一点一点来完成 ...
- 前端之HTML知识点整理
一.html概述 htyper text markup language 即超文本标记语言 超文本: 就是指页面内可以包含图片.链接,甚至音乐.程序等非文字元素. 标记语言: 标记(标签)构成的语言 ...
- npm更新到最新版本的方法
打开命令行工具 npm -v 查看是否是最新版本 如果不是 运行npm i npm g 升级 打开C:\Users\用户名用户目录找到node_modules 文件夹下的npm文件夹,复制一份 打开n ...
- Unable to simultaneously satisfy constraints.
在进行版本的迭代更新时,新功能需求需要对主页面的UI进行重新的布局,但是,报了错误,出了好多约束方面的问题: Unable to simultaneously satisfy constraints. ...