From f9919a40aa3a600962cd6e04d179ea4244db7320 Mon Sep 17 00:00:00 2001 From: Compey Date: Wed, 9 Aug 2023 21:49:42 +0530 Subject: [PATCH] feat: initial context preservation This allows the REPL to now preserve context, such as previously declared variables which can be used in future eval steps. Currently is only supported for variables, support for other types to be included soon. --- Cargo.lock | 26 ++++++++++++++++++++ Cargo.toml | 1 + src/cli/repl.rs | 64 ++++++++++++++++++++++++++++++++++++++++--------- 3 files changed, 80 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e5ee426..76066a2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -198,6 +198,21 @@ version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + [[package]] name = "bitflags" version = "1.3.2" @@ -634,6 +649,16 @@ dependencies = [ "str-buf", ] +[[package]] +name = "fancy-regex" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b95f7c0680e4142284cf8b22c14a476e87d61b004a3a0861872b32ef7ead40a2" +dependencies = [ + "bit-set", + "regex", +] + [[package]] name = "fastrand" version = "2.0.0" @@ -1087,6 +1112,7 @@ dependencies = [ "directories", "dunce", "env_logger 0.10.0", + "fancy-regex", "futures-util", "glam", "home", diff --git a/Cargo.toml b/Cargo.toml index 092677d..fca4bdb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -132,6 +132,7 @@ regex = { optional = true, version = "1.7", default-features = false, features = "std", "unicode-perl", ] } +fancy-regex = "0.11.0" rustyline = "12.0.0" ### ROBLOX diff --git a/src/cli/repl.rs b/src/cli/repl.rs index 1fdd90c..544670d 100644 --- a/src/cli/repl.rs +++ b/src/cli/repl.rs @@ -1,5 +1,6 @@ use std::{ env, + fmt::Write, io::ErrorKind, path::PathBuf, process::{exit, ExitCode}, @@ -7,19 +8,17 @@ use std::{ use anyhow::Error; use clap::Command; -use lune::{Lune, LuneError}; +use fancy_regex::Regex; +use lune::lua::stdio::formatting::{pretty_format_luau_error, pretty_format_value}; +use lune::Lune; use mlua::ExternalError; use once_cell::sync::Lazy; use rustyline::{error::ReadlineError, history::FileHistory, DefaultEditor, Editor}; -use lune::lua::stdio::formatting::pretty_format_luau_error; - fn env_var_bool(value: String) -> Option { match value.to_lowercase().as_str() { - "true" => Some(true), - "1" => Some(true), - "0" => Some(false), - "false" => Some(false), + "true" | "1" => Some(true), + "false" | "0" => Some(false), &_ => None, } } @@ -73,18 +72,36 @@ pub async fn show_interface(cmd: Command) -> Result { let no_color = env::var("NO_COLOR").unwrap_or_else(|_| "false".to_string()); if no_color.is_empty() { - false + true } else { !env_var_bool(no_color).unwrap_or_else(|| false) } }); + // Group 2 of this match pattern is the variable contents + + // We're using fancy_regex instead of regex for backreferences + // and lookaround. I'm not too good at regex, so if there's a + // way to do this without backreferences and lookarounds, + // please let me know. + const VARIABLE_DECLARATION_PAT: &str = r#"(?!local.*)(?!\s)(=\s*)(.*)"#; + + // HACK: Prepend this "context" to the source code provided, + // so that the variable is preserved even the following steps + let mut source_code_context: Option = None; + loop { let mut source_code = String::new(); match repl.readline("> ") { Ok(code) => { - source_code = code.clone(); + if let Some(ref ctx) = source_code_context { + // If something breaks, blame this + source_code = format!("{} {}", ctx, code); + } else { + source_code.push_str(code.as_str()); + } + repl.add_history_entry(code.as_str())?; // If source code eval was requested, we reset the counter @@ -117,10 +134,35 @@ pub async fn show_interface(cmd: Command) -> Result { } }; - let eval_result = lune_instance.run("REPL", source_code).await; + let eval_result = lune_instance.run("REPL", source_code.clone()).await; match eval_result { - Ok(_) => (), + Ok(_) => { + if Regex::new(VARIABLE_DECLARATION_PAT)?.is_match(&source_code)? + && !&source_code.contains("\n") + { + let declaration = source_code.split("=").collect::>()[1] + .trim() + .replace("\"", ""); + + let mut formatted_output = String::new(); + pretty_format_value( + &mut formatted_output, + &mlua::IntoLua::into_lua(declaration, &mlua::Lua::new())?, + 1, + )?; + + source_code_context = (|| -> Result, Error> { + let mut ctx = String::new(); + write!(&mut ctx, "{}\n", &source_code)?; + + Ok(Some(ctx)) + })()?; + + println!("{}", formatted_output); + } + } + Err(err) => { eprintln!( "{}",