use alloc::{
string::{String, ToString},
sync::Arc,
};
use core::{
fmt,
hash::{Hash, Hasher},
str::FromStr,
};
use miden_core::utils::{
ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable,
};
use miden_debug_types::{SourceSpan, Span, Spanned};
use miden_utils_diagnostics::{IntoDiagnostic, Report, miette};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use crate::{
LibraryNamespace, LibraryPath,
ast::{CaseKindError, Ident, IdentError},
};
#[derive(Clone)]
#[cfg_attr(feature = "arbitrary", derive(proptest_derive::Arbitrary))]
#[cfg_attr(
all(feature = "arbitrary", test),
miden_test_serde_macros::serde_test(winter_serde(true))
)]
pub struct QualifiedProcedureName {
#[cfg_attr(feature = "arbitrary", proptest(value = "SourceSpan::default()"))]
pub span: SourceSpan,
pub module: LibraryPath,
pub name: ProcedureName,
}
impl QualifiedProcedureName {
pub fn new(module: LibraryPath, name: ProcedureName) -> Self {
Self {
span: SourceSpan::default(),
module,
name,
}
}
pub fn namespace(&self) -> &LibraryNamespace {
self.module.namespace()
}
}
impl FromStr for QualifiedProcedureName {
type Err = Report;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.rsplit_once("::") {
None => Err(Report::msg("invalid fully-qualified procedure name, expected namespace")),
Some((path, name)) => {
let name = name.parse::<ProcedureName>().into_diagnostic()?;
let path = path.parse::<LibraryPath>().into_diagnostic()?;
Ok(Self::new(path, name))
},
}
}
}
impl TryFrom<&str> for QualifiedProcedureName {
type Error = Report;
fn try_from(name: &str) -> Result<Self, Self::Error> {
Self::from_str(name)
}
}
impl TryFrom<String> for QualifiedProcedureName {
type Error = Report;
fn try_from(name: String) -> Result<Self, Self::Error> {
Self::from_str(&name)
}
}
impl Eq for QualifiedProcedureName {}
impl PartialEq for QualifiedProcedureName {
fn eq(&self, other: &Self) -> bool {
self.name == other.name && self.module == other.module
}
}
impl Ord for QualifiedProcedureName {
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
self.module.cmp(&other.module).then_with(|| self.name.cmp(&other.name))
}
}
impl PartialOrd for QualifiedProcedureName {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl From<QualifiedProcedureName> for miette::SourceSpan {
fn from(fqn: QualifiedProcedureName) -> Self {
fqn.span.into()
}
}
impl Spanned for QualifiedProcedureName {
fn span(&self) -> SourceSpan {
self.span
}
}
impl fmt::Debug for QualifiedProcedureName {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("FullyQualifiedProcedureName")
.field("module", &self.module)
.field("name", &self.name)
.finish()
}
}
impl crate::prettier::PrettyPrint for QualifiedProcedureName {
fn render(&self) -> miden_core::prettier::Document {
use crate::prettier::*;
display(self)
}
}
impl fmt::Display for QualifiedProcedureName {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}::{}", &self.module, &self.name)
}
}
impl Serializable for QualifiedProcedureName {
fn write_into<W: ByteWriter>(&self, target: &mut W) {
self.module.write_into(target);
self.name.write_into(target);
}
}
impl Deserializable for QualifiedProcedureName {
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
let module = LibraryPath::read_from(source)?;
let name = ProcedureName::read_from(source)?;
Ok(Self::new(module, name))
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for QualifiedProcedureName {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
if serializer.is_human_readable() {
let name = format!("{}", self);
serializer.serialize_str(&name)
} else {
use serde::ser::SerializeStruct;
let mut builder = serializer.serialize_struct("QualifiedProcedureName", 2)?;
builder.serialize_field("module", &self.module)?;
builder.serialize_field("name", &self.name)?;
builder.end()
}
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for QualifiedProcedureName {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::de::Visitor;
if deserializer.is_human_readable() {
let name = <&'de str as serde::Deserialize>::deserialize(deserializer)?;
return Self::from_str(name).map_err(serde::de::Error::custom);
}
#[derive(Deserialize)]
#[serde(field_identifier, rename_all = "lowercase")]
enum Field {
Module,
Name,
}
struct QualifiedProcedureNameVisitor;
impl<'de> Visitor<'de> for QualifiedProcedureNameVisitor {
type Value = QualifiedProcedureName;
fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result {
formatter.write_str("struct QualifiedProcedureName")
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: serde::de::SeqAccess<'de>,
{
let module = seq
.next_element()?
.ok_or_else(|| serde::de::Error::invalid_length(0, &self))?;
let name = seq
.next_element()?
.ok_or_else(|| serde::de::Error::invalid_length(1, &self))?;
Ok(QualifiedProcedureName::new(module, name))
}
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
where
A: serde::de::MapAccess<'de>,
{
let mut module = None;
let mut name = None;
while let Some(key) = map.next_key()? {
match key {
Field::Module => {
if module.is_some() {
return Err(serde::de::Error::duplicate_field("module"));
}
module = Some(map.next_value()?);
},
Field::Name => {
if name.is_some() {
return Err(serde::de::Error::duplicate_field("name"));
}
name = Some(map.next_value()?);
},
}
}
let module = module.ok_or_else(|| serde::de::Error::missing_field("module"))?;
let name = name.ok_or_else(|| serde::de::Error::missing_field("name"))?;
Ok(QualifiedProcedureName::new(module, name))
}
}
deserializer.deserialize_struct(
"QualifiedProcedureName",
&["module", "name"],
QualifiedProcedureNameVisitor,
)
}
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(transparent))]
#[cfg_attr(
all(feature = "arbitrary", test),
miden_test_serde_macros::serde_test(winter_serde(true))
)]
pub struct ProcedureName(Ident);
impl ProcedureName {
pub const MAIN_PROC_NAME: &'static str = "$main";
pub fn new(name: impl AsRef<str>) -> Result<Self, IdentError> {
name.as_ref().parse()
}
pub fn new_with_span(span: SourceSpan, name: impl AsRef<str>) -> Result<Self, IdentError> {
name.as_ref().parse::<Self>().map(|name| name.with_span(span))
}
pub fn with_span(self, span: SourceSpan) -> Self {
Self(self.0.with_span(span))
}
pub fn from_raw_parts(name: Ident) -> Self {
Self(name)
}
pub fn main() -> Self {
let name = Arc::from(Self::MAIN_PROC_NAME.to_string().into_boxed_str());
Self(Ident::from_raw_parts(Span::unknown(name)))
}
pub fn is_main(&self) -> bool {
self.0.as_str() == Self::MAIN_PROC_NAME
}
pub fn as_str(&self) -> &str {
self.as_ref()
}
}
impl Eq for ProcedureName {}
impl PartialEq for ProcedureName {
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
impl Ord for ProcedureName {
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
self.0.cmp(&other.0)
}
}
impl PartialOrd for ProcedureName {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Hash for ProcedureName {
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.hash(state);
}
}
impl Spanned for ProcedureName {
fn span(&self) -> SourceSpan {
self.0.span()
}
}
impl From<ProcedureName> for miette::SourceSpan {
fn from(name: ProcedureName) -> Self {
name.span().into()
}
}
impl core::ops::Deref for ProcedureName {
type Target = str;
#[inline(always)]
fn deref(&self) -> &Self::Target {
self.0.as_str()
}
}
impl AsRef<Ident> for ProcedureName {
#[inline(always)]
fn as_ref(&self) -> &Ident {
&self.0
}
}
impl AsRef<str> for ProcedureName {
#[inline(always)]
fn as_ref(&self) -> &str {
self.0.as_str()
}
}
impl PartialEq<str> for ProcedureName {
fn eq(&self, other: &str) -> bool {
self.0.as_ref() == other
}
}
impl PartialEq<Ident> for ProcedureName {
fn eq(&self, other: &Ident) -> bool {
&self.0 == other
}
}
impl fmt::Display for ProcedureName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}
impl FromStr for ProcedureName {
type Err = IdentError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut chars = s.char_indices().peekable();
match chars.peek() {
None => return Err(IdentError::Empty),
Some((_, '"')) => chars.next(),
Some((_, c)) if is_valid_unquoted_identifier_char(*c) => {
let all_chars_valid =
chars.all(|(_, char)| is_valid_unquoted_identifier_char(char));
if all_chars_valid {
return Ok(Self(Ident::from_raw_parts(Span::unknown(s.into()))));
} else {
return Err(IdentError::InvalidChars { ident: s.into() });
}
},
Some((_, c)) if c.is_ascii_uppercase() => {
return Err(IdentError::Casing(CaseKindError::Snake));
},
Some(_) => return Err(IdentError::InvalidChars { ident: s.into() }),
};
while let Some((pos, char)) = chars.next() {
match char {
'"' => {
if chars.next().is_some() {
return Err(IdentError::InvalidChars { ident: s.into() });
}
let token = &s[0..pos];
return Ok(Self(Ident::from_raw_parts(Span::unknown(token.into()))));
},
c => {
if !(c.is_alphanumeric() || c.is_ascii_graphic()) {
return Err(IdentError::InvalidChars { ident: s.into() });
}
},
}
}
Err(IdentError::InvalidChars { ident: s.into() })
}
}
fn is_valid_unquoted_identifier_char(c: char) -> bool {
c.is_ascii_alphanumeric() || matches!(c, '_' | '-' | '$' | '.')
}
impl Serializable for ProcedureName {
fn write_into<W: ByteWriter>(&self, target: &mut W) {
self.as_str().write_into(target)
}
}
impl Deserializable for ProcedureName {
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
let str: String = source.read()?;
let proc_name = ProcedureName::new(str)
.map_err(|e| DeserializationError::InvalidValue(e.to_string()))?;
Ok(proc_name)
}
}
#[cfg(any(test, feature = "arbitrary"))]
impl proptest::prelude::Arbitrary for ProcedureName {
type Parameters = ();
fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
use proptest::prelude::*;
let all_possible_chars_in_mangled_name =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.$";
let mangled_rustc_name = ProcedureName::new(all_possible_chars_in_mangled_name).unwrap();
let plain = ProcedureName::new("user_func").unwrap();
let wasm_cm_style = ProcedureName::new("kebab-case-func").unwrap();
prop_oneof![Just(mangled_rustc_name), Just(plain), Just(wasm_cm_style)].boxed()
}
type Strategy = proptest::prelude::BoxedStrategy<Self>;
}