內建指令:
subst arg
set var ?val?
while cond loop
if cond branch ?cond? ?branch? ?other?
proc name args body
return
break
continue
+, -, *, /, <, >, <=, >=, ==, !=
struct tcl tcl ;
const char * s = "set x 4; puts [+ [* $x 10] 2]" ;
tcl_init ( & tcl );
if ( tcl_eval ( & tcl , s , strlen ( s )) != FERROR ) {
printf ( "%.*sn" , tcl_length ( tcl . result ), tcl_string ( tcl . result ));
}
tcl_destroy ( & tcl );
Tcl 腳本由分號或換行符號分隔的指令組成。 Commands 又由用空格分隔的單字組成。為了使空格成為單字的一部分,可以使用雙引號或大括號。
該語言的一個重要部分是命令替換,即方括號內命令的結果會作為外部命令的一部分返回,例如puts [+ 1 2]
。
該語言的唯一資料類型是字串。儘管它可能會使數學運算變得複雜,但它為建立您自己的 DSL 以增強語言開闢了廣闊的道路。
任何符號都可以是單字的一部分,但以下特殊符號除外:
r
、 n
、分號或 EOF - 用來分隔指令Partcl 對這些 char 類別有特殊的輔助函數:
static int tcl_is_space(char c);
static int tcl_is_end(char c);
static int tcl_is_special(char c, int q);
tcl_is_special
行為會因引用模式( q
參數)而異。在引號的字串大括號內,分號和行尾符號失去其特殊意義並成為常規的可列印字元。
Partcl 詞法分析器在一個函數中實作:
int tcl_next(const char *s, size_t n, const char **from, const char **to, int *q);
tcl_next
函數尋找字串s
中的下一個標記。 from
和to
設定為指向標記開始/結束, q
表示引用模式,如果滿足"
則更改。
特殊的宏tcl_each(s, len, skip_error)
可用於迭代字串中的所有標記。如果skip_error
為假-循環在字串結束時結束,否則如果發現語法錯誤,循環可以提前結束。它允許“驗證”輸入字串而不對其進行評估,並檢測何時讀取了完整的命令。
Tcl 使用字串作為主要資料類型。當 Tcl 腳本被執行時,許多字串會建立、處理或修改。在嵌入式系統中,記憶體管理可能很複雜,因此所有具有Tcl 值的操作都被移至獨立的函數中,這些函數可以輕鬆重寫以優化某些部分(例如,使用字串池、自訂記憶體分配器、快取數字或列表值來增加效能等)。
/* Raw string values */
tcl_value_t *tcl_alloc(const char *s, size_t len);
tcl_value_t *tcl_dup(tcl_value_t *v);
tcl_value_t *tcl_append(tcl_value_t *v, tcl_value_t *tail);
int tcl_length(tcl_value_t *v);
void tcl_free(tcl_value_t *v);
/* Helpers to access raw string or numeric value */
int tcl_int(tcl_value_t *v);
const char *tcl_string(tcl_value_t *v);
/* List values */
tcl_value_t *tcl_list_alloc();
tcl_value_t *tcl_list_append(tcl_value_t *v, tcl_value_t *tail);
tcl_value_t *tcl_list_at(tcl_value_t *v, int index);
int tcl_list_length(tcl_value_t *v);
void tcl_list_free(tcl_value_t *v);
請記住, ..._append()
函數必須釋放 tail 參數。此外, tcl_string()
傳回的字串並不表示會被改變或快取。
在預設實作中,列表被實作為原始字串,在每個項目周圍添加一些轉義(大括號)。這是一個簡單的解決方案,也減少了程式碼,但在某些特殊情況下,轉義可能會出錯,並且將傳回無效結果。
一種特殊型別struct tcl_env
用來保存評估環境(一組函數)。解釋器為每個使用者定義的過程建立一個新環境,每個解釋器也有一個全域環境。
與環境相關的函數只有3個。一個創造一個新環境,另一個尋找一個變數(或創建一個新變數),最後一個破壞環境及其所有變數。
這些函數使用 malloc/free,但可以輕鬆重寫以使用記憶體池。
static struct tcl_env *tcl_env_alloc(struct tcl_env *parent);
static struct tcl_var *tcl_env_var(struct tcl_env *env, tcl_value_t *name);
static struct tcl_env *tcl_env_free(struct tcl_env *env);
變數被實作為單鍊錶,每個變數都是一對值(名稱+值)和指向下一個變數的指標。
Partcl 解譯器是一個簡單的結構struct tcl
它保存目前環境、可用指令陣列和最後的結果值。
解釋器邏輯圍繞著兩個函數 - 求值和替換。
替代:
$
開頭 - 建立一個臨時命令[set name]
並評估它。在 Tcl 中$foo
只是[set foo]
的快捷方式,它會傳回目前環境中「foo」變數的值。[
開頭 - 計算方括號內的內容並傳回結果。{foo bar}
) - 按原樣返回,只是不帶大括號。評估:
TCMD
) - 然後找到合適的命令(列表中的第一個單字)並呼叫它。命令從哪裡獲取?最初,Partcl 解釋器一開始沒有指令,但可以透過呼叫tcl_register()
來新增指令。
每個命令都有一個名稱、參數數量(應採用多少個參數 - 解釋器在呼叫命令之前檢查它,對可變參數使用零參數)和實際實現該命令的 C 函數指標。
“set” - tcl_cmd_set
,為變數(如果有)賦值並傳回目前變數值。
"subst" - tcl_cmd_subst
,在參數字串中進行指令替換。
"puts" - tcl_cmd_puts
,將參數列印到標準輸出,後面跟著換行符號。可以使用#define TCL_DISABLE_PUTS
來停用此命令,這對於沒有「stdout」的嵌入式系統非常方便。
“proc” - tcl_cmd_proc
,建立一個新命令,將其附加到目前解釋器命令清單中。這就是使用者定義命令的建構方式。
"if" - tcl_cmd_if
,執行簡單的if {cond} {then} {cond2} {then2} {else}
。
"while" - tcl_cmd_while
,執行 while 迴圈while {cond} {body}
。可以在迴圈內使用「break」、「continue」或「return」來控制流程。
各種數學運算被實作為tcl_cmd_math
,但如果您的腳本不需要它們,您也可以停用它們(如果您想使用 Partcl 作為命令 shell,而不是作為程式語言)。
所有來源都在一個檔案tcl.c
中。它可以用作獨立的解釋器,也可以作為單一檔案庫包含在內(然後您可能需要將其重新命名為 tcl.h)。
使用 clang 執行測試並計算覆蓋率。只需執行“make test”即可完成。
程式碼使用 clang-format 進行格式化,以保持乾淨且可讀取的編碼風格。請也為拉取請求運行它。
程式碼是在 MIT 許可下分發的,也可以在您的專有專案中隨意使用它。