Skip to content

Commit

Permalink
experiment: rhai script error handling
Browse files Browse the repository at this point in the history
  • Loading branch information
justinpombrio committed Apr 16, 2024
1 parent 4942ca4 commit 0f6481c
Showing 1 changed file with 123 additions and 12 deletions.
135 changes: 123 additions & 12 deletions examples/rhai/script_loop.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use rhai::{CustomType, Engine, FnPtr, FuncRegistration, Module, Scope, TypeBuilder};
use rhai::{
CustomType, Dynamic, Engine, EvalAltResult, FnPtr, FuncRegistration, Module, Position, Scope,
TypeBuilder,
};
use std::cell::RefCell;
use std::collections::HashMap;
use std::io::stdin;
Expand All @@ -10,11 +13,40 @@ use std::rc::Rc;
// x close_menu=true close_menu; switch_buffer
// x close_menu=false inc_font_size; block
// x chain menus
// x fatal errors
// x non fatal errors

#[derive(Clone)]
struct EditorError {
is_fatal: bool,
message: String,
}

impl CustomType for EditorError {
fn build(mut builder: TypeBuilder<Self>) {
builder
.with_name("EditorError")
.with_get("is_fatal", |err: &mut EditorError| -> bool { err.is_fatal })
.with_get("message", |err: &mut EditorError| -> String {
err.message.clone()
});
}
}

impl From<EditorError> for Box<EvalAltResult> {
fn from(editor_error: EditorError) -> Self {
Box::new(EvalAltResult::ErrorRuntime(
Dynamic::from(editor_error),
Position::NONE,
))
}
}

#[derive(Debug, Default)]
struct Runtime {
keymaps: HashMap<String, HashMap<char, KeyProg>>,
active_menu: String,
count: u64,
}

#[derive(Debug, Clone)]
Expand All @@ -26,7 +58,7 @@ struct KeyProg {
impl CustomType for KeyProg {
fn build(mut builder: TypeBuilder<Self>) {
builder
.with_name("Runtime")
.with_name("KeyProg")
.with_get_set(
"prog",
|kp: &mut KeyProg| -> FnPtr { kp.prog.clone() },
Expand All @@ -49,8 +81,17 @@ impl Runtime {
self.keymaps.get_mut(keymap).unwrap().insert(key, keyprog);
}

fn open_menu(&mut self, menu: &str) {
fn open_menu(&mut self, menu: &str) -> Result<(), Box<EvalAltResult>> {
if !self.keymaps.contains_key(menu) {
return Err(EditorError {
is_fatal: true,
message: format!("Unknown menu name: {}", menu),
}
.into());
}

self.active_menu = menu.to_owned();
Ok(())
}

fn close_menu(&mut self) {
Expand Down Expand Up @@ -90,6 +131,28 @@ impl Runtime {
fn exit(&self) {
process::exit(0);
}

fn increment(&mut self) -> u64 {
self.count += 1;
self.count
}

fn decrement(&mut self) -> Result<u64, Box<EvalAltResult>> {
if self.count == 0 {
Err(EditorError {
is_fatal: false,
message: "decrement".to_owned(),
}
.into())
} else {
self.count -= 1;
Ok(self.count)
}
}

fn count(&self) -> u64 {
self.count
}
}

macro_rules! register {
Expand All @@ -108,19 +171,25 @@ fn register_runtime_methods(module: &mut Module) {
let runtime = Rc::new(RefCell::new(Runtime {
keymaps: HashMap::new(),
active_menu: "Default".to_owned(),
count: 0,
}));

register!(module, runtime.open_menu(menu: &str));
register!(module, runtime.close_menu());
register!(module, runtime.bind_key(keymap: &str, key: char, close_menu: bool, prog: FnPtr));
register!(module, runtime.block_for_keyprog());
register!(module, runtime.exit());
register!(module, runtime.increment());
register!(module, runtime.decrement());
register!(module, runtime.count());
}

pub fn main() {
let mut engine = Engine::new();
engine.set_fail_on_invalid_map_property(true);

engine.build_type::<KeyProg>();
engine.build_type::<EditorError>();

println!("Signatures:");
engine
Expand All @@ -144,19 +213,32 @@ pub fn main() {
fn escape() {
throw `escape`;
}
fn allow_error(f) {
try {
call(f)
} catch (err) {
if !err.is_fatal {
let msg = err.message;
print(`ignoring non-fatal error: ${msg}`);
} else {
throw err;
}
}
}
";

let init_script = "
// Default Menu
s::bind_key(`Default`, 'c', true, || s::open_menu(`Counter`));
s::bind_key(`Default`, 'c', true, || s::open_menu(`ClosureCounter`));
s::bind_key(`Default`, 'i', true, || {
s::open_menu(`Node`);
let node = block();
// return to main loop
print(` Inserting node of type ${node}`);
});
s::bind_key(`Default`, 'r', true, || {
s::open_menu(`Counter`);
s::open_menu(`ClosureCounter`);
let count = block();
s::open_menu(`Node`);
let node = block();
Expand All @@ -167,15 +249,33 @@ pub fn main() {
s::bind_key(`Default`, 'q', true, || s::escape());
s::bind_key(`Default`, 'e', true, || s::exit());
// Counter Menu
s::bind_key(`Default`, 'n', true, || s::open_menu(`RuntimeCounter`));
// ClosureCounter Menu
let count = 1;
s::bind_key(`Counter`, 'a', false, || {
s::bind_key(`ClosureCounter`, 'a', false, || {
print(` a pressed ${count} times`);
count += 1;
});
s::bind_key(`Counter`, 'd', true, || count);
s::bind_key(`Counter`, 'q', true, || s::escape());
s::bind_key(`Counter`, 'e', true, || s::exit());
s::bind_key(`ClosureCounter`, 'd', true, || count);
s::bind_key(`ClosureCounter`, 'q', true, || s::escape());
s::bind_key(`ClosureCounter`, 'e', true, || s::exit());
// RuntimeCounter Menu
s::bind_key(`RuntimeCounter`, '+', false, || {
print(s::increment())
});
s::bind_key(`RuntimeCounter`, '-', false, || {
allow_error( || print(s::decrement()));
});
s::bind_key(`RuntimeCounter`, 'm', false, || {
print(s::decrement())
});
s::bind_key(`RuntimeCounter`, 'd', true, || s::count());
s::bind_key(`RuntimeCounter`, 'q', true, || s::escape());
s::bind_key(`RuntimeCounter`, 'x', true, || s::open_menu(`BadMenu`));
s::bind_key(`RuntimeCounter`, 'X', true, || allow_error(|| s::open_menu(`BadMenu`)));
s::bind_key(`RuntimeCounter`, '!', true, || [][17]);
// Node Selection Menu
s::bind_key(`Node`, 'a', true, || `Array`);
Expand All @@ -187,8 +287,18 @@ pub fn main() {
loop {
try {
s::block(); // ignoring return value
} catch (exc) {
print(` Exception ${exc}!`);
} catch (err) {
if type_of(err) == `EditorError` {
let msg = err.message;
print(` Editor Error: ${msg}!`);
} else {
let msg = if `message` in err {
err.message
} else {
err
};
print(` Exception: ${msg}!`);
}
s::close_menu();
}
}
Expand All @@ -198,6 +308,7 @@ pub fn main() {
let mut prelude_module = Module::eval_ast_as_new(Scope::new(), &prelude_ast, &engine).unwrap();
register_runtime_methods(&mut prelude_module);
engine.register_static_module("s", prelude_module.into());
engine.set_strict_variables(true);

engine.run(init_script).unwrap();
engine.run(main_script).unwrap();
Expand Down

0 comments on commit 0f6481c

Please sign in to comment.