use super::error::{EvalResult, Flow, signal};
use super::intern::{intern, resolve_sym};
use super::value::*;
use crate::heap_types::LispString;
use std::path::Path;
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_integer_or_marker(value: &Value) -> Result<i64, Flow> {
match value.kind() {
ValueKind::Fixnum(n) => Ok(n),
other => Err(signal(
"wrong-type-argument",
vec![Value::symbol("integer-or-marker-p"), *value],
)),
}
}
fn lread_string_text(value: &Value) -> Option<String> {
value.as_runtime_string_owned()
}
fn expect_lisp_string(value: &Value) -> Result<LispString, Flow> {
match value.kind() {
ValueKind::String => Ok(value.as_lisp_string().expect("checked string").clone()),
ValueKind::Symbol(id) => Ok(LispString::from_utf8(resolve_sym(id))),
ValueKind::Nil => Ok(LispString::from_unibyte(b"nil".to_vec())),
ValueKind::T => Ok(LispString::from_unibyte(b"t".to_vec())),
other => Err(signal(
"wrong-type-argument",
vec![Value::symbol("stringp"), *value],
)),
}
}
fn strip_reader_prefix(source: &str) -> (&str, bool) {
if !source.starts_with("#!") {
return (source, false);
}
match source.find('\n') {
Some(index) => (&source[index + 1..], false),
None => ("", true),
}
}
fn strip_reader_prefix_lisp_string(source: &crate::heap_types::LispString) -> (usize, bool) {
let bytes = source.as_bytes();
if !bytes.starts_with(b"#!") {
return (0, false);
}
match bytes.iter().position(|&b| b == b'\n') {
Some(index) => (index + 1, false),
None => (0, true),
}
}
pub(crate) fn eval_forms_from_source(
eval: &mut super::eval::Context,
source: &str,
source_multibyte: bool,
) -> EvalResult {
let macroexpand_fn = super::load::get_eager_macroexpand_fn(eval);
eval_forms_from_source_streaming(eval, source, source_multibyte, macroexpand_fn)
}
pub(crate) fn eval_forms_from_lisp_source(
eval: &mut super::eval::Context,
source: &crate::heap_types::LispString,
) -> EvalResult {
let macroexpand_fn = super::load::get_eager_macroexpand_fn(eval);
eval_forms_from_lisp_source_streaming(eval, source, macroexpand_fn)
}
fn eval_forms_from_source_streaming(
eval: &mut super::eval::Context,
source: &str,
source_multibyte: bool,
macroexpand_fn: Option<Value>,
) -> EvalResult {
let (source, shebang_only_line) = strip_reader_prefix(source);
if shebang_only_line {
return Err(signal("end-of-file", vec![]));
}
if source.is_empty() {
return Ok(Value::NIL);
}
let mut pos = 0;
loop {
let read_result =
super::value_reader::read_one_with_source_multibyte(source, source_multibyte, pos)
.map_err(|e| {
signal(
"invalid-read-syntax",
vec![Value::string(format!("Read error: {}", e.message))],
)
})?;
let Some((form, next_pos)) = read_result else {
break;
};
pos = next_pos;
let eval_roots = eval.save_specpdl_roots();
eval.push_specpdl_root(form);
let eval_result = if let Some(mexp_fn) = macroexpand_fn {
eval.push_specpdl_root(mexp_fn);
super::load::eager_expand_eval(eval, form, mexp_fn).map_err(|e| match e {
super::error::EvalError::Signal {
symbol,
data,
raw_data,
} => super::error::Flow::Signal(Box::new(super::error::SignalData {
symbol,
data,
raw_data,
suppress_signal_hook: false,
selected_resume: None,
search_complete: false,
})),
super::error::EvalError::UncaughtThrow { tag, value } => {
super::error::Flow::Throw { tag, value }
}
})
} else {
eval.eval_sub(form)
};
eval.restore_specpdl_roots(eval_roots);
eval_result?;
let gc_roots = eval.save_specpdl_roots();
eval.push_specpdl_root(form);
if let Some(mexp_fn) = macroexpand_fn {
eval.push_specpdl_root(mexp_fn);
}
eval.gc_safe_point_exact();
eval.restore_specpdl_roots(gc_roots);
}
Ok(Value::NIL)
}
fn eval_forms_from_lisp_source_streaming(
eval: &mut super::eval::Context,
source: &crate::heap_types::LispString,
macroexpand_fn: Option<Value>,
) -> EvalResult {
let (start_pos, shebang_only_line) = strip_reader_prefix_lisp_string(source);
if shebang_only_line {
return Err(signal("end-of-file", vec![]));
}
if source.as_bytes().is_empty() {
return Ok(Value::NIL);
}
let read_source = super::value_reader::LispReadSource::new(source);
let mut pos = start_pos;
loop {
let read_result = read_source.read_one(pos).map_err(|e| {
signal(
"invalid-read-syntax",
vec![Value::string(format!("Read error: {}", e.message))],
)
})?;
let Some((form, next_pos)) = read_result else {
break;
};
pos = next_pos;
let eval_roots = eval.save_specpdl_roots();
eval.push_specpdl_root(form);
let eval_result = if let Some(mexp_fn) = macroexpand_fn {
eval.push_specpdl_root(mexp_fn);
super::load::eager_expand_eval(eval, form, mexp_fn).map_err(|e| match e {
super::error::EvalError::Signal {
symbol,
data,
raw_data,
} => super::error::Flow::Signal(Box::new(super::error::SignalData {
symbol,
data,
raw_data,
suppress_signal_hook: false,
selected_resume: None,
search_complete: false,
})),
super::error::EvalError::UncaughtThrow { tag, value } => {
super::error::Flow::Throw { tag, value }
}
})
} else {
eval.eval_sub(form)
};
eval.restore_specpdl_roots(eval_roots);
eval_result?;
let gc_roots = eval.save_specpdl_roots();
if let Some(mexp_fn) = macroexpand_fn {
eval.push_specpdl_root(mexp_fn);
}
eval.gc_safe_point_exact();
eval.restore_specpdl_roots(gc_roots);
}
Ok(Value::NIL)
}
fn map_eval_error_to_flow(err: super::error::EvalError) -> Flow {
match err {
super::error::EvalError::Signal {
symbol,
data,
raw_data,
} => Flow::Signal(Box::new(super::error::SignalData {
symbol,
data,
raw_data,
suppress_signal_hook: false,
selected_resume: None,
search_complete: false,
})),
super::error::EvalError::UncaughtThrow { tag, value } => Flow::Throw { tag, value },
}
}
pub(crate) fn eval_buffer_source_text_in_state(
buffers: &crate::buffer::BufferManager,
arg: Option<&Value>,
) -> Result<crate::heap_types::LispString, Flow> {
let buffer_id = resolve_eval_buffer_id_in_state(buffers, arg)?;
buffers
.get(buffer_id)
.map(|buffer| buffer.buffer_substring_lisp_string(buffer.point_min(), buffer.point_max()))
.ok_or_else(|| signal("error", vec![Value::string("No such buffer")]))
}
fn resolve_eval_buffer_id_in_state(
buffers: &crate::buffer::BufferManager,
arg: Option<&Value>,
) -> Result<crate::buffer::BufferId, Flow> {
match arg {
None => Ok(buffers
.current_buffer()
.map(|b| b.id)
.ok_or_else(|| signal("error", vec![Value::string("No current buffer")]))?),
Some(v) if v.is_nil() => Ok(buffers
.current_buffer()
.map(|b| b.id)
.ok_or_else(|| signal("error", vec![Value::string("No current buffer")]))?),
Some(v) if v.is_buffer() => Ok(v.as_buffer_id().unwrap()),
Some(v) if v.is_string() => Ok({
let name = lread_string_text(v).expect("checked string");
buffers
.find_buffer_by_name(&name)
.ok_or_else(|| signal("error", vec![Value::string("No such buffer")]))?
}),
Some(other) => {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("stringp"), *other],
));
}
}
}
fn eval_buffer_filename_in_state(
buffers: &crate::buffer::BufferManager,
buffer_id: crate::buffer::BufferId,
arg: Option<&Value>,
) -> Result<Option<LispString>, Flow> {
match arg {
None => Ok(buffers
.get(buffer_id)
.and_then(|buffer| buffer.file_name_value().as_lisp_string().cloned())),
Some(v) if v.is_nil() => Ok(buffers
.get(buffer_id)
.and_then(|buffer| buffer.file_name_value().as_lisp_string().cloned())),
Some(value) => Ok(Some(expect_lisp_string(value)?)),
}
}
fn record_eval_buffer_load_history(eval: &mut super::eval::Context, filename: &LispString) {
let entry = Value::cons(Value::heap_string(filename.clone()), Value::NIL);
let history = eval
.obarray()
.symbol_value("load-history")
.cloned()
.unwrap_or(Value::NIL);
let filtered_history = Value::list(
list_to_vec(&history)
.unwrap_or_default()
.into_iter()
.filter(|existing| {
if existing.is_cons() {
existing
.cons_car()
.as_lisp_string()
.is_none_or(|loaded| loaded != filename)
} else {
true
}
})
.collect(),
);
eval.set_variable("load-history", Value::cons(entry, filtered_history));
}
fn record_eval_buffer_save_excursion(eval: &mut super::eval::Context) {
eval.record_save_excursion();
}
pub(crate) fn eval_region_source_text_in_state(
buffers: &crate::buffer::BufferManager,
args: &[Value],
) -> Result<crate::heap_types::LispString, Flow> {
expect_min_args("eval-region", args, 2)?;
expect_max_args("eval-region", args, 4)?;
let (source, start_char_pos, end_char_pos) = {
let buffer = buffers
.current_buffer()
.ok_or_else(|| signal("error", vec![Value::string("No current buffer")]))?;
let point_char_pos = buffer.point_char() as i64 + 1;
let max_char_pos = buffer.point_max_char() as i64 + 1;
let raw_start = if args[0].is_nil() {
point_char_pos
} else {
expect_integer_or_marker(&args[0])?
};
let raw_end = if args[1].is_nil() {
point_char_pos
} else {
expect_integer_or_marker(&args[1])?
};
if raw_start < 1 || raw_start > max_char_pos || raw_end < 1 || raw_end > max_char_pos {
return Err(signal("args-out-of-range", vec![args[0], args[1]]));
}
if raw_start >= raw_end {
return Ok(crate::heap_types::LispString::new(
String::new(),
buffer.get_multibyte(),
));
}
let start_byte = buffer.lisp_pos_to_accessible_byte(raw_start);
let end_byte = buffer.lisp_pos_to_accessible_byte(raw_end);
let text = buffer.buffer_substring_lisp_string(start_byte, end_byte);
(text, raw_start, raw_end)
};
if start_char_pos >= end_char_pos {
return Ok(crate::heap_types::LispString::new(
String::new(),
source.is_multibyte(),
));
}
Ok(source)
}
pub(crate) fn builtin_eval_buffer(eval: &mut super::eval::Context, args: Vec<Value>) -> EvalResult {
expect_max_args("eval-buffer", &args, 5)?;
let buffer_id = resolve_eval_buffer_id_in_state(&eval.buffers, args.first())?;
let source = eval_buffer_source_text_in_state(&eval.buffers, args.first())?;
let filename = eval_buffer_filename_in_state(&eval.buffers, buffer_id, args.get(2))?;
let specpdl_count = eval.specpdl.len();
let old_lexical = eval.lexical_binding();
let old_lexenv = eval.lexenv;
let gc_roots = eval.save_specpdl_roots();
eval.push_specpdl_root(old_lexenv);
let result = (|| -> EvalResult {
let buffer_value = Value::make_buffer(buffer_id);
let prior_eval_buffer_list = eval.visible_variable_value_or_nil("eval-buffer-list");
eval.push_specpdl_root(buffer_value);
eval.push_specpdl_root(prior_eval_buffer_list);
let eval_buffer_list = Value::cons(buffer_value, prior_eval_buffer_list);
eval.push_specpdl_root(eval_buffer_list);
eval.specbind(intern("eval-buffer-list"), eval_buffer_list);
let do_allow_print = args.get(4).is_some_and(|v| v.is_truthy());
let standard_output = if args.get(1).is_none_or(|v| v.is_nil()) && !do_allow_print {
Value::symbol("symbolp")
} else {
args.get(1).copied().unwrap_or(Value::NIL)
};
eval.specbind(intern("standard-output"), standard_output);
record_eval_buffer_save_excursion(eval);
if let Some(filename) = filename.as_ref() {
let filename_value = Value::heap_string(filename.clone());
eval.push_specpdl_root(filename_value);
let current_load_list = Value::cons(filename_value, Value::NIL);
eval.push_specpdl_root(current_load_list);
eval.specbind(intern("current-load-list"), current_load_list);
}
let lexical_binding = if let Some(binding) = eval
.buffers
.get(buffer_id)
.and_then(|buffer| buffer.get_buffer_local_binding("lexical-binding"))
.and_then(|binding| binding.as_value())
{
binding.is_truthy()
} else {
match super::load::source_lexical_binding_for_lisp_source(
eval,
&source,
Some(buffer_value),
) {
Ok(enabled) => enabled,
Err(err) => return Err(map_eval_error_to_flow(err)),
}
};
eval.set_lexical_binding(lexical_binding);
eval.lexenv = if lexical_binding {
Value::list(vec![Value::T])
} else {
Value::NIL
};
let loading_source_file = eval
.visible_variable_value_or_nil("load-in-progress")
.is_truthy()
&& filename.is_some();
let result = if loading_source_file {
super::load::eval_lisp_source_file_in_context(
eval,
filename
.as_ref()
.expect("load-in-progress eval-buffer must have filename"),
&source,
)
.map_err(map_eval_error_to_flow)
} else {
let result = eval_forms_from_lisp_source(eval, &source);
if result.is_ok()
&& let Some(filename) = filename.as_ref()
{
record_eval_buffer_load_history(eval, filename);
}
result
};
eval.set_lexical_binding(old_lexical);
eval.lexenv = old_lexenv;
eval.unbind_to(specpdl_count);
result
})();
eval.restore_specpdl_roots(gc_roots);
result
}
pub(crate) fn builtin_eval_region(eval: &mut super::eval::Context, args: Vec<Value>) -> EvalResult {
let source = eval_region_source_text_in_state(&eval.buffers, &args)?;
if source.as_bytes().is_empty() {
return Ok(Value::NIL);
}
eval_forms_from_lisp_source(eval, &source)
}
pub(crate) fn builtin_eval_buffer_in_vm_runtime(
shared: &mut super::eval::Context,
args: &[Value],
) -> EvalResult {
let source = eval_buffer_source_text_in_state(&shared.buffers, args.first())?;
let specpdl_count = shared.specpdl.len();
record_eval_buffer_save_excursion(shared);
let result = eval_forms_from_source_in_vm_runtime_streaming(shared, args, &source);
shared.unbind_to(specpdl_count);
result
}
pub(crate) fn builtin_eval_region_in_vm_runtime(
shared: &mut super::eval::Context,
args: &[Value],
) -> EvalResult {
let source = eval_region_source_text_in_state(&shared.buffers, args)?;
if source.as_bytes().is_empty() {
return Ok(Value::NIL);
}
eval_forms_from_source_in_vm_runtime_streaming(shared, args, &source)
}
fn eval_forms_from_source_in_vm_runtime_streaming(
shared: &mut super::eval::Context,
args: &[Value],
source: &crate::heap_types::LispString,
) -> EvalResult {
let (start_pos, shebang_only_line) = strip_reader_prefix_lisp_string(source);
if shebang_only_line {
return Err(signal("end-of-file", vec![]));
}
if source.as_bytes().is_empty() {
return Ok(Value::NIL);
}
let root_scope = shared.save_specpdl_roots();
for root in args {
shared.push_specpdl_root(*root);
}
let result = (|| -> EvalResult {
let read_source = super::value_reader::LispReadSource::new(source);
let mut pos = start_pos;
loop {
let read_result = read_source.read_one(pos).map_err(|e| {
signal(
"invalid-read-syntax",
vec![Value::string(format!("Read error: {}", e.message))],
)
})?;
let Some((form, next_pos)) = read_result else {
break;
};
pos = next_pos;
let eval_roots = shared.save_specpdl_roots();
shared.push_specpdl_root(form);
let eval_result = shared.eval_sub(form);
shared.restore_specpdl_roots(eval_roots);
eval_result?;
shared.gc_safe_point_exact();
}
Ok(Value::NIL)
})();
shared.restore_specpdl_roots(root_scope);
result
}
fn event_to_int(event: &Value) -> Option<i64> {
match event.kind() {
ValueKind::Fixnum(n) => Some(n),
_ => None,
}
}
fn expect_optional_prompt_string(args: &[Value]) -> Result<(), Flow> {
if args.is_empty() || args[0].is_nil() || args[0].is_string() {
return Ok(());
}
Err(signal(
"wrong-type-argument",
vec![Value::symbol("stringp"), args[0]],
))
}
pub(crate) fn builtin_read_event(eval: &mut super::eval::Context, args: Vec<Value>) -> EvalResult {
if let Some(value) = builtin_read_event_in_runtime(eval, &args)? {
return Ok(value);
}
finish_read_event_in_eval(eval, &args)
}
pub(crate) fn finish_read_event_in_eval(
eval: &mut super::eval::Context,
args: &[Value],
) -> EvalResult {
finish_read_event_interactive_in_runtime(eval, args)
}
pub(crate) fn finish_read_event_interactive_in_runtime(
runtime: &mut impl super::reader::KeyboardInputRuntime,
args: &[Value],
) -> EvalResult {
if runtime.has_input_receiver() {
let timeout = super::reader::parse_optional_read_seconds_arg(args.get(2))?;
let Some(event) = runtime.read_char_with_timeout(timeout)? else {
return Ok(Value::NIL);
};
let seconds_is_nil_or_omitted = args.get(2).is_none_or(|v| v.is_nil());
if runtime.read_command_keys().is_empty() && seconds_is_nil_or_omitted {
runtime.set_read_command_keys(vec![event]);
}
if let Some(n) = event_to_int(&event) {
return Ok(Value::fixnum(n));
}
return Ok(event);
}
Ok(Value::NIL)
}
pub(crate) fn builtin_read_char_exclusive(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
if let Some(value) = builtin_read_char_exclusive_in_runtime(eval, &args)? {
return Ok(value);
}
finish_read_char_exclusive_in_eval(eval, &args)
}
pub(crate) fn finish_read_char_exclusive_in_eval(
eval: &mut super::eval::Context,
args: &[Value],
) -> EvalResult {
finish_read_char_exclusive_interactive_in_runtime(eval, args)
}
pub(crate) fn finish_read_char_exclusive_interactive_in_runtime(
runtime: &mut impl super::reader::KeyboardInputRuntime,
args: &[Value],
) -> EvalResult {
if runtime.has_input_receiver() {
let timeout = super::reader::parse_optional_read_seconds_arg(args.get(2))?;
let deadline = timeout.map(|timeout| std::time::Instant::now() + timeout);
loop {
let remaining = deadline
.map(|deadline| deadline.saturating_duration_since(std::time::Instant::now()));
let Some(event) = runtime.read_char_with_timeout(remaining)? else {
return Ok(Value::NIL);
};
let seconds_is_nil_or_omitted = args.get(2).is_none_or(|v| v.is_nil());
if let Some(n) = event_to_int(&event) {
if runtime.read_command_keys().is_empty() && seconds_is_nil_or_omitted {
runtime.set_read_command_keys(vec![event]);
}
return Ok(Value::fixnum(n));
}
}
}
Ok(Value::NIL)
}
pub(crate) fn builtin_read_event_in_runtime(
runtime: &mut impl super::reader::KeyboardInputRuntime,
args: &[Value],
) -> Result<Option<Value>, Flow> {
if args.len() > 3 {
return Err(signal(
"wrong-number-of-arguments",
vec![
Value::symbol("read-event"),
Value::fixnum(args.len() as i64),
],
));
}
expect_optional_prompt_string(args)?;
let seconds_is_nil_or_omitted = args.get(2).is_none_or(|v| v.is_nil());
if let Some(event) = runtime.pop_unread_command_event() {
if runtime.read_command_keys().is_empty() && seconds_is_nil_or_omitted {
runtime.set_read_command_keys(vec![event]);
}
if let Some(n) = event_to_int(&event) {
return Ok(Some(Value::fixnum(n)));
}
return Ok(Some(event));
}
if runtime.has_input_receiver() {
Ok(None)
} else {
Ok(Some(Value::NIL))
}
}
pub(crate) fn builtin_read_char_exclusive_in_runtime(
runtime: &mut impl super::reader::KeyboardInputRuntime,
args: &[Value],
) -> Result<Option<Value>, Flow> {
if args.len() > 3 {
return Err(signal(
"wrong-number-of-arguments",
vec![
Value::symbol("read-char-exclusive"),
Value::fixnum(args.len() as i64),
],
));
}
expect_optional_prompt_string(args)?;
let seconds_is_nil_or_omitted = args.get(2).is_none_or(|v| v.is_nil());
while let Some(event) = runtime.pop_unread_command_event() {
if let Some(n) = event_to_int(&event) {
if runtime.read_command_keys().is_empty() && seconds_is_nil_or_omitted {
runtime.set_read_command_keys(vec![event]);
}
return Ok(Some(Value::fixnum(n)));
}
}
if runtime.has_input_receiver() {
Ok(None)
} else {
Ok(Some(Value::NIL))
}
}
pub(crate) fn builtin_get_load_suffixes(
obarray: &super::symbol::Obarray,
args: Vec<Value>,
) -> EvalResult {
expect_max_args("get-load-suffixes", &args, 0)?;
let load_suffixes = load_suffix_list(obarray.symbol_value("load-suffixes"), "load-suffixes")?;
let rep_suffixes = load_suffix_list(
obarray.symbol_value("load-file-rep-suffixes"),
"load-file-rep-suffixes",
)?;
let mut suffixes = Vec::new();
for suffix in &load_suffixes {
for rep in &rep_suffixes {
suffixes.push(Value::string(format!("{suffix}{rep}")));
}
}
Ok(Value::list(suffixes))
}
fn load_suffix_list(value: Option<&Value>, name: &str) -> Result<Vec<String>, Flow> {
let Some(value) = value else {
return Ok(Vec::new());
};
if value.is_nil() {
return Ok(Vec::new());
}
let Some(items) = list_to_vec(value) else {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("listp"), *value],
));
};
let mut suffixes = Vec::with_capacity(items.len());
for item in items {
let Some(suffix) = item.as_runtime_string_owned() else {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("stringp"), item],
));
};
suffixes.push(suffix);
}
if suffixes.is_empty() && name == "load-file-rep-suffixes" {
suffixes.push(String::new());
}
Ok(suffixes)
}
pub(crate) fn builtin_locate_file(eval: &mut super::eval::Context, args: Vec<Value>) -> EvalResult {
expect_min_args("locate-file", &args, 3)?;
expect_max_args("locate-file", &args, 4)?;
let filename = expect_lisp_string(&args[0])?;
let path = parse_path_argument(&args[1])?;
let suffixes = parse_suffixes_argument(&args[2])?;
Ok(
match locate_file_with_path_and_suffixes(eval, &filename, &path, &suffixes, args.get(3))? {
Some(found) => Value::heap_string(found),
None => Value::NIL,
},
)
}
pub(crate) fn builtin_locate_file_internal(
eval: &mut super::eval::Context,
args: Vec<Value>,
) -> EvalResult {
expect_min_args("locate-file-internal", &args, 2)?;
expect_max_args("locate-file-internal", &args, 4)?;
let filename = expect_lisp_string(&args[0])?;
let path = parse_path_argument(&args[1])?;
let suffixes = if args.len() > 2 {
parse_suffixes_argument(&args[2])?
} else {
Vec::new()
};
Ok(
match locate_file_with_path_and_suffixes(eval, &filename, &path, &suffixes, args.get(3))? {
Some(found) => Value::heap_string(found),
None => Value::NIL,
},
)
}
pub(crate) fn builtin_read_coding_system(args: Vec<Value>) -> EvalResult {
expect_min_args("read-coding-system", &args, 1)?;
expect_max_args("read-coding-system", &args, 2)?;
if !args[0].is_string() {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("stringp"), args[0]],
));
}
Err(signal(
"end-of-file",
vec![Value::string("Error reading from stdin")],
))
}
pub(crate) fn builtin_read_non_nil_coding_system(args: Vec<Value>) -> EvalResult {
if args.len() != 1 {
return Err(signal(
"wrong-number-of-arguments",
vec![
Value::symbol("read-non-nil-coding-system"),
Value::fixnum(args.len() as i64),
],
));
}
if !args[0].is_string() {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("stringp"), args[0]],
));
}
Err(signal(
"end-of-file",
vec![Value::string("Error reading from stdin")],
))
}
fn expect_list(value: &Value) -> Result<Vec<Value>, Flow> {
list_to_vec(value)
.ok_or_else(|| signal("wrong-type-argument", vec![Value::symbol("listp"), *value]))
}
fn parse_path_argument(value: &Value) -> Result<Vec<LispString>, Flow> {
let mut path = Vec::new();
for entry in expect_list(value)? {
match entry.kind() {
ValueKind::Nil => path.push(LispString::from_unibyte(b".".to_vec())),
ValueKind::String => path.push(entry.as_lisp_string().expect("checked string").clone()),
other => {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("stringp"), entry],
));
}
}
}
Ok(path)
}
fn parse_suffixes_argument(value: &Value) -> Result<Vec<LispString>, Flow> {
let mut suffixes = Vec::new();
for entry in expect_list(value)? {
match entry.kind() {
ValueKind::Nil => suffixes.push(LispString::from_unibyte(Vec::new())),
ValueKind::String => {
suffixes.push(entry.as_lisp_string().expect("checked string").clone())
}
other => {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("stringp"), entry],
));
}
}
}
Ok(suffixes)
}
fn locate_file_with_path_and_suffixes(
eval: &mut super::eval::Context,
filename: &LispString,
path: &[LispString],
suffixes: &[LispString],
predicate: Option<&Value>,
) -> Result<Option<LispString>, Flow> {
let effective_suffixes: Vec<LispString> = if suffixes.is_empty() {
vec![LispString::from_unibyte(Vec::new())]
} else {
suffixes.to_vec()
};
let absolute = matches!(filename.as_bytes().first(), Some(b'/') | Some(b'~'));
if absolute || path.is_empty() {
let expanded = locate_file_expand_name(eval, filename, None)?;
for suffix in &effective_suffixes {
let candidate_lisp = append_lisp_file_name_suffix(&expanded, suffix);
if crate::emacs_core::fileio::lisp_file_name_to_path_buf(&candidate_lisp).exists()
&& predicate_matches_candidate(eval, predicate, &candidate_lisp)?
{
return Ok(Some(candidate_lisp));
}
}
return Ok(None);
}
for dir in path {
let base = locate_file_expand_name(eval, filename, Some(dir))?;
for suffix in &effective_suffixes {
let candidate_lisp = append_lisp_file_name_suffix(&base, suffix);
if crate::emacs_core::fileio::lisp_file_name_to_path_buf(&candidate_lisp).exists()
&& predicate_matches_candidate(eval, predicate, &candidate_lisp)?
{
return Ok(Some(candidate_lisp));
}
}
}
Ok(None)
}
fn locate_file_expand_name(
eval: &mut super::eval::Context,
name: &LispString,
default_dir: Option<&LispString>,
) -> Result<LispString, Flow> {
let mut args = vec![Value::heap_string(name.clone())];
if let Some(dir) = default_dir {
args.push(Value::heap_string(dir.clone()));
}
let expanded = crate::emacs_core::fileio::builtin_expand_file_name(eval, args)?;
Ok(expanded
.as_lisp_string()
.expect("expand-file-name should return a string")
.clone())
}
fn append_lisp_file_name_suffix(base: &LispString, suffix: &LispString) -> LispString {
let mut bytes = base.as_bytes().to_vec();
bytes.extend_from_slice(suffix.as_bytes());
if base.is_multibyte() || suffix.is_multibyte() {
LispString::from_emacs_bytes(bytes)
} else {
LispString::from_unibyte(bytes)
}
}
fn predicate_matches_candidate(
eval: &mut super::eval::Context,
predicate: Option<&Value>,
candidate: &LispString,
) -> Result<bool, Flow> {
let Some(predicate) = predicate else {
return Ok(true);
};
if predicate.is_nil() {
return Ok(true);
}
let Some(symbol) = predicate.as_symbol_name() else {
return Ok(true);
};
let Some(result) = eval.dispatch_subr(symbol, vec![Value::heap_string(candidate.clone())])
else {
return Ok(true);
};
Ok(result?.is_truthy())
}
#[cfg(test)]
#[path = "lread_test.rs"]
mod tests;