# 在src目录下执行
# 运行命令
make
# 清理命令
make clean
.
├── dataset
│ ├── art
│ │ ├── Art.input
│ │ └── Art.output
│ ├── Art.aql
│ ├── dataset_readme.txt
│ ├── perloc
│ │ ├── PerLoc.input
│ │ ├── PerLoc.output
│ │ └── PerLoc.standard.output
│ ├── PerLoc.aql
│ ├── revenue
│ │ ├── Revenue.input
│ │ ├── Revenue.output
│ │ └── Revenue.standard.output
│ └── Revenue.aql
├── README.md
└── src
├── code_token.cpp
├── code_token.h
├── common.cpp
├── common.h
├── doc_token.cpp
├── doc_token.h
├── lexer.cpp
├── lexer.h
├── main.cpp
├── makefile
├── parser.cpp
├── parser.h
├── regex.cpp
├── tokenizer.cpp
└── tokenizer.h
common.h
和common.cpp
这里我定义了一个Common
的域,该域包含了其他代码中常用到的一些通用函数:
error
: 错误输出,并终止string_format
: 字符串格式化is_char
: 判断字符串是否全部为字符(a-zA-Z0-9)is_num
: 判断字符串是否全部为数字is_symbol
: 判断字符串是否全部为特殊符号string_to_int
: 将string
类型转换为int
类型get_file_name
: 从文件路径中获取文件名
code_token.h
和code_token.cpp
该类定义了AQL代码的token
结构:
value
:CodeToken
的值type
:CodeToken
的类型line_number
:CodeToken
所在的行数
另外方便测试也有打印的print
函数
lexer.h
和lexer.cpp
该类定义了AQL的词法分析器,主要功能就是从文件中读取AQL源代码,并将其转换成一个个的CodeToken
,存放在一个vector
,方便以后的语法分析:
get_code_tokens
: 把源代码文件内容转换为CodeToken
,并用一个vector
数组存储
doc_token.h
和doc_token.cpp
该类定义了文章的token
的结构:
value
:DocToken
的值start_pos
:DocToken
的起始位置end_pos
:DocToken
的终止位置
同时也有方便打印的print
函数和to_string
函数
tokenizer.h
和tokenizer.cpp
该类定义了文章的token
分析器,主要功能为将文章划分为一个个的token
,存放在一个vector
,方便以后的语法分析:
get_content
: 得到一个包含文章内容的字符串get_doc_tokens
: 把文章内容转换为DocToken
,并用一个vector
数组存储
parser.h
和parser.cpp
该类定义语法分析器,也是这个编译器的核心,之前的所有内容也都是为此做准备,主要内容就是解释词法分析出来的CodeToken
,构建语法树,解析其内容,根据内容对文章进行处理,这一部分的思路其实也不复杂,主要还是跟着生成式走,通过函数递归调用从顶层函数调用至底层,最后达到递归下降的效果,处理生成式的函数及其对应的生成式如下:
aql_stmt
: aql_stmt → create_stmt ; | output_stmt ;create_stmt
: create_stmt → create view ID as view_stmtoutput_stmt
: output_stmt → output view ID as IDview_stmt
: view_stmt → select_stmt | extract_stmtselect_stmt
: select_stmt → select select_list from from_listselect_list
: select_list → select_item | select_list , select_itemselect_item
: select_item → ID . ID as IDextract_stmt
: extract_stmt → extract extract_spec from from_listfrom_list
: from_list → ID ID | from_list , ID IDextract_spec
: extract_spec → regex_spec | pattern_specregex_spec
: regex_spec → regex REG on ID.ID name_specpattern_spec
: pattern_spec → pattern pattern_expr name_specpattern_expr
: pattern_expr → pattern_pkg | pattern_expr pattern_pkgpattern_pkg
: pattern_pkg → atom | atom_by_range | pattern_groupname_spec
: name_spec → as ID | return group_specatom
: atom → < ID.ID > | < Token > | REGatom_by_range
: atom_by_range → atom**{ NUM , NUM }**
上面的生成式可能和给出的略有不同,我是为了便于处理做了一定的修改,但实际效果应该是差不多的。另外最后还有几个辅助函数,没有在生成式中表现出来,但是在其中也是起到了重要作用:
link
: 将两个DocToken
数组进行拼接,在pattern
的处理中使用is_connect
: 判断两个DocToken
是否相邻find_all_to_token
: 通过正则表达式找到所有的结果,并以动态数组的格式返回void view_output
: 格式化打印结果