论坛首页 综合技术论坛

ATS(Another Tcl Shell)代码解析之一 -- 代码缩进的两个实现

浏览 2017 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2010-02-26   最后修改:2010-02-27
在编写代码的时候,单击Tab完成缩进是最常用到的功能之一,当换行的时候也会自动完成这种缩进行为,一般称之为自动缩进。缩进的单位长度可能是一个Tab,4个空格或者2个空格,这个视乎个人爱好。以下的Tcl代码中,缩进了在hello函数的定义中用于打印“Hello,World”的命令。

proc hello {} {
    puts {Hello, World!}
}


这种缩进的行为不单是方便了程序员编写过程,而且使得代码结构更清晰易懂,这在代码结构复杂的情况下尤为明显。

一种简单的实现

根据考察前一行的缩进情况来调整当前行的缩进距离是有一定的实现依据的。当前一行的缩进行为正确的时候,这种实现可以提供快速的正确缩进。由于这种实现仅仅依赖与有限次的运算,所以在性能上是可控的。
就如函数hello所示,在“{”之后的一行应相比较前一行缩进一个单位,而一般情况下只需保持与前一行相同即可。为此,先要知道前一行的缩进情况,单独列出了一个函数来实现这个功能。

proc how_many_indent_chars {widget id} {
    set head [$widget search –regexp {\S} “$id linestart” “$id lineend”]
    if {$head == {}} {return 0}
    return [lindex [spilt $head .] 1]
}


How_many_indent_chars函数接受两个参数,分别是text组件实例widget和表示字符位置的id。首先在id所表示的行中,使用search函数正则查找第一个非空字符;如果查找不成功则说明该行是空行或者顶格存在非空字符,因此返回0;如果查找成功,则返回具体的空格数,由于id是“line.char”形式,所以将其分隔开后取列表的第二个值即可。
得到前一行的缩进情况后,我们就可以根据前一行是否以“{”结尾来决定当前行的缩进距离,这同样也需要一个函数。

proc current_indent_chars {widget} {
    set previous_line_indent_chars [how_may_indent_chars $widget {insert -1l}]
    if [regexp {\{\s*$} [$widget get {insert-1l linestart} {insert-1l lineend}]] {
        return [expr $previous_line_indent_chars + $::tab_to_spaces]
    }
    return $previous_line_indent_chars
}


基于上下文环境的实现

以上的实现办法在遇到前一行存在错误缩进的情况下,将一错到底,致使在此之后的所有行均错误缩进,这在编写大规模代码的时候是不可接受的。
事实上,当前行的缩进取决于其所在的上下文环境,即当前行由哪一对“{}”包围。更简单的说,当前行是在哪个未被匹配的“{”之后。
为此我们需要维护一个堆栈,并从当前行的上一行开始反向搜索“{”和“}”。当搜索到“}”时,将该位置压入栈中;搜索到“{”时,则出栈一个,如果本来就是栈是空的,那么当前被搜索到的“{”就是我们要找的上下文环境的起始点。

proc context_head {widget id braces_stack} {
    set where_is_brace [$widget search –backwards –regexp {\{|\}} id 1.0]
    if {$where_is_brace == {}} {
        return no_more_brace
    }
    switch  [$widget get $where_is_brace] {
        “{” {
            set stack_len [llength $braces_stack]
            if {$stack_len > 0} {
                set stack_end [expr $stack_len - 1]
                set braces_stack [popup $braces_stack $stack_end]
            } else {
                return $where_is_brace
            }
        }
        “}” {
            lappend brackes_stack $where_is_brace
        }
    }
    context_head $widget $where_is_brace $braces_stack
}

proc popup {lst id} {
    return [lreplace $lst $id $id]
}


在找到上下文起点之后,剩下的便是计算起点缩进的情况并添加必要的缩进了,该函数在上一节已经给出,也就是how_many_indent_chars,这里就不再重述。

隐患

寻找上下文起点的算法存在隐藏的性能陷阱。比如当代码非常巨大的时候,在代码的尾部并且处于全局环境下的某个位置运行缩进时,由于始终无法找到使context_head退出的起点“{”,函数将遍历整个代码直至1.0位置,这有可能会使编辑暂时处于不可操作的状态。
为此,可以设置一个用于强制退出的闸值indent_find_gate。比如将其设置为100即是允许context_head最多尾递归100次。至于如何对context_head修改这里也不说了。
论坛首页 综合技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics