Picol

What Picol
Where https://chiselapp.com/user/dbohdan/repository/picol/ (version 0.6.0, dbohdan's fork)
https://wiki.tcl-lang.org/_repo/wiki_images/picol0-1-22.zip (version 0.1.22)
Description A tiny Tcl interpreter.
Updated 2023-11-26 (version 0.6.1)
2007-05-01 (version 0.1.22)
License 2-clause BSD

SS 2007-03-15: Picol (mirror ) is a Tcl interpreter in 500 lines of C code! If you like it vote my entry on reddit (the reddit url changed because of a reddit bug).

RS Since 2007-04-01, I'm playing with Picol at home - it's been a long while that I prefer to do a breakfast fun project in C :) Basically I'm adding features to bring it closer to Tcl (7.x - everything is a string really). Linecount is at about 1700 now. Latest additions focused on 8.5 features like apply (see below), {*}, in/ni etc.

DISCLAIMER: This is an educational toy model only, it is weaker and slower that real Tcl. I'm not planning to add regexp, Unicode, full expr parsing etc... But it's great for experimenting with C, I've had weeks of fun with it.

As the regular Wiki is read-only, I made my first report in French .

Build it with either of:

gcc -O2 -Wall -o picol picol.c
cl /O2 /W3 -o picol.exe picol.c

Version 0.1.22 is zipped, together with test suite, at https://wiki.tcl-lang.org/_repo/wiki_images/picol0-1-22.zip - new: unknown, auto_path, tclIndex, auto_index, uplevel #0. The currently supported command set (some only partially) is

/Tcl/Picol $ picol -e 'lsort [info commands]'
! != % && * ** + - / < <= == > >= _l abs append array break catch clock close concat continue eof eq error eval
exec exit expr file flush for foreach format gets glob global if in incr info interp join lappend lindex linsert
list llength lrange lreplace lsearch lset lsort ne ni open pid proc puts pwd rand read rename return scan seek
set setenv source split string subst switch tell time trace unset uplevel variable while ||

Development notes

RS: Here's an example how I extended Picol for simple file I/O, implementing open, gets and close:

int picolCommandOpen(struct picolInterp *i, int argc, char **argv, void *pd) {
    char* mode = "r";
    FILE* fp = NULL;
    char buf[64];
    if (argc != 2 && argc != 3) return picolArityErr(i,argv[0]);
    if(argc == 3) mode = argv[2];
    fp = fopen(argv[1], mode);
    if(!fp) {return picolError1(i, "could not open file %s", argv[1]);}
    sprintf(buf,"%p",fp);
    return picolSetResult(i,buf);
}

int picolCommandGets(struct picolInterp *i, int argc, char **argv, void *pd) {
    char buf[1024];
    char* getsrc;
    FILE* fp = stdin;
    if (argc != 2 && argc != 3) return picolArityErr(i,argv[0]);
    if(!STREQ(argv[1],"stdin")) {
      sscanf(argv[1],"%p", &fp); /* caveat usor */
    }
    if(!feof(fp)) {
        getsrc = fgets(buf,sizeof(buf), fp);
        buf[strlen(buf)-1] = '\0'; /* chomp last newline */
        if (argc == 2) {
            picolSetResult(i,buf);
        } else {
            if(getsrc) {
                picolSetVar(i,argv[2],buf);
                picolSetIntResult(i,strlen(buf));
            } else {picolSetResult(i,"-1");}
        }
    } else {picolSetResult(i,"-1");}
    return PICOL_OK;
}

int picolCommandClose(struct picolInterp *i, int argc, char **argv, void *pd) {
    FILE *fp = NULL;
    if (argc != 2) return picolArityErr(i,argv[0]);
    sscanf(argv[1],"%p", &fp); /* caveat usor */
    fclose(fp);
    return PICOL_OK;
}

# ...

picolRegisterCommand(i,"open", picolCommandOpen,NULL);
picolRegisterCommand(i,"gets", picolCommandGets,NULL);
picolRegisterCommand(i,"close",picolCommandClose,NULL);

int picolCommandList(struct picolInterp *i, int argc, char **argv, void *pd) {
    char buf[1024] = "";
    int a;
    for(a=1; a<argc; a++) {
        int needbraces = (strchr(argv[a],' ')!=NULL);
        if(needbraces) strcat(buf,"{");
        strcat(buf,argv[a]);
        if(needbraces) strcat(buf,"}");
        if(a < argc-1) strcat(buf," ");
    }
    return picolSetResult(i,buf);
}

rs 2007-04-04: I couldn't resist to post this cute little clock implementation (ok, no stardate..., but only 18 lines of code):

int picolCommandClock(struct picolInterp *i, int argc, char **argv, void *pd) {
    time_t t;
    if (argc < 2) return picolArityErr(i,argv[0]);
    if(     STREQ(argv[1],"clicks")) picolSetIntResult(i,clock());
    else if(STREQ(argv[1],"format")) {
        EXPECT_INT(argv[2]);
        t = atoi(argv[2]);
        if (argc==3 || (argc==5 && STREQ(argv[3],"-format"))) {
            char buf[128], *cp;
            struct tm* mytm = localtime(&t);
            if(argc==3) cp = "%a %b %d %H:%M:%S %Y"; else cp = argv[4];
            strftime(buf,sizeof(buf),cp,mytm);
            picolSetResult(i,buf);
        } else return picolError(i,"usage: clock format $t ?-format $fmt?");
    } else if(STREQ(argv[1],"seconds")) picolSetIntResult(i,time(&t));
    else return picolError(i,"usage: clock clicks|format|seconds ..");
    return PICOL_OK;
}

RS 2007-04-25: Here's how I did apply in 11 lines of code :^)

int picol_Apply(picolInterp *i, int argc, char **argv, void *pd) {
    char* procdata[2], *cp;
    char buf[MAXSTR], buf2[MAXSTR];
    ARITY2(argc >= 2, "apply {argl body} ?arg ...?");
    cp = picolParseList(argv[1],buf);
    if(!cp) return picolErr(i,"bad apply usage");
    picolParseList(cp,buf2);
    procdata[0] = buf;
    procdata[1] = buf2;
    return picolCallProc(i, argc-1, argv+1, (void*)procdata);
}

TP 2009-06-29 - Here is a patch for picol0-1.22. For arrays and other things, Picol stores a string representation of a pointer to the internal structure(s), and parses the pointer from the string representation when retrieving values. The corresponding sprintf() / sscanf() uses the %p format character and assumes that the pointer string representation is at least 8 bytes long. On some machines, pointers are low addresses and the %p format is less than 8 bytes. This patch simply lowers the expected length of the pointer string representation from 8 bytes to 3.

At line 462, replace the picolIsPtr() function with this implementation:

void* picolIsPtr(char* str) {
    void* p = NULL;
    sscanf(str,"%p",&p);
    return (p && strlen(str)>=3? p: NULL);
}

With this patch, Picol has been ported to the Java Virtual Machine (JVM) using NestedVM [L1 ].


dbohdan 2016-01-23: A slightly refactored version 0.1.23 of Picol is available at https://chiselapp.com/user/dbohdan/repository/picol/ . The difference from version 0.1.22 above is that the interpreter has been separated from the REPL making it possible to use it as a library in your own C code. It includes two examples of such use: a "Hello, World!" and a custom command example.


dbohdan 2017-05-16: I have recently made a lot of changes to my fork of Picol. I feel that I have reached something of a milestone and also that I have done most of what I wanted to do with it. For this reason, I want to document the changes here for myself or whoever else may take interest in Picol. I'll list the changes relative to the baseline of version 0.1.22. First, however, I'd like to thank RS, on whose extended and improved fork of Picol my own fork is based. His work inspired me to improve Picol and provided a framework for doing so.

I have enjoyed working on Picol. I can recommend it to anyone who wants a Tcl interpreter to experiment with. It is small, truly EIAS, has a very straightforward architecture and only depends on the C standard library and either POSIX or the Windows API if you need I/O. As a result, Picol is easy to understand and modify, even in major ways. (The flip side is that it is slow, uses more memory than Tcl 8.x and has a limit on the maximum length of a string. To graduate from toys Picol would at least have to get rid of the length limit and improve the performance.)

The primary changes are as follows:

  • Picol's source code has been refactored and split into an stb -style header library (picol.h) and a separate REPL that uses it (interp.c). It comes with three simple example programs that embed and extend Picol (examples/{hello,command,regexp-ext}.c) and an optional regexp extension based on Tor Andersson's public domain regexp library .
  • Makefiles and build scripts for *nix, MinGW and MSVC are included. (Picol builds in MSYS2 with the stock Makefile, but MinGW is preferred because it provides an implementation of glob.h.)
  • You can disable features like arrays or I/O with C preprocessor macros.
  • There are new commands:
    • ! (logical not)
    • ~ (binary not)
    • ^, <<, >>, |, & (bitwise operators)
    • after ms (just for waiting; Picol has no event loop)
    • cd dirName
    • file delete|isdirectory|isfile|split path.
    • glob ?-directory directory? pattern
    • lassign list ?varName ...?
    • lmap varList list command
    • lrepeat count ?element ...?
    • lreverse list
    • max number ?number ...?
    • min number ?number ...?
    • pwd
    • string map
    • try body ?on error varName handler? ?finally script?
  • Some commands' functionality has been reworked or enhanced. E.g., exec returns the output of the executed process; lindex, linsert, lrange, lreplace and string range now understand end.
  • Any integer can now be given in binary (0b1011), hexadecimal (0x12ab) or octal (0o777, but not 0777).
  • Correctness:
    • Many commands have been modified to produce the same result as in Tcl 8.6.
    • A number of segfault-causing memory access errors and memory leaks has been fixed. (Though many undoubtedly remain.)
    • 700 lines of tests has been added. The number of tests has more than doubled.
    • Version 0.3.0 introduces pointer tracking, which prevents segfaults when using features that depend on serialized pointers: arrays, interp create|eval and the file I/O commands.

Picolibri

FB - 2011-11-30 09:09:42

Picolibri is a straight port of Picol (in its current version 0.1.22) to the Colibri library (version 0.12), replacing plain C strings with Colibri ropes, and malloc'd memory management with automatic garbage collection. The modified source is about 50 lines longer than the original (1735 vs 1701 for picol.c, and 147 vs. 127 for picol.h). Most changes involve replacing C calls such as strlen by their Colibri counterparts, mostly through macros for readability, and also providing string to Colibri structure conversions (ropes, lists, maps...).

The modified Picol source is part of the Colibri source distribution . A pre-compiled Windows library is available here . It includes the command-line executable along with Colibri DLL and the original Picol scripts.

See also

  • Jim Tcl, a non-toy Tcl interpreter project started by SS.
  • Small Tcl for other implementations of Tcl smaller than Tcl 8.x.