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.
- Made an engine. An
Engineholds everything you give me. You set it up once. - 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.
- Bound a keyword.
BIND PRINT write_lineis the same line a script would write. It gives the function the word scripts will use for it. - Loaded a script and ran it. Loading is where I check everything. If
loadgives 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 do | You do |
|---|---|
| Read scripts and check them before they run | Decide what words scripts get |
| Run them, in slices, on as many threads as you give me | Decide when each thread runs, and for how long |
| Keep values, and free them when nothing holds them | Do everything that touches the world outside |
| Say what went wrong, in words a person can use | Show that to the person |
| Document every function and keyword you declare | Write 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: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.