diff --git a/vstc/src/db_command.rs b/vstc/src/db_command.rs index 755da9b..4adb5c2 100644 --- a/vstc/src/db_command.rs +++ b/vstc/src/db_command.rs @@ -1,4 +1,4 @@ -use std::{process::exit, rc::Rc}; +use std::{io::Write, process::exit, rc::Rc}; use storage::{storage_head_ptr, SledBackend, Storage, StorageReader}; use valuescript_compiler::{asm, assemble, compile_str}; @@ -9,6 +9,7 @@ use valuescript_vm::{ use crate::{ handle_diagnostics_cli::handle_diagnostics_cli, + parse_command_line::parse_command_line, to_bytecode::{format_from_path, to_bytecode}, }; @@ -39,15 +40,16 @@ pub fn db_command(args: &[String]) { } }; + let mut storage = Storage::new(SledBackend::open(path).unwrap()); + match args.get(3).map(|s| s.as_str()) { - Some("new") => db_new(&path, args.get(4..).unwrap_or_default()), - Some("call") => db_call(&path, args.get(4..).unwrap_or_default()), - Some("-i") => println!("TODO: use database {} interactively", path), + Some("new") => db_new(&mut storage, args.get(4..).unwrap_or_default()), + Some("call") => db_call(&mut storage, args.get(4..).unwrap_or_default()), + Some("-i") => db_interactive(&mut storage), arg => 'b: { if let Some(arg) = arg { - // TODO: Document variations here - if arg.starts_with("this.") || arg.starts_with('{') || arg.starts_with('(') { - break 'b db_run_inline(&path, arg); + if arg.starts_with('{') || arg.starts_with('(') { + break 'b db_run_inline(&mut storage, arg); } } @@ -70,17 +72,20 @@ fn show_help() { println!(" help, -h, --help Show this message"); println!(" new [CLASS_FILE] [ARGS] Create a new database"); println!(" call [FN_FILE] [ARGS] Call a function on the database"); - println!(" 'this.[CODE]' Run inline code within the database context"); + println!(" '([EXPRESSION])' Run expression with database as `this`"); + println!(" '{{[FN BODY]}}' Run code block with database as `this`"); println!(" -i Enter interactive mode"); println!(); println!("Examples:"); println!(" vstc db path/widget.vsdb new Widget.ts Create a new widget database"); println!(" vstc db path/widget.vsdb call useWidget.ts Call useWidget.ts on the widget"); - println!(" vstc db path/widget.vsdb 'this.info()' Call info method"); + println!(" vstc db path/widget.vsdb '(this.info())' Call info method"); + println!(" vstc db path/widget.vsdb '{{ const t = this; return t.info(); }}'"); + println!(" Call info method (enforcing read-only)"); println!(" vstc db path/widget.vsdb -i Enter interactive mode"); } -fn db_new(path: &str, args: &[String]) { +fn db_new(storage: &mut Storage, args: &[String]) { let class_file = match args.get(0) { Some(class_file) => class_file, None => { @@ -121,16 +126,14 @@ fn db_new(path: &str, args: &[String]) { } }; - let mut storage = Storage::new(SledBackend::open(path).unwrap()); - storage .set_head(storage_head_ptr(b"state"), &instance) .unwrap(); - println!("Created database at {}", path); + println!("Created database"); } -fn db_call(path: &str, args: &[String]) { +fn db_call(storage: &mut Storage, args: &[String]) { let fn_file = match args.get(0) { Some(fn_file) => fn_file, None => { @@ -151,8 +154,6 @@ fn db_call(path: &str, args: &[String]) { .map(|s| s.clone().to_val()) .collect::>(); - let mut storage = Storage::new(SledBackend::open(path).unwrap()); - let mut vm = VirtualMachine::default(); let mut instance = storage @@ -175,9 +176,7 @@ fn db_call(path: &str, args: &[String]) { .unwrap(); } -fn db_run_inline(path: &str, source: &str) { - let mut storage = Storage::new(SledBackend::open(path).unwrap()); - +fn db_run_inline(storage: &mut Storage, source: &str) { let mut vm = VirtualMachine::default(); let mut instance = storage @@ -186,7 +185,7 @@ fn db_run_inline(path: &str, source: &str) { .unwrap(); let full_source = { - if source.starts_with("this.") || source.starts_with('(') { + if source.starts_with('(') { format!("export default function() {{ return (\n {}\n); }}", source) } else if source.starts_with('{') { format!("export default function() {}", source) @@ -224,3 +223,30 @@ fn db_run_inline(path: &str, source: &str) { .set_head(storage_head_ptr(b"state"), &instance) .unwrap(); } + +fn db_interactive(storage: &mut Storage) { + loop { + let mut input = String::new(); + + print!("> "); + std::io::stdout().flush().unwrap(); + std::io::stdin().read_line(&mut input).unwrap(); + input.pop(); + + let args = parse_command_line(&input); + + match args.get(0).map(|s| s.as_str()) { + // TODO: help (it's a bit different - code isn't quoted (TODO: quoted should work too)) + Some("exit" | "quit") => break, + Some("new") => db_new(storage, args.get(1..).unwrap_or_default()), + Some("call") => db_call(storage, args.get(1..).unwrap_or_default()), + _ => 'b: { + if input.starts_with('{') || input.starts_with('(') { + break 'b db_run_inline(storage, &input); + } + + println!("ERROR: Unrecognized db command {:?}\n", args); + } + } + } +} diff --git a/vstc/src/main.rs b/vstc/src/main.rs index 2b4151a..9b53996 100644 --- a/vstc/src/main.rs +++ b/vstc/src/main.rs @@ -2,6 +2,7 @@ mod assemble_command; mod compile_command; mod db_command; mod handle_diagnostics_cli; +mod parse_command_line; mod resolve_entry_path; mod run_command; mod test_inputs; diff --git a/vstc/src/parse_command_line.rs b/vstc/src/parse_command_line.rs new file mode 100644 index 0000000..071c7d6 --- /dev/null +++ b/vstc/src/parse_command_line.rs @@ -0,0 +1,55 @@ +pub fn parse_command_line(input: &str) -> Vec { + let mut args = Vec::new(); + let mut current_arg = String::new(); + let mut in_single_quote = false; + let mut in_double_quote = false; + let mut escape_next_char = false; // To handle escaping of characters + + for c in input.chars() { + if escape_next_char { + current_arg.push(c); + escape_next_char = false; + continue; + } + + match c { + ' ' if !in_single_quote && !in_double_quote => { + if !current_arg.is_empty() { + args.push(current_arg.clone()); + current_arg.clear(); + } + } + '\'' if !in_double_quote => { + if !in_single_quote || !current_arg.ends_with('\\') { + in_single_quote = !in_single_quote; + } else { + current_arg.pop(); // Remove the escape character + current_arg.push('\''); // Add the literal quote + } + } + '"' if !in_single_quote => { + if !in_double_quote || !current_arg.ends_with('\\') { + in_double_quote = !in_double_quote; + } else { + current_arg.pop(); // Remove the escape character + current_arg.push('"'); // Add the literal quote + } + } + '\\' if in_single_quote || in_double_quote => escape_next_char = true, + _ => { + current_arg.push(c); + } + } + } + + if escape_next_char { + // If the input ends with an unprocessed escape character, add it to the argument + current_arg.push('\\'); + } + + if !current_arg.is_empty() { + args.push(current_arg); + } + + args +}