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

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

思路如下:

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

先设想我们的m语言语法要怎么牛b啊,比如下面这段demo语法代码:

  1. go 计算标准体重(年龄)
  2. {
  3. 体重:年龄*3;
  4. 体重;
  5. }
  6. 体重:10;
  7. a:10;
  8. a:输出(体重);
  9. b:25;
  10. a:100+10+b;
  11. 输出(a);
  12. (a==135)->
  13. {
  14. a:a+a+a;
  15. 输出(a);
  16. }
  17. else
  18. {
  19. 输出(b);
  20. };
  21. a:1;
  22. while a<10 ->{
  23. a:a+2;
  24. 输出(a);
  25. };
  26. 输出("WHILE OK");
  27. repeat i from 0 to 100 step 10->{
  28. 输出(i);
  29. }
  30. init->{
  31. 输出("FOR INIT");
  32. }
  33. onerror->{
  34. 输出("FOR ERROR");
  35. }
  36. finally->{
  37. 输出("FOR FINALLY");
  38. };
  39. 输出(\'FOR OK\');
  40. a:10;
  41. 输出(计算标准体重(a));

 很显然,第一个语句块是用户函数的定义方式,以”go”字符串为函数定义的开始,接着是常规的函数名称、参数、函数方法块。

剩下的大致上就是顺序执行了,其中穿插着一些循环语句等,repeat循环自定义的比较厉害,好叼。。。感觉。。真的好叼。。。。

每个语句以封号后缀结束、赋值以冒号来标识。

接着来看看基于ANTLR的词法定义:

m.g4:

  1. grammar m;
  2.  
  3. import basic,function,assignStmt,ifStmt,forStmt,whileStmt;
  4.  
  5. nomalStmt
  6. :assignStmt
  7. |ifStmt
  8. |forStmt
  9. |whileStmt
  10. ;
  11. declarationStmt
  12. :functionDeclare
  13. ;
  14. stmt
  15. :nomalStmt LS
  16. |declarationStmt
  17. ;
  18.  
  19. program
  20. : stmt+
  21. ;  

 由于词法语法定义较多,不贴代码了,可以下载代码看全部的(基于ideas/需要安装antlr4插件)

接下来是时候让我们load进demo代码解析成AST树啦:

  1. String code=Utils.readTxtFile("F:\\BaiduYunDownload\\mLanguage(4)\\m_code2.m");//这个是放demo代码的文件
  2. code=code.substring(1);//去掉第一个特殊字符
  3.  
  4.  
  5. CharStream is = CharStreams.fromString(code);                 //antlr对象,读入字符串
  6. mLexer lexer = new mLexer(is);                         //mLexer是antlr自动生成的一个词法类
  7. CommonTokenStream tokens = new CommonTokenStream(lexer);         //antlr对象
  8. mParser parser = new mParser(tokens);                     //mParser是antlr自动生成的一个此法解析类
  9.  
  10. mParser.ProgramContext tree=parser.program();                //program是入口规则,根规则
  11.  
  12. program program= NodeParser.parseProgram(tree);               //自己写的NodeParser类,需要一堆自定义的节点类型配合解析整棵AST树
  13.  
  14. mRuntime runtime=new mRuntime(program);
  15.  
  16. runtime.plainInterpreter();                           //解释器执行
  17.  
  18. System.out.println("");

AST节点的定义:

  

demo代码构建成AST树的效果图(antlr插件中能看):

 

 转换成为AST树后,剩下的就是编写解释执行器,其实相当于前端编译器。

主要步骤是3步:

  1. 收集元数据
  2. 定义变量作用域
  3. 语句块的解释执行 
  1. public void execute(program program) {
  2. //1. 先扫描函数定义,收集元数据
  3. collectMetaData(program);
  4. //2. 变量作用域
  5. walkAST4Variables(program);
  6. //3. 解释执行代码
  7. runCode(program);
  8. }

 

 1. 收集元数据,其实就是对自定义函数的收集,统一放到一个Dictionary里,以便到时候引用到了执行语句块(和参数的传递)

  1. private void collectMetaData(program program) {
  2. for(com.mckay.language.m.core.nodes.m.stmt stmt:program.stmts)
  3. if(stmt.declarationStmt!=null)
  4. this.userDefinedFunctionSymbols.defineMethod(stmt.declarationStmt.functionDeclare.functionIdentifier.getIdentifier(), stmt.declarationStmt.functionDeclare);
  5. }
  6. public class UserDefinedFunctionSymbols {
  7. private Dictionary<String, functionDeclare> methods=new Hashtable<>();
  8. public functionDeclare getMethod(String identifier) {
  9. return methods.get(identifier);
  10. }
  11. public void defineMethod(String identifier, functionDeclare ast) {
  12. methods.put(identifier, ast);
  13. }
  14. }

 

 functionDeclare是具体的node,属于AST中众多节点类型中的一种,代表函数声明节点。

2. 定义变量作用域,由于存在函数(自定义函数、系统自带函数),因此需要有变量Scope的概念,存在局部变量覆盖全局变量现象

  1. private void walkAST4Variables(program program)
  2. {
  3. program.VariableSymbols=globalVariableSymbol;
  4. for(com.mckay.language.m.core.nodes.m.stmt stmt:program.stmts)
  5. {
  6. stmt.VariableSymbols=program.VariableSymbols;
  7. if(stmt.declarationStmt!=null)
  8. {
  9. stmt.declarationStmt.VariableSymbols=stmt.VariableSymbols;
  10. VarWalker.walk(stmt.declarationStmt);
  11. }
  12.  
  13. if(stmt.nomalStmt!=null)
  14. {
  15. stmt.nomalStmt.VariableSymbols=stmt.VariableSymbols;
  16. VarWalker.walk(stmt.nomalStmt);
  17. }
  18. }
  19. }
  20.  
  21.  
  22.  
  23.  
  24. public class VariableSymbol {
  25. private Dictionary<String, Variable> variables=new Hashtable<>();
  26. private VariableSymbol parentVariableSymbol;
  27.  
  28. public void setParentVariableSymbol(VariableSymbol parentVariableSymbol)
  29. {
  30. this.parentVariableSymbol=parentVariableSymbol;
  31. }
  32.  
  33. public void defineVariable(String name, Variable variable) {
  34. variables.put(name, variable);
  35. }
  36.  
  37. public void setValue(String name, Object value)
  38. {
  39. Variable variable=getVariable(name);
  40. variable.Value=value;
  41. }
  42. public Object getValue(String name)
  43. {
  44. Variable variable=getVariable(name);
  45. return variable.Value;
  46. }
  47.  
  48. private Variable getVariable(String name) {
  49. List<String> keys=Collections.list(variables.keys());
  50. if(keys.contains(name))
  51. return this.variables.get(name);
  52.  
  53. if(this.parentVariableSymbol!=null)
  54. return this.parentVariableSymbol.getVariable(name);
  55.  
  56. throw new RuntimeException("变量未定义");
  57. }
  58. }  

 当局部变量中没有找到本地变量定义时,会根据parent关联向上找变量,直到为null。

3. 语句块的解释执行,这个可以说是最容易理解的地方了

  1. private void runCode(program program) {
  2. StmtExecutor executor=new StmtExecutor(this);
  3.  
  4. for(com.mckay.language.m.core.nodes.m.stmt stmt:program.stmts)
  5. if(stmt.nomalStmt!=null)
  6. executor.execute(stmt.nomalStmt);
  7. }

 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代码对应的解释执行输出:

  1. 10
  2. 135
  3. 405
  4. 3
  5. 5
  6. 7
  7. 9
  8. 11
  9. WHILE OK
  10. FOR INIT
  11. 0
  12. 10
  13. 20
  14. 30
  15. 40
  16. 50
  17. 60
  18. 70
  19. 80
  20. 90
  21. 100
  22. FOR FINALLY
  23. FOR OK
  24. 30
  25. ok  

 

代码下载(基于java)

 

版权声明:本文为aarond原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/aarond/p/m_language.html