incodoc/
lib.rs

1mod tests;
2pub mod parsing;
3pub mod output;
4pub mod reference_doc;
5
6use std::{
7    num::ParseIntError,
8    collections::{ HashMap, HashSet },
9};
10
11use pest_derive::Parser;
12
13/// Parser for incodoc.
14#[derive(Parser)]
15#[grammar = "parse/incodoc.pest"]
16pub struct IncodocParser;
17
18/// Merge two objects by having one absorb the other.
19pub trait Absorb {
20    type Other;
21    /// Absorb other into self.
22    fn absorb(&mut self, other: Self::Other);
23}
24
25/// Remove errors from self.
26pub trait RemoveErrors {
27    fn remove_errors(&mut self);
28}
29
30/// Document.
31#[derive(Clone, Default, Debug, Eq, PartialEq)]
32pub struct Doc {
33    pub tags: Tags,
34    pub props: Props,
35    pub items: Vec<DocItem>,
36}
37
38/// Document item.
39#[derive(Clone, Debug, Eq, PartialEq)]
40pub enum DocItem {
41    Text(String),
42    /// Text with meta attached.
43    MText(TextWithMeta),
44    Emphasis(Emphasis),
45    /// Code or an error.
46    Code(Result<CodeBlock, CodeIdentError>),
47    Link(Link),
48    /// Navigation.
49    Nav(Nav),
50    List(List),
51    Paragraph(Paragraph),
52    Section(Section),
53}
54
55impl RemoveErrors for Doc {
56    fn remove_errors(&mut self) {
57        self.props.remove_errors();
58        self.items.retain(|i| !matches!(i, DocItem::Code(Err(_))));
59        for item in &mut self.items {
60            item.remove_errors();
61        }
62    }
63}
64
65impl RemoveErrors for DocItem {
66    fn remove_errors(&mut self) {
67        match self {
68            DocItem::MText(mtext) => mtext.remove_errors(),
69            DocItem::Emphasis(em) => em.remove_errors(),
70            DocItem::Code(Ok(code)) => code.remove_errors(),
71            DocItem::Link(link) => link.remove_errors(),
72            DocItem::Nav(nav) => nav.remove_errors(),
73            DocItem::List(list) => list.remove_errors(),
74            DocItem::Paragraph(par) => par.remove_errors(),
75            DocItem::Section(section) => section.remove_errors(),
76            _ => {},
77        }
78    }
79}
80
81/// Tags metadata. Each tag is a string.
82pub type Tags = HashSet<String>;
83
84impl Absorb for Tags {
85    type Other = Option<Self>;
86    fn absorb(&mut self, other: Self::Other) {
87        if let Some(o) = other {
88            for v in o {
89                self.insert(v);
90            }
91        }
92    }
93}
94
95/// Properties metadata. Each property is a tuple of an identifier and a value.
96pub type Props = HashMap<String, PropVal>;
97
98/// Value properties can take.
99#[derive(Clone, Debug, Eq, PartialEq)]
100pub enum PropVal {
101    String(String),
102    Text(String),
103    Int(i64),
104    Date(Date),
105    Error(PropValError),
106}
107
108/// Error when no valid value could be parsed.
109#[derive(Clone, Debug, Eq, PartialEq)]
110pub enum PropValError {
111    Int(ParseIntError),
112    Date(DateError),
113}
114
115impl PropVal {
116    fn is_error(&self) -> bool {
117        matches![self, PropVal::Error(_)]
118    }
119}
120
121impl Absorb for Props {
122    type Other = Self;
123    fn absorb(&mut self, other: Self::Other) {
124        for prop in other {
125            insert_prop(self, prop)
126        }
127    }
128}
129
130impl RemoveErrors for Props {
131    fn remove_errors(&mut self) {
132        self.retain(|_, v| !v.is_error());
133    }
134}
135
136fn insert_prop(props: &mut Props, (k, v): (String, PropVal)) {
137    let mut insert = true;
138    if v.is_error() {
139        if let Some(ov) = props.get(&k) {
140            if !ov.is_error() {
141                insert = false;
142            }
143        }
144    }
145    if insert {
146        props.insert(k, v);
147    }
148}
149
150/// A section is a heading followed by content that goes with it.
151#[derive(Clone, Default, Debug, Eq, PartialEq)]
152pub struct Section {
153    pub heading: Heading,
154    pub items: Vec<SectionItem>,
155    pub tags: Tags,
156    pub props: Props,
157}
158
159/// Section items are either paragraphs or sub-sections.
160#[derive(Clone, Debug, Eq, PartialEq)]
161pub enum SectionItem {
162    Paragraph(Paragraph),
163    Section(Section),
164}
165
166impl RemoveErrors for Section {
167    fn remove_errors(&mut self) {
168        self.props.remove_errors();
169        for item in &mut self.items {
170            item.remove_errors();
171        }
172    }
173}
174
175impl RemoveErrors for SectionItem {
176    fn remove_errors(&mut self) {
177        match self {
178            Self::Paragraph(par) => par.remove_errors(),
179            Self::Section(section) => section.remove_errors(),
180        }
181    }
182}
183
184/// Heading, a title for the accompanying content.
185#[derive(Clone, Default, Debug, Eq, PartialEq)]
186pub struct Heading {
187    pub level: u8,
188    pub items: Vec<HeadingItem>,
189    pub tags: Tags,
190    pub props: Props,
191}
192
193/// Headings are plain text that can have emphasis.
194#[derive(Clone, Debug, Eq, PartialEq)]
195pub enum HeadingItem {
196    String(String),
197    Em(Emphasis),
198}
199
200impl RemoveErrors for Heading {
201    fn remove_errors(&mut self) {
202        self.props.remove_errors();
203        for item in &mut self.items {
204            item.remove_errors();
205        }
206    }
207}
208
209impl RemoveErrors for HeadingItem {
210    fn remove_errors(&mut self) {
211        if let Self::Em(em) = self {
212            em.remove_errors();
213        }
214    }
215}
216
217/// Paragraph is a grouping of content.
218#[derive(Clone, Default, Debug, Eq, PartialEq)]
219pub struct Paragraph {
220    pub items: Vec<ParagraphItem>,
221    pub tags: Tags,
222    pub props: Props,
223}
224
225/// Paragraphs can have content and further structure but no smaller sub-sections.
226#[derive(Clone, Debug, Eq, PartialEq)]
227pub enum ParagraphItem {
228    Text(String),
229    MText(TextWithMeta),
230    Em(Emphasis),
231    Code(Result<CodeBlock, CodeIdentError>),
232    Link(Link),
233    List(List),
234}
235
236impl RemoveErrors for Paragraph {
237    fn remove_errors(&mut self) {
238        self.props.remove_errors();
239        for item in &mut self.items {
240            item.remove_errors();
241        }
242    }
243}
244
245impl RemoveErrors for ParagraphItem {
246    fn remove_errors(&mut self) {
247        match self {
248            Self::MText(mtext) => mtext.remove_errors(),
249            Self::Em(em) => em.remove_errors(),
250            Self::Code(Ok(code)) => code.remove_errors(),
251            Self::Link(link) => link.remove_errors(),
252            Self::List(list) => list.remove_errors(),
253            _ => (),
254        }
255    }
256}
257
258/// Emphasised or de-emphasised piece of text.
259#[derive(Clone, Default, Debug, Eq, PartialEq)]
260pub struct Emphasis {
261    pub strength: EmStrength,
262    pub etype: EmType,
263    pub text: String,
264    pub tags: Tags,
265    pub props: Props,
266}
267
268/// Degree of emphasis or de-emphasis.
269#[derive(Clone, Copy, Default, Hash, Debug, Eq, PartialEq, Ord, PartialOrd)]
270pub enum EmStrength {
271    #[default]
272    Light,
273    Medium,
274    Strong,
275}
276
277/// Whether it is an emphasis or de-emphasis.
278#[derive(Clone, Copy, Default, Hash, Debug, Eq, PartialEq, Ord, PartialOrd)]
279pub enum EmType {
280    #[default]
281    Emphasis,
282    Deemphasis,
283}
284
285impl RemoveErrors for Emphasis {
286    fn remove_errors(&mut self) {
287        self.props.remove_errors();
288    }
289}
290
291/// Lists are fine-grained structure in a document.
292#[derive(Clone, Default, Debug, Eq, PartialEq)]
293pub struct List {
294    pub ltype: ListType,
295    pub items: Vec<Paragraph>,
296    pub tags: Tags,
297    pub props: Props,
298}
299
300#[derive(Clone, Copy, Default, Hash, Debug, Eq, PartialEq, Ord, PartialOrd)]
301pub enum ListType {
302    /// Each item has a distinct denotation.
303    Distinct,
304    /// Each item is denotated identically.
305    #[default] Identical,
306    /// Each item is either checked off or not.
307    Checked,
308}
309
310impl RemoveErrors for List {
311    fn remove_errors(&mut self) {
312        self.props.remove_errors();
313        for item in &mut self.items {
314            item.remove_errors();
315        }
316    }
317}
318
319/// Navigation structure contains sub-navigation structures.
320pub type Nav = Vec<SNav>;
321
322/// Sub-navigation structure has a description, sub-navigation structures and links to navigate to.
323#[derive(Clone, Default, Debug, Eq, PartialEq)]
324pub struct SNav {
325    pub description: String,
326    pub subs: Vec<SNav>,
327    pub links: Vec<Link>,
328    pub tags: Tags,
329    pub props: Props,
330}
331
332impl RemoveErrors for Nav {
333    fn remove_errors(&mut self) {
334        for snav in self {
335            snav.remove_errors();
336        }
337    }
338}
339
340impl RemoveErrors for SNav {
341    fn remove_errors(&mut self) {
342        self.props.remove_errors();
343        for link in &mut self.links {
344            link.remove_errors();
345        }
346        for sub in &mut self.subs {
347            sub.remove_errors();
348        }
349    }
350}
351
352/// Links are pieces of text with an accompanying URL.
353#[derive(Clone, Default, Debug, Eq, PartialEq)]
354pub struct Link {
355    pub url: String,
356    pub items: Vec<LinkItem>,
357    pub tags: Tags,
358    pub props: Props,
359}
360
361impl RemoveErrors for Link {
362    fn remove_errors(&mut self) {
363        self.props.remove_errors();
364        for item in &mut self.items {
365            item.remove_errors();
366        }
367    }
368}
369
370/// Links have an exterior of plain text that may be emphasised.
371#[derive(Clone, Debug, Eq, PartialEq)]
372pub enum LinkItem {
373    String(String),
374    Em(Emphasis),
375}
376
377impl RemoveErrors for LinkItem {
378    fn remove_errors(&mut self) {
379        if let Self::Em(em) = self {
380            em.remove_errors();
381        }
382    }
383}
384
385/// CodeBlock contains computer code.
386#[derive(Clone, Default, Debug, Eq, PartialEq)]
387pub struct CodeBlock {
388    /// Computer language in which the code is written.
389    pub language: String,
390    /// Behavioural hint.
391    pub mode: CodeModeHint,
392    /// The code.
393    pub code: String,
394    pub tags: Tags,
395    pub props: Props,
396}
397
398impl RemoveErrors for CodeBlock {
399    fn remove_errors(&mut self) {
400        self.props.remove_errors();
401    }
402}
403
404/// Behavioural hint: the block hints what to do with the code.
405#[derive(Clone, Copy, Default, Hash, Debug, Eq, PartialEq, Ord, PartialOrd)]
406pub enum CodeModeHint {
407    /// Hint to show the code in the document.
408    #[default] Show,
409    /// Hint to show the code in the document and to signal that it is supposed to be able to run.
410    Runnable,
411    /// Hint to show the code in the document and to run the code and show the results as well.
412    Run,
413    /// Hint to run the code and show the results in the document instead of the code itself.
414    Replace,
415}
416
417/// Text that has metadata: tags and/or properties.
418#[derive(Clone, Default, Debug, Eq, PartialEq)]
419pub struct TextWithMeta {
420    pub text: String,
421    pub tags: Tags,
422    pub props: Props,
423}
424
425impl TextWithMeta {
426    fn meta_is_empty(&self) -> bool {
427        self.tags.is_empty() && self.props.is_empty()
428    }
429}
430
431impl RemoveErrors for TextWithMeta {
432    fn remove_errors(&mut self) {
433        self.props.remove_errors();
434    }
435}
436
437/// Error to signal that the code was not formatted properly.
438#[derive(Clone, Copy, Default, Hash, Debug, Eq, PartialEq, Ord, PartialOrd)]
439pub struct CodeIdentError;
440
441/// Simple date: it is not checked if it actually exists on the calendar.
442#[derive(Clone, Copy, Default, Hash, Debug, Eq, PartialEq, Ord, PartialOrd)]
443pub struct Date {
444    pub year: i16,
445    pub month: u8,
446    pub day: u8,
447}
448
449#[derive(Clone, Debug, Eq, PartialEq)]
450pub enum DateError {
451    /// The year was out of range of i16.
452    YearRange(i64),
453    /// The month was out of range: bigger than 12.
454    MonthRange(u64),
455    /// The day was out of range: bigger than 31.
456    DayRange(u64),
457    /// There was an error parsing an integer in the date.
458    Parsing(ParseIntError),
459}
460
461impl Date {
462    pub fn new(y: i64, m: u64, d: u64) -> Result<Self, DateError> {
463        let year: i16 = y.try_into().map_err(|_| DateError::YearRange(y))?;
464        let month: u8 = m.try_into()
465            .map_err(|_| DateError::MonthRange(m))
466            .and_then(|m| if m == 0 { Err(DateError::MonthRange(m as u64)) } else { Ok(m) } )?;
467        let day: u8 = d.try_into()
468            .map_err(|_| DateError::DayRange(d))
469            .and_then(|d| if d == 0 { Err(DateError::DayRange(d as u64)) } else { Ok(d) } )?;
470        if month > 12 { return Err(DateError::MonthRange(m)); }
471        if day > 31 { return Err(DateError::DayRange(d)); }
472        Ok(Self { year, month, day })
473    }
474}
475