前言
目前很有野心,打算做个大事。虽然编程水平还不高,看《编译原理》也看不下去,看视频教程看了十几节课发现笔记上全是数学符号。就——暂时不看了吧!我之前因为好奇买了本《两周自制脚本语言》,的确学了一些知识。但是其中从第五天开始,都使用了一个Parser库,看知乎知道了难点不在这个库上,所以一般都用现成的。而书的问题是代码没多少注释!!!实在搞不明白,再加上java用起来实在不爽!于是这条路也抛弃了,最后我打算先从一个中缀表达式转后缀表达式搞起——因为机器识别不了式子,只能识别符号和数字,所以可以先转换成后缀表达式,然后通过这个表达式求解。
一般在语法分析的时候,需要构造AST语法树。而中后缀这条路不用,需要利用栈。
形成语法树可以把各种符号涵盖进去,而中后缀只能计算数字直接的加、减、乘、除、乘方等简单的一元或二元操作,最后也只能得到一个计算结果(一般是double类型的数)。
我觉得中后缀这条路挺好用的,凡是表达式都可以用这种方法得到结果,如果有函数在其中,就把函数替换为函数的返回结果!
虽然这么说!但是这只是对表达式而言!对于其他语句和语句块,还是要构造AST的!
我目前的项目进度到了分割语句和分类语句上,已经完成了分割Token和分类Token,因为项目中变化太多,加上现在已经有了31个文件,实在没办法放上来。但是可以把一些概念贴上!
上层语言与下层语言的关系
上层语言是我的自制语言Suatin-lang,下层语言是C++。
上层语言方法都是小写,下层语言方法大部分都是驼峰
上层语言的类型,如int/number/BigNumber/complex/string/bool/function/array/file都继承于object,不是因为我想搞面向对象和高阶函数什么的,而是这样后面创建对象和传递对象时比较方便!!!
下层语言C++中有对应的类,如SuaInt/SuaNumber/SuaBigNumber/SuaComplex/SuaString/SuaFunction/SuaBool/SuaArray/SuaFile/SuaObject,这些类专门对应着处理上层语言的数据对象——变量、函数等。
//SuaUtil.h
class SuaObject {
protected:
bool isconst = false;///是否是常量。const可以修饰SuaObject
std::string name="";///标识符名称
int zoneIndex;//作用域索引,和pos一起用的。只有SuaObject才有这个
public:
//返回类型名的字符串,因为源码是字符串,输入输出都是字符串处理
virtual std::string getname()const = 0;
//返回类型
virtual std::string gettype()const = 0;
///常量操作
bool is_constant()const;
void setconst();
void resetconst();
};
解释器中的类
下层语言还有其他的类,如Token。源码中所有的符号被分类,每个符号或标识符都放入一个Token,所有的Token都会放入全局的容器中,即放入全局中缀表达式中。特殊的Token会包含SuaObject对象——即自定义变量或函数。
Expr——只有四则加乘方的运算才能算作表达式,这个我还没认真修改。只有个空壳。表达式里关键是两个变量,start&end,表示表达式在全局中缀表达式的中范围。
Pool——存储池,自定义的变量或函数,即SuaObject对象,会放入这个池子里。暂时不想搞类型推导,所以暂时打算做静态类型的语言。每次遇到一个标识符都会和池子里的进行比对,看看池子里是否有。检查标识符靠的是标识符的名称,不是地址。
在上层语言中,一个变量或函数只有两个关键的点,一是它的值,放在底层的SuaObject中。二是它字符串的名称,这意味着在此上层语言中,不能对关键字创建变量!!!
Parser——用来遍历中缀表达式,分割语句和分类语句。其中有几个关键变量:start&end&三个作用域计数器&作用域容器。
- start&end从头开始遍历到尾,来分割不同的语句。
- 作用域计数器就厉害了。每遇到一个左括号就加一,遇到右括号就减一,即当计数为n时的位置是A时,再到了计数为n-1的位置为B后,A-B之间就是一个作用域!!!识别不同作用域靠n的值。
- 三个作用域计数器就是三个int,分别计数小括号、中括号、大括号。
- 作用域容器放一个结构体,用来表示一个作用域。内容是作用域索引、作用域开头、作用域结尾。每个SuaObject内存放一个自己的作用域索引,Token中有该符号的位置,把位置和索引都传入Parser的作用域检测方法,就能知道此变量是否在该作用域内
- 但是当两个作用域并行时,他们的索引是一样的,所以需要再引入一个变量number。区分并行的作用域!
//Parser.h
//这个类暂时的样子
class Parser
{
private:
//一个语句的起始就是start到end-1
int start;//语句开头,可以是标识符、数字
int end;//语句结尾,可以是\ n ; } {
//一个作用域的起始就是count_zoneQ_xxx=n到count_zoneQ_xxx=n-1时,对应的两个位置之间
//下面三个计数器都是遇到左括号加一,遇到右括号减一。不同的作用域间的数不同。这个数指的就是作用域索引。
static int count_zoneQ_little;//小括号作用域计数
static int count_zoneQ_middle;//中括号作用域计数
static int count_zoneQ_big;//大括号作用域计数
//花括号作用域索引,在使用语句前,先经过检查是否在对应的作用域内—— GetVal()
//索引小的作用域大!!!所以如果是作用域1的数,可以在作用域2/3内使用
typedef struct _ZONE{
int index;//作用域索引
int number;//区分并行作用域
int start;//作用域开头
int end;//作用域结尾
}ZONE;
static std::vector<ZONE*> zone;
public:
//分割语句
static void SyntaxSeparate();
//分类语句
static void SyntaxClassify();
//检查变量的位置是否在其作用域内
static bool InZone(int index,int number,int pos);
};
shell——解释器的交互面。从文件中读入字符串、Token的分割与分类、显示Pool池子的内容、显示中缀表达式的内容、显示语句分类的内容等等直接得到处理效果或是运行效果的函数。
SuaUtil——最底层的仓库。自定义异常处理、各种枚举、基类的定义等等。既然是最底层的类,那么绝对不能放太多的东西来增加依赖,应该不依赖其他自定义的文件!
SuaMath——一些数学操作的集合。
SuaExcept——自定义异常类,枚举了一些能想到的错误。具体请见之前的文章lang:C++自定义异常类——用来处理自制编程语言的异常信息。
文件的引用关系
项目大了要分好文件,不能有交叉引用。因为如果后续我使用模板、友元的话会很麻烦。
解释器的结构
目前脑子里的解释器是下面这样,以后完成了肯定会不同的。
略
解释器运行流程
- 传入 .suatin文件的路径,并运行解释器。
- 从 .suatin文件中读取字符串,一行行地组成一个字符串,每读取一行就在后面添加一个\n——也可以添加\r\n,那样的话稍微有点不同而已
- 对整个字符串进行正则匹配,把关键字、标识符、五则运算符(+ - * / ^ )、判断运算符(> < <= >= == ~=)、小括号、中括号、大括号、数字(含小数、指数)、注释(含单行注释、多行注释)、换行\n、分号、等于号、逗号运算符都分割出来,然后得到分组索引,映射为优先级后,除了注释外的符号都创建Token。
- 把Token放入全局中缀表达式中。
- 遍历中缀表达式,分割语句,确定标识符(即变量、函数)作用域,把作用域信息保存到Parser和SuaObject上。分类语句,把一条条的语句放入语句链中——放语句在中缀表达式中的范围就行——执行代码就是执行这条语句链!
- 根据代码中的变量定义语句,创建SuaObject对象的实例,并把指针放入Pool池子中。
- 构造语法树AST。
- 求解语法树
因为是直接运行的,没有中间代码,所以与其说是解释器,不如说是直译器!
项目现在做到了那个位置?
下面是测试Suatin-lang的源码,C++项目就省略了。
//test.suatin
int sum=0.0e0
const int i=0
string str="hello world suatin-l
ang programmer!"
if str == null
{
print("yes") }
else
{
i=i+1 //zhushi
sum=sum+i /*duohangzhushi
*/
sum=sum/2;sum=sum+sum*i
}
print(sum+i)//zhushi
显示结果:
welcome to suatin interpretor interface
C:\Users\LX\Desktop\test.suatin内代码为>
int sum=0.0e0
const int i=0
string str="hello world suatin-l
ang programmer!"
if str == null
{
print("yes") }
else
{
i=i+1 //zhushi
sum=sum+i /*duohangzhushi
*/
sum=sum/2;sum=sum+sum*i
}
print(sum+i)//zhushi
--------------代码结束!--------------
中缀表达式>
name pos priority
int 0 1
sum 1 12
= 2 8
0.0e0 3 18
\n 4 6
const 5 1
int 6 1
i 7 12
= 8 8
0 9 18
\n 10 6
string 11 1
str 12 12
= 13 8
"hello world suatin-l
ang programmer!" 14 5
\n 15 6
if 16 1
str 17 12
== 18 11
null 19 1
\n 20 6
{
21 3
print 22 12
( 23 10
"yes" 24 5
) 25 10
} 26 4
\n 27 6
else 28 1
\n 29 6
{
30 3
\n 31 6
i 32 12
= 33 8
i 34 12
+ 35 15
1 36 18
\n 37 6
sum 38 12
= 39 8
sum 40 12
+ 41 15
i 42 12
\n 43 6
sum 44 12
= 45 8
sum 46 12
/ 47 14
2 48 18
; 49 7
sum 50 12
= 51 8
sum 52 12
+ 53 15
sum 54 12
* 55 14
i 56 12
\n 57 6
} 58 4
\n 59 6
print 60 12
( 61 10
sum 62 12
+ 63 15
i 64 12
) 65 10
\n 66 6
第1条语句>sum=0.0e0
第2条语句>i=0
第3条语句>str="hello world suatin-l
ang programmer!"
第4条语句>str==null
第5条语句>print("yes")
第6条语句>i=i+1
第7条语句>sum=sum+i
第8条语句>sum=sum/2
第9条语句>sum=sum+sum*i
第10条语句>print(sum+i)
作用域信息>
index = 1,number=1,start=21,end=26
index = 1,number=2,start=30,end=58
请按任意键继续. . .
CSDN
项目在我的资源里(要多少C币不是我决定的,明明设置的需要0C币,但是之后看被改了555)!
项目代码地址BDWP
链接:https://pan.baidu.com/s/1yX7j69kpbr4a5kd-V-LyRg
提取码:2agq
复制这段内容后打开百度网盘手机App,操作更方便哦