From 9418d7b0c821179094d1072a46c97b983b427654 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 8 Jul 2021 14:02:38 +0800 Subject: [PATCH] Add no_std_repl sample. --- no_std/no_std_repl/Cargo.toml | 38 ++++++ no_std/no_std_repl/README.md | 20 +++ no_std/no_std_repl/src/main.rs | 214 +++++++++++++++++++++++++++++++++ 3 files changed, 272 insertions(+) create mode 100644 no_std/no_std_repl/Cargo.toml create mode 100644 no_std/no_std_repl/README.md create mode 100644 no_std/no_std_repl/src/main.rs diff --git a/no_std/no_std_repl/Cargo.toml b/no_std/no_std_repl/Cargo.toml new file mode 100644 index 00000000..36e9c2f0 --- /dev/null +++ b/no_std/no_std_repl/Cargo.toml @@ -0,0 +1,38 @@ +cargo-features = ["named-profiles"] + +[workspace] + +[package] +name = "no_std_repl" +version = "0.1.0" +edition = "2018" +authors = ["Stephen Chung"] +description = "no-std REPL application" +homepage = "https://github.com/rhaiscript/rhai/tree/no_std/no_std_repl" +repository = "https://github.com/rhaiscript/rhai" + +[dependencies] +rhai = { path = "../../", default_features = false, features = [ "no_std", "decimal", "metadata" ] } +wee_alloc = { version = "0.4.5", default_features = false } + +[profile.dev] +panic = "abort" + +[profile.release] +opt-level = "z" # optimize for size +debug = false +rpath = false +debug-assertions = false +codegen-units = 1 +panic = "abort" + +[profile.unix] +inherits = "release" +lto = true + +[profile.windows] +inherits = "release" + +[profile.macos] +inherits = "release" +lto = "fat" diff --git a/no_std/no_std_repl/README.md b/no_std/no_std_repl/README.md new file mode 100644 index 00000000..521111d8 --- /dev/null +++ b/no_std/no_std_repl/README.md @@ -0,0 +1,20 @@ +`no-std` REPL Sample +==================== + +This sample application is the same version as the `rhai-repl` tool, compiled for `no-std`. + +[`wee_alloc`](https://crates.io/crates/wee_alloc) is used as the allocator. + + +To Compile +---------- + +The nightly compiler is required: + +```bash +cargo +nightly build --profile unix -Z unstable-options +``` + +Available profiles are: `unix`, `windows` and `macos`. + +The release build is optimized for size. It can be changed to optimize on speed instead. diff --git a/no_std/no_std_repl/src/main.rs b/no_std/no_std_repl/src/main.rs new file mode 100644 index 00000000..9a4b5e7f --- /dev/null +++ b/no_std/no_std_repl/src/main.rs @@ -0,0 +1,214 @@ +use rhai::{Dynamic, Engine, EvalAltResult, Scope, AST}; + +use std::{ + env, + io::{stdin, stdout, Write}, +}; + +/// Pretty-print error. +fn print_error(input: &str, mut err: EvalAltResult) { + let lines: Vec<_> = input.trim().split('\n').collect(); + let pos = err.take_position(); + + let line_no = if lines.len() > 1 { + if pos.is_none() { + "".to_string() + } else { + format!("{}: ", pos.line().unwrap()) + } + } else { + "".to_string() + }; + + // Print error position + if pos.is_none() { + // No position + println!("{}", err); + } else { + // Specific position - print line text + println!("{}{}", line_no, lines[pos.line().unwrap() - 1]); + + // Display position marker + println!( + "{0:>1$} {2}", + "^", + line_no.len() + pos.position().unwrap(), + err + ); + } +} + +/// Print help text. +fn print_help() { + println!("help => print this help"); + println!("quit, exit => quit"); + println!("scope => print all variables in the scope"); + println!("functions => print all functions defined"); + println!("json => output all functions in JSON format"); + println!("ast => print the last AST (optimized)"); + println!("astu => print the last raw, un-optimized AST"); + println!(r"end a line with '\' to continue to the next line."); + println!(); +} + +fn main() { + let title = format!("Rhai REPL tool (version {})", env!("CARGO_PKG_VERSION")); + println!("{}", title); + println!("{0:=<1$}", "", title.len()); + print_help(); + + // Initialize scripting engine + let mut engine = Engine::new(); + + // Setup Engine + #[cfg(not(feature = "no_optimize"))] + engine.set_optimization_level(rhai::OptimizationLevel::None); + + // Make Engine immutable + let engine = engine; + + // Create scope + let mut scope = Scope::new(); + + // REPL loop + let mut input = String::new(); + let mut main_ast: AST = Default::default(); + let mut ast_u: AST = Default::default(); + let mut ast: AST = Default::default(); + + 'main_loop: loop { + print!("rhai-repl> "); + stdout().flush().expect("couldn't flush stdout"); + + input.clear(); + + loop { + match stdin().read_line(&mut input) { + Ok(0) => break 'main_loop, + Ok(_) => (), + Err(err) => panic!("input error: {}", err), + } + + let line = input.as_str().trim_end(); + + // Allow line continuation + if line.ends_with('\\') { + let len = line.len(); + input.truncate(len - 1); + input.push('\n'); + } else { + break; + } + + print!("> "); + stdout().flush().expect("couldn't flush stdout"); + } + + let script = input.trim(); + + if script.is_empty() { + continue; + } + + // Implement standard commands + match script { + "help" => { + print_help(); + continue; + } + "exit" | "quit" => break, // quit + "scope" => { + scope + .iter_raw() + .enumerate() + .for_each(|(i, (name, constant, value))| { + #[cfg(not(feature = "no_closure"))] + let value_is_shared = if value.is_shared() { " (shared" } else { "" }; + #[cfg(feature = "no_closure")] + let value_is_shared = ""; + + println!( + "[{}] {}{}{} = {:?}", + i + 1, + if constant { "const " } else { "" }, + name, + value_is_shared, + *value.read_lock::().unwrap(), + ) + }); + println!(); + continue; + } + "astu" => { + // print the last un-optimized AST + println!("{:#?}\n", ast_u); + continue; + } + "ast" => { + // print the last AST + println!("{:#?}\n", ast); + continue; + } + "functions" => { + // print a list of all registered functions + engine + .gen_fn_signatures(false) + .into_iter() + .for_each(|f| println!("{}", f)); + + #[cfg(not(feature = "no_function"))] + main_ast.iter_functions().for_each(|f| println!("{}", f)); + + println!(); + continue; + } + "json" => { + println!( + "{}", + engine + .gen_fn_metadata_with_ast_to_json(&main_ast, true) + .unwrap() + ); + continue; + } + _ => (), + } + + match engine + .compile_with_scope(&scope, &script) + .map_err(Into::into) + .and_then(|r| { + ast_u = r.clone(); + + #[cfg(not(feature = "no_optimize"))] + { + ast = engine.optimize_ast(&scope, r, rhai::OptimizationLevel::Simple); + } + + #[cfg(feature = "no_optimize")] + { + ast = r; + } + + // Merge the AST into the main + main_ast += ast.clone(); + + // Evaluate + engine.eval_ast_with_scope::(&mut scope, &main_ast) + }) { + Ok(result) if !result.is::<()>() => { + println!("=> {:?}", result); + println!(); + } + Ok(_) => (), + Err(err) => { + println!(); + print_error(&input, *err); + println!(); + } + } + + // Throw away all the statements, leaving only the functions + main_ast.clear_statements(); + } +}