use super::error::{EvalResult, Flow, signal};
use super::intern::resolve_sym;
use super::value::*;
use crate::gc_trace::GcTrace;
use std::collections::HashSet;
fn expect_args(name: &str, args: &[Value], n: usize) -> Result<(), Flow> {
if args.len() != n {
Err(signal(
"wrong-number-of-arguments",
vec![Value::symbol(name), Value::fixnum(args.len() as i64)],
))
} else {
Ok(())
}
}
fn expect_min_args(name: &str, args: &[Value], min: usize) -> Result<(), Flow> {
if args.len() < min {
Err(signal(
"wrong-number-of-arguments",
vec![Value::symbol(name), Value::fixnum(args.len() as i64)],
))
} else {
Ok(())
}
}
fn expect_max_args(name: &str, args: &[Value], max: usize) -> Result<(), Flow> {
if args.len() > max {
Err(signal(
"wrong-number-of-arguments",
vec![Value::symbol(name), Value::fixnum(args.len() as i64)],
))
} else {
Ok(())
}
}
fn expect_int(value: &Value) -> Result<i64, Flow> {
match value.kind() {
ValueKind::Fixnum(n) => Ok(n),
other => Err(signal(
"wrong-type-argument",
vec![Value::symbol("integerp"), *value],
)),
}
}
fn prefix_numeric_value(value: &Value) -> i64 {
match value.kind() {
ValueKind::Nil => 1,
ValueKind::Symbol(id) if resolve_sym(id) == "-" => -1,
ValueKind::Fixnum(n) => n,
ValueKind::Cons => value.cons_car().as_int().unwrap_or(1),
_ => 1,
}
}
#[derive(Clone, Debug)]
pub struct KmacroManager {
pub macro_ring: Vec<Vec<Value>>,
pub counter: i64,
pub counter_format: String,
}
impl Default for KmacroManager {
fn default() -> Self {
Self::new()
}
}
impl GcTrace for KmacroManager {
fn trace_roots(&self, roots: &mut Vec<Value>) {
for macro_entry in &self.macro_ring {
for value in macro_entry {
roots.push(*value);
}
}
}
}
impl KmacroManager {
pub fn new() -> Self {
Self {
macro_ring: Vec::new(),
counter: 0,
counter_format: "%d".to_string(),
}
}
pub fn format_counter(&self) -> String {
let fmt = &self.counter_format;
if fmt.contains("%d") {
fmt.replace("%d", &self.counter.to_string())
} else if fmt.contains("%o") {
fmt.replace("%o", &format!("{:o}", self.counter))
} else if fmt.contains("%x") {
fmt.replace("%x", &format!("{:x}", self.counter))
} else if fmt.contains("%X") {
fmt.replace("%X", &format!("{:X}", self.counter))
} else {
self.counter.to_string()
}
}
}
pub(crate) fn builtin_defining_kbd_macro(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_min_args("defining-kbd-macro", &args, 1)?;
expect_max_args("defining-kbd-macro", &args, 2)?;
let append = args[0].is_truthy();
let no_exec = args.get(1).is_some_and(|v| v.is_truthy());
start_kbd_macro_impl(eval, append, no_exec)?;
Ok(Value::NIL)
}
fn last_kbd_macro_or_array_error(eval: &super::eval::Context) -> Result<Vec<Value>, Flow> {
eval.command_loop
.last_kbd_macro()
.map(|events| events.to_vec())
.ok_or_else(|| {
signal(
"wrong-type-argument",
vec![Value::symbol("arrayp"), Value::NIL],
)
})
}
fn execute_kbd_macro_iteration(eval: &mut super::eval::Context) -> EvalResult {
eval.execute_kbd_macro_iteration_via_command_loop()
}
fn execute_kbd_macro_events_with_runtime_state(
eval: &mut super::eval::Context,
macro_events: &[Value],
count: i64,
loopfunc: Value,
) -> EvalResult {
eval.with_executing_kbd_macro_runtime(macro_events.to_vec(), |eval| {
let mut repeat = count;
let mut success_count = 0usize;
loop {
eval.reset_executing_kbd_macro_runtime_iteration();
if !loopfunc.is_nil() {
let cont = eval.apply(loopfunc, vec![])?;
if !cont.is_truthy() {
break;
}
}
execute_kbd_macro_iteration(eval)?;
success_count += 1;
eval.note_executing_kbd_macro_iteration(success_count);
if repeat == 0 {
continue;
}
repeat -= 1;
if repeat == 0 {
break;
}
}
Ok(Value::NIL)
})
}
fn start_kbd_macro_impl(
eval: &mut super::eval::Context,
append: bool,
no_exec: bool,
) -> EvalResult {
let initial_events = if append {
Some(last_kbd_macro_or_array_error(eval)?)
} else {
None
};
if let Some(ref initial_events) = initial_events
&& !no_exec
{
execute_kbd_macro_events_with_runtime_state(eval, initial_events, 1, Value::NIL)?;
}
eval.start_kbd_macro_runtime(initial_events.as_deref(), append)?;
Ok(Value::NIL)
}
pub(crate) fn plan_call_last_kbd_macro(
last_kbd_macro: Option<&[Value]>,
args: &[Value],
) -> Result<(Vec<Value>, i64, Value), Flow> {
expect_max_args("call-last-kbd-macro", args, 2)?;
let repeat = args.first().map_or(1i64, prefix_numeric_value);
let loopfunc = args.get(1).copied().unwrap_or(Value::NIL);
let macro_keys = last_kbd_macro
.map(|events| events.to_vec())
.ok_or_else(|| {
signal(
"error",
vec![Value::string("No keyboard macro has been defined")],
)
})?;
Ok((macro_keys, repeat, loopfunc))
}
pub(crate) fn plan_execute_kbd_macro(
eval: &super::eval::Context,
args: &[Value],
) -> Result<(Vec<Value>, i64, Value), Flow> {
expect_min_args("execute-kbd-macro", args, 1)?;
expect_max_args("execute-kbd-macro", args, 3)?;
let count = args.get(1).map_or(1, prefix_numeric_value);
let loopfunc = args.get(2).copied().unwrap_or(Value::NIL);
Ok((resolve_macro_events(eval, &args[0])?, count, loopfunc))
}
pub(crate) fn builtin_start_kbd_macro(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_max_args("start-kbd-macro", &args, 2)?;
let append = args.first().is_some_and(|v| v.is_truthy());
let no_exec = args.get(1).is_some_and(|v| v.is_truthy());
start_kbd_macro_impl(eval, append, no_exec)?;
Ok(Value::NIL)
}
pub(crate) fn builtin_end_kbd_macro(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_max_args("end-kbd-macro", &args, 2)?;
let repeat = if let Some(value) = args.first() {
expect_int(value).unwrap_or(1)
} else {
1
};
let loopfunc = args.get(1).copied().unwrap_or(Value::NIL);
let recorded = eval.end_kbd_macro_runtime()?;
if repeat == 0 {
execute_kbd_macro_events_with_runtime_state(eval, &recorded, repeat, loopfunc)?;
} else if repeat > 1 {
execute_kbd_macro_events_with_runtime_state(eval, &recorded, repeat - 1, loopfunc)?;
}
Ok(Value::NIL)
}
pub(crate) fn builtin_call_last_kbd_macro(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
if eval.command_loop.keyboard.kboard.defining_kbd_macro {
return Err(signal(
"error",
vec![Value::string(
"Can't execute anonymous macro while defining one",
)],
));
}
let (macro_keys, repeat, loopfunc) =
plan_call_last_kbd_macro(eval.command_loop.last_kbd_macro(), &args)?;
let previous_last_command = eval.eval_symbol("last-command").unwrap_or(Value::NIL);
let macro_value = Value::vector(macro_keys.clone());
eval.assign("this-command", previous_last_command);
eval.assign("real-this-command", macro_value);
let result = execute_kbd_macro_events_with_runtime_state(eval, ¯o_keys, repeat, loopfunc);
if let Ok(last_command) = eval.eval_symbol("last-command") {
eval.assign("this-command", last_command);
}
result
}
pub(crate) fn builtin_execute_kbd_macro(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
let (macro_events, count, loopfunc) = plan_execute_kbd_macro(eval, &args)?;
execute_kbd_macro_events_with_runtime_state(eval, ¯o_events, count, loopfunc)
}
fn name_last_kbd_macro_impl(
eval: &mut super::eval::Context,
args: Vec<Value>,
call_name: &str,
) -> EvalResult {
expect_args(call_name, &args, 1)?;
let name = match args[0].kind() {
ValueKind::Symbol(id) => resolve_sym(id).to_owned(),
ValueKind::String => args[0].as_str().unwrap().to_owned(),
other => {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("symbolp"), args[0]],
));
}
};
let macro_val = match eval.command_loop.last_kbd_macro() {
Some(keys) => Value::vector(keys.to_vec()),
None => {
return Err(signal(
"error",
vec![Value::string("No keyboard macro has been defined")],
));
}
};
eval.obarray.set_symbol_function(&name, macro_val);
Ok(Value::NIL)
}
pub(crate) fn builtin_name_last_kbd_macro(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
name_last_kbd_macro_impl(eval, args, "name-last-kbd-macro")
}
pub(crate) fn builtin_defining_kbd_macro_p(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_args("defining-kbd-macro-p", &args, 0)?;
Ok(Value::bool_val(
eval.command_loop.keyboard.kboard.defining_kbd_macro,
))
}
pub(crate) fn builtin_executing_kbd_macro_p(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_args("executing-kbd-macro-p", &args, 0)?;
Ok(Value::bool_val(
eval.command_loop
.keyboard
.kboard
.executing_kbd_macro
.is_some(),
))
}
pub(crate) fn builtin_last_kbd_macro(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_args("last-kbd-macro", &args, 0)?;
match eval.command_loop.last_kbd_macro() {
Some(keys) => Ok(Value::vector(keys.to_vec())),
None => Ok(Value::NIL),
}
}
pub(crate) fn builtin_kmacro_p(args: Vec<Value>) -> EvalResult {
expect_args("kmacro-p", &args, 1)?;
Ok(Value::bool_val(args[0].is_vector() || args[0].is_string()))
}
pub(crate) fn builtin_kmacro_set_counter(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_min_args("kmacro-set-counter", &args, 1)?;
expect_max_args("kmacro-set-counter", &args, 2)?;
eval.kmacro.counter = expect_int(&args[0])?;
Ok(Value::NIL)
}
pub(crate) fn builtin_kmacro_add_counter(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_args("kmacro-add-counter", &args, 1)?;
eval.kmacro.counter += expect_int(&args[0])?;
Ok(Value::NIL)
}
pub(crate) fn builtin_kmacro_set_format(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_args("kmacro-set-format", &args, 1)?;
let format = match args[0].kind() {
ValueKind::String => {
let s = args[0].as_str().unwrap().to_owned();
if s.is_empty() { "%d".to_string() } else { s }
}
other => {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("stringp"), args[0]],
));
}
};
eval.kmacro.counter_format = format;
Ok(Value::NIL)
}
pub(crate) fn builtin_store_kbd_macro_event(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_args("store-kbd-macro-event", &args, 1)?;
eval.store_kbd_macro_runtime_event(args[0]);
Ok(Value::NIL)
}
fn indirect_macro_function(eval: &super::eval::Context, value: &Value) -> Value {
let mut current = *value;
let mut seen = HashSet::new();
loop {
let Some(symbol_id) = (match current.kind() {
ValueKind::Symbol(id) => Some(id),
_ if current.bits() == Value::T.bits() => Some(super::intern::intern("t")),
_ => None,
}) else {
return current;
};
if !seen.insert(symbol_id) {
return current;
}
current = eval
.obarray()
.symbol_function_id(symbol_id)
.copied()
.unwrap_or(Value::NIL);
}
}
fn resolve_macro_events(eval: &super::eval::Context, value: &Value) -> Result<Vec<Value>, Flow> {
match indirect_macro_function(eval, value).kind() {
ValueKind::Veclike(VecLikeType::Vector) => {
let items = indirect_macro_function(eval, value)
.as_vector_data()
.unwrap()
.clone();
Ok(items.clone())
}
ValueKind::String => {
let s = indirect_macro_function(eval, value)
.as_str()
.unwrap()
.to_owned();
Ok(s.chars().map(Value::char).collect())
}
_ => Err(signal(
"error",
vec![Value::string("Keyboard macros must be strings or vectors")],
)),
}
}
#[cfg(test)]
#[path = "kmacro_test.rs"]
mod tests;