(lib)Tcl 现代方法:变量

之前有人问到如何从 C 端访问 Tcl 变量,此处做一总结。庶几不失「现代」之名。有关 Tcl_Obj 的维护者资料击此

于是终于回到了主线。《Tcl 现代方法》本来是作为记录 Tcl 现代用法的系列博文,一开始是介绍 C 库的,但是后来 Tcl 脚本的内容反倒更多,结果现在纯粹谈起来 C 库又会导致歧义,因此在标题前加上 lib 标记,以示内容是 C 库而非 Tcl 脚本。

继续阅读

Windows 下配置 Tcl 开发环境

之前写过一篇一样的,不过丢失了,这次再写一次,权当帮助初学 VS 并且对嵌入 Tcl 感兴趣的人。

继续阅读

Tcl 现代方法:你知道几种写类的方法?

本文是从底向上逐步构建其 TclOO 大框架的,如果你更喜欢从顶向上的,可以参考别的教程。

另外,本文只是入门级别,读完本文后再去看文档或者其他教程应该会容易些,毕竟是汉语写的。这里我推荐 Ashok 写的《Tcl Programming for Windows》中的 TclOO 一章。

除去协程,面向对象编程也是现代编程语言不可或缺的一部分。Tcl 在 8.6 之前都没有内建支持面向对象编程,但这不意味着 Tcl 没法 OO——恰恰相反,Tcl 有一大堆 OO 库,我随口就能叫出几个出名的:[incr Tcl]、XOTcl、Snit。这些库各有千秋,有的模仿 C++,有的是原型继承,还有的是模仿 Common Lisp Object System,千姿百态。可能和大家所想不同,这样的「大好」局势并未给 Tcl 带来许多好处:依然有人批评 Tcl 没有 OO 支持;有的人虽然知道有,但批评选择太多让用户无所适从,或者为自己所爱的库口诛笔伐;有的人虽然不在乎别人怎么选择,但很苦恼其他扩展包依赖了自己根本不需要的 OO 库……在这种情况下,Tcl Core Team 站了出来,提出了 TIP 257,由 Donal K. Fellows 主要实现。这套面向对象机制起名叫做 TclOO,并随 Tcl 本身提供。TclOO 的设计十分灵活,可以以此为基础重新制作 [incr Tcl] 等库,让大家其乐融融地生活在一起。

思考一下,开源系统的桌面环境是不是也是这样呢?

继续阅读

#tcl 上删除 ijchain 的名字

Tcl Chatroom 一般是用 TkChat 交流的,不过有的时候嫌麻烦就直接上 freenode 的 #tcl 了。但是,那个 ijchain 真的非常烦人,怎么办呢?如果你用 XChat,就可以试试下面的方法。

继续阅读

Tcl 现代方法:协程(coroutine)

协程已经是现代语言的必备要素了。Tcl 的协程仿效的是 Lua,在 8.6 版本中引入。(感谢 NRE 引擎!)

 

继续阅读

Fossil 的分支处理

MemMaster 的开发是用 Fossil 做版本管理的。最近需要分支开发,于是研究了一下 Fossil 的分支处理。(但是在研究过程中对我的仓库造成了永久性不可恢复的损害……)

继续阅读

Tcl 现代方法:TclHttpd 入门

TclHttpd 是 Tcl 界人尽皆知的有名软件,功能强大,易于使用。在其原作者不再维护之后,Cliff 和 Sean 于最近接手维护,使之支持最新的 8.6。

继续阅读

Tcl 现代方法:tailcall

Tailcall 是 Tcl 8.6 引入的新功能。功能如其名,本来是用来实现尾调用的。

继续阅读

Tcl 现代方法:eval 方法大全

#include <tcl.h>

/**
 * puts [set a [expr {20 * 30}]
 */
void EvalString(Tcl_Interp *interp) {
    Tcl_Obj *result;
    char script[] = "set a [expr {20 * 30}]";
    
    if (Tcl_Eval(interp, script) != TCL_OK) {
        fprintf(stderr, "Error was: %s\n", Tcl_GetStringResult(interp));
        return;
    }
    result = Tcl_GetObjResult(interp);
    printf("%s\n", Tcl_GetStringFromObj(result, NULL));
}

/**
 * puts [set a [expr {30 * 30}]]
 */
void EvalStringObj(Tcl_Interp *interp) {
    Tcl_Obj *script;
    Tcl_Obj *result;
    
    script = Tcl_NewStringObj("set a [expr {30 * 30}]", -1);
    Tcl_IncrRefCount(script);
    Tcl_EvalObjEx(interp, script, 0);
    result = Tcl_GetObjResult(interp);
    printf("%s\n", Tcl_GetStringFromObj(result, NULL));
}

/**
 * puts {hello world}
 */
void EvalListObj(Tcl_Interp *interp) {
    Tcl_Obj *cmd;
    cmd = Tcl_NewObj();
    Tcl_ListObjAppendElement(interp, cmd, Tcl_NewStringObj("puts", -1));
    Tcl_ListObjAppendElement(interp, cmd, Tcl_NewStringObj("hello world", -1));
    Tcl_IncrRefCount(cmd);
    Tcl_EvalObjEx(interp, cmd, 0);
    Tcl_DecrRefCount(cmd);
}

/**
 * puts [set a 32]
 */
void Eval(Tcl_Interp *interp) {
    int i;
    Tcl_Obj *scriptArr[3];
    Tcl_Obj *resultObj;
    int result;
    
    scriptArr[0] = Tcl_NewStringObj("set", 3);
    scriptArr[1] = Tcl_NewStringObj("a", 1);
    scriptArr[2] = Tcl_NewIntObj(32);
    for (i = 0; i < 3; ++i) {
        Tcl_IncrRefCount(scriptArr[i]);
    }
    Tcl_EvalObjv(interp, 3, scriptArr, 0);
    for (i = 0; i < 3; ++i) {
        Tcl_DecrRefCount(scriptArr[i]);
    }
    resultObj = Tcl_GetObjResult(interp);
    Tcl_IncrRefCount(resultObj);
    Tcl_GetIntFromObj(interp, resultObj, &result);
    Tcl_DecrRefCount(resultObj);
    printf("%d\n", result);
}

int main(int argc, char *argv[]) {
    Tcl_Interp *interp;
    
    interp = Tcl_CreateInterp();
    EvalString(interp);
    EvalStringObj(interp);
    EvalListObj(interp);
    Eval(interp);
    Tcl_DeleteInterp(interp);
    return 0;
}

新设计的字符串匹配

正则太麻烦了,我又太笨,学不会,就自己随便设计一个。当然也只是随便设计而已。

'' 括起来的字符串表示是独立的单词,如输入 'hello' 则不会和 helloworld 匹配

"" 括起来的字符串表示可与其他,如输入 "hello" 则会与 helloworld 匹配

在一个匹配节中,紧挨在前面的数字是重复次数,空表示一次,多次可用空格分开, - 表示连续,如 (digit) 表示匹配一个数字,2(number) 表示匹配两个数字(如 10, 99,但不匹配 1、100),*(digit) 匹配一以上位数字,^(digital) 匹配零以上位数字。

(digit 接受的数字):匹配一个数字([0,9]),不独立。也可以在这里使用连续的列表。如 (digit 100-200) 表示匹配在 [100,200] 中的一切整数,此时无视“一个数字”限制。

(standalone):强制独立,即使写成 (standalone "str") 也视作 'str';写作 (standalone (digit)) 则匹配一个独立的数字,如匹配 1 不匹配 12,写作 2(standalone (digit)) 或 *(standalone (digit)) 则匹配 “1 2” “2 4” 等有间隔的数字。

(join 一系列东西 中间插入的东西):匹配如同 join 返回的结果一样的文本,如 (join *(digit) ".") 匹配 1.2.3.4.100 和 100.300.600.0.1 等。

(or 选择一 选择二 选择三):使用多种方式匹配,若相同一段文本在三种选择中都匹配,则返回匹配长度最长的一种。

(have 名字 匹配格式):自定义匹配方法。如 (have number (or (digit 0) "") "." *(digit)),以后就可以使用 (number) 来匹配

(match 起始 结束):匹配有开始和结束的一段文本。匹配最长的文本。如 "{{ a } b} {c}" 则匹配得到 "{{ a } b}" 和 "{c}",{ a } 并不放出。

(match 起始结束):如双引号,本身就可以结束自己。

(line 位置 匹配节):位置可以是 start,end,middle。

 

一个 () 代表一个匹配节,是一个逻辑单位,[] 作用与 () 相同,只是为了避免嵌套过多难于阅读。

 

上面这些看着有点像是文本处理的 DSL 了,举些例子:

# IPv4 地址
(join 4(digital 0-255) ".")

# 浮点数(要求有小数点)
(or [^(digital) "." ^(digital)]
    [*(digital) "." ^(digital)]
    [^(digital) "." *(digital)])

# 电话号码(3或4位区号+横线(连接在一起或没有连接在一起)+7或8位电话)
3,4(digital) (or "-" '-') 7,8(digital)

# 随便什么。。
(match 'for' 'next')

# 匹配 ⑨ 个独立的数字
(have float (or [^(digital) "." ^(digital)]
                [*(digital) "." ^(digital)]
                [^(digital) "." *(digital)]))
(have integer ^(digital))
(have number (or (integer) (float)))
9(standalone (number))