自己写编程语言-m语言
一直对技术有很强的兴趣,终于,决定要写自己的语言(m语言)。那就先从最简单的开始:解释执行器。
一套完整的语言包含的肯定不止解释执行器了,还要有编译器和IDE,也就还要有语法高亮、智能提示等,不过还没学会那些,先搞个最基本的解释执行器。
思路如下:
- 定义好希望的语法(基本语句有:顺序执行、if语句、for语句、while语句、系统自有函数定义、用户函数定义、函数调用)
- 找一款词法语法解析器工具,让字符串流变成语法书(AST)
- 编写解释执行器
- 元数据收集
- 变量作用域定义、查找
- 解释执行
先设想我们的m语言语法要怎么牛b啊,比如下面这段demo语法代码:
go 计算标准体重(年龄) { 体重:年龄*3; 体重; } 体重:10; a:10; a:输出(体重); b:25; a:100+10+b; 输出(a); (a==135)-> { a:a+a+a; 输出(a); } else { 输出(b); }; a:1; while a<10 ->{ a:a+2; 输出(a); }; 输出("WHILE OK"); repeat i from 0 to 100 step 10->{ 输出(i); } init->{ 输出("FOR INIT"); } onerror->{ 输出("FOR ERROR"); } finally->{ 输出("FOR FINALLY"); }; 输出('FOR OK'); a:10; 输出(计算标准体重(a));
很显然,第一个语句块是用户函数的定义方式,以"go"字符串为函数定义的开始,接着是常规的函数名称、参数、函数方法块。
剩下的大致上就是顺序执行了,其中穿插着一些循环语句等,repeat循环自定义的比较厉害,好叼。。。感觉。。真的好叼。。。。
每个语句以封号后缀结束、赋值以冒号来标识。
接着来看看基于ANTLR的词法定义:
m.g4:
grammar m;
import basic,function,assignStmt,ifStmt,forStmt,whileStmt;
nomalStmt
:assignStmt
|ifStmt
|forStmt
|whileStmt
;
declarationStmt
:functionDeclare
;
stmt
:nomalStmt LS
|declarationStmt
;
program
: stmt+
;
由于词法语法定义较多,不贴代码了,可以下载代码看全部的(基于ideas/需要安装antlr4插件)
接下来是时候让我们load进demo代码解析成AST树啦:
String code=Utils.readTxtFile("F:\\BaiduYunDownload\\mLanguage(4)\\m_code2.m");//这个是放demo代码的文件 code=code.substring(1);//去掉第一个特殊字符 CharStream is = CharStreams.fromString(code); //antlr对象,读入字符串 mLexer lexer = new mLexer(is); //mLexer是antlr自动生成的一个词法类 CommonTokenStream tokens = new CommonTokenStream(lexer); //antlr对象 mParser parser = new mParser(tokens); //mParser是antlr自动生成的一个此法解析类 mParser.ProgramContext tree=parser.program(); //program是入口规则,根规则 program program= NodeParser.parseProgram(tree); //自己写的NodeParser类,需要一堆自定义的节点类型配合解析整棵AST树 mRuntime runtime=new mRuntime(program); runtime.plainInterpreter(); //解释器执行 System.out.println("");
AST节点的定义:
demo代码构建成AST树的效果图(antlr插件中能看):
转换成为AST树后,剩下的就是编写解释执行器,其实相当于前端编译器。
主要步骤是3步:
- 收集元数据
- 定义变量作用域
- 语句块的解释执行
public void execute(program program) { //1. 先扫描函数定义,收集元数据 collectMetaData(program); //2. 变量作用域 walkAST4Variables(program); //3. 解释执行代码 runCode(program); }
1. 收集元数据,其实就是对自定义函数的收集,统一放到一个Dictionary里,以便到时候引用到了执行语句块(和参数的传递)
private void collectMetaData(program program) { for(com.mckay.language.m.core.nodes.m.stmt stmt:program.stmts) if(stmt.declarationStmt!=null) this.userDefinedFunctionSymbols.defineMethod(stmt.declarationStmt.functionDeclare.functionIdentifier.getIdentifier(), stmt.declarationStmt.functionDeclare); } public class UserDefinedFunctionSymbols { private Dictionary<String, functionDeclare> methods=new Hashtable<>(); public functionDeclare getMethod(String identifier) { return methods.get(identifier); } public void defineMethod(String identifier, functionDeclare ast) { methods.put(identifier, ast); } }
functionDeclare是具体的node,属于AST中众多节点类型中的一种,代表函数声明节点。
2. 定义变量作用域,由于存在函数(自定义函数、系统自带函数),因此需要有变量Scope的概念,存在局部变量覆盖全局变量现象
private void walkAST4Variables(program program) { program.VariableSymbols=globalVariableSymbol; for(com.mckay.language.m.core.nodes.m.stmt stmt:program.stmts) { stmt.VariableSymbols=program.VariableSymbols; if(stmt.declarationStmt!=null) { stmt.declarationStmt.VariableSymbols=stmt.VariableSymbols; VarWalker.walk(stmt.declarationStmt); } if(stmt.nomalStmt!=null) { stmt.nomalStmt.VariableSymbols=stmt.VariableSymbols; VarWalker.walk(stmt.nomalStmt); } } } public class VariableSymbol { private Dictionary<String, Variable> variables=new Hashtable<>(); private VariableSymbol parentVariableSymbol; public void setParentVariableSymbol(VariableSymbol parentVariableSymbol) { this.parentVariableSymbol=parentVariableSymbol; } public void defineVariable(String name, Variable variable) { variables.put(name, variable); } public void setValue(String name, Object value) { Variable variable=getVariable(name); variable.Value=value; } public Object getValue(String name) { Variable variable=getVariable(name); return variable.Value; } private Variable getVariable(String name) { List<String> keys=Collections.list(variables.keys()); if(keys.contains(name)) return this.variables.get(name); if(this.parentVariableSymbol!=null) return this.parentVariableSymbol.getVariable(name); throw new RuntimeException("变量未定义"); } }
当局部变量中没有找到本地变量定义时,会根据parent关联向上找变量,直到为null。
3. 语句块的解释执行,这个可以说是最容易理解的地方了
private void runCode(program program) { StmtExecutor executor=new StmtExecutor(this); for(com.mckay.language.m.core.nodes.m.stmt stmt:program.stmts) if(stmt.nomalStmt!=null) executor.execute(stmt.nomalStmt); }
StmtExecutor.execute(nomalStmt)会调用一系列子语句,如下图就一图就懂:
如上图中,针对expression是调用calc的,一堆calc,expression中套expression。
system built-in函数的定义,是通过NativeMethodNode.setCode来标识的,比如当前实现的code为OUTPUT,功能如下:System.out.print/Console.Write()
第一个红框是native node中判断code是哪个system built-in函数的编码代号
第二个红框是对应built-in函数的java语句执行。
demo m代码对应的解释执行输出:
10 135 405 3 5 7 9 11 WHILE OK FOR INIT 0 10 20 30 40 50 60 70 80 90 100 FOR FINALLY FOR OK 30 ok
代码下载(基于java)
相关文章
- R语言分类算法之随机森林
- R语言作为BI中ETL的工具
- PageRank算法R语言实现
- R软件导入数据_r语言怎么导入数据_R软件导入数据
- Go语言_反射篇
- 单语言精简版win10下中文网页无法正常
- 17、屏幕适配,多语言支持,手机类型适配
- Atitit 编程语言的block概念 目录 1. 匿名block 1 1.1. 函数块 方法快 1 1.2. Sp udf块 1 2. 实现block的方式 1 2.1. 早期的语言大多是采用en
- Atitit 编程语言语言规范总结 目录 1. 语言规范3 2. Types3 2.1.1. Primitive types3 2.1.2. Compound types4 3. State
- Atitit 编程语言的分类 v2 目录 1.1. 基于代数划分 第一代。。。第三代。。4gl。。5gl自然语言1 1.2. 按照编程范式分类 . 命令式语言 .函数式语言...逻辑式语言
- Atitti 跨语言异常的转换抛出 java js
- Atitit.5gl 第五代语言编程语言 PROLOG教程 人工智能语言的标准 与实现
- Atitit.5gl 第五代语言编程语言 PROLOG教程 人工智能语言的标准 与实现
- atitit.面向过程的编程语言异常处理 c语言 asp vbs 的try catch 实现
- atitit.编程语言 程序语言 的 工具性 和 材料性 双重性 and 语言无关性 本质
- Python编程语言学习:python语言中快速查询python自带模块&函数的用法及其属性方法、如何查询某个函数&关键词的用法、输出一个类或者实例化对象的所有属性和方法名之详细攻略
- 使用Qt和C语言或者C++语言实现十、二、八、十六进制之间的转换(两种方法)
- Go语言微服务开发框架:Go chassis
- 【编程语言】Go 语言简明教程
- java科学和艺术语言 第六章 课后编程
- 用通俗易懂的语言解释IOC
- C 语言 链表的创建与打印