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.
		
		
		
		
		
			
		
			
				
					
					
						
							400 lines
						
					
					
						
							13 KiB
						
					
					
				
			
		
		
	
	
							400 lines
						
					
					
						
							13 KiB
						
					
					
				|  | |
| #slow - but basically works. Needs pipeline compilation to tcl script to speed up?  funcl? | |
| package require punk | |
|  | |
|  | |
| #todo - pinned vars.. what scope? add a facility to bake values into proc body? (what equivalent is there for pipelines not bound to a proc scope? apply?) | |
| #if a fun is intended to bind in the caller's scope - then we just need a way to return the fun contents as a runnable pipeline | |
| # - conflict between build-time and run-time pinned vars? Is this really 2 separate concepts? | |
| # - using a pin to help build up the pattern vs using the pin for pipelined/context data? | |
| # - unpinned vars are clearly required in the implementation bodies - which is 'pipeline' scope only.. and limited to the running clause. | |
| #pipefun::setvar ? | |
| #consider oo version that uses properties of the object as the pinned vars in the pattern-spec? | |
| #pinned vars are important to be able to pass more complex values than are allowed by the pattern mini-lang e.g paths /etc  (or is that a misuse???) | |
|  | |
| #what does a pin on the argspec <^var|  even mean?  other vars in <| are pure pipeline scope  | |
| # pins in intermediate pipes.. e.g  |^somevar@1>  could be used as sanity checks for pipeline-scope vars.. but is there a real need/usecase? | |
|  | |
|  | |
| namespace eval pipefun { | |
|     variable funs [dict create] | |
| } | |
|  | |
|  | |
| #note this isn't a 'standard' closure in the sense that the captured vars aren't modifiable - a coro or object could be used to hold state if that is required. | |
| proc pipefun::closure0 {body} { | |
|     set binding {} | |
|     foreach v [uplevel 1 info locals] { | |
|         upvar 1 $v var | |
|         if {[info exists var]} { | |
|             lappend binding [list $v $var] ;#values captured as defaults for apply args. | |
|         } | |
|     } | |
|     return [list apply [list $binding $body [uplevel 1 namespace current]]] | |
| } | |
|  | |
| #much slower attempting to upvar all at once with different varnames | |
| proc pipefun::closure1 {body} { | |
|     set binding {} | |
|     if {[info level] == 1} { | |
|         #up 1 is global  | |
|         set get_vars [list info vars] | |
|     } else { | |
|         set get_vars [list info locals] | |
|     } | |
|     set upinfo [uplevel 1 "list \[{*}$get_vars\] \[namespace current\]"] | |
|     lassign $upinfo vars ns | |
|     #puts stdout ">>> got vars:$vars ns:$ns" | |
|      | |
|     set pairs [lmap v $vars i [lsearch -all $vars *] {list $v v$i}] | |
|     set flatpairs [concat {*}$pairs] | |
|     upvar 1 {*}$flatpairs | |
|     #puts stdout ">>> $flatpairs" | |
|     foreach {v vi} $flatpairs { | |
|         if {![array exists $vi] && [info exists $vi]} { | |
|             lappend binding [list $v [set $vi]] ;#values captured as defaults for apply args. | |
|         } | |
|     } | |
|     return [list apply [list $binding $body $ns]] | |
| } | |
| proc pipefun::closure1 {body} { | |
|     set binding {} | |
|     if {[info level] == 1} { | |
|         #up 1 is global  | |
|         set get_vars [list info vars] | |
|     } else { | |
|         set get_vars [list info locals] | |
|     } | |
|     set upinfo [uplevel 1 "list \[{*}$get_vars\] \[namespace current\]"] | |
|     lassign $upinfo vars ns | |
|     #puts stdout ">>> got vars:$vars ns:$ns" | |
|     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. | |
|         } | |
|     } | |
|     return [list apply [list $binding $body $ns]] | |
| } | |
| proc pipefun::closure {body} { | |
|     set binding {} | |
|     if {[info level] == 1} { | |
|         #up 1 is global  | |
|         set get_vars [list info vars] | |
|     } else { | |
|         set get_vars [list info locals] | |
|     } | |
|     #set upinfo [uplevel 1 "list \[{*}$get_vars\] \[namespace current\]"] | |
|     #lassign $upinfo vars ns | |
|     #puts stdout ">>> got vars:$vars ns:$ns" | |
|     foreach v [uplevel 1 {*}$get_vars] { | |
|         upvar 1 $v var | |
|         if {(![array exists var]) && [info exists var]} { | |
|             lappend binding [list $v $var] ;#values captured as defaults for apply args. | |
|         } | |
|     } | |
|     return [list apply [list $binding $body [uplevel 1 namespace current]]] | |
| } | |
|  | |
|  | |
| proc pipefun::funs {{glob *}} { | |
|     variable funs | |
|     return [dict keys $funs $glob] | |
| } | |
|  | |
| proc pipefun::testpattern {pattern val} { | |
|     set d [apply {{mv res} { | |
|         punk::_multi_bind_result $mv $res -levelup 1 | |
|     }} $pattern $val] | |
|     #set r [_handle_bind_result $d] | |
|     #set pipedvars [dict get $d setvars] | |
|     return $d | |
| }  | |
|  | |
| proc pipefun::funclear {nameglob} { | |
|     variable funs | |
|     foreach k [dict keys $funs $nameglob] { | |
|         dict unset funs $k | |
|     } | |
| } | |
| proc pipefun::funinfo {name} { | |
|     package require overtype | |
|     variable funs | |
|     set keys [dict keys [dict get $funs $name defs]] | |
|     set out     "fun: $name\n" | |
|     append out  "clause_count: [llength $keys]\n" | |
|     #append out  [punk::list_as_lines $keys] | |
|     set maxwidth [expr {[string length Pattern] + 2}] | |
|     foreach k $keys { | |
|         set w [string length $k] | |
|         if {$w > $maxwidth} { | |
|             set maxwidth $w | |
|         } | |
|     } | |
|     set col1 [string repeat " " [expr {$maxwidth + 2}]] | |
|     set linewidth [expr {[string length $col1] + 1 + [string length LineCount]}] | |
|     set dashes [string repeat - $linewidth] | |
|     append out $dashes\n | |
|     append out "[overtype::left $col1 Pattern] LineCount\n" | |
|     append out $dashes\n | |
|     foreach k $keys { | |
|         set body [dict get $funs $name defs $k] | |
|         set linecount [llength [split $body \n]] | |
|         append out "[overtype::left $col1 $k] [$linecount]\n" | |
|     } | |
|     append out $dashes\n | |
|     return $out | |
| } | |
| proc pipefun::funclause {name match args} { | |
|     variable funs | |
|     set defaults [list -delete 0 -active 1] | |
|     if {([llength $args] % 2) != 0} { | |
|        error "funclause options must be in pairs. Known options: [dict keys $defaults]" | |
|     } | |
|     set opts [dict merge $defaults $args] | |
|  | |
|     if {[dict exists $funs $name defs $match]} { | |
|         set body [dict get $funs $name defs $match] | |
|         if {[dict get $opts -delete]} { | |
|             dict unset funs $name defs $match | |
|         } | |
|      | |
|         return $body | |
|     } else { | |
|         return "no clause '$match'" | |
|     } | |
| } | |
|  | |
| #todo - examine arity of call and limit cases tested? | |
| proc pipefun::fun_proc {name argspec body} { | |
|     variable funs | |
|      | |
|     if {[dict exists $funs $name]} { | |
|         set definitions [dict get $funs $name defs] | |
|         dict set definitions $argspec $body ;#add or override | |
|         dict set funs $name defs $definitions ;#write-back | |
|     } else { | |
|         #first implementations | |
|         dict set funs $name defs [dict create $argspec $body] | |
|     } | |
|  | |
|     if {[llength [info commands $name]]} { | |
|         if {[catch {info body $name} body1]} { | |
|             error "fun: unable to update function '$name' - not a proc" | |
|         } | |
|     } else { | |
|         set body1 "" | |
|     } | |
|  | |
|     #what is the point of <argspec> at beginning? | |
|     set test_case_template { | |
|         pipecase <argspec>.= list {*}$arglist |args> { | |
|             <body> | |
|         } <<argspec>| {*}$arglist  | |
|     } | |
|     set case_template1 { | |
|         pipecase .= list {*}$arglist |args> { | |
|             <body> | |
|         } <<argspec>| {*}$arglist  | |
|     } | |
|     set case_template { | |
|         pipecase .= { | |
|             <body> | |
|         } <<argspec>| {*}$switchargs | |
|     } | |
|  | |
|     set pipecases "" | |
|      | |
|     set definitions [dict get $funs $name defs] | |
|     dict for {a b} $definitions { | |
|         set impl [string map [list <argspec> $a <body> $b] $case_template] | |
|         append pipecases ${impl} | |
|     } | |
|     set nsup [uplevel 1 [list namespace current]] | |
|     if {$nsup eq "::"} { | |
|         set nsup "" | |
|     } | |
|     if {![string match "::*" $name]} { | |
|         set name "${nsup}::$name" | |
|     } | |
|      | |
|     proc $name args [string map [list <pipecases> $pipecases] { | |
|         pipeswitchc { | |
|             <pipecases> | |
|         } $args | |
|     }] | |
|      | |
|      | |
| } | |
|  | |
| proc pipefun::fun {name argspec body} { | |
|     variable funs | |
|     if {[dict exists $funs $name]} { | |
|         set definitions [dict get $funs $name defs] | |
|         dict set definitions $argspec $body ;#add or override | |
|         dict set funs $name defs $definitions ;#write-back | |
|     } else { | |
|         #first implementations | |
|         dict set funs $name defs [dict create $argspec $body] | |
|     } | |
|     if {[llength [info commands $name]]} { | |
|         if {![llength [interp alias "" $name]]} { | |
|             error "fun: unable to update function '$name' - not an alias" | |
|         } | |
|     } | |
|      | |
|     set pipecases "" | |
|     set definitions [dict get $funs $name defs] | |
|     dict for {a b} $definitions { | |
|         #set impl [string map [list <argspec> $a <body> $b] $case_template] | |
|         #append pipecases ${impl} | |
|          | |
|         set case [list pipecase .= $b <$a|] | |
|          | |
|         #set case [list pipecase ,$a= |$a> .= $b <|] | |
|          | |
|         append  case { {*}$switchargs} | |
|         #we can't maintain case as a proper list.. because it isn't. (contains {*}$xxx) | |
|         append pipecases $case\n | |
|     } | |
|     set casenomatch [string map [list <name> $name] {pipecase .= {error "casenomatch fun: <name> args: $args" "pipefun::fun <name>" {casenomatch unhandled_args}} <args|}] | |
|     append casenomatch { {*}$switchargs} | |
|     append pipecases $casenomatch\n | |
|  | |
|  | |
|     set nsup [uplevel 1 [list namespace current]] | |
|     if {$nsup eq "::"} { | |
|         set nsup "" | |
|     } | |
|     #if {![string match "::*" $name]} { | |
|     #    set name "${nsup}::$name" | |
|     #} | |
|      | |
|     #proc $name args [string map [list <pipecases> $pipecases] { | |
|     #    % .= { | |
|     #        pipeswitch { | |
|     #            <pipecases> | |
|     #        } | |
|     #    } <arglist| {*}$args | |
|     #}] | |
|     puts "creating alias '$name' (argspec:$argspec)" | |
|     #interp alias {} $name {} pipeswitchc $pipecases  | |
|     interp alias {} $name {} pipeswitch $pipecases  | |
|      | |
|      | |
| } | |
|  | |
| interp alias {} fun {}          [nsjoin [namespace current] pipefun::fun] | |
| interp alias {} funs {}         [nsjoin [namespace current] pipefun::funs] | |
| interp alias {} funclear {}     [nsjoin [namespace current] pipefun::funclear] | |
| interp alias {} funinfo {}      [nsjoin [namespace current] pipefun::funinfo] | |
| interp alias {} funclause {}    [nsjoin [namespace current] pipefun::funclause] | |
|  | |
| fun age argc#,v@0-end { | |
|     #always runs for age - falls through to other implementations | |
|     puts "age called with argc:$argc args:$v" | |
|     error [list ignore] | |
|     } | |
|  | |
| #only runs for exactly 2 args | |
| fun age a@,m@,2# { | |
|         puts stderr "You're $a!  $m" | |
|         pipeswitch { | |
|             pipecase r@@ok/result.= age $a | |
|             pipecase r.= age $a | |
|         } | |
|         return $r | |
|     } | |
|  | |
|  | |
| #experiment with match but return error instead of ok {result xxx} | |
| fun age a { | |
|     % switchresult.= pipeswitch { | |
|         #puts "testing-arguments: $a" | |
|         if {![info exists a]} { | |
|             error [list ignore no-arg] | |
|         } | |
|         pipecase .= val $a |a@0> \ | |
|         0.= { | |
|             string is integer -strict $a | |
|         }       |> { | |
|             puts stderr "age must be an integer" | |
|             val " bad arg '$a' - must be an integer" | |
|         } | |
|          | |
|     } | |
|     #puts stdout ">>> $switchresult" | |
|     % status@@/@0.= val $switchresult | |
|     % contents@1.= val $switchresult | |
|     puts stdout ">>> $switchresult" | |
|      | |
|     #an ordinary return value (extracted from @@ok/result above) will get wrapped in  'ok {result <value>}' which can be confusing if the result is supposed to indicate a fault | |
|     #return $status | |
|     #even worse if we return switchresult as we would get a double @@ok/result | |
|      | |
|     #pipeswitch can return @@ok/result or @@error  | |
|     #  we could use something like: | |
|     #if {[$status eq "error"]} {...} | |
|     # or | |
|     #if {$status eq "ok"} {...} | |
|     # or just use error "switcherror ..." directly in the above pipecase, which would propagate through as an error | |
|      | |
|      | |
|      | |
|         #wrapping in own error-bag doesn't really help as the outer ok wrapping will still get applied | |
|         #return [list error {reason "bad arg"}] | |
|  | |
|         #raising a normal error in the above pipeswitch or anywhere in the fun results in an @@error/reason *return value* | |
|         #(as opposed to an @@error/mismatch *return value* for a failed binding) | |
|         #i.e later implementations will still be run as this will still just be a non-match | |
|         #error "$contents" | |
|  | |
|  | |
|         #will work to raise an error, but bad form to fake a pipesyntax error | |
|         #error "pipesyntax bad-arg" | |
|          | |
|         #to cause pipewitch above this to raise an error and stop processing cases.. | |
|         #use 'switcherror' or 'funerror' as first element of a list e.g | |
|         #error [list funerror [list reason "$contents"]] | |
|          | |
|         #attempting to raise a normal error in the above switch - but then a bad match here.. will only cause this implementation to be a non-match  | |
|         # and so other implementations will still be run | |
|         #return [@@ok/result.= val $switchresult] | |
|          | |
|     #in this example - we want to force a terminal error (see switcherror above) or a terminal *result* that isn't wrapped in 'ok', | |
|     #but only if we *don't* have a mismatch. | |
|     #i.e we require an @@error/mismatch to proceed to other implementations | |
|     #puts stderr "=--->$switchresult" | |
|     if {[ispipematch r@@casemismatch.= val $switchresult]} { | |
|         #all good - simulate an @@error/mismatch - or raise an 'ignore' error so that this implementation is considered to be unmatched and we fall through to others. | |
|         #puts stdout "=ispipematch true" | |
|         error [list ignore [list argcheck passed]] | |
|         #error "binding mismatch" | |
|     } else { | |
|         #puts stdout "=ispipematch false" | |
|         #error [list resultfunerror $contents] | |
|         error [list funerror $contents] | |
|     } | |
|          | |
| } | |
|  | |
|  | |
|  | |
|  | |
|      | |
| fun age 1 {puts "welcome!"} | |
| fun age 18 {puts "yay!"} | |
| fun age 42 { | |
|         error "Test that error is raised." | |
|     } | |
| fun age ''/0 {puts "enter an age, and optionally a message";return "no-value-supplied"} | |
| fun age a,100 {puts "congrats!"; return "congrats-$a"} | |
| fun age b {puts "cool"; return "cool-$b"} | |
| #fun age a,3+/# {puts stderr "too many args"; return} | |
|  | |
| age 100 | |
|  | |
|  | |
|  | |
|  | |
| 
 | |
| 
 |