aeri 0.2.1

Aeri is a Cardano smart contract language by Trevor Knott and Knott Dynamics, with tools for compiling contracts to UPLC.
Documentation
use std::{collections::HashSet, fmt};

use crate::{
    ast::TypeRef,
    diagnostic::{AeriError, Span},
};

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Type {
    Bool,
    Int,
    ByteArray,
    Data,
    Tx,
    String,
    Unit,
    List(Box<Type>),
    Custom(String),
    Never,
}

impl Type {
    pub fn from_ref(
        file: &str,
        ty: &TypeRef,
        custom_types: &HashSet<String>,
    ) -> Result<Self, AeriError> {
        match ty.name.as_str() {
            "Bool" if ty.args.is_empty() => Ok(Self::Bool),
            "Int" if ty.args.is_empty() => Ok(Self::Int),
            "ByteArray" if ty.args.is_empty() => Ok(Self::ByteArray),
            "Data" if ty.args.is_empty() => Ok(Self::Data),
            "Tx" if ty.args.is_empty() => Ok(Self::Tx),
            "String" if ty.args.is_empty() => Ok(Self::String),
            "Unit" if ty.args.is_empty() => Ok(Self::Unit),
            "List" => {
                if ty.args.len() != 1 {
                    return Err(AeriError::at_span(
                        file,
                        ty.span,
                        "List expects exactly one type argument",
                    ));
                }
                Ok(Self::List(Box::new(Self::from_ref(
                    file,
                    &ty.args[0],
                    custom_types,
                )?)))
            }
            name if custom_types.contains(name) && ty.args.is_empty() => {
                Ok(Self::Custom(name.to_string()))
            }
            name if !ty.args.is_empty() => Err(AeriError::at_span(
                file,
                ty.span,
                format!("type '{name}' does not take arguments"),
            )),
            name => Err(AeriError::at_span(
                file,
                ty.span,
                format!("unknown type '{name}'"),
            )),
        }
    }

    pub fn schema_name(&self) -> &'static str {
        match self {
            Type::Bool => "boolean",
            Type::Int => "integer",
            Type::ByteArray => "bytes",
            Type::Data => "data",
            Type::Tx => "transaction",
            Type::String => "string",
            Type::Unit => "unit",
            Type::List(_) => "list",
            Type::Custom(_) => "constructor",
            Type::Never => "never",
        }
    }
}

impl fmt::Display for Type {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let name = match self {
            Type::Bool => "Bool",
            Type::Int => "Int",
            Type::ByteArray => "ByteArray",
            Type::Data => "Data",
            Type::Tx => "Tx",
            Type::String => "String",
            Type::Unit => "Unit",
            Type::List(item) => return write!(f, "List<{item}>"),
            Type::Custom(name) => name,
            Type::Never => "Never",
        };
        write!(f, "{name}")
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Signature {
    pub params: Vec<Type>,
    pub result: Type,
    pub span: Span,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BuiltinSignature {
    pub params: Vec<Type>,
    pub result: Type,
}

pub fn is_transaction_builtin(name: &str) -> bool {
    matches!(
        name,
        "tx_signed_by"
            | "tx_paid_to"
            | "tx_mints"
            | "tx_after"
            | "tx_before"
            | "tx_spends"
            | "tx_has_datum"
    )
}

pub fn is_test_fixture_builtin(name: &str) -> bool {
    matches!(name, "test_data" | "test_tx")
}

pub fn is_builtin_type_name(name: &str) -> bool {
    matches!(
        name,
        "Bool" | "Int" | "ByteArray" | "Data" | "Tx" | "String" | "Unit" | "List" | "Never"
    )
}

pub fn is_builtin_name(name: &str) -> bool {
    builtin_signature(name).is_some() || matches!(name, "list_len" | "list_has")
}

pub fn builtin_signature(name: &str) -> Option<BuiltinSignature> {
    use Type::{Bool, ByteArray, Data, Int, Tx};

    let signature = match name {
        "tx_signed_by" => BuiltinSignature {
            params: vec![Tx, ByteArray],
            result: Bool,
        },
        "tx_paid_to" => BuiltinSignature {
            params: vec![Tx, ByteArray, Int],
            result: Bool,
        },
        "tx_mints" => BuiltinSignature {
            params: vec![Tx, ByteArray, Int],
            result: Bool,
        },
        "tx_after" | "tx_before" => BuiltinSignature {
            params: vec![Tx, Int],
            result: Bool,
        },
        "tx_spends" => BuiltinSignature {
            params: vec![Tx, ByteArray],
            result: Bool,
        },
        "tx_has_datum" => BuiltinSignature {
            params: vec![Tx, Data],
            result: Bool,
        },
        "test_data" => BuiltinSignature {
            params: vec![ByteArray],
            result: Data,
        },
        "test_tx" => BuiltinSignature {
            params: vec![
                Type::List(Box::new(ByteArray)),
                Type::List(Box::new(ByteArray)),
                Type::List(Box::new(Int)),
                Type::List(Box::new(ByteArray)),
                Type::List(Box::new(Int)),
                Int,
                Int,
                Type::List(Box::new(ByteArray)),
                Type::List(Box::new(Data)),
            ],
            result: Tx,
        },
        "datum_equals" => BuiltinSignature {
            params: vec![Data, Data],
            result: Bool,
        },
        "sha2_256" | "blake2b_256" => BuiltinSignature {
            params: vec![ByteArray],
            result: ByteArray,
        },
        "append_bytes" => BuiltinSignature {
            params: vec![ByteArray, ByteArray],
            result: ByteArray,
        },
        "list_has_bytes" => BuiltinSignature {
            params: vec![Type::List(Box::new(ByteArray)), ByteArray],
            result: Bool,
        },
        "list_has_int" => BuiltinSignature {
            params: vec![Type::List(Box::new(Int)), Int],
            result: Bool,
        },
        "list_has_bool" => BuiltinSignature {
            params: vec![Type::List(Box::new(Bool)), Bool],
            result: Bool,
        },
        "list_has_string" => BuiltinSignature {
            params: vec![Type::List(Box::new(Type::String)), Type::String],
            result: Bool,
        },
        "list_has_data" => BuiltinSignature {
            params: vec![Type::List(Box::new(Data)), Data],
            result: Bool,
        },
        "list_has_unit" => BuiltinSignature {
            params: vec![Type::List(Box::new(Type::Unit)), Type::Unit],
            result: Bool,
        },
        "list_len_bytes" => BuiltinSignature {
            params: vec![Type::List(Box::new(ByteArray))],
            result: Int,
        },
        "list_len_int" => BuiltinSignature {
            params: vec![Type::List(Box::new(Int))],
            result: Int,
        },
        "list_len_bool" => BuiltinSignature {
            params: vec![Type::List(Box::new(Bool))],
            result: Int,
        },
        "list_len_string" => BuiltinSignature {
            params: vec![Type::List(Box::new(Type::String))],
            result: Int,
        },
        "list_len_data" => BuiltinSignature {
            params: vec![Type::List(Box::new(Data))],
            result: Int,
        },
        "list_len_unit" => BuiltinSignature {
            params: vec![Type::List(Box::new(Type::Unit))],
            result: Int,
        },
        _ => return None,
    };

    Some(signature)
}