Open-source project
msteveb/jimtcl avatar
msteveb/jimtcl

Jim Tcl embeds a scripting language in 14k lines of C, at the cost of traces and Tk

Official repository of Jim Tcl, an open-source, small footprint implementation of Tcl

529 stars144 forksCNOASSERTION

At a glance

What is it?
Jim is a from-scratch Tcl implementation small enough to link into an embedded application, roughly 130k compiled with the default extensions. It skips traces and Tk, adds dict, lassign, tailcall and lambdas with garbage collection, and is not a drop-in for stock Tcl.
Who is it for?
Jim Tcl suits an engineer embedding scripting into a C application or an embedded device, where a 130k interpreter that ships inside the binary beats a 50MB runtime. It does not suit anyone expecting stock Tcl semantics, since traces and Tk are not implemented and likely never will be, and it does not suit a project that needs the ecosystem of an existing Tcl installation.
Can I use it commercially?
Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
Is it still maintained?
Yes. The repository last received commits 21 days ago.
What is it written in?
Mainly C, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A Tcl dialect sized to be linked into your application

Jim is a small footprint implementation of the Tcl programming language written from scratch, in C, and it exists for a specific reason: to make an application scriptable without linking that application against a big system. The project description is blunt that Jim is not the next little language but a Tcl implementation, and the argument for it is reuse of existing Tcl knowledge and documentation.

The size claim is the crux. The core is 14k lines of code, which the documentation says means you can hack the source to meet the needs of your application. A compiled Jim with the default extensions and -Os is about 130k, and without any extensions it is about 85k. Those two numbers are the whole pitch: an interpreter you can put inside a firmware image or a single-binary utility.

There is a second reason to embed the interpreter rather than ship a script and a runtime. The documentation compares it to Emacs and Elisp or Gimp and Scheme, where the interpreter lives inside the application, so a given version of your program always ships with a given version of Jim. That means part of your application can be written in Jim itself.

The target audience is therefore the embedded developer and the application author, not the person writing a standalone Tcl script for a system that already has Tcl installed.

What Jim drops from Tcl, and what it adds on top

The feature list is honest in both directions. There are some Tcl commands and features which are not implemented and likely never will be, including traces and Tk. So a script that relies on trace or any Tk-backed command will not run, and that is a permanent gap rather than a roadmap item.

On the other side, Jim offers a number of both Tcl8.5 and Tcl8.6 features, namely {*}, dict, lassign, tailcall and optional UTF-8 support, plus some unique features. Those unique features include lambda with garbage collection, a general GC and references system, arrays as syntax sugar for dicts, and object-based I/O.

The lambda and garbage collection combination is the one that matters for embedding. In an application that creates and discards many small scripts, a lambda that is collected when nothing references it means you do not have to track closures by hand and free them explicitly.

The core semantic is the familiar Tcl one: everything is a string, and there are no reserved words since every construct is a command. The documentation is candid about how that is implemented, saying the everything is a string behaviour is implemented internally as dual ported objects to ensure that the execution time does not reflect the semantics of the language. Pass by value and everything-is-a-command together give a clean API for gluing an application to scripts, which the documentation points to as the reason the language has a pleasant embedding interface.

The config-file argument, and why it works only if you define the commands

One of the more persuasive arguments in the documentation is that Tcl looks like a configuration file if you want it to, and that the same text can be a real script once you implement the commands it uses. The example given is a configuration block with an unusual command name:

tcl
     set MyFeature on
     ifssl {
       set SslPort 45000
       use compression
     }

The documentation then makes the condition explicit: it looks like a configuration file, but if you implement the ifssl and use commands, it is a valid Tcl script. That is the design pattern the project is pointing at. You hand a user a file that is pleasant to read and edit, and the few domain commands in it are yours to define in C as extensions.

The advantages list also names the ordering property: Tcl has no reserved words and everything is a command, so new control structures can be written in the language itself. The documentation points at the ability to reprogram the language in itself as a reason Tcl scales with the user, and notes that Tcl stays simpler for the casual programmer than other small footprint languages such as Scheme and FORTH.

The interesting structural point is that Lists are a central data structure and Dictionaries are also lists. That single representation choice is what makes dict, lassign and array-as-dict-sugar cheap, and it is why a Tcl script written for Jim reads like a Tcl script.

Building Jim takes configure and make, and the extensions decide the footprint

Compiling Jim is a two-step process. The documentation says to try:

bash
  ./configure
  make

Jim was tested under Linux, FreeBSD, MacosX, eCos, QNX and Windows XP with mingw and MVC, so the porting surface is broad and includes a couple of genuinely embedded targets.

The build is also where you decide what you are paying for in size. Many optional extensions are included, some written in C and others written in pure Tcl, and the full list is available through the configure help:

bash
  ./configure --help

That distinction between C extensions and pure Tcl extensions is a real design decision with a size consequence, and it is where the gap between the 130k figure with default extensions and the 85k figure without any comes from.

The top-level file list shows how much ships in the box. Beyond the interpreter sources, there are C extensions such as jim-aio.c, jim-clock.c, jim-eventloop.c, jim-exec.c, jim-file.c, jim-format.c, jim-history.c, jim-interactive.c, jim-interp.c, jim-json.c, jim-load.c, jim-namespace.c, jim-pack.c, jim-package.c, jim-posix.c and jim-readdir.c, and pure Tcl companions such as ensemble.tcl, glob.tcl, initjimsh.tcl, binary.tcl and bootstrap.tcl. There is a jim-taint implementation, a jim-mk.cpp written in C++, and separate README files for the ensemble, extensions, linenoise, metakit, namespaces, oo, redis, sqlite, taint and utf-8 features, which is a good map of what the optional pieces actually are.

Embedding means writing C extensions, and the examples show the two styles

The documentation points at two directories for the two halves of embedding. For embedding Jim into applications it points at the examples.api directory, and for writing extensions it points at the shipped extensions jim-readline.c, jim-clock.c, glob.tcl and oo.tcl. The first is the C side of the embedding API, the second is the template for the Tcl side.

That is a useful distinction for anyone evaluating Jim. A language you can only extend by writing C is a much smaller commitment than one whose whole extension model is C, and here both are needed: the object-oriented support comes from oo.tcl and the line editing comes from jim-readline.c, and they sit in the same tree.

The examples directory also shows how far the language reaches in practice, and it is a longer list than you would expect from a small interpreter: a client-server.tcl socket script, dns.tcl and dnstest.tcl, jtrace for tracing, jcov for coverage, jtime for timing, ootest.tcl for the object system, parray.tcl for array printing, pipe.tcl and popen.tcl for process handling, redis.tcl with redis-async.tcl and redis-pubsub.tcl for a Redis client, sqlite3test.tcl for SQLite, sdlcircles.tcl and sdlevents.tcl and sdltest.tcl for SDL, metakit.tcl, nonblocking-gets.tcl, dbgtest.tcl and jtclsh.tcl for the shell itself. There is a whole README.redis and a README.sqlite in the tree, and a binary.tcl plus a jim-json.c in the core, so JSON and binary handling are built in rather than bolted on.

The event loop extension jim-eventloop.c and the aio extension jim-aio.c are what make the socket and async examples possible, and they are the pieces an embedded application is most likely to want.

How fast is Jim, and how you would check for yourself

The performance claim is comparative and dated, which is worth reading carefully. The documentation says Jim is, in most code, faster than Tcl7.6p2, described as the latest 7.x version, and slower than Tcl 8.4.x, and that you can expect decent performance for such a little interpreter.

That comparison bracket is the honest part. Beating a seven-series Tcl while losing to 8.4 is a statement about a specific point in Tcl's history, and readers who benchmark against a modern Tcl 8.6 will not reproduce it.

The project also tells you how to measure it yourself. There is a bench.tcl inside the distribution that runs under both Jim and Tcl, so you execute it with both interpreters and see what you get. The document does not publish numbers from that run, which is the right call for a project of this size, and it means the comparison you care about is the one you run on your own workload.

For an embedder the more important question is usually footprint rather than speed, and there the two documented figures, about 130k with the default extensions and about 85k with none, are the ones to plan against.

Where Jim Tcl is the wrong tool, and what to reach for instead

The wrong-tool case is straightforward: if you have an existing Tcl 8.6 installation and scripts that depend on it, Jim is the wrong answer. Traces are not implemented and likely never will be, Tk is not implemented, and a script that uses a command Jim does not ship will fail at runtime. The standard Tcl implementation exists and the documentation says so plainly, there is already the mainstream Tcl implementation for this.

The second wrong-tool case is a project that does not need to embed anything. If you are running scripts on a machine with Tcl available, the size argument for Jim disappears, and what remains is an interpreter with a smaller command set and an ecosystem you do not have.

For the alternative question, the comparison is with a bigger embedded scripting host, and the difference in approach is architectural rather than a feature list. Something like a full language runtime embedded in an application brings a larger memory footprint, a garbage collector with its own configuration, and a much bigger surface of behaviour to reason about. Jim instead takes the Tcl semantic, keeps the interpreter at a size you can put in a firmware image, and lets you add exactly the commands your application needs by writing C extensions against a small API.

There is a licensing wrinkle to check, though. The documentation says that unless explicitly stated, all files within the Jim repository are released under a license whose text begins with a Jim copyright header and credits Salvatore Sanfilippo, Clemens Hintze, Pat Thoyts, Øyvind Harboe, Andrew Lunn, Duane Ellis, Uwe Klein, Steve Bennett, Nico Coesel and Zachary T Welch. The licence reported for the repository is a custom one that GitHub cannot classify, so the LICENSE file is the place to read the actual terms. The documentation also states that Jim is supported, and that if you need commercial software you can contact the original author or the current maintainer.

Editorial conclusion

Jim Tcl suits an engineer embedding scripting into a C application or an embedded device, where a 130k interpreter that ships inside the binary beats a 50MB runtime. It does not suit anyone expecting stock Tcl semantics, since traces and Tk are not implemented and likely never will be, and it does not suit a project that needs the ecosystem of an existing Tcl installation. Before adopting, compile it with ./configure and make and run bench.tcl under both interpreters, and confirm that the commands your scripts rely on are among the shipped extensions rather than assuming Tcl 8.6 coverage.

Frequently asked questions

How do I compile Jim Tcl?

Run ./configure and then make. The interpreter has been tested under Linux, FreeBSD, MacosX, eCos, QNX and Windows XP with mingw and MVC.

How small is the Jim Tcl interpreter?

The core is 14k lines of code. Compiled with -Os, Jim with the default extensions is about 130k, and without any extensions it is about 85k.

Which Tcl features are missing from Jim Tcl?

Traces and Tk are not implemented, and the documentation says they likely never will be. Jim does provide Tcl8.5 and Tcl8.6 features including {*}, dict, lassign, tailcall and optional UTF-8 support.

How do I add my own commands to Jim Tcl?

Write an extension in C or in pure Tcl. The documentation points at the examples.api directory for embedding into applications and at the shipped extensions jim-readline.c, jim-clock.c, glob.tcl and oo.tcl as templates.

Official sources

  1. Issues
  2. msteveb/jimtcl on GitHub
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/msteveb-jimtcl.svg)](https://hysenlabs.com/projects/msteveb-jimtcl)

Was this useful?