cl-interpret: Builtin refactor
- Everything is different now - Builtins are now built on top of Rust functions, so they can be recursive! - TODO: allow macro-defined builtins to call each other? - The builtins! macro is a lot nicer to work with - No redundant return value - Maps the result over Into::into, allowing for type inference! - Uses explicit pattern syntax instead of weird binding, where possible - Does not #[allow(unused)], so you'll get unused variable warnings now!
This commit is contained in:
parent
0c2b0002ce
commit
d95d35268e
@ -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<ConValue>,
|
||||
}
|
||||
|
||||
impl Builtin {
|
||||
/// Constructs a new Builtin
|
||||
pub const fn new(
|
||||
name: &'static str,
|
||||
desc: &'static str,
|
||||
func: &'static impl Fn(&mut Environment, &[ConValue]) -> IResult<ConValue>,
|
||||
) -> 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<ConValue> {
|
||||
(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<ConValue> {
|
||||
// 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 version of the input values,
|
||||
/// and passes them back as a tuple.
|
||||
pub fn dbg<_, args> () -> IResult<ConValue> {
|
||||
/// 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();
|
||||
}
|
||||
Ok(args.into())
|
||||
args.iter().try_for_each(|arg| writeln!(out, "{arg:#?}") ).ok();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Prints the pretty [Debug](std::fmt::Debug) version of the input values.
|
||||
pub fn dbgp<_, args> () -> IResult<ConValue> {
|
||||
let mut out = stdout().lock();
|
||||
for arg in args {
|
||||
writeln!(out, "{arg:#?}").ok();
|
||||
}
|
||||
Ok(ConValue::Empty)
|
||||
/// Dumps the environment
|
||||
fn dump() @env {
|
||||
println!("{env}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Dumps info from the environment
|
||||
pub fn dump<env, _>() -> IResult<ConValue> {
|
||||
println!("{}", *env);
|
||||
Ok(ConValue::Empty)
|
||||
fn builtins() @env {
|
||||
for builtin in env.builtins().values().flatten() {
|
||||
println!("{builtin}");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Gets the length of a container or range
|
||||
pub fn len<env, _>(list) -> IResult<ConValue> {
|
||||
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<ConValue> {
|
||||
/// 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<env, _>(ConValue::Function(f)) -> IResult<ConValue> {
|
||||
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<ConValue> {
|
||||
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<ConValue> {
|
||||
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<ConValue> {
|
||||
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<ConValue> {
|
||||
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<ConValue> {
|
||||
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<ConValue> {
|
||||
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<ConValue> {
|
||||
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<ConValue> {
|
||||
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<ConValue> {
|
||||
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<ConValue> {
|
||||
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<ConValue> {
|
||||
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<ConValue> {
|
||||
cmp!(lhs, rhs, false, <)
|
||||
}
|
||||
|
||||
/// Tests whether `a <= b`
|
||||
pub fn lt_eq(lhs, rhs) -> IResult<ConValue> {
|
||||
cmp!(lhs, rhs, true, <=)
|
||||
}
|
||||
|
||||
/// Tests whether `a == b`
|
||||
pub fn eq(lhs, rhs) -> IResult<ConValue> {
|
||||
cmp!(lhs, rhs, true, ==)
|
||||
}
|
||||
|
||||
/// Tests whether `a != b`
|
||||
pub fn neq(lhs, rhs) -> IResult<ConValue> {
|
||||
cmp!(lhs, rhs, false, !=)
|
||||
}
|
||||
|
||||
/// Tests whether `a <= b`
|
||||
pub fn gt_eq(lhs, rhs) -> IResult<ConValue> {
|
||||
cmp!(lhs, rhs, true, >=)
|
||||
}
|
||||
|
||||
/// Tests whether `a < b`
|
||||
pub fn gt(lhs, rhs) -> IResult<ConValue> {
|
||||
cmp!(lhs, rhs, false, >)
|
||||
}
|
||||
}
|
||||
builtins! {
|
||||
const RANGE;
|
||||
/// Exclusive Range `a..b`
|
||||
pub fn range_exc(lhs, rhs) -> IResult<ConValue> {
|
||||
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<ConValue> {
|
||||
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<ConValue> {
|
||||
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<ConValue> {
|
||||
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<ConValue> {
|
||||
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<const N: usize>(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)
|
||||
}
|
||||
}
|
||||
];
|
||||
|
@ -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<Function>),
|
||||
/// 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<ConValue> {
|
||||
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> => 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())
|
||||
}
|
||||
}
|
||||
|
@ -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<Sym, Option<ConValue>>;
|
||||
/// 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<Sym, Option<ConValue>> {
|
||||
from.iter().map(|&v| (v.name(), Some(v.into()))).collect()
|
||||
// fn to_hashmap(from: &[&'static dyn BuiltIn]) -> HashMap<Sym, Option<ConValue>> {
|
||||
// from.iter().map(|&v| (v.name(), Some(v.into()))).collect()
|
||||
// }
|
||||
fn to_hashmap2(from: impl IntoIterator<Item = &'static Builtin>) -> HashMap<Sym, Option<ConValue>> {
|
||||
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<ConValue> {
|
||||
|
@ -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}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)?;
|
||||
}
|
||||
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user