Skip to main content

meta_language/
decorators.rs

1//! Decorators: declared, removable changes to the items of a pipeline level.
2//!
3//! Each level shows a decorator its items as records, maps of string fields,
4//! and reads the decorated records back (docs/decorators.md lists the fields
5//! and what `drop` means at each level). A [`DecoratorSet`] is stored as
6//! Links Notation, one decorator per line, in the same canonical form the
7//! JavaScript runtime writes, so both runtimes share one corpus.
8
9use std::collections::{BTreeMap, BTreeSet};
10use std::fmt;
11
12use links_notation::{LiNo, ParserConfig, parse_lino_to_links_with_config};
13
14use crate::grammar::{percent_decode_links_text, percent_encode_links_text};
15
16/// A record a decorator sees: field names and their texts.
17pub type DecoratorRecord = BTreeMap<String, String>;
18
19/// The pipeline levels a decorator extends.
20#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
21pub enum DecoratorLevel {
22    /// The rules of an imported grammar.
23    Importer,
24    /// The rules of a grammar a parser is compiled from.
25    GrammarRule,
26    /// The decisions of a grammar merge.
27    MergeDecision,
28    /// The translation of a construct between native grammars.
29    ConceptMapping,
30    /// The nodes and tokens of a syntax tree.
31    Executor,
32    /// The ERROR and MISSING nodes of a syntax tree.
33    Recovery,
34    /// The syntax facts a program model is built from.
35    CstToAst,
36    /// The replacements of a transformation.
37    Transformation,
38    /// The lines of an emitted grammar.
39    Emitter,
40    /// The templates of a translation rule set.
41    TranslationRule,
42}
43
44/// The pipeline levels a decorator extends, in pipeline order.
45pub const DECORATOR_LEVELS: [DecoratorLevel; 10] = [
46    DecoratorLevel::Importer,
47    DecoratorLevel::GrammarRule,
48    DecoratorLevel::MergeDecision,
49    DecoratorLevel::ConceptMapping,
50    DecoratorLevel::Executor,
51    DecoratorLevel::Recovery,
52    DecoratorLevel::CstToAst,
53    DecoratorLevel::Transformation,
54    DecoratorLevel::Emitter,
55    DecoratorLevel::TranslationRule,
56];
57
58impl DecoratorLevel {
59    /// The name the JavaScript runtime and the Links Notation use.
60    #[must_use]
61    pub const fn as_str(self) -> &'static str {
62        match self {
63            Self::Importer => "importer",
64            Self::GrammarRule => "grammar-rule",
65            Self::MergeDecision => "merge-decision",
66            Self::ConceptMapping => "concept-mapping",
67            Self::Executor => "executor",
68            Self::Recovery => "recovery",
69            Self::CstToAst => "cst-to-ast",
70            Self::Transformation => "transformation",
71            Self::Emitter => "emitter",
72            Self::TranslationRule => "translation-rule",
73        }
74    }
75
76    /// The level named `name`.
77    #[must_use]
78    pub fn parse(name: &str) -> Option<Self> {
79        DECORATOR_LEVELS
80            .into_iter()
81            .find(|level| level.as_str() == name)
82    }
83}
84
85impl fmt::Display for DecoratorLevel {
86    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
87        formatter.write_str(self.as_str())
88    }
89}
90
91/// Why a decorator, a set or a decoration is refused.
92#[derive(Clone, Debug, PartialEq, Eq)]
93pub struct DecoratorError {
94    /// What is wrong.
95    pub message: String,
96}
97
98impl DecoratorError {
99    /// An error with `message`.
100    #[must_use]
101    pub fn new(message: impl Into<String>) -> Self {
102        Self {
103            message: message.into(),
104        }
105    }
106}
107
108impl fmt::Display for DecoratorError {
109    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
110        formatter.write_str(&self.message)
111    }
112}
113
114impl std::error::Error for DecoratorError {}
115
116/// One action of a decorator.
117#[derive(Clone, Debug, PartialEq, Eq)]
118pub enum DecoratorAction {
119    /// Sets `field` to `value`.
120    Set {
121        /// The field.
122        field: String,
123        /// The text it is set to.
124        value: String,
125    },
126    /// Replaces every occurrence of the non-empty text `from` in `field`.
127    Replace {
128        /// The field.
129        field: String,
130        /// The text replaced.
131        from: String,
132        /// The text written instead.
133        to: String,
134    },
135    /// Removes the record.
136    Drop,
137}
138
139/// One decorator: the records of `level` that have every field of `when`
140/// get `actions`, in order.
141#[derive(Clone, Debug, PartialEq, Eq)]
142pub struct Decorator {
143    id: String,
144    level: DecoratorLevel,
145    order: i64,
146    when: Vec<(String, String)>,
147    actions: Vec<DecoratorAction>,
148}
149
150fn is_name(value: &str) -> bool {
151    !value.is_empty()
152        && value
153            .bytes()
154            .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'.' | b'_' | b'-'))
155}
156
157impl Decorator {
158    /// Checks one decorator.
159    ///
160    /// # Errors
161    ///
162    /// A [`DecoratorError`] for an invalid id or field name, no action or a
163    /// replacement of empty text.
164    pub fn new(
165        id: impl Into<String>,
166        level: DecoratorLevel,
167        order: i64,
168        when: Vec<(String, String)>,
169        actions: Vec<DecoratorAction>,
170    ) -> Result<Self, DecoratorError> {
171        let id = id.into();
172        if !is_name(&id) {
173            return Err(DecoratorError::new(format!("invalid decorator id {id:?}")));
174        }
175        if when.iter().any(|(field, _)| !is_name(field)) {
176            return Err(DecoratorError::new(format!(
177                "decorator {id} has an invalid condition"
178            )));
179        }
180        if actions.is_empty() {
181            return Err(DecoratorError::new(format!("decorator {id} has no action")));
182        }
183        for action in &actions {
184            match action {
185                DecoratorAction::Drop => {}
186                DecoratorAction::Set { field, .. } if is_name(field) => {}
187                DecoratorAction::Replace { field, from, .. } if is_name(field) => {
188                    if from.is_empty() {
189                        return Err(DecoratorError::new(format!(
190                            "decorator {id} replaces an empty or non-string text in {field}"
191                        )));
192                    }
193                }
194                _ => {
195                    return Err(DecoratorError::new(format!(
196                        "decorator {id} has an action without a field"
197                    )));
198                }
199            }
200        }
201        Ok(Self {
202            id,
203            level,
204            order,
205            when,
206            actions,
207        })
208    }
209
210    /// The id, unique in a set.
211    #[must_use]
212    pub fn id(&self) -> &str {
213        &self.id
214    }
215
216    /// The level the decorator extends.
217    #[must_use]
218    pub const fn level(&self) -> DecoratorLevel {
219        self.level
220    }
221
222    /// The position among the decorators of the level; ties go by id.
223    #[must_use]
224    pub const fn order(&self) -> i64 {
225        self.order
226    }
227
228    /// The fields a record must have for the decorator to apply.
229    #[must_use]
230    pub fn when(&self) -> &[(String, String)] {
231        &self.when
232    }
233
234    /// The actions, applied in order.
235    #[must_use]
236    pub fn actions(&self) -> &[DecoratorAction] {
237        &self.actions
238    }
239
240    fn render(&self) -> String {
241        let mut parts = vec![
242            format!("decorator {}", self.id),
243            format!("(level {})", self.level),
244            format!("(order {})", self.order),
245        ];
246        if !self.when.is_empty() {
247            let pairs: Vec<String> = self
248                .when
249                .iter()
250                .map(|(field, value)| format!("({field} {})", percent_encode_links_text(value)))
251                .collect();
252            parts.push(format!("(when {})", pairs.join(" ")));
253        }
254        for action in &self.actions {
255            parts.push(match action {
256                DecoratorAction::Drop => "(drop)".to_owned(),
257                DecoratorAction::Set { field, value } => {
258                    format!("(set {field} {})", percent_encode_links_text(value))
259                }
260                DecoratorAction::Replace { field, from, to } => format!(
261                    "(replace {field} {} {})",
262                    percent_encode_links_text(from),
263                    percent_encode_links_text(to)
264                ),
265            });
266        }
267        format!("({})", parts.join(" "))
268    }
269}
270
271/// An immutable set of decorators.
272///
273/// The decorators of a level compose in a defined order, by `order` and then
274/// by id, each one decorating what the one before it produced;
275/// [`DecoratorSet::add`] and [`DecoratorSet::remove`] return new sets, so
276/// removing a decorator gives exactly the output of the set without it.
277#[derive(Clone, Debug, Default, PartialEq, Eq)]
278pub struct DecoratorSet {
279    decorators: Vec<Decorator>,
280}
281
282impl DecoratorSet {
283    /// A set of `decorators`, sorted by order and then by id.
284    ///
285    /// # Errors
286    ///
287    /// A [`DecoratorError`] when two decorators share an id.
288    pub fn new(mut decorators: Vec<Decorator>) -> Result<Self, DecoratorError> {
289        let mut ids = BTreeSet::new();
290        for decorator in &decorators {
291            if !ids.insert(decorator.id.as_str()) {
292                return Err(DecoratorError::new(format!(
293                    "duplicate decorator id {}",
294                    decorator.id
295                )));
296            }
297        }
298        decorators.sort_by(|left, right| {
299            left.order
300                .cmp(&right.order)
301                .then_with(|| left.id.cmp(&right.id))
302        });
303        Ok(Self { decorators })
304    }
305
306    /// The set without decorators.
307    #[must_use]
308    pub const fn empty() -> Self {
309        Self {
310            decorators: Vec::new(),
311        }
312    }
313
314    /// Reads the decorators of a Links Notation document (see
315    /// [`DecoratorSet::to_lino`]).
316    ///
317    /// # Errors
318    ///
319    /// A [`DecoratorError`] for text that is not Links Notation or a
320    /// statement that is not a valid decorator.
321    pub fn from_lino(text: &str) -> Result<Self, DecoratorError> {
322        let statements = parse_lino_to_links_with_config(text, &ParserConfig::without_comments())
323            .map_err(|error| DecoratorError::new(error.to_string()))?;
324        Self::new(
325            statements
326                .iter()
327                .map(read_decorator)
328                .collect::<Result<_, _>>()?,
329        )
330    }
331
332    /// The decorators, in the order they compose.
333    #[must_use]
334    pub fn decorators(&self) -> &[Decorator] {
335        &self.decorators
336    }
337
338    /// The number of decorators.
339    #[must_use]
340    pub const fn len(&self) -> usize {
341        self.decorators.len()
342    }
343
344    /// Whether the set has no decorator.
345    #[must_use]
346    pub const fn is_empty(&self) -> bool {
347        self.decorators.is_empty()
348    }
349
350    /// The ids, in the order the decorators compose.
351    #[must_use]
352    pub fn ids(&self) -> Vec<&str> {
353        self.decorators.iter().map(Decorator::id).collect()
354    }
355
356    /// The set with `decorators` added.
357    ///
358    /// # Errors
359    ///
360    /// A [`DecoratorError`] when an id is taken.
361    pub fn add(
362        &self,
363        decorators: impl IntoIterator<Item = Decorator>,
364    ) -> Result<Self, DecoratorError> {
365        Self::new(self.decorators.iter().cloned().chain(decorators).collect())
366    }
367
368    /// The set without the decorator `id`.
369    ///
370    /// # Errors
371    ///
372    /// A [`DecoratorError`] when the set has no decorator `id`.
373    pub fn remove(&self, id: &str) -> Result<Self, DecoratorError> {
374        if !self.decorators.iter().any(|decorator| decorator.id == id) {
375            return Err(DecoratorError::new(format!("no decorator {id} to remove")));
376        }
377        Ok(Self {
378            decorators: self
379                .decorators
380                .iter()
381                .filter(|decorator| decorator.id != id)
382                .cloned()
383                .collect(),
384        })
385    }
386
387    /// The decorators of `level`, in the order they compose.
388    pub fn for_level(&self, level: DecoratorLevel) -> impl Iterator<Item = &Decorator> {
389        self.decorators
390            .iter()
391            .filter(move |decorator| decorator.level == level)
392    }
393
394    /// Whether the set has a decorator of `level`.
395    #[must_use]
396    pub fn has(&self, level: DecoratorLevel) -> bool {
397        self.for_level(level).next().is_some()
398    }
399
400    /// The record the decorators of `level` make of `record`, or `None` when
401    /// one of them drops it. A missing field reads as empty text.
402    #[must_use]
403    pub fn decorate(
404        &self,
405        level: DecoratorLevel,
406        record: &DecoratorRecord,
407    ) -> Option<DecoratorRecord> {
408        let mut current = record.clone();
409        for decorator in self.for_level(level) {
410            let applies = decorator
411                .when
412                .iter()
413                .all(|(field, value)| current.get(field).map_or("", String::as_str) == value);
414            if !applies {
415                continue;
416            }
417            for action in &decorator.actions {
418                match action {
419                    DecoratorAction::Drop => return None,
420                    DecoratorAction::Set { field, value } => {
421                        current.insert(field.clone(), value.clone());
422                    }
423                    DecoratorAction::Replace { field, from, to } => {
424                        let text = current
425                            .get(field)
426                            .map_or("", String::as_str)
427                            .replace(from, to);
428                        current.insert(field.clone(), text);
429                    }
430                }
431            }
432        }
433        Some(current)
434    }
435
436    /// Decorates every record of `records`, leaving out the dropped ones.
437    #[must_use]
438    pub fn decorate_all(
439        &self,
440        level: DecoratorLevel,
441        records: &[DecoratorRecord],
442    ) -> Vec<DecoratorRecord> {
443        records
444            .iter()
445            .filter_map(|record| self.decorate(level, record))
446            .collect()
447    }
448
449    /// The canonical Links Notation of the set, one decorator per line.
450    #[must_use]
451    pub fn to_lino(&self) -> String {
452        self.decorators
453            .iter()
454            .fold(String::new(), |mut text, decorator| {
455                text.push_str(&decorator.render());
456                text.push('\n');
457                text
458            })
459    }
460}
461
462/// Builds a record from `(field, text)` pairs.
463#[must_use]
464pub fn decorator_record<const N: usize>(fields: [(&str, &str); N]) -> DecoratorRecord {
465    fields
466        .into_iter()
467        .map(|(field, text)| (field.to_owned(), text.to_owned()))
468        .collect()
469}
470
471/// The text of `field` in a decorated record, empty when absent.
472#[must_use]
473pub fn record_field<'a>(record: &'a DecoratorRecord, field: &str) -> &'a str {
474    record.get(field).map_or("", String::as_str)
475}
476
477type Node = LiNo<String>;
478
479fn word_of(node: &Node) -> Option<&str> {
480    match node {
481        LiNo::Ref(word) => Some(word),
482        LiNo::Link {
483            id: Some(word),
484            values,
485        } if values.is_empty() => Some(word),
486        LiNo::Link { .. } => None,
487    }
488}
489
490fn values_of(node: &Node) -> &[Node] {
491    match node {
492        LiNo::Link { values, .. } => values,
493        LiNo::Ref(_) => &[],
494    }
495}
496
497fn words(values: &[Node]) -> Result<Vec<&str>, DecoratorError> {
498    values
499        .iter()
500        .map(|value| {
501            word_of(value).ok_or_else(|| {
502                DecoratorError::new("a decorator field holds a nested link where a word belongs")
503            })
504        })
505        .collect()
506}
507
508fn unquote(text: &str, id: &str) -> Result<String, DecoratorError> {
509    percent_decode_links_text(text).map_err(|_| {
510        DecoratorError::new(format!(
511            "decorator {id} has a malformed percent-encoded value {text}"
512        ))
513    })
514}
515
516fn read_decorator(statement: &Node) -> Result<Decorator, DecoratorError> {
517    let shape = || {
518        DecoratorError::new("a decorator statement must read (decorator <id> (level <level>) …)")
519    };
520    let LiNo::Link { id: None, values } = statement else {
521        return Err(shape());
522    };
523    let [head, name, rest @ ..] = values.as_slice() else {
524        return Err(shape());
525    };
526    if word_of(head) != Some("decorator") {
527        return Err(shape());
528    }
529    let id = word_of(name).ok_or_else(shape)?.to_owned();
530    let mut level = None;
531    let mut order = 0;
532    let mut when = Vec::new();
533    let mut actions = Vec::new();
534    for part in rest {
535        let parts = values_of(part);
536        let keyword = parts.first().and_then(word_of);
537        let args = parts.get(1..).unwrap_or_default();
538        match keyword {
539            Some("level") => {
540                let name = words(args)?.first().copied().unwrap_or_default();
541                level = Some(DecoratorLevel::parse(name).ok_or_else(|| {
542                    DecoratorError::new(format!("decorator {id} names unknown level {name:?}"))
543                })?);
544            }
545            Some("order") => {
546                let text = words(args)?.first().copied().unwrap_or_default();
547                order = text
548                    .parse()
549                    .ok()
550                    .filter(|_| {
551                        let digits = text.strip_prefix('-').unwrap_or(text);
552                        !digits.is_empty() && digits.bytes().all(|byte| byte.is_ascii_digit())
553                    })
554                    .ok_or_else(|| {
555                        DecoratorError::new(format!("decorator {id} has a non-integer order"))
556                    })?;
557            }
558            Some("when") => {
559                for pair in args {
560                    let [field, value] = words(values_of(pair))?[..] else {
561                        return Err(DecoratorError::new(format!(
562                            "decorator {id} has an invalid condition"
563                        )));
564                    };
565                    when.push((field.to_owned(), unquote(value, &id)?));
566                }
567            }
568            Some("set") => {
569                let [field, value] = words(args)?[..] else {
570                    return Err(DecoratorError::new(format!(
571                        "decorator {id} has an action without a field"
572                    )));
573                };
574                actions.push(DecoratorAction::Set {
575                    field: field.to_owned(),
576                    value: unquote(value, &id)?,
577                });
578            }
579            Some("replace") => {
580                let [field, from, to] = words(args)?[..] else {
581                    return Err(DecoratorError::new(format!(
582                        "decorator {id} replaces an empty or non-string text"
583                    )));
584                };
585                actions.push(DecoratorAction::Replace {
586                    field: field.to_owned(),
587                    from: unquote(from, &id)?,
588                    to: unquote(to, &id)?,
589                });
590            }
591            Some("drop") => actions.push(DecoratorAction::Drop),
592            other => {
593                return Err(DecoratorError::new(format!(
594                    "decorator {id} has an unknown part {other:?}"
595                )));
596            }
597        }
598    }
599    let level =
600        level.ok_or_else(|| DecoratorError::new(format!("decorator {id} names no level")))?;
601    Decorator::new(id, level, order, when, actions)
602}