mul/src/builtins.rs

170 lines
4.9 KiB
Rust

//! Language builtins, wired into the implementation.
use std::fmt::Write;
use crate::dictionary::Dictionary;
use crate::evaluate::Value;
use crate::syntax::Type;
/// Builtin functions.
#[derive(Debug, Copy, Clone)]
pub enum Builtin {
Print,
Add,
Sub,
Mul,
Div,
Equals,
GreaterThan,
LessThan,
}
#[track_caller]
fn unwrap_int_value(op_name: &'static str) -> impl Fn(Value) -> i64 {
move |value| match value {
Value::Integer(i) => i,
_ => panic!("got non-integer during {op_name}"),
}
}
macro_rules! int_op {
($arguments:expr, $op:tt, $op_name:expr) => {{
let i = $arguments
.into_iter()
.map(unwrap_int_value($op_name))
.reduce(|acc, v| acc $op v)
.unwrap();
Value::Integer(i)
}};
}
macro_rules! int_cmp {
($arguments:expr, $op:tt) => {{
let mut arguments = $arguments;
let unwrap = unwrap_int_value("compare");
let y = unwrap(arguments.pop().unwrap());
let x = unwrap(arguments.pop().unwrap());
Value::Boolean(x $op y)
}};
}
fn int_type() -> Type {
Type::Constructor("Integer".to_string())
}
fn bool_type() -> Type {
Type::Constructor("Boolean".to_string())
}
fn int_op_type() -> Type {
Type::Function {
parameters: vec![int_type(), int_type()],
result: Box::new(int_type()),
}
}
fn int_cmp_type() -> Type {
Type::Function {
parameters: vec![int_type(), int_type()],
result: Box::new(bool_type()),
}
}
impl Builtin {
/// A mapping from runtime names to builtin sentinels.
pub fn dictionary() -> Dictionary<Value> {
[
("_print", Builtin::Print),
("_add", Builtin::Add),
("_sub", Builtin::Sub),
("_mul", Builtin::Mul),
("_div", Builtin::Div),
("_equals", Builtin::Equals),
("_greaterthan", Builtin::GreaterThan),
("_lessthan", Builtin::LessThan),
]
.map(|(name, builtin)| (name.to_string(), Value::Builtin(builtin)))
.into()
}
/// A mapping from runtime names to builtin types.
pub fn type_dictionary() -> Dictionary<Type> {
[
("_print", Type::Print),
("_add", int_op_type()),
("_sub", int_op_type()),
("_mul", int_op_type()),
("_div", int_op_type()),
("_equals", Type::Equals),
("_greaterthan", int_cmp_type()),
("_lessthan", int_cmp_type()),
]
.map(|(name, ty)| (name.to_string(), ty))
.into()
}
/// Minimum # of required arguments for a builtin.
pub fn min_parameters(&self) -> usize {
match self {
Builtin::Print => 0,
Builtin::Add
| Builtin::Sub
| Builtin::Mul
| Builtin::Div
| Builtin::Equals
| Builtin::GreaterThan
| Builtin::LessThan => 2,
}
}
/// Maximum # of parameters allowed, if any.
pub fn max_parameters(&self) -> Option<usize> {
match self {
Builtin::Print
| Builtin::Add
| Builtin::Sub
| Builtin::Mul
| Builtin::Div
| Builtin::Equals => None,
Builtin::GreaterThan | Builtin::LessThan => Some(2),
}
}
/// Execute the given builtin on the provided arguments.
pub fn call(&self, arguments: Vec<Value>) -> Value {
match self {
Builtin::Print => {
let mut output = String::new();
let mut arguments = arguments.into_iter().peekable();
while let Some(term) = arguments.next() {
if arguments.peek().is_some() {
write!(output, "{term} ").expect("Error during printing");
} else {
write!(output, "{term}").expect("Error during printing");
}
}
println!("{output}");
Value::Unit
}
Builtin::Add => int_op!(arguments, +, "add"),
Builtin::Sub => int_op!(arguments, -, "subtract"),
Builtin::Mul => int_op!(arguments, *, "multiply"),
Builtin::Div => int_op!(arguments, /, "divide"),
Builtin::Equals => {
let mut arguments = arguments;
let y = arguments.pop().unwrap();
let x = arguments.pop().unwrap();
let b = match (x, y) {
(Value::Integer(x), Value::Integer(y)) => x == y,
(Value::Boolean(x), Value::Boolean(y)) => x == y,
(Value::Unit, Value::Unit) => true,
_ => false,
};
Value::Boolean(b)
}
Builtin::GreaterThan => int_cmp!(arguments, >),
Builtin::LessThan => int_cmp!(arguments, <),
}
}
}