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.
 
 
 
 
 
 

911 lines
32 KiB

#PATTERN
# - A prototype-based Object system.
#
# Julian Noble 2003
# License: Public domain
#
# "I need pattern" - Lexx Series 1 Episode 3 - Eating Pattern.
#
#
# Pattern uses a mixture of class-based and prototype-based object instantiation.
#
# A pattern object has 'properties' and 'methods'
# The system makes a distinction between them with regards to the access syntax for write operations,
# and yet provides unity in access syntax for read operations.
# e.g >object . myProperty
# will return the value of the property 'myProperty'
# >ojbect . myMethod
# will return the result of the method 'myMethod'
# contrast this with the write operations:
# set [>object . myProperty .] blah
# >object . myMethod blah
# however, the property can also be read using:
# set [>object . myProperty .]
# Note the trailing . to give us a sort of 'reference' to the property.
# this is NOT equivalent to
# set [>object . myProperty]
# This last example is of course calling set against a standard variable whose name is whatever value is returned by reading the property
# i.e it is equivalent in this case to: set blah
#All objects are represented by a command, the name of which contains a leading ">".
#Any commands in the interp which use this naming convention are assumed to be a pattern object.
#Use of non-pattern commands containing this leading character is not supported. (Behaviour is undefined)
#All user-added properties & methods of the wrapped object are accessed
# using the separator character "."
#Metamethods supplied by the patterm system are accessed with the object command using the metamethod separator ".."
# e.g to instantiate a new object from an existing 'pattern' (the equivalent of a class or prototype)
# you would use the 'Create' metamethod on the pattern object like so:
# >MyFactoryClassOrPrototypeLikeThing .. Create >NameOfNewObject
# '>NameOfNewObject' is now available as a command, with certain inherited methods and properties
# of the object it was created from. (
#The use of the access-syntax separator character "." allows objects to be kept
# 'clean' in the sense that the only methods &/or properties that can be called this way are ones
# the programmer(you!) put there. Existing metamethods such as 'Create' are accessed using a different syntax
# so you are free to implement your own 'Create' method on your object that doesn't conflict with
# the metamethod.
#Chainability (or how to violate the Law of Demeter!)
#The . access-syntax gives TCL an OO syntax more closely in line with many OO systems in other
# languages such as Python & VB, and allows left to right keyboard-entry of a deeply nested object-reference
# structure, without the need to regress to enter matching brackets as is required when using
# standard TCL command syntax.
# ie instead of:
# [[[object nextObject] getItem 4] getItem [chooseItemNumber]] doSomething
# we can use:
# >object . nextObject . getItem 4 . getItem [chooseItemNumber] . doSomething
#
# This separates out the object-traversal syntax from the TCL command syntax.
# . is the 'traversal operator' when it appears between items in a commandlist
# . is the 'reference operator' when it is the last item in a commandlist
# , is the 'index traversal operator' (or 'nest operator') - mathematically it marks where there is a matrix 'partition'.
# It marks breaks in the multidimensional structure that correspond to how the data is stored.
# e.g obj . arraydata x y , x1 y1 z1
# represents an element of a 5-dimensional array structured as a plane of cubes
# e.g2 obj . arraydata x y z , x1 y1
# represents an element of a 5-dimensional array structured as a cube of planes
# The underlying storage for e.g2 might consist of something such as a Tcl array indexed such as cube($x,$y,$z) where each value is a patternlib::>matrix object with indices x1 y1
# .. is the 'meta-traversal operator' when it appears between items in a commandlist
# .. is the 'meta-info operator'(?) when it is the last item in a commandlist
#!todo - Duck Typing: http://en.wikipedia.org/wiki/Duck_typing
# implement iStacks & pStacks (interface stacks & pattern stacks)
#see also: Using namsepace ensemble without a namespace: http://wiki.tcl.tk/16975
#------------------------------------------------------------
# System objects.
#------------------------------------------------------------
#::pp::Obj-1 ::p::internals::>metaface
#::pp::Obj0 ::p::ifaces::>null
#::pp::Obj1 ::>pattern
#------------------------------------------------------------
#TODO
#investigate use of [namespace path ... ] to resolve command lookup (use it to chain iStacks?)
#CHANGES
#2018-09 - v 1.2.2
# varied refactoring
# Changed invocant datastructure curried into commands (the _ID_ structure)
# Changed MAP structure to dict
# Default Method no longer magic "item" - must be explicitly set with .. DefaultMethod (or .. PatternDefaultMethod for patterns)
# updated test suites
#2018-08 - v 1.2.1
# split ::p::predatorX functions into separate files (pkgs)
# e.g patternpredator2-1.0.tm
# patternpredator1-1.0 - split out but not updated/tested - probably obsolete and very broken
#
#2017-08 - v 1.1.6 Fairly big overhaul
# New predator function using coroutines
# Added bang operator !
# Fixed Constructor chaining
# Added a few tests to test::pattern
#
#2008-03 - preserve ::errorInfo during var writes
#2007-11
#Major overhaul + new functionality + new tests v 1.1
# new dispatch system - 'predator'.
# (preparing for multiple interface stacks, multiple invocants etc)
#
#
#2006-05
# Adjusted 'var' expansion to use the new tcl8.5 'namespace upvar $ns v1 n1 v2 n2 ... ' feature.
#
#2005-12
# Adjusted 'var' expansion in method/constructor etc bodies to be done 'inline' where it appears rather than aggregated at top.
#
# Fixed so that PatternVariable default applied on Create.
#
# unified interface/object datastructures under ::p::<id>:: instead of seperate ::p::IFACE::<id>::
# - heading towards multiple-interface objects
#
#2005-10-28
# 1.0.8.1 passes 80/80 tests
# >object .. Destroy - improved cleanup of interfaces & namespaces.
#
#2005-10-26
# fixes to refsync (still messy!)
# remove variable traces on REF vars during .. Destroy
# passes 76/76
#
#2005-10-24
# fix objectRef_TraceHandler so that reading a property via an object reference using array syntax will call a PropertyRead function if defined.
# 1.0.8.0 now passes 75/76
#
#2005-10-19
# Command alias introduced by @next@ is now placed in the interfaces namespace. (was unnamespaced before)
# changed IFACE array names for level0 methods to be m-1 instead of just m. (now consistent with higher level m-X names)
# 1.0.8.0 (passes 74/76)
# tests now in own package
# usage:
# package require test::pattern
# test::p::list
# test::p::run ?nameglob? ?-version <value>?
#
#2005-09?-12
#
# fixed standalone 'var' statement in method bodies so that no implicit variable declarations added to proc.
# fixed @next@ so that destination method resolved at interface compile time instead of call time
# fixed @next@ so that on Create, .. PatternMethod x overlays existing method produced by a previous .. PatternMethod x.
# (before, the overlay only occured when '.. Method' was used to override.)
#
#
# miscellaneous tidy-ups
#
# 1.0.7.8 (passes 71/73)
#
#2005-09-10
# fix 'unknown' system such that unspecified 'unknown' handler represented by lack of (unknown) variable instead of empty string value
# this is so that a mixin with an unspecified 'unknown' handler will not undo a lowerlevel 'unknown' specificier.
#
#2005-09-07
# bugfix indexed write to list property
# bugfix Variable default value
# 1.0.7.7 (passes 70/72)
# fails:
# arrayproperty.test - array-entire-reference
# properties.test - property_getter_filter_via_ObjectRef
#
#2005-04-22
# basic fix to PatternPropertyRead dispatch code - updated tests (indexed case still not fixed!)
#
# 1.0.7.4
#
#2004-11-05
# basic PropertyRead implementation (non-indexed - no tests!)
#
#2004-08-22
# object creation speedups - (pattern::internals::obj simplified/indirected)
#
#2004-08-17
# indexed property setter fixes + tests
# meta::Create fixes - state preservation on overlay (correct constructor called, property defaults respect existing values)
#
#2004-08-16
# PropertyUnset & PatternPropertyUnset metaMethods (filter method called on property unset)
#
#2004-08-15
# reference syncing: ensure writes to properties always trigger traces on property references (+ tests)
# - i.e method that updates o_myProp var in >myObj will cause traces on [>myObj . myProp .] to trigger
# - also trigger on curried traces to indexed properties i.e list and array elements.
# - This feature presumably adds some overhead to all property writes - !todo - investigate desirability of mechanism to disable on specific properties.
#
# fix (+ tests) for ref to multiple indices on object i.e [>myObj key1 key2 .]
#
#2004-08-05
# add PropertyWrite & PatternPropertyWrite metaMethods - (filter method called on property write)
#
# fix + add tests to support method & property of same name. (method precedence)
#
#2004-08-04
# disallow attempt to use method reference as if it were a property (raise error instead of silently setting useless var)
#
# 1.0.7.1
# use objectref array access to read properties even when some props unset; + test
# unset property using array access on object reference; + test
#
#
#2004-07-21
# object reference changes - array property values appear as list value when accessed using upvared array.
# bugfixes + tests - properties containing lists (multidimensional access)
#
#1.0.7
#
#2004-07-20
# fix default property value append problem
#
#2004-07-17
# add initial implementation of 'Unknown' and 'PatternUnknown' meta-methods
# (
#
#2004-06-18
# better cleanup on '>obj .. Destroy' - recursively destroy objects under parents subnamespaces.
#
#2004-06-05
# change argsafety operator to be anything with leading -
# if standalone '-' then the dash itself is not added as a parameter, but if a string follows '-'
# i.e tkoption style; e.g -myoption ; then in addition to acting as an argsafety operator for the following arg,
# the entire dash-prefixed operator is also passed in as an argument.
# e.g >object . doStuff -window .
# will call the doStuff method with the 2 parameters -window .
# >object . doStuff - .
# will call doStuff with single parameter .
# >object . doStuff - -window .
# will result in a reference to the doStuff method with the argument -window 'curried' in.
#
#2004-05-19
#1.0.6
# fix so custom constructor code called.
# update Destroy metamethod to unset $self
#
#1.0.4 - 2004-04-22
# bug fixes regarding method specialisation - added test
#
#------------------------------------------------------------
package provide pattern2 [namespace eval pattern {variable version; set version 2.0}]
package require patterncmd ;#utility/system diagnostic commands (may be used by metaface lib etc)
package require cmdline
package require patterndispatcher
namespace eval pattern {
variable initialised 0
}
namespace eval pp {
#this is also the interp alias namespace. (object commands created here , then renamed into place)
#the object aliases are named as incrementing integers.. !todo - consider uuids?
variable ID 0
namespace eval func {}
}
#!store all interface objects here?
namespace eval ::pp::ifaces {}
proc ::pp::assert {condition errmsg} {
if {![uplevel 1 expr $condition]} {
return -code error "assertion failed. condition:'$condition' msg:'$errmsg'"
}
}
#proc ::pp::assert args {}
proc ::pp::get_new_object_id {} {
tailcall incr ::pp::ID
#tailcall ::pattern::new_uuid
}
#create a new minimal object - with no interfaces or patterns.
proc ::pp::func::new_object {obj {OID ""}} {
puts stderr "(::pp::func::new_object) obj:$obj OID:$OID"
if {[string range $obj 0 1] ne "::"} {
set nsbase [uplevel 1 [list namespace current]]
if {$nsbase eq "::"} {
set obj ::$obj
} else {
set obj ${nsbase}::$obj
}
}
if {[info object isa object $obj]} {
puts stderr "(::pp::func::new_object) Object $obj already exists "
}
if {$OID eq ""} {
set OID [::pp::get_new_object_id]
}
set main_ns ::pp::Obj${OID}
if {[namespace exists $main_ns]} {
error "(::pp::func::new_object) Cannot create Object with id:'$OID' - corresponding namespace already exists"
}
set default_method {}
set object_command $obj
#set INVOCANTRECORD [list $OID $main_ns $default_method $object_command {}]
set invocantD [list id $OID ns $main_ns defaultmethod $default_method object $object_command]
# _ID_ structure
#set _InvocantData_ [dict create i [dict create this [list $INVOCANTRECORD]] context ""]
set _InvocantData_ [dict create i [dict create this [list $invocantD]] context ""]
#must create main varspace first as it is also the parent namespace for all varspaces
set vs_main [::pp::varspace_main create ::pp::Obj${OID} [set varspacename ""] $_InvocantData_]
set vs_meta [::pp::varspace_meta create ::pp::Obj${OID}::_meta _meta $_InvocantData_]
puts stderr "\t(::pp::func::new_object) --- about to call pp::dispatcher create $obj $_InvocantData_ main"
pp::dispatcher create $obj $_InvocantData_ "main"
return $obj
}
proc ::pp::func::new_dispatcher {obj _InvocantData_ apiname} {
pp::dispatcher create $obj $_InvocantData_ $apiname
}
#aliased from ::p::${OID}::
# called when no DefaultMethod has been set for an object, but it is called with indices e.g >x something
proc ::pp::func::no_default_method {_ID_ args} {
puts stderr "no_default_method _ID_:'$_ID_' args:'$args'"
lassign [lindex [dict get $_ID_ i this] 0] OID alias default_method object_command wrapped
tailcall error "No default method on object $object_command. (To get or set, use: $object_command .. DefaultMethod ?methodname?)"
}
#>x .. Create >y
# ".." is special case equivalent to "._."
# (whereas in theory it would be ".default.")
# "." is equivalent to ".default." is equivalent to ".default.default." (.<iStack>.<iFace>.)
#>x ._. Create >y
#>x ._.default. Create >y ???
#
#
# create object using 'blah' as source interface-stack ?
#>x .blah. .. Create >y
#>x .blah,_. ._. Create .iStackDestination. >y
#
# ">x .blah,_." is a reference(cast) to >x that contains only the iStacks in the order listed. i.e [list blah _]
# the 1st item, blah in this case becomes the 'default' iStack.
#
#>x .*.
# cast to object with all iStacks
#
#>x .*,!_.
# cast to object with all iStacks except _
#
# ---------------------
#!todo - MultiMethod support via transient and persistent object conglomerations. Operators '&' & '@'
# - a persistent conglomeration will have an object id (OID) and thus associated namespace, whereas a transient one will not.
#
#eg1: >x & >y . some_multi_method arg arg
# this is a call to the MultiMethod 'some_multi_method' with 2 objects as the invocants. ('>x & >y' is a transient conglomeration of the two objects)
# No explicit 'invocation role' is specified in this call - so it gets the default role for multiple invocants: 'these'
# The invocant signature is thus {these 2}
# (the default invocation role for a standard call on a method with a single object is 'this' - with the associated signature {this 1})
# Invocation roles can be specified in the call using the @ operator.
# e.g >x & >y @ points . some_multi_method arg arg
# The invocant signature for this is: {points 2}
#
#eg2: {*}[join $objects &] @ objects & >p @ plane . move $path
# This has the signature {objects n plane 1} where n depends on the length of the list $objects
#
#
# To get a persistent conglomeration we would need to get a 'reference' to the conglomeration.
# e.g set pointset [>x & >y .]
# We can now call multimethods on $pointset
#
if {[namespace which ::pp::ifaces>null] eq ""} {
set ::pp::ID 4 ;#0,1,2,3 reserved for null interface,>pattern, >ifinfo & ::p::>interface
#OID = 0
::pp::func::new_object ::pp::ifaces::>null 0
::pp::func::new_object ::>pattern 1
#'class' for ::pp::ifaces::>x instances
::pp::func::new_object ::pp::>interface 3
}
#NOOP - for compatibility with libraries which still call it
proc ::pattern::init {args} {
}
proc ::pattern::initXXX {args} {
if {[set ::pattern::initialised]} {
if {[llength $args]} {
#if callers want to avoid this error, they can do their own check of $::pattern::initialised
error "pattern package is already initialised. Unable to apply args: $args"
} else {
return 1
}
}
set ::pp::ID 4 ;#0,1,2,3 reserved for null interface,>pattern, >ifinfo & ::p::>interface
#OID = 0
::pp::func::new_object ::pp::ifaces::>null 0
::pp::func::new_object ::>pattern 1
#'class' for ::pp::ifaces::>x instances
::pp::func::new_object ::pp::>interface 3
#::pp::>interface ## PatternSystem . add_pattern_interface 2
#add to constructor?
#::pp::Obj${o_OID}::_iface API(PatternInternal)add_tcloo_interface_on_api "varspace_iface" "pattern::IPatternInterface"
set ::pattern::initialised 1
}
# >pattern has object ID 1
# meta interface has object ID 0
proc ::pattern::init2 args {
if {[set ::pattern::initialised]} {
if {[llength $args]} {
#if callers want to avoid this error, they can do their own check of $::pattern::initialised
error "pattern package is already initialised. Unable to apply args: $args"
} else {
return 1
}
}
set ::pp::ID 4 ;#0,1,2,3 reserved for null interface,>pattern, >ifinfo & ::p::>interface
#create metaface - IID = -1 - also OID = -1
# all objects implement this special interface - accessed via the .. operator.
package require metaface
#OID = 0
::pp::func::new_object ::p::ifaces::>null 0
#? null object has itself as level0 & level1 interfaces?
#set ::p::ifaces::>null [list [list 0 ::p::ifaces::>null item] [list [list 0] [list 0]] [list {} {}]]
#null interface should always have 'usedby' members. It should never be extended.
array set ::p::0::_iface::o_usedby [list i-1 ::p::internals::>metaface i0 ::p::ifaces::>null i1 ::>pattern] ;#'usedby' array
set ::p::0::_iface::o_open 0
set ::p::0::_iface::o_constructor [list]
set ::p::0::_iface::o_variables [list]
set ::p::0::_iface::o_properties [dict create]
set ::p::0::_iface::o_methods [dict create]
set ::p::0::_iface::o_varspace ""
set ::p::0::_iface::o_varspaces [list]
array set ::p::0::_iface::o_definition [list]
set ::p::0::_iface::o_propertyunset_handlers [dict create]
###############################
# OID = 1
# >pattern
###############################
::pp::func::new_object ::>pattern 1
set _self ::pattern
#set IFID [::p::internals::new_interface 1] ;#level 0 interface usedby object 1
#set IFID_1 [::p::internals::new_interface 1] ;#level 1 interface usedby object 1
#1)this object references its interfaces
#lappend ID $IFID $IFID_1
#set body [string map [::list @self@ ::>pattern @_self@ ::pattern @self_ID@ 0 @itemCmd@ item] $::p::internals::OBJECTCOMMAND]
#proc ::>pattern args $body
#######################################################################################
#OID = 2
# >ifinfo interface for accessing interfaces.
#
::p::internals::new_object ::p::ifaces::>2 "" 2 ;#>ifinfo object
set ::p::2::_iface::o_constructor [list]
set ::p::2::_iface::o_variables [list]
set ::p::2::_iface::o_properties [dict create]
set ::p::2::_iface::o_methods [dict create]
set ::p::2::_iface::o_varspace ""
set ::p::2::_iface::o_varspaces [list]
array set ::p::2::_iface::o_definition [list]
set ::p::2::_iface::o_open 1 ;#open for extending
::p::ifaces::>2 .. AddInterface 2
#Manually create a minimal >ifinfo implementation using the same general pattern we use for all method implementations
#(bootstrap because we can't yet use metaface methods on it)
proc ::p::2::_iface::isOpen.1 {_ID_} {
return $::p::2::_iface::o_open
}
interp alias {} ::p::2::_iface::isOpen {} ::p::2::_iface::isOpen.1
proc ::p::2::_iface::isClosed.1 {_ID_} {
return [expr {!$::p::2::_iface::o_open}]
}
interp alias {} ::p::2::_iface::isClosed {} ::p::2::_iface::isClosed.1
proc ::p::2::_iface::open.1 {_ID_} {
set ::p::2::_iface::o_open 1
}
interp alias {} ::p::2::_iface::open {} ::p::2::_iface::open.1
proc ::p::2::_iface::close.1 {_ID_} {
set ::p::2::_iface::o_open 0
}
interp alias {} ::p::2::_iface::close {} ::p::2::_iface::close.1
#proc ::p::2::_iface::(GET)properties.1 {_ID_} {
# set ::p::2::_iface::o_properties
#}
#interp alias {} ::p::2::_iface::(GET)properties {} ::p::2::_iface::(GET)properties.1
#interp alias {} ::p::2::properties {} ::p::2::_iface::(GET)properties
#proc ::p::2::_iface::(GET)methods.1 {_ID_} {
# set ::p::2::_iface::o_methods
#}
#interp alias {} ::p::2::_iface::(GET)methods {} ::p::2::_iface::(GET)methods.1
#interp alias {} ::p::2::methods {} ::p::2::_iface::(GET)methods
#link from object to interface (which in this case are one and the same)
#interp alias {} ::p::2::isOpen {} ::p::2::_iface::isOpen [::p::ifaces::>2 --]
#interp alias {} ::p::2::isClosed {} ::p::2::_iface::isClosed [::p::ifaces::>2 --]
#interp alias {} ::p::2::open {} ::p::2::_iface::open [::p::ifaces::>2 --]
#interp alias {} ::p::2::close {} ::p::2::_iface::close [::p::ifaces::>2 --]
interp alias {} ::p::2::isOpen {} ::p::2::_iface::isOpen
interp alias {} ::p::2::isClosed {} ::p::2::_iface::isClosed
interp alias {} ::p::2::open {} ::p::2::_iface::open
interp alias {} ::p::2::close {} ::p::2::_iface::close
#namespace eval ::p::2 "namespace export $method"
#######################################################################################
set ::pattern::initialised 1
::p::internals::new_object ::p::>interface "" 3
#create a convenience object on which to manipulate the >ifinfo interface
#set IF [::>pattern .. Create ::p::>interface]
set IF ::p::>interface
#!todo - put >ifinfo on a separate pStack so that end-user can more freely treat interfaces as objects?
# (or is forcing end user to add their own pStack/iStack ok .. ?)
#
::p::>interface .. AddPatternInterface 2 ;#
::p::>interface .. PatternVarspace _iface
::p::>interface .. PatternProperty methods
::p::>interface .. PatternPropertyRead methods {} {
varspace _iface
var {o_methods mmm}
return $mmm
}
::p::>interface .. PatternProperty properties
::p::>interface .. PatternPropertyRead properties {} {
varspace _iface
var o_properties
return $o_properties
}
::p::>interface .. PatternProperty variables
::p::>interface .. PatternProperty varspaces
::p::>interface .. PatternProperty definition
::p::>interface .. Constructor {{usedbylist {}}} {
#var this
#set this @this@
#set ns [$this .. Namespace]
#puts "-> creating ns ${ns}::_iface"
#namespace eval ${ns}::_iface {}
varspace _iface
var o_constructor o_variables o_properties o_methods o_definition o_usedby o_varspace o_varspaces
set o_constructor [list]
set o_variables [list]
set o_properties [dict create]
set o_methods [dict create]
set o_varspaces [list]
array set o_definition [list]
foreach usedby $usedbylist {
set o_usedby(i$usedby) 1
}
}
::p::>interface .. PatternMethod isOpen {} {
varspace _iface
var o_open
return $o_open
}
::p::>interface .. PatternMethod isClosed {} {
varspace _iface
var o_open
return [expr {!$o_open}]
}
::p::>interface .. PatternMethod open {} {
varspace _iface
var o_open
set o_open 1
}
::p::>interface .. PatternMethod close {} {
varspace _iface
var o_open
set o_open 0
}
::p::>interface .. PatternMethod refCount {} {
varspace _iface
var o_usedby
return [array size o_usedby]
}
set ::p::2::_iface::o_open 1
uplevel #0 {package require patternlib}
return 1
}
#detect attempt to treat a reference to a method as a property
proc ::pp::func::commandrefMisuse_TraceHandler {OID field args} {
#puts "commandrefMisuse_TraceHandler fired OID:$OID field:$field args:$args"
lassign [lrange $args end-2 end] vtraced vidx op
#NOTE! cannot rely on vtraced as it may have been upvared
switch -- $op {
write {
error "$field is not a property" "property ref write failure for property $field (OID: $OID refvariable: [lindex $args 0])"
}
unset {
#!todo - monitor stat of Tcl bug# 1911919 - when/(if?) fixed - reinstate 'unset' trace
#trace add variable $traced {read write unset} [concat ::pp::func::commandrefMisuse_TraceHandler $OID $field $args]
#!todo - don't use vtraced!
trace add variable $vtraced {read write unset array} [concat ::pp::func::commandrefMisuse_TraceHandler $OID $field $args]
#pointless raising an error as "Any errors in unset traces are ignored"
#error "cannot unset. $field is a method not a property"
}
read {
error "$field is not a property (args $args)" "property ref read failure for property $field (OID: $OID refvariable: [lindex $args 0])"
}
array {
error "$field is not a property (args $args)" "property ref use as array failure for property $field (OID: $OID refvariable: [lindex $args 0])"
#error "unhandled operation in commandrefMisuse_TraceHandler - got op:$op expected read,write,unset. OID:$OID field:$field args:$args"
}
}
return
}
#!todo - review calling-points for make_dispatcher.. probably being called unnecessarily at some points.
#
# The 'dispatcher' is an object instance's underlying object command.
#
#proc ::p::make_dispatcher {obj ID IFID} {
# proc [string map {::> ::} $obj] {{methprop INFO} args} [string map [::list @IID@ $IFID @oid@ $ID] {
# ::p::@IID@ $methprop @oid@ {*}$args
# }]
# return
#}
################################################################################################################################################
################################################################################################################################################
################################################################################################################################################
#force 1 will extend an interface even if shared. (??? why is this necessary here?)
#if IID empty string - create the interface.
proc ::pp::func::expand_interface {IID {force 0}} {
#puts stdout ">>> expand_interface $IID [info level -1]<<<"
if {![string length $IID]} {
set iid [expr {$::p::ID + 1}]
::pp::>interface .. Create ::pp::ifaces::>$iid
return $iid
} else {
if {[set ::pp::Obj${IID}::_iface::o_open]} {
#interface open for extending - shared or not!
return $IID
}
error "temporary error. Interface can't be expanded. Not implemented"
if {[array size ::pp::Obj${IID}::_iface::o_usedby] > 1} {
#upvar #0 ::p::${IID}::_iface::o_usedby prev_usedby
#oops.. shared interface. Copy before specialising it.
set prev_IID $IID
set IID [expr {$::pp::ID + 1}]
::pp::>interface .. Create ::pp::ifaces::>$IID
::pp::func::linkcopy_interface $prev_IID $IID
#assert: prev_usedby contains at least one other element.
}
#whether copied or not - mark as open for extending.
set ::pp::Obj${IID}::_iface::o_open 1
return $IID
}
}
#params: old - old (shared) interface ID
# new - new interface ID
proc ::pp::func::linkcopy_interface {old new} {
#puts stderr " ** ** ** linkcopy_interface $old $new"
set ns_old ::pp::Obj${old}::_iface
set ns_new ::pp::Obj${new}::_iface
foreach nsmethod [info commands ${ns_old}::*.1] {
#puts ">>> adding $nsmethod to iface $new"
set tail [namespace tail $nsmethod]
set method [string range $tail 0 end-2] ;#strip .1
if {![llength [info commands ${ns_new}::$method]]} {
set oldhead [interp alias {} ${ns_old}::$method] ;#the 'head' of the cmdchain that it actually points to ie $method.$x where $x >=1
#link from new interface namespace to existing one.
#(we assume that since ${ns_new}::$method didn't exist, that all the $method.$x chain slots are empty too...)
#!todo? verify?
#- actual link is chainslot to chainslot
interp alias {} ${ns_new}::$method.1 {} $oldhead
#!todo - review. Shouldn't we be linking entire chain, not just creating a single .1 pointer to the old head?
#chainhead pointer within new interface
interp alias {} ${ns_new}::$method {} ${ns_new}::$method.1
namespace eval $ns_new "namespace export $method"
#if {[string range $method 0 4] ni {(GET) (SET) (UNSE (CONS }} {
# lappend ${ns_new}::o_methods $method
#}
} else {
if {$method eq "(VIOLATE)"} {
#ignore for now
#!todo
continue
}
#!todo - handle how?
#error "command $cmd already exists in interface $new"
#warning - existing chainslot will be completely shadowed by linked method.
# - existing one becomes unreachable. #!todo review!?
error "linkcopy_interface $old -> $new - chainslot shadowing not implemented (method $method already exists on target interface $new)"
}
}
#foreach propinf [set ${ns_old}::o_properties] {
# lassign $propinf prop _default
# #interp alias {} ${ns_new}::(GET)$prop {} ::p::predator::getprop $prop
# #interp alias {} ${ns_new}::(SET)$prop {} ::p::predator::setprop $prop
# lappend ${ns_new}::o_properties $propinf
#}
set ${ns_new}::o_variables [set ${ns_old}::o_variables]
set ${ns_new}::o_properties [set ${ns_old}::o_properties]
set ${ns_new}::o_methods [set ${ns_old}::o_methods]
set ${ns_new}::o_constructor [set ${ns_old}::o_constructor]
set ::pp::${old}::_iface::o_usedby(i$new) linkcopy
#obsolete.?
#array set ::p::${new}:: [array get ::p::${old}:: ]
#!todo - is this done also when iface compiled?
#namespace eval ::p::${new}::_iface {namespace ensemble create}
#puts stderr "copy_interface $old $new"
#assume that the (usedby) data is now obsolete
#???why?
#set ${ns_new}::(usedby) [::list]
#leave ::(usedby) reference in place for caller to change as appropriate - 'copy'
return
}
################################################################################################################################################
################################################################################################################################################
################################################################################################################################################
#pattern::init
#return $::pattern::version
proc pp::repl {} {
set command ""
set prompt "% "
puts -nonewline stdout $prompt
flush stdout
while {[gets stdin line] >=0} {
append command "\n$line"
if {[info complete $command]} {
catch {uplevel #0 $command} result
puts stdout $result
set command ""
set prompt "% "
} else {
set prompt "(cont)% "
}
puts -nonewline stdout $prompt
flush stdout
}
}
if {[info exists ::argv0] && [file dirname [file normalize [info script]/ ]] eq [file dirname [file normalize $argv0/]]} {
pp::repl
}