File: starmethod.tcl

package info (click to toggle)
nsf 2.3.0-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, bullseye
  • size: 12,628 kB
  • sloc: ansic: 32,245; tcl: 10,636; sh: 664; pascal: 176; lisp: 41; makefile: 24
file content (206 lines) | stat: -rw-r--r-- 5,832 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
#
# = Star Methods
#
# Design study for implementing methods which applies to instances of
# instances meta-classes. This study implements in addition to the
# regular "method" a new construct called "*method" which has the
# mentioned transitive property. The same behavior can be achieved in
# many ways. In this study, we define a special class (the method
# container class for *methods) which is kept in the precedence path
# of instances. This way, it can be defined freely with other
# extension mechanisms such as mixins, traits or filters.
#
package req nx::test

nx::Class eval {
    #
    # Define a *method, which is a method that applies for instances of
    # the instances of a meta-class.
    # - *methods are only defineable on meta-classes
    # - *methods are applicable on the instances of the instances of the
    #   meta-class
    # - If one defines a *method "bar" on a meta-class "MClass", and a
    #   class "C" as an instance of "MClass", and "c1" is an instance of
    #   "C", then "bar" is applicable for "c1".
    
    #
    # The "*method" has the same signature as regular methods, and can
    # be used in combination with the modifiers
    # public/protected/private as usual.
    #
    :public method *method {name arguments:parameter,0..* -returns body -precondition -postcondition} {
	#
	# Allow the definition only on meta-classes
	#
	if {![nsf::is metaclass [self]]} {
	    error "[self] is not a meta-class"
	}
	#
	# Do we have the class for keeping the *methods already?
	#
	set starClass [nx::Class create [self]::*]
	
	if {![nsf::object::exists $starClass]} {
	    #
	    # If not, create the *method container class and provide
	    # it as a default in the superclass hierarchy. This
	    # happens by modifying the property "-superclasses" which
	    # is used on every class to specify the class hierarchy.
	    #
	    :property [list superclasses $starClass] {
		#
		# Define a slot-specific method for keeping the
		# *method container class in the hierarchy.
		#
		:public object method appendToRelations { class property value } {
		    set sc [nsf::relation::get $class $property]
		    if {$sc eq "::nx::Object"} {
			nsf::relation::set $class $property $value
		    } else {
			nsf::relation::set $class $property [concat $sc $value]
		    }
		}
		
		#
		# Whenever the "-superclasses" relation is called,
		# make sure, we keep the *method container class in
		# the hierarchy.
		#
		:public object method value=set { class property value } {
		    :appendToRelations $class superclass $value
		}
	    }
	    
	    #
	    # Update class hierarchies of the previously created instances
	    # of the meta-class.
	    #
	    foreach class [:info instances] {
		set slot [$class info lookup slots superclasses]
		$slot appendToRelations $class superclass $starClass
	    }
	}

	#
	# Define the *method as regular method in the star method
	# container class.
	#
	[self]::* method $name $arguments \
	    {*}[expr {[info exists returns] ? [list -returns $returns] : ""}] \
	    $body \
	    {*}[expr {[info exists precondition]  ? [list -precondition $precondition] : ""}] \
	    {*}[expr {[info exists postcondition] ? [list -postcondition $postcondition] : ""}]
    }
}
set ::nsf::methodDefiningMethod(*method) 1


#
# == Some base test cases:
#
# Define a meta-class MClass with a method "foo" and to star methods
# named "foo" and "bar".
#
nx::Class create MClass -superclass nx::Class {
    :public method foo {} {return MClass-[next]}
    :public *method foo {} {return *-[next]}
    :public *method bar {} {return *-[next]}
}

#
# Define a class based on MClass and define here as well a method
# "foo" to show the next-path in combination with the *methods.
#
MClass create C {
    :public method foo {} {return C-[next]}
}

? {C info superclasses} "::MClass::*"

#
# Finally create an instance with the method foo as well.
#
C create c1 {
    :public object method foo {} {return c1-[next]}
}

#
# The result of "foo" reflects the execution order: object before
# classes (including the *method container).
#

? {c1 info precedence} "::C ::MClass::* ::nx::Object"
? {c1 foo} "c1-C-*-"
? {c1 bar} "*-"


#
# Define a Class D as a specialization of C
#
MClass create D -superclass C {
    :public method foo {} {return D-[next]}
    :create d1
}

? {d1 info precedence} "::D ::C ::MClass::* ::nx::Object"
? {d1 foo} "D-C-*-"

#
# Dynamically add *method "baz". 
#
? {d1 baz} "::d1: unable to dispatch method 'baz'"
MClass eval {
    :public *method baz {} {return baz*-[next]}
}
? {d1 baz} "baz*-"

#
# Test adding of *methods at a time, when the meta-class has already
# instances.
#
# Create a meta-class without a *method
nx::Class create MClass2 -superclass nx::Class
MClass2 create X {:create x1}
? {x1 info precedence} "::X ::nx::Object"

# Now add a *method
MClass2 eval {
    :public *method baz {} {return baz*-[next]}
}

# Adding the *method alters the superclass order of already created
# instances of the meta-class
? {x1 info precedence} "::X ::MClass2::* ::nx::Object"
? {x1 baz} "baz*-"


#
# Finally, there is a simple application example for ActiveRecord
# pattern. All instances of the application classes (such as
# "Product") should have a method "save" (together with other methods
# now shown here). First define the ActiveRecord class (as a
# meta-class).
#
Class create ActiveRecord -superclass nx::Class {
    :property table_name

    :method init {} {
	if {![info exists :table_name]} {
	    set :table_name [string tolower [namespace tail [self]]s]
	}
    }
    :public *method save {} {
	puts "save [self] into table [[:info class] cget -table_name]"
    }
}

#
# Define the application class "Product" with an instance
#
ActiveRecord create Product
Product create p1
p1 save

# The last command prints out: "save ::p1 into table products"