This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

mlang / native / mlang_repl / src / main.rs
3.8 kB 130 lines
1use rustyline::error::ReadlineError; 2use rustyline::DefaultEditor; 3use std::env; 4use std::path::{Path, PathBuf}; 5use std::process::{Command, ExitCode}; 6 7fn repo_root() -> Result<PathBuf, String> { 8 env::current_dir().map_err(|err| format!("failed to get current directory: {err}")) 9} 10 11fn mlang_binary(root: &Path) -> PathBuf { 12 root.join(".lake").join("build").join("bin").join("mlang") 13} 14 15fn eval(binary: &Path, input: &str) -> Result<(), String> { 16 let output = Command::new(binary) 17 .arg("--eval") 18 .arg(input) 19 .output() 20 .map_err(|err| format!("failed to run {}: {err}", binary.display()))?; 21 22 if !output.stdout.is_empty() { 23 print!("{}", String::from_utf8_lossy(&output.stdout)); 24 } 25 if !output.stderr.is_empty() { 26 eprint!("{}", String::from_utf8_lossy(&output.stderr)); 27 } 28 29 if output.status.success() { 30 Ok(()) 31 } else { 32 Err(format!("mlang exited with {}", output.status)) 33 } 34} 35 36fn parse_bare_let_binding(input: &str) -> Option<(&str, &str)> { 37 let rest = input.strip_prefix("let ")?; 38 let eq_idx = rest.find('=')?; 39 let name = rest[..eq_idx].trim(); 40 if name.is_empty() 41 || !name 42 .chars() 43 .all(|c| c == '_' || c.is_ascii_alphanumeric()) 44 || !name 45 .chars() 46 .next() 47 .is_some_and(|c| c == '_' || c.is_ascii_alphabetic()) 48 { 49 return None; 50 } 51 52 let value = rest[eq_idx + 1..].trim(); 53 if value.is_empty() || input.contains(';') { 54 return None; 55 } 56 57 Some((name, value)) 58} 59 60fn render_repl_input(prelude: &[String], input: &str) -> String { 61 let body = match parse_bare_let_binding(input) { 62 Some((name, value)) => format!("let {name} = {value} in {name}"), 63 None => input.to_owned(), 64 }; 65 66 if prelude.is_empty() { 67 body 68 } else { 69 let mut rendered = prelude 70 .iter() 71 .rev() 72 .fold(body, |acc, binding| format!("{binding} in {acc}")); 73 rendered.shrink_to_fit(); 74 rendered 75 } 76} 77 78fn main() -> ExitCode { 79 let root = match repo_root() { 80 Ok(root) => root, 81 Err(err) => { 82 eprintln!("{err}"); 83 return ExitCode::FAILURE; 84 } 85 }; 86 let binary = mlang_binary(&root); 87 if !binary.is_file() { 88 eprintln!("missing evaluator binary at {}", binary.display()); 89 return ExitCode::FAILURE; 90 } 91 92 let mut rl = match DefaultEditor::new() { 93 Ok(rl) => rl, 94 Err(err) => { 95 eprintln!("failed to initialize line editor: {err}"); 96 return ExitCode::FAILURE; 97 } 98 }; 99 100 println!("mlang REPL. enter :quit to exit."); 101 let mut prelude: Vec<String> = Vec::new(); 102 loop { 103 match rl.readline("mlang> ") { 104 Ok(line) => { 105 let input = line.trim(); 106 if input.is_empty() { 107 continue; 108 } 109 if input == ":quit" || input == ":q" { 110 return ExitCode::SUCCESS; 111 } 112 let _ = rl.add_history_entry(input); 113 let rendered = render_repl_input(&prelude, input); 114 if eval(&binary, &rendered).is_ok() { 115 if let Some((name, value)) = parse_bare_let_binding(input) { 116 prelude.push(format!("let {name} = {value}")); 117 } 118 } 119 } 120 Err(ReadlineError::Interrupted) | Err(ReadlineError::Eof) => { 121 println!(); 122 return ExitCode::SUCCESS; 123 } 124 Err(err) => { 125 eprintln!("readline error: {err}"); 126 return ExitCode::FAILURE; 127 } 128 } 129 } 130}