diff --git a/bin/el/src/main.rs b/bin/el/src/main.rs index 483e3ff..74eb298 100644 --- a/bin/el/src/main.rs +++ b/bin/el/src/main.rs @@ -5738,6 +5738,109 @@ fn dispatch_builtin( BuiltinResult::NotBuiltin } + // ── List helpers (append / set / concat) ────────────────────────────── + "list_append" => { + // list_append(list, item) -> [item, ...] + let item = stack.pop().unwrap_or(Value::Nil); + let list = match stack.pop().unwrap_or(Value::List(vec![])) { Value::List(l) => l, _ => vec![] }; + let mut new_list = list; + new_list.push(item); + stack.push(Value::List(new_list)); BuiltinResult::Handled + } + "list_set" => { + // list_set(list, idx, value) -> new list with item at idx replaced + let value = stack.pop().unwrap_or(Value::Nil); + let idx = match stack.pop().unwrap_or(Value::Int(0)) { Value::Int(i) => i, _ => 0 }; + let list = match stack.pop().unwrap_or(Value::List(vec![])) { Value::List(l) => l, _ => vec![] }; + let mut new_list = list; + if idx >= 0 && (idx as usize) < new_list.len() { + new_list[idx as usize] = value; + } + stack.push(Value::List(new_list)); BuiltinResult::Handled + } + "list_concat" => { + // list_concat(a, b) -> combined list + let b = match stack.pop().unwrap_or(Value::List(vec![])) { Value::List(l) => l, _ => vec![] }; + let a = match stack.pop().unwrap_or(Value::List(vec![])) { Value::List(l) => l, _ => vec![] }; + let mut new_list = a; + new_list.extend(b); + stack.push(Value::List(new_list)); BuiltinResult::Handled + } + "list_slice" => { + // list_slice(list, start, end) -> sublist [start..end) + let end_idx = match stack.pop().unwrap_or(Value::Int(0)) { Value::Int(i) => i, _ => 0 }; + let start_idx = match stack.pop().unwrap_or(Value::Int(0)) { Value::Int(i) => i, _ => 0 }; + let list = match stack.pop().unwrap_or(Value::List(vec![])) { Value::List(l) => l, _ => vec![] }; + let len = list.len() as i64; + let s = start_idx.max(0) as usize; + let e = end_idx.min(len) as usize; + let sliced = if s < e && s < list.len() { list[s..e.min(list.len())].to_vec() } else { vec![] }; + stack.push(Value::List(sliced)); BuiltinResult::Handled + } + "list_reverse" => { + let list = match stack.pop().unwrap_or(Value::List(vec![])) { Value::List(l) => l, _ => vec![] }; + let mut rev = list; + rev.reverse(); + stack.push(Value::List(rev)); BuiltinResult::Handled + } + "list_contains" => { + // list_contains(list, item) -> Bool — string equality check + let item = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, other => other.to_string() }; + let list = match stack.pop().unwrap_or(Value::List(vec![])) { Value::List(l) => l, _ => vec![] }; + let found = list.iter().any(|v| { + let vs = match v { Value::Str(s) => s.clone(), other => other.to_string() }; + vs == item + }); + stack.push(Value::Bool(found)); BuiltinResult::Handled + } + + // ── String character mutation ────────────────────────────────────────── + "str_set_char" => { + // str_set_char(s, idx, ch) -> new string with ch at position idx + let ch = match stack.pop().unwrap_or(Value::Str(String::new())) { Value::Str(s) => s, _ => String::new() }; + let idx = match stack.pop().unwrap_or(Value::Int(0)) { Value::Int(i) => i, _ => 0 }; + let s = match stack.pop().unwrap_or(Value::Str(String::new())) { Value::Str(s) => s, _ => String::new() }; + let ch_char = ch.chars().next().unwrap_or(' '); + let mut chars: Vec = s.chars().collect(); + if idx >= 0 && (idx as usize) < chars.len() { + chars[idx as usize] = ch_char; + } + stack.push(Value::Str(chars.into_iter().collect())); BuiltinResult::Handled + } + + // ── Shell execution ─────────────────────────────────────────────────── + "shell_exec" => { + // shell_exec(cmd: String) -> String — execute shell command, return stdout+stderr + let cmd = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() }; + let output = std::process::Command::new("sh") + .arg("-c") + .arg(&cmd) + .output(); + let result = match output { + Ok(o) => { + let stdout = String::from_utf8_lossy(&o.stdout).to_string(); + let stderr = String::from_utf8_lossy(&o.stderr).to_string(); + if stdout.is_empty() { stderr } else { stdout } + } + Err(e) => format!("error: {}", e), + }; + stack.push(Value::Str(result)); BuiltinResult::Handled + } + + // ── TTY write (raw ANSI output without newline) ─────────────────────── + "native_tty_write" => { + let s = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, other => other.to_string() }; + use std::io::Write; + print!("{}", s); + let _ = std::io::stdout().flush(); + stack.push(Value::Nil); BuiltinResult::Handled + } + "native_tty_size" => { + // Returns "cols,rows" string + let (cols, rows) = term_size(); + stack.push(Value::Str(format!("{},{}", cols, rows))); BuiltinResult::Handled + } + // ── Stack aliases ───────────────────────────────────────────────────── "stack_push" => { let item = stack.pop().unwrap_or(Value::Nil); @@ -6448,10 +6551,16 @@ fn is_builtin(name: &str) -> bool { | "str_pad_left" | "str_pad_right" | "format_float" | "str_format" // List additions | "list_push" | "list_pop" | "list_pop_front" | "list_peek_last" | "list_range" - | "list_new" | "list_empty" + | "list_new" | "list_empty" | "list_append" | "list_set" | "list_concat" + | "list_slice" | "list_reverse" | "list_contains" | "stack_push" | "stack_pop" | "stack_peek" | "stack_new" | "queue_enqueue" | "queue_dequeue" | "queue_peek" | "queue_new" | "list_map" | "list_filter" | "list_reduce" | "fn_ref" + // String character mutation + | "str_set_char" + // Shell and TTY + | "shell_exec" | "native_tty_write" | "native_tty_size" + | "native_tty_read" // reserved for future raw key input // Time | "time_now_utc" | "time_to_parts" | "time_from_parts" | "time_format" | "time_parse" | "time_add" | "time_diff" | "time_start_of" | "time_tz_offset" | "time_to_tz"