use crate::ast::{self, BuiltinFn, FnDef, Identifier, ParamDef, ParameterList, Span, Type};
use crate::lowering::{Context, Error as LowerError, IntoLower};
use tx3_tir::model::v1beta0 as ir;
pub struct Signature {
pub params: Vec<(&'static str, Type)>,
pub returns: Type,
}
pub trait Builtin: Sync {
fn kind(&self) -> BuiltinFn;
fn name(&self) -> &'static str;
fn signature(&self) -> Signature;
fn lower_call(
&self,
args: &[ast::DataExpr],
ctx: &Context,
) -> Result<ir::Expression, LowerError>;
fn definition(&self) -> FnDef {
let sig = self.signature();
FnDef {
name: Identifier::new(self.name()),
parameters: ParameterList {
parameters: sig
.params
.into_iter()
.map(|(name, r#type)| ParamDef {
name: Identifier::new(name),
r#type,
docstring: None,
})
.collect(),
span: Span::DUMMY,
},
return_type: sig.returns,
body: None,
builtin: Some(self.kind()),
span: Span::DUMMY,
scope: None,
}
}
}
macro_rules! builtin_boilerplate {
(
$variant:ident,
$name:literal,
( $( $param:ident : $pty:ident ),* $(,)? ) -> $ret:ident
=> $op:ident $(( $( $oparg:ident ),* ))?
) => {
pub struct $variant;
impl Builtin for $variant {
fn kind(&self) -> BuiltinFn {
BuiltinFn::$variant
}
fn name(&self) -> &'static str {
$name
}
fn signature(&self) -> Signature {
Signature {
params: vec![ $( (stringify!($param), Type::$pty) ),* ],
returns: Type::$ret,
}
}
#[allow(unused_mut, unused_variables)]
fn lower_call(
&self,
args: &[ast::DataExpr],
ctx: &Context,
) -> Result<ir::Expression, LowerError> {
let mut args = args.iter();
$(
let $param = args
.next()
.ok_or_else(|| LowerError::InvalidAst(format!(
"built-in '{}' expects argument '{}'",
$name,
stringify!($param),
)))?
.into_lower(ctx)?;
)*
Ok(ir::Expression::EvalCompiler(Box::new(
ir::CompilerOp::$op $(( $( $oparg ),* ))?
)))
}
}
};
}
builtin_boilerplate!(MinUtxo, "min_utxo", (output: Int) -> AnyAsset => ComputeMinUtxo(output));
builtin_boilerplate!(TipSlot, "tip_slot", () -> Int => ComputeTipSlot);
builtin_boilerplate!(SlotToTime, "slot_to_time", (slot: Int) -> Int => ComputeSlotToTime(slot));
builtin_boilerplate!(TimeToSlot, "time_to_slot", (time: Int) -> Int => ComputeTimeToSlot(time));
pub fn resolve(kind: BuiltinFn) -> &'static dyn Builtin {
match kind {
BuiltinFn::MinUtxo => &MinUtxo,
BuiltinFn::TipSlot => &TipSlot,
BuiltinFn::SlotToTime => &SlotToTime,
BuiltinFn::TimeToSlot => &TimeToSlot,
}
}
pub fn all() -> impl Iterator<Item = &'static dyn Builtin> {
BuiltinFn::ALL.into_iter().map(resolve)
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashSet;
#[test]
fn registry_is_consistent() {
let mut names = HashSet::new();
for kind in BuiltinFn::ALL {
let b = resolve(kind);
assert_eq!(b.kind(), kind);
let def = b.definition();
assert_eq!(def.builtin, Some(kind));
assert_eq!(def.name.value, b.name());
assert_eq!(def.parameters.parameters.len(), b.signature().params.len());
assert!(names.insert(b.name()), "duplicate built-in name: {}", b.name());
}
}
}