1use 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#[derive(Debug, Clone, PartialEq, Eq)]
49pub enum LinksNotationReading {
50 Reference(String),
52 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 #[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 #[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 #[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
134fn format_lino_readings(readings: &[LinksNotationReading]) -> String {
139 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("e_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
211fn 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
256enum 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 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
300struct 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 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 fn is_reference(&self, link: usize) -> bool {
526 self.links[link].values.is_empty() && self.links[link].id.is_some()
527 }
528
529 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}