Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Your first host

Everything so far was about writing Crash BASIC. This part is about the other side: putting me inside a program of your own, so that your users can write it.

The program I live in is called the host. I’m a Rust library, and a small one: I read scripts, check them, and run them. Everything a script can actually do — draw, make a noise, move a character, save a file — is yours to give me. Out of the box I don’t even have PRINT.

That’s the whole idea. A game engine gives me sprites and sound. A level editor gives me tiles. A robot gives me motors. The language stays the same and the words change.

Getting me

I’m a Rust crate called crashbasic. Point Cargo at wherever you keep me:

[dependencies]
crashbasic = { path = "../crashbasic_interpreter" }

I need Rust 1.88 or newer, and I bring very little with me: a parser library, and serde for writing my reference as JSON. I build for desktop, phones and the web.

The smallest host

    use crashbasic::{Engine, Source, Value};

    let mut engine = Engine::new();

    // A function is its declaration, written as a script would write it,
    // and a closure for its body.
    engine.function("/// Writes its values out.\nFUNCTION write_line(values...)", move |call| {
        if let Value::Array(values) = &call.args[0] {
            let shown: Vec<String> = values.read().unwrap().iter().map(Value::to_string).collect();
            lines.lock().unwrap().push(shown.join(" "));
        }
        Value::Null
    })?;
    // With `typed`, the closure takes Rust types and I check the arguments.
    engine.typed("/// Twice a number.\nFUNCTION double(n AS INTEGER) AS INTEGER", |n: i64| n * 2)?;
    // A keyword is a word bound to a function.
    engine.bind("BIND PRINT write_line")?;

    let script = engine.load(&Source {
        file: "hello.crash".to_string(),
        text: "PRINT \"twice 21 is\", double(21)".to_string(),
    })?;
    script.run_all(10_000)?;

Four things happened there, and they’re the four things every host does.

  1. Made an engine. An Engine holds everything you give me. You set it up once.
  2. Gave me functions. Each is a declaration, written exactly the way a script would write it, and a Rust closure to run when it’s called.
  3. Bound a keyword. BIND PRINT write_line is the same line a script would write. It gives the function the word scripts will use for it.
  4. Loaded a script and ran it. Loading is where I check everything. If load gives you a script, every name and keyword in it is real.

The script itself is one line:

PRINT "twice 21 is", double(21)

What I do, and what you do

I doYou do
Read scripts and check them before they runDecide what words scripts get
Run them, in slices, on as many threads as you give meDecide when each thread runs, and for how long
Keep values, and free them when nothing holds themDo everything that touches the world outside
Say what went wrong, in words a person can useShow that to the person
Document every function and keyword you declareWrite one line of description above each

The rest of this part takes those one at a time.

Crash-tan’s Tip: The test harness that comes with me, in harness/, is a complete small host: PRINT, FOR, timers, a window to draw in, a debugger console. When you want to see how something here looks in a real program, it’s all there in one file.