tramlines_core/grammar/lower/
shapes.rs1use super::{Lowerer, bool_of, str_of, usize_of};
6use crate::{
7 Spanned,
8 grammar::{FmFormat, Format, FrontmatterConstraint, IdTemplate, Shape, shape_of},
9};
10
11impl Lowerer<'_> {
12 pub fn shape(&mut self, node: &Spanned) -> Option<Shape> {
14 match shape_of(node, &self.types, self.regexes) {
15 Ok(s) => s,
16 Err(e) => {
17 self.fail_with(node.line, e);
18 None
19 }
20 }
21 }
22
23 pub fn check_key(
27 &mut self,
28 line: usize,
29 key: &str,
30 shape: Option<&Shape>,
31 format: Option<&Format>,
32 ) {
33 let shapes: Vec<&Shape> = match (shape, format) {
34 (Some(s), _) => vec![s],
35 (None, Some(f)) => f.shapes().map(|(_, s)| s).collect(),
36 (None, None) => vec![],
37 };
38 if shapes.is_empty() || shapes.iter().any(|s| !s.has_group(key)) {
39 self.fail(
40 line,
41 "TL_31",
42 format!("`key` `{key}` names no hole or named group of the rule's shape"),
43 );
44 }
45 }
46
47 pub fn frontmatter_constraint(&mut self, node: &Spanned, key: &str) -> FrontmatterConstraint {
49 if key != "id" {
52 self.explained(node, &["pattern", "template"], false);
53 }
54 self.one_voice(node);
55 let shape = self.shape(node);
56 let width = usize_of(node, "seq-width");
60 let mut id_template = None;
61 match (&shape, key) {
62 (Some(Shape::Template(t)), "id") => {
63 for other in ["const", "enum", "format", "list", "min", "max"] {
64 if let Some(o) = node.get(other) {
65 self.fail(
67 o.line,
68 "TL_115",
69 format!(
70 "`{other}` beside the `id` template: `id` takes `template` alone"
71 ),
72 );
73 }
74 }
75 match IdTemplate::from_template(t, width.unwrap_or(3), &self.lifecycle) {
76 Ok(parsed) => {
77 if width.is_some() && parsed.seq_width().is_none() {
78 self.fail(
80 node.line,
81 "TL_116",
82 "`seq-width` beside a template with no `seq` hole",
83 );
84 }
85 id_template = Some(parsed);
86 }
87 Err(e) => self.fail(node.line, e.criterion, e.message),
88 }
89 }
90 (None, "id") if node.get("const").is_some() => {
93 match IdTemplate::fixed(&str_of(node, "const").unwrap_or_default()) {
94 Ok(fixed) => id_template = Some(fixed),
95 Err(e) => self.fail(node.line, e.criterion, e.message),
96 }
97 }
98 _ => {
99 if width.is_some() {
100 self.fail(
101 node.line,
102 "TL_116",
103 "`seq-width` applies to the `id` template alone",
104 );
105 }
106 }
107 }
108 if let Some(min) = usize_of(node, "min") {
109 self.check_bounds(node.line, min, usize_of(node, "max"));
110 }
111 FrontmatterConstraint {
112 common: self.common(node),
113 const_: node.get("const").and_then(|c| c.scalar_text()),
114 shape,
115 enum_: node
116 .get("enum")
117 .and_then(|e| e.as_seq())
118 .map(|items| items.iter().filter_map(|i| i.scalar_text()).collect()),
119 format: str_of(node, "format").map(|f| match f.as_str() {
122 "url" => FmFormat::Url,
123 "slug" => FmFormat::Slug,
124 _ => FmFormat::Host(f),
125 }),
126 id_template,
127 list: bool_of(node, "list"),
128 min: usize_of(node, "min"),
129 max: usize_of(node, "max"),
130 optional: bool_of(node, "optional"),
131 example: str_of(node, "example"),
132 guidance: str_of(node, "guidance"),
133 }
134 }
135}