use crate::_builtins::BUILTINS;
use crate::value;
use crate::value::{Dict, Object, RefValue, Value};
use crate::vm::{Accept, Context, Reject};
extern crate self as tokay;
use std::io::{self, Write};
use tokay_macros::tokay_function;
pub struct Builtin {
pub name: &'static str, pub func: fn(Option<&mut Context>, Vec<RefValue>, Option<Dict>) -> Result<Accept, Reject>, }
impl Builtin {
pub fn get(ident: &str) -> Option<&'static Builtin> {
for builtin in &BUILTINS {
if builtin.name == ident {
return Some(builtin);
}
}
None
}
pub fn get_method(type_name: &str, method_name: &str) -> Result<&'static Builtin, String> {
for builtin in &BUILTINS {
if builtin.name.starts_with(type_name)
&& builtin.name.ends_with(method_name)
&& builtin.name.len() == type_name.len() + method_name.len() + 1
&& builtin.name.chars().nth(type_name.len()) == Some('_')
{
return Ok(builtin);
}
}
Err(format!("Method '{}_{}' not found", type_name, method_name))
}
pub fn call(
&self,
context: Option<&mut Context>,
args: Vec<RefValue>,
) -> Result<Option<RefValue>, String> {
match (self.func)(context, args, None) {
Ok(Accept::Next | Accept::Hold) => Ok(None),
Ok(Accept::Push(capture)) => Ok(Some(capture.get_value())),
Err(Reject::Error(error)) => Err(error.message),
other => Err(format!("Cannot handle {:?} on direct call", other)),
}
}
}
#[derive(Clone)]
pub struct BuiltinRef(pub &'static Builtin);
impl Object for BuiltinRef {
fn name(&self) -> &'static str {
"builtin"
}
fn repr(&self) -> String {
format!("<{} {}>", self.name(), self.0.name)
}
fn is_callable(&self, _without_arguments: bool) -> bool {
true }
fn is_consuming(&self) -> bool {
crate::utils::identifier_is_consumable(self.0.name)
}
fn call(
&self,
context: &mut Context,
args: usize,
nargs: Option<Dict>,
) -> Result<Accept, Reject> {
let args = context.drain(args);
(self.0.func)(Some(context), args, nargs)
}
}
impl PartialEq for BuiltinRef {
fn eq(&self, other: &Self) -> bool {
self.id() == other.id()
}
}
impl PartialOrd for BuiltinRef {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
self.id().partial_cmp(&other.id())
}
}
impl std::fmt::Debug for BuiltinRef {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0.name)
}
}
impl From<&'static Builtin> for RefValue {
fn from(builtin: &'static Builtin) -> Self {
Value::Object(Box::new(BuiltinRef(builtin))).into()
}
}
tokay_function!("chr(i)", {
RefValue::from(format!(
"{}",
std::char::from_u32(i.to_usize() as u32).unwrap()
))
.into()
});
#[test]
fn test_chr() {
assert_eq!(
crate::run("i = ord(\"€\"); i chr(i)", ""),
Ok(Some(value![[(8364 as usize), "€"]]))
);
}
tokay_function!("ord(c)", {
let c = c.to_string();
if c.chars().count() != 1 {
Err(format!(
"{} expects a single character, but received string of length {}",
__function,
c.len()
)
.into())
} else {
RefValue::from(c.chars().next().unwrap() as usize).into()
}
});
#[test]
fn test_ord() {
assert_eq!(
crate::run("ord(\"12\")", ""),
Err(
"Line 1, column 1: ord() expects a single character, but received string of length 2"
.to_string()
)
);
assert_eq!(
crate::run("ord(\"\")", ""),
Err(
"Line 1, column 1: ord() expects a single character, but received string of length 0"
.to_string()
)
);
}
tokay_function!("print(*args)", {
if args.len() == 0 && context.is_some() {
if let Some(capture) = context.unwrap().get_capture(0) {
print!("{}", capture.to_string());
}
} else {
for i in 0..args.len() {
if i > 0 {
print!(" ");
}
print!("{}", args[i].to_string());
}
}
print!("\n");
io::stdout().flush().unwrap();
value!(void).into()
});
tokay_function!("repr(value)", value!(value.repr()).into());
#[test]
fn test_repr() {
assert_eq!(
crate::run("repr(\"Hello World\")", ""),
Ok(Some(value!("\"Hello World\"")))
);
}
tokay_function!("type(value)", value!(value.name()).into());
#[test]
fn test_type() {
assert_eq!(
crate::run(
"type(void) type(true) type(1) type(23.5) type(\"hello\") type((1,2))",
""
),
Ok(Some(value!([
"void", "bool", "int", "float", "str", "list"
])))
);
}
#[test]
fn test_buildin_call_error_reporting() {
for (call, msg) in [
(
"str_replace()",
"Line 1, column 1: str_replace() expected argument 'str'",
),
(
"str_replace(1, 2, 3, 4, 5)",
"Line 1, column 1: str_replace() expected at most 4 arguments (5 given)",
),
(
"str_replace(1, 2, x=3)",
"Line 1, column 1: str_replace() doesn't accept named argument 'x'",
),
(
"str_replace(1, 2, x=3, y=4)",
"Line 1, column 1: str_replace() doesn't accept named arguments (2 given)",
),
] {
assert_eq!(crate::run(&call, ""), Err(msg.to_owned()));
}
}