Version 6 of knit

Updated 2014-10-10 04:11:48 by pooryorick

knit, by PYK, is another macro facility for Tcl

Description

knit uses tailcall to provide some of the features that were problematic in earlier macro systems. Each macro is a procedure that fills out a template according to the arguments it receives, and then tailcalls the template.

knit takes an EIAS approach to macros, providing just three template substitutions:

${var}
the value of $var is substituted, properly escaped as a list
#{var}
the value of $var is substituted without escaping the value as a list. This is useful for substituting, for example, entire subexpressions into expr.
!{varname}
this is simply for convenience, and is exactly equivalent to [set ${varname}]. In other words, the value ${varname} is the name of a variable, and it will be arranged for the value of that variable to be substituted at execution time. In the examples below, lpop2 does the same thing as lpop, but thanks to !{varname} is a little more concise.

knitbuild can be used to build a macro procedure specification without actually creating the macro procedure. knit is implemented as a trivial wrapper around knitbuild.

knit is also available as ycl::knit::knit

Implementation

proc knit {name varnames body} {
        set cmdspec [uplevel [
                list [namespace current]::knitbuild $varnames $body]]
        uplevel [list proc $name {*}$cmdspec]
}

proc knitbuild {varnames body} {
        variable knittemplate
        foreach varname $varnames {
                if {[llength $varname] > 1} {
                        set varname [lindex $varname 0]
                }
                lappend mapparts [string map [list @varname@ [list $varname]] { \
                        \${@varname@} "\[lindex [list [set @varname@]]]" \
                        #{@varname@} [set @varname@] \
                        !{@varname@} "\[set [set @varname@]]" \
                }]
        }
        set map \[list\ [join $mapparts { }]]
        set body [string map [list @map@ $map @template@ [
                list $body]] $knittemplate]
        list $varnames $body
}

variable knittemplate {
        set body [string map @map@ @template@]
        tailcall eval $body
}

Examples

These examples show how the macros presented in Sugar, along with various other macros are implemented in knit

knit double x {expr {${x} * 2}}

knit exp2 x {* ${x} * ${x}}

knit charcount {x {char { }}} {
    regexp -all ***=${char} ${x}
}

knit clear arg1 {unset ${arg1}}

knit first list {lindex ${list} 0}

knit rest list {lrange ${list} 1 end}

knit last list {lindex ${list} end}

knit drop list {lrange ${list} 0 end-1}

knit K {x y} {
    first [list ${x} ${y}]
}

knit yank varname {
    K [set ${varname}] [set ${varname} {}] 
}

knit lremove {varname idx} {
    set ${varname} [lreplace [yank ${varname}] ${idx} ${idx}]
}

knit lpop listname {
    K [lindex [set ${listname}] end] [lremove ${listname} end] 
}

knit lpop2 listname {
    K [lindex !{listname} end] [lremove ${listname} end] 
}

foreach cmdname {* + - /} {
    knit $cmdname args "
        expr \[join \${args} [list $cmdname]]
    "
}

knit sete {varname exp} {
    set ${varname} [expr {#{exp}}]
}

knit greeting? x {expr {${x} in {hello hi}}}

knit until {expr body} {
    while {!(#{expr})} ${body}
}

See Also

macro tests from Peter Spjuth