Skip to main content

lex_core/lex/ast/elements/
annotation.rs

1//! Annotation
2//!
3//! Annotations are a core element in lex, but not the document's content , rather it's metadata one.
4//! They provide not only a way for authors and collaborators to register non content related
5//! information, but the right hooks for tooling to build on top of lex (e.g., comments, review
6//! metadata, publishing hints).
7//!
8//! As such they provide : -
9//! - labels: a way to identify the annotation
10//! - parameters (optional): a way to provide structured data
11//! - Optional content, like all other elements:
12//!     - Nestable containter that can host any element but sessions
13//!     - Shorthand for for single or no content annotations.
14//!
15//!
16//! Syntax:
17//!   Short Hand Form:
18//!     <lex-marker> <label> <parameters>? <lex-marker>
19//!   Long Hand Form:
20//!     <lex-marker> <label> <parameters>? <lex-marker>
21//!     <indent> <content> ... any number of content elements
22//!     <dedent> <lex-marker>
23//!
24//! Parsing Structure:
25//!
26//! | Element    | Prec. Blank | Head                | Blank | Content | Tail          |
27//! |------------|-------------|---------------------|-------|---------|---------------|
28//! | Annotation | Optional    | AnnotationStartLine | Yes   | Yes     | AnnotationEnd |
29//!
30//! Special Case: Short form annotations are one-liners without content or dedent.
31//!
32//!  Examples:
33//!      Label only:
34//!         :: image ::  
35//!      Label and parameters:
36//!         :: note severity=high :: Check this carefully
37//!      Marker form (no content):
38//!         :: debug ::
39//!      Parameters augmenting the label:
40//!         :: meta type=python :: (parameters need an accompanying label)
41//!      Long Form:
42//!         :: label ::
43//!             John has reviewed this paragraph. Hence we're only lacking:
44//!             - Janest's approval
45//!             - OK from legal
46//! Learn More:
47//! - The annotation spec: specs/v1/elements/annotation.lex
48//! - The annotation sample: specs/v1/samples/element-based/annotations/annotations.simple.lex
49//! - Labels: specs/v1/elements/label.lex
50//! - Parameters: specs/v1/elements/parameter.lex
51
52use super::super::range::{Position, Range};
53use super::super::traits::{AstNode, Container, Visitor, VisualStructure};
54use super::container::GeneralContainer;
55use super::content_item::ContentItem;
56use super::data::Data;
57use super::label::Label;
58use super::parameter::Parameter;
59use super::typed_content::ContentElement;
60use std::fmt;
61
62/// An annotation represents some metadata about an AST element.
63#[derive(Debug, Clone, PartialEq)]
64pub struct Annotation {
65    pub data: Data,
66    pub children: GeneralContainer,
67    pub location: Range,
68}
69
70impl Annotation {
71    fn default_location() -> Range {
72        Range::new(0..0, Position::new(0, 0), Position::new(0, 0))
73    }
74    pub fn new(label: Label, parameters: Vec<Parameter>, children: Vec<ContentElement>) -> Self {
75        let data = Data::new(label, parameters);
76        Self::from_data(data, children)
77    }
78    pub fn marker(label: Label) -> Self {
79        Self::from_data(Data::new(label, Vec::new()), Vec::new())
80    }
81    pub fn with_parameters(label: Label, parameters: Vec<Parameter>) -> Self {
82        Self::from_data(Data::new(label, parameters), Vec::new())
83    }
84    pub fn from_data(data: Data, children: Vec<ContentElement>) -> Self {
85        Self {
86            data,
87            children: GeneralContainer::from_typed(children),
88            location: Self::default_location(),
89        }
90    }
91
92    /// Preferred builder
93    pub fn at(mut self, location: Range) -> Self {
94        self.location = location;
95        self
96    }
97
98    /// Range covering only the annotation header (label + parameters).
99    pub fn header_location(&self) -> &Range {
100        &self.data.location
101    }
102
103    /// Bounding range covering only the annotation's children.
104    pub fn body_location(&self) -> Option<Range> {
105        Range::bounding_box(self.children.iter().map(|item| item.range()))
106    }
107}
108
109impl AstNode for Annotation {
110    fn node_type(&self) -> &'static str {
111        "Annotation"
112    }
113    fn display_label(&self) -> String {
114        if self.data.parameters.is_empty() {
115            self.data.label.value.clone()
116        } else {
117            format!(
118                "{} ({} params)",
119                self.data.label.value,
120                self.data.parameters.len()
121            )
122        }
123    }
124    fn range(&self) -> &Range {
125        &self.location
126    }
127
128    fn accept(&self, visitor: &mut dyn Visitor) {
129        visitor.visit_annotation(self);
130        super::super::traits::visit_children(visitor, &self.children);
131        visitor.leave_annotation(self);
132    }
133}
134
135impl VisualStructure for Annotation {
136    fn is_source_line_node(&self) -> bool {
137        true
138    }
139
140    fn has_visual_header(&self) -> bool {
141        true
142    }
143}
144
145impl Container for Annotation {
146    fn label(&self) -> &str {
147        &self.data.label.value
148    }
149    fn children(&self) -> &[ContentItem] {
150        &self.children
151    }
152    fn children_mut(&mut self) -> &mut Vec<ContentItem> {
153        self.children.as_mut_vec()
154    }
155}
156
157impl fmt::Display for Annotation {
158    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
159        write!(
160            f,
161            "Annotation('{}', {} params, {} items)",
162            self.data.label.value,
163            self.data.parameters.len(),
164            self.children.len()
165        )
166    }
167}
168
169#[cfg(test)]
170mod tests {
171    use super::*;
172    use crate::lex::ast::elements::paragraph::Paragraph;
173    use crate::lex::ast::elements::typed_content::ContentElement;
174
175    #[test]
176    fn test_annotation_header_and_body_locations() {
177        let header_range = Range::new(0..4, Position::new(0, 0), Position::new(0, 4));
178        let child_range = Range::new(10..20, Position::new(1, 0), Position::new(2, 0));
179        let label = Label::new("note".to_string()).at(header_range.clone());
180        let data = Data::new(label, Vec::new()).at(header_range.clone());
181        let child = ContentElement::Paragraph(
182            Paragraph::from_line("body".to_string()).at(child_range.clone()),
183        );
184
185        let annotation = Annotation::from_data(data, vec![child]).at(Range::new(
186            0..25,
187            Position::new(0, 0),
188            Position::new(2, 0),
189        ));
190
191        assert_eq!(annotation.header_location().span, header_range.span);
192        assert_eq!(annotation.body_location().unwrap().span, child_range.span);
193    }
194}