Browse Source

vendormodule update for pattern lib

master
Julian Noble 3 months ago
parent
commit
1bde78cb57
  1. 3
      src/vendormodules/include_modules.config
  2. 1940
      src/vendormodules/patterndispatcher-1.2.4.tm
  3. 664
      src/vendormodules/patternpredator1-1.0.tm
  4. 664
      src/vendormodules/patternpredator1-1.2.4.tm
  5. BIN
      src/vendormodules/treeobj-1.3.1.tm

3
src/vendormodules/include_modules.config

@ -19,7 +19,10 @@ set local_modules [list\
c:/repo/jn/tclmodules/pattern/modules patternlib\
c:/repo/jn/tclmodules/pattern/modules patterncipher\
c:/repo/jn/tclmodules/pattern/modules metaface\
c:/repo/jn/tclmodules/pattern/modules patternpredator1\
c:/repo/jn/tclmodules/pattern/modules patternpredator2\
c:/repo/jn/tclmodules/pattern/modules patterndispatcher\
c:/repo/jn/tclmodules/pattern/modules treeobj\
c:/repo/jn/tarjar/modules tarjar\
]

1940
src/vendormodules/patterndispatcher-1.2.4.tm

File diff suppressed because it is too large Load Diff

664
src/vendormodules/patternpredator1-1.0.tm

@ -0,0 +1,664 @@
package provide patternpredator1 1.0
proc ::p::internals::trailing, {map command stack i arglist pending} {
error "trailing ',' is not a valid sequence. If the comma was meant to be an argument, precede it with the -- operator."
}
proc ::p::internals::trailing.. {map command stack i arglist pending} {
error "trailing .. references not implemented."
}
proc ::p::internals::trailing. {map command _ID_ stack i arglist pending} {
if {![llength $map]} {
error "Trailing . but no map - command:'$command' stack:$stack i:$i arglist:$arglist pending:$pending"
}
#trailing dot - get reference.
#puts ">>trailing dot>> map:$map command:$command _ID_:$_ID_ stack:$stack i:$i arglist:$arglist pending:$pending"
lassign [lindex $map 0] OID alias itemCmd cmd
#lassign $command command _ID_
if {$pending eq {}} {
#no pending operation requiring evaluation.
#presumably we're getting a ref to the object, not a property or method.
#set traceCmd [::list ::p::objectRef_TraceHandler $cmd $_self $OID]
#if {[::list {array read write unset} $traceCmd] ni [trace info variable $refname]} {
# trace add variable $refname {array read write unset} $traceCmd
#}
set refname ::p::${OID}::_ref::__OBJECT ;#!todo - avoid potential collision with ref to member named '__OBJECT'.
#object ref - ensure trace-var is an array upon which the objects methods & properties can be accessed as indices
array set $refname [list]
#!todo?- populate array with object methods/properties now?
set _ID_ [list i [list this [list [list $OID [list map $map]]]]]
#!todo - review. What if $map is out of date?
set traceCmd [list ::p::predator::object_read_trace $OID $_ID_]
if {[list {read} $traceCmd] ni [trace info variable $refname]} {
trace add variable $refname {read} $traceCmd
}
set traceCmd [list ::p::predator::object_array_trace $OID $_ID_]
if {[list {array} $traceCmd] ni [trace info variable $refname]} {
trace add variable $refname {array} $traceCmd
}
set traceCmd [list ::p::predator::object_write_trace $OID $_ID_]
if {[list {write} $traceCmd] ni [trace info variable $refname]} {
trace add variable $refname {write} $traceCmd
}
set traceCmd [list ::p::predator::object_unset_trace $OID $_ID_]
if {[list {unset} $traceCmd] ni [trace info variable $refname]} {
trace add variable $refname {unset} $traceCmd
}
#set command $refname
return $refname
} else {
#puts "- 11111111 '$command' '$stack'"
if {[string range $command 0 171] eq "::p::-1::"} {
#!todo - review/enable this branch?
#reference to meta-member
#STALE map problem!!
puts "\naaaaa command: $command\n"
set field [namespace tail [lindex $command 0]]
set map [lindex $stack 0]
set OID [lindex $map 0 0]
if {[llength $stack]} {
set refname ::p::${OID}::_ref::[join [concat $field [lrange $stack 1 end]] +]
set command [interp alias {} $refname {} {*}$command {*}$stack]
} else {
set refname ::p::${OID}::_ref::$field
set command [interp alias {} $refname {} {*}$command]
}
puts "???? command '$command' \n refname '$refname' \n"
} else {
#Property or Method reference (possibly with curried indices or arguments)
#we don't want our references to look like objects.
#(If they did, they might be found by namespace tidyup code and treated incorrectly)
set field [string map {> __OBJECT_} [namespace tail $command]]
#!todo?
# - make every object's OID unpredictable and sparse (UUID) and modify 'namespace child' etc to prevent iteration/inspection of ::p namespace.
#references created under ::p::${OID}::_ref are effectively inside a 'varspace' within the object itself.
# - this would in theory allow a set of interface functions on the object which have direct access to the reference variables.
if {[llength $stack]} {
set refname ::p::${OID}::_ref::[join [concat $field $stack] +]
#puts stdout " ------------>>>> refname:$refname"
if {[string length $_ID_]} {
set command [interp alias {} $refname {} $command $_ID_ {*}$stack]
} else {
set command [interp alias {} $refname {} $command {*}$stack]
}
} else {
set refname ::p::${OID}::_ref::$field
#!review - for consistency.. we don't directly return method name.
if {[string length $_ID_]} {
set command [interp alias {} $refname {} $command $_ID_]
} else {
set command [interp alias {} $refname {} $command]
}
}
#puts ">>>!>>>> refname $refname \n"
#NOTE! - we always create a command alias even if $field is not a method.
#(
#!todo? - build a list of properties from all interfaces (cache it on object??)
set iflist [lindex $map 1 0]
set found 0
foreach IFID [lreverse $iflist] {
#if {$field in [dict keys [set ::p::${IFID}::_iface::o_methods]]} {
# set found 1
# break
#}
if {[llength [info commands ::p::${IFID}::_iface::(GET)$field]] || [llength [info commands ::p::${IFID}::_iface::(SET)$field]]} {
set found 1
break
}
}
if {$found} {
#property reference
#?
#set readref [string map [list ::_ref:: ::_ref::(GET)
#set writeref [string map [list ::_ref:: ::_ref::(SET)
#puts "-2222222222 $refname"
#puts "---HERE! $OID $property ::p::${OID}::_ref::${property}"
#trace remove variable $refname {read write unset} [list ::p::internals::commandrefMisuse_TraceHandler $alias $field]
foreach tinfo [trace info variable $refname] {
#puts "-->removing traces on $refname: $tinfo"
if {[lindex $tinfo 1 0] eq "::p::internals::commandrefMisuse_TraceHandler"} {
trace remove variable $refname {*}$tinfo
}
}
#!todo - move to within trace info test below.. no need to test for refsync trace if no other trace?
#We only trace on entire property.. not array elements (if references existed to both the array and an element both traces would be fired -(entire array trace first))
set Hndlr [::list ::p::predator::refsync_TraceHandler $OID $alias "" $field]
if { [::list {write unset} $Hndlr] ni [trace info variable ::p::${OID}::o_${field}]} {
trace add variable ::p::${OID}::o_${field} {write unset} $Hndlr
}
set _ID_ [dict create i [dict create this [list [list $OID [list map $map] ]]]]
#supply all data in easy-access form so that prop_trace_read is not doing any extra work.
set get_cmd ::p::${OID}::(GET)$field
set traceCmd [list ::p::predator::prop_trace_read $get_cmd $_ID_ $refname $field $stack]
if {[list {read} $traceCmd] ni [trace info variable $refname]} {
#synch the refvar with the real var if it exists
#catch {set $refname [$refname]}
if {[array exists ::p::${OID}::o_$field]} {
if {![llength $stack]} {
#unindexed reference
array set $refname [array get ::p::${OID}::o_$field]
} else {
#refs to nonexistant array members common? (catch vs 'info exists')
if {[info exists ::p::${OID}::o_${field}([lindex $stack 0])]} {
set $refname [set ::p::${OID}::o_${field}([lindex $stack 0])]
}
}
} else {
#catch means retrieving refs to non-initialised props slightly slower.
set errorInfo_prev $::errorInfo ;#preserve errorInfo across these catches!
if {![llength $stack]} {
catch {set $refname [set ::p::${OID}::o_$field]}
} else {
if {[llength $stack] == 1} {
catch {set $refname [lindex [set ::p::${OID}::o_$field] [lindex $stack 0]]}
} else {
catch {set $refname [lindex [set ::p::${OID}::o_$field] $stack]}
}
}
#! what if someone has put a trace on ::errorInfo??
set ::errorInfo $errorInfo_prev
}
trace add variable $refname {read} $traceCmd
set traceCmd [list ::p::predator::prop_trace_write $_ID_ $OID $alias $refname]
trace add variable $refname {write} $traceCmd
set traceCmd [list ::p::predator::prop_trace_unset $_ID_ $OID $alias $refname]
trace add variable $refname {unset} $traceCmd
set traceCmd [list ::p::predator::prop_trace_array $_ID_ $OID $alias $refname]
trace add variable $refname {array} $traceCmd
}
} else {
#matching variable in order to detect attempted use as property and throw error
#puts "$refname ====> adding refMisuse_traceHandler $alias $field"
trace add variable $refname {read write unset} [list ::p::internals::commandrefMisuse_TraceHandler $alias $field]
}
}
return $command
}
}
#script to inline at placeholder @reduce_pending_stack@
set ::p::internals::reduce_pending_stack {
if {$pending eq {idx}} {
if {$OID ne {null}} {
#pattern object
#set command [list ::p::${OID}::$itemCmd [dict create i [dict create this [list $OID]]]]
set command ::p::${OID}::$itemCmd
set _ID_ [dict create i [dict create this [list [list $OID [list map $map]]]]]
#todo: set _ID_ [dict create i [dict create this [list [list $OID - - - {}]]] context {}]
} else {
set command [list $itemCmd $command]
}
}
if {![llength [info commands [lindex $command 0]]]} {
set cmdname [namespace tail [lindex $command 0]]
if {[llength [info commands ::p::${OID}::(UNKNOWN)]]} {
lset command 0 ::p::${OID}::(UNKNOWN)
#puts stdout "!\n calling UNKNOWN $command $cmdname $stack\n\n"
if {[string length $_ID_]} {
set interim [uplevel 1 [list {*}$command $_ID_ $cmdname {*}$stack]] ;#delegate to UNKNOWN, along with original commandname as 1st arg.
} else {
set interim [uplevel 1 [list {*}$command $cmdname {*}$stack]] ;#delegate to UNKNOWN, along with original commandname as 1st arg.
}
} else {
return -code error -errorinfo "1)error running command:'$command' argstack:'$stack'\n - command not found and no 'unknown' handler" "method '$command' not found"
}
} else {
#puts "---??? uplevelling $command $_ID_ $stack"
if {[string length $_ID_]} {
set interim [uplevel 1 [list {*}$command $_ID_ {*}$stack]]
} else {
set interim [uplevel 1 [list {*}$command {*}$stack]]
}
#puts "---?2? interim:$interim"
}
if {[string first ::> $interim] >= 0} {
#puts "--- ---> tailcalling $interim [lrange $args $i end]"
tailcall {*}$interim {*}[lrange $args $i end] ;#don't use a return statement as tailcall implies return
} else {
#the interim result is not a pattern object - but the . indicates we should treat it as a command
#tailcall ::p::internals::predator [list [list {null} {} {lindex} $interim {}]] {*}[lrange $args $i end]
#set nextmap [list [list {null} {} {lindex} $interim {}]]
#tailcall ::p::internals::predator [dict create i [dict create this [list null ]] map $nextmap] {*}[lrange $args $i end]
#tailcall ::p::internals::predator [dict create i [dict create this [list [list null [list]] ]] xmap $nextmap] {*}[lrange $args $i end]
tailcall ::p::internals::predator [dict create i [dict create this [list [list null [list map [list [list {null} {} {lindex} $interim {}]]]] ]] callerinfo ""] {*}[lrange $args $i end]
}
}
proc ::p::predator1 {subject args} [string map [list @reduce_pending_stack@ $::p::internals::reduce_pending_stack] {
#set OID [lindex [dict get $subject i this] 0 0]
set this_invocant [lindex [dict get $subject i this] 0] ;#for the role 'this' we assume only one invocant in the list.
lassign $this_invocant OID this_info
if {$OID ne {null}} {
#upvar #0 ::p::${OID}::_meta::map map
#if {![dict exists [lindex [dict get $subject i this] 0 1] map]} {
# set map [set ::p::${OID}::_meta::map]
#} else {
# set map [dict get [lindex [dict get $subject i this] 0 1] map]
#}
#seems to be faster just to grab from the variable, than to unwrap from $_ID_ !?
#set map [set ::p::${OID}::_meta::map]
# if {![dict exists $this_info map]} {
set map [set ::p::${OID}::_meta::map]
#} else {
# set map [dict get $this_info map]
#}
lassign [lindex $map 0] OID alias itemCmd cmd
set cheat 1
#-------
#the common optimised case first. A single . followed by args which contain no other operators (non-chained call)
#(it should be functionally equivalent to remove this shortcut block)
if {$cheat} {
if {([llength $args] > 1) && ([lindex $args 0] eq {.}) && ([llength [lsearch -all $args .]] == 1) && ({,} ni $args) && ({..} ni $args) && ({--} ni $args)} {
set command ::p::${OID}::[lindex $args 1]
if {![llength [info commands $command]]} {
if {[llength [info commands ::p::${OID}::(UNKNOWN)]]} {
set cmdname [namespace tail $command]
lset command 0 ::p::${OID}::(UNKNOWN)
#return [uplevel 1 [list {*}$command [dict create i [dict create this [list [list $OID [list map $map]]]]] $cmdname {*}[lrange $args 2 end]]] ;#delegate to UNKNOWN, along with original commandname as 1st arg.
tailcall {*}$command [dict create i [dict create this [list [list $OID [list map [list [lindex $map 0] {{} {}} ] ] ]]]] $cmdname {*}[lrange $args 2 end] ;#delegate to UNKNOWN, along with original commandname as 1st arg.
} else {
return -code error -errorinfo "2)error running command:'$command' argstack:'[lrange $args 2 end]'\n - command not found and no 'unknown' handler" "method '$command' not found"
}
} else {
#puts " -->> tailcalling $command [lrange $args 2 end]"
#tailcall $command [dict create i [dict create this [list [list $OID [list map $map]] ]]] {*}[lrange $args 2 end]
#tailcall $command [dict create i [dict create this [list [list $OID {}] ]]] {*}[lrange $args 2 end]
#jjj
#tailcall $command [list i [list this [list [list $OID [list map $map]] ]]] {*}[lrange $args 2 end]
tailcall $command [list i [list this [list [list $OID [list map [list [lindex $map 0] { {} {} } ]]] ]]] {*}[lrange $args 2 end]
}
}
}
#------------
if {![llength $args]} {
#return $map
return [lindex $map 0 1]
} elseif {[llength $args] == 1} {
#short-circuit the single index case for speed.
if {$args ni {.. . -- - & @}} {
if {$cheat} {
lassign [lindex $map 0] OID alias itemCmd
#return [::p::${OID}::$itemCmd [lindex $args 0]]
#tailcall ::p::${OID}::$itemCmd [dict create i [dict create this [list [list $OID [list map $map]]]]] [lindex $args 0]
tailcall ::p::${OID}::$itemCmd [list i [list this [list [list $OID [list map $map]]]]] [lindex $args 0]
}
} elseif {[lindex $args 0] eq {--}} {
#!todo - we could hide the invocant by only allowing this call from certain uplevel procs..
# - combined with using UUIDs for $OID, and a secured/removed metaface on the object
# - (and also hiding of [interp aliases] command so they can't iterate and examine all aliases)
# - this could effectively hide the object's namespaces,vars etc from the caller (?)
return $map
}
}
} else {
#null OID - assume map is included in the _ID_ dict.
#set map [dict get $subject map]
set map [dict get $this_info map]
lassign [lindex $map 0] OID alias itemCmd cmd
}
#puts "predator==== subject:$subject args:$args map:$map cmd:$cmd "
#set map $invocant ;#retain 'invocant' in case we need original caller info.. 'map' is the data for whatever is at base of stack.
set command $cmd
set stack [list]
#set operators [list . , ..] ;#(exclude --)
#!todo? short-circuit/inline commonest/simplest case {llength $args == 2}
set argProtect 0
set pending "" ;#pending operator e.g . , idx .. & @
set _ID_ ""
set i 0
while {$i < [llength $args]} {
set word [lindex $args $i]
if {$argProtect} {
#argProtect must be checked first.
# We are here because a previous operator necessitates that this word is an argument, not another operator.
set argProtect 0
lappend stack $word
if {$pending eq {}} {
set pending idx ;#An argument only implies the index operator if no existing operator is in effect. (because '>obj $arg' must be equivalent to '>obj . item $arg'
}
incr i
} else {
switch -- $word {.} {
#$i is the operator, $i + 1 is the command.
if {[llength $args] > ($i + 1)} {
#there is at least a command, possibly args too
if {$pending ne {}} {
#puts ">>>> >>>>> about to reduce. pending:$pending command:$command _ID_:$_ID_ stack:$stack"
#always bounces back into the predator via tailcall
@reduce_pending_stack@
} else {
if {$OID ne {null}} {
#set command ::p::${OID}::[lindex $args $i+1]
#lappend stack [dict create i [dict create this [list $OID]]]
set command ::p::${OID}::[lindex $args $i+1]
set _ID_ [list i [list this [list [list $OID [list map $map]]]]]
} else {
#set command [list $command [lindex $args $i+1]]
lappend stack [lindex $args $i+1]
}
set pending .
set argProtect 0
incr i 2
}
} else {
#this is a trailing .
#puts "----> MAP $map ,command $command ,stack $stack"
if {$OID ne {null}} {
return [::p::internals::trailing. $map $command $_ID_ $stack $i $args $pending]
} else {
#!todo - fix. This is broken!
#the usefulness of a reference to an already-resolved value is questionable.. but for consistency it should probably be made to work.
#for a null object - we need to supply the map in the invocation data
set command ::p::internals::predator
set this_info [dict create map [list [list {null} {} {lindex} $command {}]] ]
set this_invocant [list null $this_info]
set _ID_ [dict create i [dict create this [list $this_invocant]] ]
return [::p::internals::trailing. $map $command $_ID_ $stack $i $args $pending]
}
}
} {--} {
#argSafety operator (see also "," & -* below)
set argProtect 1
incr i
} {,} {
set argProtect 1
if {$i+1 < [llength $args]} {
#not trailing
if {$pending ne {}} {
@reduce_pending_stack@
} else {
if {$OID ne {null}} {
#set command [list ::p::[lindex $map 0 0]::$itemCmd [lindex $args $i+1]]
#set command [list $command . $itemCmd [lindex $args $i+1]]
set stack [list . $itemCmd [lindex $args $i+1]]
set _ID_ ""
#lappend stack [dict create i [dict create this [list $OID]]]
set pending "."
} else {
# this is an index operation into what is presumed to be a previously returned standard tcl list. (as opposed to an index operation into a returned pattern object)
#set command [list $itemCmd $command [lindex $args $i+1]]
#set command [list ::p::internals::predator [list [list {null} {} {lindex} $command {}]] [lindex $args $i+1] ]
#set command ::p::internals::predator
#set _ID_ [dict create i [dict create this [list null]] map [list [list {null} {} {lindex} [lindex $args $i+1] {}]] ]
#lappend stack [lindex $args $i+1]
set command [list $itemCmd $command] ;#e.g {lindex {a b c}}
#set command ::p::internals::predator
#set _ID_ [dict create i [dict create this [list null]] map [list [list {null} {} {lindex} $nextcommand {}]]]
set _ID_ {}
lappend stack [lindex $args $i+1]
set pending "." ;#*not* idx or ","
}
set argProtect 0
incr i 2
}
} else {
return [::p::internals::trailing, $map $command $stack $i $args $pending]
}
} {..} {
#Metaface operator
if {$i+1 < [llength $args]} {
#operator is not trailing.
if {$pending ne {}} {
@reduce_pending_stack@
} else {
incr i
#set command [list ::p::-1::[lindex $args $i] [dict create i [dict create this [list $OID]]]]
set command ::p::-1::[lindex $args $i]
#_ID_ is a list, 1st element being a dict of invocants.
# Each key of the dict is an invocant 'role'
# Each value is a list of invocant-aliases fulfilling that role
#lappend stack [list [list caller [lindex $map 0 1] ]]
#lappend stack [list [dict create this [lindex $map 0 1]]] ;#'this' being the 'subject' in a standard singledispatch method call.
#lappend stack [dict create i [dict create this [list [lindex $map 0 1]]]]
set _ID_ [dict create i [dict create this [list [list $OID [list map $map]]]]]
set pending ..
incr i
}
} else {
return [::p::internals::trailing.. $map $command $stack $i $args $pending]
}
} {&} {
#conglomeration operator
if {$i+1 < [llength $args]} {
if {$pending ne {} } {
@reduce_pending_stack@
#set interim [uplevel 1 [list {*}$command {*}$stack]]
#tailcall {*}$interim {*}[lrange $args $i end] ;#don't use a return statement as tailcall implies return
}
set command [list ::p::-1::Conglomerate $command]
lappend stack [lindex $args $i+1]
set pending &
incr i
} else {
error "trailing & not supported"
}
} {@} {
#named-invocant operator
if {$i+1 < [llength $args]} {
if {$pending ne {} } {
@reduce_pending_stack@
} else {
error "@ not implemented"
set pending @
incr i
}
} else {
error "trailing @ not supported"
}
} default {
if {[string index $word 0] ni {. -}} {
lappend stack $word
if {$pending eq {}} {
set pending idx
}
incr i
} else {
if {[string match "-*" $word]} {
#argSafety operator (tokens that appear to be Tcl 'options' automatically 'protect' the subsequent argument)
set argProtect 1
lappend stack $word
incr i
} else {
if {([string index $word 0] eq ".") && ([string index $word end] eq ".") } {
#interface accessor!
error "interface casts not yet implemented!"
set ifspec [string range $word 1 end]
if {$ifspec eq "!"} {
#create 'snapshot' reference with all current interfaces
} else {
foreach ifname [split $ifspec ,] {
#make each comma-separated interface-name accessible via the 'casted object'
}
}
} else {
#has a leading . only. treat as an argument not an operator.
lappend stack $word
if {$pending eq {}} {
set pending idx
}
incr i
}
}
}
}
}
}
#assert: $pending ne ""
#(we've run out of args, and if no 'operators' were found, then $pending will have been set to 'idx' - equivalent to '. item' )
#puts "---> evalling command + stack:'$command' '$stack' (pending:'$pending')"
if {$pending in {idx}} {
if {$OID ne {null}} {
#pattern object
set command ::p::${OID}::$itemCmd
set _ID_ [dict create i [dict create this [list [list $OID [list map $map] ] ]]]
} else {
# some other kind of command
set command [list $itemCmd $command]
}
}
if {![llength [info commands [lindex $command 0]]]} {
set cmdname [namespace tail [lindex $command 0]]
if {[llength [info commands ::p::${OID}::(UNKNOWN)]]} {
lset command 0 ::p::${OID}::(UNKNOWN)
#puts stdout "!\n calling UNKNOWN $command $cmdname $stack\n\n"
if {[string length $_ID_]} {
tailcall {*}$command $_ID_ $cmdname {*}$stack ;#delegate to UNKNOWN, along with original commandname as 1st arg.
} else {
tailcall {*}$command $cmdname {*}$stack ;#delegate to UNKNOWN, along with original commandname as 1st arg.
}
} else {
return -code error -errorinfo "3)error running command:'$command' argstack:'$stack'\n - command not found and no 'unknown' handler" "method '$command' not found"
}
}
#puts "... tailcalling $command $stack"
if {[string length $_ID_]} {
tailcall {*}$command $_ID_ {*}$stack
} else {
tailcall {*}$command {*}$stack
}
}]

664
src/vendormodules/patternpredator1-1.2.4.tm

@ -0,0 +1,664 @@
package provide patternpredator1 1.2.4
proc ::p::internals::trailing, {map command stack i arglist pending} {
error "trailing ',' is not a valid sequence. If the comma was meant to be an argument, precede it with the -- operator."
}
proc ::p::internals::trailing.. {map command stack i arglist pending} {
error "trailing .. references not implemented."
}
proc ::p::internals::trailing. {map command _ID_ stack i arglist pending} {
if {![llength $map]} {
error "Trailing . but no map - command:'$command' stack:$stack i:$i arglist:$arglist pending:$pending"
}
#trailing dot - get reference.
#puts ">>trailing dot>> map:$map command:$command _ID_:$_ID_ stack:$stack i:$i arglist:$arglist pending:$pending"
lassign [lindex $map 0] OID alias itemCmd cmd
#lassign $command command _ID_
if {$pending eq {}} {
#no pending operation requiring evaluation.
#presumably we're getting a ref to the object, not a property or method.
#set traceCmd [::list ::p::objectRef_TraceHandler $cmd $_self $OID]
#if {[::list {array read write unset} $traceCmd] ni [trace info variable $refname]} {
# trace add variable $refname {array read write unset} $traceCmd
#}
set refname ::p::${OID}::_ref::__OBJECT ;#!todo - avoid potential collision with ref to member named '__OBJECT'.
#object ref - ensure trace-var is an array upon which the objects methods & properties can be accessed as indices
array set $refname [list]
#!todo?- populate array with object methods/properties now?
set _ID_ [list i [list this [list [list $OID [list map $map]]]]]
#!todo - review. What if $map is out of date?
set traceCmd [list ::p::predator::object_read_trace $OID $_ID_]
if {[list {read} $traceCmd] ni [trace info variable $refname]} {
trace add variable $refname {read} $traceCmd
}
set traceCmd [list ::p::predator::object_array_trace $OID $_ID_]
if {[list {array} $traceCmd] ni [trace info variable $refname]} {
trace add variable $refname {array} $traceCmd
}
set traceCmd [list ::p::predator::object_write_trace $OID $_ID_]
if {[list {write} $traceCmd] ni [trace info variable $refname]} {
trace add variable $refname {write} $traceCmd
}
set traceCmd [list ::p::predator::object_unset_trace $OID $_ID_]
if {[list {unset} $traceCmd] ni [trace info variable $refname]} {
trace add variable $refname {unset} $traceCmd
}
#set command $refname
return $refname
} else {
#puts "- 11111111 '$command' '$stack'"
if {[string range $command 0 171] eq "::p::-1::"} {
#!todo - review/enable this branch?
#reference to meta-member
#STALE map problem!!
puts "\naaaaa command: $command\n"
set field [namespace tail [lindex $command 0]]
set map [lindex $stack 0]
set OID [lindex $map 0 0]
if {[llength $stack]} {
set refname ::p::${OID}::_ref::[join [concat $field [lrange $stack 1 end]] +]
set command [interp alias {} $refname {} {*}$command {*}$stack]
} else {
set refname ::p::${OID}::_ref::$field
set command [interp alias {} $refname {} {*}$command]
}
puts "???? command '$command' \n refname '$refname' \n"
} else {
#Property or Method reference (possibly with curried indices or arguments)
#we don't want our references to look like objects.
#(If they did, they might be found by namespace tidyup code and treated incorrectly)
set field [string map {> __OBJECT_} [namespace tail $command]]
#!todo?
# - make every object's OID unpredictable and sparse (UUID) and modify 'namespace child' etc to prevent iteration/inspection of ::p namespace.
#references created under ::p::${OID}::_ref are effectively inside a 'varspace' within the object itself.
# - this would in theory allow a set of interface functions on the object which have direct access to the reference variables.
if {[llength $stack]} {
set refname ::p::${OID}::_ref::[join [concat $field $stack] +]
#puts stdout " ------------>>>> refname:$refname"
if {[string length $_ID_]} {
set command [interp alias {} $refname {} $command $_ID_ {*}$stack]
} else {
set command [interp alias {} $refname {} $command {*}$stack]
}
} else {
set refname ::p::${OID}::_ref::$field
#!review - for consistency.. we don't directly return method name.
if {[string length $_ID_]} {
set command [interp alias {} $refname {} $command $_ID_]
} else {
set command [interp alias {} $refname {} $command]
}
}
#puts ">>>!>>>> refname $refname \n"
#NOTE! - we always create a command alias even if $field is not a method.
#(
#!todo? - build a list of properties from all interfaces (cache it on object??)
set iflist [lindex $map 1 0]
set found 0
foreach IFID [lreverse $iflist] {
#if {$field in [dict keys [set ::p::${IFID}::_iface::o_methods]]} {
# set found 1
# break
#}
if {[llength [info commands ::p::${IFID}::_iface::(GET)$field]] || [llength [info commands ::p::${IFID}::_iface::(SET)$field]]} {
set found 1
break
}
}
if {$found} {
#property reference
#?
#set readref [string map [list ::_ref:: ::_ref::(GET)
#set writeref [string map [list ::_ref:: ::_ref::(SET)
#puts "-2222222222 $refname"
#puts "---HERE! $OID $property ::p::${OID}::_ref::${property}"
#trace remove variable $refname {read write unset} [list ::p::internals::commandrefMisuse_TraceHandler $alias $field]
foreach tinfo [trace info variable $refname] {
#puts "-->removing traces on $refname: $tinfo"
if {[lindex $tinfo 1 0] eq "::p::internals::commandrefMisuse_TraceHandler"} {
trace remove variable $refname {*}$tinfo
}
}
#!todo - move to within trace info test below.. no need to test for refsync trace if no other trace?
#We only trace on entire property.. not array elements (if references existed to both the array and an element both traces would be fired -(entire array trace first))
set Hndlr [::list ::p::predator::refsync_TraceHandler $OID $alias "" $field]
if { [::list {write unset} $Hndlr] ni [trace info variable ::p::${OID}::o_${field}]} {
trace add variable ::p::${OID}::o_${field} {write unset} $Hndlr
}
set _ID_ [dict create i [dict create this [list [list $OID [list map $map] ]]]]
#supply all data in easy-access form so that prop_trace_read is not doing any extra work.
set get_cmd ::p::${OID}::(GET)$field
set traceCmd [list ::p::predator::prop_trace_read $get_cmd $_ID_ $refname $field $stack]
if {[list {read} $traceCmd] ni [trace info variable $refname]} {
#synch the refvar with the real var if it exists
#catch {set $refname [$refname]}
if {[array exists ::p::${OID}::o_$field]} {
if {![llength $stack]} {
#unindexed reference
array set $refname [array get ::p::${OID}::o_$field]
} else {
#refs to nonexistant array members common? (catch vs 'info exists')
if {[info exists ::p::${OID}::o_${field}([lindex $stack 0])]} {
set $refname [set ::p::${OID}::o_${field}([lindex $stack 0])]
}
}
} else {
#catch means retrieving refs to non-initialised props slightly slower.
set errorInfo_prev $::errorInfo ;#preserve errorInfo across these catches!
if {![llength $stack]} {
catch {set $refname [set ::p::${OID}::o_$field]}
} else {
if {[llength $stack] == 1} {
catch {set $refname [lindex [set ::p::${OID}::o_$field] [lindex $stack 0]]}
} else {
catch {set $refname [lindex [set ::p::${OID}::o_$field] $stack]}
}
}
#! what if someone has put a trace on ::errorInfo??
set ::errorInfo $errorInfo_prev
}
trace add variable $refname {read} $traceCmd
set traceCmd [list ::p::predator::prop_trace_write $_ID_ $OID $alias $refname]
trace add variable $refname {write} $traceCmd
set traceCmd [list ::p::predator::prop_trace_unset $_ID_ $OID $alias $refname]
trace add variable $refname {unset} $traceCmd
set traceCmd [list ::p::predator::prop_trace_array $_ID_ $OID $alias $refname]
trace add variable $refname {array} $traceCmd
}
} else {
#matching variable in order to detect attempted use as property and throw error
#puts "$refname ====> adding refMisuse_traceHandler $alias $field"
trace add variable $refname {read write unset} [list ::p::internals::commandrefMisuse_TraceHandler $alias $field]
}
}
return $command
}
}
#script to inline at placeholder @reduce_pending_stack@
set ::p::internals::reduce_pending_stack {
if {$pending eq {idx}} {
if {$OID ne {null}} {
#pattern object
#set command [list ::p::${OID}::$itemCmd [dict create i [dict create this [list $OID]]]]
set command ::p::${OID}::$itemCmd
set _ID_ [dict create i [dict create this [list [list $OID [list map $map]]]]]
#todo: set _ID_ [dict create i [dict create this [list [list $OID - - - {}]]] context {}]
} else {
set command [list $itemCmd $command]
}
}
if {![llength [info commands [lindex $command 0]]]} {
set cmdname [namespace tail [lindex $command 0]]
if {[llength [info commands ::p::${OID}::(UNKNOWN)]]} {
lset command 0 ::p::${OID}::(UNKNOWN)
#puts stdout "!\n calling UNKNOWN $command $cmdname $stack\n\n"
if {[string length $_ID_]} {
set interim [uplevel 1 [list {*}$command $_ID_ $cmdname {*}$stack]] ;#delegate to UNKNOWN, along with original commandname as 1st arg.
} else {
set interim [uplevel 1 [list {*}$command $cmdname {*}$stack]] ;#delegate to UNKNOWN, along with original commandname as 1st arg.
}
} else {
return -code error -errorinfo "1)error running command:'$command' argstack:'$stack'\n - command not found and no 'unknown' handler" "method '$command' not found"
}
} else {
#puts "---??? uplevelling $command $_ID_ $stack"
if {[string length $_ID_]} {
set interim [uplevel 1 [list {*}$command $_ID_ {*}$stack]]
} else {
set interim [uplevel 1 [list {*}$command {*}$stack]]
}
#puts "---?2? interim:$interim"
}
if {[string first ::> $interim] >= 0} {
#puts "--- ---> tailcalling $interim [lrange $args $i end]"
tailcall {*}$interim {*}[lrange $args $i end] ;#don't use a return statement as tailcall implies return
} else {
#the interim result is not a pattern object - but the . indicates we should treat it as a command
#tailcall ::p::internals::predator [list [list {null} {} {lindex} $interim {}]] {*}[lrange $args $i end]
#set nextmap [list [list {null} {} {lindex} $interim {}]]
#tailcall ::p::internals::predator [dict create i [dict create this [list null ]] map $nextmap] {*}[lrange $args $i end]
#tailcall ::p::internals::predator [dict create i [dict create this [list [list null [list]] ]] xmap $nextmap] {*}[lrange $args $i end]
tailcall ::p::internals::predator [dict create i [dict create this [list [list null [list map [list [list {null} {} {lindex} $interim {}]]]] ]] callerinfo ""] {*}[lrange $args $i end]
}
}
proc ::p::predator1 {subject args} [string map [list @reduce_pending_stack@ $::p::internals::reduce_pending_stack] {
#set OID [lindex [dict get $subject i this] 0 0]
set this_invocant [lindex [dict get $subject i this] 0] ;#for the role 'this' we assume only one invocant in the list.
lassign $this_invocant OID this_info
if {$OID ne {null}} {
#upvar #0 ::p::${OID}::_meta::map map
#if {![dict exists [lindex [dict get $subject i this] 0 1] map]} {
# set map [set ::p::${OID}::_meta::map]
#} else {
# set map [dict get [lindex [dict get $subject i this] 0 1] map]
#}
#seems to be faster just to grab from the variable, than to unwrap from $_ID_ !?
#set map [set ::p::${OID}::_meta::map]
# if {![dict exists $this_info map]} {
set map [set ::p::${OID}::_meta::map]
#} else {
# set map [dict get $this_info map]
#}
lassign [lindex $map 0] OID alias itemCmd cmd
set cheat 1
#-------
#the common optimised case first. A single . followed by args which contain no other operators (non-chained call)
#(it should be functionally equivalent to remove this shortcut block)
if {$cheat} {
if {([llength $args] > 1) && ([lindex $args 0] eq {.}) && ([llength [lsearch -all $args .]] == 1) && ({,} ni $args) && ({..} ni $args) && ({--} ni $args)} {
set command ::p::${OID}::[lindex $args 1]
if {![llength [info commands $command]]} {
if {[llength [info commands ::p::${OID}::(UNKNOWN)]]} {
set cmdname [namespace tail $command]
lset command 0 ::p::${OID}::(UNKNOWN)
#return [uplevel 1 [list {*}$command [dict create i [dict create this [list [list $OID [list map $map]]]]] $cmdname {*}[lrange $args 2 end]]] ;#delegate to UNKNOWN, along with original commandname as 1st arg.
tailcall {*}$command [dict create i [dict create this [list [list $OID [list map [list [lindex $map 0] {{} {}} ] ] ]]]] $cmdname {*}[lrange $args 2 end] ;#delegate to UNKNOWN, along with original commandname as 1st arg.
} else {
return -code error -errorinfo "2)error running command:'$command' argstack:'[lrange $args 2 end]'\n - command not found and no 'unknown' handler" "method '$command' not found"
}
} else {
#puts " -->> tailcalling $command [lrange $args 2 end]"
#tailcall $command [dict create i [dict create this [list [list $OID [list map $map]] ]]] {*}[lrange $args 2 end]
#tailcall $command [dict create i [dict create this [list [list $OID {}] ]]] {*}[lrange $args 2 end]
#jjj
#tailcall $command [list i [list this [list [list $OID [list map $map]] ]]] {*}[lrange $args 2 end]
tailcall $command [list i [list this [list [list $OID [list map [list [lindex $map 0] { {} {} } ]]] ]]] {*}[lrange $args 2 end]
}
}
}
#------------
if {![llength $args]} {
#return $map
return [lindex $map 0 1]
} elseif {[llength $args] == 1} {
#short-circuit the single index case for speed.
if {$args ni {.. . -- - & @}} {
if {$cheat} {
lassign [lindex $map 0] OID alias itemCmd
#return [::p::${OID}::$itemCmd [lindex $args 0]]
#tailcall ::p::${OID}::$itemCmd [dict create i [dict create this [list [list $OID [list map $map]]]]] [lindex $args 0]
tailcall ::p::${OID}::$itemCmd [list i [list this [list [list $OID [list map $map]]]]] [lindex $args 0]
}
} elseif {[lindex $args 0] eq {--}} {
#!todo - we could hide the invocant by only allowing this call from certain uplevel procs..
# - combined with using UUIDs for $OID, and a secured/removed metaface on the object
# - (and also hiding of [interp aliases] command so they can't iterate and examine all aliases)
# - this could effectively hide the object's namespaces,vars etc from the caller (?)
return $map
}
}
} else {
#null OID - assume map is included in the _ID_ dict.
#set map [dict get $subject map]
set map [dict get $this_info map]
lassign [lindex $map 0] OID alias itemCmd cmd
}
#puts "predator==== subject:$subject args:$args map:$map cmd:$cmd "
#set map $invocant ;#retain 'invocant' in case we need original caller info.. 'map' is the data for whatever is at base of stack.
set command $cmd
set stack [list]
#set operators [list . , ..] ;#(exclude --)
#!todo? short-circuit/inline commonest/simplest case {llength $args == 2}
set argProtect 0
set pending "" ;#pending operator e.g . , idx .. & @
set _ID_ ""
set i 0
while {$i < [llength $args]} {
set word [lindex $args $i]
if {$argProtect} {
#argProtect must be checked first.
# We are here because a previous operator necessitates that this word is an argument, not another operator.
set argProtect 0
lappend stack $word
if {$pending eq {}} {
set pending idx ;#An argument only implies the index operator if no existing operator is in effect. (because '>obj $arg' must be equivalent to '>obj . item $arg'
}
incr i
} else {
switch -- $word {.} {
#$i is the operator, $i + 1 is the command.
if {[llength $args] > ($i + 1)} {
#there is at least a command, possibly args too
if {$pending ne {}} {
#puts ">>>> >>>>> about to reduce. pending:$pending command:$command _ID_:$_ID_ stack:$stack"
#always bounces back into the predator via tailcall
@reduce_pending_stack@
} else {
if {$OID ne {null}} {
#set command ::p::${OID}::[lindex $args $i+1]
#lappend stack [dict create i [dict create this [list $OID]]]
set command ::p::${OID}::[lindex $args $i+1]
set _ID_ [list i [list this [list [list $OID [list map $map]]]]]
} else {
#set command [list $command [lindex $args $i+1]]
lappend stack [lindex $args $i+1]
}
set pending .
set argProtect 0
incr i 2
}
} else {
#this is a trailing .
#puts "----> MAP $map ,command $command ,stack $stack"
if {$OID ne {null}} {
return [::p::internals::trailing. $map $command $_ID_ $stack $i $args $pending]
} else {
#!todo - fix. This is broken!
#the usefulness of a reference to an already-resolved value is questionable.. but for consistency it should probably be made to work.
#for a null object - we need to supply the map in the invocation data
set command ::p::internals::predator
set this_info [dict create map [list [list {null} {} {lindex} $command {}]] ]
set this_invocant [list null $this_info]
set _ID_ [dict create i [dict create this [list $this_invocant]] ]
return [::p::internals::trailing. $map $command $_ID_ $stack $i $args $pending]
}
}
} {--} {
#argSafety operator (see also "," & -* below)
set argProtect 1
incr i
} {,} {
set argProtect 1
if {$i+1 < [llength $args]} {
#not trailing
if {$pending ne {}} {
@reduce_pending_stack@
} else {
if {$OID ne {null}} {
#set command [list ::p::[lindex $map 0 0]::$itemCmd [lindex $args $i+1]]
#set command [list $command . $itemCmd [lindex $args $i+1]]
set stack [list . $itemCmd [lindex $args $i+1]]
set _ID_ ""
#lappend stack [dict create i [dict create this [list $OID]]]
set pending "."
} else {
# this is an index operation into what is presumed to be a previously returned standard tcl list. (as opposed to an index operation into a returned pattern object)
#set command [list $itemCmd $command [lindex $args $i+1]]
#set command [list ::p::internals::predator [list [list {null} {} {lindex} $command {}]] [lindex $args $i+1] ]
#set command ::p::internals::predator
#set _ID_ [dict create i [dict create this [list null]] map [list [list {null} {} {lindex} [lindex $args $i+1] {}]] ]
#lappend stack [lindex $args $i+1]
set command [list $itemCmd $command] ;#e.g {lindex {a b c}}
#set command ::p::internals::predator
#set _ID_ [dict create i [dict create this [list null]] map [list [list {null} {} {lindex} $nextcommand {}]]]
set _ID_ {}
lappend stack [lindex $args $i+1]
set pending "." ;#*not* idx or ","
}
set argProtect 0
incr i 2
}
} else {
return [::p::internals::trailing, $map $command $stack $i $args $pending]
}
} {..} {
#Metaface operator
if {$i+1 < [llength $args]} {
#operator is not trailing.
if {$pending ne {}} {
@reduce_pending_stack@
} else {
incr i
#set command [list ::p::-1::[lindex $args $i] [dict create i [dict create this [list $OID]]]]
set command ::p::-1::[lindex $args $i]
#_ID_ is a list, 1st element being a dict of invocants.
# Each key of the dict is an invocant 'role'
# Each value is a list of invocant-aliases fulfilling that role
#lappend stack [list [list caller [lindex $map 0 1] ]]
#lappend stack [list [dict create this [lindex $map 0 1]]] ;#'this' being the 'subject' in a standard singledispatch method call.
#lappend stack [dict create i [dict create this [list [lindex $map 0 1]]]]
set _ID_ [dict create i [dict create this [list [list $OID [list map $map]]]]]
set pending ..
incr i
}
} else {
return [::p::internals::trailing.. $map $command $stack $i $args $pending]
}
} {&} {
#conglomeration operator
if {$i+1 < [llength $args]} {
if {$pending ne {} } {
@reduce_pending_stack@
#set interim [uplevel 1 [list {*}$command {*}$stack]]
#tailcall {*}$interim {*}[lrange $args $i end] ;#don't use a return statement as tailcall implies return
}
set command [list ::p::-1::Conglomerate $command]
lappend stack [lindex $args $i+1]
set pending &
incr i
} else {
error "trailing & not supported"
}
} {@} {
#named-invocant operator
if {$i+1 < [llength $args]} {
if {$pending ne {} } {
@reduce_pending_stack@
} else {
error "@ not implemented"
set pending @
incr i
}
} else {
error "trailing @ not supported"
}
} default {
if {[string index $word 0] ni {. -}} {
lappend stack $word
if {$pending eq {}} {
set pending idx
}
incr i
} else {
if {[string match "-*" $word]} {
#argSafety operator (tokens that appear to be Tcl 'options' automatically 'protect' the subsequent argument)
set argProtect 1
lappend stack $word
incr i
} else {
if {([string index $word 0] eq ".") && ([string index $word end] eq ".") } {
#interface accessor!
error "interface casts not yet implemented!"
set ifspec [string range $word 1 end]
if {$ifspec eq "!"} {
#create 'snapshot' reference with all current interfaces
} else {
foreach ifname [split $ifspec ,] {
#make each comma-separated interface-name accessible via the 'casted object'
}
}
} else {
#has a leading . only. treat as an argument not an operator.
lappend stack $word
if {$pending eq {}} {
set pending idx
}
incr i
}
}
}
}
}
}
#assert: $pending ne ""
#(we've run out of args, and if no 'operators' were found, then $pending will have been set to 'idx' - equivalent to '. item' )
#puts "---> evalling command + stack:'$command' '$stack' (pending:'$pending')"
if {$pending in {idx}} {
if {$OID ne {null}} {
#pattern object
set command ::p::${OID}::$itemCmd
set _ID_ [dict create i [dict create this [list [list $OID [list map $map] ] ]]]
} else {
# some other kind of command
set command [list $itemCmd $command]
}
}
if {![llength [info commands [lindex $command 0]]]} {
set cmdname [namespace tail [lindex $command 0]]
if {[llength [info commands ::p::${OID}::(UNKNOWN)]]} {
lset command 0 ::p::${OID}::(UNKNOWN)
#puts stdout "!\n calling UNKNOWN $command $cmdname $stack\n\n"
if {[string length $_ID_]} {
tailcall {*}$command $_ID_ $cmdname {*}$stack ;#delegate to UNKNOWN, along with original commandname as 1st arg.
} else {
tailcall {*}$command $cmdname {*}$stack ;#delegate to UNKNOWN, along with original commandname as 1st arg.
}
} else {
return -code error -errorinfo "3)error running command:'$command' argstack:'$stack'\n - command not found and no 'unknown' handler" "method '$command' not found"
}
}
#puts "... tailcalling $command $stack"
if {[string length $_ID_]} {
tailcall {*}$command $_ID_ {*}$stack
} else {
tailcall {*}$command {*}$stack
}
}]

BIN
src/vendormodules/treeobj-1.3.1.tm

Binary file not shown.
Loading…
Cancel
Save