自己寫編程語言-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)

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM