176 lines
6.1 KiB
Rust
176 lines
6.1 KiB
Rust
use std::{
|
|
path::{Path, PathBuf},
|
|
rc::Rc,
|
|
};
|
|
|
|
use crate::{
|
|
error::MachineError,
|
|
vm::{
|
|
Value,
|
|
env::{Environment, Macro},
|
|
value::{Keyword, StringValue, convert::TryFromValue},
|
|
},
|
|
};
|
|
|
|
pub fn load(env: &Rc<Environment>) {
|
|
env.defun_native(
|
|
"import",
|
|
"Evaluates the given script file",
|
|
|vm, env, args| {
|
|
if args.is_empty() {
|
|
return Err(MachineError::InvalidArgumentCount);
|
|
}
|
|
|
|
let caller = vm
|
|
.read_call_stack(0)
|
|
.ok_or(MachineError::CallStackUnderflow)?;
|
|
let caller_directory = caller
|
|
.closure
|
|
.function
|
|
.script
|
|
.as_ref()
|
|
.and_then(|path| path.parent())
|
|
.map(Path::to_owned)
|
|
.unwrap_or_else(|| PathBuf::from("."));
|
|
|
|
for arg in args {
|
|
let path_str = StringValue::try_from_value(arg)?;
|
|
let mut path = PathBuf::from(&*path_str);
|
|
|
|
if path.is_relative() {
|
|
let in_caller_directory = caller_directory.join(&path);
|
|
if in_caller_directory.exists() {
|
|
path = in_caller_directory;
|
|
}
|
|
}
|
|
|
|
vm.load_file(Default::default(), env, &path)
|
|
.map_err(|error| MachineError::LoadError(path_str, error.into()))?;
|
|
}
|
|
|
|
Ok(Value::Nil)
|
|
},
|
|
);
|
|
|
|
env.defun_native("gensym", "Provides an unique symbol name", |_, env, _| {
|
|
Ok(env.gensym().into())
|
|
});
|
|
env.defmacro_native(
|
|
"explain",
|
|
"Provides an explanation for a given value",
|
|
|_, env, args| {
|
|
let [argument] = args else {
|
|
return Err(MachineError::InvalidArgumentCount);
|
|
};
|
|
if let Value::Identifier(identifier) = argument
|
|
&& let Some(mac) = env.global_macro(identifier)
|
|
{
|
|
return match mac {
|
|
Macro::Native(mac) => Ok(Value::String(
|
|
format!("built-in macro {}: {}", mac.name(), mac.docstring()).into(),
|
|
)),
|
|
Macro::Bytecode(mac) => Ok(Value::String(
|
|
format!("macro {}: {}", mac.name(), mac.docstring()).into(),
|
|
)),
|
|
};
|
|
}
|
|
|
|
Ok(Value::list_or_nil([
|
|
Value::Identifier("explain_".into()),
|
|
argument.clone(),
|
|
]))
|
|
},
|
|
);
|
|
env.defun_native(
|
|
"explain_",
|
|
"Provides an explanation for a given value",
|
|
|_, _, args| {
|
|
let [argument] = args else {
|
|
return Err(MachineError::InvalidArgumentCount);
|
|
};
|
|
let explanation = match argument {
|
|
Value::Nil => "an empty list".into(),
|
|
Value::Cons(_) => "a cons-cell".into(),
|
|
Value::Number(_) => "a number".into(),
|
|
Value::Boolean(_) => "a boolean value".into(),
|
|
Value::Quasi(_) => "a quasi-quoted value".into(),
|
|
Value::Quote(_) => "a quoted value".into(),
|
|
Value::Unquote(_) => "an unquoted value".into(),
|
|
Value::UnquoteSplice(_) => "an unquote-spliced value".into(),
|
|
Value::Identifier(identifier) => format!("an identifier {:?}", identifier.as_ref()),
|
|
Value::Vector(_) => "a vector".into(),
|
|
Value::HashTable(_) => "a hash table".into(),
|
|
Value::String(_) => "a string".into(),
|
|
Value::Keyword(_) => "a keyword".into(),
|
|
Value::Closure(closure) => {
|
|
format!(
|
|
"function {}: {}",
|
|
closure.function.name(),
|
|
closure.function.docstring()
|
|
)
|
|
}
|
|
Value::Function(function) => {
|
|
format!("function {}: {}", function.name(), function.docstring())
|
|
}
|
|
Value::NativeFunction(function) => {
|
|
format!(
|
|
"built-in function {}: {}",
|
|
function.name(),
|
|
function.docstring()
|
|
)
|
|
}
|
|
Value::NativeValue(_) => "a native value".into(),
|
|
};
|
|
Ok(Value::String(explanation.into()))
|
|
},
|
|
);
|
|
env.defun_native(
|
|
"type",
|
|
"Returns the data type of the argument",
|
|
|_, _, args| {
|
|
let [argument] = args else {
|
|
return Err(MachineError::InvalidArgumentCount);
|
|
};
|
|
Ok(argument.type_id())
|
|
},
|
|
);
|
|
env.defmacro_native(
|
|
"assert",
|
|
"Checks that the condition is true, otherwise aborts the execution",
|
|
|vm, _, args| match args {
|
|
[] => Err(MachineError::InvalidArgumentCount),
|
|
[cond] => {
|
|
let assertion_failure_msg = match vm.current_location() {
|
|
Some(ip) => format!("{ip}: assertion failed: {cond}"),
|
|
None => format!("<undefined>: assertion failed: {cond}"),
|
|
};
|
|
let assertion_failure = Value::list_or_nil([
|
|
Value::Identifier("abort".into()),
|
|
Value::String(assertion_failure_msg.into()),
|
|
]);
|
|
Ok(Value::list_or_nil([
|
|
Value::Keyword(Keyword::If),
|
|
cond.clone(),
|
|
Value::Nil,
|
|
assertion_failure,
|
|
]))
|
|
}
|
|
_ => Err(MachineError::InvalidArgumentCount),
|
|
},
|
|
);
|
|
env.defun_native(
|
|
"abort",
|
|
"Aborts the execution with the given reason message",
|
|
|_, _, args| {
|
|
let mut message = String::new();
|
|
for (i, arg) in args.iter().enumerate() {
|
|
if i != 0 {
|
|
message.push(' ');
|
|
}
|
|
message.push_str(&format!("{arg}"));
|
|
}
|
|
Err(MachineError::Abort(message.into()))
|
|
},
|
|
);
|
|
}
|