#[derive(Debug, Default)]
pub struct ReplOutcome {
pub new_lines: Vec<String>,
pub error: Option<String>,
}
#[derive(Debug, Default)]
pub struct ReplSession {
defs: Vec<String>,
stmts: Vec<String>,
emitted: usize,
argv: Vec<String>,
}
impl ReplSession {
pub fn new() -> Self {
Self { defs: Vec::new(), stmts: Vec::new(), emitted: 0, argv: vec!["repl".to_string()] }
}
pub async fn eval(&mut self, input: &str) -> ReplOutcome {
let trimmed = input.trim();
if trimmed.is_empty() || trimmed == "## Main" {
return ReplOutcome::default();
}
let is_def = trimmed.starts_with("## ");
if is_def {
self.defs.push(trimmed.to_string());
} else {
self.stmts.push(trimmed.to_string());
}
let program = self.source();
let result = crate::interpret_for_ui_with_args(&program, &self.argv).await;
let new_lines: Vec<String> =
result.lines.get(self.emitted..).map(|s| s.to_vec()).unwrap_or_default();
match result.error {
Some(err) => {
if is_def {
self.defs.pop();
} else {
self.stmts.pop();
}
ReplOutcome { new_lines, error: Some(err) }
}
None => {
self.emitted = result.lines.len();
ReplOutcome { new_lines, error: None }
}
}
}
pub fn eval_sync(&mut self, input: &str) -> ReplOutcome {
futures::executor::block_on(self.eval(input))
}
pub fn source(&self) -> String {
let mut out = String::new();
for def in &self.defs {
out.push_str(def);
out.push_str("\n\n");
}
out.push_str("## Main\n\n");
for stmt in &self.stmts {
out.push_str(stmt);
out.push('\n');
}
out
}
pub fn vars(&self) -> Vec<(String, String, String)> {
crate::ui_bridge::repl_global_bindings(&self.source(), &self.argv).unwrap_or_default()
}
pub fn binding_names(&self) -> Vec<String> {
let mut names: Vec<String> = Vec::new();
let mut push = |name: &str| {
let name = name.trim();
if !name.is_empty() && !names.iter().any(|n| n == name) {
names.push(name.to_string());
}
};
for chunk in self.defs.iter().chain(self.stmts.iter()) {
for line in chunk.lines() {
let t = line.trim_start();
if let Some(rest) = t.strip_prefix("Let ") {
if let Some((name, _)) = rest.split_once(" be") {
push(name);
}
} else if let Some(rest) = t.strip_prefix("## To ") {
let name: String = rest
.chars()
.take_while(|c| c.is_alphanumeric() || *c == '_')
.collect();
push(&name);
}
}
}
names
}
pub fn load_source(&mut self, src: &str) -> ReplOutcome {
self.reset();
let headerless = !src.lines().any(|l| l.starts_with("## "));
let mut current_def: Option<String> = None;
let mut main_body = String::new();
let mut in_main = headerless;
for line in src.lines() {
if line.trim_end() == "## Main" {
if let Some(def) = current_def.take() {
self.defs.push(def.trim().to_string());
}
in_main = true;
} else if line.starts_with("## ") {
if let Some(def) = current_def.take() {
self.defs.push(def.trim().to_string());
}
in_main = false;
current_def = Some(format!("{line}\n"));
} else if in_main {
main_body.push_str(line);
main_body.push('\n');
} else if let Some(def) = current_def.as_mut() {
def.push_str(line);
def.push('\n');
}
}
if let Some(def) = current_def.take() {
self.defs.push(def.trim().to_string());
}
let main_body = main_body.trim().to_string();
if !main_body.is_empty() {
self.stmts.push(main_body);
}
let program = self.source();
let result =
futures::executor::block_on(crate::interpret_for_ui_with_args(&program, &self.argv));
match result.error {
Some(err) => {
self.reset();
ReplOutcome { new_lines: Vec::new(), error: Some(err) }
}
None => {
self.emitted = result.lines.len();
ReplOutcome { new_lines: result.lines, error: None }
}
}
}
pub fn reset(&mut self) {
self.defs.clear();
self.stmts.clear();
self.emitted = 0;
}
}