Version 1 of knit

Updated 2014-10-09 16:30:52 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 substitutint, 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 oof 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.

Implementation

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

proc knitbuild {name varnames body} {
        variable knittemplate
        foreach varname $varnames {
                lappend mapparts [string map [list @varname@ [list $varname]] { \
                        \${@varname@} "\[lindex [list [set @varname@]]]" \
                        #{@varname@} [set @varname@] \
                        !{@varname@} "\[set [set @varname@]]" \
                }]
        }
        set map [string map [
                list @mapparts@ [join $mapparts { }]] {[list @mapparts@]}]
        set body [string map [list @map@ $map @template@ [
                list $body]] $knittemplate]
        list proc $name $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 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