Skip to main content

meta_language/grammar/interchange/
feature_links.rs

1//! The grammar feature union in the native links form: the declaration links
2//! `(import NAME)`, `(mode NAME)`, `(extra EXPRESSION)`, `(conflict NAME...)`,
3//! `(precedences (name|rule NAME)...)`, `(macro NAME (parameters P...) EXPRESSION)` and `(scanner NAME (tokens
4//! T...) (operations OPERATION...))` between the grammar link and the first
5//! rule, the rule fields `(parameters P...)`, `(channel NAME)`, `(modes
6//! M...)` and `(action OPERATION...)` and the rule metadata `(concept ID)` and
7//! `(source-names (SOURCE NAME)...)` before `(doc TEXT)`, and the feature
8//! expressions. It mirrors `js/src/grammar-links.js`.
9
10use super::super::feature::class_expression;
11use super::super::{
12    FeatureExpr, GrammarDeclarations, GrammarExpr, GrammarImportError, GrammarMacro,
13    GrammarScanner, GrammarSourceName, Operation, OperationCategory, PrecedenceEntry,
14    RuleAttributes, UnicodeClassItem,
15};
16use super::feature_forms::{
17    FormCodec, LinksReader, read_links_feature, read_links_operation, render_feature_form,
18    render_operation,
19};
20use super::links::{
21    Node, arity, char_text, decoded_word, link, links_error, parse_node, parts,
22    percent_decode_links_text, percent_encode_links_text, render_links_expression, word,
23};
24
25/// The links spelling of the feature union forms.
26struct LinksCodec;
27
28impl FormCodec for LinksCodec {
29    fn name(&self, value: &str) -> String {
30        percent_encode_links_text(value)
31    }
32
33    fn text(&self, value: &str) -> String {
34        percent_encode_links_text(value)
35    }
36
37    fn expression(&self, expr: &GrammarExpr) -> String {
38        render_links_expression(expr)
39    }
40
41    fn call(&self, head: &str, parts: Vec<String>) -> String {
42        if parts.is_empty() {
43            return head.to_owned();
44        }
45        let mut all = vec![head.to_owned()];
46        all.extend(parts);
47        link(&all)
48    }
49
50    fn block(&self, word: &str, parts: Vec<String>) -> String {
51        let mut all = vec![word.to_owned()];
52        all.extend(parts);
53        link(&all)
54    }
55
56    fn byte_class(&self, negated: bool, items: Vec<String>) -> String {
57        let mut all = vec![
58            "byteClass".to_owned(),
59            if negated { "negated" } else { "plain" }.to_owned(),
60        ];
61        all.extend(items);
62        link(&all)
63    }
64}
65
66/// Renders one feature union expression of the native links form.
67pub(super) fn render_links_feature(feature: &FeatureExpr) -> String {
68    match feature {
69        FeatureExpr::UnicodeClass { negated, items } => {
70            let mut parts = vec![
71                "class".to_owned(),
72                if *negated { "negated" } else { "plain" }.to_owned(),
73            ];
74            parts.extend(items.iter().map(|item| match item {
75                UnicodeClassItem::Char(value) => link(&["char".to_owned(), char_text(*value)]),
76                UnicodeClassItem::Range(start, end) => {
77                    link(&["range".to_owned(), char_text(*start), char_text(*end)])
78                }
79                UnicodeClassItem::Category(value) => {
80                    link(&["category".to_owned(), percent_encode_links_text(value)])
81                }
82                UnicodeClassItem::Script(value) => {
83                    link(&["script".to_owned(), percent_encode_links_text(value)])
84                }
85            }));
86            link(&parts)
87        }
88        FeatureExpr::Call { name, arguments } => {
89            let mut parts = vec!["ref".to_owned(), percent_encode_links_text(name)];
90            parts.extend(arguments.iter().map(render_links_expression));
91            link(&parts)
92        }
93        FeatureExpr::Form(_) | FeatureExpr::ByteClass { .. } => {
94            render_feature_form(feature, &LinksCodec).unwrap_or_default()
95        }
96    }
97}
98
99fn render_names(head: &str, names: &[String]) -> String {
100    let mut parts = vec![head.to_owned()];
101    parts.extend(names.iter().map(|name| percent_encode_links_text(name)));
102    link(&parts)
103}
104
105fn render_operations(head: &str, operations: &[Operation]) -> String {
106    let mut parts = vec![head.to_owned()];
107    parts.extend(
108        operations
109            .iter()
110            .map(|operation| render_operation(operation, &LinksCodec)),
111    );
112    link(&parts)
113}
114
115/// The declaration links between the grammar link and the first rule.
116#[must_use]
117pub fn render_declaration_links(declarations: &GrammarDeclarations) -> Vec<String> {
118    let mut lines = Vec::new();
119    for name in &declarations.imports {
120        lines.push(format!("(import {})", percent_encode_links_text(name)));
121    }
122    for name in &declarations.modes {
123        lines.push(format!("(mode {})", percent_encode_links_text(name)));
124    }
125    for extra in &declarations.extras {
126        lines.push(format!("(extra {})", render_links_expression(extra)));
127    }
128    for group in &declarations.conflicts {
129        lines.push(render_names("conflict", group));
130    }
131    for order in &declarations.precedences {
132        let entries = order.iter().map(|entry| {
133            format!(
134                "({} {})",
135                entry.kind(),
136                percent_encode_links_text(entry.value())
137            )
138        });
139        lines.push(format!(
140            "(precedences {})",
141            entries.collect::<Vec<_>>().join(" ")
142        ));
143    }
144    for declared in &declarations.macros {
145        lines.push(format!(
146            "(macro {} {} {})",
147            percent_encode_links_text(&declared.name),
148            render_names("parameters", &declared.parameters),
149            render_links_expression(&declared.expression)
150        ));
151    }
152    for scanner in &declarations.scanners {
153        lines.push(format!(
154            "(scanner {} {} {})",
155            percent_encode_links_text(&scanner.name),
156            render_names("tokens", &scanner.tokens),
157            render_operations("operations", &scanner.operations)
158        ));
159    }
160    lines
161}
162
163/// The rule fields before the `(doc TEXT)` field.
164#[must_use]
165pub fn render_rule_fields(attributes: &RuleAttributes) -> Vec<String> {
166    let mut fields = Vec::new();
167    if !attributes.parameters.is_empty() {
168        fields.push(render_names("parameters", &attributes.parameters));
169    }
170    if let Some(channel) = &attributes.channel {
171        fields.push(format!("(channel {})", percent_encode_links_text(channel)));
172    }
173    if let Some(modes) = &attributes.modes {
174        fields.push(render_names("modes", modes));
175    }
176    if let Some(action) = &attributes.action {
177        fields.push(render_operations("action", action));
178    }
179    fields
180}
181
182/// The links reader of the shared feature form tables.
183struct Helpers;
184
185impl LinksReader for Helpers {
186    type Node = Node;
187
188    fn parts<'a>(&self, node: &'a Node) -> Result<(&'a str, &'a [Node]), GrammarImportError> {
189        parts(node)
190    }
191
192    fn word<'a>(&self, node: &'a Node, what: &str) -> Result<&'a str, GrammarImportError> {
193        word(Some(node), what)
194    }
195
196    fn decoded_word(&self, node: &Node, what: &str) -> Result<String, GrammarImportError> {
197        decoded_word(Some(node), what)
198    }
199
200    fn expression(&self, node: &Node) -> Result<GrammarExpr, GrammarImportError> {
201        parse_node(node)
202    }
203
204    fn fail(&self, detail: String) -> GrammarImportError {
205        links_error(detail)
206    }
207}
208
209/// Reads a feature union expression headed by `head`, or `None`.
210pub(super) fn read_feature(
211    head: &str,
212    args: &[Node],
213) -> Result<Option<GrammarExpr>, GrammarImportError> {
214    read_links_feature(&Helpers, head, args)
215}
216
217/// Reads a `(class plain|negated ITEM...)` link's items.
218pub(super) fn read_class(negated: bool, items: &[Node]) -> Result<GrammarExpr, GrammarImportError> {
219    let items = items
220        .iter()
221        .map(|item| {
222            let (head, args) = parts(item)?;
223            match head {
224                "char" => {
225                    arity(head, args, 1)?;
226                    Ok(UnicodeClassItem::Char(super::links::character(
227                        args.first(),
228                        "a class character",
229                    )?))
230                }
231                "range" => {
232                    arity(head, args, 2)?;
233                    Ok(UnicodeClassItem::Range(
234                        super::links::character(args.first(), "a range start")?,
235                        super::links::character(args.get(1), "a range end")?,
236                    ))
237                }
238                "category" | "script" => {
239                    arity(head, args, 1)?;
240                    let value = decoded_word(args.first(), &format!("a Unicode {head}"))?;
241                    Ok(if head == "category" {
242                        UnicodeClassItem::Category(value)
243                    } else {
244                        UnicodeClassItem::Script(value)
245                    })
246                }
247                other => Err(links_error(format!("unknown class item {other}"))),
248            }
249        })
250        .collect::<Result<_, _>>()?;
251    Ok(class_expression(negated, items))
252}
253
254fn names(node: &Node, head: &str) -> Result<Vec<String>, GrammarImportError> {
255    let (found, args) = parts(node)?;
256    if found != head {
257        return Err(links_error(format!("expected ({head} ...), not {found}")));
258    }
259    args.iter()
260        .map(|item| decoded_word(Some(item), &format!("a {head} name")))
261        .collect()
262}
263
264fn operations(node: &Node, head: &str) -> Result<Vec<Operation>, GrammarImportError> {
265    let (found, args) = parts(node)?;
266    if found != head {
267        return Err(links_error(format!("expected ({head} ...), not {found}")));
268    }
269    args.iter()
270        .map(|item| read_links_operation(&Helpers, item, OperationCategory::Statement))
271        .collect()
272}
273
274/// Reads one declaration link into `declarations`; `false` when `head` names none.
275pub(super) fn read_declaration(
276    head: &str,
277    args: &[Node],
278    declarations: &mut GrammarDeclarations,
279) -> Result<bool, GrammarImportError> {
280    let one = || arity(head, args, 1).map(|()| &args[0]);
281    match head {
282        "import" => declarations
283            .imports
284            .push(decoded_word(Some(one()?), "an imported grammar")?),
285        "mode" => declarations
286            .modes
287            .push(decoded_word(Some(one()?), "a mode")?),
288        "extra" => declarations.extras.push(parse_node(one()?)?),
289        "conflict" => {
290            if args.is_empty() {
291                return Err(links_error("conflict names at least one rule"));
292            }
293            declarations.conflicts.push(
294                args.iter()
295                    .map(|item| decoded_word(Some(item), "a rule name"))
296                    .collect::<Result<_, _>>()?,
297            );
298        }
299        "precedences" => {
300            if args.len() < 2 {
301                return Err(links_error("precedences orders at least two entries"));
302            }
303            let mut order = Vec::new();
304            for item in args {
305                let (kind, values) = parts(item)?;
306                let what = match kind {
307                    "name" => "a precedence name",
308                    "rule" => "a rule name",
309                    other => {
310                        return Err(links_error(format!(
311                            "a precedence entry is (name NAME) or (rule NAME), not {other}"
312                        )));
313                    }
314                };
315                arity(kind, values, 1)?;
316                let value = decoded_word(values.first(), what)?;
317                order.extend(PrecedenceEntry::from_kind(kind, value));
318            }
319            declarations.precedences.push(order);
320        }
321        "macro" => {
322            arity(head, args, 3)?;
323            declarations.macros.push(GrammarMacro {
324                name: decoded_word(args.first(), "a macro name")?,
325                parameters: names(&args[1], "parameters")?,
326                expression: parse_node(&args[2])?,
327            });
328        }
329        "scanner" => {
330            arity(head, args, 3)?;
331            declarations.scanners.push(GrammarScanner {
332                name: decoded_word(args.first(), "a scanner name")?,
333                tokens: names(&args[1], "tokens")?,
334                operations: operations(&args[2], "operations")?,
335            });
336        }
337        _ => return Ok(false),
338    }
339    Ok(true)
340}
341
342/// The rule fields in order: the attributes, the metadata `(concept ID)` and
343/// `(source-names (SOURCE NAME)...)`, and `(doc TEXT)`.
344const RULE_FIELDS: [&str; 7] = [
345    "parameters",
346    "channel",
347    "modes",
348    "action",
349    "concept",
350    "source-names",
351    "doc",
352];
353
354/// The optional fields of a rule link.
355#[derive(Default)]
356pub(super) struct RuleFields {
357    pub attributes: RuleAttributes,
358    pub concept: Option<String>,
359    pub source_names: Vec<GrammarSourceName>,
360    pub doc: Option<String>,
361}
362
363/// Reads the `(SOURCE NAME)...` items of `(source-names (SOURCE NAME)...)`.
364pub(super) fn read_source_names(
365    args: &[Node],
366) -> Result<Vec<GrammarSourceName>, GrammarImportError> {
367    if args.is_empty() {
368        return Err(links_error("source-names lists at least one source name"));
369    }
370    args.iter()
371        .map(|item| {
372            let (source, name) = parts(item)?;
373            if name.len() != 1 {
374                return Err(links_error("a source name is (SOURCE NAME)"));
375            }
376            Ok(GrammarSourceName {
377                source: percent_decode_links_text(source)?,
378                name: decoded_word(name.first(), "a source name")?,
379            })
380        })
381        .collect()
382}
383
384/// Reads the optional fields of a rule link: its attributes, its metadata and
385/// its doc.
386pub(super) fn read_rule_fields(fields: &[Node]) -> Result<RuleFields, GrammarImportError> {
387    let mut read = RuleFields::default();
388    let mut order = 0;
389    for field in fields {
390        let (head, args) = parts(field)?;
391        let Some(position) = RULE_FIELDS.iter().position(|name| *name == head) else {
392            return Err(links_error(format!("unexpected rule field {head}")));
393        };
394        if position < order {
395            return Err(links_error(format!("rule field {head} is out of order")));
396        }
397        order = position + 1;
398        match head {
399            "parameters" => read.attributes.parameters = names(field, "parameters")?,
400            "modes" => read.attributes.modes = Some(names(field, "modes")?),
401            "action" => read.attributes.action = Some(operations(field, "action")?),
402            "source-names" => read.source_names = read_source_names(args)?,
403            "channel" => {
404                arity(head, args, 1)?;
405                read.attributes.channel = Some(decoded_word(args.first(), "a channel name")?);
406            }
407            "concept" => {
408                arity(head, args, 1)?;
409                read.concept = Some(decoded_word(args.first(), "a concept id")?);
410            }
411            _ => {
412                arity(head, args, 1)?;
413                read.doc = Some(decoded_word(args.first(), "a doc text")?);
414            }
415        }
416    }
417    Ok(read)
418}