-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathfunc-static.reb
executable file
·257 lines (201 loc) · 7.14 KB
/
func-static.reb
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
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
REBOL [
Title: "Rebol Functions with Static Locals"
Purpose: "Easy-to-use alternative to closure for static local variables"
Description: {
Rebol 3 introduced closures as a native feature of the
language and shows examples of using them to declare functions which
capture and retain state between function calls. However, working with
the 2-step process and having to think in terms of returning functions
from within a closure can be overwhelming.
This defines func-static, which is for people who just want a function
to accumulate state of its own but not pollute the parent context. As
a further feature to test out, it can automatically provide a reset
refinement for resetting the static locals to the expressions that
were used to initialize them.
!!!WARNING!!!
This is very experimental right now and I would like some people with
more experience writing these sorts of things to give it a look. It
needs error handling and a lot of other things. (Also there should be
variants for the different function types and not just FUNC.) Locals
are not being handled in proper "FUNCTION" form yet!!!
}
Author: "Hostile Fork"
Home: http://hostilefork.com
License: mit
File: %func-static.reb
Date: 9-Nov-2009
Version: 0.1.0
; Header conventions: http://www.rebol.org/one-click-submission-help.r
Type: function
Level: advanced
Usage: {
Imagine you want a function that prints out a string and also
counts the number of times it is called:
printAndCount: func-static [s [string!]] [numCalls: 0] [
print s
numCalls: numCalls + 1
print rejoin ["(numCalls: " numCalls ")"]
]
The first block is the usual function spec. The second is a list of
static local variables and their initial values.
>> printAndCount "Hello"
Hello
(numCalls: 1)
>> printAndCount "Goodbye"
Goodbye
(numCalls: 2)
Additionally, if you would like to be able to make a call reset the
static variables, use the resettable refinement.
printAndCount: func-static/resettable [s [string!]] [numCalls: 0] [
print s
numCalls: numCalls + 1
print rejoin ["(numCalls: " numCalls ")"]
]
>> printAndCount "Hello"
Hello
(numCalls: 1)
>> printAndCount "Goodbye"
Goodbye
(numCalls: 2)
>> printAndCount/reset "World"
World
(numCalls: 1)
You can use expressions to initialize the defaults of the local
variables. They will be evaluated in the context where you defined
the static function, and this context is remembered for when you reset:
globalString: "Hello"
doubleAndAppend: func-static/resettable [arg [integer!]] [
onePlusOne: 1 + 1
message: globalString
] [
print rejoin ["onePlusOne * arg = " onePlusOne * arg]
append message " World"
print rejoin ["message: " message newline]
]
doubleAndAppend 3
doubleAndAppend 4
globalString: "Goodbye"
use [globalString] [
globalString: "Reset should capture {Goodbye} instead!!!"
doubleAndAppend/reset 5
]
The code above will output:
onePlusOne * arg = 6
message: Hello World
onePlusOne * arg = 8
message: Hello World World
onePlusOne * arg = 10
message: Goodbye World
}
History: [
0.1.0 [9-Nov-2009 {Private deployment to Rebolers for feedback.} "Fork"]
]
]
get-parameter-information: function [
parameters [block!]
] [
; Imagine you have something like:
;
; [a b [block!] c: 3 + 4 d: "hello"]
;
; This routine analyzes that and gives you back an object with two fields.
; One is a spec (suitable for use in a function definition) and the other
; is a list of defaults. In this case the result would be:
;
; [
; spec: [a b [block!] c d]
; defaults: [none none (3 + 4) ("hello")]
; ]
result: copy/deep [spec: [] defaults: []]
pos: head parameters
while [not tail? pos] [
either set-word? first pos [
; If we encounter a set-word, build an expression out of the
; list members up until the next set-word
expression: to-paren []
append/only result/spec to-word first pos
while [(not tail? next pos) and (not set-word? second pos)] [
append/only expression second pos
pos: next pos
]
append/only result/defaults expression
] [
; For everything else, just pass it through to the parameters.
; If we see an unadorned word value then also put "none" in the
; defaults list
append/only result/spec first pos
if word? first pos [
append/only result/defaults none
]
; TODO think about refinements. They are not really compatible
; with positional ideas like this
]
pos: next pos
]
return result
]
; NOTE: there is no "function" variation at this point in time
; It was attempted and then reverted
; If anyone can figure out how to implement a function-static beast please do
func-static: function [
spec [block!]
statics [block!]
body [block!]
/resettable
] [
; We are going to make an object which initializes some local members to
; look just like the statics parameter
objSpec: copy statics
; There is some overhead to resetting, because we have to save a copy of
; the initial statics specification. We don't pay for this by default and
; disable the reset refinement, but if the user wants it then it is there
if resettable [
append objSpec compose/deep [
FUNC-STATIC.reset: func [] [
do [(statics)]
]
]
]
; We return a generated function which is actually going to be a member in
; an object... the object where the static state is stored.
staticsInfo: get-parameter-information statics
append objSpec compose/deep [
; The main function is NOT bound to the object that contains the
; statics which we are declaring here. It defaults to the caller's
; notion of context, or the caller-specified "with" context.
; so we must explicitly pass the statics as parameters
FUNC-STATIC.main: func [(spec)] [
(body)
]
; Unlike the main function, the run function *IS* bound inside the
; object we are declaring. Thus it can read the statics long enough
; to proxy them as parameters to the main, which runs in the context
; desired by the caller at the point of definition.
FUNC-STATIC.run: func [(spec) (either resettable [[/reset]] [[]])] [
(either resettable [
[if reset [FUNC-STATIC.reset]]
] [[]])
FUNC-STATIC.mainArgs: copy [(collect-words spec)]
; This is a bit tricky. If one declares a static member that is a
; function assignment, you can't simply use the word you assigned
; that function to as a parameter to main because it will try to
; call the function. Here's the temporary solution: turn any
; words into get words, probably something better...
FUNC-STATIC.argIterator: FUNC-STATIC.mainArgs
while [not tail? FUNC-STATIC.argIterator] [
if word? first FUNC-STATIC.argIterator [
change FUNC-STATIC.argIterator (
to-get-word first FUNC-STATIC.argIterator
)
]
FUNC-STATIC.argIterator: next FUNC-STATIC.argIterator
]
;insert FUNC-STATIC.mainArgs [(collect-words spec)]
return do append to-block 'FUNC-STATIC.main FUNC-STATIC.mainArgs
]
]
;print "Object specification for FUNC-STATIC"
;probe objSpec
;print newline
return select make object! objSpec 'FUNC-STATIC.run
]