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
|