西电编译原理实验-函数绘图语言解释器

Dustball’s Interpreter

西电函数绘图语言解释器

一、实验目的

1.近期学习的cmake和makefile管理项目实际操作一下

2.应用模块化编程范式,面向对象编程范式,追求高内聚低耦合

3.熟悉c++STL,c++11标准,文件流fstream的使用

4.学会递归下降算法

二、实验环境

win11+wsl(kali linux)

项目备注
oswin11 22H2 + wsl(5.10.102.1-microsoft-standard-WSL2 kali linux)
cmakecmake 3.24.3
languageC++11,python3.8(绘图语言)
hardware处理器 11th Gen Intel® Core™ i7-11800H @ 2.30GHz 2.30 GHz
机带 RAM 32.0 GB (31.8 GB 可用)

三、实验内容

项目地址:DeutschBall/Interpreter

构建

linux平台

在linux上要有gcc,g++,makefile,cmake等工具,没有直接apt install

创建build路径

mkdir build && cd build

以下所有工作均在build目录中完成

外部构建

cmake ..

编译链接

make

解释

./DustComp ../test.txt

python运行

python ./Drawer.py

这一步在wsl终端上不行,需要图形界面支持,可以用kex登录wsl桌面或者直接本机上使用python绘图

windows平台
mkdir build && cd build
cmake ..

此后在build目录下生成sln解决方案文件,使用visual studio打开之即可

项目结构

┌──(root㉿Executor)-[/mnt/c/Users/86135/Desktop/compilier]
└─# tree -r
.
├── test.txt
├── semantic
│   ├── semantic.hpp
│   ├── semantic.cpp
│   └── CMakeLists.txt
├── README.md
├── parser
│   ├── parser.hpp
│   ├── parser.cpp
│   └── CMakeLists.txt
├── message
│   ├── message.hpp
│   ├── message.cpp
│   └── CMakeLists.txt
├── main.cpp
├── lexer
│   ├── main.cpp
│   ├── lexer.hpp
│   ├── lexer.cpp
│   └── CMakeLists.txt
└── CMakeLists.txt

4 directories, 17 files
文件/文件夹子文件作用
/CMakeLists.txt顶层cmake规则,构建项目使用
/main.cpp程序入口点
/README.cpp使用方法
/build/外部构建目录
/test.txt输入文件,测试用例
/lexer/词法分析器目录
CMakeLists.txt词法分析模块的cmake规则
lexer.hpp词法分析器头,包括Token,接口的声明
lexer.cpp词法分析器模块实现
/parser/语法分析器目录
CMakeLists.txt语法分析模块的cmake规则
parser.hpp语法分析器头,包括分析树节点,接口声明
parser.cpp语法分析器模块实现
/semantic/语义分析器目录
CMakeLists.txt语义分析模块的cmake规则
semantic.hpp语义分析器头
semantic.cpp语义分析器模块

设计思路

整个项目分为三个模块,词法分析器,语法分析器,语义分析器

每个模块都制作为静态链接库,方便与main.cpp的链接

lexer,parser,semantic均使用模块化程序设计思想,不使用类表示,而是用整个模块表示.

其组成部分比如Token或者Node采用面向对象思想,自定义Token和Node类,对外提供setter和getter方法

词法分析器

词法分析器中,getToken函数包装了一个硬编码的DFA,这是最简单的DFA实现,如果想使用有向图驱动的或者表驱动的DFA,只需要改写getToken函数内部即可,最终只需要返回一个Token*类型的记号.

至于DFA如何长什么样,放一张图意思意思,具体实现见代码和注释

a
0
*
/
-
+
,
;
(
)
.
*
/
-
a
0
0
0
0,26,START
1,1,ID
2,24,CONST_ID
3,24,CONST_ID
4,20,MUL
5,22,POWER
6,21,DIV
7,19,MINUS
8,18,PLUS
9,17,COMMA
10,14,SEMICO
11,15,L_BRACKET
12,16,R_BRACKET
13,0,COMMENT

这个图上所有的数字都用0代表

所有的字母都用a代表

除了START节点,都是终态

至于如何从正规式建立DFA,见词法分析 | Deutschball’s blog (dustball.top),但是本次实验不是重点,我们直接从建立好的DFA开始,只需要知道如何从DFA上进行状态转移即可

语法分析器

语法分析器负责建立抽象语法树

老师给出的代码是严格按照文法编写的
P r o g r a m → { S t a t e m e n t ; } S t a t e m e n t → O r i g i n S t a t e m e n t ∣ S c a l e S t a t e m e n t ∣ R o t S t a t e m e n t ∣ F o r S t a t e m e n t O r i g i n S t a t e m e n t → O R I G I N   I S   ( E x p r e s s i o n , E x p r e s s i o n ) S c a l e S t a t e m e n t → S C A L E   I S   ( E x p r e s s i o n , E x p r e s s i o n ) R o t S t a t e m e n t → R O T   I S   E x p r e s s i o n F o r S t a t e m e n t → F O R   T   F R O M   E x p r e s s i o n   T O   E x p r e s s i o n   S T E P   E x p r e s s i o n   D R A W   ( E x p r e s s i o n , E x p r e s s i o n ) E x p r e s s i o n → T e r m   E x p r e s s i o n ′ E x p r e s s i o n ′ → { ± T e r m } T e r m → F a c t o r   T e r m ′ T e r m ′ → { × F a c t o r } F a c t o r → ± F a c t o r ∣ C o m p o n e n t C o m p o n e n t → A t o m   P O W E R   C o m p o n e n t ∣ A t o m A t o m → C O N S T _ I D ∣ T \begin{aligned} Program&\rightarrow \{Statement;\}\\ Statement&\rightarrow OriginStatement|ScaleStatement|RotStatement|ForStatement\\ OriginStatement&\rightarrow ORIGIN\ IS\ (Expression,Expression)\\ ScaleStatement&\rightarrow SCALE\ IS\ (Expression,Expression)\\ RotStatement&\rightarrow ROT\ IS\ Expression\\ ForStatement&\rightarrow FOR\ T\ FROM\ Expression\ TO \ Expression\ STEP\ Expression\ DRAW\ (Expression,Expression)\\ \\ Expression&\rightarrow Term\ Expression'\\ Expression'&\rightarrow \{±Term\}\\ Term&\rightarrow Factor\ Term'\\ Term'&\rightarrow\{×Factor\}\\ Factor&\rightarrow ±Factor|Component\\ Component&\rightarrow Atom\ POWER\ Component|Atom\\ Atom&\rightarrow CONST\_ID|T \end{aligned} ProgramStatementOriginStatementScaleStatementRotStatementForStatementExpressionExpressionTermTermFactorComponentAtom{Statement;}OriginStatementScaleStatementRotStatementForStatementORIGIN IS (Expression,Expression)SCALE IS (Expression,Expression)ROT IS ExpressionFOR T FROM Expression TO Expression STEP Expression DRAW (Expression,Expression)Term Expression{±Term}Factor Term{×Factor}±FactorComponentAtom POWER ComponentAtomCONST_IDT
然而C++2019版实现中,光是语法树的节点类就有一大堆,使得代码可读性下降

原因是将Statement的文法也建立了分析树节点

如果只对Expression及之下的文法建立分析树,那么顶多有二元节点,只涉及左右操作数和一个运算符

而这些Statement可能有多个子节点,比如ForStatement,他又5个操作数,这就得定义一个五个指针的节点类

实际上不需要

将各种Statement硬编码处理,只建立算术表达式的分析树即可,这样只需要一个节点类即可

语义分析器

语义分析和语法分析的界限没有语法分析和词法分析那么明显

我认为的语义分析就是对语法分析建立的表达式树进行求值

实际上对于一些参数的设置,在语法分析阶段就可以进行,

比如SCALE IS(2,3);这句在语法分析阶段就可以得到两个放缩尺寸,scale_x=2,scale_y=3,不需要语义分析

甚至将python绘图语句输出到程序也是语法分析器提醒语义分析器做的

感觉上语法分析器是整个程序的核心,词法分析和语义分析都为其服务

四、心得体会

优点

写之前我参考了老师给出的2018C语言实现和2019C++实现两个版本,还有19级学长的实现arttnba3/compiler_principles

总结并克服了各方缺点,主要有这么几个

1.类体系太复杂,节点类太多了,并且符号类有一个basic_token有一个token,意思是basic_token是最重要的业务,token加上了一些锦上添花的功能.但是这样写在类型转换,参数传递时都会犯浑,到底用的是哪个token类型?

应该用尽可能少的类尽可能简便地实现功能

2.命名不统一,比如函数名,函数名有驼峰规则的,也有下划线的.应该将变量命名统一,函数命名统一,类型命名统一

我的变量全部使用小写和下划线,函数名和类型名使用驼峰,函数名开头小写,类型开头大写

3.c,c++混用.如果选用c++实现,应该尽量不使用c的语法,用相同功能的c++语句实现.

就比如文件读写,用FILE,fgetc又麻烦又丑,为了获取一个token的字符串,需要往缓冲区读入,手动移动缓冲区指针.如果使用fstream流和std::string,那么往缓冲区添加字符就直接buffer+=character

比如这是一个c实现的维护缓冲区,太麻烦了

static inline char* create_letme_buf(void)
{
    char    *buf;
    buf = new char[token_buffer_loc];
    if (!buf)
        err_exit("run out of memory.", nullptr, -ENOMEM);
    memset(buf, 0, sizeof(char) * token_buffer_loc);
    memcpy(buf, token_buffer, token_buffer_loc);
    return buf;
}

如果使用std::string

    Token *token=new Token();//返回值,一个符号
    char character;//当前读取的字符
    std::string buffer="";//字符缓冲区,用于构造lexme
	...
    character=fin.get();
	...
	buffer+=character;
	...
    token.setLexme(buffer);

还有就是如何维护符号表

用c维护的符号表

// built-in token table
static struct token bulit_in_token_table[] = 
{
    { CONST_ID,     "PI",       3.1415926,  nullptr },
	...
    { DRAW,         "DRAW",     0.0,        nullptr },
};

#define BUILTIN_TOKEN_TABLE_NUM (sizeof(bulit_in_token_table) / sizeof(struct token))

#define MAX_TOKEN_TABLE_NUM 1024

static int append_token_table_size = 0;

static struct token *append_token_table[MAX_TOKEN_TABLE_NUM];

还得定义宏给符号表服务

如果使用vector,相关的宏直接调用vector的成员函数即可

static std::vector<Token> build_in_token_table={
    {CONST_ID, "PI", 3.1415926, NULL},
	...
    {DRAW, "DRAW", 0.0, NULL},
};

static std::vector<Token*> append_token_table;

4.控制耦合

别人的实现中有这么一个函数,其作用是建立一个分析树节点

static struct expr_node* make_expr_node(enum token_type opcode, void *arg1, void *arg2)
{
    struct expr_node    *new_node;

    new_node = new struct expr_node;
    if (!new_node)
        err_exit("run out of memory.", nullptr, -ENOMEM);
    new_node->opcode = opcode;
    switch (opcode)
    {
        case CONST_ID:
                new_node->content.case_const = *reinterpret_cast<double*>(arg1);
                break;
        case T:
                new_node->content.case_param = reinterpret_cast<double*>(arg1);
                break;
        case FUNC:
                new_node->content.case_func.func_ptr = reinterpret_cast<double (*)(double)>(arg1);
                new_node->content.case_func.child = reinterpret_cast<struct expr_node*>(arg2);
                break;
        default:
                new_node->content.case_operator.left = reinterpret_cast<struct expr_node*>(arg1);
                new_node->content.case_operator.right = reinterpret_cast<struct expr_node*>(arg2);
                break; 
    }

    return new_node;
}

这个函数的arg1和arg2参数需要根据第一个参数opcode的值决定,函数内的控制流也会根据opcode进入不同的分支,这实际上是控制耦合

缺点一是两个参数arg1和arg2的作用很容易忘记,二是需要使用强制类型转换,太不优雅.

实际上这个函数可以不存在,将相关功能实现在Node类型的成员函数里即可

这个函数也并没有带来多少复用,弊大于利

缺点

1.在语法错误的时候,采取的方式时报告出错位置并且立刻终止程序,没有做到从错误中恢复

2.硬编码的DFA虽然简单,但是不灵活,相对不容易拓展

收获

1.auto语法和初始化列表语法是C++11的特性,编译时需要加入编译选项比如g++ -std=c++11 main.cpp -c

2.类体系复杂了不一定让结构清晰,继承不要太多,甚至可以牺牲空间,尽可能少用类

  • 3
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
### 回答1: 西安电子科技大学编译原理实验SQL解释是一个用于解析和执行SQL语句的工具。SQL是一种结构化查询语言,用于管理和操作关系型数据库中的数据。 该解释的主要功能是将用户输入的SQL语句进行解析和执行。首先,它会将输入的SQL语句进行词法分析,将其划分为不同的单词和符号,并生成一个词法分析树。然后,语法分析将根据语法规则对词法分析树进行语法分析,生成一个语法分析树。 接下来,解释会对语法分析树进行语义分析,检查语句是否符合数据库的定义和限制,例如表的存在性、属性的类型和完整性约束等。如果存在错误,解释会给出相应的错误信息。如果语句通过了语义分析,解释将根据语义分析树生成对应的查询执行计划。 最后,解释将执行查询执行计划,从数据库中获取所需的数据,并返回给用户。在执行过程中,解释会处理各种SQL语句,例如查询语句、更新语句、插入语句等,以及各种子句和操作符,如SELECT、JOIN、WHERE等。 西安电子科技大学编译原理实验SQL解释的开发需要掌握词法分析、语法分析、语义分析和查询执行等相关知识与技术。它在数据库管理系统中具有重要的应用价值,能够提供方便、高效和准确的数据操作和管理功能,为用户提供了便捷的数据库操作界面。 ### 回答2: 西安电子科技大学编译原理实验中,我们设计了一个SQL解释。SQL(Structured Query Language)是一种用于管理和处理关系型数据库的编程语言。我们的目标是设计一个能够解析和执行SQL语句的程序。 在我们的实验中,我们首先学习了SQL语言的语法和语义规则,了解了其基本的结构和常用的操作。然后,我们使用编译原理中的词法分析和语法分析技术,对输入的SQL语句进行解析。词法分析阶段将输入的SQL语句拆分为一个个单词(例如关键字、标识符、运算符等),然后语法分析阶段通过对这些单词进行组合和判断,构建SQL语句的抽象语法树。 接下来,我们需要对抽象语法树进行语义分析。通过检查语法树中每个节点的类型和属性,我们可以判断SQL语句在语义上是否合法。例如,我们可以验证表是否存在、列是否匹配、约束是否满足等。如果发现语义错误,我们将在解释中报告错误信息。 一旦经过语义分析,我们就可以执行SQL语句了。在解释中,我们根据SQL语句的类型(例如查询、插入、删除、更新等),调用相应的数据库操作,如查询表、插入记录等。我们还可以通过解释实现一些高级功能,比如支持条件查询、连接查询、聚合函数等。 在实验中,我们还考虑了性能优化。例如,我们可以对SQL语句进行查询优化,选择合适的查询计划来提高查询速度。我们还可以使用缓存来避免重复执行相同的SQL语句。 总的来说,通过实现SQL解释,我们能够更好地理解和学习编译原理的知识,同时也能够提升对SQL语言的理解和应用能力。通过实验,我们可以更好地掌握SQL语句的解析和执行过程,为未来在数据库设计和开发中更好地使用SQL语言打下基础。 ### 回答3: 西安电子科技大学编译原理 实验中的SQL解释是一款用于解释和执行SQL语句的程序。SQL(Structured Query Language)是一种用于管理和操作关系数据库的语言,而编译原理是研究如何将高级语言翻译成机语言的学科。 该SQL解释的主要功能是接受用户输入的SQL语句,对其进行词法分析、语法分析和语义分析,最终生成并执行相应的数据库操作。 在词法分析过程中,解释会将输入的SQL语句分解为一个个单词或符号。然后,在语法分析阶段,解释会根据SQL语法规则,将单词和符号组合成具有语法结构的语句树。接着,在语义分析过程中,解释会验证语句的语义正确性,并进行必要的类型检查和作用域分析。 完成了上述步骤后,解释将根据语句树生成相应的数据库操作。这些操作可以包括查询数据、插入记录、更新数据和删除数据等。解释会调用数据库系统提供的接口,将生成的操作发送给数据库引擎执行,然后将结果返回给用户。 通过实现SQL解释,我们可以更深入地理解编译原理的相关概念和技术,同时也能够提升对数据库管理系统的理解和应用能力。此外,SQL解释还可以用于实际开发中,对于处理和管理大量数据的应用程序来说,具有重要的实际意义。 总之,西安电子科技大学编译原理实验中的SQL解释是一个用于解释和执行SQL语句的程序,它通过词法分析、语法分析和语义分析等步骤,将SQL语句转化为数据库操作,并与数据库引擎进行交互,实现对数据库的操作和管理。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

灰球球

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值