You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

376 lines
13 KiB

package provide punk::lib [namespace eval punk::lib {
variable version
set version 0.1
}]
namespace eval punk::lib {
>pattern .. Create >libpattern ;#clone to a library factory
>libpattern .. Construct {args} {
var o_this
set o_this @this@
var o_last_child
set o_last_child ""
}
>libpattern .. Method version {} {
return 1.0.0
}
>libpattern .. Method aliasprefix {pfx} {
var o_this
var o_last_child
if {![string length $o_last_child]} {
error " . aliasprefix - Create library object with . new >somename first."
}
set patternmethods [$o_this .. PM]
set aliases [list]
foreach m $patternmethods {
set a ${pfx}${m}
if {[llength [info commands $a]]} {
puts stderr "WARNING - a command was already present at: $a"
}
interp alias "" $a "" [$o_last_child . $m .]
lappend aliases $a
}
return $aliases
}
>libpattern .. Method new {objcmdname} {
var o_this
set o_this @this@
var o_last_child
set nscaller [uplevel 1 [list namespace current]]
if {![string match ::* $objcmdname]} {
if {$nscaller eq "::"} {set nscaller ""}
set objcmdname ${nscaller}::$objcmdname
}
uplevel 1 [list $o_this .. Create $objcmdname]
set o_last_child $objcmdname
}
>libpattern .. Constructor {args} {
var o_this
set o_this @this@
}
>libpattern .. Clone >ls_lib
>ls_lib .. PatternMethod tail {args} {
if {![llength $args]} {
error "argumenterror cannot retrieve tail on an empty input list" ">ls_lib . tail $args" [list argumenterror tail empty_list]
}
lrange $args 1 end
}
>ls_lib .. PatternMethod init {args} {
if {![llength $args]} {
error "argumenterror cannot retrieve init on an empty input list" ">ls_lib . init $args" [list argumenterror init empty_list]
}
lrange $args 0 end-1
}
>ls_lib .. PatternMethod head {args} {
if {![llength $args]} {
error "argumenterror cannot retrieve head on an empty input list" ">ls_lib . head $args" [list argumenterror head empty_list]
}
lindex $args 0
}
>ls_lib .. PatternMethod last {args} {
if {![llength $args]} {
error "argumenterror cannot retrieve last on an empty input list. Use li.index end to avoid list length check" ">ls_lib . last $args" [list argumenterror last empty_list]
}
lindex $args end
}
>ls_lib .. PatternMethod elem {val args} {
expr {$val in $args}
}
>ls_lib .. PatternMethod index {idx args} {
lindex $args $idx
}
>ls_lib .. PatternMethod range {s e args} {
lrange $args $s $e
}
#take/drop - haskell-like - but no lazy support REVIEW
#see also https://www.haskellforall.com/2022/05/why-does-haskells-take-function-accept.html
>ls_lib .. PatternMethod take {n args} {
#keep basic behaviour like Haskell ie we allow returning less than n (without error) if insufficient elements
lrange $args 0 $n-1
}
>ls_lib .. PatternMethod drop {n args} {
lrange $args $n end
}
>ls_lib . new >ls
>ls_lib . aliasprefix "ls."
#list item lib
>libpattern .. Clone >li_lib
>li_lib .. PatternMethod tail {listdata} {
if {![llength $listdata]} {
error "argumenterror cannot retrieve tail on an empty input list" ">li_lib . tail $listdata" [list argumenterror tail empty_list]
}
lrange $listdata 1 end
}
>li_lib .. PatternMethod init {listdata} {
if {![llength $listdata]} {
error "argumenterror cannot retrieve init on an empty input list" ">li_lib . init $listdata" [list argumenterror init empty_list]
}
lrange $listdata 0 end-1
}
>li_lib .. PatternMethod head {listdata} {
if {![llength $listdata]} {
error "argumenterror cannot retrieve head on an empty input list" ">li_lib . head $listdata" [list argumenterror head empty_list]
}
lindex $listdata 0
}
>li_lib .. PatternMethod last {listdata} {
if {![llength $listdata]} {
error "argumenterror cannot retrieve last on an empty input list. Use li.index end to avoid list length check" ">li_lib . last $listdata" [list argumenterror last empty_list]
}
lindex $listdata end
}
>li_lib .. PatternMethod elem {val listdata} {
expr {$val in $listdata}
}
>li_lib .. PatternMethod index {idx listdata} {
lindex $listdata $idx
}
>li_lib .. PatternMethod range {s e listdata} {
lrange $listdata $s $e
}
#take/drop - haskell-like - but no lazy support REVIEW
#see also https://www.haskellforall.com/2022/05/why-does-haskells-take-function-accept.html
>li_lib .. PatternMethod take {n listdata} {
#keep basic behaviour like Haskell ie we allow returning less than n (without error) if insufficient elements
lrange $listdata 0 $n-1
}
>li_lib .. PatternMethod drop {n listdata} {
lrange $listdata $n end
}
#todo - takeWhile, dropWhile, takeWhileEnd, dropWhileEnd
>li_lib .. PatternMethod is_list_all_in_list {a b} {
package require struct::list
package require struct::set
set a_in_b [lsort [struct::set intersect [lsort -unique $a] $b ]]
return [struct::list equal [lsort $a] $a_in_b]
}
>li_lib .. PatternMethod is_list_all_ni_list {a b} {
package require struct::set
set i [struct::set intersect $a $b]
return [expr {[llength $i] == 0}]
}
>li_lib . new >li
>li_lib . aliasprefix "li."
>pattern .. Create >f_lib
>f_lib .. Construct {args} {
var o_this
set o_this @this@
var o_last_child
set o_last_child ""
}
>f_lib .. Method version {} {
return 1.0.0
}
>f_lib .. Method aliasprefix {pfx} {
var o_this
var o_last_child
if {![string length $o_last_child]} {
error ">f_lib . aliasprefix - Create library object with >f_lib . new >somename first."
}
set patternmethods [$o_this .. PM]
set aliases [list]
foreach m $patternmethods {
set a ${pfx}${m}
if {[llength [info commands $a]]} {
puts stderr "WARNING - a command was already present at: $a"
}
interp alias "" $a "" [$o_last_child . $m .]
lappend aliases $a
}
return $aliases
}
>f_lib .. Method new {objcmdname} {
var o_this
var o_last_child
set nscaller [uplevel 1 [list namespace current]]
if {![string match ::* $objcmdname]} {
if {$nscaller eq "::"} {set nscaller ""}
set objcmdname ${nscaller}::$objcmdname
}
uplevel 1 [list $o_this .. Create $objcmdname]
set o_last_child $objcmdname
}
>f_lib .. Constructor {args} {
var o_this
set o_this @this@
}
>f_lib .. PatternMethod foldl {total func sequence} {
struct::list::Lfold $sequence $total $func
}
#note: foldr is not equivalent to just doing a foldl on the reversed list
#todo - review/test/fix
>f_lib .. PatternMethod foldr {total func sequence} {
set this @this@
if {![llength $sequence]} {
return $total
}
v,h@head,t@tail.=val $sequence |h@head,t@tail> {
puts "-->$h"
$func [$this . foldr $total $func $t] $h
} <this@,func@,total@| $this $func $total
return 0
return $v
}
# reduce: simplest case - list of numbers - reduce with +
# more complex case: list of pipelines (e.g parsers) - reduce with 'andThen' operator of some sort.
>f_lib .. PatternMethod reduce {func sequence} {
struct::list::Lfold [lrange $sequence 1 end] [lindex $sequence 0] $func
}
>f_lib .. PatternMethod list_map {commandlist list} {
tailcall lmap item $list $commandlist
}
>f_lib .. PatternMethod list_unique {args} {
set list [concat {*}$args]
set d [dict create]
foreach item $list {
dict set d $item ""
}
dict keys $d
}
>f_lib .. PatternMethod list_as_lines {args} {
set list [concat {*}$args]
join $list \n
}
>f_lib .. PatternMethod list_filter_cond {itemcond listval} {
#maintenance - proc list_filter_cond
set filtered_list [list]
set binding {}
if {[info level] == 1} {
#up 1 is global
set get_vars [list info vars]
} else {
set get_vars [list info locals]
}
set vars [uplevel 1 {*}$get_vars]
set posn [lsearch $vars item]
set vars [lreplace $vars $posn $posn]
foreach v $vars {
upvar 1 $v var
if {(![array exists var]) && [info exists var]} {
lappend binding [list $v $var] ;#values captured as defaults for apply args.
}
}
#lappend binding [list item $args]
#puts stderr "binding: [join $binding \n]"
#apply [list $binding $pipescript [uplevel 1 namespace current]]
foreach item $listval {
set bindlist [list {*}$binding [list item $item]]
if {[apply [list $bindlist $itemcond [uplevel 1 namespace current]] ]} {
lappend filtered_list $item
}
}
return $filtered_list
}
>f_lib .. PatternMethod sum_llength {total listval} {
expr {$total + [llength $listval]}
}
>f_lib .. PatternMethod sum_length {total stringval} {
expr {$total + [string length $stringval]}
}
>f_lib .. PatternMethod debug {total item} {
puts stderr "incr tally: $total item: $item"
expr {$total + 1}
}
>f_lib .. PatternMethod dict_walk {d key} {
dict get $d $key
}
>f_lib .. PatternMethod sum {total num} {
expr {$total + $num}
}
>f_lib .. PatternMethod lcomp {expression args} {
#from https://wiki.tcl-lang.org/page/lcomp
set __0__ "lappend __1__ \[expr [list $expression]\]"
while {[llength $args] && [lindex $args 0] ni {for if with}} {
append __0__ " \[expr [list [lindex $args 0]]\]"
set args [lrange $args 1 end]
}
set tmpvar 2
set structure {}
set upvars {}
while {[llength $args]} {
set prefix ""
switch [lindex $args 0] {
for {
set nest [list foreach]
while {[llength $nest] == 1 || [lindex $args 0] eq "and"} {
if {[llength $args] < 4 || [lindex $args 2] ni {in inside}} {
error "wrong # operands: must be \"for\" vars \"in?side?\"\
vals ?\"and\" vars \"in?side?\" vals? ?...?"
}
switch [lindex $args 2] {
in {
lappend nest [lindex $args 1] [lindex $args 3]
} inside {
lappend nest __${tmpvar}__ [lindex $args 3]
append prefix "lassign \$__${tmpvar}__ [lindex $args 1]\n"
incr tmpvar
}}
set args [lrange $args 4 end]
}
lappend structure $nest $prefix
} if {
if {[llength $args] < 2} {
error "wrong # operands: must be \"if\" condition"
}
lappend structure [list if [lindex $args 1]] $prefix
set args [lrange $args 2 end]
} with {
if {[llength $args] < 2} {
error "wrong # operands: must be \"with\" varlist"
}
foreach var [lindex $args 1] {
lappend upvars $var $var
}
set args [lrange $args 2 end]
} default {
error "bad opcode \"[lindex $args 0]\": must be for, if, or with"
}}
}
foreach {prefix nest} [lreverse $structure] {
set __0__ [concat $nest [list \n$prefix$__0__]]
}
if {[llength $upvars]} {
set __0__ "upvar 1 $upvars; $__0__"
}
unset -nocomplain expression args tmpvar prefix nest structure var upvars
set __1__ ""
eval $__0__
return $__1__
}
>f_lib . new ::punk::lib::>f
>f_lib . aliasprefix "f."
interp alias {} >f {} ::punk::lib::>f
#Pattern-matching based functional operations
>pattern .. Create >P
>P .. Method map {pattern commandlist sequence} {
#set segment [string map [list <cmd> $commandlist] {<cmd>}]
set pipeline [list % {val $item} "|,item,$pattern>" $commandlist <item/0|]
tailcall % list $pipeline $sequence |p/0,l/1> {lmap val $l {{*}$p $val }}
}
}