如何自己写一门简单的编程语言(Unix系统编程第一次实验 / U language 解释型语言)

Posted 狱典司

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何自己写一门简单的编程语言(Unix系统编程第一次实验 / U language 解释型语言)相关的知识,希望对你有一定的参考价值。

一、实验要求

1.目标

实现一个解释性语言Ulanguage,用C语言解释,使得其能够在终端中执行
Java、Python 都是解释性语言,其需要解释器,在执行时解释执行 —— 看到一条指令就翻译解释一条指令(优点:跨平台;缺点:效率低)。

2.U Language 功能

(1) 基本要求

  1. 支持基础变量数据类型 int 和 string
  2. 支持(全局)变量的声明、赋值、取值、取址、计算等操作(未声明无法使用,报错)
    Int 型变量支持:+、-、*、/等计算
  3. String 型变量支持:+(拼接)、size(s)取长度、| (取小字母)、^ (取大字母)等计算
  4. 支持 if(exp)statementlelse statement2 endif 条件复合语句(无需实现嵌套,仅支持单语句复合)
    支持 temp 变量存储(无赋值计算结果存在 temp 中)
  5. 支持全局变量表、符号表的打印 listMem 和 listSymbolTable2提高要求(+5~8 分)

(2) 提高要求

  1. 支持函数定义 define f returntype argutype1 argutype2 statements return enddefine
  2. 支持函数变量的类型和语句存储
  3. 支持函数调用(激活存储及符号表)
  4. 支持局部变量定义,优先访问局部变量
  5. 可选:支持while(exp)statement1 statement2 endwhile 操作

3. 关注点

  1. 通过本次实验来达到实践巩固、深化理解学习到的进程对内存的使用和管理、进程地址空间等知识
  2. 按照计算机基本运算 —— 赋值、计算、取址、取值、声明来尽可能还原程序的执行过程,理解程序执行过程的合理以及必要性,在编写程序的过程中体会句法存在的必要性。
  3. 按照所学知识去还原全局变量在内存表和符号表中的存储和查询、调用情况。
  4. 对于函数,可以先不考虑函数激活(Activation Record)中的访问链、控制链等复杂情况(不用考虑函数的递归调用),关注其私有变量的内存存储与使用。
  5. 除了全局变量和私有变量,也需要考虑寄存器的实现。
  6. 支持函数的定义和函数调用,并可以在函数内调用全局变量。
  7. 支持while和if语句。
  8. 编程的同时思考语言的句法结构,对基本的语法错误要有报错信息。

4. 本实验使用的数据结构

如果实验要求中的内存表、符号表、进程地址空间、函数激活等知识点都了然于胸的话可以跳过文字部分直接看代码。

(1) 符号表

什么是符号表?

  • 在编译过程中,编译器需要不断地去查证出现在源程序中的各种变量名的属性和特征等信息;
  • 编译器使用符号表来记录名字的作用域以及绑定信息;
  • 符号表中的信息在编译的不同阶段都用到在语义分析中;
  • 符号表是变量地址分配的依据;

符号表的常见属性:

  符号名 
  符号的类型
  符号的存储类别 
  符号的作用域及可视性 
  符号变量的存储分配信息 
  符号的其它属性
  数组内情向量 
  记录结构型的成员信息
  函数及过程的形参 

在本次实验中,符号表的结构简单考虑为:

#define SIZE_OF_SYM 1000  //符号表大小
bool func_lock = false;   //函数"锁",为区分全局变量和局部变量,执行函数前上锁,函数执行完就解锁 

typedef struct ST{   //符号表的结构体
	string varName;  //变量名
	int addrr;       //变量实际值存储的起始位置
	string varType;  //变量类型(注意,函数也有类型)
	int varLength;   //变量长度
}ST;

ST symList[SIZE_OF_SYM];//符号表,最多同时支持1000个变量

(2)内存表

变量实际的值并不存储在符号表中,而是存在内存表中,符号表中只是存储了变量的存储分配信息 ;这里所说的“内存表”其实指的就是内存的地址空间。
可以这样理解:符号表像是为寄存器快速查证变量实际内存地址的快速索引目录。

#define SIZE_OF_MRY 10000 //内存表的地址空间为0~9999
int mry[SIZE_OF_MRY];    //内存表,该实验使用C原有的int来存储数据
                         //即用int来模拟地址单元

(3)寄存器

用于存储计算中间过程;
该实验只为没有赋值操作的计算(包括函数调用)的结果调用寄存器。

typedef struct temp{
	string varType;
	string varValue;
}temp;
temp tmp;//临时变量存储器(假设只有一个)

(4)进程地址空间

(5)激活记录 / 函数激活 / 栈帧(Activation Record)

引用百度百科的经典解释:

“栈帧也叫过程活动记录,是编译器用来实现过程/函数调用的一种数据结构。”


怕有的小伙伴搞混淆了索性把常见的几种叫法都写出来了…

Unix为例,一般函数激活需要存的内容:

参数
类型
函数中局部数据or临时变量存储位置
控制链:指向调用该函数的函数激活
返回值:给上一个函数激活使用(根据栈帧压栈的顺序)
访问链:指向上一个非本函数自身的函数激活
状态记录:用于记录进程被等停时的状态

在调用函数时:
1.装入文本段和数据段(等)------- 执行main ------ 分配栈中的激活记录/栈帧;
2.把实参(从上一个函数激活、全局变量等)放入实参表;
3.计算结果并存入返回值;
4.根据控制链找到相应的语句将值写回;
5.释放激活记录;

/*
 * 本实验暂时考虑只有一个栈帧的情况,不考虑函数访问链与控制链;
 * 即不考虑多函数嵌套和函数递归的情况。
 * (ps:其实也可以考虑,不过学习紧张时间有限就没额外去写)
 */
#define SIZE_OF_MRY_FUNC 1000 //激活记录的内存表大小
#define SIZE_OF_SYM_FUNC 100  //激活记录的符号表大小

typedef struct funcActivation{    //激活记录的结构体
	string funcName;              //函数名
	string returnType;            //返回值
	string returnVar;             //返回值
	string funcVar1;              //参数1
	string funcVar2;              //参数2 (本实验只要求考虑二元函数的情况)
	ST func_ST[SIZE_OF_SYM_FUNC]; //函数激活中的局部变量(局部符号表),最多支持100个局部变量 
	int func_mry[SIZE_OF_MRY_FUNC]; //局部内存表
}funcActivation;
funcActivation fat;                //本实验暂时考虑只有一个栈帧的情况

二 、结果演示

对int类型的简单赋值、声明与计算:

【说明】 “ ?”是输出变量值的意思。
毕竟是你自己写的语言,怎么输出由你自己定~

对string类的声明、赋值和计算:

【说明】“|” 和“^”分别是对字符串按位取小和按位取大。

对符号表和内存表的查看:

【说明】
symtab是查看全局符号表
memory是查看全局内存表。

取变量地址和变量长度:

【说明】这…没啥好说的,值得一提的是本实验采用的内存管理方法是FIFO首次适应算法。

函数定义(注意语法):

【说明】定义了函数名、返回类型、参数后解释器会实时返回(#System notice字段)该函数的数据类型。
注意:因为该程序中只模拟了一个栈帧,所以在define新的函数时,需要把上一个栈帧弹出(其实就是抹除它滞留在局部地址的信息)

写一个MAX函数做示范:

函数定义的完整语法:

  • define 函数名
    returnType 参数类型 (返回值类型)
    参数类型 参数名
    参数类型 参数名

    语句

    return 参数名
    endDefine;

【说明】默认文本段(text)的地址设置在10001
注意,函数名的相关信息是会存入全局符号表里的。

函数调用:

【说明】
函数调用的语法:函数名(val1,val2)

寄存器的演示

【说明】查看寄存器内容:tmptab

while、if 语句的实现 && 函数内支持全局变量

【说明】

  1. while语句语法格式:
    while(表达式) 执行语句 endWhile
  2. if语句语法格式:
    if (exp) statement1 else statement2 endif
    注:if语句也可以不使用else。

调用一下刚刚定义的mega函数:

看看结果:

可以看到全局变量global因为函数调用(根据函数内while和if的结果)被赋值了。


【说明】
查看局部符号表:fats
查看局部内存表:fatm


三、完整代码

本着开源和分享交流、相互学习、回馈社区的精神,给同学们放上完整代码,感谢点赞收藏和关注哦 ^ _ ^

#include<iostream>
#include<stdlib.h> 
#include<stdio.h>
#include<string>
#include<cstring>
#include<iomanip>
#include<malloc.h>
#include<vector>
#include<conio.h>
#include<sstream>
#include <algorithm>
using namespace std;

//预定义这些常量,可以消除计算数组长度而产生的时间消耗 
#define SIZE_OF_MRY 10000 //内存表
#define SIZE_OF_SYM 1000//符号表
#define SIZE_OF_MRY_FUNC 1000 //激活记录的内存表
#define SIZE_OF_SYM_FUNC 100//激活记录的符号表

bool func_lock = false;  //函数锁,执行函数前上锁,函数执行完就解锁 

typedef struct ST{
	string varName;
	int addrr;
	string varType;
	int varLength;
}ST;

ST symList[SIZE_OF_SYM];//符号表,最多同时支持1000个变量
int mry[SIZE_OF_MRY];//内存表 

typedef struct temp{
	string varType;
	string varValue;
}temp;
temp tmp;//临时变量存储器

typedef struct funcActivation{
	string funcName;
	string returnType;
	string returnVar;
	string funcVar1;
	string funcVar2;
	ST func_ST[SIZE_OF_SYM_FUNC]; //函数激活中的局部变量,最多支持100个局部变量 
	int func_mry[SIZE_OF_MRY_FUNC]; 
}funcActivation;
funcActivation fat;

int getSTLen(ST s[]){
	if(s == symList){
		return SIZE_OF_SYM;
	}else if(s == fat.func_ST){
		return SIZE_OF_SYM_FUNC;
	}
} 

int getMRYLen(int m[]){
	if(m == mry){
		return SIZE_OF_MRY;
	}else if(m == fat.func_mry){
		return SIZE_OF_MRY_FUNC;
	}
} 

vector<string> split(const string& str, const string& delim) {  
	vector<string> res;  
	if("" == str) return res;  
	char * strs = new char[str.length() + 1] ;  
	strcpy(strs, str.c_str());   
 
	char * d = new char[delim.length() + 1];  
	strcpy(d, delim.c_str());  
 
	char *p = strtok(strs, d);  
	while(p) {  
		string s = p; //分割得到的字符串转换为string类型  
		res.push_back(s); //存入结果数组  
		p = strtok(NULL, d);  
	}  
	return res;  
}  

string intToStr(int ans){
	stringstream s;
	s << ans;
	string p = s.str();
	const char* res = p.c_str();
	return res;
}

void memory(int *mry){
	cout<<"************ Memory List ************"<<endl; 
	cout<<"addrress"<<'\\t'<<"value"<<endl;
	int len = getMRYLen(mry);
	for(int i = 1; i < len; i++ ){
		if(mry[i] != 0) 
		cout<<i<<'\\t'<<mry[i]<<endl;
	}
	cout<<endl;
} 

void fat_memory(int *mry){
	cout<<"************ Memory List of Activation Record ************"<<endl; 
	cout<<"addrress"<<'\\t'<<"value"<<endl;
	for(int i = 1; i < 1000; i++ ){
		if(fat.func_mry[i] != 0) 
		cout<<i<<'\\t'<<fat.func_mry[i]<<endl;
	}
	cout<<endl;
} 

void symctl(ST *symList){
	cout<<"************ Symbol List ************"<<endl; 
	int len = getSTLen(symList);
	cout<<"varType"<<'\\t'<<"varName"<<'\\t'<<"    varAddrr"<<'\\t'<<"varLength"<<endl;
	for(int i = 0; i < len; i++){
		if(symList[i].varName == "") break;
		cout<<symList[i].varType<<'\\t'<<symList[i].varName<<"\\t    \\t"<<symList[i].addrr<<"\\t\\t"<<symList[i].varLength<<endl;
	}
	cout<<endl;
}

void showTmp(){
	cout<<"********** 寄存器 **********"<<endl;
	cout<<"所存数据类型:"<<tmp.varType<<endl;
	cout<<"所存数据值: "<<tmp.varValue<<endl; 
} 

void declear(string type, ST *symList){
	if(type == "string" || type == "int"){
		string varName;
		cin >> varName;
		int len = getSTLen(symList);
		for(int i = 0; i < len; i++){ //内存表顺序存储管理 
			if( symList[i].varName == "" ){
				symList[i].varName = varName; //加入符号表 
				symList[i].varType = type;
				break; 
			}
			if(symList[i].varName == varName){
				cout<<"Variable name conflict! This varluable has been defined already!"<<endl<<endl;
				break;
			}
		}
	}else{
		cout<<"The data type is not supported !"<<endl;
	}
}

void func_declear(string type, string varName, ST *symList){
	if(type == "string" || type == "int"){
		int len = getSTLen(symList);
		for(int i = 0; i < len; i++){ //内存表顺序存储管理 
			if( symList[i].varName == "" ){
				symList[i].varName = varName; //加入符号表 
				symList[i].varType = type;
				break; 
			}
			
			if(symList[i].varName == varName){
				cout<<"Variable name conflict! This varluable has been defined already!"<<endl<<endl;
				break;
			}
		}
	}else{
		cout<<"The data type is not supported !"<<endl;
	}
}

int findVar(string cmd1,ST *symList){
	int len = getSTLen(symList);
	for(int i = 0; i < len; i++){
		if(symList[i].varName == cmd1) return i;
		else if(symList[i].varName == "") return -1;
	}
} 

void op_againAssignment(string varValue, int keyInST,ST *symList, int *mry){
	if(symList[keyInST].varType == "int"){
		mry[symList[keyInST].addrr] = atoi(varValue.c_str());
	}else if(symList[keyInST].varType == "string"){
		for(int i = symList[keyInST].addrr; i < symList[keyInST].addrr+symList[keyInST].varLength; i++){
			mry[i] = 0;
		}
		int len = getMRYLen(mry);
		for(int i = 1; i < len; i++){
			if(mry[i] == NULL){
				int j = i;
				for(; j < i+varValue.length(); j++){ //首次适应方法,考虑碎片问题 
					if(mry[j] != NULL){
						 i =j;
						 break;
					}
				}
				if(j == i+varValue.length()){ //循环结束后判断j的位置决定是否在此存储 
					for(int j = i, seq = 0; j < i+varValue.length(); j++, seq++){
						mry[j] = varValue[seq]; //将string中的每一个char按照ASSIC码存储入整形数组 
					}
					symList[keyInST].varLength = varValue.length();
					symList[keyInST].addrr = i;
					break;
				}
			}
		}
		
	}
} 
void op_assignment(string varValue, int keyInST, ST *symList, int *mry){
	if(symList[keyInST].varType == "int"){
		
		int value = atoi(varValue.c_str());
		if(value == 0 && varValue != "0"){
			cout<<"数据类型不匹配!赋值失败!"<<endl;
		}
		else{
			int len = getMRYLen(mry);
			for(int i = 1; i < len; i++){
				if(mry[i] == NULL){
					mry[i] = value;
					symList[keyInST].addrr = i;
					symList[keyInST].varLength = 1; //整形占一个int位置 (4Bytes) 
					break;
				}
			}
		}
	}else if( symList[keyInST].varType == "string"){
		int len = getMRYLen(mry);
		for

以上是关于如何自己写一门简单的编程语言(Unix系统编程第一次实验 / U language 解释型语言)的主要内容,如果未能解决你的问题,请参考以下文章

如何入门编程

转载-如何写代码-编程智慧

UNIX网络编程卷1 第一章 读书笔记。

多进程编程

如何评价 Racket 这门编程语言?

用 C 语言写一个简单的 Unix Shell