Mufi 語言是一種兼具簡潔性與功能性的程式語言,支援多種資料型別、控制流程、迴圈、函式以及類別等特性。其語法設計類似 C 語言,並允許開發者使用 C 語言撰寫原生函式擴充其功能。本文將深入探討 Mufi 語言的核心特性,包含資料型別、控制流程、迴圈、函式與類別,並詳細說明如何使用 C 語言實作原生函式,以及 Mufi 的建置模式與繼承機制。此外,本文也將分析 Mufi 的 Bytecode 執行流程,以幫助開發者更深入地理解 Mufi 語言的運作方式。

程式結構

Mufi 的程式結構相對簡單,主要由變數宣告、指定和運算組成。以下是 Mufi 程式的一個基本結構:

var x = 10;
var y = 3.14;
var name = "Mufi";

// 進行運算和輸出

這個結構可以根據具體需求進行擴充套件和修改。

內容解密:

Mufi 的變數宣告和資料型別是理解這種語言的基礎。透過掌握這些基礎知識,可以更好地使用 Mufi 進行程式設計和開發。

  flowchart TD
    A[變數宣告] --> B[資料型別]
    B --> C[整數]
    B --> D[浮點數數]
    B --> E[字串]
    C --> F[運算]
    D --> F
    E --> F
    F --> G[輸出]

圖表翻譯:

這個流程圖展示了 Mufi 的變數宣告和資料型別之間的關係。變數宣告是程式的基礎,資料型別決定了變數的內容和使用方式。整數、浮點數數和字串是 Mufi 支援的三種基本資料型別,透過這些資料型別可以進行各種運算和輸出。

Mufi 程式語言基礎

Mufi 是一種具有豐富特性的程式語言,支援多種資料型別、控制流程、迴圈、函式和類別等。以下是 Mufi 的一些基礎特性:

資料型別

Mufi 支援多種資料型別,包括整數、浮點數數和布林值(bool)。其中,布林值可以是 true 或 false。例如:

var integer = 10;
print integer;  // 輸出: 10

此外,Mufi 也支援字串(string),但與其他語言不同,Mufi 的字串是物件,需要動態組態和垃圾回收。

控制流程

Mufi 的控制流程類似於 C 語言,使用 if 陳述式來控制程式的流程。if 陳述式需要使用括號括住條件。例如:

if ((6 > 7) and (9 < 10)) {
  print "true";
} else {
  print "false";
}
// 輸出: false

Mufi 也支援邏輯運運算元和條件運運算元。

迴圈

Mufi 支援兩種迴圈:while 和 for。while 迴圈需要一個條件,當條件為 true 時,迴圈內的程式碼會被執行。for 迴圈則需要一個初始化表示式、條件和遞增或遞減表示式。例如:

var count = 0;
while (count < 5) {
  for (var i = 0; i < count; i = i + 1) {
    print i;
  }
  count = count + 1;
}

函式

Mufi 的函式是物件,使用 fun 關鍵字宣告。函式不需要指定傳回型別,使用 return 關鍵字傳回值,如果沒有傳回值,則傳回 nil。函式也可以是遞迴的,例如:

fun fib(n) {
  if (n < 2) return n;
  return fib(n - 2) + fib(n - 1);
}
print fib(10);  // 輸出: 55

類別

Mufi 的類別使用 class 關鍵字宣告,類別內的方法不需要使用 fun 關鍵字。類別的欄位可以在 init 方法中定義,使用 self 關鍵字存取。例如:

class Foo {
  init(a, b) {
    self.a = a;
    self.b = b;
  }

  update(factor) {
    self.a = self.a * factor;
    self.b = self.b * factor;
  }
}
var foo = Foo(3, 4);
foo.update(5);
print foo.a;  // 輸出: 15
print foo.b;  // 輸出: 20

圖表翻譯:

  classDiagram
  class Foo {
    - a: int
    - b: int
    + init(a: int, b: int)
    + update(factor: int)
  }
  Foo ..> Foo : init
  Foo ..> Foo : update

以上是 Mufi 的一些基礎特性,包括資料型別、控制流程、迴圈、函式和類別等。Mufi 的語法和 C 語言類似,但也有其自身的特點和限制。

繼承在 Mufi

Mufi 的最後一個主要特性是類別繼承,使用 < 運運算元來實作,後面跟著類別名稱(一個類別不能繼承自己)。要存取繼承類別的繫結方法,需要使用 super 關鍵字。以下是 Mufi 中類別繼承的例子:

class Foo {
  foo() {
    print "This is inherited from foo";
  }
}

class Bar < Foo {
  bar() {
    print "This is bar";
    super.foo();
  }
}
var b = Bar();
b.bar();
// 輸出:
// This is bar
// This is inherited from foo

Mufi 的建置模式

完成 Mufi 的介紹後,現在來討論兩種不同的建置模式。當我們提到 debug 和 release 模式時,你可能會想到 Rust 編譯器的最佳化模式。然而,在 Mufi 中情況並非如此。沒有額外的最佳化。這兩種模式的差異在於 compiler/common.h 中的定義,讓我們來看看:

// 所有共同的匯入和前處理器巨集定義在這裡
#ifndef mufi_common_h
#define mufi_common_h
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>

#define DEBUG_PRINT_CODE
#define DEBUG_TRACE_EXECUTION
#define DEBUG_STRESS_GC
#define DEBUG_LOG_GC
#define UINT8_COUNT (UINT8_MAX + 1)

#endif

這是 compiler/common.h 在 debug 模式下的內容,因為所有的 DEBUG_* 巨集都被定義了,並且會在編譯器執行程式時使用。如果我們不想看到不同的除錯記錄,需要取消定義這些巨集,可以在 release 模式下看到:

#ifndef mufi_common_h
#define mufi_common_h
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#define DEBUG_PRINT_CODE
#define DEBUG_TRACE_EXECUTION
#define DEBUG_STRESS_GC
#define DEBUG_LOG_GC
#define UINT8_COUNT (UINT8_MAX + 1)
#endif
// 在生產環境中,我們想要關閉這些除錯功能
#undef DEBUG_TRACE_EXECUTION
#undef DEBUG_PRINT_CODE
#undef DEBUG_STRESS_GC
#undef DEBUG_LOG_GC

在 C 中建立原生函式

原生函式是內建於 Mufi 編譯器中的函式,這一節將以 C 語言編寫,但可用於 Mufi。每個由玄貓定義的原生函式:

Value fooNative(int argCount, Value* args);

整數 argCount 代表函式中的引數數量,動態陣列 args 代表 Mufi 值的陣列。然後,為了將我們的原生函式新增到編譯器中,我們需要前往 compiler/vm.c,在 initVM() 函式中初始化虛擬機器。我們將使用 defineNative() 函式來定義不同的原生函式。

但是,我們尚未編寫任何原生函式,因此,為了開始,我們將建立新的檔案 compiler/mufi_std_c.ccompiler/mufi_std_c.h。為了開始,我們將編寫一些簡單的算術函式,然後再開始編寫更複雜的函式,這些函式將在標頭檔 compiler/mufi_std_c.h 中定義:

// 包含在 C 中編寫的原生函式
#ifndef mufi_std_c_h
#define mufi_std_c_h
#include "common.h"
#include "value.h"

// 將兩個數字相加
Value csumNative(int argCount, Value* args);

// 將兩個數字相乘
Value cprodNative(int argCount, Value* args);

// 將兩個數字相除
Value cdivNative(int argCount, Value* args);

#endif

內容解密:

上述程式碼定義了三個原生函式:csumNativecprodNativecdivNative,分別用於將兩個數字相加、相乘和相除。這些函式將在 compiler/mufi_std_c.c 中實作。

圖表翻譯:

  graph LR
    A[Mufi] -->|繼承|> B[Foo]
    B -->|繼承|> C[Bar]
    C -->|呼叫|> D[super.foo()]
    D -->|印出|> E["This is inherited from foo"]

這個圖表展示了 Mufi 的繼承關係和方法呼叫過程。

基礎資料型別與運運算元

在實作 Mufi-Lang 的過程中,我們需要定義基礎的資料型別和運運算元。首先,讓我們來瞭解一下 Value 的結構。

typedef enum {
    VAL_BOOL,
    VAL_NIL,
    VAL_INT,
    VAL_DOUBLE,
    VAL_OBJ,
} ValueType;

typedef struct {
    ValueType type;
    union {
        bool boolean;
        double num_double;
        int num_int;
        Obj* obj;
    } as;
} Value;

這個結構體使用了一個 enum 來定義不同的資料型別,並使用一個 union 來儲存實際的資料值。這樣的設計使得我們可以使用相同的 Value 結構體來代表不同的資料型別。

型別檢查與轉換

為了方便地檢查和轉換 Value 的型別,我們定義了一些宏。

#define IS_BOOL(value) ((value).type == VAL_BOOL)
#define IS_NIL(value) ((value).type == VAL_NIL)
#define IS_INT(value) ((value).type == VAL_INT)
#define IS_DOUBLE(value) ((value).type == VAL_DOUBLE)
#define IS_OBJ(value) ((value).type == VAL_OBJ)

#define AS_OBJ(value) ((value).as.obj)
#define AS_BOOL(value) ((value).as.boolean)
#define AS_INT(value) ((value).as.num_int)
#define AS_DOUBLE(value) ((value).as.num_double)

#define BOOL_VAL(value) ((Value){VAL_BOOL, {.boolean = value}})
#define NIL_VAL ((Value){VAL_NIL, {.num_int = 0}})
#define INT_VAL(value) ((Value){VAL_INT, {.num_int = value}})
#define DOUBLE_VAL(value) ((Value){VAL_DOUBLE, {.num_double = value}})
#define OBJ_VAL(object) ((Value){VAL_OBJ, {.obj = (Obj*)object}})

這些宏使得我們可以輕鬆地檢查 Value 的型別,並將其轉換為對應的 C 資料型別。

錯誤處理

在實作 Mufi-Lang 的過程中,我們需要處理錯誤的情況。為此,我們定義了一個 error 函式,該函式接受一個錯誤訊息,並傳回一個 NIL_VAL

static Value error(const char* message) {
    fprintf(stderr, "%s\n", message);
    return NIL_VAL;
}

這個函式會將錯誤訊息輸出到標準錯誤流中,並傳回一個 NIL_VAL

基礎運運算元

現在,我們可以開始實作一些基礎的運運算元。例如,我們可以實作加法運運算元。

static Value add(Value a, Value b) {
    if (IS_INT(a) && IS_INT(b)) {
        return INT_VAL(AS_INT(a) + AS_INT(b));
    } else if (IS_DOUBLE(a) && IS_DOUBLE(b)) {
        return DOUBLE_VAL(AS_DOUBLE(a) + AS_DOUBLE(b));
    } else {
        return error("Unsupported operand types for +");
    }
}

這個函式會檢查兩個運算元的型別,如果都是整數或都是浮點數數,則進行加法運算並傳回結果。如果型別不匹配,則傳回一個錯誤訊息。

Mufi語言中的原生函式實作

在Mufi語言中,實作原生函式是為了提供基本的運算功能,例如加法、乘法和除法。在這個章節中,我們將實作這些原生函式,包括csumNativecprodNativecdivNative

csumNative函式

csumNative函式用於實作加法運算。它接受兩個引數,分別是第一個數值和第二個數值。函式首先檢查引數的數量是否正確,如果不正確,則傳回錯誤資訊。

Value csumNative(int argCount, Value* args){
    if (argCount != 2){
        return error("csum() only expects two arguments.");
    }
    Value first = args[0];
    Value second = args[1];
    // ...
}

然後,函式使用switch陳述式來處理不同的資料型別。如果第一個數值是整數,則檢查第二個數值是否也是整數或雙精確度浮點數數。如果是,則進行相應的加法運算。

switch (first.type)
{
case VAL_INT:{
    if(IS_INT(second)){
        int sum = AS_INT(first) + AS_INT(second);
        return INT_VAL(sum);
    } else if (IS_DOUBLE(second)){
        double sum = (double)AS_INT(first) + AS_DOUBLE(second);
        return DOUBLE_VAL(sum);
    } else {
        return error("Second argument in csum() cannot be added to integer.");
    }
    break;
}
// ...
}

cprodNative函式

cprodNative函式用於實作乘法運算。它的實作方式與csumNative函式類似,主要區別在於乘法運算的實作。

Value cprodNative(int argCount, Value* args){
    if (argCount != 2){
        return error("cprod() only expects two arguments.");
    }
    Value first = args[0];
    Value second = args[1];
    // ...
}

cprodNative函式中,使用if-else陳述式來檢查第一個數值和第二個數值的型別,並進行相應的乘法運算。

if(IS_INT(second)){
    int prod = AS_INT(first) * AS_INT(second);
    return INT_VAL(prod);
} else if (IS_DOUBLE(second)){
    double prod = (double)AS_INT(first) * AS_DOUBLE(second);
    return DOUBLE_VAL(prod);
} else {
    return error("Second argument in cprod() cannot be multiplied to integer.");
}

cdivNative函式

cdivNative函式用於實作除法運算。它的實作方式比csumNativecprodNative函式更簡單,因為它只需要檢查第一個數值和第二個數值是否都是數字。

Value cdivNative(int argCount, Value* args){
    if (argCount != 2){
        return error("cdiv() only expects two arguments.");
    }
    Value first = args[0];
    Value second = args[1];
    // ...
}

cdivNative函式中,使用if陳述式來檢查第一個數值和第二個數值是否都是數字,如果是,則進行除法運算。

if((IS_INT(first) || IS_DOUBLE(first)) && (IS_INT(second) || IS_DOUBLE(second))){
    double quotient = AS_DOUBLE(first) / AS_DOUBLE(second);
    return DOUBLE_VAL(quotient);
} else {
    return error("Type not supported in cdiv() cannot be divided.");
}

定義原生函式

為了使原生函式在Mufi語言中可用,需要在initVM函式中定義它們。

void initVM() {
    // ...
    defineNative("csum", csumNative);
    defineNative("cprod", cprodNative);
    defineNative("cdiv", cdivNative);
}

測試原生函式

可以使用Mufi語言的REPL或建立一個指令碼來測試原生函式。例如,可以在REPL中輸入以下程式碼來測試csumNative函式。

csum 1 2

這將輸出3,表示12的和。

圖表翻譯:

  graph LR
    A[Mufi語言] --> B[原生函式]
    B --> C[csumNative]
    B --> D[cprodNative]
    B --> E[cdivNative]
    C --> F[加法運算]
    D --> G[乘法運算]
    E --> H[除法運算]

這個圖表展示了Mufi語言中的原生函式,包括csumNativecprodNativecdivNative,以及它們所實作的運算。

執行 MuFi 指令及 Bytecode 分析

在 MuFi 的指令環境中,我們可以看到版本號為 0.1.0(Baloo Release),並且我們輸入了幾個指令以測試 MuFi 的功能。以下是對這些指令的分析:

指令 1: print csum(40.4, 30)

  • 功能: 這個指令呼叫 csum 函式,傳入引數 40.430,然後印出結果。
  • 結果: 70.4
  • 分析: csum 函式很可能是一個計算兩個數字之和的函式,因此 40.4 + 30 = 70.4

指令 2: print cprod(30, 0.2)

  • 功能: 這個指令呼叫 cprod 函式,傳入引數 300.2,然後印出結果。
  • 結果: 6
  • 分析: cprod 函式很可能是一個計算兩個數字之積的函式,因此 30 * 0.2 = 6

指令 3: print cdiv(3.4, 0.5)

  • 功能: 這個指令呼叫 cdiv 函式,傳入引數 3.40.5,然後印出結果。
  • 結果: 6.8
  • 分析: cdiv 函式很可能是一個計算兩個數字之商的函式,因此 3.4 / 0.5 = 6.8

Bytecode 分析

MuFi 的 Bytecode 運作如下:

  1. OP_GET_GLOBAL: 取得全域變數或函式。
  2. OP_CONSTANT: 載入常數值。
  3. OP_CALL: 呼叫函式。
  4. OP_PRINT: 印出結果。

例如,對於第一個指令 print csum(40.4, 30),Bytecode 的運作步驟是:

  • 0000: 取得全域函式 csum
  • 00020004: 載入常數值 40.430
  • 0006: 呼叫 csum 函式,並將結果印出。

這些指令和 Bytecode 分析展示了 MuFi 如何執行基本的算術運算和函式呼叫。瞭解 MuFi 的指令集和 Bytecode 結構可以幫助開發者更好地使用這個平臺進行程式設計和最佳化。

圖表翻譯:

  flowchart TD
    A[開始] --> B[載入 MuFi]
    B --> C[執行指令]
    C --> D[計算 csum]
    D --> E[計算 cprod]
    E --> F[計算 cdiv]
    F --> G[印出結果]
    G --> H[結束]

內容解密:

MuFi 的運作過程涉及載入 MuFi 平臺、執行使用者輸入的指令、進行相應的計算(如加、乘、除),最後印出結果。每一步驟都對應著特定的 Bytecode 運作,例如取得全域變數、載入常數、呼叫函式和印出結果。這些過程展示了 MuFi 如何支援基本的算術運算和函式呼叫,為使用者提供了一個簡單而強大的計算工具。

從底層實作到高階應用的全面檢視顯示,Mufi 語言以其簡潔的程式結構和豐富的特性,展現出輕量級程式語言的獨特魅力。透過分析其變數宣告、資料型別、控制流程、迴圈、函式和類別等核心元素,我們可以發現 Mufi 在兼顧易用性的同時,也具備一定的效能潛力。csum、cprod 和 cdiv 等原生函式的引入,更進一步豐富了 Mufi 的功能,使其能夠處理基本的算術運算。然而,Mufi 目前仍處於早期發展階段,其 debug 和 release 模式的區別主要在於除錯資訊的輸出,效能最佳化方面還有待提升。此外,Mufi 的字串物件需要動態組態和垃圾回收,這在特定場景下可能影響效能表現。展望未來,Mufi 的發展方向應著重於效能調校、標準函式函式庫的擴充以及更完善的錯誤處理機制。玄貓認為,Mufi 語言在嵌入式系統、指令碼編寫等領域具有應用潛力,但仍需持續發展和社群支援才能走向成熟。對於資源有限的專案,優先探索 Mufi 在非效能關鍵環節的應用或許更具效益。接下來的 2-3 年,將是 Mufi 從實驗性專案走向實際應用的關鍵視窗期。