use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum OwnershipPattern {
Alloc,
Free,
Borrow,
Transfer,
}
impl fmt::Display for OwnershipPattern {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
OwnershipPattern::Alloc => write!(f, "alloc"),
OwnershipPattern::Free => write!(f, "free"),
OwnershipPattern::Borrow => write!(f, "borrow"),
OwnershipPattern::Transfer => write!(f, "transfer"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Viewtype {
pub name: String,
pub c_type: String,
pub nullable: bool,
pub size_param: Option<String>,
}
impl Viewtype {
pub fn new(name: &str, c_type: &str) -> Self {
Self {
name: name.to_string(),
c_type: c_type.to_string(),
nullable: false,
size_param: None,
}
}
pub fn nullable(name: &str, c_type: &str) -> Self {
Self {
name: name.to_string(),
c_type: c_type.to_string(),
nullable: true,
size_param: None,
}
}
pub fn sized(name: &str, c_type: &str, size_param: &str) -> Self {
Self {
name: name.to_string(),
c_type: c_type.to_string(),
nullable: false,
size_param: Some(size_param.to_string()),
}
}
pub fn to_ats2_definition(&self) -> String {
let size_suffix = match &self.size_param {
Some(param) => format!("({})", param),
None => String::new(),
};
if self.nullable {
format!(
"viewtypedef {}{} = [l:addr] (option_v({} @ l, l > null) | ptr l)",
self.name, size_suffix, self.c_type
)
} else {
format!(
"viewtypedef {}{} = [l:addr] ({} @ l | ptr l)",
self.name, size_suffix, self.c_type
)
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct LinearPtr {
pub id: String,
pub viewtype: Viewtype,
pub pattern: OwnershipPattern,
}
impl LinearPtr {
pub fn new(id: &str, viewtype: Viewtype, pattern: OwnershipPattern) -> Self {
Self {
id: id.to_string(),
viewtype,
pattern,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum MemorySafetyProof {
AllocProof {
viewtype: String,
},
FreeProof {
ptr_id: String,
},
BorrowProof {
ptr_id: String,
mutable: bool,
},
BoundsProof {
buffer_id: String,
index_expr: String,
},
NullCheckProof {
ptr_id: String,
},
}
impl MemorySafetyProof {
pub fn to_ats2_proof(&self) -> String {
match self {
MemorySafetyProof::AllocProof { viewtype } => {
format!("prval (pf_{} | p_{}) = alloc_{}", viewtype, viewtype, viewtype)
}
MemorySafetyProof::FreeProof { ptr_id } => {
format!("prval () = free_{}(pf_{}, p_{})", ptr_id, ptr_id, ptr_id)
}
MemorySafetyProof::BorrowProof { ptr_id, mutable } => {
let borrow_kind = if *mutable { "vw" } else { "v" };
format!(
"prval (pf_borrow | p_ref) = borrow_{}(pf_{}, p_{})",
borrow_kind, ptr_id, ptr_id
)
}
MemorySafetyProof::BoundsProof {
buffer_id,
index_expr,
} => {
format!(
"prval () = lemma_bounds(pf_{}, {})",
buffer_id, index_expr
)
}
MemorySafetyProof::NullCheckProof { ptr_id } => {
format!(
"prval () = opt_unsome(pf_{})",
ptr_id
)
}
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ATSModule {
pub name: String,
pub includes: Vec<String>,
pub viewtypes: Vec<Viewtype>,
pub functions: Vec<ATSFunction>,
pub proofs: Vec<MemorySafetyProof>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ATSFunction {
pub name: String,
pub c_function: String,
pub params: Vec<ATSParam>,
pub return_type: Option<String>,
pub pattern: OwnershipPattern,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ATSParam {
pub name: String,
pub ats_type: String,
pub consumed: bool,
}
impl ATSModule {
pub fn new(name: &str) -> Self {
Self {
name: name.to_string(),
includes: Vec::new(),
viewtypes: Vec::new(),
functions: Vec::new(),
proofs: Vec::new(),
}
}
pub fn to_ats2_source(&self) -> String {
let mut out = String::new();
out.push_str(&format!(
"(*\n** SPDX-License-Identifier: PMPL-1.0-or-later\n\
** Generated by atsiser — do not edit manually.\n\
** Module: {}\n*)\n\n",
self.name
));
for inc in &self.includes {
out.push_str(&format!("#include \"{}\"\n", inc));
}
if !self.includes.is_empty() {
out.push('\n');
}
out.push_str(&format!(
"staload \"{}_c.sats\"\n\n",
self.name
));
for vt in &self.viewtypes {
out.push_str(&vt.to_ats2_definition());
out.push_str("\n\n");
}
for func in &self.functions {
out.push_str(&generate_ats_function(func));
out.push('\n');
}
out
}
}
fn generate_ats_function(func: &ATSFunction) -> String {
let mut out = String::new();
let params_str: Vec<String> = func
.params
.iter()
.map(|p| {
if p.consumed {
format!("{}: {}", p.name, p.ats_type)
} else {
format!("!{}: {}", p.name, p.ats_type)
}
})
.collect();
let ret = func
.return_type
.as_deref()
.unwrap_or("void");
out.push_str(&format!(
"implement\nfun {}({}): {} = let\n",
func.name,
params_str.join(", "),
ret
));
match func.pattern {
OwnershipPattern::Alloc => {
out.push_str(&format!(
" val (pf | p) = $extfcall(ptr, \"{}\"",
func.c_function
));
for param in &func.params {
out.push_str(&format!(", {}", param.name));
}
out.push_str(")\n");
out.push_str("in\n (pf | p)\nend\n");
}
OwnershipPattern::Free => {
let ptr_param = func.params.first().map(|p| p.name.as_str()).unwrap_or("p");
out.push_str(&format!(
" val () = $extfcall(void, \"{}\", {})\n",
func.c_function, ptr_param
));
out.push_str(" prval () = __assert() where {\n");
out.push_str(" extern praxi __assert(): void\n");
out.push_str(" }\n");
out.push_str("in end\n");
}
OwnershipPattern::Borrow => {
out.push_str(&format!(
" val result = $extfcall(_, \"{}\", ",
func.c_function
));
let param_names: Vec<&str> = func.params.iter().map(|p| p.name.as_str()).collect();
out.push_str(¶m_names.join(", "));
out.push_str(")\n");
out.push_str("in\n result\nend\n");
}
OwnershipPattern::Transfer => {
let ptr_param = func.params.first().map(|p| p.name.as_str()).unwrap_or("p");
out.push_str(&format!(
" val () = $extfcall(void, \"{}\", {})\n",
func.c_function, ptr_param
));
out.push_str(" // Ownership transferred — linear resource consumed\n");
out.push_str("in end\n");
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ownership_pattern_display() {
assert_eq!(OwnershipPattern::Alloc.to_string(), "alloc");
assert_eq!(OwnershipPattern::Free.to_string(), "free");
assert_eq!(OwnershipPattern::Borrow.to_string(), "borrow");
assert_eq!(OwnershipPattern::Transfer.to_string(), "transfer");
}
#[test]
fn test_viewtype_definition() {
let vt = Viewtype::new("FILE_vt", "FILE");
let def = vt.to_ats2_definition();
assert!(def.contains("viewtypedef FILE_vt"));
assert!(def.contains("FILE @ l"));
assert!(!def.contains("option_v"));
}
#[test]
fn test_nullable_viewtype() {
let vt = Viewtype::nullable("ptr_vt", "void");
let def = vt.to_ats2_definition();
assert!(def.contains("option_v"));
}
#[test]
fn test_sized_viewtype() {
let vt = Viewtype::sized("buf_vt", "char", "n");
let def = vt.to_ats2_definition();
assert!(def.contains("buf_vt(n)"));
}
#[test]
fn test_ats_module_generation() {
let mut module = ATSModule::new("test_mod");
module.viewtypes.push(Viewtype::new("int_vt", "int"));
let source = module.to_ats2_source();
assert!(source.contains("Module: test_mod"));
assert!(source.contains("viewtypedef int_vt"));
assert!(source.contains("staload"));
}
#[test]
fn test_memory_safety_proof_generation() {
let proof = MemorySafetyProof::AllocProof {
viewtype: "buf".to_string(),
};
let ats = proof.to_ats2_proof();
assert!(ats.contains("prval"));
assert!(ats.contains("alloc_buf"));
}
}