package provide funcl [namespace eval funcl { variable version set version 0.1 }] #funcl = function list (nested call structure) # #a basic functional composition o combinator #o(f,g)(x) == f(g(x)) namespace eval funcl { #from punk proc arg_is_script_shaped {arg} { if {[string first " " $arg] >= 0} { return 1 } elseif {[string first \n $arg] >= 0} { return 1 } elseif {[string first ";" $arg] >= 0} { return 1 } elseif {[string first \t $arg] >= 0} { return 1 } else { return 0 } } proc o args { set closing [string repeat {]} [expr [llength $args]-1]] set body "[join $args { [}] \$data $closing" return $body } proc o_ args { set body "" set tails [lrepeat [llength $args] ""] puts stdout "tails: $tails" set end [lindex $args end] if {[llength $end] == 1 && [arg_is_script_shaped $end]} { set endfunc [string map [list $end] {uplevel 1 [list if 1 ]}] } else { set endfunc $end } if {[llength $args] == 1} { return $endfunc } set wrap { [} append wrap $endfunc append wrap { ]} set i 0 foreach cmdlist [lrange $args 0 end-1] { set is_script 0 if {([llength $cmdlist] == 1) && [arg_is_script_shaped [lindex $cmdlist 0]]} { set is_script 1 set script [lindex $cmdlist 0] } set t "" if {$i > 0} { append body { [} } set posn [lsearch $cmdlist _] if {$posn <= 0} { append body $cmdlist if {$i == [expr {[llength $args] -2}]} { #append body " \$data" append body " $wrap" } if {$i > 0} { set t {]} } } else { append body [lrange $cmdlist 0 $posn-1] if {$i == [expr {[llength $args] -2}]} { #append body " \$data" append body " $wrap" } set t [lrange $cmdlist $posn+1 end] if {$i > 0} { append t { ]} } } lset tails $i $t incr i } append body [join [lreverse $tails] " "] puts stdout "tails: $tails" return $body } #review - consider _call -- if count > 1 then they must all be callable cmdlists(?) # what does it mean to have additional _fn wrapper with no other elements? (no actual function) #e.g _fn 2 5 6 somefunc {_fn 1 3 {_call 1 3 xxx}} {_fn 1 4 command {_fn ...}} # what type indicates running subtrees in parallel vs sequentially? # any reason to have _call count other than 1? Presumably the parent node indicates the parallelism/sequentialism etc. # # # accept or return a funcl (or funcltree if multiple funcls in one commandlist) # also accept/return a call - return empty list if passed a call proc next_funcl {funcl_or_tree} { if {[lindex $funcl_or_tree 0] eq "_call"} { return [list] } if {[lindex $funcl_or_tree 0] in [list "_fn" "_call"]} { set funcl $funcl_or_tree } else { error "funcltree not implemented" } set count [lindex $funcl 1] if {$count == 0} { #null funcl.. what is it? metadata/placeholder? return $funcl } set indices [lrange $funcl 2 [expr {1 + $count}]] set i 0 foreach idx $indices { if {$i > 0} { #todo - return a funcltree error "multi funcl not implemented" } set next [lindex $funcl $idx] incr i } return $next } #convert a funcl to a tcl script proc funcl_script {funcl} { if {![llength $funcl]} { return "" } set body "" set tails [list] set type [lindex $funcl 0] if {$type ni [list "_fn" "_call"]} { #todo - handle funcltree error "type $type not implemented" } #only count of 1 with index 3 supported(?) if {$type eq "_call"} { #leaf set cmdlist [lindex $funcl 3] return $cmdlist } #we will use next_funcl to walk the nodes.. todo support treefuncl response from next_funcl which could branch multiple times. #by continually passing back the resulting treefuncl/funcl to next_funcl we can process in correct order (?) # we would still need to maintain state to stitch it back together once returned from a subtree.. # ie multiple tail parts set count [lindex $funcl 1] if {$count == 1} { set idx [lindex $funcl 2] if {$idx == 3} { set cmdlist_pre [list] } else { set cmdlist_pre [lrange $funcl 3 $idx-1] } append body $cmdlist_pre set t [lrange $funcl $idx+1 end] lappend tails $t } else { #?? error "funcl_script branching not yet supported" } set get_next 1 set i 1 while {$get_next} { set funcl [next_funcl $funcl] if {![llength $funcl]} { set get_next 0 } lassign $funcl type count idx ;#todo support count > 1 if {$type eq "_call"} { set get_next 0 } set t "" if {$type eq "_call"} { append body { [} append body [lindex $funcl $idx] append body { ]} } else { append body { [} if {$idx == 3} { set cmdlist_pre [list] } else { set cmdlist_pre [lrange $funcl 3 $idx-1] } append body $cmdlist_pre set t [lrange $funcl $idx+1 end] lappend tails $t lappend tails { ]} } incr i } append body [join [lreverse $tails] " "] #puts stdout "tails: $tails" return $body } interp alias "" o_of "" o_of_n 1 #o_of_n #tcl list rep o combinator # # can take lists of ordinary commandlists, scripts and funcls # _fn 1 x where 1 indicates number of subfuncls and where x indicates next funcl position (_fn list or _arg) # _fn 0 indicates next item is an unwrapped commandlist (terminal command) # #o_of is equivalent to o_of_n 1 (1 argument o combinator) #last n args are passed to the prior function #e.g for n=1 f a b = f(a(b)) #e.g for n=2, e f a b = e(f(a b)) proc o_of_n {n args} { if {$n != 1} { error "o_of_n only implemented for 1 sub-funcl" } set comp [list] ;#composition list set end [lindex $args end] if {[lindex $end 0] in [list "_fn" "_call"]} { #is_funcl set endfunc [lindex $args end] } else { if {[llength $end] == 1 && [arg_is_script_shaped $end]} { #set endfunc [string map [list $end] {uplevel 1 [list if 1 ]}] set endfunc [list _call 1 3 [list uplevel 1 [list if 1 [lindex $end 0]]]] } else { set endfunc [list _call 1 3 [list {*}$end]] } } if {[llength $args] == 1} { return $endfunc } set comp $endfunc set revlist [lreverse [lrange $args 0 end-1]] foreach cmdlist $revlist { if {([llength $cmdlist] == 1) && [arg_is_script_shaped [lindex $cmdlist 0]]} { set is_script 1 set script [lindex $cmdlist 0] set arglist [list data] set comp [list _fn 1 6 call_script $script $arglist $comp] } else { set posn1 [expr {[llength $cmdlist] + 2 + $n}] set comp [list _fn $n $posn1 {*}$cmdlist $comp] } } return $comp } proc call_script {script argnames args} { uplevel 3 [list if 1 [list apply [list $argnames $script] {*}$args]] } proc funcl_script_test {scr} { do_funcl_script_test $scr } proc do_funcl_script_test {scr} { #set j "in do_funcl_script_test" #set data "xxx" #puts '$scr' if 1 $scr } #standard o_ with no script-handling proc o_plain args { set body "" set i 0 set tails [lrepeat [llength $args] ""] #puts stdout "tails: $tails" foreach cmdlist $args { set t "" if {$i > 0} { append body { [} } set posn [lsearch $cmdlist _] if {$posn <= 0} { append body $cmdlist if {$i == [expr {[llength $args] -1}]} { append body " \$data" } if {$i > 0} { set t {]} } } else { append body [lrange $cmdlist 0 $posn-1] if {$i == [expr {[llength $args] -1}]} { append body " \$data" } set t [lrange $cmdlist $posn+1 end] if {$i > 0} { append t { ]} } } lset tails $i $t incr i } append body [join [lreverse $tails] " "] #puts stdout "tails: $tails" return $body } #timings suggest no faster to split out the first item from the cmdlist loop }