use std::{collections::BTreeMap, fmt, mem, sync::Arc};
use crate::{
ast::Argument,
symbol::{Symbol, SymbolRef},
};
pub trait MetadataEnv {
fn get_metadata(&self, id: &SymbolRef) -> Option<Arc<Metadata>>;
}
impl<'a, T: ?Sized + MetadataEnv> MetadataEnv for &'a T {
fn get_metadata(&self, id: &SymbolRef) -> Option<Arc<Metadata>> {
(**self).get_metadata(id)
}
}
impl MetadataEnv for () {
fn get_metadata(&self, _id: &SymbolRef) -> Option<Arc<Metadata>> {
None
}
}
#[derive(Clone, Copy, Eq, PartialEq, Debug, Hash)]
#[cfg_attr(feature = "serde_derive", derive(Deserialize, Serialize))]
pub enum CommentType {
Block,
Line,
}
#[derive(Clone, Eq, PartialEq, Debug, Hash)]
#[cfg_attr(feature = "serde_derive", derive(Deserialize, Serialize))]
pub struct Comment<S = String> {
pub typ: CommentType,
pub content: S,
}
#[derive(Clone, Debug, Default, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "serde_derive", derive(Deserialize, Serialize))]
pub struct Attribute {
pub name: String,
pub arguments: Option<String>,
}
impl fmt::Display for Attribute {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "#[{}", self.name)?;
if let Some(arguments) = &self.arguments {
write!(f, "({})", arguments)?;
}
write!(f, "]")
}
}
#[derive(Debug, Default, Eq, PartialEq, Hash, gluon_codegen::AstClone)]
pub struct BaseMetadata<'ast> {
pub metadata: Option<&'ast mut Metadata>,
}
impl From<BaseMetadata<'_>> for Metadata {
fn from(meta: BaseMetadata<'_>) -> Self {
meta.metadata.map(|m| m.clone()).unwrap_or_default()
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "serde_derive", derive(Deserialize, Serialize))]
pub struct Metadata {
pub definition: Option<Symbol>,
pub comment: Option<Comment>,
pub attributes: Vec<Attribute>,
pub args: Vec<Argument<Symbol>>,
pub module: BTreeMap<String, Arc<Metadata>>,
}
impl Metadata {
pub fn has_data(&self) -> bool {
self.definition.is_some()
|| self.comment.is_some()
|| !self.module.is_empty()
|| !self.attributes.is_empty()
}
pub fn merge(mut self, other: Metadata) -> Metadata {
self.merge_with(other);
self
}
pub fn merge_with(&mut self, other: Metadata) {
if other.definition.is_some() {
self.definition = other.definition;
}
if self.comment.is_none() {
self.comment = other.comment;
}
for (key, value) in other.module {
use std::collections::btree_map::Entry;
match self.module.entry(key) {
Entry::Vacant(entry) => {
entry.insert(value);
}
Entry::Occupied(entry) => Arc::make_mut(entry.into_mut()).merge_with_ref(&value),
}
}
self.attributes.extend(other.attributes);
if self.args.is_empty() {
self.args = other.args;
}
}
pub fn merge_ref(mut self, other: &Metadata) -> Self {
self.merge_with_ref(other);
self
}
pub fn merge_with_ref(&mut self, other: &Metadata) {
if other.definition.is_some() {
self.definition = other.definition.clone();
}
if self.comment.is_none() {
self.comment = other.comment.clone();
}
for (key, value) in &other.module {
use std::collections::btree_map::Entry;
match self.module.entry(key.clone()) {
Entry::Vacant(entry) => {
entry.insert(value.clone());
}
Entry::Occupied(entry) => Arc::make_mut(entry.into_mut()).merge_with_ref(&value),
}
}
self.attributes.extend(other.attributes.iter().cloned());
if self.args.is_empty() {
self.args = other.args.clone();
}
}
pub fn merge_with_base(mut self, other: &BaseMetadata<'_>) -> Self {
self.merge_with_base_ref(other);
self
}
pub fn merge_with_base_ref(&mut self, other: &BaseMetadata<'_>) {
if let Some(other) = &other.metadata {
self.merge_with_ref(other);
}
}
pub fn get_attribute(&self, name: &str) -> Option<&str> {
self.attributes()
.find(|attribute| attribute.name == name)
.map(|t| t.arguments.as_ref().map_or("", |s| s))
}
pub fn attributes(&self) -> impl Iterator<Item = &Attribute> {
self.attributes.iter()
}
}
impl<'ast> BaseMetadata<'ast> {
pub fn has_data(&self) -> bool {
self.metadata.is_some()
}
pub fn merge(&mut self, metadata: BaseMetadata<'ast>) {
match &mut self.metadata {
Some(self_) => {
if let Some(metadata) = metadata.metadata {
self_.merge_with(mem::take(metadata));
}
}
None => *self = metadata,
}
}
pub fn comment(&self) -> Option<&Comment> {
self.metadata.as_ref().and_then(|m| m.comment.as_ref())
}
pub fn get_attribute(&self, name: &str) -> Option<&str> {
self.attributes()
.find(|attribute| attribute.name == name)
.map(|t| t.arguments.as_ref().map_or("", |s| s))
}
pub fn attributes(&self) -> impl Iterator<Item = &Attribute> {
self.metadata.iter().flat_map(|m| m.attributes())
}
pub fn to_metadata(&self) -> Metadata {
self.metadata
.as_ref()
.map(|m| (**m).clone())
.unwrap_or_default()
}
}