博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
自己写编程语言-m语言
阅读量:5953 次
发布时间:2019-06-19

本文共 5394 字,大约阅读时间需要 17 分钟。

一直对技术有很强的兴趣,终于,决定要写自己的语言(m语言)。那就先从最简单的开始:解释执行器。

一套完整的语言包含的肯定不止解释执行器了,还要有编译器和IDE,也就还要有语法高亮、智能提示等,不过还没学会那些,先搞个最基本的解释执行器。

思路如下:

  1. 定义好希望的语法(基本语句有:顺序执行、if语句、for语句、while语句、系统自有函数定义、用户函数定义、函数调用)
  2. 找一款词法语法解析器工具,让字符串流变成语法书(AST)
  3. 编写解释执行器
    1. 元数据收集
    2. 变量作用域定义、查找
    3. 解释执行

先设想我们的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步:

  1. 收集元数据
  2. 定义变量作用域
  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
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
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
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)

 

转载地址:http://cmaxx.baihongyu.com/

你可能感兴趣的文章
文件下载_中文乱码:"Content-disposition","attachment; filename=中文名
查看>>
HBase 笔记3
查看>>
2017.11.23 display fun --STM8
查看>>
深入学习jQuery选择器系列第八篇——过滤选择器之伪子元素选择器
查看>>
一个关于log4j的悲伤的故事
查看>>
PCA
查看>>
ajax上传文件
查看>>
java中通过绝对路径将图片存入数据库
查看>>
ConcurrentHashMap(Java8)源码分析
查看>>
Python文件处理之文件指针(四)
查看>>
Numpy用法详解
查看>>
DataGridView在vb.net中的操作技巧
查看>>
PMP考试冲刺进行中。。。
查看>>
大换血的代价
查看>>
Learn in FCC(3)
查看>>
RunLoop--
查看>>
chrome 2行换行省略号 ... text-ellipse
查看>>
C语言第四次作业
查看>>
Java学习-集合的理解
查看>>
iOS验证码倒计时(GCD实现)
查看>>