extern crate alloc;
use alloc::collections::BTreeMap;
use alloc::string::String;
use alloc::vec::Vec;
use crate::ast::{EnumDef, Expr, NewtypeDef, PrimType, StructDef, TypeExpr};
use crate::bytecode::ConstValue;
use crate::interval::IntervalSet;
const MAX_DEPTH: usize = 64;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ZeroValueError {
UnknownType(String),
EmptyEnum(String),
RecursionLimit,
}
pub struct TypeRegistry<'a> {
pub structs: &'a BTreeMap<String, StructDef>,
pub enums: &'a BTreeMap<String, EnumDef>,
pub newtypes: &'a BTreeMap<String, NewtypeDef>,
pub refinement_bodies: &'a BTreeMap<String, (String, Expr)>,
}
pub fn zero_value(ty: &TypeExpr, reg: &TypeRegistry) -> Result<ConstValue, ZeroValueError> {
zero_value_at(ty, reg, 0)
}
fn zero_value_at(
ty: &TypeExpr,
reg: &TypeRegistry,
depth: usize,
) -> Result<ConstValue, ZeroValueError> {
if depth > MAX_DEPTH {
return Err(ZeroValueError::RecursionLimit);
}
match ty {
TypeExpr::Unit(_) => Ok(ConstValue::Unit),
TypeExpr::Prim(p, _) => Ok(zero_prim(p)),
TypeExpr::Multiword(n, _, _) => Ok(ConstValue::Array(
alloc::vec![zero_prim(&crate::ast::PrimType::Word); n.as_lit().unwrap_or(0) as usize],
)),
TypeExpr::Option(_, _) => Ok(ConstValue::None),
TypeExpr::Tuple(elems, _) => {
let mut out = Vec::with_capacity(elems.len());
for e in elems {
out.push(zero_value_at(e, reg, depth + 1)?);
}
Ok(ConstValue::Tuple(out))
}
TypeExpr::Array(elem, n, _) => {
let count = n.as_lit().unwrap_or(0).max(0) as usize;
let z = zero_value_at(elem, reg, depth + 1)?;
Ok(ConstValue::Array(alloc::vec![z; count]))
}
TypeExpr::Named(name, _generics, _, _) => zero_named(name, reg, depth),
TypeExpr::Labelled(inner, _, _) | TypeExpr::NegativeLabelled(inner, _, _) => {
zero_value_at(inner, reg, depth + 1)
}
}
}
fn zero_prim(p: &PrimType) -> ConstValue {
match p {
PrimType::Word => ConstValue::Int(0),
PrimType::Byte => ConstValue::Byte(0),
PrimType::Fixed(_) => ConstValue::Fixed(0),
PrimType::Bool => ConstValue::Bool(false),
PrimType::Text => ConstValue::StaticStr(String::new()),
#[cfg(feature = "floats")]
PrimType::Float => ConstValue::Float(0.0),
#[cfg(not(feature = "floats"))]
PrimType::Float => ConstValue::Int(0),
}
}
fn zero_named(name: &str, reg: &TypeRegistry, depth: usize) -> Result<ConstValue, ZeroValueError> {
if let Some(nt) = reg.newtypes.get(name) {
if let Some(v) = lowest_valid(nt, reg) {
return wrap_underlying(&nt.underlying, v, reg, depth);
}
return zero_value_at(&nt.underlying, reg, depth + 1);
}
if let Some(sd) = reg.structs.get(name) {
let mut fields = Vec::with_capacity(sd.fields.len());
for f in &sd.fields {
fields.push((f.name.clone(), zero_value_at(&f.type_expr, reg, depth + 1)?));
}
return Ok(ConstValue::Struct {
type_name: String::from(name),
fields,
});
}
if let Some(ed) = reg.enums.get(name) {
let variant = ed
.variants
.iter()
.find(|v| v.discriminant_value == 0)
.or_else(|| ed.variants.iter().min_by_key(|v| v.discriminant_value))
.ok_or_else(|| ZeroValueError::EmptyEnum(String::from(name)))?;
let mut fields = Vec::with_capacity(variant.fields.len());
for t in &variant.fields {
fields.push(zero_value_at(t, reg, depth + 1)?);
}
return Ok(ConstValue::Enum {
type_name: String::from(name),
variant: variant.name.clone(),
discriminant: Some(variant.discriminant_value),
fields,
});
}
Err(ZeroValueError::UnknownType(String::from(name)))
}
fn wrap_underlying(
underlying: &TypeExpr,
v: i64,
reg: &TypeRegistry,
depth: usize,
) -> Result<ConstValue, ZeroValueError> {
match underlying {
TypeExpr::Prim(PrimType::Word, _) => Ok(ConstValue::Int(v)),
TypeExpr::Prim(PrimType::Byte, _) => Ok(ConstValue::Byte(v.clamp(0, 0xFF) as u8)),
TypeExpr::Prim(PrimType::Fixed(_), _) => Ok(ConstValue::Fixed(v)),
_ => zero_value_at(underlying, reg, depth + 1),
}
}
pub fn lowest_valid(nt: &NewtypeDef, reg: &TypeRegistry) -> Option<i64> {
if let Some(m) = nt.saturate_min {
return Some(m);
}
let pred_name = nt.refinement.as_ref()?;
let (param, body) = reg.refinement_bodies.get(pred_name)?;
let set = crate::compiler::predicate_true_set(body, param)?;
interval_set_min(&set)
}
fn interval_set_min(set: &IntervalSet) -> Option<i64> {
set.parts().first().and_then(|iv| iv.lo)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::{BinOp, FieldDecl, Literal, VariantDecl};
use crate::token::Span;
fn sp() -> Span {
Span {
start: 0,
end: 0,
line: 1,
column: 1,
}
}
fn prim(p: PrimType) -> TypeExpr {
TypeExpr::Prim(p, sp())
}
#[allow(clippy::type_complexity)]
fn empty_reg() -> (
BTreeMap<String, StructDef>,
BTreeMap<String, EnumDef>,
BTreeMap<String, NewtypeDef>,
BTreeMap<String, (String, Expr)>,
) {
(
BTreeMap::new(),
BTreeMap::new(),
BTreeMap::new(),
BTreeMap::new(),
)
}
fn reg<'a>(
s: &'a BTreeMap<String, StructDef>,
e: &'a BTreeMap<String, EnumDef>,
n: &'a BTreeMap<String, NewtypeDef>,
r: &'a BTreeMap<String, (String, Expr)>,
) -> TypeRegistry<'a> {
TypeRegistry {
structs: s,
enums: e,
newtypes: n,
refinement_bodies: r,
}
}
#[test]
fn scalar_zeros() {
let (s, e, n, r) = empty_reg();
let reg = reg(&s, &e, &n, &r);
assert_eq!(
zero_value(&prim(PrimType::Word), ®),
Ok(ConstValue::Int(0))
);
assert_eq!(
zero_value(&prim(PrimType::Byte), ®),
Ok(ConstValue::Byte(0))
);
assert_eq!(
zero_value(&prim(PrimType::Fixed(Some(16))), ®),
Ok(ConstValue::Fixed(0))
);
assert_eq!(
zero_value(&prim(PrimType::Bool), ®),
Ok(ConstValue::Bool(false))
);
assert_eq!(
zero_value(&prim(PrimType::Text), ®),
Ok(ConstValue::StaticStr(String::new()))
);
assert_eq!(
zero_value(&TypeExpr::Unit(sp()), ®),
Ok(ConstValue::Unit)
);
}
#[test]
fn option_zero_is_none() {
let (s, e, n, r) = empty_reg();
let reg = reg(&s, &e, &n, &r);
let ty = TypeExpr::Option(alloc::boxed::Box::new(prim(PrimType::Word)), sp());
assert_eq!(zero_value(&ty, ®), Ok(ConstValue::None));
}
#[test]
fn tuple_and_array_zeros() {
let (s, e, n, r) = empty_reg();
let reg = reg(&s, &e, &n, &r);
let tup = TypeExpr::Tuple(
alloc::vec![prim(PrimType::Word), prim(PrimType::Bool)],
sp(),
);
assert_eq!(
zero_value(&tup, ®),
Ok(ConstValue::Tuple(alloc::vec![
ConstValue::Int(0),
ConstValue::Bool(false)
]))
);
let arr = TypeExpr::array_lit(alloc::boxed::Box::new(prim(PrimType::Byte)), 3, sp());
assert_eq!(
zero_value(&arr, ®),
Ok(ConstValue::Array(alloc::vec![
ConstValue::Byte(0),
ConstValue::Byte(0),
ConstValue::Byte(0)
]))
);
}
#[test]
fn struct_zero_recurses_fields() {
let mut s = BTreeMap::new();
s.insert(
String::from("Point"),
StructDef {
name: String::from("Point"),
type_params: Vec::new(),
const_params: Vec::new(),
fields: alloc::vec![
FieldDecl {
name: String::from("x"),
type_expr: prim(PrimType::Word),
span: sp(),
},
FieldDecl {
name: String::from("ok"),
type_expr: prim(PrimType::Bool),
span: sp(),
},
],
span: sp(),
},
);
let (_s, e, n, r) = empty_reg();
let reg = reg(&s, &e, &n, &r);
let z = zero_value(
&TypeExpr::Named(String::from("Point"), Vec::new(), Vec::new(), sp()),
®,
)
.unwrap();
assert_eq!(
z,
ConstValue::Struct {
type_name: String::from("Point"),
fields: alloc::vec![
(String::from("x"), ConstValue::Int(0)),
(String::from("ok"), ConstValue::Bool(false)),
],
}
);
}
fn unit_variant(name: &str, disc: i64) -> VariantDecl {
VariantDecl {
name: String::from(name),
fields: Vec::new(),
explicit_discriminant: Some(disc),
discriminant_value: disc,
span: sp(),
}
}
#[test]
fn enum_zero_prefers_zero_discriminant() {
let mut e = BTreeMap::new();
e.insert(
String::from("Dir"),
EnumDef {
name: String::from("Dir"),
type_params: Vec::new(),
const_params: Vec::new(),
variants: alloc::vec![unit_variant("North", 0), unit_variant("South", 1)],
span: sp(),
},
);
let (s, _e, n, r) = empty_reg();
let reg = reg(&s, &e, &n, &r);
let z = zero_value(
&TypeExpr::Named(String::from("Dir"), Vec::new(), Vec::new(), sp()),
®,
)
.unwrap();
assert_eq!(
z,
ConstValue::Enum {
type_name: String::from("Dir"),
variant: String::from("North"),
discriminant: Some(0),
fields: Vec::new(),
}
);
}
#[test]
fn enum_zero_falls_back_to_lowest_discriminant() {
let mut e = BTreeMap::new();
e.insert(
String::from("Code"),
EnumDef {
name: String::from("Code"),
type_params: Vec::new(),
const_params: Vec::new(),
variants: alloc::vec![unit_variant("B", 8), unit_variant("A", 5)],
span: sp(),
},
);
let (s, _e, n, r) = empty_reg();
let reg = reg(&s, &e, &n, &r);
let z = zero_value(
&TypeExpr::Named(String::from("Code"), Vec::new(), Vec::new(), sp()),
®,
)
.unwrap();
match z {
ConstValue::Enum { variant, .. } => assert_eq!(variant, "A"),
other => panic!("expected enum, got {:?}", other),
}
}
fn newtype(name: &str, refinement: Option<&str>, sat_min: Option<i64>) -> NewtypeDef {
NewtypeDef {
name: String::from(name),
underlying: prim(PrimType::Word),
refinement: refinement.map(String::from),
saturate_max: None,
saturate_min: sat_min,
span: sp(),
}
}
#[test]
fn newtype_lowest_valid_prefers_declared_saturate_min() {
let mut n = BTreeMap::new();
n.insert(String::from("Limited"), newtype("Limited", None, Some(5)));
let (s, e, _n, r) = empty_reg();
let reg = reg(&s, &e, &n, &r);
let nt = &n[&String::from("Limited")];
assert_eq!(lowest_valid(nt, ®), Some(5));
assert_eq!(
zero_value(
&TypeExpr::Named(String::from("Limited"), Vec::new(), Vec::new(), sp()),
®
),
Ok(ConstValue::Int(5))
);
}
#[test]
fn newtype_lowest_valid_from_predicate_interval() {
let body = Expr::BinOp {
op: BinOp::GtEq,
left: alloc::boxed::Box::new(Expr::Ident {
name: String::from("x"),
span: sp(),
}),
right: alloc::boxed::Box::new(Expr::Literal {
value: Literal::Int(5),
span: sp(),
}),
span: sp(),
};
let mut r = BTreeMap::new();
r.insert(String::from("at_least_5"), (String::from("x"), body));
let mut n = BTreeMap::new();
n.insert(
String::from("Big"),
newtype("Big", Some("at_least_5"), None),
);
let (s, e, _n, _r) = empty_reg();
let reg = reg(&s, &e, &n, &r);
let nt = &n[&String::from("Big")];
assert_eq!(lowest_valid(nt, ®), Some(5));
}
#[test]
fn newtype_without_lower_bound_falls_back_to_hard_zero() {
let mut n = BTreeMap::new();
n.insert(String::from("Raw"), newtype("Raw", None, None));
let (s, e, _n, r) = empty_reg();
let reg = reg(&s, &e, &n, &r);
let nt = &n[&String::from("Raw")];
assert_eq!(lowest_valid(nt, ®), None);
assert_eq!(
zero_value(
&TypeExpr::Named(String::from("Raw"), Vec::new(), Vec::new(), sp()),
®
),
Ok(ConstValue::Int(0))
);
}
#[test]
fn unknown_named_type_errors() {
let (s, e, n, r) = empty_reg();
let reg = reg(&s, &e, &n, &r);
assert_eq!(
zero_value(
&TypeExpr::Named(String::from("Nope"), Vec::new(), Vec::new(), sp()),
®
),
Err(ZeroValueError::UnknownType(String::from("Nope")))
);
}
}