内置命令:
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 许可下分发的,也可以在您的专有项目中随意使用它。