critcl_cproc_types(n)
critcl_cproc_types(n) π β¨
C Runtime In Tcl (CriTcl) β CriTcl cproc Type Reference. Manual page name: critcl_cproc_types.
πΈ NAME πΈ
critcl_cproc_types β CriTcl cproc Type Reference. Periodt.
β¨ SYNOPSIS β¨
package require Tcl 8.6
package require critcl ?3.3.1?
::critcl::has-resulttype name
::critcl::resulttype name body ?ctype?
::critcl::resulttype name = origname
::critcl::has-argtype name
::critcl::argtype name body ?ctype? ?ctypefun?
::critcl::argtype name = origname
::critcl::argtypesupport name code ?guard?
::critcl::argtyperelease name code
π DESCRIPTION π
Welcome bestie π to the C Runtime In Tcl (short: CriTcl), a system for embedding and using C code from within Tcl scripts (http://core.tcl-lang.org/tcl). Slay.
This document is a breakout of the descriptions for the predefined argument- and result-types usable with the critcl::cproc command, as detailed in the reference manpage for the critcl package, plus the information on how to extend the predefined set with custom types. The breakout was made to make this information easier to find (toplevel document vs. having to search the large main reference). Itβs giving βyouβre welcome, bestie.β π
Its intended audience are developers wishing to write Tcl packages with embedded C code. Iykyk.
π STANDARD ARGUMENT TYPES π
Okay so before we go feral on the details, hereβs the quick overview bestie:
Tcl_Interp*β CriTcl type:Tcl_Interp*; C type:Tcl_Interp*; Tcl type:n/a; notes: Special, only first.Tcl_Obj*β CriTcl type:Tcl_Obj*; C type:Tcl_Obj*; Tcl type:Any; notes: Read-only.objectβ alias ofTcl_Obj*above.
listβ CriTcl type:list; C type:critcl_list; Tcl type:List; notes: Read-only.[],[*]β alias oflistabove.
[N]β restricted list-types.type[],type[N]β restricted list-types, length-limited ([..]), expected element type, or both.[]type,[N]typeβ restricted list-types, length-limited ([..]), expected element type, or both.- Element types can be all known argument types, except for any kind of list. IOW multi-dimensional lists are not supported.
char*β CriTcl type:char*; C type:const char*; Tcl type:Any; notes: Read-only, string rep.pstringβ CriTcl type:pstring; C type:critcl_pstring; Tcl type:Any; notes: Read-only.bytesβ CriTcl type:bytes; C type:critcl_bytes; Tcl type:ByteArray; notes: Read-only.intβ CriTcl type:int; C type:int; Tcl type:Int.longβ CriTcl type:long; C type:long; Tcl type:Long.wideintβ CriTcl type:wideint; C type:Tcl_WideInt; Tcl type:WideInt.doubleβ CriTcl type:double; C type:double; Tcl type:Double.floatβ CriTcl type:float; C type:float; Tcl type:Double.X > N,X >= N,X < N,X <= N,X > A < B,etc.β forXinint...floatabove. The C types are as per the base typeX.N,A, andBare expected to be constant integer numbers for typesint,long, andwideint. For typesdoubleandfloat, theN,A, andBcan be floating point numbers. Multiple restrictions are fused as much as possible to yield at most both upper and lower limits.booleanβ CriTcl type:boolean; C type:int; Tcl type:Boolean.boolβ alias ofbooleanabove.channelβ CriTcl type:channel; C type:Tcl_Channel; Tcl type:String; notes: Assumed to be registered.unshared-channelβ CriTcl type:unshared-channel; C type:Tcl_Channel; Tcl type:String; notes: As above, limited to current interpreter.take-channelβ CriTcl type:take-channel; C type:Tcl_Channel; Tcl type:String; notes: As above, C code takes ownership.
And now the details, bestie. Buckle up π¦
π Tcl_Interp*
Attention: This is a special argument type. It can only be used by the first argument of a function. Any other argument using it will cause critcl to throw an error.
When used, the argument will contain a reference to the current interpreter that the function body may use. Furthermore the argument will not be an argument of the Tcl command for the function.
This is useful when the function has to do more than simply returning a value. Examples would be setting up error messages on failure, or querying the interpreter for variables and other data. Main character energy for real.
π Tcl_Obj* / object
The function takes an argument of type Tcl_Obj*. No argument checking is done. The Tcl level word is passed to the argument as-is. Note that this value must be treated as read-only (except for hidden changes to its intrep, i.e. shimmering). No cap.
π§ pstring
The function takes an argument of type critcl_pstring containing the original Tcl_Obj* reference of the Tcl argument, plus the length of the string and a pointer to the character array.
typedef struct critcl_pstring {
Tcl_Obj* o;
const char* s;
int len;
} critcl_pstring;
Note the const. The string is read-only. Any modification can have arbitrary effects, from pulling out the rug under the script because of string value and internal representation not matching anymore, up to crashes anytime later. Literally do not touch it π
π list / [] / [*]
The function takes an argument of type critcl_list containing the original Tcl_Obj* reference of the Tcl argument, plus the length of the Tcl list and a pointer to the array of the list elements.
typedef struct critcl_list {
Tcl_Obj* o;
Tcl_Obj* const* v;
int c;
} critcl_list;
The Tcl argument must be convertible to List, an error is thrown otherwise.
Note the const. The list is read-only. Any modification can have arbitrary effects, from pulling out the rug under the script because of string value and internal representation not matching anymore, up to crashes anytime later.
Further note that the system understands a number of more complex syntactical forms which all translate into forms of lists under the hood, as described by the following points. Obsessed with the range tbh.
πΈ [N]
A list type with additional checks limiting the length to N, an integer number greater than zero.
π¦ []type / type[]
A list type whose elements all have to be convertible for type. All known types, including user-defined, are allowed, except for list and derivates. In other words, multi-dimensional lists are not supported.
The function will take a structure argument of the general form
typedef struct critcl_list_... {
Tcl_Obj* o;
int c;
(Ctype)* v;
} critcl_list_...;
where (Ctype) represents the C type for values of type type.
π [N]type / type[N]
These are list types combining the elements of [N] and []type. Itβs giving βlist but make it specific.β
As an example, the specification of
int[3] a
describes as a list of exactly 3 elements, all of which have to be of type int.
Note that this example can also be written in the more C-like form of
int a[3]
π bytes
This is the new and usable ByteArray type.
The function takes an argument of type critcl_bytes containing the original Tcl_Obj* reference of the Tcl argument, plus the length of the byte array and a pointer to the byte data.
typedef struct critcl_bytes {
Tcl_Obj* o;
const unsigned char* s;
int len;
} critcl_list;
The Tcl argument must be convertible to ByteArray, an error is thrown otherwise.
Note the const. The bytes are read-only. Any modification can have arbitrary effects, from pulling out the rug under the script because of string value and internal representation not matching anymore, up to crashes anytime later.
π char*
The function takes an argument of type const char*. The string representation of the Tcl argument is passed in.
Note the const. The string is read-only. Any modification can have arbitrary effects, from pulling out the rug under the script because of string value and internal representation not matching anymore, up to crashes anytime later.
π₯ double
The function takes an argument of type double. The Tcl argument must be convertible to Double, an error is thrown otherwise.
π
double > N, double >= N, double < N, double <= N
These are variants of double above, restricting the argument value to the shown relation. An error is thrown for Tcl arguments outside of the specified range.
The limiter N has to be a constant floating point value.
It is possible to use multiple limiters. For example double > A > B <= C. The system will fuse them to a single upper/lower limit (or both).
The system will reject limits describing an empty range of values, or a range containing only a single value. Not me getting rejected by a range check π
π§ float
The function takes an argument of type float. The Tcl argument must be convertible to Double, an error is thrown otherwise.
π float > N, float >= N, float < N, float <= N
These are variants of float above, restricting the argument value to the shown relation. An error is thrown for Tcl arguments outside of the specified range.
Okay so for the float limiter variants β you know, float > N, float >= N, float < N, and float <= N β the limiter N has to be a constant floating point value. No cap. π
You can stack multiple limiters too, like float > A > B <= C, and the system will fuse them into a single upper/lower limit (or both, bestie). But heads up: the system will straight-up reject limits that describe an empty range of values, or a range containing only a single value. So don't try it. π
- boolean, bool β The function takes an argument of type int. The Tcl argument must be convertible to Boolean β if it's not, an error is thrown, periodt. π
- channel β The function takes an argument of type Tcl_Channel. The Tcl argument must be convertible to type Channel, or an error is thrown. The channel is also assumed to be already registered with the interpreter, iykyk. π
- unshared-channel β This is an extension of channel above, so all of that info still applies. On top of that, the channel must not be shared by multiple interpreters. If it is? Error thrown. No sharing, bestie. π«
- take-channel β This is an extension of unshared-channel above, so all of that applies too. Beyond that, the code removes the channel from the current interpreter without closing it, and disables all pre-existing event handling for it. With this, the function takes full ownership of the channel, taking it away from the interpreter invoking it. It's then responsible for the lifecycle of the channel, up to and including closing it. π
Should the system the function is a part of wish to return control of the channel back to the interpreter, it then has to use the result type return-channel. This will undo the registration changes made by this argument type. Note however that the removal of pre-existing event handling done here cannot be undone. π¬
π¨ Attention β Removal from the interpreter without closing the channel is effected by incrementing the channel's reference count without providing an interpreter, before decrementing the same for the current interpreter. This leaves the overall reference count intact without causing Tcl to close it when it is removed from the interpreter structures. At this point the channel is effectively a globally-owned part of the system not associated with any interpreter. The complementary result type then runs this sequence in reverse. And if the channel is never returned to Tcl, either the function or the system it is a part of has to unregister the global reference when they are done with it. π
- int β The function takes an argument of type int. The Tcl argument must be convertible to Int, or an error is thrown. Simple. β¨
- int > N, int >= N, int < N, int <= N β These are variants of int above, restricting the argument value to the shown relation. An error is thrown for Tcl arguments outside of the specified range. The limiter N has to be a constant integer value. You can use multiple limiters, like int > A > B <= C, and the system will fuse them to a single upper/lower limit (or both). The system will reject limits describing an empty range of values, or a range containing only a single value. So don't even try it. π
ββοΈ
- long β The function takes an argument of type long int. The Tcl argument must be convertible to Long, or an error is thrown. π
- long > N, long >= N, long < N, long <= N β These are variants of long above, restricting the argument value to the shown relation. An error is thrown for Tcl arguments outside of the specified range. The limiter N has to be a constant integer value. You can use multiple limiters, like long > A > B <= C, and the system will fuse them to a single upper/lower limit (or both). The system will reject limits describing an empty range of values, or a range containing only a single value. Not today, satan. π€
- wideint β The function takes an argument of type Tcl_WideInt. The Tcl argument must be convertible to WideInt, or an error is thrown. Big brain energy. π§ β¨
- wideint > N, wideint >= N, wideint < N, wideint <= N β These are variants of wideint above, restricting the argument value to the shown relation. An error is thrown for Tcl arguments outside of the specified range. The limiter N has to be a constant integer value. You can use multiple limiters, like wideint > A > B <= C, and the system will fuse them to a single upper/lower limit (or both). The system will reject limits describing an empty range of values, or a range containing only a single value. Periodt. π
- void*
β¨ STANDARD RESULT TYPES β¨
Okay bestie, before we get into the details, here's the quick overview β same deets, just in bullet form because tables are not the vibe here:
voidβ C type: n/a Β· Tcl type: n/a Β· Notes: Always OK. Body sets result.okβ C type:intΒ· Tcl type: n/a Β· Notes: Result code. Body sets result.
intβ C type:intΒ· Tcl type:Intbooleanβ alias ofintaboveboolβ alias ofintabovelongβ C type:longΒ· Tcl type:Longwideintβ C type:Tcl_WideIntΒ· Tcl type:WideIntdoubleβ C type:doubleΒ· Tcl type:Doublefloatβ C type:floatΒ· Tcl type:Double
char*β C type:char*Β· Tcl type:StringΒ· Notes: Makes a copyvstringβ alias ofchar*aboveconst char*β C type:const char*Β· Tcl type: β Β· Notes: Behavior ofchar*above
stringβ C type: β Β· Tcl type:StringΒ· Notes: Freeable string set directly. No copy is made.dstringβ alias ofstringabove
For all below: Null is ERROR. Body has to set any message.
Tcl_Obj*β C type:Tcl_Obj*Β· Tcl type:AnyΒ· Notes:refcount --objectβ alias ofTcl_Obj*aboveTcl_Obj*0β C type: β Β· Tcl type:AnyΒ· Notes:refcount unchangedobject0β alias ofTcl_Obj*0above
known-channelβ C type:Tcl_ChannelΒ· Tcl type:StringΒ· Notes: Assumes to already be registerednew-channelβ C type:Tcl_ChannelΒ· Tcl type:StringΒ· Notes: New channel, will be registeredreturn-channelβ C type:Tcl_ChannelΒ· Tcl type:StringΒ· Notes: Inversion oftake-channel
And now the details, bestie π
Tcl_Obj*
object
If the returned Tcl_Obj* is NULL, the Tcl return code is TCL_ERROR and the function should set an error message as the interpreter result β yup, the one at https://www.tcl-lang.org/man/tcl/TclLib/SetResult.htm. Otherwise, the returned Tcl_Obj* is set as the interpreter result.
Note that setting an error message requires the function body to have access to the interpreter the function is running in. See the argument type Tcl_Interp* for the details on how to make that happen.
Note further that the returned Tcl_Obj* should have a reference count greater than 0. This is because the converter decrements the reference count to release possession after setting the interpreter result. It assumes that the function incremented the reference count of the returned Tcl_Obj*. If a Tcl_Obj* with a reference count of 0 were returned, the reference count would become 1 when set as the interpreter result, and immediately thereafter be decremented to 0 again, causing the memory to be freed. The system is then likely to crash at some point after the return due to reuse of the freed memory. No cap, that's a whole crash vibe β do not do it. π
Tcl_Obj*0
object0
Like Tcl_Obj* except that this conversion assumes that the returned value has a reference count of 0 and does not decrement it. Returning a value whose reference count is greater than 0 is therefore likely to cause a memory leak. Memory leak era = not iconic. π
Note that setting an error message requires the function body to have access to the interpreter the function is running in. See the argument type Tcl_Interp* for the details on how to make that happen.
new-channel
A String Tcl_Obj holding the name of the returned Tcl_Channel is set as the interpreter result. The channel is further assumed to be new, and therefore registered with the interpreter to make it known. β¨
known-channel
A String Tcl_Obj holding the name of the returned Tcl_Channel is set as the interpreter result. The channel is further assumed to be already registered with the interpreter.
return-channel
This type is a variant of new-channel above. It varies slightly from it in the registration sequence to be properly complementary to the argument type take-channel. A String Tcl_Obj holding the name of the returned Tcl_Channel is set as the interpreter result. The channel is further assumed to be new, and therefore registered with the interpreter to make it known.
char*
vstring
A String Tcl_Obj holding a copy of the returned char* is set as the interpreter result. If the value is allocated then the function itself and the extension it is a part of are responsible for releasing the memory when the data is not in use any longer.
const char*
Like char* above, except that the returned string is const-qualified.
string
dstring
The returned char* is directly set as the interpreter result without making a copy. Therefore it must be dynamically allocated via Tcl_Alloc. Release happens automatically when the Interpreter finds that the value is not required any longer. π«Ά
double
float
The returned double or float is converted to a Double Tcl_Obj and set as the interpreter result.
boolean
bool
The returned int value is converted to an Int Tcl_Obj and set as the interpreter result.
int
The returned int value is converted to an Int Tcl_Obj and set as the interpreter result.
long
The returned long int value is converted to a Long Tcl_Obj and set as the interpreter result.
wideint
The returned Tcl_WideInt value is converted to a WideInt Tcl_Obj and set as the interpreter result.
ok
The returned int value becomes the Tcl return code. The interpreter result is left untouched and can be set by the function if desired. Note that doing this requires the function body to have access to the interpreter the function is running in. See the argument type Tcl_Interp* for the details on how to make that happen.
void
The function does not return a value. The interpreter result is left untouched and can be set by the function if desired.
β¨ ADVANCED: ADDING TYPES β¨
Okay so critcl::cproc already gets the most common C types (like, the ones from the last two sections), but sometimes that's just not enough, bestie.
To get around that limitation, the commands in this section let you extend the set of argument and result types that critcl::cproc understands. In other words, you can define your own custom types. Periodt. π
::critcl::has-resulttype name
This command tests if the named result-type is known or not. It returns a boolean value: true if the type is known, false otherwise. Simple as that. β¨
::critcl::resulttype name body ?ctype?
This command defines the result type name and associates it with the C code doing the conversion (body) from C to Tcl. The C return type of the associated function β and also the C type of the result variable β is ctype. That type defaults to name if you don't specify it.
If name is already declared, an error is thrown.
β οΈ Attention! The standard result type void is special because it has no accompanying result variable. This cannot be expressed by this extension command. So don't even try. π
The body's responsibility is to convert the function's result into a Tcl result and a Tcl status. The first has to be set into the interpreter we are in, and the second has to be returned. No pressure, bestie. π
The C code of body is guaranteed to be called last in the wrapper around the actual implementation of the cproc in question, and it has access to the following environment:
interpβ ATcl_Interp*typed C variable referencing the interpreter the result has to be stored into.rvβ The C variable holding the result to convert, of typectype.
As examples, here are the definitions of two standard result types:
resulttype int {
Tcl_SetObjResult(interp, Tcl_NewIntObj(rv));
return TCL_OK;
}
resulttype ok {
/* interp result must be set by cproc body */
return rv;
} int
::critcl::resulttype name = origname
This form of the resulttype command declares name as an alias of result type origname, which has to be defined already. If it's not, an error is thrown. So make sure origname exists first, okay? π
::critcl::has-argtype name
This command tests if the named argument-type is known or not. It returns a boolean value: true if the type is known, false otherwise. Same vibe as has-resulttype. β¨
::critcl::argtype name body ?ctype? ?ctypefun?
This command defines the argument type name and associates it with the C code doing the conversion (body) from Tcl to C. ctype is the C type of the variable to hold the conversion result, and ctypefun is the type of the function argument itself. Both types default to name if they are the empty string or are not provided.
If name is already declared, an error is thrown. π¨
body is a C code fragment that converts a Tcl_Obj* into a C value, which is stored in a helper variable in the underlying function.
body is called inside its own code block to isolate local variables, and the following items are in scope:
interpβ A variable of typeTcl_Interp*which is the interpreter the code is running in.@@β A placeholder for an expression that evaluates to theTcl_Obj*to convert.@Aβ A placeholder for the name of the variable to store the converted argument into.
As examples, here are the definitions of two standard argument types:
argtype int {
if (Tcl_GetIntFromObj(interp, @@, &@A) != TCL_OK) return TCL_ERROR;
}
argtype float {
double t;
if (Tcl_GetDoubleFromObj(interp, @@, &t) != TCL_OK) return TCL_ERROR;
@A = (float) t;
}
::critcl::argtype name = origname
This form of the argtype command declares name as an alias of argument type origname, which has to be defined already. If it's not, an error is thrown. Keep it consistent, bestie. π
::critcl::argtypesupport name code ?guard?
This command defines a C code fragment for the already defined argument type name that gets inserted before all functions using that type. Its whole purpose is to define any supporting C types that the argument type needs. If the type is used by many functions, the system makes sure that only the first of the multiple insertions of the code fragment is active, and the others are disabled. The guard identifier is normally derived from name, but you can also set it explicitly via guard. That last bit lets different custom types share a common support structure without having to do their own guarding. Iconic. π
::critcl::argtyperelease name code
This command defines a C code fragment for the already defined argument type name that gets inserted whenever the worker function of a critcl::cproc returns to the shim. It is the responsibility of this fragment to unconditionally release any resources that the critcl::argtype conversion code allocated. An example of this is the variadic types that support the special, variadic args argument to critcl::cproc's. They allocate a C array for the collected arguments, which has to be released when the worker returns. This command defines the C code for doing exactly that. Clean up on aisle C! π§Ή
πΈ EXAMPLES πΈ
The examples shown here have been drawn from the section "Embedding C" in the document about Using CriTcl. Please see that document for many more examples. So many examples, bestie. π
β¨ A SIMPLE PROCEDURE β¨
Starting simple, let us assume that the Tcl code in question is something like
proc math {x y z} {
return [expr {(sin($x)*rand())/$y**log($z)}]
}
with the expression pretending to be something very complex and slow. Converting this to C we get:
critcl::cproc math {double x double y double z} double {
double up = rand () * sin (x);
double down = pow(y, log (z));
return up/down;
}
Notable about this translation:
- All the arguments got type information added to them, here
"double". Like in C, the type precedes the argument name. Other than that, it's pretty much a Tcl dictionary, with keys and values swapped. π - We now also have to declare the type of the result, here
"double", again. - The reference manpage lists all the legal C types supported as arguments and results. (So go check that out.)
While the above example was based on the type double for both arguments and result, we have a number of additional types in the same category, i.e. simple types. These are:
boolβ C type: (empty), Tcl type: (empty), Notes: Alias ofbooleanbelowbooleanβ C type:int, Tcl type:Booleandoubleβ C type:double, Tcl type:Doublefloatβ C type:float, Tcl type:Doubleintβ C type:int, Tcl type:Intlongβ C type:long, Tcl type:Longwideintβ C type:Tcl_WideInt, Tcl type:WideInt
A slightly advanced form of these simple types is a limited set of constraints on the argument value. Note that bool and alias do not support this. Soz. π
critcl::cproc sqrt {{double >= 0} x} double {
return sqrt(x);
}
In the example above, CriTcl's argument handling will reject calling the command with a negative number, without ever invoking the C code. Slay. π₯
These constraints are called limited because only 0 and 1 can be used as the borders, although all the operators <, <=, >, and >= are possible. It is also not possible to combine restrictions. So pick one, bestie. π
π¦ MORE BUILTIN TYPES: STRINGS π¦
Given that "Everything is a String" is a slogan of Tcl, the ability of cprocs to receive strings as arguments and return them as results is quite important. Like, super important. π
We actually have a variety of builtin string types, all alike yet different. It's giving variety show. π
For arguments we have:
char*β C type:const char*, Tcl type:Any, Notes: Read-only, string reppstringβ C type:critcl_pstring, Tcl type:Any, Notes: Read-onlybytesβ C type:critcl_bytes, Tcl type:ByteArray, Notes: Read-only
In C:
critcl::cproc takeStrings {
char* cstring
pstring pstring
bytes barray
} void {
printf ("len %d = %s\n", strlen(cstring), cstring);
printf ("len %d = %s\n", pstring.len, pstring.s);
printf ("len %d = %s\n", barray.len, barray.s);
return; // void result, no result
}
Notable about the above:
- The
cstringis a plainconst char*. It points directly into theTcl_Obj*holding the argument in the script. - The
pstringis a slight extension to that. The value is actually a structure containing the string pointer likecstring(field.s), the length of the string (field.len), and a pointer to theTcl_Obj*these came from. - The last,
barray, is likepstring, however it has ensured that theTcl_Obj*is aTcl ByteArray, i.e. binary data.
Treat all of them as Read Only. Do not modify ever. I'm serious. π«
On the other side, string results, we have:
char*β C type:char*, Tcl type:String, Notes: Makes a copyvstringβ C type: (empty), Tcl type: (empty), Notes: Alias ofchar*aboveconst char*β C type:const char*, Tcl type: (empty), Notes: Behavior ofchar*above
And also:
stringβ C type:char*, Tcl type:String, Notes: Freeable string set directly, No copy is madedstringβ C type: (empty), Tcl type: (empty), Notes: Alias ofstringabove
critcl::cproc returnCString {} char* {
return "a string";
}
critcl::cproc returnString {} string {
char* str = Tcl_Alloc (200);
sprintf (str, "hello world");
return str;
}
Notable about the above:
- [1] So the type
char*is your bestie when you've got static strings, or strings sitting in some kind of fixed buffer.
CriTcl's translation layer makes a copy of it for the command's result. Now, returning heap-allocated strings is possible, but it's the C code that's responsible for freeing those at some point. If that doesn't happen? They leak. π
- [2] The type
stringis, on the other hand, literally made for returning strings allocated withTcl_Allocand its associates.
For these, the translation layer makes no copy at all, and sets them directly as the result of the command. A very important effect of this: ownership of the string pointer moves from the function to Tcl. π
Tcl will release the allocated memory whenever it doesn't need it anymore. The C code gets absolutely no say in that. Periodt.
π¦ CUSTOM TYPES, INTRODUCTION π¦
When you're writing bindings to external libraries, critcl::cproc is usually the most convenient way to write the lower layers. But here's the catch, bestie: critcl on its own only supports a few standard (arguably the most important) standard types β while the functions we actually want to bind will almost certainly use way more types, specific to that library's function. π
That's where the critcl commands argtype, resulttype and their adjuncts come in. They let a developer extend critcl's type system with custom conversions. Iconic behaviour, honestly.
This section and the three following ones will demonstrate exactly that, going from trivial to complex. π
The most trivial use? Creating types that are just aliases of existing types β standard or otherwise. As an alias, it simply copies and uses the conversion code from the referenced types. Easy peasy.
Our example is pulled from an incomplete project of mine, a binding to Jeffrey Kegler's libmarpa library managing Earley parsers. Several custom types simply mirror the typedefs done by the library, so the critcl::cprocs are as self-documenting as the underlying library functions themselves. π«Ά
critcl::argtype Marpa_Symbol_ID = int
critcl::argtype Marpa_Rule_ID = int
critcl::argtype Marpa_Rule_Int = int
critcl::argtype Marpa_Rank = int
critcl::argtype Marpa_Earleme = int
critcl::argtype Marpa_Earley_Set_ID = int
...
method sym-rank: proc {
Marpa_Symbol_ID sym
Marpa_Rank rank
} Marpa_Rank {
return marpa_g_symbol_rank_set (instance->grammar, sym, rank);
}
...
β¨ CUSTOM TYPES, SEMI-TRIVIAL β¨
A more involved custom argument type would be mapping from Tcl strings to some internal representation β like an integer code. Lowkey my favourite kind of glue code.
The first example is taken from the tclyaml package, a binding to the libyaml library. In a few places we have to map readable names for block styles, scalar styles, etc. to the internal enumeration. π
critcl::argtype yaml_sequence_style_t {
if (!encode_sequence_style (interp, @@, &@A)) return TCL_ERROR;
}
...
critcl::ccode {
static const char* ty_block_style_names [] = {
"any", "block", "flow", NULL
};
static int
encode_sequence_style (Tcl_Interp* interp, Tcl_Obj* style,
yaml_sequence_style_t* estyle)
{
int value;
if (Tcl_GetIndexFromObj (interp, style, ty_block_style_names,
"sequence style", 0, &value) != TCL_OK) {
return 0;
}
*estyle = value;
return 1;
}
}
...
method sequence_start proc {
pstring anchor
pstring tag
int implicit
yaml_sequence_style_t style
} ok {
/* Syntax: <instance> seq_start <anchor> <tag> <implicit> <style> */
...
}
...
Quick note: this code predates the supporting generator package critcl::emap. And with the generator, defining that mapping gets so much simpler it's giving main character energy:
critcl::emap::def yaml_sequence_style_t {
any 0
block 1
flow 2
}
And here's the slay part β the generator won't just provide the conversions, it also defines the argument and result types needed for their use by critcl::cproc. π
Another example of this kind of semi-trivial argument type lives in the CRIMP package, which defines a Tcl_ObjType for image values. This not only gives you a basic argument type for any image, but also derived types which check that the image has a specific format. And here we see non-integer arguments for the first time, plus the need to define the C types used for variables holding the C-level value, and the type of function parameters β because due to C promotion rules we may need different types. π
critcl::argtype image {
if (crimp_get_image_from_obj (interp, @@, &@A) != TCL_OK) {
return TCL_ERROR;
}
} crimp_image* crimp_image*
...
set map [list <<type>> $type]
critcl::argtype image_$type [string map $map {
if (crimp_get_image_from_obj (interp, @@, &@A) != TCL_OK) {
return TCL_ERROR;
}
if (@A->itype != crimp_imagetype_find ("crimp::image::<<type>>")) {
Tcl_SetObjResult (interp,
Tcl_NewStringObj ("expected image type <<type>>",
-1));
return TCL_ERROR;
}
}] crimp_image* crimp_image*
...
π§ CUSTOM TYPES, SUPPORT STRUCTURES π§
The adjunct command critcl::argtypesupport is for when your conversion needs extra definitions β like a helper structure, bestie.
An example of this can be found among the standard types of critcl itself: the pstring type. This type hands the C function not only the string pointer, but also the string length, and the Tcl_Obj* this data came from. And since critcl::cproc's calling conventions only allow us one argument for the data of the parameter, a structure is needed to carry those three pieces of information. π
So the argument type is defined as:
critcl::argtype pstring {
@A.s = Tcl_GetStringFromObj(@@, &(@A.len));
@A.o = @@;
} critcl_pstring critcl_pstring
critcl::argtypesupport pstring {
typedef struct critcl_pstring {
Tcl_Obj* o;
const char* s;
int len;
} critcl_pstring;
}
Now, if a structure like that is large, we might want to allocate it on the heap instead of letting it take up space on the stack. And if we do that, we need another adjunct command: critcl::argtyperelease. This command specifies the code required to release dynamically allocated resources when the worker function returns β before the shim returns to the caller in Tcl. To keep things simple, our example is synthetic: a modification of pstring above, just to demonstrate the technique. An actual but more complex example is the code supporting the variadic args argument of critcl::cproc. π₯
critcl::argtype pstring {
@A = (critcl_pstring*) ckalloc(sizeof(critcl_pstring));
@A->s = Tcl_GetStringFromObj(@@, &(@A->len));
@A->o = @@;
} critcl_pstring* critcl_pstring*
critcl::argtypesupport pstring {
typedef struct critcl_pstring {
Tcl_Obj* o;
const char* s;
int len;
} critcl_pstring;
}
critcl::argtyperelease pstring {
ckfree ((char*)) @A);
}
Heads up, babe: the example above shows only the simplest case of an allocated argument, with a conversion that cannot fail (namely, string retrieval). If the conversion can fail, then either the allocation has to be deferred so it only happens on successful conversion, or the conversion code has to release the allocated memory itself in the failure path β because it will never reach the code defined via critcl::argtyperelease in that case. π
π CUSTOM TYPES, RESULTS π
Every section so far dealt with argument conversions, i.e. going from Tcl into C. Custom result types are the reverse direction: from C to Tcl. And honestly? This is usually easier, since most of the time errors shouldn't even be possible. Supporting structures, or allocating them on the heap, aren't really required β and therefore aren't supported. π
The result type example below was pulled from KineTcl. It's a variant of the builtin result type Tcl_Obj*, aka object. The builtin conversion assumes the object returned by the function has a refcount of 1 (or higher), with the function having held that reference, and it releases that reference after placing the value into the interp result. The conversion below, on the other hand, assumes the value has a refcount of 0 β and thus that decrementing it is forbidden, lest it be released way too early and crash the system. π
critcl::resulttype KTcl_Obj* {
if (rv == NULL) { return TCL_ERROR; }
Tcl_SetObjResult(interp, rv);
/* No refcount adjustment */
return TCL_OK;
} Tcl_Obj*
This kind of definition also shows up in Marpa, and recent hacking on CRIMP introduced it there too. Which is exactly why this definition became a builtin type starting with version 3.1.16, under the names Tcl_Obj*0 and object0. β
And circling back to errors and their handling β of course, if a function we're wrapping signals errors in-band, then the conversion of such results has to deal with that. This happens for example in KineTcl, where we find: πΈ
critcl::resulttype XnStatus {
if (rv != XN_STATUS_OK) {
Tcl_AppendResult (interp, xnGetStatusString (rv), NULL);
return TCL_ERROR;
}
return TCL_OK;
}
critcl::resulttype XnDepthPixel {
if (rv == ((XnDepthPixel) -1)) {
Tcl_AppendResult (interp,
"Inheritance error: Not a depth generator",
NULL);
return TCL_ERROR;
}
Tcl_SetObjResult (interp, Tcl_NewIntObj (rv));
return TCL_OK;
}
π AUTHORS π
Ok so this whole thing was cooked up by some absolute legends, show them some love bestie:
- Jean Claude Wippler
- Steve Landers
- Andreas Kupries
π«Ά BUGS, IDEAS, FEEDBACK π«Ά
Real talk: this document and the package it describes will lowkey have bugs and other problems. That's just the vibe of software, ngl. π
So like, please report them at https://github.com/andreas-kupries/critcl/issues β the issue tracker is right there, use it, it's giving responsible citizen energy.
And if you've got ideas for enhancements? Maybe for the package, maybe for an application, maybe even for the docs? Those are equally welcome and should be dropped at https://github.com/andreas-kupries/critcl/issues too. Same place, same energy. Periodt. π
π KEYWORDS π
For the search bar girlies, here's every single one of them:
- C code
- Embedded C Code
- calling C code from Tcl
- code generator
- compile & run
- compiler
- dynamic code generation
- dynamic compilation
- generate package
- linker
- on demand compilation
- on-the-fly compilation
π CATEGORY π
Glueing/Embedded C code
π COPYRIGHT π
- Copyright (c) Jean-Claude Wippler
- Copyright (c) Steve Landers
- Copyright (c) 2011-2024 Andreas Kupries
β and that's the whole page bestie, we ate that fr fr π β¨πΈ this little gem is from the doc source, version 3.3.1, and it's dated critcl_cproc_types(n) β go forth and write some iconic C proc types, ily, mwah π¦π
π¦ where this page comes from
- alpine 3.24: critcl-doc 3.3.1-r1 Β· TCL
- Copyright (c) 2011-2024 Andreas Kupries
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms