knit, by PYK, is another macro facility for Tcl
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:
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. knitbuild itself is useful for creating anonymous macros:
apply [knitbuild x {expr {${x} * ${x}}}] 5
knit is also available as ycl::knit::knit
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 try $body }
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} } knit ?: {cond val1 val2} { if {#{cond}} {lindex ${val1}} else {lindex ${val2}} } knit finally {init finally do} { #{init} try ${do} finally ${finally} }