Molt: More Or Less Tcl

Molt 0.3.2 is a minimal implementation of the TCL language for embedding in Rust apps and for scripting Rust libraries. Molt is intended to be:

  • Small in size. Embedding Molt shouldn't greatly increase the size of the application.

  • Small in language. Standard TCL has many features intended for building entire software systems. Molt is intentionally limited to those needed for embedding.

  • Small in dependencies. Including the Molt interpreter in your project shouldn't drag in anything else--unless you ask for it.

  • Easy to build. Building Standard TCL is non-trivial. Embedding Molt should be as simple as using any other crate.

  • Easy to embed. Extending Molt with TCL commands that wrap Rust APIs should be easy and simple.

Hence, perfect compatibility with Standard TCL is explicitly not a goal. Many features will not be implemented at all (e.g., octal literals); and others may be implemented somewhat differently where a clearly better alternative exists (e.g., -nocomplain will always be the normal behavior). In addition, Molt will prefer Rust standards where appropriate.

On the other hand, Molt is meant to be TCL (more or less), not simply a "Tcl-like language", so gratuitous differences are to be avoided. One of the goals of this document is to carefully delineate:

  • The features that have not yet been implemented.
  • The features that likely will never be implemented.
  • Any small differences in behavior.
  • And especially, any features that have intentionally been implemented in a different way.

What Molt Is For

Using Molt, you can:

  • Create a shell interpreter for scripting and interactive testing of your Rust crates.
  • Provide your Rust applications with an interactive REPL for debugging and administration.
  • Extend your Rust application with scripts provided at compile-time or at run-time.
  • Allow your users to script your applications and libraries.

See the molt-sample repo for a sample Molt client skeleton.

New in Molt 0.3.2

Nothing, yet! See the Annotated Change Log for the new features by version.

Coming Attractions

At this point Molt is capable and robust enough for real work, though the Rust-level API is not yet completely stable. Standard Rust 0.y.z semantic versioning applies: ".y" changes can break the Rust-level API, ".z" changes will not.

  • More TCL Commands
  • Testing improvements
  • Documentation improvements
  • Feature: Regex and Glob pattern matching by Molt commands

Tcl Compatibility

Molt is aiming at limited compatibility with TCL 8.x, the current stable version of Standard TCL, as described above. The development plan is as follows:

  • Implement the complete Molt semantics
    • Core interpreter
    • Essential TCL commands
    • Robust and ergonomic Rust-level API for extending TCL in Rust
    • Related tools (e.g., TCL-level test harness)
    • Thorough and complete test suite (at both Rust and TCL levels)
    • Thorough documentation
  • Optimize for speed
    • Ideally including byte-compilation
  • Extend with new features as requested.

Each TCL command provided standard by the Molt interpreter is documented in this book with a complete man page. A command's man page documents the semantics of the command, and any temporary or permanent differences between it and the similarly named command in Standard TCL.

The remainder of this section documents overall differences; see the Molt README for details on current development.

Note that some of the features described as never to be implemented could conceivably be added as extension crates.

Features that already exist

See the command reference for the set of commands that have already been implemented. The current set of features includes:

At the TCL Level:

  • Script execution
  • Procedure definition
  • Standard control structures (except the switch command)
  • Local and global variables, including associative arrays
  • Boolean and numeric expressions
  • Dictionaries
  • Many standard TCL commands
  • A modicum of introspection

At the Rust Level:

  • A clean and modular embedding API
  • The Interp struct (e.g., Standard TCL's Interp)
    • API for defining new commands in Rust, setting and querying variables, and evaluating TCL code.
  • The Value type (e.g., Tcl_Obj)
    • TCL values are strings; Value shares them efficiently by reference counting, and caches binary data representations for run-time efficiency.

Related Tools:

  • An interactive REPL
    • Using the rustyline crate for line editing.
  • A shell, for execution of script files
  • A test harness

Features to be added soon

See the overview and the Molt README.

Features to be added eventually

  • Globs and Regexes
  • Some way to create ensemble commands and simple objects

Features that might someday be added (depending on demand)

  • Namespaces
  • Slave interpreters
  • File I/O
  • Event loop
  • Byte Compilation
  • Communication between Interps in different threads
  • Traces
  • Some kind of TCL-level module architecture

Features that will almost certainly never be added

  • The TCL autoloader
  • Packages/TCL Modules (as represented in Standard TCL)
  • Coroutines
  • Support for dynamically loading Molt extensions written in Rust
  • Support for Molt extensions written in C (or anything but Rust)
    • But note that a Molt extension written in Rust can certainly call into C libraries in the usual way.
  • Network I/O
  • OOP (in the form of TclOO)

Miscellaneous Differences

See the man pages for specific commands for other differences.

  • Integer literals beginning with "0" are NOT assumed to be octal, Nor will they ever be.
  • The encoding is currently always UTF-8.
  • In variable names, e.g. $name, the name may include underscores and any character that Rust considers to be alphanumeric.
  • The notion of what constitutes whitespace is generally left up to Rust.
  • When using the TCL shell interactively, TCL will attempt to match partial names of commands and subcommands as a convenience. Molt does not.
    • In principle, some form of tab-completion could be added at some point.

Annotated Change Log

New in Molt 0.3.2

Nothing yet!

New in Molt 0.3.1

  • Added the molt_throw! macro.
  • Improved the API documentation for molt_ok! and molt_err!.
  • Added Exception::error_code and Exception::error_info, to streamline using exceptions.
  • Added the env() array, which contains the current environment variable settings.
    • Note: as yet, changes to the env() array are not mirrored back to the process environment.
  • Added the string command
    • string cat
    • string compare
    • string first
    • string last
    • string length
    • string map
    • string range
    • string tolower
    • string toupper
    • string trim
    • string trimleft
    • string trimright

New in Molt 0.3.0

The changes in Molt 0.3.0 break the existing API in two ways:

  • The syntax for molt_shell::repl has changed slightly.
  • The MoltResult type has changed significantly.

Keep reading for the full details.

Molt Shell: User-Definable Prompts

Thanks to Coleman McFarland, molt_shell::repl now supports programmable prompts via the tcl_prompt1 variable. See the rustdocs and the molt_shell discussion in this book for more information.

Error Stack Traces

Molt now provides error stack traces in more-or-less the same form as standard TCL. Stack traces are accessible to Rust clients, are printed by the Molt shell, and can be accessed in scripts via the catch command and the errorInfo variable in the usual TCL way.

Error Codes

Molt scripts and Rust code can now throw errors with an explicit error code, as in Standard TCL; see the throw and catch commands.

Return Protocol

Molt now supports the full return/catch protocol for building application-specific control structures in script code. The mechanism as implemented is slightly simpler than in Standard TCL, but should be sufficient for all practical purposes. See the referenced commands for specifics.

MoltResult and the Exception Struct

In order to support the above changes, the definition of the MoltResult type has changed. Instead of


# #![allow(unused_variables)]
#fn main() {
pub type MoltResult = Result<Value, ResultCode>;
#}

it is now


# #![allow(unused_variables)]
#fn main() {
pub type MoltResult = Result<Value, Exception>;
#}

where Exception is a struct containing the ResultCode and other necessary data. The ResultCode enum still exists, but has been simplified. See the rust doc for details.

New in Molt 0.2

Dictionaries and the dict command

Molt now supports TCL dictionary values. The dict command provides the following subcommands:

  • dict create
  • dict exists
  • dict keys
  • dict get
  • dict remove
  • dict set
  • dict size
  • dict unset
  • dict values

Other dict subcommands will be added over time.

Associative Arrays

Molt now includes TCL's associative array variables:

% set a(1) "Howdy"
Howdy
% set a(foo.bar) 5
5
% puts [array get a]
1 Howdy foo.bar 5

The Expansion Operator

Molt now supports TCL's {*} operator, which expands a single command argument into multiple arguments:

% set a {a b c}
a b c
% list 1 2 $a 3 4
1 2 {a b c} 3 4
% list 1 2 {*}$a 3 4
1 2 a b c 3 4

More info Subcommands

Molt now supports the following subcommands of the info command:

  • info args
  • info body
  • info cmdtype
  • info default
  • info exists
  • info globals (no glob-filtering as yet)
  • info locals (no glob-filtering as yet)
  • info procs

Rust API Change: Test Harness

The Molt test harness code has moved from molt_shell:test_harness to molt::test_harness, so that it can be used in the molt/tests/tcl_tests.rs integration test.

Rust API Change: Variable Access

The addition of array variables required changes to the molt::Interp struct's API for setting and retrieving variables. In particular, the molt::Interp::var, molt::Interp::set_var, and molt::Interp::set_and_return methods now take the variable name as a &Value rather than a &str; this simplifies client code, and means that most commands implemented in Rust that work with variables don't need to care whether the variable in question is a scalar or an array element.

Rust API Change: Command Definition

Defining Molt commands in Rust has been simplified.

First, the Command trait has been removed. It was intended to provide a way to attach context data to a command; but it was not very good for mutable data, and had no way to share data among related commands (a common pattern).

Second, the interpreter's context cache has been improved. Multiple commands can share a context ID (and hence access to the shared context); and the cached data will be dropped automatically when the last such command is removed from the interpreter.

Third, there is now only one command function signature:

fn my_command(interp: &mut Interp, context_id: ContextID, argv: &[Value]) -> MoltResult {
    ...
}

Commands that don't use a cached context should be defined as follows:

fn my_command(interp: &mut Interp, _: ContextID, argv: &[Value]) -> MoltResult {
    ...
}

Molt Command Line Tool

The molt-app crate provides a command line tool for use in development and experimentation. The command line tool, called molt, has several subcommands:

molt shell executes scripts and provides an interactive REPL.

molt test executes Molt test suites, most notably Molt's own test suite.

molt bench executes Molt benchmarks. This tool is experimental, and is primarily for use in optimizing molt itself.

Note: the molt-shell crate provides the same features for use with customized Molt interpreters.

molt shell ?script? ?args...?

The molt shell command invokes the Molt interpreter.

Interactive Use

When called without any arguments, the command invokes the interactive interpreter:

$ molt shell
Molt 0.3.0
%

Molt commands may be entered at the % prompt. Enter exit to leave the interpreter.

Script Execution

When called with arguments, the first argument is presumed to be the name of a Molt script; any subsequent arguments are passed to the script.

$ molt shell my_script.tcl arg1 arg2 arg3
...
$

When called in this way, the variable arg0 contains the name of the script, and the variable argv contains a list of the additional arguments (if any).

For example, consider the following script, args.tcl:

puts "arg0 = $arg0"
puts "argv = $argv"

This script may be run as follows

$ molt shell args.tcl a b c
arg0 = args.tcl
argv = a b c
$

Interactive Prompts

The molt shell and its underlying method, molt_shell::repl, support interactive prompts via the tcl_prompt1 variable. If defined, tcl_prompt1 should be a script; its value will be output as the prompt.

$ molt shell
% set count 0
% set tcl_prompt1 {return "[incr count]> "}
return "[incr count]> "
1> puts "Howdy!"
Howdy!
2>

This is slightly different than in Standard TCL, where the tcl_prompt1 script is intended to output the prompt rather than return it.

TCL Liens

The Standard TCL shell, tclsh, provides a number of features that Molt currently does not.

  • A .tclshrc file for initializing interactive shells.

    • A similar file will be added in the future.
  • An option to execute a script and then start the interactive shell.

    • This can be added if there is demand.
  • Environment variables for locating the interpreter's library of TCL code, locally installed TCL packages, etc.

    • Molt's library of TCL code is compiled into the interpreter, rather than being loaded from disk at run-time.
    • At present, Molt has no support for externally-defined TCL packages (other than the source command).

molt test filename ?args...?

This command executes the test script called filename using the Molt test harness, which is similar to Standard TCL's tcltest framework (though much simpler, at present). Any arguments are passed to the test harness (which ignores them, at present).

Test Suites

molt test is often used to execute an entire test suite, spread over multiple files. To simplify writing such a suite, molt test assumes that the folder containing the specified filename is the base folder for the test suite, and sets the current working directory to that folder. This allows the named test script to use source to load other test scripts using paths relative to its own location.

Writing Tests

Tests are written using the test command. See that man page for examples.

Running Tests

For example,

$ molt test good_tests.tcl
molt 0.2.0 -- Test Harness

5 tests, 5 passed, 0 failed, 0 errors
$ molt test bad_tests.tcl
molt 0.2.0 -- Test Harness

*** FAILED mytest-1.1 some proc
Expected -ok <this result>
Received -ok <that result>

2 tests, 1 passed, 1 failed, 0 errors

test name description args ...

Available in molt test scripts only!

The test command executes its body as a Molt script and compares its result to an expected value. It may be used to test Molt commands, whether built-in or coded in Molt. The expected value may be an -ok result or an -error message.

The name and description are used to identify the test in the output. The name can be any string, but the convention is to use the format "baseName-x.y", e.g., mycommand-1.1. In the future, molt test will allow the user to filter the set of tests on this name string.

The test is executed in its own local variable scope; variables used by the test will be cleaned up automatically at the end of the test. The global command may be used to reference global variables; however, changes to these must be cleaned up explicitly. Similarly, any procs defined by the test must be cleaned up explicitly.

The test command has two forms, a brief form and an extended form with more options.

test name description body -ok|-error expectedValue

In the brief form, the body is the test script itself; and it is expected to return a normal result or an error message. Either way, expectedValue is the expected value.

  • The test passes if the body returns the right kind of result with the expected value.
  • The test fails if the body returns the right kind of result (e.g., -ok) with some other value.
  • The test is in error if the body returns the wrong kind of result, (e.g., an error was returned when a normal result was expected).

test name description option value ?option value ...?

In the extended form, the details of the test are specified using options:

  • -setup: indicates a setup script, which will be executed before the body of the test. The test is flagged as an error if the setup script returns anything but a normal result.

  • -body: indicates the test's body, which is interpreted as described above.

  • -cleanup: indicates a cleanup script, which will be executed after the body of the test. The test is flagged as an error if the cleanup script returns anything but a normal result.

  • -ok | -error: indicates the expected value, as described above.

Examples

The following tests are for an imaginary square command that returns the square of a number. They use the brief form.

test square-1.1 {square errors} {
    square
} -error {wrong # args: should be "square number"}

test square-2.1 {square command} {
    square 3
} -ok {9}

The following test shows the extended form:

test newproc-1.1 {new proc} -setup {
    # Define a proc for use in the test
    proc myproc {} { return "called myproc" }
} -body {
    # Call the proc
    myproc
} -cleanup {
    # Clean up the proc
    rename myproc ""
} -error {called myproc}

TCL Notes

This command is a simplified version of the test command defined by Standard TCL's tcltest(n) framework. The intention is to increase the similarity over time.

This command has an enhancement over TCL's test command: the test has its own local variable scope, just as a proc does. The body must use the global command to access global variables.

molt bench filename ?-csv?

This command executes the benchmark script called filename using the Molt benchmark framework. The framework runs the benchmarks in the script and outputs the results in nanoseconds.

NOTE: The benchmark tool is experimental, subject to change, and primarily intended as aid for Molt optimization.

The output looks like this.

$ molt bench benchmarks/basic.tcl
Molt 0.2.0 -- Benchmark

   Nanos     Norm -- Benchmark
    3344     1.00 -- ok-1.1 ok, no arguments
    4110     1.23 -- ok-1.2 ok, one argument
    4442     1.33 -- ok-1.3 ok, two arguments
    4005     1.20 -- ident-1.1 ident, simple argument
    7175     2.15 -- incr-1.1 incr a
    6648     1.99 -- set-1.1 set var value
    7926     2.37 -- list-1.1 list of six items
...
$

The Norm column shows the times relative to the first benchmark in the set.

CSV Output

Use the -csv option to produce output in CSV format:

$ molt bench benchmarks/basic.tcl -csv
"benchmark","description","nanos","norm"
"ok-1.1","ok, no arguments",3313,1
"ok-1.2","ok, one argument",4027,1.2155146392997283
"ok-1.3","ok, two arguments",4439,1.3398732266827649
"ident-1.1","ident, simple argument",4026,1.2152127980682161
"incr-1.1","incr a",7325,2.210987020827045
"set-1.1","set var value",6499,1.9616661635979475
"list-1.1","list of six items",7848,2.3688499849079383
...

Writing Benchmarks

Benchmarks are written using the benchmark or measure commands. See those man pages for examples.

benchmark name description body ?count?

Available in molt bench scripts only!

Defines a benchmark with the given name and description. The body is a Tcl script; it is executed count times via the time command, and records the average runtime in microseconds. The count defaults to 1000 iterations.

The name should be a symbolic name for easy searching; the description should be a brief human-readable description of the benchmark.

Example

The following is a simple benchmark of the incr command.

benchmark incr-1.1 {incr a} {
    incr a
}

measure name description micros

Available in molt bench scripts only!

This is a low-level command used by the benchmark command to record measurements. All recorded measurements will be included in the tool's output.

Benchmark scripts won't usually need to call this; however, it can be useful when defining custom benchmarking commands.

Example

measure incr-1.1 "incr a" 1.46

ok ?arg arg...?

Available in molt bench scripts only!

This command takes any number of arguments and returns the empty string. It is useful when benchmarking code that calls other commands, as (with no arguments) it represents the minimum amount of computation the Molt interpreter can do.

Example

For example, Molt's own benchmark suite includes the following as its baseline, as a lower bound on the run-time of evaluating a script:

benchmark ok-1.1 {ok, no arguments} {
    ok
}

ident value

Available in molt bench scripts only!

Returns its argument unchanged. Like ok, this is a command used for constructing benchmarks of the Molt interpreter itself.

Molt Command Reference

Molt implements the following commands. See the reference for each command to see any differences from Standard TCL.

Command Description
append Appends values to a list
array Query and manipulate array variables
assert_eq Equality assertion
break Break loop execution
catch Catch exceptions
continue Continue with next iteration
dict Dictionary manipulation
error Throws an error
exit Exit the application
expr Evaluate algebraic expressions
for "For" loop
foreach "For each" loop
global Bring global into scope
if If/then/else
incr Increment integer
info Interpreter introspection
join Join list elements into a string
lindex Index into a list
list Create a list
llength Length of a list
proc Procedure definition
puts Print a string
rename Rename a command
return Return a value
set Set a variable's value
source Evaluate a script file
string String manipulation
throw Throws an exception
time Time script execution
unset Clear a variable
while "While" loop

append -- Appends values to a list

Syntax: append varName ?value ...?

Appends zero or more values to the value of variable varName. If varName didn't previously exist, it is set to the concatenation of the values.

Examples

set x "this"
append x "that"
assert_eq $x "thisthat"

append y a b c
assert_eq $y abc

array -- Query and manipulate array variables

Syntax: array subcommand ?arg ...?

This command queries and manipulates array variables.

Subcommand Description
array exists Is the string the name of an array variable?
array get A dictionary of the array's elements by name
array names A list of the array's indices
array set Merges a dictionary of elements into the array
array size The number of elements in the array
array unset Unsets an array variable

TCL Liens

  • Does not support filtering using glob or regex matches at this time. The plan is to support glob and regex matching as a configuration option at build time.
  • Will never support the array iteration commands array startsearch, array anymore, array donesearch, array nextelement, because they are unnecessary and rarely used. The normal idiom for iterating over an array is a foreach over array names.
  • Will never support array statistics, as Rust's std::collections::HashMap doesn't provide a way to gather them.

array exists

Syntax: array exists arrayName

Returns 1 if arrayName names an array variable, and 0 otherwise.

array get

Syntax: array get arrayName

Returns a flat list of the keys and values in the named array. The key/value pairs appear in unsorted order. If there is no array variable with the given name, returns the empty list.

TCL Liens: does not support filtering the list using glob and regex matches.

array names

Syntax: array names arrayName

Returns an unsorted list of the indices of the named array variable. If there is no array variable with the given name, returns the empty list.

TCL Liens: does not support filtering the list using glob and regex matches.

array set

Syntax: array set arrayName list

Merges a flat list of keys and values into the array, creating the array variable if necessary. The list must have an even number of elements. It's an error if the variable exists but has a scalar value, or if arrayName names an array element.

array size

Syntax: array size arrayName

Returns the number of elements in the named array. If there is no array variable with the given name, returns "0".

array unset

Syntax: array unset arrayName ?index?

Unsets the array element in arrayName with the given index. If index is not given, unsets the entire array.

Note:

  • array unset my_array is equivalent to unset my_array, but only works on array variables.
  • array unset my_array my_index is equivalent to unset my_array(my_index)

The real value of array unset depends on pattern matching on the index argument, which is not yet available.

TCL Liens: does not support glob matching on the optional argument.

assert_eq -- Equality assertion

Syntax: assert_eq received expected

Asserts that the string received equals the string expected. On success, returns the empty string; on failure, returns an error.

This command is primarily intended for use in examples, to show the expected result of a computation, rather than for use in test suites. For testing, see the test command and the molt test tool.

TCL Notes

This command is not part of Standard TCL; it is provided because of its similarity to the Rust assert_eq! macro.

break -- Break loop execution

Syntax: break

Breaks execution of the inmost loop containing the break command, continuing execution after the loop.

foreach item $list {
    ...
    if {[someCondition]} {
        break
    }
    ...
}

# Execution continues here after the break

break and return

The break command is semantically equivalent to return -code break -level 0, as is the following procedure:

proc my_break {} {
    return -code break -level 1
}

See the return reference page for more information.

catch -- Catch exceptions

Syntax: catch script ?resultVarName? ?optionsVarName?

Executes the script, catching the script's result. The catch command returns an integer result code, indicating why the script returned. If resultVarName is given, the named variable in the caller's scope is set to the script's actual return value. If optionsVarName is given, the named variable is set to the return options dictionary in the caller's scope.

catch is most often used to catch errors. For example,

if {[catch {do_something} result]} {
    puts "Error message: $result"
} else {
    puts "Good result: $result"
}

Return Codes

The return value of catch is an integer code that indicates why the script returned. There are five standard return codes:

Return Code Effect
0 (ok) Normal. The result variable is set to the script's result.
1 (error) A command in the script threw an error. The result variable is set to the error message.
2 (return) The script called return. The result variable is set to the returned value.
3 (break) The script called break.
4 (continue) The script called continue.

In addition, the return command allows any integer to be used as a return code; together with catch, this can be used to implement new control structures.

The errorCode and errorInfo Variables

When catch catches an error (or when an error message is output in the Molt REPL), the global variable errorCode will be set to the specific error code (see throw) and the global variable errorInfo will be set to a human-readable stack trace.

The Options Dictionary

The options dictionary saved to the optionsVarName contains complete information about the return options. See return for a complete discussion of what the return options are and how they are used.

Rethrowing an Error

Sometimes it's desirable to catch an error, take some action (e.g., log it), and then rethrow it. The return command is used to do this:

set code [catch {
    # Some command or script that can throw an error
} result opts]

if {$code == 1} {
    # Log the error message
    puts "Got an error: $result"

    # Rethrow the error by returning with exactly the options and return
    # result that we received.
    return {*}$opts $result
}

Visualizing the Return Protocol

The semantics of the return/catch protocol are tricky. When implementing a new control structure, or a modified or extended version of return, break, continue, etc., it is often useful to execute short scripts and examine the options dictionary in the REPL:

% catch { break } result opts
3
% set result
% set opts
-code 3 -level 0
% catch { return "Foo" } result opts
2
% set result
Foo
% set opts
-code 0 -level 1
%

This REPL dialog shows that break yields result code 3 immediately, to be handled by the calling command (usually a loop), while return returns from the calling procedure (-level 1) and then yields an ok (i.e., normal) result to its caller.

TCL Liens

Molt's catch command differs from Standard TCL's in the following ways:

  • The options dictionary, as returned, lacks the -errorline and -errorstack options. These might be added over time.

  • All options passed to return, whether understood by Standard TCL or not, are passed through and included in the catch options dictionary. Molt does not currently support this.

All of the common patterns of use are supported.

continue -- Continue with next iteration

Syntax: continue

Continues execution with the next iteration of the inmost loop containing the continue command.

foreach item $list {
    ...
    if {[someCondition]} {
        continue
    }

    # Skips this code on [someCondition]
    ...
}

continue and return

The continue command is semantically equivalent to return -code continue -level 0, as is the following procedure:

proc my_continue {} {
    return -code continue -level 1
}

See the return reference page for more information.

dict -- Dictionary manipulation

Syntax: dict subcommand ?arg ...?

This command manipulates TCL dictionaries. A dictionary is a Molt value containing a hash map from keys to values. Keys are maintained in order of initial insertion.

Subcommand Description
dict create Creates a dictionary
dict exists Is there a value with these keys?
dict get Gets a value from the dictionary
dict keys Gets the keys from the dictionary
dict remove Removes keys from the dictionary
dict set Sets a value in a dictionary
dict unset Unsets a value in a dictionary
dict size The number of elements in the dictionary
dict values Gets the values from the dictionary

TCL Liens

  • Not all of the standard TCL dict subcommands are implemented at this time.
  • dict keys and dict values do not support filtering using glob or regex matches at this time. The plan is to support glob and regex matching as an optional feature.
  • dict info is not supported; it is intended for tuning the standard TCL hash table implementation. Molt relies on std::collections::HashMap.

dict create

Syntax: dict create ?key value ...?

Creates a dictionary given any number of key/value pairs.

% set dict [dict create a 1 b 2]
a 1 b 2
% dict get $dict a
1

dict exists

Syntax: dict exists dictionary key ?key ...?

Returns 1 if the key (or the path of keys through nested dictionaries) is found in the given dictionary value, and 0 otherwise. It returns 1 exactly when dict get will succeed for the same arguments. It does not throw errors on invalid dictionary values, but simply returns 0.

Looks up the key in the dictionary and returns its value. It's an error if the key is not present in the dictionary. If multiple keys are provided, the command looks up values through nested dictionaries. If no keys are provided, the dictionary itself is returned.

% dict exists {a 1 b 2} b
1
% dict exists {a {x 1 y2} b {p 3 q 4}} b p
1
% dict exists {a 1 b 2} c
0
% dict exists not-a-dict a
0

dict get

Syntax: dict get dictionary ?key ...?

Looks up the key in the dictionary and returns its value. It's an error if the key is not present in the dictionary. If multiple keys are provided, the command looks up values through nested dictionaries. If no keys are provided, the dictionary itself is returned.

% dict get {a 1 b 2} b
2
% dict get {a {x 1 y2} b {p 3 q 4}} b p
3

dict keys

Syntax: dict keys dictionary

Returns a list of the keys in the dictionary, in the order of initial insertion.

% dict keys {a 1 b 2}
a b

dict remove

Syntax: dict remove dictionary ?key ...?

Removes each of the keys from the dictionary, returning the modified dictionary. The keys need not be present in the original dictionary value. If no keys are given, returns the dictionary unchanged.

% dict remove {a 1 b 2 c 3 d 4} b c
a 1 d 4

dict set

Syntax: dict set dictVarName key ?key ...? value

Given the name of a variable containing a dictionary, sets the value of the given key in the dictionary. If multiple keys are given, the command indexes down the path of keys and sets the value in the nested dictionary. The variable is created if it does not exist, and the nested dictionaries are also created as needed. Returns the modified dictionary, which is also saved back into the variable.

For example,

% dict set var a 1
a 1
% dict set var b 2
a 1 b 2
% dict set var c x 3
a 1 b 2 c {x 3}
% dict set var c y z 4
a 1 b 2 c {x 3 y {z 4}}

dict size

Syntax: dict size dictionary

Gets the number of entries in the dictionary.

% set dict [dict create a 1 b 2 c 3]
a 1 b 2 c 3
% dict size $dict
3

dict unset

Syntax: dict unset dictVarName ?key ...?

Given the name of a variable containing a dictionary, removes the value at the end of the path of keys through any number of nested dictionaries. The last key need not exist in the inmost dictionary, but it is an error if any of the other dictionaries in the path are unknown. Returns the modified dictionary, which is also saved back into the variable.

For example,

% set var {a 1 b {x 2 z 3} c 4}
a 1 b {x 2 y 3} c 4
% dict unset c        ;# Remove "c" from the outermost dictionary
a 1 b {x 2 y 3}
% dict unset b y      ;# Remove "y" from an inner dictionary "b"
a 1 b {x 2}
% dict unset var c    ;# "c" is already not there
a 1 b {x 2}
% dict unset var b y  ;# "y" is already not in "b"
a 1 b {x 2}
% dict unset var c z  ;# Inner dictionary "c" is not present.
key "c" is not known in dictionary

dict values

Syntax: dict values dictionary

Returns a list of the values in the dictionary, in the order of initial insertion of their keys.

% dict values {a 1 b 2}
1 2

error -- Throws an error

Syntax: error message

Returns an error with the given message and an error code of NONE. The error may be caught using the catch command.

Example

proc myproc {x} {
    if {$x < 0} {
        error "input must be non-negative"
    }
    ...
}

TCL Liens

In standard TCL, the error also has optional errorInfo and errorCode arguments. These are used in older TCL code to rethrow errors without polluting the stack trace. Modern TCL code uses the throw command to throw an error with an error code and the return command to rethrow an error (see the reference page for an example). Consequently, Molt doesn't implement these arguments.

exit -- Exit the application

Syntax: exit ?returnCode?

Terminates the application by calling std::process:exit() with the given returnCode, which must be an integer. If not present, the returnCode defaults to 0.

expr -- Evaluate algebraic expressions

Syntax: expr expr

Evaluates the expression, returning the result.

expr implements a little language that has a syntax separate from that of Molt. An expression is composed of values and operators, with parentheses for grouping, just as in C, Java, and so forth. Values consist of numeric and boolean literals, function calls, variable and command interpolations, and double-quoted and braced strings. Every value that looks like a number is treated as a number, and every value that looks like a boolean is treated as a boolean.

The operators permitted in expressions include most of those permitted in C expressions, with a few additional ones The operators have the same meaning and precedence as in C. Expressions can yield numeric or non-numeric results.

Integer computations are done with Rust's i64 type; floating-point computations are done with Rust's f64 type.

Examples

expr {1 + 1}

set x 7.5
set y 3.4
expr {$x + $y}

expr {[mycommand] + 2}

expr {2*(1 + abs($x))}

Operators and Precedence

The following table shows the operators in order of precedence.

Operators Details
- + ~ ! Unary plus, minus, bit-wise not, and logical not
* / % Multiplication, division, integer remainder
+ - Addition, subtraction
<< >> Left and right shift.
< > <= >= Ordering relations (see below)
== != Equality, inequality (see below)
eq ne String equality, inequality
in ni List inclusion, exclusion
& Bit-wise AND
^ Bit-wise exclusive OR
| Bit-wise OR
&& Logical AND, short circuiting
|| Logical OR, short circuiting
x ? y : z Ternary "if-then-else" operator.

Boolean Values

  • True values: any non-zero number, true, yes, on.
  • False values: zero, false, no, off.
  • Logical operators always return 0 or 1.
  • By convention, predicate commands also return 0 or 1.

Math Functions

Functions are written as "name(argument,...)". Each argument is itself a complete expression.

The following functions are available in Molt expressions:

abs(x) — Absolute value of x.

double(x) — Returns integer x as a floating-point value.

int(x) — Truncates floating-point value x and returns it as an integer.

round(x) — Rounds floating-point value x to the nearest integer and returns it as an integer.

TCL Liens

Expr Command Syntax: In standard TCL expr takes any number of arguments, which it concatenates into a single expression for evaluation. This means that variable and command interpolation is done twice, once by the TCL parser and once by expr, which hurts performance and can also be a source of subtle and confusing errors. Consequently it is almost always best to provide the expression as a single braced string, and so Molt's expr takes a single argument. This is unlikely to change.

Expression Syntax: Molt's expression parsing is meant to be consistent with TCL 7.6, with the addition of the TCL 8.x eq, ne, in, and ni operators.

  • Molt does not yet support the full range of math functions supported by TCL 7.6.
  • Molt does not yet do precise float-to-string-to-float conversions, per TCL 8.6. See
    "String Representation of Floating Point Numbers" on the Tcler's Wiki expr page.
  • Molt's handling of floating point arithmetic errors is still naive.

Integer Division: Integer division in Molt rounds down towards zero, following the example of Rust, Python, C99, and many other languages. Standard TCL rounds toward negative infinity, a decision that dates to a time when the C standard did not define the correct behavior and C compilers varied. It seems reasonable that an extension language should do something as basic as this in the same way as the host language.

Possible Futures: The following TCL 8.6 features are not on the road map at present, but might be added in the future.

  • Bignums
  • The exponential operator, **
  • The tcl::mathfunc:: namespace, and the ability to define new functions in TCL code.

for -- "For" loop

Syntax: for start test next command

The for command provides a C-like "for" loop, where start is a script that initializes the loop counter, test is a conditional expression, next is a script that updates the loop counter, and command is the body script.

If the command script calls the break command, the loop terminates immediately; if the command script calls the continue command, loop execution continues with the next iteration.

Example

For example, the following loop counts from 0 to 9:

for {set i 0} {$i < 10} {incr i} {
    puts "i=$i"
}

Note, though, that the start and next arguments are arbitrary scripts; for example, start can initialize multiple variables, and next can update multiple variables.

foreach -- "For each" loop

Syntax: foreach varList list body

Loops over the elements in the list, assigning them to the variables in the varList and executing the body for each set of assignments.

The break and continue commands can be used to control loop execution; see their reference pages for details.

Examples

Prints out the values "1", "2", and "3" on successive lines.

foreach a {1 2 3} {
    puts $a
}

Prints out pairs of values from the list. In the final iteration there is only value left, so b is assigned the empty string.

foreach {a b} {1 2 3 4 5} {
    puts "$a,$b"
}
# Outputs:
#
#  1,2
#  3,4
#  5,

TCL Liens

In standard TCL, foreach can iterate over multiple lists at the same time, e.g., the following script will output the pairs "a,1", "b,2", and "c,3". Molt doesn't currently support this extended syntax.

foreach x {a b c} y {1 2 3} {
    puts "$x,$y"
}

global -- Bring global into scope

Syntax: global ?varname ...?

Brings global variable(s) varname into scope in a proc body. This command has no effect if called in the global scope.

TCL Differences

At the script level, global works the same in Molt as in Standard TCL. However, Molt's internal implementation of variables is currently much simpler than standard TCL's, e.g., no arrays, no namespaces.

if -- If/then/else

Syntax: if expr1 ?then? body1 elseif expr2 ?then? body2 elseif ... ?else? ?bodyN?

Tests a chain of one or more expressions, and executes the matching body, which must be a script. Returns the result of the last command executed in the selected body.

Both the then and else keywords are optional. The standard TCL convention is to always omit the then keywords and to always include the else keyword when there's an else clause.

Examples

if {$x > 0} {
    puts "positive"
}

if {$x < 0} {
    puts "negative"
} else {
    puts "non-negative"
}

if {$x > 0} {
    puts "positive"
} elseif {$x < 0} {
    puts "negative"
} else {
    puts "zero"
}

set value [if {$x > 0} {
    expr {$x + $y}   
} else {
    expr {$x - $y}   
}]

incr -- Increment integer

Syntax: incr varName ?increment?

Increments integer-valued-variable varName by the given increment, which defaults to 1. If the variable is unset, it is set to the increment. The command returns the incremented value.

Examples

unset a
incr a    ;# => 1
incr a    ;# => 2
incr a 3  ;# => 5

for {set a 1} {$a < 10} {incr a} {
    ...
}

info -- Interpreter introspection

Syntax: info subcommand ?arg ...?

Returns information about the state of the Molt interpreter.

Subcommand Description
info args Names of procedure's arguments
info body Gets procedure body
info cmdtype Queries a command's type
info commands Names of all defined commands
info complete Is this string a syntactically complete command?
info default A procedure argument's default value
info exists Is this a variable in the current scope?
info globals Names of all variables in the global scope
info locals Names of all local variables in the current scope
info procs Names of all defined procedures
info vars Names of all variables in the current scope

info args

Syntax: info args procname

Retrieves a list of the names of the arguments of the named procedure. Returns an error if the command is undefined or is a binary command.

For example,

% proc myproc {a b c} { ... }
% info args myproc
a b c
%

info body

Syntax: info body procname

Retrieves the body of the named procedure. Returns an error if the command is undefined or is a binary command.

For example,

% proc myproc {name} { puts "Hello, $name" }
% info body myproc
puts "Hello, $name"
%

info cmdtype

Syntax: info cmdtype command

Retrieves the named command's type, either native or proc. The command is native if it's implemented in Rust and proc if it's implemented as a TCL procedure.

% proc myproc {} { ... }
% info cmdtype set
native
% info cmdtype myproc
proc
%

TCL Liens: Standard TCL defines a variety of other command types, e.g., slave interpreters, interpreter aliases, objects, and so forth. These will be added naturally if and when they are added to Molt.

info commands

Syntax: info commands

Returns an unsorted list of the names of the commands defined in the interpreter, including both binary commands and procedures.

TCL Liens: does not support filtering the list using a glob pattern.

info complete

Syntax: info complete command

Returns 1 if the command appears to be a complete Tcl command, i.e., it has no unmatched quotes, braces, or brackets, and 0 otherwise. REPLs can use this to allow the user to build up a multi-line command.

For example,

% info complete { puts "Hello, world!" }
1
% info complete { puts "Hello, world! }
0
%

info default

Syntax: info default procname arg varname

Retrieves the default value of procedure procname's argument called arg. If arg has a default value, info default returns 1 and assigns the default value to the variable called varname. Otherwise, info default returns 0 and assigns the empty string to the variable called varname.

The command throws an error if:

  • procname doesn't name a procedure
  • The procedure procname has no argument called arg
  • The value can't be assigned to a variable called varname.

In the following example, myproc has two arguments, a and b. a has no default value; b has the default value Howdy.

% proc myproc {a {b Howdy}} { ... }
% info default myproc a defvalue
0
% puts "<$defval>"
<>
% info default myproc b defvalue
1
% puts "<$defval>"
<Howdy>
%

info exists

Syntax: info exists varname

Returns 1 if varname is the name of a variable or array element in the current scope, and 0 otherwise.

% set a 1
1
% set b(1) 1
1
% info exists a
1
% info exists b
1
% info exists c
0
% info exists b(1)
1
% info exists b(2)
0

info globals

Syntax: info globals

Returns an unsorted list of the names of all variables defined in the global scope.

TCL Liens: does not support filtering the list using a glob pattern.

info locals

Syntax: info locals

Returns an unsorted list of the names of all local variables defined in the current scope, e.g., proc arguments and variables defined locally, but no variables brought in from other scopes via global or upvar.

TCL Liens: does not support filtering the list using a glob pattern.

info procs

Syntax: info procs

Returns an unsorted list of the names of the procedures defined in the interpreter, omitting binary commands.

TCL Liens: does not support filtering the list using a glob pattern.

info vars

Syntax: info vars

Returns an unsorted list of the names of all variables that are visible in the current scope, whether global or local.

TCL Liens: does not support filtering the list using a glob pattern.

join -- Join list elements into a string

Syntax: join list ?joinString?

Joins the elements of a list into a string, including the joinString in between each element. If not given, the joinString defaults to a single space character.

lindex -- Index into a list

Syntax: lindex list ?index ...?

Returns an element from the list, indexing into nested lists. The indices may be represented as individual indices on the command line, or as a list of indices. Indices are integers from 0 to length - 1. If an index is less than 0 or greater than or equal to the list length, lindex will return the empty string.

Examples

lindex {a {b c d} e}        ;# "a {b c d} e"
lindex {a {b c d} e} 1      ;# "b c d"
lindex {a {b c d} e} 1 1    ;# "c"
lindex {a {b c d} e} {}     ;# "a {b c d} e"
lindex {a {b c d} e} {1 1}  ;# "c"

TCL Liens

Indices in standard TCL may take several additional forms. For example, end indexes the last entry in the list; end-1 indexes the next to last entry, and so forth. Molt doesn't yet support this.

list -- Create a list

Syntax: list ?arg ...?

Returns a list whose elements are the given arguments. The list will be in canonical list form.

llength -- Length of a list

Syntax: llength list

Returns the length of the list.

proc -- Procedure definition

Syntax: proc name args body

Defines a procedure with the given name, argument list args, and script body. The procedure may be called like any built-in command.

The argument list, args, is a list of argument specifiers, each of which may be:

  • A name, representing a required argument
  • A list of two elements, a name and a default value, representing an optional argument
  • The name args, representing any additional arguments.

Optional arguments must follow required arguments, and args must appear last.

When called, the procedure returns the result of the last command in the body script, or the result of calling return, or an error.

TCL Liens

Molt does not support namespaces or namespace syntax in procedure names.

puts -- Print a string

Syntax: puts string

Outputs the string to standard output.

TCL Liens

  • Does not support -nonewline
  • Does not support ?channelId?

rename -- Rename a command

Syntax: rename oldName newName

Renames the command called oldName to be newName instead.

Any command may be renamed in this way; it is a common TCL approach to wrap a command by renaming it and defining a new command with the oldName that calls the old command at its newName.

If the newName is the empty string, the command will be removed from the interpreter.

Examples

proc myproc {} { ... }

# Rename the proc
rename myproc yourproc

# Remove the proc from the interpreter
rename yourproc ""

return -- Return a value

Syntax: return ?options? ?value?

Returns from a TCL procedure or script, optionally including a value. By default, the command simply returns the given value, or the empty string if value is omitted.

proc just_return {} {
    ...
    if {$a eq "all done"} {
        # Just return.  The return value will be the empty string, ""
        return
    }
    ...
}

proc identity {x} {
    # Return the argument
    return $x
}

The options allow the caller to return any TCL return code and to return through multiple procedures at once. The options are as follows:

Option Description
-code code The TCL result code; defaults to ok.
-level level Number of stack levels to return through; defaults to 1.
-errorcode errorCode The error code, when -code is error. Defaults to NONE.
-errorinfo errorInfo The initial error stack trace. Defaults to the empty string.

The -code and -level Options

The -code and -level options work together. The semantics are tricky to understand; a good aid is to try things and use catch to review the result value and options.

If -code is given, the code must be one of ok (the default), error, return, break, continue, or an integer. Integer codes 0, 1, 2, 3, and 4 correspond to the symbolic constants just given. Other integers can be used to implement application-specific control structures.

If -level is given, the level must be an integer greater than or equal to zero; it represents the number of stack levels to return through, and defaults to 1.

Because of the defaults, a bare return is equivalent to return -code ok -level 1:

# These are the same:
proc simple {}  { return "Hello world" }
proc complex {} { return -code ok -level 1 "Hello world" }

Both tell the interpreter to return "Hello world" to caller the caller of the current procedure as a normal (ok) return value.

By selecting a different -code, one can return some other error code. For example, break and return -code break -level 0 are equivalent. This can be useful in several ways. For example, suppose you want to extend the language to support break and continue with labels, to be used with some new control structure. You could do the following; note the -level 1. The return command returns from your labeled_break procedure to its caller, where it is understood as a break result.

proc labeled_break {{label ""}} {
    return -code break -level 1 $label
}

Your new control structure would [catch] the result, see that it's a break, and jump to the indicated label.

Similarly, suppose you want to write a command that works like return but does some additional processing. You could do the following; note the -level 2. The 2 is because the command needs to return from your list_return method, and then from the calling procedure: two stack levels.

# Return arguments as a list
proc list_return {a b c} {
    return -level 2 -code ok [list a b c]
}

Returning Errors Cleanly

The normal way to throw an error in TCL is to use either the error or throw command; the latter is used in more modern code when there's an explicit error code. However, both of these commands will appear in the error stack trace.

Some TCL programmers consider it good style in library code to throw errors using return, as follows (with or without the -errorcode):

proc my_library_proc {} {
   ...
   return -code error -level 1 -errorcode {MYLIB MYERROR} "My Error Message"
}

The advantage of this approach is that the stack trace will show my_library_proc as the source of the error, rather than error or catch.

The -errorinfo Option and Re-throwing Errors

Sometimes it's desirable to catch an error, take some action (e.g., log it), and then rethrow it. The return command is used to do this:

set code [catch {
    # Some command or script that can throw an error
} result opts]

if {$code == 1} {
    # Log the error message
    puts "Got an error: $result"

    # Rethrow the error by returning with exactly the options and return
    # result that we received.
    return {*}$opts $result
}

TCL Liens

The standard TCL return command is more complicated than shown here; however, the Molt implementation provides all of the useful patterns the author has ever seen in use. Some of the specific differences are as follows:

  • Molt rejects any options other than the ones listed above, and ignores -errorcode and -errorinfo if the -code is anything other than error. Standard TCL's return retains all option/value pairs it is given, to be included in the catch options.

  • Standard TCL's return takes an -options option; in Standard TCL, return -options $opts is equivalent to return {*}$ops. Molt doesn't support -options, as it doesn't add any value and is confusing.

  • Standard TCL provides two versions of the stack trace: the "error info", meant to be human readable, and the "error stack", for programmatic use. The -errorstack is used to initialize the error stack when rethrowing errors, as -errorinfo is used to initialize the error info string. Molt does not support the error stack at this time.

Some of these liens may be reconsidered over time.

set -- Set a variable's value

Syntax: set varName ?newValue?

Sets variable varName to the newValue, returning the newValue. If newValue is omitted, simply returns the variable's existing value, or returns an error if there is no existing value.

The set command operates in the current scope, e.g., in proc bodies it operates on the set of local variables.

See also: global

TCL Liens

  • Molt does not support namespaces or namespace notation.

source -- Evaluate a script file

Syntax: source filename

Executes the named file as a Molt script, returning the result of the final command executed in the script.

TCL Differences

  • Standard TCL provides a -encoding option, for choosing a specific Unicode encoding. Molt assumes that the text read from the file is in the UTF-8 encoding, and does nothing special about it.

  • Standard TCL reads from the source'd file only up to the first ^Z.
    This allows for the creation of scripted documents: binary files beginning with a TCL script. The script can then open the file and read the rest of the data. Molt does not implement this behavior.

string -- String manipulation

Syntax: string subcommand ?args...?

Subcommand Description
string cat Concatenates zero or more strings
string compare Compares two strings lexicographically
string equal Compares two strings for equality
string first Finds first occurrence of a string
string last Finds last occurrence of a string
string length String length in characters
string map Maps keys to values in a string
string range Extracts a substring
string tolower Converts a string to lower case
string toupper Converts a string to upper case
string trim Trims leading and trailing whitespace
string trimleft Trims leading whitespace
string trimright Trims trailing whitespace

TCL Liens

  • Supports a subset of the subcommands provided by the standard TCL string command. The subset will increase over time.
  • Does not currently support index syntax, e.g., end-1, for the string first, string last, and string range commands. These commands accept simple numeric indices only.

Molt Strings and Unicode

Molt strings are exactly and identically Rust String values, and are treated at the TCL level as vectors of Rust char values. A Rust char is a "Unicode scalar value", and is also (in most cases) a Unicode code point. It is not a not a grapheme; graphemes that consist of multiple code points will be treated as multiple characters. This is more or less the same as Standard TCL, but Unicode being what it is there may be edge cases where behavior will differ slightly.

string cat


Syntax: string cat ?args ...?

Returns the concatenation of zero or more strings.

string compare


Syntax: string compare ?options? string1 string2

Compares the two strings lexicographically, returning -1 if string1 is less than string2, 0 if they are equal, and 1 if string1 is greater than string2.

The options are as follows:

Option Description
-nocase The comparison is case-insensitive.
-length length Only the first length characters will be compared.

Notes:

  • When -nocase is given, the strings are compared by converting them to lowercase using a naive method that may fail for more complex Unicode graphemes.

string equal


Syntax: string equal ?options? string1 string2

Compares the two strings, returning 1 if they are equal, and 0 otherwise.

The options are as follows:

Option Description
-nocase The comparison is case-insensitive.
-length length Only the first length characters will be compared.

Notes:

  • When -nocase is given, the strings are compared by converting them to lowercase using a naive method that may fail for more complex Unicode graphemes.

string first


Syntax: string first needleString haystackString ?startIndex?

Returns the index of the first occurrence of the needleString in the haystackString, or -1 if the needleString is not found. If the startIndex is given, the search will begin at the startIndex.

string last


Syntax: string last needleString haystackString ?startIndex?

Returns the index of the last occurrence of the needleString in the haystackString, or -1 if the needleString is not found. If the startIndex is given, the search will begin at the startIndex.

string length


Syntax: string length string

Returns the length of the string in Rust characters.

string map


Syntax: string map ?-nocase? mapping string

Replaces old substrings in string with new ones based on the key/value pairs in mapping, which is a dictionary or flat key/value list. If -nocase is given, substring matches will be case-insensitive. The command iterates through the string in a single pass, checking for each key in order, so that earlier key replacements have no effect on later key replacements.

string range


Syntax: string range string first last

Returns the substring of string starting with the character whose index is first and ending with the character whose index is last. Values of first that are less than 0 are treated as 0, and values of last that are greater than the index of the last character in the string are treated as that index.

string tolower


Syntax: string tolower string

Converts the string to all lower case, using the standard Rust String::to_lowercase method.

TCL Liens: Tcl 8.6 provides for optional first and last indices; only the text in that range is affected.

string toupper


Syntax: string toupper string

Converts the string to all upper case, using the standard Rust String::to_uppercase method.

TCL Liens: Tcl 8.6 provides for optional first and last indices; only the text in that range is affected.

string trim


Syntax: string trim string

Returns string trimmed of leading and trailing whitespace by the standard Rust String::trim method.

string trimleft


Syntax: string trimleft string

Returns string trimmed of leading whitespace by the standard Rust String::trim_start method.

string trimright


Syntax: string trimright string

Returns string trimmed of trailing whitespace by the standard Rust String::trim_end method.

throw -- Throws an exception

Syntax: throw type message

Throws an error with error code type and the given error message. The error may be caught using the catch command.

The error code is usually defined as a TCL list of symbols, e.g., ARITH DIVZERO. Most standard TCL error codes begin with ARITH (for arithmetic errors) or TCL.

Example

proc myproc {x} {
    if {$x < 0} {
        throw NEGNUM "input must be non-negative"
    }
    ...
}

Note that the error command is equivalent to throw NONE; also, the return command can also throw an error with an error code. The three following commands are semantically identical:

error "My error message"

throw NONE "My error message"

return -code error -level 0 -errorcode NONE "My error message"

time -- Time script execution

Syntax: time command ?count?

Evaluates the given command the given number of times, or once if no count is specified, timing each execution. The average run time in microseconds is returned as a string, "average microseconds per iteration".

Example

% time { mycommand } 1000
15 microseconds per iteration
%

unset -- Clear a variable

Syntax: unset ?-nocomplain? ?--? ?name name name...?

Unsets one or more variables whose names are passed to the command. It does not matter whether the variables actually exist or not.

The -nocomplain option is ignored. The argument -- indicates the end of options; all arguments following -- will be treated as variable names whether they begin with a hyphen or not.

TCL Differences

In standard TCL, it's an error to unset a variable that doesn't exist; the command provides the -nocomplain option to cover this case. In Molt, unset never complains; the -nocomplain option is provided only for compatible with legacy TCL code. (Per the TCL Core Team, the -nocomplain option indicates, wherever it is found, that the original definition of the command got the default behaviour wrong.)

while -- "While" loop

Syntax: while test command

The while command is a standard "while" loop, executing the command script just so long as the test expression evaluates to true.

Example

The following code will output the numbers from 1 to 10.

set i 0
while {$i < 10} {
    puts "i=[incr i]"
}

Embedding Molt

This chapter explains how to embed Molt in a Rust application. There are several parts to this:

An application may execute scripts for its own purposes and arbitrary scripts defined by the user. One common pattern is to define a shell application the user may use to execute their own scripts using the application-specific command set.

It is also possible to define Molt library crate that defines commands for installation into an interpreter.

The initial step, creating a Molt interpreter, is trivially easy:


# #![allow(unused_variables)]
#fn main() {
use molt::Interp;

let mut interp = Interp::new();

// Add application-specific commands
#}

This creates an interpreter containing the standard set of Molt commands. Alternatively, you can create a completely empty interpreter and add just the commands you want:


# #![allow(unused_variables)]
#fn main() {
use molt::Interp;

let mut interp = Interp::empty();

// Add application-specific commands
#}

This is useful if you wish to use the Molt interpreter as a safe file parser.

Eventually there will be an API for adding specific standard Molt commands back into an empty interpreter so that the application can create a custom command set (e.g., including variable access and control structures but excluding file I/O), but that hasn't yet been implemented.

We'll cover the remaining topics in the following sections.

The Molt Value Type

The Value type is the standard representation in Rust of Molt values. In the Tcl language, "everything is a string"; which is to say, every value can be represented as a string. Many values—e.g., numbers and lists—also have a binary data representation, but a single value can move from one binary data representation to another depending on how it is used by the user. Consider the following:

set x [expr {2 + 3}]  ;# It's the integer 5.
puts "x=$x"           ;# It's converted to a string.
set y [lindex $x 0]   ;# It's converted to a one-element list.

Initially, the variable x contains a Value with only a data representation, the integer 5. Then puts needs it as a string, and so the Value acquires a string representation as well, but retains its integer representation. Then lindex needs to look at it as a list, so the string is parsed into a Molt list and the 0th element is returned. The integer representation is lost and replaced by the list representation. The Value type manages all of these transformations internally, with the effect that string-to-binary and binary-to-string conversions happen only when absolutely necessary.

Note: A Value's string representation is never lost, once acquired: semantically, Values are immutable. The data transformations that go on under the hood are an aid to performance, but in principle the value is unchanged.

Creating Values

Values can be created easily from a variety of kinds of input:

let a = Value::from("abc");                              // &str
let b = Value::from("def".to_string());                  // String
let c = Value::from(123);                                // MoltInt (i64)
let d = Value::from(45.67);                              // MoltFloat (f64)
let e = Value::from(true);                               // bool
let f = Value::from(&[Value::from(1), Value::from(2)]);  // &[Value]

And in fact, a Value can contain any Rust type that supports the Display, Debug, and FromStr types via the Value::from_other method. Such types are called "external types" in the Molt documentation set.

Cloning Values

Because Values are immutable, they have been designed to be cheaply and easy cloned with reference counting via the standard Rc type.

Retrieving Data from Values

It is always possible to retrieve a Value's data as a string:

let value = Value::from(5);
let text: String = value.to_string();
assert_eq!(&text, "5");

The to_string method creates a brand new String in the usual way; it is usually better to use as_str, which returns an &str:

let value = Value::from(5);
let text = value.as_str();
assert_eq!(text, "5");

It is also possible to retrieve data representations; but since this isn't guaranteed to work the relevant methods all return Result<_,ResultCode>. (See The MoltResult type for a discussion of ResultCodes.) For example,

let value = Value::from("123");
let x = value.as_int()?;
assert_eq!(x, 123);

Retrieving Values of External Types

Values of external types can be retrieved as well using the Value::as_copy or Value::as_other method, depending on whether the type implements the Copy trait. These are different than their peers, in that they return Option<T> and Option<Rc<T>> rather than Result<T,ResultCode> or Result<Rc<T>,ResultCode>. The reason is that Molt doesn't know what the appropriate error message should be when it finds a value it can't convert into the external type T and so returns None, leaving the error handling up to the client.

For this reason, when using an external type MyType with Molt it is usual to define a function that converts a Value to a Result<MyType,ResultCode>. If MyType is an enum, for example, you might write this:


# #![allow(unused_variables)]
#fn main() {
impl MyType {
    /// A convenience: retrieves the enumerated value, converting it from
    /// `Option<MyType>` into `Result<MyType,ResultCode>`.
    pub fn from_molt(value: &Value) -> Result<Self, ResultCode> {
        if let Some(x) = value.as_copy::<MyType>() {
            Ok(x)
        } else {
            Err(ResultCode::Error(Value::from("Not a MyType string")))
        }
    }
}
#}

The MoltResult Type

MoltResult is Molt's standard Result<T,E> type; it is defined as


# #![allow(unused_variables)]
#fn main() {
pub type MoltResult = Result<Value, Exception>;
#}

The Value type is described in the previous section; by default, many Molt methods and functions return Value on success.

The Exception struct is used for all exceptional returns, including not only errors but also procedure returns, loop breaks and continues, and application-specific result codes defined as part of application-specific control structures.

The heart of the Exception struct is the ResultCode, which indicates the kind of exception return. It is defined as follows:


# #![allow(unused_variables)]
#fn main() {
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum ResultCode {
    Okay,
    Error,
    Return,
    Break,
    Continue,
    Other(MoltInt),
}
#}
  • ResultCode::Okay is used internally.

  • ResultCode::Error indicates that an error has been thrown; the exception's value() is the error message. Use the exception's error_code() and error_info() methods to access the error code and stack trace.

  • ResultCode::Return, which indicates that the Molt code has called the return command; the value is the returned value. Molt procedures, defined using the proc command, will catch this and return value as the value of the procedure call. See the documentation for the return and catch commands for information on a variety of advanced things that can be done using this result code.

  • ResultCode::Break and ResultCode::Continue are returned by the break and continue commands and control loop execution in the usual way.

  • ResultCode::Other can be returned by the return command, and is used when defining application-specific control structures in script code.

Of these, client Rust code will usually only deal with ResultCode::Error and ResultCode::Return. For example,


# #![allow(unused_variables)]
#fn main() {
# use molt::types::*;
# use molt::Interp;

let mut interp = Interp::new();

let input = "set a 1";

match interp.eval(input) {
   Ok(val) => {
       // Computed a Value
       println!("Value: {}", val);
   }
   Err(exception) => {
       if exception.is_error() {
           // Got an error; print it out.
           println!("Error: {}", exception.value());
       } else {
           // It's a Return.
           println!("Value: {}", exception.value());
       }
   }
}
#}

Result Macros

Application-specific Rust code will usually only use Ok(value) and ResultCode::Error. Since these two cases pop up so often, Molt provides several macros to make them easier: molt_ok!, molt_err!, and molt_throw!.

molt_ok! takes one or more arguments and converts them into an Ok(Value).


# #![allow(unused_variables)]
#fn main() {
// Returns the empty result.
return molt_ok!();

// Returns its argument as a Value (if Molt knows how to convert it)
return molt_ok!(5);

// A plain Value is OK to.
return molt_ok!(Value::from(5));

// Returns a formatted string as a Value using a Rust `format!` string.
return molt_ok!("The answer is {}.", x);
#}

molt_err! works just the same way, but returns Err(Exception) with ResultCode::Error.

// Return a simple error message
return molt_err!("error message");

// Return a formatted error message
if x > 5 {
    return molt_err!("value is out of range: {}", x);
}

molt_throw! is like molt_err!, but allows the caller to set an explicit error code. (By default, Molt errors have an error code of NONE.) Error codes can be retrieved from the Exception object in Rust code and via the catch command in scripts.

// Throw a simple error
return molt_throw!("MYCODE", "error message");

// Throw a formatted error message
if x > 5 {
    return molt_throw!("MYCODE", "value is out of range: {}", x);
}

Defining Commands

At base, a Molt command is a Rust function that performs some kind of work and optionally returns a value in the context of a specific Rust interpreter. There are two ways an application (or library crate) can define application-specific Rust commands:

  • As a Rust CommandFunc function
  • As a Molt procedure, or proc.

CommandFunc Commands

A CommandFunc command is any Rust function that implements CommandFunc:


# #![allow(unused_variables)]
#fn main() {
pub type CommandFunc = fn(&mut Interp, ContextID, &[Value]) -> MoltResult;
#}

For example, here's a simple command that takes one argument and returns it unchanged.


# #![allow(unused_variables)]
#fn main() {
fn cmd_ident(_interp: &mut Interp, _context_id: ContextID, argv: &[Value]) -> MoltResult {
    check_args(1, argv, 2, 2, "value")?;

    molt_ok!(argv[1].clone())
}
#}

The argv vector contains the arguments to the command, beginning with the command's name. The check_args method verifies that the command has the right number of arguments, and returns the standard Tcl error message if not. Finally, it uses molt_ok! to return its first argument.

Install this command into the interpreter using the Interp::add_command method:


# #![allow(unused_variables)]
#fn main() {
interp.add_command("ident", cmd_ident);
#}

CommandFunc Commands with Context

A normal CommandFunc is useful when extending the Molt language itself; but application-specific commands need to manipulate the application and its data. In this case, add the required data to the interpreter's context cache. The cached data can be retrieved, used, and mutated by commands tagged with the relevant context ID.

The context cache is a hash map that allows the interpreter to keep arbitrary data and make it available to commands. The usual pattern is like this:

  • The application defines a type containing the data the command (or commands) requires. We'll call it AppContext for the purpose of this example.

  • The application saves an instance of AppContext into the context cache, retrieving a ContextID.

  • The application includes the ContextID when adding the command to the interpreter.

  • The command retrieves the AppContext as a mutable borrow.

// The AppContext
struct AppContext { text: String }

// The Command
fn cmd_whatsit(interp: &mut Interp, context_id: ContextID, argv: &[Value]) -> MoltResult {
    check_args(1, argv, 2, 2, "value")?;

    let ctx = interp.context::<AppContext>(context_id);

    // Append the first argument's string rep to the
    // AppContext struct's text field.
    ctx.text.push_str(argv[1].as_str());

    molt_ok!()
}

// Registering the command
fn main() {
    let interp = Interp::new();
    let id = interp.save_context(AppContext::new());

    interp.add_context_command("whatsit", cmd_whatsit, id);

    ...
}

The saved AppContext will be dropped automatically if the whatsit command is removed from the interpreter.

Commands with Shared Context

Any number of Molt commands can share a single cached context struct:

    let interp = Interp::new();
    let id = interp.save_context(AppContext::new());

    interp.add_context_command("first", cmd_first, id);
    interp.add_context_command("second", cmd_second, id);
    interp.add_context_command("third", cmd_third, id);
    ...

The context struct will persist in the cache until the final command is removed (or, of course, until the interpreter is dropped).

Molt Objects

The standard way to represent an object in TCL is to define a command with attached context data. The command's methods are implemented as subcommands.

The context cache supports this pattern trivially. Define the object's instance variables as a context struct, and define a command to create instances.

// Instance Data
struct InstanceContext { text: String }

// Command to make an instance
fn cmd_make(interp: &mut Interp, _: ContextID, argv: &[Value]) -> MoltResult {
    check_args(1, argv, 2, 2, "name")?;

    let id = interp.save_context(InstanceContext::new());

    interp.add_context_command(argv[1].as_str(), cmd_instance, id);

    molt_ok!()
}

// Instance Command
fn cmd_instance(interp: &mut Interp, _: ContextID, argv: &[Value]) -> MoltResult {
    check_args(1, argv, 2, 0, "subcommand ?args...?")?;

    // Get the context
    let ctx = interp.context::<AppContext>(context_id);

    // Do stuff based on argv[1], the subcommand.
    ...
}

// Registering the command
fn main() {
    let interp = Interp::new();

    interp.add_command("make", cmd_make);

    ...
}

Then, in Molt code you can create an object called fred, use its methods, and then destroy it by renaming it to the empty string.

% make fred
% fred do_something 1 2 3
...
% rename fred ""

Molt Procedures

A Molt procedure is a routine coded in Tcl and defined using the proc command. A crate can compile Tcl procedures into itself using the include_str! macro. Start by defining a script that defines the required procedure, say, procs.tcl, and put it in the crate's src/ folder adjacent to the Rust file that will load it. The Rust file can then do this:


# #![allow(unused_variables)]
#fn main() {
let mut interp = Interp::new();

match interp.eval(include_str!("commands.tcl")) {
    Err(exception) => {
        if exception.is_error() {
            panic!("Couldn't load procs.tcl: {}", msg.value());
        }
    }
    _ => ()
}
#}

Evaluating Molt Code

An application can evaluate Molt code in several ways:

  • Use one of the molt::Interp::eval or molt::Interp::eval_value to evaluate an individual Molt command or script.

  • Use the molt::expr function to evaluate a Molt expression, returning a Molt Value, or molt::expr_bool, molt::expr_int, and molt::expr_float for results of specific types.

  • Use the molt_shell::repl function to provide an interactive REPL to the user.

  • Use the molt_shell::script function to evaluate a script file (or just load the script's content and pass it to molt::Interp::eval).

Evaluating Scripts with eval

The molt::Interp::eval method evaluates a string as a Molt script and returns the result. When executed at the top level, ResultCode::Break, ResultCode::Continue, and ResultCode::Other are converted to errors, just as they are in proc bodies. See The MoltResult Type for details.)

Thus, the following code will execute a script, returning its value and propagating any exceptions to the caller.


# #![allow(unused_variables)]
#fn main() {
use molt::Interp;
use molt::types::*;

let mut interp = Interp::new();

...

let value: Value = interp.eval("...some Molt code...")?;
#}

The molt::Interp::eval_value method has identical semantics, but evaluates the string representation of a molt Value. In this case, the Value will cache the parsed internal form of the script to speed up subsequent evaluations.

Evaluating Control Structure Bodies

The molt::Interp::eval_value method is used when implementing control structures. For example, this is an annotated version of of Molt's while command.


# #![allow(unused_variables)]
#fn main() {
pub fn cmd_while(interp: &mut Interp, _: ContextID, argv: &[Value]) -> MoltResult {
    check_args(1, argv, 3, 3, "test command")?;

    // Here we evaluate the test expression as a boolean.  Any errors are propagated.
    while interp.expr_bool(&argv[1])? {
        // Here we evaluate the loop's body.
        let result = interp.eval_value(&argv[2]);

        if let Err(exception) = result {
            match exception.code() {
                // They want to break; so break out of the rust loop.
                ResultCode::Break => break,

                // They want to continue; so continue with the next iteration.
                ResultCode::Continue => (),

                // It's some other exception; just propagate it.
                _ => return Err(exception),
            }
        }
    }

    // All is good, so return Ok!
    molt_ok!()
}
#}

See The MoltResult Type for more information.

Evaluating Expressions with expr and expr_bool.

Evaluating Molt expressions is similar. To get any expression result (usually a numeric or boolean Value), use the Interp::expr method.


# #![allow(unused_variables)]
#fn main() {
use molt::Interp;
use molt::types::*;
use molt::expr;

let mut interp = Interp::new();

...

let value: Value = interp.expr("1 + 1")?;
#}

Use Interp::expr_bool when a specifically boolean result is wanted:


# #![allow(unused_variables)]
#fn main() {
let flag: bool = interp.expr_bool("1 == 1")?;
#}

(See the expr command reference for more about Molt expressions.)

Providing an interactive REPL

An interactive user shell or "REPL" (Read-Eval-Print-Loop) can be a great convenience when developing and debugging application scripts; it can also be useful tool for administering server processes. To provide an interactive shell, use the molt_shell::repl function.

use molt::Interp;

// FIRST, create and initialize the interpreter.
let mut interp = Interp::new();

// NOTE: commands can be added to the interpreter here.

// NEXT, invoke the REPL.
molt_shell::repl(&mut interp);

The REPL's prompt can be set using the tcl_prompt1 variable to a script; see the molt shell documentation for an example.

Evaluating Script Files

To execute a user script file, one can load the file contents and use Interp::eval in the normal way, or use the molt_shell::script function. A shell application might execute a user script as follows. Any errors are output to the console.

use molt::Interp;
use std::env;

// FIRST, get the command line arguments.
let args: Vec<String> = env::args().collect();

// NEXT, create and initialize the interpreter.
let mut interp = Interp::new();

// NOTE: commands can be added to the interpreter here.

// NEXT, evaluate the file, if any.
if args.len() > 1 {
    molt_shell::script(&mut interp, &args[1..]);
} else {
    eprintln!("Usage: myshell filename.tcl");
}

Custom Shells

A custom Molt shell is simply an application that:

  • Creates a Molt interp
  • Adds any desired commands by the methods described in the previous section
  • Passes the interp to molt_shell::repl (for an interactive shell)
  • Passes the interp and a file to molt_shell::script

The sample Molt application provides a full example; here's a sketch:

fn main() {
    use std::env;

    // FIRST, get the command line arguments.
    let args: Vec<String> = env::args().collect();

    // NEXT, create and initialize the interpreter.
    let mut interp = Interp::new();

    // NOTE: commands can be added to the interpreter here, e.g.,

    // Add a single module
    interp.add_command("hello", cmd_hello);

    // Install a Molt extension crate
    molt_sample::install(&mut interp).expect("Could not install.");

    // NEXT, evaluate the file, if any.
    if args.len() > 1 {
        molt_shell::script(&mut interp, &args[1..]);
    } else {
        molt_shell::repl(&mut interp);
    }
}

pub fn cmd_hello(_interp: &mut Interp,  _: ContextID, argv: &[Value]) -> MoltResult {
    // Correct number of arguments?
    check_args(1, argv, 2, 2, "name")?;

    println!("Hello, {}", argv[1].as_str());
    molt_ok!()
}

Molt Library Crates

A Molt library crate is simply a Rust crate that can install commands into a Molt interpreter using any of the methods described in this chapter. For example, a crate might provide an install function:


# #![allow(unused_variables)]
#fn main() {
use molt::Interp

pub fn install(interp: &mut Interp) {
    interp.add_command("mycommand", mycommand);
    ...
}
#}