• 自己写编程语言-m语言


    一直对技术有很强的兴趣,终于,决定要写自己的语言(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<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)

  • 相关阅读:
    激活OFFICE2010时,提示choice.exe不是有效的win32程序
    Redis 学习之持久化机制、发布订阅、虚拟内存
    Redis 学习之事务处理
    Redis 学习之主从复制
    Redis 学习之常用命令及安全机制
    Redis 学习之数据类型
    Redis 学习之简介及安装
    Tomcat 虚拟主机配置
    mysql学习之权限管理
    mysql学习之主从复制
  • 原文地址:https://www.cnblogs.com/aarond/p/m_language.html
Copyright © 2020-2023  润新知