Skip to main content

meta_language/grammar/surface/
mod.rs

1//! Meta-notation-derived textual surface syntax for grammar authoring.
2//!
3//! The public parser first runs the input through the existing links-network
4//! parse boundary, then lowers the surface tokens into the grammar IR. The
5//! `LiNo` helpers reuse the grammar links codec plus [`LinkNetwork::to_lino`] and
6//! [`LinkNetwork::from_lino`], so there is still only one canonical network
7//! serializer.
8
9use std::error::Error;
10use std::fmt;
11
12use crate::grammar::Grammar;
13use crate::link_network::{Link, LinkId, LinkNetwork, LinkType};
14use crate::rust_codec::{FromLinks, LinksDecoder, LinksEncoder, ToLinks};
15use crate::ParseConfiguration;
16
17mod lower;
18mod token;
19mod write;
20
21const GRAMMAR_SURFACE_LANGUAGE: &str = "grammar-surface";
22const GRAMMAR_ROOT_TAG: &str = "grammar::grammar";
23
24/// Error raised while parsing grammar surface text.
25#[derive(Debug, Clone, PartialEq, Eq)]
26pub enum GrammarSurfaceError {
27    /// The delimiter skeleton could not be parsed.
28    Skeleton {
29        /// Human-readable error message.
30        message: String,
31    },
32    /// A skeleton node had no valid lowering.
33    Lowering {
34        /// Rule being lowered, when known.
35        rule: Option<String>,
36        /// Human-readable error message.
37        message: String,
38    },
39    /// A rule referenced a name that no rule defines.
40    UndefinedReference {
41        /// Rule containing the undefined reference.
42        rule: String,
43        /// Referenced rule name that was not defined.
44        name: String,
45    },
46}
47
48impl fmt::Display for GrammarSurfaceError {
49    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
50        match self {
51            Self::Skeleton { message } => write!(formatter, "grammar skeleton error: {message}"),
52            Self::Lowering { rule, message } => match rule {
53                Some(rule) => write!(formatter, "grammar lowering error in {rule}: {message}"),
54                None => write!(formatter, "grammar lowering error: {message}"),
55            },
56            Self::UndefinedReference { rule, name } => {
57                write!(formatter, "rule {rule} references undefined rule {name}")
58            }
59        }
60    }
61}
62
63impl Error for GrammarSurfaceError {}
64
65/// Parses meta-notation-derived grammar surface text into the grammar IR.
66///
67/// # Errors
68///
69/// Returns [`GrammarSurfaceError`] when the delimiter skeleton is malformed,
70/// surface nodes cannot be lowered, or non-terminal references are undefined.
71pub fn parse_grammar_surface(text: &str) -> Result<Grammar, GrammarSurfaceError> {
72    let _skeleton = skeletonise(text);
73    let tokens = token::parse_surface_tokens(text)?;
74    lower::lower_document(&tokens)
75}
76
77/// Lifts a grammar back to canonical surface text.
78#[must_use]
79pub fn write_grammar_surface(grammar: &Grammar) -> String {
80    write::write_surface(grammar)
81}
82
83/// Encodes a grammar through the existing links codec and serializes it as `LiNo`.
84#[must_use]
85pub fn grammar_to_lino(grammar: &Grammar) -> String {
86    let mut encoder = LinksEncoder::new();
87    let _root = grammar.to_links(&mut encoder);
88    encoder.into_network().to_lino()
89}
90
91/// Decodes a grammar from `LiNo` text produced by [`grammar_to_lino`].
92///
93/// # Errors
94///
95/// Returns [`GrammarSurfaceError`] when the `LiNo` text or grammar links are
96/// malformed.
97pub fn grammar_from_lino(text: &str) -> Result<Grammar, GrammarSurfaceError> {
98    let network = LinkNetwork::from_lino(text).map_err(|error| GrammarSurfaceError::Skeleton {
99        message: error.to_string(),
100    })?;
101    let root = grammar_root(&network).ok_or_else(|| GrammarSurfaceError::Lowering {
102        rule: None,
103        message: "LiNo network does not contain a grammar root".to_string(),
104    })?;
105    let mut decoder = LinksDecoder::new(&network);
106    Grammar::from_links(&mut decoder, root).map_err(|error| GrammarSurfaceError::Lowering {
107        rule: None,
108        message: error.to_string(),
109    })
110}
111
112fn skeletonise(text: &str) -> LinkNetwork {
113    LinkNetwork::parse(
114        text,
115        GRAMMAR_SURFACE_LANGUAGE,
116        ParseConfiguration::default(),
117    )
118}
119
120fn grammar_root(network: &LinkNetwork) -> Option<LinkId> {
121    network
122        .links()
123        .find(|link| {
124            link.metadata().link_type() == Some(LinkType::Grammar)
125                && link.metadata().term() == Some(GRAMMAR_ROOT_TAG)
126        })
127        .map(Link::id)
128}
129
130fn skeleton_error(message: impl Into<String>) -> GrammarSurfaceError {
131    GrammarSurfaceError::Skeleton {
132        message: message.into(),
133    }
134}
135
136fn lowering_error(rule: Option<&str>, message: impl Into<String>) -> GrammarSurfaceError {
137    GrammarSurfaceError::Lowering {
138        rule: rule.map(str::to_string),
139        message: message.into(),
140    }
141}