use alloc::{collections::BTreeSet, string::String};
use core::fmt;
use miden_debug_types::{SourceSpan, Span, Spanned};
use super::ProcedureName;
use crate::ast::{Attribute, AttributeSet, Block, DocString, FunctionType, Invoke, Visibility};
#[derive(Clone)]
pub struct Procedure {
span: SourceSpan,
docs: Option<DocString>,
attrs: AttributeSet,
name: ProcedureName,
visibility: Visibility,
syscall: bool,
ty: Option<FunctionType>,
num_locals: u16,
body: Block,
pub(crate) invoked: BTreeSet<Invoke>,
}
impl Procedure {
pub fn new(
span: SourceSpan,
visibility: Visibility,
name: ProcedureName,
num_locals: u16,
body: Block,
) -> Self {
Self {
span,
docs: None,
attrs: Default::default(),
name,
visibility,
syscall: false,
ty: None,
num_locals,
invoked: Default::default(),
body,
}
}
pub fn new_syscall(
span: SourceSpan,
name: ProcedureName,
num_locals: u16,
body: Block,
) -> Self {
Self {
span,
docs: None,
attrs: Default::default(),
name,
visibility: Visibility::Public,
syscall: true,
ty: None,
num_locals,
invoked: Default::default(),
body,
}
}
pub fn with_signature(mut self, ty: FunctionType) -> Self {
self.ty = Some(ty);
self
}
pub fn with_docs(mut self, docs: Option<Span<String>>) -> Self {
self.docs = docs.map(DocString::new);
self
}
pub fn with_attributes<I>(mut self, attrs: I) -> Self
where
I: IntoIterator<Item = Attribute>,
{
self.attrs.extend(attrs);
self
}
pub fn set_visibility(&mut self, visibility: Visibility) {
self.visibility = visibility;
}
pub fn set_syscall(&mut self, yes: bool) {
self.syscall = yes;
}
pub fn set_signature(&mut self, signature: FunctionType) {
self.ty = Some(signature);
}
pub fn set_num_locals(&mut self, num_locals: u16) {
self.num_locals = num_locals;
}
}
impl Procedure {
pub fn name(&self) -> &ProcedureName {
&self.name
}
pub fn visibility(&self) -> Visibility {
self.visibility
}
pub fn is_syscall(&self) -> bool {
self.syscall
}
pub fn signature(&self) -> Option<&FunctionType> {
self.ty.as_ref()
}
pub fn signature_mut(&mut self) -> Option<&mut FunctionType> {
self.ty.as_mut()
}
pub fn num_locals(&self) -> u16 {
self.num_locals
}
pub fn is_entrypoint(&self) -> bool {
self.name.is_main()
}
pub fn docs(&self) -> Option<Span<&str>> {
self.docs.as_ref().map(|docstring| docstring.as_spanned_str())
}
#[inline]
pub fn attributes(&self) -> &AttributeSet {
&self.attrs
}
#[inline]
pub fn attributes_mut(&mut self) -> &mut AttributeSet {
&mut self.attrs
}
#[inline]
pub fn has_attribute(&self, name: impl AsRef<str>) -> bool {
self.attrs.has(name)
}
#[inline]
pub fn get_attribute(&self, name: impl AsRef<str>) -> Option<&Attribute> {
self.attrs.get(name)
}
pub fn body(&self) -> &Block {
&self.body
}
pub fn body_mut(&mut self) -> &mut Block {
&mut self.body
}
pub fn iter(&self) -> core::slice::Iter<'_, crate::ast::Op> {
self.body.iter()
}
pub fn invoked<'a, 'b: 'a>(&'b self) -> impl Iterator<Item = &'a Invoke> + 'a {
if self.invoked.is_empty() {
InvokedIter::Empty
} else {
InvokedIter::NonEmpty(self.invoked.iter())
}
}
pub fn extend_invoked<I>(&mut self, iter: I)
where
I: IntoIterator<Item = Invoke>,
{
self.invoked.extend(iter);
}
}
#[doc(hidden)]
pub(crate) enum InvokedIter<'a, I: Iterator<Item = &'a Invoke> + 'a> {
Empty,
NonEmpty(I),
}
impl<'a, I> Iterator for InvokedIter<'a, I>
where
I: Iterator<Item = &'a Invoke> + 'a,
{
type Item = <I as Iterator>::Item;
fn next(&mut self) -> Option<Self::Item> {
match self {
Self::Empty => None,
Self::NonEmpty(iter) => {
let result = iter.next();
if result.is_none() {
*self = Self::Empty;
}
result
},
}
}
}
impl Spanned for Procedure {
fn span(&self) -> SourceSpan {
self.span
}
}
impl crate::prettier::PrettyPrint for Procedure {
fn render(&self) -> crate::prettier::Document {
use crate::prettier::*;
let mut doc = self
.docs
.as_ref()
.map(|docstring| docstring.render())
.unwrap_or(Document::Empty);
if !self.attrs.is_empty() {
doc += self
.attrs
.iter()
.map(|attr| attr.render())
.reduce(|acc, attr| acc + nl() + attr)
.unwrap_or(Document::Empty);
}
if self.is_entrypoint() {
doc += const_text("begin");
} else {
if self.num_locals > 0 {
doc += text(format!("@locals(\"{}\")", &self.num_locals)) + nl();
}
match self.signature() {
Some(sig) if sig.cc != crate::ast::types::CallConv::Fast => {
doc += text(format!("@callconv(\"{}\")", &sig.cc)) + nl();
},
_ => (),
}
if self.visibility.is_public() {
doc += display(self.visibility) + const_text(" ");
}
doc += const_text("proc") + const_text(" ") + display(&self.name);
if let Some(sig) = self.signature() {
doc += sig.render();
}
}
doc + self.body.render() + const_text("end") + nl()
}
}
impl fmt::Debug for Procedure {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Procedure")
.field("docs", &self.docs)
.field("attrs", &self.attrs)
.field("name", &self.name)
.field("visibility", &self.visibility)
.field("syscall", &self.syscall)
.field("num_locals", &self.num_locals)
.field("ty", &self.ty)
.field("body", &self.body)
.field("invoked", &self.invoked)
.finish()
}
}
impl Eq for Procedure {}
impl PartialEq for Procedure {
fn eq(&self, other: &Self) -> bool {
self.name == other.name
&& self.visibility == other.visibility
&& self.syscall == other.syscall
&& self.num_locals == other.num_locals
&& self.ty == other.ty
&& self.body == other.body
&& self.attrs == other.attrs
&& self.docs == other.docs
}
}