critcl_package(3tcl)

πŸ“¦
version 3.3.1 Β· 2 versions
, in ubuntu resolute, ubuntu stonking

✨ NAME ✨

bestie wake up, it's critcl_package(3tcl) β€” C Runtime In Tcl (aka CriTcl) β€” the official CriTcl Package Reference. πŸŽ€


🌸 SYNOPSIS 🌸

okay so first, the prerequisites, no skipping:

package require Tcl 8.6

package require critcl ?3.3.1?

package require platform ?1.0.2?

package require md5 ?2?

and then the whole entire roster of commands, in order, here she is πŸ’…:

::critcl::ccode fragment

::critcl::ccommand tclname cname

::critcl::ccommand tclname arguments body ?option value...?

::critcl::cdata tclname data

::critcl::cconst tclname resulttype value

::critcl::cdefines list of glob patterns ?namespace?

::critcl::cproc name arguments resulttype body ?option value...?

::critcl::cproc name arguments resulttype

::critcl::cinit text externals

::critcl::include path

::critcl::api import name version

::critcl::api function resulttype name arguments

::critcl::api header ?glob pattern...?

::critcl::api extheader ?file...?

::critcl::license author ?text...?

::critcl::summary text

::critcl::description text

::critcl::subject ?key...?

::critcl::meta key ?word...?

::critcl::meta? key

::critcl::buildrequirement script

::critcl::cheaders ?arg...?

::critcl::csources ?glob pattern...?

::critcl::clibraries ?glob pattern...?

::critcl::source glob pattern

::critcl::tsources glob pattern...

::critcl::owns glob pattern...

::critcl::cflags ?arg...?

::critcl::ldflags ?arg...?

::critcl::framework ?arg...?

::critcl::tcl version

::critcl::tk

::critcl::preload lib...

::critcl::debug area...

::critcl::check ?label? text

::critcl::checklink ?label? text

::critcl::msg ?-nonewline? msg

::critcl::print ?-nonewline? ?chan? msg

::critcl::compiled

::critcl::compiling

::critcl::done

::critcl::failed

::critcl::load

::critcl::config option ?val?

::critcl::cache ?path?

::critcl::clean_cache ?pattern...?

::critcl::readconfig path

::critcl::showconfig ?chan?

::critcl::showallconfig ?chan?

::critcl::chooseconfig target ?nomatcherr?

::critcl::setconfig target

::critcl::actualtarget

::critcl::buildforpackage ?flag?

::critcl::cnothingtodo file

::critcl::cresults ?file?

::critcl::crosscheck

::critcl::error msg

::critcl::knowntargets

::critcl::sharedlibext

::critcl::targetconfig

::critcl::buildplatform

::critcl::targetplatform

::critcl::cobjects ?glob pattern...?

::critcl::scan path

::critcl::name2c name

::critcl::argnames arguments

::critcl::argcnames arguments

::critcl::argcsignature arguments

::critcl::argvardecls arguments

::critcl::argconversion arguments ?n?

::critcl::argoptional arguments

::critcl::argdefaults arguments

::critcl::argsupport arguments

::critcl::userconfig define name description type ?default?

::critcl::userconfig query name

::critcl::userconfig set name value

::critcl::at::caller

::critcl::at::caller offset

::critcl::at::caller offset level

::critcl::at::here

::critcl::at::get*

::critcl::at::get

::critcl::at::= file line

::critcl::at::incr n...

::critcl::at::incrt str...

::critcl::at::caller!

::critcl::at::caller! offset

::critcl::at::caller! offset level

::critcl::at::here!

::critcl::collect_begin

::critcl::collect_end

::critcl::collect script

::critcl::make path contents

::preload library

πŸ¦‹ DESCRIPTION πŸ¦‹

welcomeeee to the C Runtime In Tcl (short: CriTcl) β€” the whole system for embedding and using C code from inside your Tcl scripts (http://core.tcl-lang.org/tcl). literally the glow-up of your C-and-Tcl situationship. πŸ”₯

The critcl package is the core of the system. Wanna see the whole picture? Go read Introduction To CriTcl. Wanna actually run the standalone critcl program? That's CriTcl Application. This core package is the one you use to embed C code into Tcl scripts. It also gives you access to the internals that other parts of the core lean on, and those are the ones worth knowing if you're the type to understand how the core works and what API it hands to the CriTcl Application. Those advanced sections are marked as such, so if you're just here to use the package, you can skip right past them, no guilt. 🫢

This package lives in the Core Package Layer of CriTcl. the vibes, mapped:

+----------------+
|Applications    |
| critcl         |
| critcl::app    |
+----------------+

*================*
|Core Packages   |
| critcl         |
| critcl::util   |
*================*

+----------------+
|Support Packages|
| stubs::*       |
| md5, platform  |
|  ...           |
+----------------+

πŸ’… API πŸ’…

quick note before we get into it, bestie: instead of saying "a Tcl script with embedded C code" or "a Tcl script containing CriTcl commands" over and over like a broken record, we're just gonna call that a CriTcl script. and a file holding a CriTcl script usually has the extension .tcl or .critcl. periodt.

🌈 EMBEDDED C CODE 🌈

These commands below all append C code fragments onto the current module. Fragments show up in the module in the order they're appended, so earlier fragments (variables, functions, macros, whatever) are visible to the later ones. order matters, iykyk.

  • ::critcl::ccode fragment β€” appends the C code in fragment to the current module, and returns the empty string. See Runtime Behaviour. ✨
  • ::critcl::ccommand tclname cname β€” exactly like the version documented below, except cname is the name of a C function that already exists. (so you're not writing it here, you're just pointing at it)
  • ::critcl::ccommand tclname arguments body ?option value...? β€” appends the code to create a Tcl command named tclname, plus the matching C function whose body is body, behaving exactly like Tcl's own Tcl_CreateObjCommand (docs at https://www.tcl-lang.org/man/tcl/TclLib/CrtObjCmd.htm).

arguments is a list of zero to four names for the standard arguments clientdata, interp, objc, and objv. Any names you leave out get the standard default names instead. ngl this is a way more low-level way to define a command than critcl::cproc β€” processing the items in objv is left entirely to you, the author, which means complete control over how the command's arguments get handled. See the Runtime Behaviour section.

Returns the empty string.

Each option can be one of:

  • -clientdata c-expression β€” hands over the client data for the new command. Defaults to NULL.
  • -delproc c-expression β€” hands over a function pointer of type Tcl_CmdDeleteProc (see https://www.tcl-lang.org/man/tcl/TclLib/CrtObjCmd.htm) to act as the deletion function for the new command. Defaults to NULL.
  • -cname boolean β€” if it's false (which is the default), a name for the corresponding C function gets automatically derived from the fully-qualified tclname. Otherwise, the name of the C function is just the last component of tclname.
  • ::critcl::cdata tclname data β€” appends the code to create a brand new Tcl command named tclname, which returns data as a ByteArray result. πŸ“

Returns the empty string.

  • ::critcl::cconst tclname resulttype value β€” appends the code to create a new Tcl command named tclname, which returns the constant value having the Tcl type resulttype. value can be a C macro, or a function call (including the parentheses) to any visible C function that takes no arguments. and unlike critcl::cdata, resulttype can be any type known to critcl::cproc. its semantics are equivalent to:
cproc $tclname {} $resulttype "return $value ;"

This is more efficient than critcl::cproc because there's no C function generated. efficient queen πŸ‘‘

Returns the empty string.

  • ::critcl::cdefines list of glob patterns ?namespace? β€” arranges for C enum and #define values that match one of the patterns in list of glob patterns to be created in the namespace namespace, each variable having the same as the corresponding C item. the default namespace is the global one, and a pattern that matches nothing just gets ignored, no drama.

The Tcl variables get created when the module is compiled, using the preprocessor so it can properly find every matching C definition.

Produces no C code. the C definitions you want must already exist β€” this only reads them. πŸ™ˆ

  • ::critcl::cproc name arguments resulttype body ?option value...? β€” appends a function having body as its body, plus another shim function to do all the needed conversions, plus the code to create a corresponding Tcl command named tclname. unlike critcl::ccommand, the arguments and result are typed, and CriTcl generates the conversion code between Tcl_Obj values and C data types for you. See also Runtime Behaviour.

Returns the empty string.

  • string option β€” each may be one of:
    • -cname boolean β€” if false (the default), a name for the corresponding C function is automatically derived from the fully-qualified tclname. Otherwise, the name of the C function is the last component of tclname.
    • -pass-cdata boolean β€” if false (the default), the shim function doing the conversion to and from the Tcl level does not pass the ClientData as the first argument to the function.
    • -arg-offset int β€” a non-negative integer, 0 by default, indicating how many hidden arguments come before the actual procedure arguments. used by higher-order code generators where there are prefix arguments that the function doesn't directly see but which still influence argument counting and extraction. sneaky but iconic
  • string resulttype β€” may be a predefined or a custom type. see the CriTcl cproc Type Reference for the full list of predefined types and how to extend them. unless otherwise noted, the Tcl return code is always TCL_OK.
  • list arguments β€” a multi-dictionary where each key is an argument type and its value is the argument name. for example:
int x int y

Each argument name must be a valid C identifier.

  • If the name is a list with two items in it, item one is the name and item two is the default value. Sneaky little trick: you can do a limited form of variadic arguments using these default values. Like, for example:
int {x 1}

So here x is an optional argument, type int, default value 1. Cute, right?

Heads up bestie: argument conversion is completely bypassed when the argument isn't provided. So a custom converter that does validation does not get a chance to validate the default value. In that case you gotta check the value yourself in the body of the function. No cap.

Each argument type can be a predefined type or a custom one. Go look at CriTcl cproc Type Reference for the full list of predefined types and how to extend them. πŸ’–

  • ::critcl::cproc name arguments resulttype β€” same as documented below, but you reach for this one when the C function named name already exists. Reusing, not creating.
  • ::critcl::cinit text externals β€” appends the C code in text and externals, but only after every other fragment appended by the previously-listed commands, no matter where this command sits in the CriTcl script relative to them. So like, everything they contain is visible. See also Runtime Behaviour.

The C code in text goes into the body of the initialization function of the shared library backing the CriTcl script, and it runs when that library is loaded into the interpreter. It gets access to the variable Tcl_Interp* interp, which references the Tcl interpreter currently being initialized.

externals goes outside and just before the initialization function β€” which makes it a great spot for any external symbols the initialization function needs but that shouldn't be reachable by any other parts of the C code. 🀫

Calls to this command are cumulative.

Returns the empty string.

  • ::critcl::include path β€” this one is just a convenient shorthand for
critcl::code {
  #include <${path}>
}

🧩 STUBS TABLE MANAGEMENT 🧩

CriTcl versions 3 and later give you critcl::api, which creates and manipulates stubs tables β€” that's Tcl's dynamic linking mechanism handling the resolution of symbols between C extensions. New to this? Go read http://wiki.tcl-lang.org/285 for an intro, and check the section Stubs Tables for the details of CriTcl's particular variant. iykyk. ✨

Importing stubs tables (aka APIs) from another extension:

  • ::critcl::api import name version β€” drops the following include directives into the CriTcl script and each of its companion .c files:
    1. #include <name/nameDecls.h>
    2. #include <name/nameStubLib.h>

Returns an error if name isn't in the search path for the compiler. See critcl::cheaders and the critcl application's -I and -includedir options.

Important: if name is a fully-qualified name in a non-global namespace, e.g. c::stack, the namespace separators :: get converted into underscores (_) in path names, C code, etc. Just so you know. πŸ’…

name/nameDecls.h holds the stubs table type declarations, mapping macros, etc., and may include package-specific headers. See critcl::api header below. An #include directive gets added at the beginning of the generated code for the CriTcl script, and at the beginning of each of its companion .c files.

name/nameStubLib.h holds the stubs table variable definition and the function to initialize it. An #include directive for it gets added to the initialization code for the CriTcl script, along with a call to the initializer function.

If name/name.decls accompanies name/nameDecls.h, it should contain the external representation of the stubs table used to generate the headers. The file is read and the internal representation of the stubs table is returned for use by the importing package. Otherwise, the empty string is returned.

One possible use? Automatically generating C code that calls on the functions listed in the imported API. Obsessed. 🫢

When generating a TEA package, the names of the imported APIs are used to declare configure options, so the user can declare a non-standard directory for the headers of the API. Any API name gets translated into a single configure option --with-name-include.

Declaration and export of a stubs table (i.e. an API) for the CriTcl script:

  • ::critcl::api function resulttype name arguments β€” adds to the public API of the CriTcl script the signature for the function named name, with the signature specified by arguments and resulttype. Code gets generated for a .decls file, the corresponding public headers, and a stubs table usable by critcl::api import.

arguments is a multidict where each key is an argument type and its value is the argument name, and resulttype is a C type.

  • ::critcl::api header ?glob pattern...? β€” every file matching a glob pattern gets copied into the directory containing the generated headers, and an #include directive for it gets added to Decls.h for the CriTcl script. Returns an error if a glob pattern matches nothing. Bestie, an empty match is not a vibe. πŸ’€

A pattern for a relative path is resolved relative to the directory containing the CriTcl script.

  • ::critcl::api extheader ?file...? β€” like ::critcl::api header, but each file should exist in the external development environment. An #include directive is added to fooDecls.h, but file is not copied to the package header directory. And file is not a glob pattern, because CriTcl has no context β€” i.e. no directory β€” in which to expand such patterns.

As with the headers for an imported API, an #include directive is added to the generated code for the CriTcl script and to each companion .c file.

In "compile & run" mode the generated header files and any companion headers are placed in the Result Cache subdirectory for the CriTcl script. This directory is added to the include search path of any other package importing this API and building in mode "compile & run".

In "generate package" mode -includedir specifies the subdirectory in the package to place the generated headers in. This directory is added to the search paths for header files, making sure a package importing an API finds it if the package exporting that API used the same setting for -includedir.

In "generate TEA" mode the static scanner recognizes critcl::api header as a source of companion files. It also uses data from calls to critcl::api import to add support for --with-foo-include options into the generated configure(.in), so a user can specify custom locations for the headers of any imported API. ✨


🏷️ PACKAGE META DATA 🏷️

CriTcl versions 3 and later can create TEApot meta-data to be placed into teapot.txt, in a format suitable for use by the TEApot tools [http://docs.activestate.com/activetcl/8.5/tpm/toc.html].

Back in version 2, some meta data support was already present through ::critcl::license, but that was only used to generate license.txt. Glow-up era, fr fr. 🌸

  • ::critcl::license author ?text...? β€” ignored in "compile & run" mode.

In "generate package" mode it provides info about the author of the package and the license for the package.

The text arguments are concatenated to form the text of the license, which is written to license.terms in the same directory as pkgIndex.tcl. If no text is provided, the license is read from license.terms in the same directory as the CriTcl script.

This information takes precedence over anything specified through the generic API ::critcl::meta. It's additionally placed into the meta data file teapot.txt under the keys as::author and license.

  • ::critcl::summary text β€” ignored in "compile & run" mode.

In "generate package" mode it places a short, preferably one-line description of the package into the meta data file teapot.txt under the key summary. This info takes precedence over information specified through the generic API ::critcl::meta.

  • ::critcl::description text β€” ignored in "compile & run" mode.

In "generate package" mode it places a longer description of the package into the meta data file teapot.txt, under the key description. The data given by this command takes precedence over any information specified through the generic API ::critcl::meta.

  • ::critcl::subject ?key...? β€” ignored in "compile & run" mode.

In "generate package" mode it places each key into the meta data file teapot.txt, under the key subject. This info takes precedence over any information specified through the generic API ::critcl::meta.

Calls to this command are cumulative. Stack 'em up. πŸ’…

  • ::critcl::meta key ?word...? β€” gives you arbitrary meta data outside of the following reserved keys: as::author, as::build::date, description, license, name, platform, require subject, summary, and version. Its behaviour is like ::critcl::subject, in that it treats all keys as a list of words, with each call providing one or more words for the key, and multiple calls extending the data for an existing key β€” if it's not reserved.
Okay so technically you can declare information for one of the reserved keys with this command, but that data gets ignored when the final meta data is assembled and written. So don't. πŸ’€

Use the commands ::critcl::license, ::critcl::summary, ::critcl::description, ::critcl::subject, package require, and package provide to declare data for the reserved keys.

The information for the reserved keys as::build::date and platform is automatically generated by critcl itself. Iconic behaviour honestly.

  • ::critcl::meta? key β€” returns the value in the metadata associated with key.

Used primarily to retrieve the name of the package from within utility packages that have to adapt C code templates to their environment. For example, critcl::class does this. King of reuse. πŸ‘‘

  • ::critcl::buildrequirement script β€” gives you control over the capturing of dependencies declared via package require. script is evaluated and any dependencies declared within it are ignored, i.e. not recorded in the meta data. Bye bye dependency. πŸ™ˆ

CONTROL & INTERFACE πŸŽ›οΈ

Okay bestie, these commands are the ones that control the deets of compiling and linking a CriTcl script. The info you give them is used only to compile/link the object for that CriTcl script. Like, information for "FOO.tcl" is kept totally separate from information for "BAR.tcl". No cross-contamination, we love boundaries πŸ’…

  • ::critcl::cheaders ?arg...? β€” gives you extra header locations to look in.

Each argument is a glob pattern. If an argument begins with -, it's not a path, it's an argument to the compiler. Otherwise, the parent directory of each matching path is a directory to be searched for header files. Returns an error if a pattern matches no files. A pattern for a relative path gets resolved relative to the directory containing the CriTcl script.

Heads up: #include lines are not automatically generated for matching header files. You gotta use critcl::include or critcl::ccode as necessary to add them yourself. No free rides ✨

Calls to this command are cumulative.

  • ::critcl::csources ?glob pattern...? β€” matching paths become inputs to the compilation of the current object, along with the sources for the current CriTcl script. Returns an error if no paths match a pattern. A pattern for a relative path is resolved relative to the directory containing the CriTcl script.

Calls to this command are cumulative.

  • ::critcl::clibraries ?glob pattern...? β€” hands the link step extra libraries and library locations. A glob pattern that begins with - is added as an argument to the linker. Otherwise, matching files are linked into the shared library. Returns an error if no paths match a pattern. A pattern for a relative path is resolved relative to the directory containing the CriTcl script.

Calls to this command are cumulative.

  • ::critcl::source glob pattern β€” evaluates as scripts the files matching each glob pattern. Returns an error if there are no matching files. A pattern for a relative path is resolved relative to the directory containing the CriTcl script.
  • ::critcl::tsources glob pattern... β€” provides the info about additional Tcl script files to source when the shared library is loaded.

Matching paths are made available to the generated shared library when it's loaded for the current CriTcl script. Returns an error if a pattern matches no files. A pattern for a relative path is resolved relative to the directory containing the CriTcl script.

Calls to this command are cumulative.

After the shared library has been loaded, the declared files are sourced in the same order that they were provided as arguments. Order matters, periodt πŸ”₯

  • ::critcl::owns glob pattern... β€” ignored in "compile and run" and "generate package" modes. In "generate TEA" mode each file matching a glob pattern is a file to be included in the TEA extension but that could not be ascertained as such from previous commands like critcl::csources and critcl::tsources β€” either because they were specified dynamically or because they were directly sourced.
  • ::critcl::cflags ?arg...?

Each arg is an argument to the compiler.

Calls to this command are cumulative.

  • ::critcl::ldflags ?arg...?

Each arg is an argument to the linker.

Calls to this command are cumulative.

  • ::critcl::framework ?arg...? β€” each arg is the name of a framework to link on MacOS X. This command is ignored if OS X is not the target, so you can specify frameworks unconditionally without breaking your non-Mac build. Iconic behaviour honestly 🍎

Calls to this command are cumulative.

  • ::critcl::tcl version β€” specifies the minimum version of the Tcl runtime to compile and link the package for. The default is 8.4.
  • ::critcl::tk β€” arranges to include the Tk headers and link to the Tk stubs.
  • ::critcl::preload lib... β€” arranges for the external shared library lib to be loaded before the shared library for the CriTcl script gets loaded.

Calls to this command are cumulative.

Each library FOO is searched for in the directories listed below, in the order listed. The search stops at the first existing path. Additional notes:

  • platform is the placeholder for the target platform of the package.
  • The extension ".so" is the placeholder for whatever actual extension is used by the target platform for its shared libraries.
  • The search is relative to the current working directory.

And now the paths, depending on the exact form of the library name:

  • FOO
    1. FOO.so
    2. FOO/FOO.so
    3. FOO/platform/FOO.so
  • PATH/FOO

The exact set searched depends on the existence of directory "PATH/FOO". If it exists, critcl searches:

  1. FOO.so
  2. PATH/FOO/FOO.so
  3. PATH/FOO/platform/FOO.so

Otherwise it searches:

  1. FOO.so
  2. PATH/FOO.so
  3. PATH/platform/FOO.so

instead.

  • /PATH/FOO

Even when specifying FOO with an absolute path, the first path searched is relative to the current working directory. Sneaky but true πŸ™ˆ

  1. FOO.so
  2. /PATH/FOO.so
  3. /PATH/platform/FOO.so

For developers who want to understand or modify the internals of the critcl package, Preloading functionality explains how preloading is implemented.

  • ::critcl::debug area... β€” specifies what debugging features to activate. Internally each area is translated into area-specific flags for the compiler, which are then handed over to critcl::cflags.
    • memory β€” specifies Tcl memory debugging.
    • symbols β€” specifies compilation and linking with debugging symbols for use by a debugger or other tool.
    • all β€” specifies all available debugging. Go big or go home πŸ’…

INTROSPECTION πŸ”

The following commands control compilation and linking.

  • ::critcl::check ?label? text β€” returns true if the C code in text compiles successfully, and false otherwise. Used to check for availability of features in the build environment. If provided, label is used to uniquely mark the results in the generated log.
  • ::critcl::checklink ?label? text β€” like critcl::check but also links the compiled objects, returning true if the link is successful and false otherwise. If specified, label is used to uniquely mark the results in the generated log.
  • ::critcl::msg ?-nonewline? msg β€” scripts using critcl::check and critcl::checklink can use this command to report results. Does nothing in compile & run mode. Tools like the CriTcl Application may redefine this command to implement their own message reporting. For example, critcl::app and any packages built on it print messages to stdout.
  • ::critcl::print ?-nonewline? ?chan? msg β€” used by the CriTcl internals to report activity. By default, effectively the same thing as ::puts. Tools directly using either the CriTcl package or the CriTcl application package may redefine this procedure to implement their own output functionality.

For example, the newest revisions of Kettle (https://chiselapp.com/user/andreas_kupries/repository/Kettle/index) use this to highlight build warnings.

  • ::critcl::compiled β€” returns true if the current CriTcl script is already compiled and false otherwise.

Enables a CriTcl script used as its own Tcl companion file (see critcl::tsources) to distinguish between being sourced for compilation in compile & run mode and being sourced from either the result of generate package mode or during the load phase of compile & run mode. The result is false in the first case and true in the later two cases.

  • ::critcl::compiling β€” returns true if a working C compiler is available and false otherwise.
  • ::critcl::done β€” returns true when the CriTcl script has been built and false otherwise. Only useful from within a CriTcl script. Enables the Tcl parts of a CriTcl script to distinguish between prebuilt package mode and compile & run mode.

See also Modes Of Operation/Use.

  • ::critcl::failed β€” returns true if the CriTcl script could not be built, and false otherwise. Forces the building of the package if it hasn't already been done, but not its loading. So a CriTcl script can check itself for availability of the compiled components. Only useful from within a CriTcl script.
  • ::critcl::load β€” like critcl::failed except that it also forces the loading of the generated shared library, and that it returns true on success and false on failure. So a CriTcl script can check itself for availability of the compiled components. Only useful from within a CriTcl script.

BUILD MANAGEMENT πŸ—οΈ

The following command manages global settings β€” i.e. configuration options which are independent of any CriTcl script.

bestie, you should pretty much never need this command to write a CriTcl script. It's a management command, and it's only useful to the CriTcl Application or similar tools. Not your vibe, usually πŸ’…
  • ::critcl::config option ?val? β€” sets and returns the following global configuration options:
    • force bool β€” when false (the default), the C files are not built if there is a cached shared library.
    • lines bool β€” when true (the default), #line directives are embedded into the generated C code.

This facility requires the use of a tclsh that provides info frame. Otherwise, no #line directives are emitted. The command is supported by Tcl 8.5 and higher. It is also supported by Tcl 8.4 provided that it was compiled with the define -DTCL_TIP280. An example of such is ActiveState's ActiveTcl.

Also lowkey a PSA before we get into the rest of these knobs: if you're out here building higher-level packages that generate their own C code β€” whether you're doing it directly or letting critcl do it for you β€” you really wanna read the Advanced: Location management section too, so you can see how critcl helps you generate your directives. Want examples? They ship with critcl itself. Go peek at critcl::iassoc and critcl::class. 🫢

  • trace bool β€” when this is false (which, bestie, is the default ✨), no code that traces the entry and exit of CriTcl-backed commands in the CriTcl script gets inserted. And heads up: inserting that code implicitly activates the tracing facility in general. More deets in critcl::cutil.
  • I path β€” one single global include path, used for all the files. Not set by default, so if you need it, say so.
  • combine enum β€” okay so this one has ✨modes✨:
    • dynamic β€” the default, and the vibe is: object files get the suffix _pic.
    • static β€” object files get the suffix _stub instead.
    • standalone β€” object files get no suffix at all, and the generated C files are compiled without using Tcl/Tk stubs. What you get are object files usable for static linking into a big shell.
  • language string
  • keepsrc bool β€” when false (default bestie, you know the drill), the generated ".c" files get deleted after the ".o" files have been built. Clean up after yourself, iconic. πŸ’…
  • outdir directory β€” the directory where a generated shared library gets placed. By default it gets dropped into the Result Cache.

πŸŽ€ RESULT CACHE MANAGEMENT πŸŽ€

These commands control the Result Cache. Bestie, quick note: you do not need these just to write a CriTcl script. They're for the girlies doing more.

  • ::critcl::cache ?path? β€” sets and returns the path to the directory for the package's result cache. The default location is "~/.critcl/[platform::generic]" and it usually does not require any changes. Leave it alone, she's fine. πŸ™ˆ
  • ::critcl::clean_cache ?pattern...? β€” cleans the result cache, aka removes any and all files and directories in it. If you pass one or more patterns, then only the files and directories matching them get removed. Selective cleansing, we love to see it.

πŸ› οΈ BUILD CONFIGURATION πŸ› οΈ

These commands manage the build configuration, i.e. all that per-platform info about compilers, linkers, and their commandline options. Lowkey again: not needed if you're just writing a CriTcl script.

  • ::critcl::readconfig path β€” reads the build configuration file at path and configures the package using the info for the target platform. πŸ“–
  • ::critcl::showconfig ?chan? β€” converts the active build configuration into a human-readable string and returns it. Or, if chan is provided, prints the result to that channel instead.
  • ::critcl::showallconfig ?chan? β€” converts the set of all known build configurations β€” from the currently active build configuration file, last set with critcl::readconfig β€” into a string and returns it. Or, if chan is provided, prints it to that channel. The full buffet. πŸ“
  • ::critcl::chooseconfig target ?nomatcherr? β€” matches target against all known targets, returning a list containing every matching one. This search happens on an exact basis first, then via glob matching. If nothing matches, the default move is to return an empty list. But β€” if nomatcherr is specified and set, an error gets thrown using critcl::error instead. So you can make her be dramatic if you want. πŸ’€
  • ::critcl::setconfig target β€” configures the package to use the settings of target. Done and done.

🌈 TOOL API 🌈

These commands give tools like CriTcl Application (or similar) deeper access to the package's internals. And yep, once again: not needed to simply write a CriTcl script. This is the backstage pass, iykyk. πŸ‘‘

  • ::critcl::actualtarget β€” returns the platform identifier for the target platform, i.e. the platform to build for. Unlike ::critcl::targetplatform, this is the true target, with any cross-compilation info resolved. No disguises, no cap. ✨
  • ::critcl::buildforpackage ?flag? β€” signals whether the next file is to be built for inclusion into a package. If not specified, flag defaults to true, i.e. building for a package. This disables a number of things in the backend β€” namely the linking of that file into a shared library, and the loading of that library. The expectation is that the build results get wrapped into a larger collection later. Playing the long game.
  • ::critcl::cnothingtodo file β€” checks whether there is anything to build for file. Vibe check on the build queue. πŸ”
  • ::critcl::cresults ?file? β€” returns information about building file, or info script if file is not provided. The result is a dictionary containing these items:
    • clibraries β€” a list of external shared libraries and/or directories needed to link file.
    • ldflags β€” a list of linker flags needed to link file.
    • license β€” the text of the license for the package file is located in.
    • mintcl β€” the minimum version of Tcl required by the package file is in to run successfully. A proper Tcl version number.
    • objects β€” a list of object files to link into file.
    • preload β€” a list of libraries to be preloaded in order to sucessfully load and use file.
    • tk β€” true if file requires Tk and false otherwise.
    • tsources β€” a list of companion ".tcl" files to source in order to load and use the CriTcl script file.
    • log β€” the full build log generated by the compiler/linker, including command line data from critcl, and other things.
    • exl β€” the raw build log generated by the compiler/linker. Contains the output generated by the invoked applications.
  • ::critcl::crosscheck β€” determines whether the package is configured for cross-compilation and prints a message to the standard error channel if so. Just a lil heads-up notice, bestie.
  • ::critcl::error msg β€” used to report internal errors. The default implementation simply returns the error. Tools like the CriTcl Application are allowed to redefine this procedure to do their own style of error reporting. But there's one constraint they are not allowed to change: the procedure must not return to the caller. Periodt. 🚫
  • ::critcl::knowntargets β€” returns a list of the identifiers of all targets found during the last invocation of critcl::readconfig.
  • ::critcl::sharedlibext β€” returns the file extension for shared libraries on the target platform.
  • ::critcl::targetconfig β€” returns the identifier of the target to build for, as specified by either the user or the system.
  • ::critcl::buildplatform β€” returns the identifier of the build platform, i.e. where the package is running on. That's home base. 🏠
  • ::critcl::targetplatform β€” returns the identifier of the target platform, i.e. the platform to compile for. In contrast to ::critcl::actualtarget, this may be the name of a cross-compilation target.
  • ::critcl::cobjects ?glob pattern...? β€” like ::critcl::clibraries, but instead of matching libraries, each glob pattern matches object files to be linked into the shared object (at compile time, not runtime, okay?). If a glob pattern matches nothing, an error is returned. Not listed in Control & Interface because it is of no use to package writers β€” only tools like the CriTcl Application need it. A pattern for a relative path is resolved relative to the directory containing the CriTcl script. Calls to this command are cumulative. Stack 'em up. πŸ’…
  • ::critcl::scan path β€” the main entry point to CriTcl's static code scanner. Used by tools to implement processing modes like the assembly of a directory hierarchy containing a TEA-lookalike buildystem, etc. Scans path and returns a dictionary containing these items:
    • version β€” package version.
    • org β€” author(ing organization).
    • files β€” list of the companion files, relative to the directory of the input file.
  • ::critcl::name2c name β€” given the Tcl-level identifier name, returns a list containing these details of its conversion to C:
    • Tcl namespace prefix
    • C namespace prefix
    • Tcl base name
    • C base name

For use by utilities that provide Tcl commands without going through standard commands like critcl::ccommand or critcl::cproc. critcl::class does this. Sneaky but valid. πŸ¦‹


🧚 ADVANCED: EMBEDDED C CODE 🧚

For advanced use, these following commands β€” used by critcl::cproc itself β€” are exposed. Main character energy only. ✨

  • ::critcl::argnames arguments β€” given an argument declaration as documented for critcl::cproc, returns a list of the corresponding user-visible names.
  • ::critcl::argcnames arguments β€” given an argument declaration as documented for critcl::cproc, returns a list of the corresponding C variable names for the user-visible names. The names returned here match the names used in the declarations and code returned by ::critcl::argvardecls and ::critcl::argconversion.
  • ::critcl::argcsignature arguments β€” given an argument declaration as documented for critcl::cproc, returns a list of the corresponding C parameter declarations.
  • ::critcl::argvardecls arguments β€” given an argument declaration as documented for critcl::cproc, returns a list of the corresponding C variable declarations. The names used in these declarations match the names returned by ::critcl::argcnames.
  • ::critcl::argconversion arguments ?n? β€” given an argument declaration as documented for critcl::cproc, returns a list of C code fragments converting the user visible arguments found in the declaration from Tcl_Obj* to C types. The names used in these statements match the names returned by ::critcl::argcnames. The generated code assumes that the procedure arguments start at index n of the objv array. The default is 1.
  • ::critcl::argoptional arguments β€” given an argument declaration as documented for critcl::cproc, returns a list of boolean values indicating which arguments are optional (true), and which are not (false).
  • ::critcl::argdefaults arguments β€” given an argument declaration as documented for critcl::cproc, returns a list containing the default values for all optional arguments.
  • ::critcl::argsupport arguments β€” hand it an argument declaration (the kind documented over at critcl::cproc) and it hands back a list of C code fragments β€” literally the ones you need to define the supporting types. iykyk πŸ’…

🌸 CUSTOM BUILD CONFIGURATION 🌸

okay so this package gives you exactly one command for managing package-specific build config options β€” meaning the ones you, the developer, decided to add ✨

  • ::critcl::userconfig define name description type ?default? β€” this one defines a custom build configuration option, and it wants a description, a type, and optionally a default value.

the type can either be bool, or a list of values 🫢

  1. for bool, the default value, if you actually specified one, has to be a boolean. if you didn't specify one, it just defaults to true.
  2. for a list of values, the default value, if specified, has to be a value that's actually found in that list. if you don't specify one, it defaults to the first value of the list.

the description is basically serving as in-code documentation for what the option even means, and otherwise it gets ignored. but β€” when a TEA wrapper is being generated, that description does get used for the configure option that's derived from the option declared by the command πŸ‘‘

a boolean option FOO gets translated into a pair of configure options, --enable-FOO and --disable-FOO, whereas an option whose type is a list of values gets translated into a single configure option, --with-FOO. periodt πŸ’…

  • ::critcl::userconfig query name β€” this queries the database of custom build configuration options for the current .critcl file and returns the chosen value πŸ¦‹. that might just be the default if nothing was ever set via ::critcl::userconfig set.

and THIS, bestie, is the moment where definitions and set values finally get brought together β€” with the set values validated against the definition. no cap.

  • ::critcl::userconfig set name value β€” this one is for use by a tool, like the critcl application itself, to specify values for custom build configuration options πŸ”§

at the time this command runs, only the association between the option name and value gets recorded, and literally nothing else happens. why? because the system may not know whether an option with the specified name even exists when the command is invoked β€” nor what its type is. so this behaviour is necessary.

any and all validation is deferred to when the value of an option is asked for, via ::critcl::userconfig query.

which means it's fully possible to set values for any option we like, and the value only actually takes effect if such an option is both defined and used later on. iconic behaviour, honestly.


🧭 ADVANCED: LOCATION MANAGEMENT 🧭

first, a lil intro for the girlies asking themselves "what even is location management" 😭

by default, critcl embeds #line directives into the generated C code, so that any errors, warnings and notes the C compiler finds during compilation refer to the .critcl file the faulty code came from β€” instead of referring to the generated .c file. chef's kiss πŸŽ€

this whole facility requires a tclsh that provides info frame. if it doesn't, then no #line directives get emitted at all πŸ’€ the command is supported by Tcl 8.5 and higher. it's also supported by Tcl 8.4, provided that it was compiled with the define -DTCL_TIP280. an example of such a thing is ActiveState's ActiveTcl.

most users will not care about this feature beyond simply wanting it to work and getting proper code references when they read compiler output 🌸

but β€” developers of higher-level packages that generate their own C code should care about this, so they can make sure their generated code contains proper references too. especially since this is key to separating bugs concerning code generated by the package itself from bugs in the user's code going into the package (if any) πŸ‘‘

examples of such packages come with critcl itself β€” go peek at the implementations of packages critcl::iassoc and critcl::class.

to help these developers, eight commands are provided to manage such location information. they're listed below πŸ’–

the main concept here is that they all operate on a single stored location β€” setting it, returning it, and clearing it. note that this location information is completely independent of the generation of #line directives within critcl itself.

  • ::critcl::at::caller β€” stores the location of the caller of the current procedure as a tuple of file name and linenumber. any previously stored location gets overwritten. the result of the command is the empty string ✨
  • ::critcl::at::caller offset β€” same as above, except the stored line number gets modified by the specified offset. in essence it's an implicit call of critcl::at::incr.
  • ::critcl::at::caller offset level β€” same as above, but the level the location information is taken from gets modified too. level 0 is the caller, -1 is its caller, etc.
  • ::critcl::at::here β€” stores the current location in the current procedure as a tuple of file name and linenumber. any previously stored location gets overwritten. result is the empty string.

in terms of ::critcl::at::caller this is equivalent to

critcl::at::caller 0 1
  • ::critcl::at::get* β€” takes the stored location and returns a formatted #line directive, ready for embedding into some C code. the stored location is left untouched 🫢 note that the directive contains its own closing newline.

for proper nesting and use, it's recommended that such directives always get added to the beginning of a code fragment. this way, should deeper layers add their own directives, those come before ours and are thus inactive. end result: the outermost layer generating a directive will 'win', i.e. have its directive used. as it should πŸ‘‘

  • ::critcl::at::get β€” just like the above, except it also clears the stored location.
  • ::critcl::at::= file line β€” lets the caller set the stored location to literally anything they want, outside of critcl's control. the result of the command is the empty string πŸ’…
  • ::critcl::at::incr n...
  • ::critcl::at::incrt str...

these two let you modify the line number of the stored location, changing it incrementally 🌈 the increment is specified either as a series of integer numbers (incr), or as a series of strings to consider (incrt). in the latter case, the delta is the number of line endings found in the strings.

  • ::critcl::at::caller!
  • ::critcl::at::caller! offset
  • ::critcl::at::caller! offset level
  • ::critcl::at::here!

these are convenience commands, combining caller and here with get. i.e. they store the location and immediately return it formatted as a proper #line directive. also note that after their use, the stored location gets cleared 🧚


πŸŒ€ ADVANCED: DIVERSIONS πŸŒ€

diversions are for higher-level packages generating their own C code, to make their use of critcl's commands that generate Embedded C Code easier 🫢

these commands normally generate all of their C code for the current .critcl file β€” which may not be what a higher-level package actually wants πŸ’€

with a diversion, the generator output can be redirected into memory, and from there on it can be handled and processed however the caller desires, before it gets committed to an actual .c file. obsessed ✨

an example of such a package comes with critcl itself β€” see the implementation of package critcl::class.

to help such developers, three commands are provided to manage diversions and the collection of C code in memory. they are:

  • ::critcl::collect_begin β€” this one starts the diversion of C code collection into memory πŸ’Ύ

the result of the command is the empty string.

multiple calls are allowed, and each call opens a new nesting level of diversion πŸŽ€

  • ::critcl::collect_end β€” this one ends the diversion of C code collection into memory and returns the collected C code.

if multiple levels of diversion are open, the call only closes and returns the data from the last level.

the command will throw an error if no diversion is active at all β€” which indicates a mismatch in the pairing of collect_begin and collect_end 😭

  • ::critcl::collect script β€” a convenience command that runs the script under diversion and returns the collected C code, ensuring the correct pairing of collect_begin and collect_end πŸ’–

πŸ“ ADVANCED: FILE GENERATION πŸ“

so, file generation is related to the diversions from the previous section, but they are absolutely not the same thing. even so, just like diversions, this feature is for higher-level packages generating their own C code πŸ’…

three examples of utility packages using this facility come with critcl itself. go check out the implementations of packages critcl::literals, critcl::bitmap, and critcl::enum 🌈

when you're splitting a package implementation into pieces, it's often sensible to have a bunch of pure C companion files containing low-level code β€” but those files might need information about the code in the main .critcl file 😭 such declarations are normally not exportable, and using the stub table support doesn't make sense here, because this is completely internal to the package.

with the file generation command below, the main .critcl file can generate any number of header files for the C companions to pick up πŸŽ€

  • ::critcl::make path contents β€” this creates the file path in a location where the C companion files of the package are able to pick it up by simply including path during their compilation, and without interfering with the outer system at all ✨

the generated file will contain the specified contents.


πŸ’­ CONCEPTS πŸ’­

πŸ‘‘ MODES OF OPERATION/USE πŸ‘‘

CriTcl can be used in three different modes of operation, called:

  1. Compile & Run, and
  2. Generate Package
  3. Generate TEA Package

Compile & Run was the original mode, and it's the default for critcl_pkg. it collects the C fragments from the CriTcl script, builds them as needed, and caches the results to improve load times later πŸš€

the second mode, Generate Package, was introduced to enable the creation of (prebuilt) deliverable packages that don't depend on the existence of a build system β€” i.e. a C compiler β€” on the target machine. this was originally done through the experimental Critbind tool, and is now handled by the CriTcl Application, also named critcl πŸ’…

newly introduced with CriTcl version 3 is Generate TEA Package. this mode constructs a directory hierarchy from the package which can later be built like a regular TEA package, i.e. using

.../configure --prefix ...
make all isntall

Oh and about caching results β€” bestie, that's a whole thing, go read the Result Cache section for the tea. 🍡


✨ RUNTIME BEHAVIOUR ✨

Okay so the default behaviour of critcl, the package, is to procrastinate. Iconically. It defers the compilation, linking, and loading of any C code as much as it possibly can, because that whole operation is expensive β€” mostly in the time it eats. πŸ•°οΈ

In other words: the C code embedded into a .critcl file gets built only when the first C command or procedure it provides is actually invoked. Lazy queen behaviour, and honestly valid. πŸ’…

This part of the system runs on standard functionality baked into the Tcl core, meaning:

  • the auto_index variable, which maps from commands to the scripts that provide them
  • the unknown command, which uses that info when a command turns out to be needed
⚠️ Lowkey important limitation: you can't just use info commands to check whether a critcl-defined command exists. Nah. You also have to search the auto_index array, in case it hasn't been built yet. πŸ™ˆ

You can flip this behaviour with the control command critcl::load. When it's invoked, the building β€” including loading of the result β€” is forced. And once that command has been invoked for a .critcl file, further definition of C code in that file is not allowed any longer. Door's closed, bestie. πŸšͺ


πŸ—‚οΈ FILE MAPPING πŸ—‚οΈ

Every .critcl file is backed by a single private .c file containing that code, plus all the boilerplate needed to compile and link it as one single shared library. One file, one library, one vibe. ✨

The Embedded C Code fragments show up in that file in the exact same order they were defined in the .critcl file β€” with one exception. The C code you hand over via critcl::cinit gets shoved to the back, after all the other fragments. So basically:

  • all fragments get access to the symbols defined by earlier fragments
  • the critcl::cinit fragment gets access to all of them, no matter where it sits in the .critcl file
πŸ“ Note: a limitation of the current system is the near impossibility of C-level access between different critcl-based packages. And no, the problem isn't having to write and share the proper extern statements β€” it's that the management (export and import) of package-specific stubs-tables is not supported. Which means dependent parts have to be forcibly loaded before their user, with all that entails. 😭 Check the Runtime Behaviour section for the relevant critcl limitation, and remember that a lot of older platforms don't support the necessary resolution of symbols β€” which is, like, literally the whole reason stubs were invented for Tcl in the first place. 🫠

πŸ’Ύ RESULT CACHE πŸ’Ύ

Compiling C code takes forever, so critcl doesn't just defer it as much as it can (see Runtime Behaviour) β€” it also caches the results. Queen of not doing work twice. πŸ‘‘

So here's the deal: the first time you use a .critcl file "FOO.tcl", the resulting object file and shared library get saved into the cache. On future uses of that same file, they get reused β€” loaded directly, no compilation required β€” provided the contents of "FOO.tcl" didn't change.

The change detection is MD5-hash based. A single hash gets computed for each .critcl file, built from the hashes of all the C code fragments and configuration options β€” i.e. everything that affects the resulting binary. πŸ”

As long as the input file doesn't change per that hash, a previously built shared library found in the cache is reused, straight-up bypassing the compilation and link stages. Slay.

The commands for managing the cache live in the Result Cache Management section. But heads up: they're only useful to tools built on top of the package, like the CriTcl Application. Package writers have no need for them, fr fr.

And one last thing β€” the default directory for the cache is chosen based on the chosen build target. Which means the cache can sit on a shared (network) filesystem without you having to fear interference between machines of different architectures. 🌐


🧚 PRELOADING FUNCTIONALITY 🧚

πŸ‘€ This section's audience is developers who want to understand and possibly modify the internals of the critcl package and application. Package writers, you can skip this one, bestie.

So how is the preloading of external libraries actually realized? Let's get into it.

Whenever a package declares libraries for preloading, critcl will build a supporting shared library that provides a Tcl package named "preload". This package isn't distributed separately β€” it ships as part of the package that needs the preload functionality. This support package exports a single Tcl command:

::preload library

...which gets invoked once per library to preload, with the absolute path of that library. The command then loads that library.

On windows the command will further use the Tcl command ::critcl::runtime::precopy to copy the library to the disk, should its path be in a virtual filesystem which doesn't directly support the loading of a shared library from it. πŸͺŸ

The command ::critcl::runtime::precopy is provided by the file "critcl-rt.tcl" in the generated package β€” as is the command ::critcl::runtime::loadlib, which generates the ifneeded script that Tcl's package management expects. This generated ifneeded script contains the invocations of ::preload.

Where to find everything:

  • The C code for the supporting library lives in the file "critcl_c/preload.c", which is part of the critcl package.
  • The Tcl code for the supporting runtime "critcl-rt.tcl" lives in the file "runtime.tcl", which is part of the critcl::app package.

βš™οΈ CONFIGURATION INTERNALS βš™οΈ

πŸ‘€ Again: this section's audience is developers who want to understand and possibly modify the internals of the critcl package and application. Package writers, skip away, you're free. πŸ•ŠοΈ

This is where we explain the syntax of configuration files, plus the configuration keys critcl uses to configure its build backend β€” i.e. how this part of the system gets at the compiler, linker, etc.

Pro tip: open the file containing the standard configurations ("path/to/critcl/Config") in the editor of your choice while you read this section, and use it as an extended set of examples going beyond the simple defaults shown here. πŸ“–

First up: the keys, what their values mean, and examples pulled from the standard configurations distributed with the package. Note that when you write a custom configuration it's not necessary to specify all the keys listed below β€” only those whose default values are wrong or insufficient for the platform in question. πŸ’«

  • version β€” the command to print the compiler version number. Defaults to:
gcc -v
  • compile β€” the command to compile a single C source file to an object file. Defaults to:
gcc -c -fPIC
  • debug_memory β€” the list of flags for the compiler to enable memory debugging in Tcl. Defaults to:
-DTCL_MEM_DEBUG
  • debug_symbols β€” the list of flags for the compiler to add symbols to the object files and the resulting library. Defaults to:
-g
  • include β€” the compiler flag to add an include directory. Defaults to:
-I
  • tclstubs β€” the compiler flag to set USE_TCL_STUBS. Defaults to:
-DUSE_TCL_STUBS
  • tkstubs β€” the compiler flag to set USE_TK_STUBS. Defaults to:
-DUSE_TK_STUBS
  • threadflags β€” the list of compiler flags to enable a threaded build. Defaults to:
-DUSE_THREAD_ALLOC=1 -D_REENTRANT=1 -D_THREAD_SAFE=1
-DHAVE_PTHREAD_ATTR_SETSTACKSIZE=1 -DHAVE_READDIR_R=1
-DTCL_THREADS=1
  • noassert β€” the compiler flag to turn off assertions in Tcl code. Defaults to:
-DNDEBUG
  • optimize β€” the compiler flag to specify optimization level. Defaults to:
-O2
  • output β€” the compiler flags to set the output file of a compilation. Defaults to:
-o [list $outfile]
🚨 NOTE: observe the use of Tcl commands and variables here. At the time critcl uses the value of this key, the value of the referenced variable is substituted into it. The named variable is the only variable whose value is defined for this substitution. iykyk πŸ’…
  • object β€” the file extension for object files on the platform. Defaults to:
.o
  • preproc_define β€” the command to preprocess a C source file without compiling it, but leaving #define's in the output. Defaults to:
gcc -E -dM
  • preproc_enum β€” see preproc_define, except that #define's are not left in the output. Defaults to:
gcc -E
  • link β€” the command to link one or more object files and create a shared library. Defaults to:
gcc -shared
  • link_preload β€” the list of linker flags to use when dependent libraries are preloaded. Defaults to:
--unresolved-symbols=ignore-in-shared-libs
  • strip β€” the flag to tell the linker to strip symbols from the shared library. Defaults to:
-Wl,-s
  • ldoutput β€” like output, but for the linker. Defaults to the value of output.
  • link_debug β€” the list of linker flags needed to build a shared library with symbols. Defaults to the empty string. One platform requiring this is all variants of Windows, which uses:
-debug:full -debugtype:cv
  • link_release β€” the list of linker flags needed to build a shared library without symbols, i.e. a regular build. Defaults to the empty string. One platform requiring this is all variants of Windows, which uses:
-release -opt:ref -opt:icf,3 -ws:aggressive
  • sharedlibext β€” the file extension for shared library files on the platform. Defaults to:
[info sharedlibextension]
  • platform β€” the identifier of the platform used in generated packages. Defaults to:
[platform::generic]
  • target β€” the presence of this key marks the configuration as a cross-compilation target, and the value is the actual platform identifier of the target. No default. 🎯

So the syntax expected from configuration files? That's governed by the rules below. And again β€” open the file containing the standard configurations ("path/to/critcl/Config") in the editor of your choice while reading this section, and use it as an extended set of examples for the syntax:

  1. Each logical line of the configuration file consists of one or more physical lines. In case of the latter, the physical lines have to follow each other, and all but the first must be marked by a trailing backslash. This is the same marker for continuation lines as used by Tcl itself. πŸ”—
  2. A (logical) line starting with the character "#" (modulo whitespace) is a comment which runs until the end of the line, and is otherwise ignored. πŸ—’οΈ
  • [3] A logical line starting with the word "if" (modulo whitespace, bestie πŸ‘€) gets read as Tcl's if command and run just like that. So it has to follow Tcl's syntax for if, which can totally stretch across multiple logical lines. It runs in a safe interpreter. 🫢
  • [4] Same energy for set: a logical line starting with the word "set" (again, modulo whitespace) is interpreted as Tcl's set command and executed as such. So it follows Tcl's syntax for set, can span multiple logical lines too, and also runs in a safe interpreter. ✨
  • [5] A line shaped like "platform variable value" defines a platform-specific configuration variable and value. The variable part has to be the name of one of the configuration keys listed earlier in this section, and the platform string identifies which platform the setting is for. Every setting with the same identification string forms one configuration block for that platform. πŸ’…
  • [6] The special one: a line of the form "platform when expression" marks the platform and every setting in its configuration block as conditional on expression.

If the build platform is neither a prefix of platform nor vice versa, the whole block gets ignored. Otherwise, expression is evaluated via expr, in the same safe interpreter used to run any set and if commands found in the configuration file (see above).

If it evaluates to true, this configuration block is considered the build platform for the host and gets chosen as the default configuration. A big example of this feature is the handling of OS X in the standard configuration file, where it picks the architectures to build based on the version of the operating system, the available SDK, etc. I.e. it chooses whether the output is universal or not, and whether it's old-style (ix86 + ppc) vs. new-style (ix86 32+64) universality. No cap. πŸ‘‘

  • [7] A line of the special form "platform copy sourceplatform" copies the configuration variables and values currently defined in the configuration block for sourceplatform over to platform, overwriting existing values and creating missing ones. Variables in platform that aren't defined by sourceplatform are left completely untouched.

The copied values can be overridden later in the configuration file. Multiple copy lines may exist for a platform, and they can be intermixed with normal configuration definitions. Only the last definition of a variable is used.

  • [8] And last but never least, a line of the form "variable value" defines a default configuration variable and value. πŸ’–

🧚 STUBS TABLES 🧚

This section is for the devs making extensions that are not critcl-based, yet still want to interface with stubs the way critcl understands and uses them β€” either by exporting their own stubs table to a critcl-based extension, or by importing a critcl-based extension's stubs table into their own. iykyk 🌈

To make all that happen, here's the stubs table information for a package foo.

  1. Notice how the capitalization flips around between foo, Foo, FOO, etc. below β€” that's on purpose, it shows how to capitalize the actual package name in each context. πŸŽ€
  2. All the relevant files must be available in a sub-directory foo which can be found on the include search paths.
  3. That directory may contain a file foo.decls. If it's present, it's assumed to hold the external representation of the stubs table that the headers mentioned in the following items are based on.

critcl is able to use such a file to give the importing package programmatic access to the imported API β€” for automatic code generation and the like. ⭐

  1. The directory must contain a header file fooDecls.h. This file declares the exported API. Both exporting and importing packages use it. It's usually generated and must contain (in the order specified):
    1. the declarations of the exported, i.e. public, functions of foo,
    2. the declaration of structure FooStubs for the stub table,
    3. the C preprocessor macros which route the invocations of the public functions through the stubs table.

These macros must be defined if, and only if, the C preprocessor macro USE_FOO_STUBS is defined. Package foo does not define this macro, since it's allowed to use the exported functions directly. All importing packages however must define this macro, to ensure that they don't use any of the exported functions directly β€” only through the stubs table.

  1. If the exported functions need additional types for their proper declaration, then those types should go into a separate header file (any name you like), and fooDecls.h should contain an #include directive to that header at the top.

A very reduced, yet also complete example, from a package for low-level random number generator functions can be found at the end of this section. πŸ’…

  1. The directory must contain a header file fooStubLib.h. This file defines everything needed to use the API of foo. Consequently, it's used only by importing packages. It's usually generated and must contain (in the order specified):
    1. An #include directive for tcl.h, with USE_TCL_STUBS surely defined.
    2. An #include directive for fooDecls.h, with USE_FOO_STUBS surely defined.
    3. A definition of the stubs table variable, i.e.
const FooStubs* fooStubsPtr;
  1. A definition of the stubs initializer function, like
char *
Foo_InitStubs(Tcl_Interp *interp, CONST char *version, int exact)
{
    /*
     * Boiler plate C code initalizing the stubs table variable,
     * i.e. "fooStubsPtr".
     */

    CONST char *actualVersion;

    actualVersion = Tcl_PkgRequireEx(interp, "foo", version,
               exact, (ClientData *) &fooStubsPtr);

    if (!actualVersion) {
  return NULL;
    }

    if (!fooStubsPtr) {
  Tcl_SetResult(interp,
           "This implementation of Foo does not support stubs",
           TCL_STATIC);
  return NULL;
    }

    return (char*) actualVersion;
}

This header file must be included by an importing package exactly once, so that it contains only one definition of both the stubs table and the stubs initializer function.

The importing package's initialization function must further contain a statement like

if (!Foo_InitStubs (ip, "1", 0)) {
    return TCL_ERROR;
}

which invokes foo's stubs initializer function to set the local stub table up. 😀

For a complete example of such a header file see below, at the end of this section.

  1. That last item, about fooStubLib.h, differs from the regular stub stable system used by Tcl. The regular system assumes that a static library libfoostub.a was installed by package foo, and links it.

IMVHO critcl's approach is simpler, using only header files found in a single location, vs. header files and static library found in multiple, different locations.

A second simplification is that we avoid having to extend critcl's compiler backend with settings for the creation of static libraries. 🌸


Below is a complete set of example header files, reduced, yet still complete, from a package for low-level random number generator functions:

"rngDecls.h":

#ifndef rng_DECLS_H
#define rng_DECLS_H

#include <tcl.h>

/*
 * Exported function declarations:
 */

/* 0 */
EXTERN void rng_bernoulli(double p, int*v);

typedef struct RngStubs {
    int magic;
    const struct RngStubHooks *hooks;

    void (*rng_bernoulli) (double p, int*v); /* 0 */
} RngStubs;

#ifdef __cplusplus
extern "C" {
#endif
extern const RngStubs *rngStubsPtr;
#ifdef __cplusplus
}
#endif

#if defined(USE_RNG_STUBS)

/*
 * Inline function declarations:
 */

#define rng_bernoulli  (rngStubsPtr->rng_bernoulli) /* 0 */

#endif /* defined(USE_RNG_STUBS) */
#endif /* rng_DECLS_H */

"rngStubLib.h":

/*
 * rngStubLib.c --
 *
 * Stub object that will be statically linked into extensions that wish
 * to access rng.
 */

#ifndef USE_TCL_STUBS
#define USE_TCL_STUBS
#endif
#undef  USE_TCL_STUB_PROCS

#include <tcl.h>

#ifndef USE_RNG_STUBS
#define USE_RNG_STUBS
#endif
#undef  USE_RNG_STUB_PROCS

#include "rngDecls.h"

/*
 * Ensure that Rng_InitStubs is built as an exported symbol.  The other stub
 * functions should be built as non-exported symbols.
 */

#undef  TCL_STORAGE_CLASS
#define TCL_STORAGE_CLASS DLLEXPORT

const RngStubs* rngStubsPtr;

/*
 *----------------------------------------------------------------------
 *
 * Rng_InitStubs --
 *
 * Checks that the correct version of Rng is loaded and that it
 * supports stubs. It then initialises the stub table pointers.
 *
 * Results:
 *  The actual version of Rng that satisfies the request, or
 *  NULL to indicate that an error occurred.
 *
 * Side effects:
 *  Sets the stub table pointers.
 *
 *----------------------------------------------------------------------
 */

#ifdef Rng_InitStubs
#undef Rng_InitStubs
#endif

char *
Rng_InitStubs(Tcl_Interp *interp, CONST char *version, int exact)
{
    CONST char *actualVersion;
                  actualVersion = Tcl_PkgRequireEx(interp, "rng", version,
                             exact, (ClientData *) &rngStubsPtr);
                  if (!actualVersion) {
                return NULL;
                  }

                  if (!rngStubsPtr) {
                Tcl_SetResult(interp,
                         "This implementation of Rng does not support stubs",
                         TCL_STATIC);
                return NULL;
                  }

                  return (char*) actualVersion;
              }

πŸŽ€ EXAMPLES πŸŽ€

okay bestie, the real hands-on walkthrough lives in the section "Embedding C" inside Using CriTcl πŸ’– go read that one, it's the whole glow-up tutorial ✨


πŸ‘‘ AUTHORS πŸ‘‘

the absolute icons who made this happen 🫢

  • Jean Claude Wippler
  • Steve Landers
  • Andreas Kupries

😭 BUGS, IDEAS, FEEDBACK 😭

ngl, this document and the package it describes will undoubtedly contain bugs and other problems πŸ’€ like, it's software, that's just the vibe.

Please report them at:

https://github.com/andreas-kupries/critcl/issues

Ideas for enhancements you may have for either package, application, and/or the documentation are also very welcome fr fr πŸ₯Ή and should be reported at the exact same place:

https://github.com/andreas-kupries/critcl/issues

like, same link, both times, that's just where the magic happens πŸ’…


πŸ”‘ KEYWORDS πŸ”‘

the tags, the search terms, the lil breadcrumbs 🌸:

  • 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 ✨ that's the shelf it lives on πŸ’œ


  • Copyright (c) Jean-Claude Wippler
  • Copyright (c) Steve Landers
  • Copyright (c) 2011-2024 Andreas Kupries

that's a wrap bestie πŸ’–βœ¨ This was the critcl_package(3tcl) page from the doc corner of the critcl world, sitting at version 3.3.1 β€” no cap, you made it all the way to the end, so proud of you πŸ₯ΉπŸ«Ά now go compile something iconic πŸ‘‘

πŸ“¦ where this page comes from