use serde::{Deserialize, Serialize};
use super::Span;
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "camelCase")]
pub enum Type {
Nat,
Int,
Bool,
String,
Unit,
Float,
Fixed {
bits: u32,
signed: bool,
},
Data {
name: std::string::String,
},
Array {
element: Box<Self>,
},
Named {
path: Vec<std::string::String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
span: Option<Span>,
},
Literal,
Hypothesis,
Output,
}
pub const NAT: Type = Type::Nat;
pub const INT: Type = Type::Int;
pub const BOOL: Type = Type::Bool;
pub const STRING: Type = Type::String;
pub const UNIT: Type = Type::Unit;
pub const FLOAT: Type = Type::Float;
#[must_use]
pub const fn fixed(bits: u32, signed: bool) -> Type {
Type::Fixed { bits, signed }
}
#[must_use]
pub fn array(element: Type) -> Type {
Type::Array {
element: Box::new(element),
}
}
#[must_use]
pub fn data(name: impl Into<std::string::String>) -> Type {
Type::Data { name: name.into() }
}
impl Type {
#[must_use]
pub fn key(&self) -> std::string::String {
match self {
Self::Fixed { bits, signed } => format!("{}{bits}", if *signed { 'i' } else { 'u' }),
Self::Data { name } => format!("data:{name}"),
Self::Array { element } => format!("array:{}", element.key()),
other => other.kind().to_owned(),
}
}
#[must_use]
pub const fn kind(&self) -> &'static str {
match self {
Self::Nat => "nat",
Self::Int => "int",
Self::Bool => "bool",
Self::String => "string",
Self::Unit => "unit",
Self::Float => "float",
Self::Fixed { .. } => "fixed",
Self::Data { .. } => "data",
Self::Array { .. } => "array",
Self::Named { .. } => "named",
Self::Literal => "literal",
Self::Hypothesis => "hypothesis",
Self::Output => "output",
}
}
#[must_use]
pub fn same(&self, other: &Self) -> bool {
self.key() == other.key()
}
#[must_use]
pub const fn is_numeric(&self) -> bool {
matches!(self, Self::Nat | Self::Int | Self::Fixed { .. })
}
#[must_use]
pub const fn is_float(&self) -> bool {
matches!(self, Self::Float)
}
#[must_use]
pub const fn is_ordered(&self) -> bool {
self.is_numeric() || self.is_float()
}
#[must_use]
pub const fn is_natural(&self) -> bool {
matches!(self, Self::Nat | Self::Fixed { signed: false, .. })
}
#[must_use]
pub const fn is_literal(&self) -> bool {
matches!(self, Self::Literal)
}
#[must_use]
pub fn element(&self) -> Option<&Self> {
match self {
Self::Array { element } => Some(element),
_ => None,
}
}
#[must_use]
pub fn data_name(&self) -> Option<&str> {
match self {
Self::Data { name } => Some(name),
_ => None,
}
}
}
#[must_use]
pub fn fixed_bounds(bits: u32, signed: bool) -> (i128, u128) {
if signed {
let max = (1u128 << (bits - 1)) - 1;
let min = -i128::try_from(max).unwrap_or(i128::MAX) - 1;
(min, max)
} else if bits >= 128 {
(0, u128::MAX)
} else {
(0, (1u128 << bits) - 1)
}
}
#[must_use]
pub fn rust_fixed_type(name: &str) -> Option<Type> {
let signed = match name.as_bytes().first() {
Some(b'u') => false,
Some(b'i') => true,
_ => return None,
};
let bits = match &name[1..] {
"8" => 8,
"16" => 16,
"32" => 32,
"64" | "size" => 64,
"128" => 128,
_ => return None,
};
Some(fixed(bits, signed))
}