use std::collections::HashMap;
use super::super::collection::common::{require_arity, require_arity_range};
use super::super::{Runtime, Value};
use crate::error::Result;
pub(super) fn exports() -> HashMap<String, Value> {
let mut exports = HashMap::new();
for func in [
"class_name",
"classof",
"object_slots",
"ansi_esc",
"ref_id",
"setprop",
"setupperprop",
"getprop",
"getupperprop",
"make_instance",
"load_module",
"to_binary",
] {
exports.insert(func.to_owned(), Value::native_function(func.to_owned()));
}
for (export_name, native_name) in [
("to_String", "std/internals.to_String"),
("to_Number", "std/internals.to_Number"),
("to_Boolean", "std/internals.to_Boolean"),
("to_Regexp", "std/internals.to_Regexp"),
("to_Regexp_with_flags", "std/internals.to_Regexp_with_flags"),
] {
exports.insert(
export_name.to_owned(),
Value::native_function(native_name.to_owned()),
);
}
exports
}
pub(super) fn call(
runtime: &Runtime,
name: &str,
args: &[Value],
named_args: &[(String, Value)],
) -> Option<Result<Value>> {
if !is_internal_function(name) {
return None;
}
if !named_args.is_empty() {
return Some(Err(crate::error::ZuzuRustError::runtime(
"named call arguments are not implemented for native functions",
)));
}
let value = match name {
"class_name" => require_arity(name, args, 1).map(|_| match &args[0] {
Value::Object(object) => Value::String(object.borrow().class.name.clone()),
Value::Pair(_, _) => Value::String("Pair".to_owned()),
_ => Value::Null,
}),
"classof" => require_arity(name, args, 1).map(|_| classof_value(runtime, &args[0])),
"object_slots" => require_arity(name, args, 1).map(|_| match &args[0] {
Value::Object(object) => {
let mut map = HashMap::new();
for (key, value) in &object.borrow().fields {
if !key.starts_with('_') {
map.insert(key.clone(), value.clone());
}
}
Value::Dict(map)
}
Value::Pair(key, value) => {
let mut map = HashMap::new();
map.insert(
"pair".to_owned(),
Value::Array(vec![Value::String(key.clone()), (**value).clone()]),
);
Value::Dict(map)
}
_ => Value::Null,
}),
"ansi_esc" => require_arity(name, args, 0).map(|_| Value::String("\u{1b}".to_owned())),
"ref_id" => require_arity(name, args, 1).map(|_| match &args[0] {
Value::Shared(value) => Value::String(format!("{:p}", std::rc::Rc::as_ptr(value))),
Value::Object(object) => Value::String(format!("{:p}", std::rc::Rc::as_ptr(object))),
Value::Ref(reference) => Value::String(format!("{:p}", std::rc::Rc::as_ptr(reference))),
Value::UserClass(class) => {
Value::String(format!("class:{:p}", std::rc::Rc::as_ptr(class)))
}
Value::Trait(trait_value) => {
Value::String(format!("trait:{:p}", std::rc::Rc::as_ptr(trait_value)))
}
Value::Class(name) => Value::String(format!("class:{name}")),
Value::Array(_)
| Value::Set(_)
| Value::Bag(_)
| Value::Dict(_)
| Value::PairList(_)
| Value::Pair(_, _) => Value::String(format!(
"{}:{}",
args[0].type_name(),
runtime.render_value(&args[0]).unwrap_or_default()
)),
_ => Value::Null,
}),
"setupperprop" => require_arity(name, args, 3).and_then(|_| {
let level = runtime.value_to_number(&args[0])? as usize;
let key = match &args[1] {
Value::String(value) => value.clone(),
_ => {
return Err(crate::error::ZuzuRustError::thrown(
"setupperprop key must be String",
))
}
};
Ok(runtime.set_special_prop_at_level(level, &key, args[2].clone()))
}),
"setprop" => require_arity(name, args, 2).and_then(|_| {
let key = match &args[0] {
Value::String(value) => value.clone(),
_ => {
return Err(crate::error::ZuzuRustError::thrown(
"setprop key must be String",
))
}
};
Ok(runtime.set_special_prop(&key, args[1].clone()))
}),
"getprop" => require_arity(name, args, 1).and_then(|_| {
let key = match &args[0] {
Value::String(value) => value.clone(),
_ => {
return Err(crate::error::ZuzuRustError::thrown(
"getprop key must be String",
))
}
};
Ok(runtime.get_special_prop(&key))
}),
"getupperprop" => require_arity(name, args, 2).and_then(|_| {
let level = runtime.value_to_number(&args[0])? as usize;
let key = match &args[1] {
Value::String(value) => value.clone(),
_ => {
return Err(crate::error::ZuzuRustError::thrown(
"getupperprop key must be String",
))
}
};
Ok(runtime.get_special_prop_at_level(level, &key))
}),
"make_instance" => require_arity_range(name, args, 1, 2).and_then(|_| {
let class = match &args[0] {
Value::UserClass(class) => std::rc::Rc::clone(class),
_ => {
return Err(crate::error::ZuzuRustError::thrown(
"make_instance expects a user class",
))
}
};
let slots = match args.get(1) {
None | Some(Value::Null) => HashMap::new(),
Some(Value::Dict(map)) => map.clone(),
Some(_) => {
return Err(crate::error::ZuzuRustError::thrown(
"make_instance slot values must be Dict",
))
}
};
runtime.make_user_instance_without_build(class, slots)
}),
"load_module" => require_arity_range(name, args, 1, 2).and_then(|_| {
let module_name = match &args[0] {
Value::String(value) => value,
_ => {
return Err(crate::error::ZuzuRustError::thrown(
"load_module module must be String",
))
}
};
let exports = runtime.load_module_exports(module_name)?;
if let Some(symbol) = args.get(1) {
let symbol_name = match symbol {
Value::String(value) => value,
_ => {
return Err(crate::error::ZuzuRustError::thrown(
"load_module symbol must be String",
))
}
};
return exports
.get(symbol_name)
.cloned()
.ok_or_else(|| {
crate::error::ZuzuRustError::runtime(format!(
"module '{}' has no export '{}'",
module_name, symbol_name
))
})
.and_then(|value| runtime.normalize_value(value));
}
let mut map = HashMap::new();
for (export_name, value) in exports {
map.insert(export_name, runtime.normalize_value(value)?);
}
Ok(Value::Dict(map))
}),
"to_binary" => require_arity(name, args, 1).and_then(|_| match &args[0] {
Value::BinaryString(bytes) => Ok(Value::BinaryString(bytes.clone())),
value => Ok(Value::BinaryString(
runtime.render_value(value)?.into_bytes(),
)),
}),
"to_string" => require_arity(name, args, 1).and_then(|_| match &args[0] {
Value::BinaryString(bytes) => String::from_utf8(bytes.clone())
.map(Value::String)
.map_err(|_| {
crate::error::ZuzuRustError::runtime("BinaryString is not valid UTF-8")
}),
value => Ok(Value::String(runtime.render_value(value)?)),
}),
"std/internals.to_String" => require_arity(name, args, 1)
.and_then(|_| Ok(Value::String(runtime.value_to_operator_string(&args[0])?))),
"std/internals.to_Number" => require_arity(name, args, 1)
.and_then(|_| Ok(Value::Number(runtime.value_to_number(&args[0])?))),
"std/internals.to_Boolean" => require_arity(name, args, 1)
.and_then(|_| Ok(Value::Boolean(runtime.value_is_truthy(&args[0])?))),
"std/internals.to_Regexp" => require_arity(name, args, 1).and_then(|_| match &args[0] {
Value::Regex(pattern, flags) => Ok(Value::Regex(pattern.clone(), flags.clone())),
value => {
let pattern = runtime.value_to_operator_string(value)?;
runtime.compile_regex(&pattern, "")?;
Ok(Value::Regex(pattern, String::new()))
}
}),
"std/internals.to_Regexp_with_flags" => require_arity(name, args, 2).and_then(|_| {
let pattern = match &args[0] {
Value::Regex(pattern, _) => pattern.clone(),
value => runtime.value_to_operator_string(value)?,
};
let flags = runtime.value_to_operator_string(&args[1])?;
runtime.compile_regex(&pattern, &flags)?;
Ok(Value::Regex(pattern, flags))
}),
_ => unreachable!(),
};
Some(value)
}
fn classof_value(runtime: &Runtime, value: &Value) -> Value {
let value = runtime.deref_value(value).unwrap_or_else(|_| value.clone());
match value {
Value::Object(object) => Value::UserClass(std::rc::Rc::clone(&object.borrow().class)),
Value::Array(_) | Value::SystemArray(_) => Value::builtin_class("Array"),
Value::Set(_) => Value::builtin_class("Set"),
Value::Bag(_) => Value::builtin_class("Bag"),
Value::Dict(_) | Value::SystemDict(_) => Value::builtin_class("Dict"),
Value::PairList(_) => Value::builtin_class("PairList"),
Value::Pair(_, _) => Value::builtin_class("Pair"),
Value::BinaryString(_) => Value::builtin_class("BinaryString"),
Value::Regex(_, _) => Value::builtin_class("Regexp"),
Value::Function(_) | Value::NativeFunction(_) | Value::Iterator(_) => {
Value::builtin_class("Function")
}
Value::Class(_) | Value::UserClass(_) => Value::builtin_class("Class"),
Value::Trait(_) => Value::builtin_class("Trait"),
Value::Task(_) => Value::builtin_class("Task"),
Value::Channel(_) => Value::builtin_class("Channel"),
Value::CancellationSource(_) => Value::builtin_class("CancellationSource"),
Value::CancellationToken(_) => Value::builtin_class("CancellationToken"),
_ => Value::Null,
}
}
fn is_internal_function(name: &str) -> bool {
matches!(
name,
"class_name"
| "classof"
| "object_slots"
| "ansi_esc"
| "ref_id"
| "setprop"
| "setupperprop"
| "getprop"
| "getupperprop"
| "make_instance"
| "load_module"
| "to_binary"
| "to_string"
| "std/internals.to_String"
| "std/internals.to_Number"
| "std/internals.to_Boolean"
| "std/internals.to_Regexp"
| "std/internals.to_Regexp_with_flags"
)
}