Crate wartcl

Crate wartcl 

Source
Expand description

This is an aggressively simple Tcl-like language optimized for binary size, code complexity, and performance, in that order. That is, it’s mostly intended to be small, but with readable code and ok performance.

wartcl has been used on a Cortex-M0 for basic boundary scan and manufacturing test scripting. In that application it required 10 kiB of flash and 8 kiB of RAM.

§Putting this in your application

wartcl is designed to be very easy to embed in a larger application, including exposing custom commands. Here’s a toy example:

let mut tcl = wartcl::Env::default();

tcl.register(b"my-custom-command", 1, |_, _| {
    println!("hi!");
    Ok(wartcl::empty())
});

match tcl.eval(b"my-custom-command\n") {
    Ok(_) => {
        // the script worked!
    }
    Err(e) => panic!("script failed: {e:?}"),
}

§The wartcl language

The language implemented by wartcl is intended to be very close to Tcl, but smaller. Most (all?) wartcl programs should be valid Tcl programs, but not vice versa.

wartcl supports the following Tcl-like commands by default. Some are controlled by a Cargo feature in case you want to disable them.

  • break
  • continue
  • +, -, *, / (feature arithmetic)
  • >, >=, <, <=, ==, != (feature comparison)
  • if
  • incr (feature incr)
  • proc (feature proc)
  • puts (feature std)
  • return
  • set
  • subst
  • while

Probably the biggest difference is that the expr command, which does math-style expression parsing, is not included. You can still do math, but in prefix notation; instead of expr 2*(3+4), you must write * 2 [+ 3 4]. This isn’t ideal, but expression parsing is big and wartcl is small.

§Implementation design and theory of operation

The Tcl language is an extended meditation on the idea “everything is a string.” All of Tcl’s data types are — notionally, at least — represented as strings, and they can be converted from one to the other by parsing. Modern Tcl implementations provide this illusion while using more efficient representations under the hood.

wartcl takes it literally. Everything is a string, a heap-allocated sequence of human-readable bytes, encoded in either ASCII (if you leave the top bit of each byte clear) or UTF-8 (if you don’t). wartcl doesn’t actually care about character encoding.

This keeps the implementation very simple but has significant performance costs. Want to add two numbers? Well, you’ll have to parse two numeric strings, add the result, and then re-format the result into another (heap-allocated) numeric string. (This is not the fastest way to use a computer, but wartcl is not really designed for arithmetic-heavy applications.)

Basically every value, from the program’s source code on up, is represented as a Box<[u8]>. This is an owned pointer to a slice of bytes. Cloning it implies a full copy of its contents; dropping it deallocates the contents.

The advantages of Box<[u8]> over Vec<u8> are:

  1. Vec may retain extra memory for expansion, which we don’t generally need because values are immutable once constructed.
  2. Vec is one word larger, making it correspondingly more expensive to store and pass around.

To clarify intent, in the implementation, [u8] is given the type alias Value.

§About the name

wartcl stands for “wartcl Ain’t Really Tcl” because the language differs from standard Tcl in a whole bunch of ways.

It’s also a pun on the C partcl library’s name, after the “warticle” term humorously used to describe phenomena exhibiting wave/partical duality in quantum physics.

Modules§

cmd
Built-in commands.

Structs§

Env
An instance of the interpreter, the “Tcl machine.”
Tokenizer
The input tokenizer, exposed mostly because it can be useful for building a REPL.

Enums§

FlowChange
Flow control instructions that can be returned by commands, in place of normal completion.
Token
A token, produced by the Tokenizer.

Functions§

empty
Convenience function for getting an empty value.
int
Parses v as a signed integer. This always succeeds; if v is not a valid signed integer, this returns 0.
int_value
Formats an integer as a decimal string.
int_value_into
Formats an integer as a decimal string, appending it to an existing buffer.
int_value_len
Determines the number of bytes required to format x in decimal.
parse_list
Processes a value as a list, breaking it at (top-level) whitespace into a vector of element values.

Type Aliases§

Int
Integer type used internally for arithmetic.
OwnedValue
Shorthand type for a Value that you own.
Value
This is a little weird, but it’s convenient below to indicate where we’re treating a slice of bytes as a value vs just any old bytes.