diff --git a/compiler/cl-interpret/src/builtin.rs b/compiler/cl-interpret/src/builtin.rs index 57f650d..e4ca79f 100644 --- a/compiler/cl-interpret/src/builtin.rs +++ b/compiler/cl-interpret/src/builtin.rs @@ -1,124 +1,200 @@ -//! Implementations of built-in functions +#![allow(non_upper_case_globals)] -use super::{ +use crate::{ convalue::ConValue, env::Environment, error::{Error, IResult}, - BuiltIn, Callable, }; -use cl_ast::Sym; use std::{ io::{stdout, Write}, - rc::Rc, slice, }; -builtins! { - const MISC; +/// A function built into the interpreter. +#[derive(Clone, Copy)] +pub struct Builtin { + /// An identifier to be used during registration + name: &'static str, + /// The signature, displayed when the builtin is printed + desc: &'static str, + /// The function to be run when called + 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::{builtin2::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 + $(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::BuiltinDebug] using interpolation of runtime expressions. +/// See [std::format]. +pub macro error_format ($($t:tt)*) { + $crate::error::Error::BuiltinDebug(format!($($t)*)) +} + +pub const Builtins: &[Builtin] = &builtins![ /// Unstable variadic format function - pub fn format<_, args> () -> IResult { + fn fmt(args @ ..) { use std::fmt::Write; let mut out = String::new(); - for arg in args { - write!(out, "{arg}").ok(); + if let Err(e) = args.iter().try_for_each(|arg| write!(out, "{arg}")) { + eprintln!("{e}"); } - Ok(ConValue::String(out.into())) + Ok(out) } - /// Unstable variadic print function - pub fn print<_, args> () -> IResult { + /// Prints the arguments in-order, with no separators + fn print(args @ ..) { let mut out = stdout().lock(); - for arg in args { - write!(out, "{arg}").ok(); - } - Ok(ConValue::Empty) + args.iter().try_for_each(|arg| write!(out, "{arg}") ).ok(); + Ok(()) } - /// Unstable variadic println function - pub fn println<_, args> () -> IResult { + /// Prints the arguments in-order, followed by a newline + fn println(args @ ..) { let mut out = stdout().lock(); - for arg in args { - write!(out, "{arg}").ok(); - } + args.iter().try_for_each(|arg| write!(out, "{arg}") ).ok(); writeln!(out).ok(); - Ok(ConValue::Empty) + Ok(()) } - /// Prints the [Debug](std::fmt::Debug) version of the input values, - /// and passes them back as a tuple. - pub fn dbg<_, args> () -> IResult { + /// 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(); - for arg in args { - writeln!(out, "{arg:?}").ok(); + args.iter().try_for_each(|arg| writeln!(out, "{arg:#?}") ).ok(); + Ok(()) + } + + /// Dumps the environment + fn dump() @env { + println!("{env}"); + Ok(()) + } + + fn builtins() @env { + for builtin in env.builtins().values().flatten() { + println!("{builtin}"); } - Ok(args.into()) + Ok(()) } - /// Prints the pretty [Debug](std::fmt::Debug) version of the input values. - pub fn dbgp<_, args> () -> IResult { - let mut out = stdout().lock(); - for arg in args { - writeln!(out, "{arg:#?}").ok(); - } - Ok(ConValue::Empty) - } - - /// Dumps info from the environment - pub fn dump() -> IResult { - println!("{}", *env); - Ok(ConValue::Empty) - } - - /// Gets the length of a container or range - pub fn len(list) -> IResult { - Ok(ConValue::Int(match list { + /// Returns the length of the input list as a [ConValue::Int] + fn len(list) @env { + Ok(match list { ConValue::Empty => 0, ConValue::String(s) => s.chars().count() as _, - ConValue::Ref(r) => return len.call(env, slice::from_ref(r.as_ref())), + ConValue::Ref(r) => return len(env, slice::from_ref(r.as_ref())), ConValue::Array(t) => t.len() as _, ConValue::Tuple(t) => t.len() as _, ConValue::RangeExc(start, end) => (end - start) as _, ConValue::RangeInc(start, end) => (end - start + 1) as _, _ => Err(Error::TypeError)?, - })) + }) } - /// Gets a line of text from stdin - pub fn get_line() -> IResult { + /// Gets a line of input from stdin + fn get_line() { let mut line = String::new(); let _ = std::io::stdin().read_line(&mut line); - Ok(ConValue::String(line.into())) + Ok(line) } - /// Lists the potential "upvars" (lifted environment) of a function - pub fn collect_upvars(ConValue::Function(f)) -> IResult { - use crate::function::collect_upvars::collect_upvars; - for (name, bind) in collect_upvars(f.decl(), env) { - match bind { - Some(bind) =>println!("{name}: {bind}"), - None => println!("{name}: _"), - } - } - Ok(ConValue::Empty) + /// Returns a shark + fn shark() { + Ok('\u{1f988}') } - /// Lists the collected environment of a function. - pub fn upvars(ConValue::Function(f)) -> IResult { - let uv = f.upvars(); - for (name, bind) in uv.iter() { - match bind { - Some(bind) =>println!("{name}: {bind}"), - None => println!("{name}: _"), - } - } - Ok(ConValue::Empty) + /// Clears the screen + fn clear() { + println!("\x1b[G"); + Ok(()) } -} +]; -builtins! { - const BINARY; +pub const Math: &[Builtin] = &builtins![ /// Multiplication `a * b` - pub fn mul(lhs, rhs) -> IResult { + fn mul(lhs, rhs) { Ok(match (lhs, rhs) { (ConValue::Empty, ConValue::Empty) => ConValue::Empty, (ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a * b), @@ -127,7 +203,7 @@ builtins! { } /// Division `a / b` - pub fn div(lhs, rhs) -> IResult { + fn div(lhs, rhs) { Ok(match (lhs, rhs){ (ConValue::Empty, ConValue::Empty) => ConValue::Empty, (ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a / b), @@ -136,7 +212,7 @@ builtins! { } /// Remainder `a % b` - pub fn rem(lhs, rhs) -> IResult { + fn rem(lhs, rhs) { Ok(match (lhs, rhs) { (ConValue::Empty, ConValue::Empty) => ConValue::Empty, (ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a % b), @@ -145,7 +221,7 @@ builtins! { } /// Addition `a + b` - pub fn add(lhs, rhs) -> IResult { + fn add(lhs, rhs) { Ok(match (lhs, rhs) { (ConValue::Empty, ConValue::Empty) => ConValue::Empty, (ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a + b), @@ -155,7 +231,7 @@ builtins! { } /// Subtraction `a - b` - pub fn sub(lhs, rhs) -> IResult { + fn sub(lhs, rhs) { Ok(match (lhs, rhs) { (ConValue::Empty, ConValue::Empty) => ConValue::Empty, (ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a - b), @@ -164,7 +240,7 @@ builtins! { } /// Shift Left `a << b` - pub fn shl(lhs, rhs) -> IResult { + fn shl(lhs, rhs) { Ok(match (lhs, rhs) { (ConValue::Empty, ConValue::Empty) => ConValue::Empty, (ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a << b), @@ -173,7 +249,7 @@ builtins! { } /// Shift Right `a >> b` - pub fn shr(lhs, rhs) -> IResult { + fn shr(lhs, rhs) { Ok(match (lhs, rhs) { (ConValue::Empty, ConValue::Empty) => ConValue::Empty, (ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a >> b), @@ -182,7 +258,7 @@ builtins! { } /// Bitwise And `a & b` - pub fn and(lhs, rhs) -> IResult { + fn and(lhs, rhs) { Ok(match (lhs, rhs) { (ConValue::Empty, ConValue::Empty) => ConValue::Empty, (ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a & b), @@ -192,7 +268,7 @@ builtins! { } /// Bitwise Or `a | b` - pub fn or(lhs, rhs) -> IResult { + fn or(lhs, rhs) { Ok(match (lhs, rhs) { (ConValue::Empty, ConValue::Empty) => ConValue::Empty, (ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a | b), @@ -202,7 +278,7 @@ builtins! { } /// Bitwise Exclusive Or `a ^ b` - pub fn xor(lhs, rhs) -> IResult { + fn xor(lhs, rhs) { Ok(match (lhs, rhs) { (ConValue::Empty, ConValue::Empty) => ConValue::Empty, (ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a ^ b), @@ -211,58 +287,24 @@ builtins! { }) } - /// Tests whether `a < b` - pub fn lt(lhs, rhs) -> IResult { - cmp!(lhs, rhs, false, <) - } - - /// Tests whether `a <= b` - pub fn lt_eq(lhs, rhs) -> IResult { - cmp!(lhs, rhs, true, <=) - } - - /// Tests whether `a == b` - pub fn eq(lhs, rhs) -> IResult { - cmp!(lhs, rhs, true, ==) - } - - /// Tests whether `a != b` - pub fn neq(lhs, rhs) -> IResult { - cmp!(lhs, rhs, false, !=) - } - - /// Tests whether `a <= b` - pub fn gt_eq(lhs, rhs) -> IResult { - cmp!(lhs, rhs, true, >=) - } - - /// Tests whether `a < b` - pub fn gt(lhs, rhs) -> IResult { - cmp!(lhs, rhs, false, >) - } -} -builtins! { - const RANGE; /// Exclusive Range `a..b` - pub fn range_exc(lhs, rhs) -> IResult { - let (&ConValue::Int(lhs), &ConValue::Int(rhs)) = (lhs, rhs) else { + fn range_exc(from, to) { + let (&ConValue::Int(from), &ConValue::Int(to)) = (from, to) else { Err(Error::TypeError)? }; - Ok(ConValue::RangeExc(lhs, rhs.saturating_sub(1))) + Ok(ConValue::RangeExc(from, to)) } /// Inclusive Range `a..=b` - pub fn range_inc(lhs, rhs) -> IResult { - let (&ConValue::Int(lhs), &ConValue::Int(rhs)) = (lhs, rhs) else { + fn range_inc(from, to) { + let (&ConValue::Int(from), &ConValue::Int(to)) = (from, to) else { Err(Error::TypeError)? }; - Ok(ConValue::RangeInc(lhs, rhs)) + Ok(ConValue::RangeInc(from, to)) } -} -builtins! { - const UNARY; + /// Negates the ConValue - pub fn neg(tail) -> IResult { + fn neg(tail) { Ok(match tail { ConValue::Empty => ConValue::Empty, ConValue::Int(v) => ConValue::Int(v.wrapping_neg()), @@ -272,7 +314,7 @@ builtins! { } /// Inverts the ConValue - pub fn not(tail) -> IResult { + fn not(tail) { Ok(match tail { ConValue::Empty => ConValue::Empty, ConValue::Int(v) => ConValue::Int(!v), @@ -281,64 +323,23 @@ builtins! { }) } + /// 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::String(a), ConValue::String(b)) => a.cmp(b) as _, + _ => Err(error_format!("Incomparable values: {head}, {tail}"))? + })) + } + /// Does the opposite of `&` - pub fn deref(tail) -> IResult { + fn deref(tail) { + use std::rc::Rc; Ok(match tail { ConValue::Ref(v) => Rc::as_ref(v).clone(), _ => tail.clone(), }) } -} - -/// Turns an argument slice into an array with the (inferred) correct number of elements -pub fn to_args(args: &[ConValue]) -> IResult<&[ConValue; N]> { - args.try_into() - .map_err(|_| Error::ArgNumber { want: N, got: args.len() }) -} - -/// Turns function definitions into ZSTs which implement [Callable] and [BuiltIn] -macro builtins ( - $(prefix = $prefix:literal)? - const $defaults:ident $( = [$($additional_builtins:expr),*$(,)?])?; - $( - $(#[$meta:meta])*$vis:vis fn $name:ident$(<$env:tt, $args:tt>)? ( $($($arg:pat),+$(,)?)? ) $(-> $rety:ty)? - $body:block - )* -) { - /// Builtins to load when a new interpreter is created - pub const $defaults: &[&dyn BuiltIn] = &[$(&$name,)* $($additional_builtins)*]; - $( - $(#[$meta])* #[allow(non_camel_case_types)] #[derive(Clone, Debug)] - /// ```rust,ignore - #[doc = stringify!(builtin! fn $name($($($arg),*)?) $(-> $rety)? $body)] - /// ``` - $vis struct $name; - impl BuiltIn for $name { - fn description(&self) -> &str { concat!("builtin ", stringify!($name), stringify!(($($($arg),*)?) )) } - } - impl Callable for $name { - #[allow(unused, irrefutable_let_patterns)] - fn call(&self, env: &mut Environment, args: &[ConValue]) $(-> $rety)? { - $(let $env = env; - let $args = args;)? - $(let [$($arg),*] = to_args(args)? else { - Err(Error::TypeError)? - };)? - $body - } - fn name(&self) -> Sym { stringify!($name).into() } - } - )* -} - -/// Templates comparison functions for [ConValue] -macro cmp ($a:expr, $b:expr, $empty:literal, $op:tt) { - match ($a, $b) { - (ConValue::Empty, ConValue::Empty) => Ok(ConValue::Bool($empty)), - (ConValue::Int(a), ConValue::Int(b)) => Ok(ConValue::Bool(a $op b)), - (ConValue::Bool(a), ConValue::Bool(b)) => Ok(ConValue::Bool(a $op b)), - (ConValue::Char(a), ConValue::Char(b)) => Ok(ConValue::Bool(a $op b)), - (ConValue::String(a), ConValue::String(b)) => Ok(ConValue::Bool(&**a $op &**b)), - _ => Err(Error::TypeError) - } -} +]; diff --git a/compiler/cl-interpret/src/convalue.rs b/compiler/cl-interpret/src/convalue.rs index 9553a2b..cdb033b 100644 --- a/compiler/cl-interpret/src/convalue.rs +++ b/compiler/cl-interpret/src/convalue.rs @@ -4,9 +4,9 @@ use cl_ast::{format::FmtAdapter, ExprKind, Sym}; use super::{ + builtin::Builtin, error::{Error, IResult}, - function::Function, - BuiltIn, Callable, Environment, + function::Function, Callable, Environment, }; use std::{collections::HashMap, ops::*, rc::Rc}; @@ -47,7 +47,7 @@ pub enum ConValue { /// A callable thing Function(Rc), /// A built-in function - BuiltIn(&'static dyn BuiltIn), + Builtin(&'static Builtin), } impl ConValue { @@ -113,14 +113,14 @@ impl Callable for ConValue { fn name(&self) -> Sym { match self { ConValue::Function(func) => func.name(), - ConValue::BuiltIn(func) => func.name(), + ConValue::Builtin(func) => func.name(), _ => "".into(), } } fn call(&self, interpreter: &mut Environment, args: &[ConValue]) -> IResult { match self { Self::Function(func) => func.call(interpreter, args), - Self::BuiltIn(func) => func.call(interpreter, args), + Self::Builtin(func) => func.call(interpreter, args), _ => Err(Error::NotCallable(self.clone())), } } @@ -170,7 +170,7 @@ from! { ExprKind => ConValue::Quote, Function => ConValue::Function, Vec => ConValue::Tuple, - &'static dyn BuiltIn => ConValue::BuiltIn, + &'static Builtin => ConValue::Builtin, } impl From<()> for ConValue { fn from(_: ()) -> Self { @@ -328,7 +328,7 @@ impl std::fmt::Display for ConValue { ConValue::Function(func) => { write!(f, "{}", func.decl()) } - ConValue::BuiltIn(func) => { + ConValue::Builtin(func) => { write!(f, "{}", func.description()) } } diff --git a/compiler/cl-interpret/src/env.rs b/compiler/cl-interpret/src/env.rs index 7276a15..c6aa59a 100644 --- a/compiler/cl-interpret/src/env.rs +++ b/compiler/cl-interpret/src/env.rs @@ -1,11 +1,13 @@ //! Lexical and non-lexical scoping for variables +use crate::builtin::Builtin; + use super::{ - builtin::{BINARY, MISC, RANGE, UNARY}, + builtin::{Builtins, Math}, convalue::ConValue, error::{Error, IResult}, function::Function, - BuiltIn, Callable, Interpret, + Callable, Interpret, }; use cl_ast::{Function as FnDecl, Sym}; use std::{ @@ -20,6 +22,7 @@ type StackFrame = HashMap>; /// Implements a nested lexical scope #[derive(Clone, Debug)] pub struct Environment { + builtin: StackFrame, global: Vec<(StackFrame, &'static str)>, frames: Vec<(StackFrame, &'static str)>, } @@ -49,19 +52,19 @@ impl Display for Environment { impl Default for Environment { fn default() -> Self { Self { - global: vec![ - (to_hashmap(RANGE), "range ops"), - (to_hashmap(UNARY), "unary ops"), - (to_hashmap(BINARY), "binary ops"), - (to_hashmap(MISC), "builtins"), - (HashMap::new(), "globals"), - ], + builtin: to_hashmap2(Builtins.iter().chain(Math.iter())), + global: vec![(HashMap::new(), "globals")], frames: vec![], } } } -fn to_hashmap(from: &[&'static dyn BuiltIn]) -> HashMap> { - from.iter().map(|&v| (v.name(), Some(v.into()))).collect() +// fn to_hashmap(from: &[&'static dyn BuiltIn]) -> HashMap> { +// from.iter().map(|&v| (v.name(), Some(v.into()))).collect() +// } +fn to_hashmap2(from: impl IntoIterator) -> HashMap> { + from.into_iter() + .map(|v| (v.name(), Some(v.into()))) + .collect() } impl Environment { @@ -70,7 +73,19 @@ impl Environment { } /// Creates an [Environment] with no [builtins](super::builtin) pub fn no_builtins() -> Self { - Self { global: vec![(Default::default(), "globals")], frames: vec![] } + Self { + builtin: HashMap::new(), + global: vec![(Default::default(), "globals")], + frames: vec![], + } + } + + pub fn builtins(&self) -> &StackFrame { + &self.builtin + } + + pub fn add_builtin(&mut self, builtin: &'static Builtin) { + self.builtin.insert(builtin.name(), Some(builtin.into())); } pub fn push_frame(&mut self, name: &'static str, frame: StackFrame) { @@ -112,7 +127,7 @@ impl Environment { return Ok(var); } } - Err(Error::NotDefined(id)) + self.builtin.get_mut(&id).ok_or(Error::NotDefined(id)) } /// Resolves a variable immutably. /// @@ -132,7 +147,11 @@ impl Environment { _ => (), } } - Err(Error::NotDefined(id)) + self.builtin + .get(&id) + .cloned() + .flatten() + .ok_or(Error::NotDefined(id)) } pub(crate) fn get_local(&self, id: Sym) -> IResult { @@ -145,7 +164,7 @@ impl Environment { } Err(Error::NotInitialized(id)) } - + /// Inserts a new [ConValue] into this [Environment] pub fn insert(&mut self, id: Sym, value: Option) { if let Some((frame, _)) = self.frames.last_mut() { diff --git a/compiler/cl-interpret/src/error.rs b/compiler/cl-interpret/src/error.rs index a03a383..bc83213 100644 --- a/compiler/cl-interpret/src/error.rs +++ b/compiler/cl-interpret/src/error.rs @@ -44,6 +44,8 @@ pub enum Error { PatFailed(Pattern), /// Fell through a non-exhaustive match MatchNonexhaustive, + /// Error produced by a Builtin + BuiltinDebug(String), } impl std::error::Error for Error {} @@ -89,6 +91,7 @@ impl std::fmt::Display for Error { Error::MatchNonexhaustive => { write!(f, "Fell through a non-exhaustive match expression!") } + Error::BuiltinDebug(s) => write!(f, "DEBUG: {s}"), } } } diff --git a/compiler/cl-interpret/src/interpret.rs b/compiler/cl-interpret/src/interpret.rs index 275ebf5..3c2bcf0 100644 --- a/compiler/cl-interpret/src/interpret.rs +++ b/compiler/cl-interpret/src/interpret.rs @@ -345,7 +345,8 @@ mod assignment { pub(super) fn pat_assign(env: &mut Environment, pat: &Pattern, value: ConValue) -> IResult<()> { let mut substitution = HashMap::new(); - append_sub(&mut substitution, pat, value).map_err(|_| Error::PatFailed(pat.clone()))?; + append_sub(&mut substitution, pat, value) + .map_err(|_| Error::PatFailed(pat.clone().into()))?; for (path, value) in substitution { assign_path(env, path, value)?; } diff --git a/compiler/cl-interpret/src/lib.rs b/compiler/cl-interpret/src/lib.rs index 1928c9c..0a88f9d 100644 --- a/compiler/cl-interpret/src/lib.rs +++ b/compiler/cl-interpret/src/lib.rs @@ -17,11 +17,6 @@ pub trait Callable: std::fmt::Debug { fn name(&self) -> Sym; } -/// [BuiltIn]s are [Callable]s with bespoke definitions -pub trait BuiltIn: std::fmt::Debug + Callable { - fn description(&self) -> &str; -} - pub mod convalue; pub mod interpret;