interpreter: BuiltIn
overhaul!
- Allowed builtins to self-describe - Broke builtins into their own module - Created a macro to work with BuiltIns easier - Uses macro 2.0 syntax, so it requires passing in ALL externally referenced identifiers - Luckily, this is already a requirement of good, readable macro definitions! - As a temporary hack, turn overloadable operators into function calls - This is kind of pointless at the moment, since there can only be one definition of a function per name (no ADL/function overloading/traits/type namespaces yet!) - This is also pretty slow, but benchmarking shows it's not as slow as I thought (~400x slower in release mode than a native Rust implementation when running `fib.cl`/`fib.rs`. Totally unacceptable for most work, but this is a tree walk interpreter.) - TODO: Remove this when desugaring from operators to function calls is implemented
This commit is contained in:
parent
e9dc8a7e32
commit
5eb6411d53
16
libconlang/examples/fib.rs
Normal file
16
libconlang/examples/fib.rs
Normal file
@ -0,0 +1,16 @@
|
||||
// Calculate Fibonacci numbers
|
||||
|
||||
fn main() {
|
||||
for num in 0..=30 {
|
||||
println!("fib({num}) = {}", fib(num))
|
||||
}
|
||||
}
|
||||
|
||||
/// Implements the classic recursive definition of fib()
|
||||
fn fib(a: i64) -> i64 {
|
||||
if a > 1 {
|
||||
fib(a - 1) + fib(a - 2)
|
||||
} else {
|
||||
1
|
||||
}
|
||||
}
|
@ -15,7 +15,9 @@ pub trait Callable: std::fmt::Debug {
|
||||
}
|
||||
|
||||
/// [BuiltIn]s are [Callable]s with bespoke definitions
|
||||
pub trait BuiltIn: std::fmt::Debug + Callable {}
|
||||
pub trait BuiltIn: std::fmt::Debug + Callable {
|
||||
fn description(&self) -> &str;
|
||||
}
|
||||
|
||||
pub mod temp_type_impl {
|
||||
//! Temporary implementations of Conlang values
|
||||
@ -164,6 +166,7 @@ pub mod temp_type_impl {
|
||||
String => ConValue::String,
|
||||
Function => ConValue::Function,
|
||||
Vec<ConValue> => ConValue::Tuple,
|
||||
&'static dyn BuiltIn => ConValue::BuiltIn,
|
||||
}
|
||||
impl From<()> for ConValue {
|
||||
fn from(_: ()) -> Self {
|
||||
@ -246,27 +249,6 @@ pub mod temp_type_impl {
|
||||
_ => Err(Error::TypeError)?
|
||||
]
|
||||
}
|
||||
impl Neg for ConValue {
|
||||
type Output = IResult<Self>;
|
||||
fn neg(self) -> Self::Output {
|
||||
Ok(match self {
|
||||
ConValue::Empty => ConValue::Empty,
|
||||
ConValue::Int(v) => ConValue::Int(-v),
|
||||
_ => Err(Error::TypeError)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
impl Not for ConValue {
|
||||
type Output = IResult<Self>;
|
||||
fn not(self) -> Self::Output {
|
||||
Ok(match self {
|
||||
ConValue::Empty => ConValue::Empty,
|
||||
ConValue::Int(v) => ConValue::Int(!v),
|
||||
ConValue::Bool(v) => ConValue::Bool(!v),
|
||||
_ => Err(Error::TypeError)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
impl std::fmt::Display for ConValue {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
@ -301,7 +283,7 @@ pub mod temp_type_impl {
|
||||
write!(f, "fn {}", func.name())
|
||||
}
|
||||
ConValue::BuiltIn(func) => {
|
||||
write!(f, "internal fn {}", func.name())
|
||||
write!(f, "{}", func.description())
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -499,48 +481,51 @@ pub mod interpret {
|
||||
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||
let Binary { head, tail } = self;
|
||||
let mut head = head.interpret(env)?;
|
||||
// Short-circuiting ops
|
||||
for (op, tail) in tail {
|
||||
head = match op {
|
||||
match op {
|
||||
BinaryKind::LogAnd => {
|
||||
if head.truthy()? {
|
||||
tail.interpret(env)
|
||||
} else {
|
||||
return Ok(head); // Short circuiting
|
||||
head = tail.interpret(env)?;
|
||||
continue;
|
||||
}
|
||||
return Ok(head); // Short circuiting
|
||||
}
|
||||
BinaryKind::LogOr => {
|
||||
if !head.truthy()? {
|
||||
tail.interpret(env)
|
||||
} else {
|
||||
head = tail.interpret(env)?;
|
||||
continue;
|
||||
}
|
||||
return Ok(head); // Short circuiting
|
||||
}
|
||||
}
|
||||
BinaryKind::LogXor => {
|
||||
// TODO: It should be possible to assemble better error information from
|
||||
// this
|
||||
let (lhs, rhs) = (head.truthy()?, tail.interpret(env)?.truthy()?);
|
||||
Ok(ConValue::Bool(lhs ^ rhs))
|
||||
head = ConValue::Bool(head.truthy()? ^ tail.interpret(env)?.truthy()?);
|
||||
continue;
|
||||
}
|
||||
// TODO: For all overloadable operators, transmute into function call
|
||||
BinaryKind::Mul => head * tail.interpret(env)?,
|
||||
BinaryKind::Div => head / tail.interpret(env)?,
|
||||
BinaryKind::Rem => head % tail.interpret(env)?,
|
||||
BinaryKind::Add => head + tail.interpret(env)?,
|
||||
BinaryKind::Sub => head - tail.interpret(env)?,
|
||||
BinaryKind::Shl => head << tail.interpret(env)?,
|
||||
BinaryKind::Shr => head >> tail.interpret(env)?,
|
||||
BinaryKind::BitAnd => head & tail.interpret(env)?,
|
||||
BinaryKind::BitOr => head | tail.interpret(env)?,
|
||||
BinaryKind::BitXor => head ^ tail.interpret(env)?,
|
||||
BinaryKind::RangeExc => head.range_exc(tail.interpret(env)?),
|
||||
BinaryKind::RangeInc => head.range_inc(tail.interpret(env)?),
|
||||
BinaryKind::Lt => head.lt(&tail.interpret(env)?),
|
||||
BinaryKind::LtEq => head.lt_eq(&tail.interpret(env)?),
|
||||
BinaryKind::Equal => head.eq(&tail.interpret(env)?),
|
||||
BinaryKind::NotEq => head.neq(&tail.interpret(env)?),
|
||||
BinaryKind::GtEq => head.gt_eq(&tail.interpret(env)?),
|
||||
BinaryKind::Gt => head.gt(&tail.interpret(env)?),
|
||||
_ => {}
|
||||
}
|
||||
let tail = tail.interpret(env)?;
|
||||
head = match op {
|
||||
BinaryKind::Mul => env.call("mul", &[head, tail]),
|
||||
BinaryKind::Div => env.call("div", &[head, tail]),
|
||||
BinaryKind::Rem => env.call("rem", &[head, tail]),
|
||||
BinaryKind::Add => env.call("add", &[head, tail]),
|
||||
BinaryKind::Sub => env.call("sub", &[head, tail]),
|
||||
BinaryKind::Shl => env.call("shl", &[head, tail]),
|
||||
BinaryKind::Shr => env.call("shr", &[head, tail]),
|
||||
BinaryKind::BitAnd => env.call("and", &[head, tail]),
|
||||
BinaryKind::BitOr => env.call("or", &[head, tail]),
|
||||
BinaryKind::BitXor => env.call("xor", &[head, tail]),
|
||||
BinaryKind::RangeExc => env.call("range_exc", &[head, tail]),
|
||||
BinaryKind::RangeInc => env.call("range_inc", &[head, tail]),
|
||||
BinaryKind::Lt => env.call("lt", &[head, tail]),
|
||||
BinaryKind::LtEq => env.call("lt_eq", &[head, tail]),
|
||||
BinaryKind::Equal => env.call("eq", &[head, tail]),
|
||||
BinaryKind::NotEq => env.call("noteq", &[head, tail]),
|
||||
BinaryKind::GtEq => env.call("gt_eq", &[head, tail]),
|
||||
BinaryKind::Gt => env.call("gt", &[head, tail]),
|
||||
BinaryKind::Dot => todo!("search within a type's namespace!"),
|
||||
_ => Ok(head),
|
||||
}?;
|
||||
}
|
||||
Ok(head)
|
||||
@ -553,9 +538,9 @@ pub mod interpret {
|
||||
|
||||
for op in ops.iter().rev() {
|
||||
operand = match op {
|
||||
UnaryKind::Deref => todo!("Deref operator"),
|
||||
UnaryKind::Neg => (-operand)?,
|
||||
UnaryKind::Not => (!operand)?,
|
||||
UnaryKind::Deref => env.call("deref", &[operand])?,
|
||||
UnaryKind::Neg => env.call("neg", &[operand])?,
|
||||
UnaryKind::Not => env.call("not", &[operand])?,
|
||||
UnaryKind::At => {
|
||||
println!("{operand}");
|
||||
operand
|
||||
@ -646,7 +631,8 @@ pub mod interpret {
|
||||
}
|
||||
impl Interpret for AddrOf {
|
||||
fn interpret(&self, env: &mut Environment) -> IResult<ConValue> {
|
||||
todo!("Implement AddrOf in {env}")
|
||||
let Self { count: _, mutable: _, expr } = self;
|
||||
todo!("Create reference\nfrom expr: {expr}\nin env:\n{env}\n")
|
||||
}
|
||||
}
|
||||
impl Interpret for Block {
|
||||
@ -809,77 +795,16 @@ pub mod function {
|
||||
}
|
||||
}
|
||||
|
||||
pub mod builtin {
|
||||
//! Implementations of built-in functions
|
||||
mod builtin_imports {
|
||||
pub use crate::interpreter::{
|
||||
env::Environment, error::IResult, temp_type_impl::ConValue, BuiltIn, Callable,
|
||||
};
|
||||
}
|
||||
use super::BuiltIn;
|
||||
/// Builtins to load when a new interpreter is created
|
||||
pub const DEFAULT_BUILTINS: &[&dyn BuiltIn] = &[&print::Print, &dbg::Dbg, &dump::Dump];
|
||||
|
||||
mod print {
|
||||
//! Implements the unstable `print(...)` builtin
|
||||
use super::builtin_imports::*;
|
||||
/// Implements the `print(...)` builtin
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Print;
|
||||
impl BuiltIn for Print {}
|
||||
#[rustfmt::skip]
|
||||
impl Callable for Print {
|
||||
fn name(&self) -> &'static str { "print" }
|
||||
fn call(&self, _inter: &mut Environment, args: &[ConValue]) -> IResult<ConValue> {
|
||||
for arg in args {
|
||||
print!("{arg}")
|
||||
}
|
||||
println!();
|
||||
Ok(ConValue::Empty)
|
||||
}
|
||||
}
|
||||
}
|
||||
mod dbg {
|
||||
//! Implements the unstable `dbg(...)` builtin
|
||||
use super::builtin_imports::*;
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Dbg;
|
||||
impl BuiltIn for Dbg {}
|
||||
#[rustfmt::skip]
|
||||
impl Callable for Dbg {
|
||||
fn name(&self) -> &str { "dbg" }
|
||||
fn call(&self, _inter: &mut Environment, args: &[ConValue]) -> IResult<ConValue> {
|
||||
println!("{args:?}");
|
||||
Ok(args.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
mod dump {
|
||||
use super::builtin_imports::*;
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Dump;
|
||||
impl BuiltIn for Dump {}
|
||||
impl Callable for Dump {
|
||||
fn call(&self, env: &mut Environment, _args: &[ConValue]) -> IResult<ConValue> {
|
||||
println!("{}", *env);
|
||||
Ok(ConValue::Empty)
|
||||
}
|
||||
|
||||
fn name(&self) -> &str {
|
||||
"dump"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
pub mod builtin;
|
||||
|
||||
pub mod env {
|
||||
//! Lexical and non-lexical scoping for variables
|
||||
use super::{
|
||||
builtin::DEFAULT_BUILTINS,
|
||||
builtin::{BINARY, MISC, RANGE, UNARY},
|
||||
error::{Error, IResult},
|
||||
function::Function,
|
||||
temp_type_impl::ConValue,
|
||||
Callable, Interpret,
|
||||
BuiltIn, Callable, Interpret,
|
||||
};
|
||||
use crate::ast::{Function as FnDecl, Identifier};
|
||||
use std::{
|
||||
@ -899,9 +824,9 @@ pub mod env {
|
||||
for (frame, name) in self.frames.iter().rev() {
|
||||
writeln!(f, "--- {name} ---")?;
|
||||
for (var, val) in frame {
|
||||
write!(f, "- {var}: ")?;
|
||||
write!(f, "{var}: ")?;
|
||||
match val {
|
||||
Some(value) => writeln!(f, "{value}"),
|
||||
Some(value) => writeln!(f, "\t{value}"),
|
||||
None => writeln!(f, "<undefined>"),
|
||||
}?
|
||||
}
|
||||
@ -911,14 +836,22 @@ pub mod env {
|
||||
}
|
||||
impl Default for Environment {
|
||||
fn default() -> Self {
|
||||
let mut builtins = HashMap::new();
|
||||
for &builtin in DEFAULT_BUILTINS {
|
||||
builtins.insert(builtin.name().into(), Some(ConValue::BuiltIn(builtin)));
|
||||
Self {
|
||||
frames: vec![
|
||||
(to_hashmap(RANGE), "range ops"),
|
||||
(to_hashmap(UNARY), "unary ops"),
|
||||
(to_hashmap(BINARY), "binary ops"),
|
||||
(to_hashmap(MISC), "builtins"),
|
||||
(HashMap::new(), "globals"),
|
||||
],
|
||||
}
|
||||
// FIXME: Temporary until modules are implemented
|
||||
Self { frames: vec![(builtins, "builtins"), (HashMap::new(), "globals")] }
|
||||
}
|
||||
}
|
||||
fn to_hashmap(from: &[&'static dyn BuiltIn]) -> HashMap<String, Option<ConValue>> {
|
||||
from.iter()
|
||||
.map(|&v| (v.name().into(), Some(v.into())))
|
||||
.collect()
|
||||
}
|
||||
|
||||
impl Environment {
|
||||
pub fn new() -> Self {
|
||||
@ -1073,7 +1006,11 @@ pub mod error {
|
||||
/// A value was called, but is not callable
|
||||
NotCallable(ConValue),
|
||||
/// A function was called with the wrong number of arguments
|
||||
ArgNumber { want: usize, got: usize },
|
||||
ArgNumber {
|
||||
want: usize,
|
||||
got: usize,
|
||||
},
|
||||
NullPointer,
|
||||
}
|
||||
|
||||
impl std::error::Error for Error {}
|
||||
@ -1109,6 +1046,9 @@ pub mod error {
|
||||
Error::ArgNumber { want, got } => {
|
||||
write!(f, "Expected {want} arguments, got {got}")
|
||||
}
|
||||
Error::NullPointer => {
|
||||
write!(f, "Attempted to dereference a null pointer?")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
239
libconlang/src/interpreter/builtin.rs
Normal file
239
libconlang/src/interpreter/builtin.rs
Normal file
@ -0,0 +1,239 @@
|
||||
//! Implementations of built-in functions
|
||||
|
||||
use super::{
|
||||
env::Environment,
|
||||
error::{Error, IResult},
|
||||
temp_type_impl::ConValue,
|
||||
BuiltIn, Callable,
|
||||
};
|
||||
use std::io::{stdout, Write};
|
||||
|
||||
builtins! {
|
||||
const MISC;
|
||||
/// Unstable variadic print function
|
||||
pub fn print<_, args> () -> IResult<ConValue> {
|
||||
let mut out = stdout().lock();
|
||||
for arg in args {
|
||||
write!(out, "{arg}").ok();
|
||||
}
|
||||
writeln!(out).ok();
|
||||
Ok(ConValue::Empty)
|
||||
}
|
||||
/// Prints the [Debug](std::fmt::Debug) version of the input values
|
||||
pub fn dbg<_, args> () -> IResult<ConValue> {
|
||||
let mut out = stdout().lock();
|
||||
for arg in args {
|
||||
writeln!(out, "{arg:?}").ok();
|
||||
}
|
||||
Ok(args.into())
|
||||
}
|
||||
/// Dumps info from the environment
|
||||
pub fn dump<env, _>() -> IResult<ConValue> {
|
||||
println!("{}", *env);
|
||||
Ok(ConValue::Empty)
|
||||
}
|
||||
}
|
||||
builtins! {
|
||||
const BINARY;
|
||||
/// Multiplication `a * b`
|
||||
pub fn mul(lhs, rhs) -> IResult<ConValue> {
|
||||
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`
|
||||
pub fn div(lhs, rhs) -> IResult<ConValue> {
|
||||
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`
|
||||
pub fn rem(lhs, rhs) -> IResult<ConValue> {
|
||||
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`
|
||||
pub fn add(lhs, rhs) -> IResult<ConValue> {
|
||||
Ok(match (lhs, rhs) {
|
||||
(ConValue::Empty, ConValue::Empty) => ConValue::Empty,
|
||||
(ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a + b),
|
||||
(ConValue::String(a), ConValue::String(b)) => ConValue::String(a.to_string() + b),
|
||||
_ => Err(Error::TypeError)?
|
||||
})
|
||||
}
|
||||
/// Subtraction `a - b`
|
||||
pub fn sub(lhs, rhs) -> IResult<ConValue> {
|
||||
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`
|
||||
pub fn shl(lhs, rhs) -> IResult<ConValue> {
|
||||
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`
|
||||
pub fn shr(lhs, rhs) -> IResult<ConValue> {
|
||||
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`
|
||||
pub fn and(lhs, rhs) -> IResult<ConValue> {
|
||||
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`
|
||||
pub fn or(lhs, rhs) -> IResult<ConValue> {
|
||||
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`
|
||||
pub fn xor(lhs, rhs) -> IResult<ConValue> {
|
||||
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)?,
|
||||
})
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
Err(Error::TypeError)?
|
||||
};
|
||||
Ok(ConValue::RangeExc(lhs, rhs.saturating_sub(1)))
|
||||
}
|
||||
/// Inclusive Range `a..=b`
|
||||
pub fn range_inc(lhs, rhs) -> IResult<ConValue> {
|
||||
let (&ConValue::Int(lhs), &ConValue::Int(rhs)) = (lhs, rhs) else {
|
||||
Err(Error::TypeError)?
|
||||
};
|
||||
Ok(ConValue::RangeInc(lhs, rhs))
|
||||
}
|
||||
}
|
||||
builtins! {
|
||||
const UNARY;
|
||||
/// Negates the ConValue
|
||||
pub fn neg(tail) -> IResult<ConValue> {
|
||||
Ok(match tail {
|
||||
ConValue::Empty => ConValue::Empty,
|
||||
ConValue::Int(v) => ConValue::Int(-v),
|
||||
_ => Err(Error::TypeError)?,
|
||||
})
|
||||
}
|
||||
/// Inverts the ConValue
|
||||
pub fn not(tail) -> IResult<ConValue> {
|
||||
Ok(match tail {
|
||||
ConValue::Empty => ConValue::Empty,
|
||||
ConValue::Int(v) => ConValue::Int(!v),
|
||||
ConValue::Bool(v) => ConValue::Bool(!v),
|
||||
_ => Err(Error::TypeError)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// 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:tt),+$(,)?)? ) $(-> $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)]
|
||||
fn call(&self, env: &mut Environment, args: &[ConValue]) $(-> $rety)? {
|
||||
// println!("{}", stringify!($name), );
|
||||
$(let $env = env;
|
||||
let $args = args;)?
|
||||
$(let [$($arg),*] = to_args(args)?;)?
|
||||
$body
|
||||
}
|
||||
fn name(&self) -> &str { stringify!($name) }
|
||||
}
|
||||
)*
|
||||
}
|
||||
|
||||
/// 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)
|
||||
}
|
||||
}
|
@ -1,12 +1,13 @@
|
||||
// Calculate Fibonacci numbers
|
||||
|
||||
fn main() -> i128 {
|
||||
let num = 10;
|
||||
print("fib(", num, "): ", fib(num));
|
||||
fn main() {
|
||||
for num in 0..=30 {
|
||||
print("fib(", num, ") = ", fib(num))
|
||||
}
|
||||
}
|
||||
|
||||
/// Implements the classic recursive definition of fib()
|
||||
fn fib(a: i128) -> i128 {
|
||||
fn fib(a: i64) -> i64 {
|
||||
if a > 1 {
|
||||
fib(a - 1) + fib(a - 2)
|
||||
} else {
|
Loading…
Reference in New Issue
Block a user