openlang

TSV 里一个普通字符串报 invalid command name "Dana",什么情况?

好吧,这个问题把我绕晕了。Arch 上的 Tcl 9.0,跑的是 tclsh main.tcl,给客户的五金供应网站做的一个小订单报表。

我在 config.tcl 里原本只有一条规则 (total >= 200),一切正常。后来我加了第二条规则,让带客户备注的订单也被标记出来。

set ::flag_rules {
    {total >= 200}
    {note ne {}}
}

现在程序跑到一半就挂了,报 invalid command name "Dana"。栈追踪指向 rule_match 里的 expr 那一行。

proc rule_match {row rule} {
    lassign $rule field op value
    set have [dict get $row $field]
    return [expr "\"$have\" $op \"$value\""]
}

我的代码里根本没有叫 "Dana" 的命令,它出现在数据里。金额比较大的订单里有一条,在 orders.tsv 中的备注是这样的。

1003	Petrakis Plumbing	1250.75	call [Dana] before shipping

其他备注 (比如 "leave at side door") 都能正常通过。我在崩溃前一刻把这一行的 dict 打印出来了,看起来完全正常。制表符切得没问题,四个字段都在,备注也是完整的。所以我已经对着 load_orders 折腾了一个小时,一直以为是客户导出的文件有什么奇怪的编码问题或者混进了多余的字节。我还在 Neovim 里用 :set list 看过,制表符就是制表符。(顺便问一句,为什么他们导出的文件里,空备注的行末尾多了一个制表符,而 1005 那行没有?这是另一个问题,改天再说。)

我想不通的是,一个躺在 dict 里的字符串是怎么变成命令调用的。没有任何地方对它做 eval 啊。为什么只有这一行出问题?

从英语翻译查看原文

后续讨论 1

顺着彼此的依据读下去
  1. 固定链接

    自己回复一下自己的帖子,我找到原因了,从头到尾都是我自己的问题。

    不是数据的问题,也不是 TSV 的问题,问题出在 expr "..." 这一行。因为我用双引号来拼表达式,Tcl 会先替换 $have。然后 expr 去解析替换后的结果,表达式里带引号的字符串中的 [Dana] 会被当作命令替换。所以备注文本被当成代码执行了。"leave at side door" 里没有任何可替换的东西,所以没事。(total 那条规则从来没炸过,因为 1250.75 只是个数字。)

    修复办法是别再拼字符串了,直接让运算符作为命令:

    proc rule_match {row rule} {
        lassign $rule field op value
        return [::tcl::mathop::$op [dict get $row $field] $value]
    }

    ne 和 >= 在那里都有。文档在 https://www.tcl-lang.org/man/tcl9.0/TclCmd/mathop.htm

    另外,expr 那页文档让你给表达式加花括号是有原因的。我几年前读过那一页,显然没往心里去。好笑的是,我居然宁可在 Neovim 里花一个小时找多余字节,也不肯花三十秒读手册。

    从英语翻译查看原文

同一个问题的其他角度

  1. 同一个 Int,两个编译目标,两种结果

    同一份源码,同一个文件,两个 target。JS 上有一个参与者消失了。没有报错,也没有警告。我对此很不满意。 gleam run 的输出如下。 gleam run --target javascript 的输出如下。 行都读取正常,所以解码器拿到了…

    回复 0最近活动 gleam
  2. pure 宿主函数被缓存了,对参数的写入直接消失了

    这一周我逢人就吹我那个玩具求值器 registry 里的 memo 缓存(D 写的,周末项目)。规则是这样,宿主函数只要是 pure,就不能有副作用,所以按函数名加打印出来的参数做 key 缓存结果。现在它反咬了我一口。 我预期每行都是 [1]。第…

    回复 2最近活动 d
  3. 同样的 parseServices,错误行只在构建时才会出问题?

    内置的表一直不会变,所以我把 auto 改成了 enum,没问题吧?现在 smtp 25(没写 proto)会让 dub build 失败,报错最后停在 stderr writeln 不能在编译期使用。ci.sh 里用 ldc2 也找不到 servi…

    回复 2最近活动 d