File: random.tcl

package info (click to toggle)
critcl 3.3.1%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 9,680 kB
  • sloc: ansic: 41,058; tcl: 12,090; sh: 7,230; pascal: 3,456; asm: 3,058; ada: 1,681; cpp: 1,001; cs: 879; makefile: 333; perl: 104; xml: 95; f90: 10
file content (376 lines) | stat: -rw-r--r-- 10,462 bytes parent folder | download
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
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
# -*- tcl -*- (critcl actually, Tcl + embedded C)
# sr.tcl --
#
#	Object-based random number generators. The low-level math, i.e.
#	the rnmath functions, is provided by package 'rnmath'.
#
# Concept pulled out of and derived from tcllib/modules/simulation/random.tcl
# Copyright (c) 2007 by Arjen Markus <arjenmarkus@users.sourceforge.net>
#
# Critcl code generation and setup
# Copyright (c) 2011,2022 by Andreas Kupries <andreas_kupris@users.sourceforge.net>
#
# Example of how to IMPORT a C-level stubs API through critcl v3.

# # ## ### ##### ######## ############# #####################
## Requirements

package require Tcl 8.6 9
package require critcl 3.2

critcl::buildrequirement {
    package require stubs::gen ; # Generator/iterator framework ...
}

namespace eval ::random {}

# # ## ### ##### ######## ############# #####################
## Configuration

critcl::license \
    {Arjen Markus, Andreas Kupries} \
    {BSD licensed.}

critcl::summary {Random number generator objects for Tcl.}

critcl::description {
    This package implements random number generator objects for
    Tcl. It uses the functions provided by package 'rnmath' for
    the actual math.
}

critcl::subject {random number generator}
# plus the distributions, see inside of 'genclass'

# # ## ### ##### ######## ############# #####################
## Dependencies.

# C random number generator functions.
set ::random::T [critcl::api import rnmath 1]

if {$::random::T eq {}} {
    critcl::error "Unable to work without programmatic access to the rnmath API, the expected rnmath.decls was not found."
}

# # ## ### ##### ######## ############# #####################
## Code generation commands converting a simple RNMATH declaration into
## the necessary C code for class and object commands.

proc ::random::wrap {} {
    variable T
    variable PS {}
    # Iterate over the slots in the stubs table and generator wrap code for each
    # rnmath_ function.
    ::stubs::gen::forall $T rnmath [namespace current]::Make 0

    # Finalize the parameter union ...
    set PS "typedef union RNMATHparam \{\n${PS}\} RNMATHparam;"

    # The union is placed into a generated header, getting around
    # ordering problems, namely that this is finalized after a number
    # of pieces needing the type are declared. The first such piece
    # has a #include to the generated header. Whic h works because the
    # build happens after everything is generated.

    # critcl::stash ? command
    file mkdir     [critcl::cache]
    set    c [open [critcl::cache]/sr_param.h w]
    puts  $c $PS
    close $c
    #critcl::ccode $PS
    return
}

proc ::random::Make {name decl index} {
    # Handle a single slot.

    variable PS ; # The code for the parameter union (RNMATHparam) is accumulated here.

    lassign $decl ftype fname farguments
    # ASSERT ftype == void
    if {$ftype ne "void"} return

    # ASSERT fname match rnmath_*
    if {![string match rnmath_* $fname]} return

    # Extract generator name from function name.
    regsub {^rnmath_} $fname {} rname

    # Split arguments into arguments and results. The latter are
    # recognized through their pointer types (Ending in '*').
    set arguments {}
    set rtypes    {}
    foreach a $farguments {
	lassign $a atype aname aflag
	# ASSERT aflag == {}
	if {[string index $atype end] eq "*"} {
	    lappend rtypes [string range $atype 0 end-1] $aname
	} else {
	    lappend arguments $atype $aname
	}
    }

    # Generate a structure to hold the function arguments.
    # This is added to PS and will become a union of all
    # parameter structures.
    append PS "    struct \{\n"
    foreach {atype aname} $arguments {
	append PS "        $atype $aname;\n"
    }
    append PS "    \} $rname;\n"

    # Invoke the actual code generator.
    critcl::msg -nonewline " ($rname)"
    genclass $rtypes $rname $arguments
    return
}

proc ::random::genclass {rtypes name arguments} {
    # Extend the meta data. Same as used by 'rnmath', to put them
    # together, near each other.
    critcl::subject "$name probability distribution"
    critcl::subject "probability distribution $name"
    critcl::subject "distribution $name"

    set ingest ""
    foreach {t a} $arguments {
	append  ingest        "\t    rnmathp->$name.$a = _$a;\n"
	lappend theparameters "rnmathp->${name}.$a"
    }

    set argnames     [critcl::argnames $arguments]
    set thearguments [join [critcl::argcsignature $arguments] {, }]
    set argvars      [indent "\t    " [join [critcl::argvardecls   $arguments] \n]]\n
    set argcheck     [indent "\t  "   [join [critcl::argconversion $arguments] \n]]\n

    if {[llength $rtypes] == 2} {
	# Single-value result. Variables for each, and construction of a list.

	lassign $rtypes t r

	append  resultvars     "\t    $t _$r;\n"
	append  resultvars     "\t    Tcl_Obj* _lv;\n"

	append  resultget      "\t    _lv = Tcl_New[cap $t]Obj (_$r);\n"
	append  thearguments  ", $t* $r"
	lappend theparameters "&_$r"
	set resultset "_lv"
    } else {
	# Multi-value result. Variables for each, and construction of a list.
	set lc 0
	foreach {t r} $rtypes {
	    append resultvars     "\t    $t _$r;\n"
	    append resultget      "\t    _lv\[$lc\] = Tcl_New[cap $t]Obj (_$r);\n"
	    append thearguments  ", $t* $r"
	    lappend theparameters "&_$r"
	    incr lc
	}
	append resultvars "\t    Tcl_Obj* _lv\[$lc\];\n"
	set resultset "Tcl_NewListObj ($lc,_lv)"
    }

    set theparameters [join $theparameters {, }]

    # Low-level math function generating the numbers. Imported from rnmath stubs.

    # Instance command for the generators. Invokes the math function
    # with the parameters it got through its client data.

    critcl::ccode [subst -nocommand {
	static int
	r_${name}_objcmd (ClientData cd, Tcl_Interp* interp, int objc, Tcl_Obj *CONST objv[])
	{
	    RNMATHparam* rnmathp = (RNMATHparam*) cd;
$resultvars
	    if (objc > 1) {
		Tcl_WrongNumArgs (interp, 1, objv, "");
		return TCL_ERROR;
	    }

	    rnmath_$name ($theparameters);
$resultget
	    Tcl_SetObjResult (interp, $resultset);
	    return TCL_OK;
	}
    }]

    # Class command for generators of this type. Creates instance
    # commands with proper client data.
    set np   [llength $argnames]
    set nmin [expr {$np + 1}]
    set nmax [expr {$np + 2}]

    critcl::ccommand ::random::$name {cd interp oc ov} [subst -nocommands {
	RNMATHparam* rnmathp;
	char* name;
$argvars
	if (oc == $nmin) {
	    name = NULL;
	} else if (oc == $nmax) {
	    name = Tcl_GetString (ov [1]);
	    ov++;
	} else {
	    Tcl_WrongNumArgs (interp, 1, ov, "?name? $argnames");
	    return TCL_ERROR;
	}

$argcheck
	rnmathp = RNMATHnewCmd (interp, "$name", name, r_${name}_objcmd);

	if (!rnmathp) {
	    return TCL_ERROR;
	}

$ingest
	return TCL_OK;
    }]

    return
}

proc ::random::cap {name} {
    return [string toupper [string index $name 0]][string range $name 1 end]
}

proc ::random::indent {prefix text} {
    return ${prefix}[join [split $text \n] \n$prefix]
}

# # ## ### ##### ######## ############# #####################
## Intro and shared/common/fixed code.

critcl::ccode {
    /* -*- c -*- */
    /* .................................................. */
    /* Global generator management, per interp */

#include <sr_param.h> /* Generated, see random::wrap */
#define PREFIX "random"

    typedef struct RNMATHglobal {
	long int counter;
	char buf [sizeof (PREFIX) + 40 + 40];
	/* 40 - generator type string, 40 - space for long integer */
    } RNMATHglobal;

    /* Union parameter structure for all generators. */
    /* So that we have one structure for all, and a single destructor function */
    /* We can't get around the need for multiple constructors for the different */
    /* generators */

    static void
    RNMATHglobalFree (ClientData cd, Tcl_Interp* interp)
    {
	ckfree((char*) cd);
    }

    static char*
    AutoName (Tcl_Interp* interp, char* rtype)
    {
#define KEY "package/random"

	Tcl_InterpDeleteProc* proc = RNMATHglobalFree;
	RNMATHglobal*            rnmathglobal;

	rnmathglobal = Tcl_GetAssocData (interp, KEY, &proc);
	if (rnmathglobal == NULL) {
	    rnmathglobal = (RNMATHglobal*) ckalloc (sizeof (RNMATHglobal));
	    rnmathglobal->counter = 0;

	    Tcl_SetAssocData (interp, KEY, proc,
			      (ClientData) rnmathglobal);
	}

	rnmathglobal->counter ++;
	sprintf (rnmathglobal->buf, PREFIX "%s%d", rtype, rnmathglobal->counter);
	return rnmathglobal->buf;

#undef  KEY
    }

    static void
    RNMATHdeleteCmd (ClientData clientData)
    {
	/* Release the generator parameters */
	ckfree ((char*) clientData);
    }

    static RNMATHparam*
    RNMATHnewCmd (Tcl_Interp* interp, char* rtype, char* name, Tcl_ObjCmdProc p)
    {
	Tcl_Obj*    fqn;
	Tcl_CmdInfo ci;
	RNMATHparam*   rnmathp;

	if (!name) {
	    name = AutoName (interp, rtype);
	}

	if (!Tcl_StringMatch (name, "::*")) {
	    /* Relative name. Prefix with current namespace */

	    Tcl_Eval (interp, "namespace current");
	    fqn = Tcl_GetObjResult (interp);
	    fqn = Tcl_DuplicateObj (fqn);
	    Tcl_IncrRefCount (fqn);

	    if (!Tcl_StringMatch (Tcl_GetString (fqn), "::")) {
		Tcl_AppendToObj (fqn, "::", -1);
	    }
	    Tcl_AppendToObj (fqn, name, -1);
	} else {
	    fqn = Tcl_NewStringObj (name, -1);
	    Tcl_IncrRefCount (fqn);
	}
	Tcl_ResetResult (interp);

	if (Tcl_GetCommandInfo (interp,
				Tcl_GetString (fqn),
				&ci)) {
	    Tcl_Obj* err;

	    err = Tcl_NewObj ();
	    Tcl_AppendToObj    (err, "command \"", -1);
	    Tcl_AppendObjToObj (err, fqn);
	    Tcl_AppendToObj    (err, "\" already exists, unable to create generator", -1);

	    Tcl_DecrRefCount (fqn);
	    Tcl_SetObjResult (interp, err);
	    return NULL;
	}

	rnmathp = (RNMATHparam*) ckalloc (sizeof (RNMATHparam));

	Tcl_CreateObjCommand (interp, Tcl_GetString (fqn),
			      p, (ClientData) rnmathp,
			      RNMATHdeleteCmd);

	Tcl_SetObjResult (interp, fqn);
	Tcl_DecrRefCount (fqn);

	return rnmathp;
    }

    static double
    RANDOM (void)
    {
	return random () / 2147483647.0;
    }
}

# # ## ### ##### ######## ############# #####################
## Genereate generators from the imported API.

::random::wrap

# # ## ### ##### ######## ############# #####################
## Finalization; drop helper commands, and provide the package.

unset  ::random::T
rename ::random::cap      {}
rename ::random::indent   {}
rename ::random::genclass {}
rename ::random::wrap     {}

package provide random 1
return