Julian Noble
2 years ago
2 changed files with 445 additions and 77 deletions
@ -0,0 +1,320 @@ |
|||||||
|
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> $end] {uplevel 1 [list if 1 <end> ]}] |
||||||
|
} 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 "" funcl::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> $end] {uplevel 1 [list if 1 <end> ]}] |
||||||
|
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 |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in new issue