diff --git a/libconlang/examples/fib.rs b/libconlang/examples/fib.rs new file mode 100644 index 0000000..db45d80 --- /dev/null +++ b/libconlang/examples/fib.rs @@ -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 + } +} diff --git a/libconlang/src/interpreter.rs b/libconlang/src/interpreter.rs index 6826229..d115340 100644 --- a/libconlang/src/interpreter.rs +++ b/libconlang/src/interpreter.rs @@ -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::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; - 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; - 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 { 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 { - return Ok(head); // Short circuiting + 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 { - 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 { - 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 { - 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 { - 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, ""), }? } @@ -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> { + 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?") + } } } } diff --git a/libconlang/src/interpreter/builtin.rs b/libconlang/src/interpreter/builtin.rs new file mode 100644 index 0000000..ede42bc --- /dev/null +++ b/libconlang/src/interpreter/builtin.rs @@ -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 { + 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 { + let mut out = stdout().lock(); + for arg in args { + writeln!(out, "{arg:?}").ok(); + } + Ok(args.into()) + } + /// Dumps info from the environment + pub fn dump() -> IResult { + println!("{}", *env); + Ok(ConValue::Empty) + } +} +builtins! { + const BINARY; + /// Multiplication `a * b` + pub fn mul(lhs, rhs) -> IResult { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + Err(Error::TypeError)? + }; + Ok(ConValue::RangeExc(lhs, rhs.saturating_sub(1))) + } + /// Inclusive Range `a..=b` + pub fn range_inc(lhs, rhs) -> IResult { + 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 { + Ok(match tail { + ConValue::Empty => ConValue::Empty, + ConValue::Int(v) => ConValue::Int(-v), + _ => Err(Error::TypeError)?, + }) + } + /// Inverts the ConValue + pub fn not(tail) -> IResult { + 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(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) + } +} diff --git a/sample-code/fibonacci.cl b/sample-code/fib.cl similarity index 60% rename from sample-code/fibonacci.cl rename to sample-code/fib.cl index 75444e1..44a6eb0 100644 --- a/sample-code/fibonacci.cl +++ b/sample-code/fib.cl @@ -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 {