用C++從0開始開發自己的程式語言

amadan發表於2021-09-09

導語

製作程式語言是一項很有趣但也有難度的活,準備徐徐道來,慢慢推進與講解程式語言直譯器的演算法和程式碼。
都說程式設計師的最大理想就是開發出自己的程式語言來,現在,我也開始完成我最大的理想——開發我屬於我自己的程式語言。
閱讀了許多關於自制程式語言的書籍,可略感失望,感覺很多都是使用yacc/lex這些他人寫好的工具生成程式碼,從而製作直譯器,唯一得到的幫助就是直譯器的大概基本原理和結構。

取名記

名字,是一個好專案很重要的一點,我絞盡腦汁思索許久,終於創造出來一個英語短語:BerryMath。為什麼取這個名呢?首先Berry就是漿果,梅的意思,然後Math是數學的意思,翻譯成中文就是數梅語言。因為Berry是我比較喜歡吃的一項水果型別????,並且在程式設計中數學是非常重要的一環,所以取名BerryMath。

使用語言

為了速度和跨平臺性著想,我選擇C++語言進行編寫。

專案連結

專案架構

專案架構圖

圖片描述

專案檔案結構

  • include
    • BerryMath.h
    • interpreter.h
    • value.h
    • ast.h
    • lex.h
  • include
    • BerryMath.cpp
    • interpreter.cpp
    • value.cpp
    • ast.cpp
    • lex.cpp
  • main.cpp

直譯器流程

圖片描述

著手實現

介面檔案編寫

首先建立BerryMath.h和BerryMath.cpp檔案,後期擴充開發等都需要引入該標頭檔案
include/BerryMath.h

#ifndef BERRYMATH_BERRYMATH_H
#define BERRYMATH_BERRYMATH_H

#include "value.h"
#include "lex.h"

namespace BM {

}

#endif //BERRYMATH_BERRYMATH_H

src/BerryMath.cpp

#include "BerryMath.h"

語言記憶體實現

首先,這門語言我設計是動態型別語言,萬物皆物件,所以記憶體部分設計如下:

ObjectNumberStringNullUndefined派生派生派生派生proto屬性是一個hashmap,用於儲存key和value(std::map<std::string,BM::Object*>)擁有double型別用於儲存數擁有std::string型別用於儲存字串ObjectNumberStringNullUndefined

並且,為了記憶體管理,編寫妥善的垃圾回收機制,我們在基類Object中需要用unsigned long型別的linked屬性以儲存引用次數。這樣在執行解構函式時,遍歷所以屬性,linked減一,爾後linked為0的就是沒有任何Object或者後面的Variable類應用,即可刪除了。
接著,為了便於除錯檢視,和後面編寫語言擴充庫時print等函式的編寫,基類Object應當還有toString()成員函式用於返回std::string型別的值, 並有private的print函式用於列印資料,最後過載運算子,使得其可以被ostream類的例項化物件輸出,比如cout。
在這時,我們還要考慮一點:toString轉換有一個小bug:如果object的屬性中有一個屬性或屬性的屬性的值是自己(即迴圈呼叫),就會陷入死迴圈,所以我們還需要一個parent物件儲存父節點,並有一個has成員函式用於判斷某值是否是祖先節點,如果是,就將那個值toString的值為…。
然後呢,我們開始看一些有關屬性讀寫的成員函式。
首先,我們需要一個插入值的函式,insert,需要接受std::string和Object*,這個函式流程主要是:插入屬性值,增加引用計數,將值的parent屬性設為自己。
有set函式,用於寫屬性值,如果沒有該屬性,就insert進去,有就修改,流程基本同insert
有get函式,用於獲得某屬性的值,return Object*型別的值。
還有del函式,用於刪除某屬性,首先找到該屬性的值,然後從proto中刪除,引用計數減一,如果引用計數為0,delete.
最後,解構函式,也與del函式類似,不過是遍歷每一個屬性,刪除每一個屬性罷了。
最後include/value.h、src/value.cpp程式碼如下:
include/value.h

#ifndef BERRYMATH_VALUE_H
#define BERRYMATH_VALUE_H
#include <iostream>
#include <string>
#include <map>
#include <sstream>
using std::string;
using std::map;
typedef unsigned long UL;

namespace BM {
    class Object {
    public:
        Object() : linked(0), parent(nullptr) { }
        bool has(Object*, Object*);
        void set(const string &key, Object *value);
        void insert(string, Object*);
        Object* get(const string &key);
        void del(const string &key);
        Object& operator[](const string &key) { return *get(key); }
        UL links() { return linked; }
        UL bind() { return ++linked; }
        UL unbind() { return --linked; }
        virtual string toString(bool = true, bool = true, string = "");
        virtual Object* copy() {
            auto object = new Object();
            for (auto iter = proto.begin(); iter != proto.end(); iter++) {
                object->set(iter->first, iter->second->copy());
            }
            return object;
        }
        virtual ~Object();
        friend std::ostream& operator<<(std::ostream& o, Object& v) {
            v.print(o);
            return o;
        }
    protected:
        void print(std::ostream&, bool = true);
        UL linked;
        map<string, Object*> proto;
        Object* parent;
    };
    class Number : public Object {
    public:
        Number() : Object(), v(0) { }
        Number(double t) : Object(), v(t) { }
        string toString(bool = true, bool hl = true, string tab = "") {
            string o("");
            std::ostringstream oss;
            oss << v;
            if (hl) o += "

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/2310/viewspace-2823084/,如需轉載,請註明出處,否則將追究法律責任。

相關文章