#![allow(non_upper_case_globals)] use crate::{ convalue::ConValue, env::Environment, error::{Error, IResult}, }; use std::io::{Write, stdout}; /// A function built into the interpreter. #[derive(Clone, Copy)] pub struct Builtin { /// An identifier to be used during registration pub name: &'static str, /// The signature, displayed when the builtin is printed pub desc: &'static str, /// The function to be run when called pub func: &'static dyn Fn(&mut Environment, &[ConValue]) -> IResult, } impl Builtin { /// Constructs a new Builtin pub const fn new( name: &'static str, desc: &'static str, func: &'static impl Fn(&mut Environment, &[ConValue]) -> IResult, ) -> Builtin { Builtin { name, desc, func } } pub const fn description(&self) -> &'static str { self.desc } } impl std::fmt::Debug for Builtin { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("Builtin") .field("description", &self.desc) .finish_non_exhaustive() } } impl super::Callable for Builtin { fn call(&self, interpreter: &mut Environment, args: &[ConValue]) -> IResult { (self.func)(interpreter, args) } fn name(&self) -> cl_ast::Sym { self.name.into() } } /// Turns a function definition into a [Builtin]. /// /// ```rust /// # use cl_interpret::{builtin::builtin, convalue::ConValue}; /// let my_builtin = builtin! { /// /// Use the `@env` suffix to bind the environment! /// /// (needed for recursive calls) /// fn my_builtin(ConValue::Bool(b), rest @ ..) @env { /// // This is all Rust code! /// eprintln!("my_builtin({b}, ..)"); /// match rest { /// [] => Ok(ConValue::Empty), /// _ => my_builtin(env, rest), // Can be called as a normal function! /// } /// } /// }; /// ``` pub macro builtin( $(#[$($meta:tt)*])* fn $name:ident ($($arg:pat),*$(,)?) $(@$env:tt)? $body:block ) {{ $(#[$($meta)*])* fn $name(_env: &mut Environment, _args: &[ConValue]) -> IResult { // Set up the builtin! environment $(#[allow(unused)]let $env = _env;)? // Allow for single argument `fn foo(args @ ..)` pattern #[allow(clippy::redundant_at_rest_pattern, irrefutable_let_patterns)] let [$($arg),*] = _args else { Err($crate::error::Error::TypeError())? }; $body.map(Into::into) } Builtin { name: stringify!($name), desc: stringify![builtin fn $name($($arg),*)], func: &$name, } }} /// Constructs an array of [Builtin]s from pseudo-function definitions pub macro builtins($( $(#[$($meta:tt)*])* fn $name:ident ($($args:tt)*) $(@$env:tt)? $body:block )*) { [$(builtin!($(#[$($meta)*])* fn $name ($($args)*) $(@$env)? $body)),*] } /// Creates an [Error::BuiltinError] using interpolation of runtime expressions. /// See [std::format]. pub macro error_format ($($t:tt)*) { $crate::error::Error::BuiltinError(format!($($t)*)) } pub const Builtins: &[Builtin] = &builtins![ /// Unstable variadic format function fn fmt(args @ ..) { use std::fmt::Write; let mut out = String::new(); if let Err(e) = args.iter().try_for_each(|arg| write!(out, "{arg}")) { eprintln!("{e}"); } Ok(out) } /// Prints the arguments in-order, with no separators fn print(args @ ..) { let mut out = stdout().lock(); args.iter().try_for_each(|arg| write!(out, "{arg}") ).ok(); Ok(()) } /// Prints the arguments in-order, followed by a newline fn println(args @ ..) { let mut out = stdout().lock(); args.iter().try_for_each(|arg| write!(out, "{arg}") ).ok(); writeln!(out).ok(); Ok(()) } /// Debug-prints the argument, returning a copy fn dbg(arg) { println!("{arg:?}"); Ok(arg.clone()) } /// Debug-prints the argument fn dbgp(args @ ..) { let mut out = stdout().lock(); args.iter().try_for_each(|arg| writeln!(out, "{arg:#?}") ).ok(); Ok(()) } fn panic(args @ ..) @env { use std::fmt::Write; let mut out = String::new(); if let Err(e) = args.iter().try_for_each(|arg| write!(out, "{arg}")) { println!("{e}"); } let mut stdout = stdout().lock(); write!(stdout, "Explicit panic: `").ok(); args.iter().try_for_each(|arg| write!(stdout, "{arg}") ).ok(); writeln!(stdout, "`").ok(); Err(Error::Panic(out))?; Ok(()) } /// Dumps the environment fn dump() @env { println!("{env}"); Ok(()) } /// Gets all global variables in the environment fn globals() @env { let globals = env.globals(); Ok(ConValue::Slice(globals.base, globals.binds.len())) } fn builtins() @env { let len = env.globals().binds.len(); for builtin in 0..len { if let Some(value @ ConValue::Builtin(_)) = env.get_id(builtin) { println!("{builtin}: {value}") } } Ok(()) } fn alloca(ConValue::Int(len)) @env { Ok(env.alloca(ConValue::Empty, *len as usize)) } /// Returns the length of the input list as a [ConValue::Int] fn len(list) @env { Ok(match list { ConValue::Empty => 0, ConValue::Str(s) => s.chars().count() as _, ConValue::String(s) => s.chars().count() as _, ConValue::Ref(r) => { return len(env, &[env.get_id(*r).ok_or(Error::StackOverflow(*r))?.clone()]) } ConValue::Slice(_, len) => *len as _, ConValue::Array(arr) => arr.len() as _, ConValue::Tuple(t) => t.len() as _, _ => Err(Error::TypeError())?, }) } fn push(ConValue::Ref(index), item) @env{ let Some(ConValue::Array(v)) = env.get_id_mut(*index) else { Err(Error::TypeError())? }; let mut items = std::mem::take(v).into_vec(); items.push(item.clone()); *v = items.into_boxed_slice(); Ok(ConValue::Empty) } fn chars(string) @env { Ok(match string { ConValue::Str(s) => ConValue::Array(s.chars().map(Into::into).collect()), ConValue::String(s) => ConValue::Array(s.chars().map(Into::into).collect()), ConValue::Ref(r) => { return chars(env, &[env.get_id(*r).ok_or(Error::StackOverflow(*r))?.clone()]) } _ => Err(Error::TypeError())?, }) } fn dump_symbols() { println!("{}", cl_structures::intern::string_interner::StringInterner::global()); Ok(ConValue::Empty) } fn slice_of(ConValue::Ref(arr), ConValue::Int(start)) { Ok(ConValue::Slice(*arr, *start as usize)) } /// Returns a shark fn shark() { Ok('\u{1f988}') } ]; pub const Math: &[Builtin] = &builtins![ /// Multiplication `a * b` fn mul(lhs, rhs) { Ok(match (lhs, rhs) { (ConValue::Empty, ConValue::Empty) => ConValue::Empty, (ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a * b), _ => Err(Error::TypeError())? }) } /// Division `a / b` fn div(lhs, rhs) { Ok(match (lhs, rhs){ (ConValue::Empty, ConValue::Empty) => ConValue::Empty, (ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a / b), _ => Err(Error::TypeError())? }) } /// Remainder `a % b` fn rem(lhs, rhs) { Ok(match (lhs, rhs) { (ConValue::Empty, ConValue::Empty) => ConValue::Empty, (ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a % b), _ => Err(Error::TypeError())?, }) } /// Addition `a + b` fn add(lhs, rhs) { Ok(match (lhs, rhs) { (ConValue::Empty, ConValue::Empty) => ConValue::Empty, (ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a + b), (ConValue::Str(a), ConValue::Str(b)) => (a.to_string() + b).into(), (ConValue::Str(a), ConValue::String(b)) => (a.to_string() + b).into(), (ConValue::String(a), ConValue::Str(b)) => (a.to_string() + b).into(), (ConValue::String(a), ConValue::String(b)) => (a.to_string() + b).into(), (ConValue::Str(s), ConValue::Char(c)) => { let mut s = s.to_string(); s.push(*c); s.into() } (ConValue::String(s), ConValue::Char(c)) => { let mut s = s.to_string(); s.push(*c); s.into() } (ConValue::Char(a), ConValue::Char(b)) => { ConValue::String([a, b].into_iter().collect::()) } _ => Err(Error::TypeError())? }) } /// Subtraction `a - b` fn sub(lhs, rhs) { Ok(match (lhs, rhs) { (ConValue::Empty, ConValue::Empty) => ConValue::Empty, (ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a - b), _ => Err(Error::TypeError())?, }) } /// Shift Left `a << b` fn shl(lhs, rhs) { Ok(match (lhs, rhs) { (ConValue::Empty, ConValue::Empty) => ConValue::Empty, (ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a << b), _ => Err(Error::TypeError())?, }) } /// Shift Right `a >> b` fn shr(lhs, rhs) { Ok(match (lhs, rhs) { (ConValue::Empty, ConValue::Empty) => ConValue::Empty, (ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a >> b), _ => Err(Error::TypeError())?, }) } /// Bitwise And `a & b` fn and(lhs, rhs) { Ok(match (lhs, rhs) { (ConValue::Empty, ConValue::Empty) => ConValue::Empty, (ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a & b), (ConValue::Bool(a), ConValue::Bool(b)) => ConValue::Bool(a & b), _ => Err(Error::TypeError())?, }) } /// Bitwise Or `a | b` fn or(lhs, rhs) { Ok(match (lhs, rhs) { (ConValue::Empty, ConValue::Empty) => ConValue::Empty, (ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a | b), (ConValue::Bool(a), ConValue::Bool(b)) => ConValue::Bool(a | b), _ => Err(Error::TypeError())?, }) } /// Bitwise Exclusive Or `a ^ b` fn xor(lhs, rhs) { Ok(match (lhs, rhs) { (ConValue::Empty, ConValue::Empty) => ConValue::Empty, (ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a ^ b), (ConValue::Bool(a), ConValue::Bool(b)) => ConValue::Bool(a ^ b), _ => Err(Error::TypeError())?, }) } #[allow(non_snake_case)] fn RangeExc(start, end) @env { Ok(ConValue::TupleStruct(Box::new(( "RangeExc", Box::new([start.clone(), end.clone()]) )))) } #[allow(non_snake_case)] fn RangeInc(start, end) @env { Ok(ConValue::TupleStruct(Box::new(( "RangeInc", Box::new([start.clone(), end.clone()]) )))) } #[allow(non_snake_case)] fn RangeTo(end) @env { Ok(ConValue::TupleStruct(Box::new(( "RangeTo", Box::new([end.clone()]) )))) } #[allow(non_snake_case)] fn RangeToInc(end) @env { Ok(ConValue::TupleStruct(Box::new(( "RangeToInc", Box::new([end.clone()]) )))) } /// Negates the ConValue fn neg(tail) { Ok(match tail { ConValue::Empty => ConValue::Empty, ConValue::Int(v) => ConValue::Int(v.wrapping_neg()), ConValue::Float(v) => ConValue::Float(-v), _ => Err(Error::TypeError())?, }) } /// Inverts the ConValue fn not(tail) { Ok(match tail { ConValue::Empty => ConValue::Empty, ConValue::Int(v) => ConValue::Int(!v), ConValue::Bool(v) => ConValue::Bool(!v), _ => Err(Error::TypeError())?, }) } /// Compares two values fn cmp(head, tail) { Ok(ConValue::Int(match (head, tail) { (ConValue::Int(a), ConValue::Int(b)) => a.cmp(b) as _, (ConValue::Bool(a), ConValue::Bool(b)) => a.cmp(b) as _, (ConValue::Char(a), ConValue::Char(b)) => a.cmp(b) as _, (ConValue::Str(a), ConValue::Str(b)) => a.cmp(b) as _, (ConValue::Str(a), ConValue::String(b)) => a.to_ref().cmp(b.as_str()) as _, (ConValue::String(a), ConValue::Str(b)) => a.as_str().cmp(b.to_ref()) as _, (ConValue::String(a), ConValue::String(b)) => a.cmp(b) as _, _ => Err(error_format!("Incomparable values: {head}, {tail}"))? })) } /// Does the opposite of `&` fn deref(tail) @env { Ok(match tail { ConValue::Ref(v) => env.get_id(*v).cloned().ok_or(Error::StackOverflow(*v))?, _ => tail.clone(), }) } ];