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.
breakcontinue+,-,*,/(featurearithmetic)>,>=,<,<=,==,!=(featurecomparison)ifincr(featureincr)proc(featureproc)puts(featurestd)returnsetsubstwhile
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:
Vecmay retain extra memory for expansion, which we don’t generally need because values are immutable once constructed.Vecis 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§
- Flow
Change - 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
vas a signed integer. This always succeeds; ifvis 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
xin 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.
- Owned
Value - Shorthand type for a
Valuethat 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.