TCL Rust and the Death of Rube Goldberg
TCL, Rust, and the Death of Rube Goldberg Will Duquette 26 th Annual Tcl/Tk Conference 7 November, 2019
“Life is too short to program in C on my own time. ” — Me, c. 2001 (and ever after)
Standard Ways of Using TCL (and C)
Add Convenience to C C T C L
Add Speed & Utility to TCL C C
“Plain” TCL + Some kind of interpreter, wrapper, kit system, … Are you sure you aren’t a virus?
Why Use C? Rube Goldberg implementing a cross-platform TCL extension in C. Memory Safety Cyber-security Ecosystem! Concurrency Cross-platform
Welcome to Bonaventure! A Dreary Clearing A wide spot in the woods. The trees are dense, but there seem to be paths heading to the north, south, and east. You see: note. You don't know where you are. You don't even know where you want to be. All you know is that your feet are wet, your hands are dirty, and gosh, this doesn’t look anything like the toy aisle. >
Why Rust? Performance Memory Safety Cyber-security Ecosystem! Concurrency Cross-platform Zero-Cost Abstractions
Text Adventure App Text Adventure Definition Text Adventure Compiler Text Adventure App Text Adventure Framework Time Text Adventure Engine
“Thou shalt not implement half-vast extension languages. ” — Me, afterwards. Every time.
Molt 0. 1. 1 % puts "Hello, world!" Hello, world! % expr {2+2} 4 % Molt: More or Less Tcl Goals: • An extension language • Small footprint • Modular • Excellent Rust API • Performant • Non-gratuitous consistency with Standard TCL
A Tour of Rust
fn append(s: String, suffix: str) { s. push_str(suffix); } fn main() { let text = String: : from(". . . "); text. push_str("some more; "); append(text, "even more"); // ". . . some more; even more" println!("text={}", text); }
fn append(s: String, suffix: str) { s. push_str(suffix); } fn main() { let text = String: : from(". . . "); text. push_str("some more; "); append(text, "even more"); // ". . . some more; even more" println!("text={}", text); }
fn append(s: String, suffix: str) { s. push_str(suffix); } fn main() { let mut text = String: : from(". . . "); text. push_str("some more; "); append(text, "even more"); // ". . . some more; even more" println!("text={}", text); }
fn append(s: String, suffix: str) { s. push_str(suffix); } fn main() { let mut text = String: : from(". . . "); text. push_str("some more; "); append(text, "even more"); // ". . . some more; even more" println!("text={}", text); }
fn append(s: mut String, suffix: str) { s. push_str(suffix); } fn main() { let mut text = String: : from(". . . "); text. push_str("some more; "); append(text, "even more"); // ". . . some more; even more" println!("text={}", text); }
fn append(s: mut String, suffix: str) { s. push_str(suffix); } fn main() { let mut text = String: : from(". . . "); text. push_str("some more; "); append(text, "even more"); // ". . . some more; even more" println!("text={}", text); }
fn append(s: &mut String, suffix: &str) { s. push_str(suffix); } fn main() { let mut text = String: : from(". . . "); text. push_str("some more; "); append(&text, "even more"); // ". . . some more; even more" println!("text={}", text); }
enum Colors { Red, Orange Yellow Green, Blue, Indigo, Violet, } let color = Colors: : Red;
enum Things { This(i 32), That(i 32, String), Nothing } let this = Things: : This(123); let that = Things: : That(5, "abc". into());
Value: Molt's two-legged stork let value = Value: : from(123); let s: &str = value. as_str(); let i: Molt. Int = value. as_int()? ; let lst: Rc<Molt. List> = value. as_list()? ;
struct Script { commands: Vec<Word. Vec> } struct Word. Vec { words: Vec<Word> } enum Word { Value(Value), // "123", "xyz" Var. Ref(String), // $x Script(Script), // [foo. . . ] Tokens(Vec<Word>), // "a $x [foo] b" String(String), // "a ", " b" }
Evaluating a Script: For each word_vec in commands: Convert the Words to an array of Values Look up variable values Evaluate interpolated scripts Look up and execute the named command Handle the return code (might return early) Return the script’s result.
// Convert a Word to a Value // (“self” is the molt: : Interp) let value = match word { Word: : Value(val) => val. clone(), Word: : Var. Ref(name) => self. var(name)? , Word: : Script(script) => self. eval_script(script)? , . . . elided. . . };
Defining Commands: C vs. Rust
int Tcl_Exit. Obj. Cmd(Client. Data dummy, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { int value; if ((objc != 1) && (objc != 2)) { Tcl_Wrong. Num. Args(interp, 1, objv, "? return. Code? "); return TCL_ERROR; } if (objc == 1) { value = 0; } else if (Tcl_Get. Int. From. Obj(interp, objv[1], &value) != TCL_OK) { return TCL_ERROR; } Tcl_Exit(value); /*NOTREACHED*/ return TCL_OK; } /* Better not ever reach this! */
An Oft-Repeated Pattern /* * Do something, setting the error result * on error. */ int code = do_something(. . . ); if (code != TCL_OK) { return code; } /* Do the next thing */. . .
enum Result<T, E> { Ok(T), Err(E), } // T: The successful result type // E: The error result type
fn sqrt(x: f 32) -> Result<f 32, String> { if x >= 0 { let y =. . . ; Result: : Ok(y) } else { Result: : Err("negative!". into()) } }
let value: f 32 =. . . ; // Pattern matching match sqrt(value) { Ok(r) => println!("root={}", r), Err(msg) => println!("err={}", msg), } // The “try” operator fn do_something() -> Result<_, String> { let root: f 32 = sqrt(value)? ; println!(“root={}”, root); }
type Molt. Result = Result<Value, Result. Code>; enum Result. Code { Error(Value), Return(Value), Break, Continue, }
fn cmd_exit(_interp: &mut Interp, argv: &[Value]) -> Molt. Result { check_args(1, argv, 1, 2, "? return. Code? ")? ; let return_code = if argv. len() == 1 { 0 } else { // as_int() -> Result<Molt. Int, Return. Code> argv[1]. as_int()? }; std: : process: : exit(return_code as i 32) }
Building Applications and Extensions
The Rust Ecosystem • rustup — installs the compiler and tools • cargo — the Rust build tool • Crates — Rust packages, library & binary • cargo. toml — The crate manifest file • crates. io — The Rust package repository
Creating an Application $ cargo new hello Created binary (application) `hello` package $ cd hello $ cargo run Compiling hello v 0. 1. 0 (/Users/will/hello) Finished dev [unoptimized + debuginfo] target(s) in 3. 71 s Running `target/debug/hello` Hello, world! $
// cargo. toml [package] name = "hello" version = "0. 1. 0" authors = ["Will Duquette <will@wjduquette. com>"] edition = "2018" [dependencies] // src/main. rs fn main() { println!("Hello, world!"); }
A Vanilla Molt Shell // molt-sample/cargo. toml [package] name = "molt-sample" version = "0. 1. 0" authors = ["wduquette <will@wjduquette. com>"] edition = "2018" [dependencies] molt = "0. 1. 0" molt-shell = "0. 1. 0"
// molt-sample/src/main. rs fn main() { use std: : env; let args: Vec<String> = env: : args(). collect(); let mut interp = Interp: : new(); // NOTE: add commands here // NEXT, evaluate the file, if any. if args. len() > 1 { molt_shell: : script(&mut interp, &args[1. . ]); } else { molt_shell: : repl(&mut interp, "% "); } }
// molt-sample/src/main. rs fn main() {. . . let mut interp = Interp: : new(); // NOTE: add commands here interp. add_command("square", cmd_square); . . . } pub fn cmd_square(_interp: &mut Interp, argv: &[Value]) -> Molt. Result { //. . . compute the square of a number }
A Molt Extension pub fn install(interp: &mut Interp) -> Molt. Result { interp. add_command("double", cmd_double); interp. eval(include_str!("lib. tcl")) } fn cmd_double(_interp: &mut Interp, argv: &[Value]) -> Molt. Result {. . . }
Bottom Line Using cargo, you can: • Build library crates that extend the Molt interpreter (binary TCL extensions) • Build library crates that include TCL code (starkits) • Build applications that include Molt, extensions, and TCL code (starpacks) • That can be distributed via crates. io • That run on Windows, Linux, mac. OS • And, with some extra work, WASM and embedded • (Molt doesn't support embedded at this time. )
Molt Details
Current Status • Script execution, interactive shell • True TCL syntax (less "{*}") • Lists and expressions • Procs and control structures • Test harness and test suite • Minimal command set
Current Lacks • Arrays • Dictionaries • Namespaces • Traces • Full "return" options and error handling • Lots of commands • Packages (of some kind) • Objects (of some kind)
What's Next (? ) • Parse expressions to internal form • {*} • Value type experiments • Stack traces • Arrays • Additional commands • Regex as optional feature • File handling as optional feature • Namespaces?
Help? • There's lots of low-hanging fruit.
References Bonaventure: https: //github. com/wduquette/bonaventure Molt: https: //github. com/wduquette/molt The Molt Book https: //wduquette. github. io/molt Molt Sample: https: //github. com/wduquette/molt-sample My programming blog: https: //wduquette. github. io/ The Rust Book: https: //doc. rust-lang. org/book/ The Cargo Book: https: //doc. rust-lang. org/cargo/ Crates. io: https: //crates. io Eric Raymond on alternatives to C: http: //esr. ibiblio. org/? p=7711
- Slides: 49