在我的TclTk项目中,我需要允许我的用户以一种明确的方式来纠错字符串。
我的想法是,允许用户在配置文件中声明一个 "字符串纠错 "procexprfunction...,然后应用到相关的字符串上。
我有点担心如何正确地实现这一点。
到目前为止,我考虑过的可能性有
正则表达式
这是我的第一个想法,但有两个注意事项。
procs?
既然目标平台是Tcl,那么为什么不利用Tcl的强大功能来做字符串杂烩呢?"函数 "应该有一个单一的输入和产生一个单一的输出,而且理想的情况是,它应该引导用户做正确的事情(例如,不能定义一个需要两个参数的proc),而且它不可能产生副作用(比如改变应用程序的状态)。
一个简单的方法是使用 proc mymangler s $body
(有 $body
是用户定义的字符串),但有很多事情可能出错。
$body
假设一个不同的arg-name(例如 $x
而不是 $s
)$body
一无所获$body
在环境中改变变量,...。expr
句子看起来更像它(总是返回东西,不允许轻易修改环境),但我不能让它们在字符串上工作,而且没有办法在不同意其名称的情况下传递一个变量。
所以,目前我想到的最好的办法是。
set userfun {return $s} # user-defined string
proc mymangler s ${userfun}
set output [mymangler $input]
有没有更好的方法可以在Tcl中实现用户自定义的字符串编辑器?
你可以使用 apply
--用户提供了一个2元素的列表:第二个元素是 "proc body",即做混搭的代码;第一个元素是存放字符串的变量名,这个变量在body中使用。
比如说
set userfun {{str} {string reverse $str}}
set input "some string"
set result [apply $userfun $input] ;# => "gnirts emos"
当然,你从用户那里得到的代码是任意的Tcl代码。你可以在一个 安全翻译:
set userfun {{str} {exec some malicious code; return [string reverse $str]}}
try {
set interp [safe::interpCreate]
set result [$interp eval [list apply $userfun $input]]
puts "mangled string is: $result"
safe::interpDelete $interp
} on error e {
error "Error: $e"
}
结果:
Error: invalid command name "exec"
注释:
apply
这个方案确实保护了环境。
set userfun {{str} {set ::env(SOME_VAR) "safe slave"; return $str$str}}
set env(SOME_VAR) "main"
puts $env(SOME_VAR)
try {
set interp [safe::interpCreate]
set result [$interp eval [list apply $userfun $input]]
puts "mangled string is: $result"
safe::interpDelete $interp
} on error e {
error "Error: $e"
}
puts $env(SOME_VAR)
输出
main
mangled string is: some stringsome string
main
这种 "简单化 "的方法就像 foreach
中,它需要用户提供一个变量名和一个使用该变量的脚本来评估,是一个很好的方法。如果你不想让它影响程序的其他部分,可以在一个单独的解释器中运行它。
set x 0
proc mymangler {name body} {
set i [interp create -safe]
set s "some string to change"
try {
# Build the lambda used by apply here instead of making
# the user do it.
$i eval [list apply [list $name $body] $s]
} on error e {
return $e
} finally {
interp delete $i
}
}
puts [mymangler s { set x 1; string toupper $s }]
puts $x
输出
SOME STRING TO CHANGE
0
如果打电话的人说要用 s
作为一个变量,然后在正文中使用其他的东西,这是在他们身上。和提供一个不返回任何东西的脚本一样。
我一般会允许用户指定一个 命令前缀 作为一个 Tcl 列表 (大多数简单的命令名都适合于此),然后你可以将其应用到参数中。
set mangled [{*}$commandPrefix $valueToMangle]
这让人们可以提供任何他们想要的东西,尤其是他们可以使用 apply
和一个lambda项来根据需要来捣鼓东西。当然,如果你是在一个过程中,那么你可能会更好地做。
set mangled [uplevel 1 [list {*}$commandPrefix $valueToMangle]]
这样你就会在调用者的上下文中运行(改变了 1
到 #0
来代替使用全局上下文),这可以帮助保护你的过程不被意外更改,并使使用 upvar
内的mangler更容易。
如果mangling前缀的源头是不受信任的(这意味着什么,很大程度上取决于你的应用和部署),那么你可以在一个单独的解释器中运行mangling代码。
# Make the safe evaluation context; this is *expensive*
set context [interp create -safe]
# You might want to let them define extra procedures too
# interp invokehidden $context source /the/users/file.tcl
# Use the context
try {
set mangled [interp eval $context [list {*}$commandPrefix $valueToMangle]]
} on error {msg} {
# User supplied something bad; error message in $msg
}
有各种方法来支持用户指定转换,但如果你能向他们暴露你正在使用Tcl工作的事实,那么这可能是最简单和最灵活的。