Skip to main content

meta_language/
lino_parser.rs

1//! The links-notation semantics of a `LiNo` document.
2//!
3//! The links are read from the built-in lossless `LiNo` CST
4//! ([`parse_lino_cst`]) with the transformation the official links-notation
5//! 0.22 `Parser` applies to its raw parse (`links-notation/src/Parser.js`): an
6//! indented id takes the lines below it as its values, a line with values and
7//! indented lines is combined with each of them, and a parenthesized group
8//! holding one link is that link. Every link becomes a Relation spanning its
9//! source, named for `name:`; every reference becomes the link carrying that
10//! name, so a name resolves to its definition wherever it appears (shared,
11//! recursive and forward references), or else to one Concept point per name.
12//! A path element the official parser repeats in several combined links is one
13//! shared link. Mirrors `js/src/lino-semantics.js`.
14
15use std::collections::HashSet;
16
17use serde::ser::{Serialize, SerializeSeq, Serializer};
18
19use crate::builtin_grammar::GrammarNode;
20use crate::line_index::LineIndex;
21use crate::lino_grammar::{parse_lino_cst, quoted_reference_value};
22use crate::{
23    ByteRange, LinkId, LinkMetadata, LinkNetwork, LinkType, ParseConfiguration, SourceSpan,
24    structured_text_parser,
25};
26
27pub fn parse(text: &str, language: &str, configuration: ParseConfiguration) -> LinkNetwork {
28    parse_with_links(text, language, configuration).0
29}
30
31fn parse_with_links(
32    text: &str,
33    language: &str,
34    configuration: ParseConfiguration,
35) -> (LinkNetwork, Vec<LinkId>) {
36    let mut network = structured_text_parser::parse_lino(text, language, configuration);
37    let line_index = LineIndex::new(text);
38    let documents = LinoSemantics::new(&mut network, text, &line_index, language).insert_document();
39    (network, documents)
40}
41
42/// The links-notation reading of one link.
43///
44/// A reference is its name and a link is its optional name followed by its
45/// values, the form of `parity/fixtures/lino-compatibility-matrix.json`. It
46/// serializes as that JSON: a string, or an array whose first item is the
47/// name or `null`.
48#[derive(Debug, Clone, PartialEq, Eq)]
49pub enum LinksNotationReading {
50    /// A reference to a name.
51    Reference(String),
52    /// A link: its name, if it is named, and its values in order.
53    Link {
54        name: Option<String>,
55        values: Vec<Self>,
56    },
57}
58
59impl Serialize for LinksNotationReading {
60    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
61        match self {
62            Self::Reference(name) => serializer.serialize_str(name),
63            Self::Link { name, values } => {
64                let mut sequence = serializer.serialize_seq(Some(values.len() + 1))?;
65                sequence.serialize_element(name)?;
66                for value in values {
67                    sequence.serialize_element(value)?;
68                }
69                sequence.end()
70            }
71        }
72    }
73}
74
75impl LinkNetwork {
76    /// Parses a `LiNo` document and returns its network with the ids of its
77    /// top-level links in document order, as the official links-notation
78    /// parser lists them.
79    #[must_use]
80    pub fn parse_links_notation(
81        text: &str,
82        configuration: ParseConfiguration,
83    ) -> (Self, Vec<LinkId>) {
84        parse_with_links(text, "LiNo", configuration)
85    }
86
87    /// The links-notation reading of the link `id` that
88    /// [`LinkNetwork::parse_links_notation`] inserted. A named link appearing
89    /// as a value outside its own source (a reference to it) reads as its name.
90    #[must_use]
91    pub fn links_notation_reading(&self, id: LinkId) -> Option<LinksNotationReading> {
92        let metadata = self.link(id)?.metadata();
93        if metadata.link_type() != Some(LinkType::Relation) {
94            return Some(LinksNotationReading::Reference(
95                metadata.term().unwrap_or_default().to_owned(),
96            ));
97        }
98        let mut values = Vec::new();
99        for &value in self.link(id)?.references() {
100            let value_metadata = self.link(value)?.metadata();
101            let is_reference = value_metadata.link_type() == Some(LinkType::Relation)
102                && value_metadata.is_named()
103                && (value == id || !within(value_metadata.span(), metadata.span()));
104            values.push(if is_reference {
105                LinksNotationReading::Reference(
106                    value_metadata.term().unwrap_or_default().to_owned(),
107                )
108            } else {
109                self.links_notation_reading(value)?
110            });
111        }
112        Some(LinksNotationReading::Link {
113            name: metadata
114                .is_named()
115                .then(|| metadata.term().unwrap_or_default().to_owned()),
116            values,
117        })
118    }
119}
120
121impl LinkNetwork {
122    /// The links-notation text of the links `ids`, one per line, which reads
123    /// back as the same links.
124    #[must_use]
125    pub fn links_notation_text(&self, ids: &[LinkId]) -> String {
126        let readings: Vec<LinksNotationReading> = ids
127            .iter()
128            .filter_map(|&id| self.links_notation_reading(id))
129            .collect();
130        format_lino_readings(&readings)
131    }
132}
133
134/// The links-notation text of `readings`, one top-level link per line, which
135/// the official parser reads back as the same readings: a link is
136/// parenthesized, a top-level reference is `name:`, and a name that is not a
137/// plain reference is quoted. Mirrors `formatLinoReadings`.
138fn format_lino_readings(readings: &[LinksNotationReading]) -> String {
139    // A quoted reference opened by N delimiters closes at the next run R with
140    // an odd R / N anywhere after it, so the empty reference is written as an
141    // even run of `"` longer than every `"` run of the document, which nothing
142    // closes.
143    fn collect<'r>(reading: &'r LinksNotationReading, names: &mut Vec<&'r str>) {
144        match reading {
145            LinksNotationReading::Reference(name) => names.push(name),
146            LinksNotationReading::Link { name, values } => {
147                names.extend(name.as_deref());
148                for value in values {
149                    collect(value, names);
150                }
151            }
152        }
153    }
154    let mut names = Vec::new();
155    for reading in readings {
156        collect(reading, &mut names);
157    }
158    let longest = names
159        .iter()
160        .map(|name| longest_run(&quote_name(name), '"'))
161        .max()
162        .unwrap_or(0);
163    let empty = "\"".repeat(longest + 2 - longest % 2);
164    let name_text = |name: &str| {
165        if name.is_empty() {
166            empty.clone()
167        } else {
168            quote_name(name)
169        }
170    };
171    let mut text = String::new();
172    for reading in readings {
173        match reading {
174            LinksNotationReading::Reference(name) => {
175                text.push_str(&name_text(name));
176                text.push(':');
177            }
178            LinksNotationReading::Link { .. } => format_link(reading, &name_text, &mut text),
179        }
180        text.push('\n');
181    }
182    text
183}
184
185fn format_link(
186    reading: &LinksNotationReading,
187    name_text: &dyn Fn(&str) -> String,
188    text: &mut String,
189) {
190    match reading {
191        LinksNotationReading::Reference(name) => text.push_str(&name_text(name)),
192        LinksNotationReading::Link { name, values } => {
193            text.push('(');
194            if let Some(name) = name {
195                text.push_str(&name_text(name));
196                text.push(':');
197            }
198            for (index, value) in values.iter().enumerate() {
199                if index > 0 || name.is_some() {
200                    text.push(' ');
201                }
202                format_link(value, name_text, text);
203            }
204            text.push(')');
205        }
206    }
207}
208
209const QUOTES: [char; 3] = ['"', '\'', '`'];
210
211// A plain reference is `[^ \t\n\r(:)]+` without delimiters that opens no
212// comment. Otherwise the name is quoted with a delimiter it does not start
213// with, preferring one it does not contain, and a delimiter inside is doubled.
214fn quote_name(name: &str) -> String {
215    if name.is_empty() {
216        return "\"\"".to_owned();
217    }
218    let special = |character: char| {
219        matches!(character, ' ' | '\t' | '\n' | '\r' | '(' | ')' | ':')
220            || QUOTES.contains(&character)
221    };
222    if !name.contains(special) && !name.starts_with('#') {
223        return name.to_owned();
224    }
225    let quote = QUOTES
226        .into_iter()
227        .find(|candidate| !name.contains(*candidate))
228        .or_else(|| {
229            QUOTES
230                .into_iter()
231                .find(|candidate| !name.starts_with(*candidate))
232        })
233        .expect("a name starts with at most one delimiter");
234    let doubled = name.replace(quote, &format!("{quote}{quote}"));
235    format!("{quote}{doubled}{quote}")
236}
237
238fn longest_run(text: &str, character: char) -> usize {
239    let mut longest = 0;
240    let mut run = 0;
241    for current in text.chars() {
242        run = if current == character { run + 1 } else { 0 };
243        longest = longest.max(run);
244    }
245    longest
246}
247
248const fn within(inner: Option<SourceSpan>, outer: Option<SourceSpan>) -> bool {
249    let (Some(inner), Some(outer)) = (inner, outer) else {
250        return false;
251    };
252    outer.byte_range().start() <= inner.byte_range().start()
253        && inner.byte_range().end() <= outer.byte_range().end()
254}
255
256/// The raw parse, as the official PEG grammar returns it.
257enum RawItem {
258    Reference {
259        id: String,
260        start: usize,
261        end: usize,
262    },
263    Group {
264        nested: Vec<Self>,
265        start: usize,
266        end: usize,
267    },
268    Link {
269        id: Option<String>,
270        values: Vec<Self>,
271        children: Vec<Self>,
272        start: usize,
273        end: usize,
274    },
275}
276
277impl RawItem {
278    const fn span(&self) -> (usize, usize) {
279        match self {
280            Self::Reference { start, end, .. }
281            | Self::Group { start, end, .. }
282            | Self::Link { start, end, .. } => (*start, *end),
283        }
284    }
285
286    fn children(&self) -> &[Self] {
287        match self {
288            Self::Link { children, .. } => children,
289            _ => &[],
290        }
291    }
292
293    // An indented id: `name:` with no values and the lines below it.
294    const fn is_indented_id(&self) -> bool {
295        matches!(self, Self::Link { id: Some(_), values, children, .. }
296            if values.is_empty() && !children.is_empty())
297    }
298}
299
300/// A link as the official parser builds it, kept in an arena so a path
301/// element shared by several combined links stays one link. No values and a
302/// name is a reference. `start` and `end` are byte offsets of its source.
303struct Official {
304    id: Option<String>,
305    values: Vec<usize>,
306    start: usize,
307    end: usize,
308}
309
310struct LinoSemantics<'a> {
311    network: &'a mut LinkNetwork,
312    text: &'a str,
313    lines: &'a LineIndex,
314    language: &'a str,
315    links: Vec<Official>,
316    ids: Vec<Option<LinkId>>,
317}
318
319impl<'a> LinoSemantics<'a> {
320    const fn new(
321        network: &'a mut LinkNetwork,
322        text: &'a str,
323        lines: &'a LineIndex,
324        language: &'a str,
325    ) -> Self {
326        Self {
327            network,
328            text,
329            lines,
330            language,
331            links: Vec::new(),
332            ids: Vec::new(),
333        }
334    }
335
336    fn insert_document(mut self) -> Vec<LinkId> {
337        let document = parse_lino_cst(self.text);
338        let mut links = Vec::new();
339        for child in &document.children {
340            if child.term == "link" || child.term == "group" {
341                let item = self.raw_item(child);
342                self.collect_links(&item, &[], &mut links);
343            }
344        }
345        self.ids = vec![None; self.links.len()];
346        for &link in &links {
347            self.allocate(link);
348        }
349        let mut connected = HashSet::new();
350        for &link in &links {
351            self.connect(link, &mut connected);
352        }
353        links.into_iter().map(|link| self.id_for(link)).collect()
354    }
355
356    fn raw_item(&self, node: &GrammarNode) -> RawItem {
357        let (start, end) = (node.start, node.end);
358        match node.term {
359            "reference" => RawItem::Reference {
360                id: self.text[start..end].to_owned(),
361                start,
362                end,
363            },
364            "quoted_reference" => RawItem::Reference {
365                id: quoted_reference_value(self.text, start, end),
366                start,
367                end,
368            },
369            "group" => RawItem::Group {
370                nested: self.fields(node, "link"),
371                start,
372                end,
373            },
374            _ => RawItem::Link {
375                id: self
376                    .fields(node, "id")
377                    .into_iter()
378                    .next()
379                    .map(|id| match id {
380                        RawItem::Reference { id, .. } => id,
381                        RawItem::Group { .. } | RawItem::Link { .. } => String::new(),
382                    }),
383                values: self.fields(node, "value"),
384                children: self.fields(node, "child"),
385                start,
386                end,
387            },
388        }
389    }
390
391    fn fields(&self, node: &GrammarNode, field: &str) -> Vec<RawItem> {
392        node.children
393            .iter()
394            .filter(|child| child.field == Some(field))
395            .map(|child| self.raw_item(child))
396            .collect()
397    }
398
399    // ---- The official transformation (`links-notation/src/Parser.js`) --------
400
401    fn official(
402        &mut self,
403        id: Option<String>,
404        values: Vec<usize>,
405        start: usize,
406        end: usize,
407    ) -> usize {
408        self.links.push(Official {
409            id,
410            values,
411            start,
412            end,
413        });
414        self.links.len() - 1
415    }
416
417    fn collect_links(&mut self, item: &RawItem, parent_path: &[usize], result: &mut Vec<usize>) {
418        if let (
419            true,
420            RawItem::Link {
421                id,
422                children,
423                start,
424                end,
425                ..
426            },
427        ) = (item.is_indented_id(), item)
428        {
429            let values = children
430                .iter()
431                .map(|child| self.transform_indented_value(child))
432                .collect();
433            let current = self.official(id.clone(), values, *start, *end);
434            let combined = self.combine_path_elements(parent_path, current);
435            result.push(combined);
436            return;
437        }
438        let current = self.transform_link(item);
439        let combined = self.combine_path_elements(parent_path, current);
440        result.push(combined);
441        if item.children().is_empty() {
442            return;
443        }
444        let mut path = parent_path.to_vec();
445        path.push(current);
446        for child in item.children() {
447            self.collect_links(child, &path, result);
448        }
449    }
450
451    fn transform_indented_value(&mut self, item: &RawItem) -> usize {
452        let (start, end) = item.span();
453        if let (true, RawItem::Link { id, children, .. }) = (item.is_indented_id(), item) {
454            let values = children
455                .iter()
456                .map(|child| self.transform_indented_value(child))
457                .collect();
458            return self.official(id.clone(), values, start, end);
459        }
460        let current = self.transform_link(item);
461        if !item.children().is_empty() {
462            let mut values = self.links[current].values.clone();
463            for child in item.children() {
464                values.push(self.transform_indented_value(child));
465            }
466            let id = self.links[current].id.clone();
467            return self.official(id, values, start, end);
468        }
469        if matches!(item, RawItem::Link { id: None, .. }) && self.links[current].values.len() == 1 {
470            return self.links[current].values[0];
471        }
472        current
473    }
474
475    fn combine_path_elements(&mut self, path: &[usize], current: usize) -> usize {
476        let Some((&last, rest)) = path.split_last() else {
477            return current;
478        };
479        let parent = if rest.is_empty() {
480            last
481        } else {
482            self.combine_path_elements(rest, last)
483        };
484        let start = self.links[parent].start.min(self.links[current].start);
485        let end = self.links[parent].end.max(self.links[current].end);
486        self.official(None, vec![parent, current], start, end)
487    }
488
489    fn transform_nested(&mut self, nested: &[RawItem], start: usize, end: usize) -> usize {
490        let mut links = Vec::new();
491        for item in nested {
492            self.collect_links(item, &[], &mut links);
493        }
494        let wraps_single_group = nested.len() == 1 && matches!(nested[0], RawItem::Group { .. });
495        if links.len() == 1 && !wraps_single_group {
496            return links[0];
497        }
498        self.official(None, links, start, end)
499    }
500
501    fn transform_link(&mut self, item: &RawItem) -> usize {
502        match item {
503            RawItem::Group { nested, start, end } => self.transform_nested(nested, *start, *end),
504            RawItem::Reference { id, start, end } => {
505                self.official(Some(id.clone()), Vec::new(), *start, *end)
506            }
507            RawItem::Link {
508                id,
509                values,
510                start,
511                end,
512                ..
513            } => {
514                let values = values
515                    .iter()
516                    .map(|value| self.transform_link(value))
517                    .collect();
518                self.official(id.clone(), values, *start, *end)
519            }
520        }
521    }
522
523    // ---- Links into the network ----------------------------------------------
524
525    fn is_reference(&self, link: usize) -> bool {
526        self.links[link].values.is_empty() && self.links[link].id.is_some()
527    }
528
529    // Every link becomes a Relation and every name is registered before any
530    // value is resolved, so a name used before its definition, or inside it,
531    // still resolves to it.
532    fn allocate(&mut self, link: usize) {
533        if self.is_reference(link) || self.ids[link].is_some() {
534            return;
535        }
536        let Official { id, start, end, .. } = &self.links[link];
537        let mut metadata = LinkMetadata::new()
538            .with_link_type(LinkType::Relation)
539            .with_named(id.is_some())
540            .with_language(self.language)
541            .with_span(self.span(*start, *end));
542        if let Some(name) = id {
543            metadata = metadata.with_term(name);
544        }
545        self.ids[link] = Some(self.network.insert_dynamic_link(&[], metadata));
546        for value in self.links[link].values.clone() {
547            self.allocate(value);
548        }
549    }
550
551    fn connect(&mut self, link: usize, connected: &mut HashSet<usize>) {
552        if self.is_reference(link) || !connected.insert(link) {
553            return;
554        }
555        let values = self.links[link].values.clone();
556        for &value in &values {
557            self.connect(value, connected);
558        }
559        let references: Vec<LinkId> = values.into_iter().map(|value| self.id_for(value)).collect();
560        let id = self.ids[link].expect("an allocated link");
561        self.network.set_references(id, &references);
562    }
563
564    fn id_for(&mut self, link: usize) -> LinkId {
565        if !self.is_reference(link) {
566            return self.ids[link].expect("an allocated link");
567        }
568        let name = self.links[link].id.clone().unwrap_or_default();
569        self.network.find_term(&name).unwrap_or_else(|| {
570            self.network
571                .insert_typed_point(&name, LinkType::Concept, None)
572        })
573    }
574
575    fn span(&self, start: usize, end: usize) -> SourceSpan {
576        SourceSpan::new(
577            ByteRange::new(start, end),
578            self.lines.byte_point(start),
579            self.lines.byte_point(end),
580        )
581    }
582}