Skip to main content

lex_core/lex/ast/elements/
definition.rs

1//! Definition element
2//!
3//!  Definitions are a core element for explaining terms and concepts.
4//!  They pair a subject (the term being defined) with its content, the definition body.
5//!
6//! Syntax:
7//!     <text-span>+ <colon> <line-break>
8//!     <indent> <content> ... any number of content elements
9//!     <dedent>
10//!
11//! Parsing Structure:
12//!
13//! | Element    | Prec. Blank | Head        | Blank | Content | Tail   |
14//! |------------|-------------|-------------|-------|---------|--------|
15//! | Definition | Optional    | SubjectLine | No    | Yes     | dedent |
16//!
17//! Examples:
18//!     Cache:
19//!         Temporary storage for frequently accessed data.
20//!
21//!     Microservice:
22//!         An architectural style that structures applications as loosely coupled services.
23//!
24//!         Each service is independently deployable and scalable.
25//!
26//! Learn More:
27//! - The definition spec: specs/v1/elements/definition.lex
28//! - The definition sample: specs/v1/samples/element-based/definitions/definitions.simple.lex
29
30use super::super::range::{Position, Range};
31use super::super::text_content::TextContent;
32use super::super::traits::{AstNode, Container, Visitor, VisualStructure};
33use super::annotation::Annotation;
34use super::container::GeneralContainer;
35use super::content_item::ContentItem;
36use super::typed_content::ContentElement;
37use std::fmt;
38
39/// A definition provides a subject and associated content
40#[derive(Debug, Clone, PartialEq)]
41pub struct Definition {
42    pub subject: TextContent,
43    pub children: GeneralContainer,
44    pub annotations: Vec<Annotation>,
45    pub location: Range,
46}
47
48impl Definition {
49    fn default_location() -> Range {
50        Range::new(0..0, Position::new(0, 0), Position::new(0, 0))
51    }
52    pub fn new(subject: TextContent, children: Vec<ContentElement>) -> Self {
53        Self {
54            subject,
55            children: GeneralContainer::from_typed(children),
56            annotations: Vec::new(),
57            location: Self::default_location(),
58        }
59    }
60    pub fn with_subject(subject: String) -> Self {
61        Self {
62            subject: TextContent::from_string(subject, None),
63            children: GeneralContainer::empty(),
64            annotations: Vec::new(),
65            location: Self::default_location(),
66        }
67    }
68    /// Preferred builder
69    pub fn at(mut self, location: Range) -> Self {
70        self.location = location;
71        self
72    }
73
74    /// Annotations attached to this definition.
75    pub fn annotations(&self) -> &[Annotation] {
76        &self.annotations
77    }
78
79    /// Mutable access to definition annotations.
80    pub fn annotations_mut(&mut self) -> &mut Vec<Annotation> {
81        &mut self.annotations
82    }
83
84    /// Iterate over annotation blocks attached to this definition.
85    pub fn iter_annotations(&self) -> std::slice::Iter<'_, Annotation> {
86        self.annotations.iter()
87    }
88
89    /// Iterate over all content items nested inside attached annotations.
90    pub fn iter_annotation_contents(&self) -> impl Iterator<Item = &ContentItem> {
91        self.annotations
92            .iter()
93            .flat_map(|annotation| annotation.children())
94    }
95
96    /// Range covering only the subject line.
97    pub fn header_location(&self) -> Option<&Range> {
98        self.subject.location.as_ref()
99    }
100
101    /// Bounding range covering only the definition's children.
102    pub fn body_location(&self) -> Option<Range> {
103        Range::bounding_box(self.children.iter().map(|item| item.range()))
104    }
105}
106
107impl AstNode for Definition {
108    fn node_type(&self) -> &'static str {
109        "Definition"
110    }
111    fn display_label(&self) -> String {
112        let subject_text = self.subject.as_string();
113        if subject_text.chars().count() > 50 {
114            format!("{}…", subject_text.chars().take(50).collect::<String>())
115        } else {
116            subject_text.to_string()
117        }
118    }
119    fn range(&self) -> &Range {
120        &self.location
121    }
122
123    fn accept(&self, visitor: &mut dyn Visitor) {
124        visitor.visit_definition(self);
125        super::super::traits::visit_children(visitor, &self.children);
126        visitor.leave_definition(self);
127    }
128}
129
130impl VisualStructure for Definition {
131    fn is_source_line_node(&self) -> bool {
132        true
133    }
134
135    fn has_visual_header(&self) -> bool {
136        true
137    }
138}
139
140impl Container for Definition {
141    fn label(&self) -> &str {
142        self.subject.as_string()
143    }
144    fn children(&self) -> &[ContentItem] {
145        &self.children
146    }
147    fn children_mut(&mut self) -> &mut Vec<ContentItem> {
148        self.children.as_mut_vec()
149    }
150}
151
152impl fmt::Display for Definition {
153    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
154        write!(
155            f,
156            "Definition('{}', {} items)",
157            self.subject.as_string(),
158            self.children.len()
159        )
160    }
161}
162
163#[cfg(test)]
164mod tests {
165    use super::*;
166    use crate::lex::ast::elements::paragraph::Paragraph;
167
168    #[test]
169    fn test_definition() {
170        let location = super::super::super::range::Range::new(
171            0..0,
172            super::super::super::range::Position::new(1, 0),
173            super::super::super::range::Position::new(1, 10),
174        );
175        let definition = Definition::with_subject("Subject".to_string()).at(location.clone());
176        assert_eq!(definition.location, location);
177    }
178
179    #[test]
180    fn test_definition_header_and_body_locations() {
181        let subject_range = Range::new(0..7, Position::new(0, 0), Position::new(0, 7));
182        let child_range = Range::new(10..15, Position::new(1, 0), Position::new(1, 5));
183        let subject = TextContent::from_string("Subject".to_string(), Some(subject_range.clone()));
184        let child = ContentElement::Paragraph(
185            Paragraph::from_line("Body".to_string()).at(child_range.clone()),
186        );
187
188        let definition = Definition::new(subject, vec![child]).at(Range::new(
189            0..20,
190            Position::new(0, 0),
191            Position::new(1, 5),
192        ));
193
194        assert_eq!(definition.header_location(), Some(&subject_range));
195        assert_eq!(definition.body_location().unwrap().span, child_range.span);
196    }
197}