Skip to main content

meta_language/
link_network.rs

1use std::collections::{BTreeMap, BTreeSet};
2use std::fmt;
3use std::sync::Arc;
4
5use crate::configuration::ParseConfiguration;
6use crate::embedded_region_parser;
7use crate::language_catalog::{GrammarProvenance, grammar_provenance};
8use crate::language_parser::{BuiltInLanguageParser, LanguageParser};
9use crate::language_profile::LanguageProfile;
10use crate::link_flags::LinkFlags;
11use crate::mixed_regions::EmbeddedRegion;
12use crate::natural_language::annotate_natural_language;
13pub use crate::network_projection::NetworkProjection;
14use crate::query::{LinkQuery, QueryMatch, QueryPredicateHost, RejectPredicateHost};
15use crate::self_description::{SELF_DESCRIPTION_ROOTS, definition_expression};
16use crate::source::{ByteRange, Point, SourceSpan};
17use crate::substitution::{
18    SubstitutionBindings, SubstitutionReport, SubstitutionRule, VariableSubstitutionRule,
19};
20
21mod trivia;
22mod verification;
23/// Stable identifier for a link inside a [`LinkNetwork`].
24#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
25pub struct LinkId(pub(crate) u64);
26
27impl LinkId {
28    /// Builds a link identifier from its numeric representation.
29    #[must_use]
30    pub const fn from_u64(value: u64) -> Self {
31        Self(value)
32    }
33
34    /// Returns the numeric identifier.
35    #[must_use]
36    pub const fn as_u64(self) -> u64 {
37        self.0
38    }
39}
40
41impl fmt::Display for LinkId {
42    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
43        write!(formatter, "{}", self.0)
44    }
45}
46
47/// Coarse role for a link in the meta-language network.
48#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
49pub enum LinkType {
50    Link,
51    Reference,
52    Relation,
53    Language,
54    Grammar,
55    Type,
56    Concept,
57    Syntax,
58    Field,
59    Trivia,
60    Token,
61    Document,
62    Semantic,
63    Region,
64    Object,
65}
66
67impl fmt::Display for LinkType {
68    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
69        let name = match self {
70            Self::Link => "link",
71            Self::Reference => "reference",
72            Self::Relation => "relation",
73            Self::Language => "language",
74            Self::Grammar => "grammar",
75            Self::Type => "type",
76            Self::Concept => "concept",
77            Self::Syntax => "syntax",
78            Self::Field => "field",
79            Self::Trivia => "trivia",
80            Self::Token => "token",
81            Self::Document => "document",
82            Self::Semantic => "semantic",
83            Self::Region => "region",
84            Self::Object => "object",
85        };
86        formatter.write_str(name)
87    }
88}
89
90/// Metadata carried by a link.
91#[derive(Clone, Debug, Default, PartialEq, Eq)]
92pub struct LinkMetadata {
93    link_type: Option<LinkType>,
94    named: bool,
95    term: Option<Arc<str>>,
96    definition: Option<Arc<str>>,
97    language: Option<Arc<str>>,
98    span: Option<SourceSpan>,
99    flags: LinkFlags,
100}
101
102impl LinkMetadata {
103    /// Creates empty metadata.
104    #[must_use]
105    pub fn new() -> Self {
106        Self::default()
107    }
108
109    /// Returns metadata with a link type.
110    #[must_use]
111    pub const fn with_link_type(mut self, link_type: LinkType) -> Self {
112        self.link_type = Some(link_type);
113        self
114    }
115
116    /// Returns metadata with the named flag set.
117    #[must_use]
118    pub const fn with_named(mut self, named: bool) -> Self {
119        self.named = named;
120        self
121    }
122
123    /// Returns metadata with a term label.
124    #[must_use]
125    pub fn with_term(mut self, term: impl Into<String>) -> Self {
126        self.term = Some(Arc::from(term.into()));
127        self
128    }
129
130    /// Returns metadata with a self-description definition.
131    #[must_use]
132    pub fn with_definition(mut self, definition: impl Into<String>) -> Self {
133        self.definition = Some(Arc::from(definition.into()));
134        self
135    }
136
137    /// Returns metadata with a language label.
138    #[must_use]
139    pub fn with_language(mut self, language: impl Into<String>) -> Self {
140        self.language = Some(Arc::from(language.into()));
141        self
142    }
143
144    /// Returns metadata with a source span.
145    #[must_use]
146    pub const fn with_span(mut self, span: SourceSpan) -> Self {
147        self.span = Some(span);
148        self
149    }
150
151    /// Returns metadata with parse status flags.
152    #[must_use]
153    pub const fn with_flags(mut self, flags: LinkFlags) -> Self {
154        self.flags = flags;
155        self
156    }
157
158    /// Link type, when known.
159    #[must_use]
160    pub const fn link_type(&self) -> Option<LinkType> {
161        self.link_type
162    }
163
164    /// Whether this link is named.
165    #[must_use]
166    pub const fn is_named(&self) -> bool {
167        self.named
168    }
169
170    /// Term label attached to this link.
171    #[must_use]
172    pub fn term(&self) -> Option<&str> {
173        self.term.as_deref()
174    }
175
176    /// Self-description definition attached to this link.
177    #[must_use]
178    pub fn definition(&self) -> Option<&str> {
179        self.definition.as_deref()
180    }
181
182    /// Language label attached to this link.
183    #[must_use]
184    pub fn language(&self) -> Option<&str> {
185        self.language.as_deref()
186    }
187
188    /// Source span attached to this link.
189    #[must_use]
190    pub const fn span(&self) -> Option<SourceSpan> {
191        self.span
192    }
193
194    /// Parse status flags attached to this link.
195    #[must_use]
196    pub const fn flags(&self) -> LinkFlags {
197        self.flags
198    }
199}
200
201/// A link is an n-tuple of references to other links.
202#[derive(Clone, Debug, PartialEq, Eq)]
203pub struct Link {
204    pub(crate) id: LinkId,
205    pub(crate) references: Arc<[LinkId]>,
206    pub(crate) metadata: LinkMetadata,
207}
208
209impl Link {
210    /// Link identifier.
211    #[must_use]
212    pub const fn id(&self) -> LinkId {
213        self.id
214    }
215
216    /// Ordered references carried by this link.
217    #[must_use]
218    pub fn references(&self) -> &[LinkId] {
219        &self.references
220    }
221
222    /// Metadata carried by this link.
223    #[must_use]
224    pub const fn metadata(&self) -> &LinkMetadata {
225        &self.metadata
226    }
227
228    const fn metadata_mut(&mut self) -> &mut LinkMetadata {
229        &mut self.metadata
230    }
231}
232
233/// Mutable links network for CST, AST, semantic, and self-description links.
234#[derive(Clone, Debug, Default, PartialEq, Eq)]
235pub struct LinkNetwork {
236    pub(crate) next_id: u64,
237    pub(crate) links: BTreeMap<LinkId, Arc<Link>>,
238    pub(crate) terms: BTreeMap<Arc<str>, LinkId>,
239    pub(crate) concept_syntax: BTreeMap<(Arc<str>, Arc<str>), Arc<str>>,
240    pub(crate) strings: BTreeSet<Arc<str>>,
241}
242
243impl LinkNetwork {
244    /// Creates an empty links network.
245    #[must_use]
246    pub const fn new() -> Self {
247        Self {
248            next_id: 1,
249            links: BTreeMap::new(),
250            terms: BTreeMap::new(),
251            concept_syntax: BTreeMap::new(),
252            strings: BTreeSet::new(),
253        }
254    }
255
256    /// Creates a links network containing the common self-description roots.
257    #[must_use]
258    pub fn self_describing() -> Self {
259        let mut network = Self::new();
260        for root in SELF_DESCRIPTION_ROOTS {
261            let definition = definition_expression(root.term, root.references);
262            network.insert_typed_point(root.term, root.link_type, Some(&definition));
263        }
264
265        for root in SELF_DESCRIPTION_ROOTS {
266            let mut references = Vec::with_capacity(root.references.len() + 1);
267            references.push(
268                network
269                    .find_term(root.term)
270                    .expect("seeded self-description root exists"),
271            );
272            for reference in root.references {
273                references.push(
274                    network
275                        .find_term(reference)
276                        .expect("seeded self-description reference exists"),
277                );
278            }
279
280            network.insert_dynamic_link(
281                &references,
282                LinkMetadata::new().with_link_type(LinkType::Relation),
283            );
284        }
285
286        network
287    }
288
289    /// Serializes the seeded self-description roots as LiNo-style definition lines.
290    #[must_use]
291    pub fn self_description_text(&self) -> String {
292        let mut output = String::new();
293        for root in SELF_DESCRIPTION_ROOTS {
294            let Some(id) = self.find_term(root.term) else {
295                continue;
296            };
297            let Some(definition) = self.definition_for(id) else {
298                continue;
299            };
300            output.push_str(definition);
301            output.push('\n');
302        }
303        output
304    }
305
306    /// Parses plain source text into a lossless token network.
307    ///
308    /// This is the default parse operation. It is lossless by construction; use
309    /// [`LinkNetwork::projected_links`] when a lower-level view should be
310    /// stripped away for CST, AST, or semantic-only work.
311    #[must_use]
312    pub fn parse(text: &str, language: &str, configuration: ParseConfiguration) -> Self {
313        let mut network = BuiltInLanguageParser.parse_source(text, language, configuration);
314        if text.contains('\0') {
315            let retained = network.reconstruct_text();
316            if text.starts_with(&retained) && retained.len() < text.len() {
317                let span = SourceSpan::new(
318                    ByteRange::new(retained.len(), text.len()),
319                    end_point_for_text(&retained),
320                    end_point_for_text(text),
321                );
322                network.insert_link(
323                    [],
324                    LinkMetadata::new()
325                        .with_link_type(LinkType::Token)
326                        .with_named(false)
327                        .with_term(&text[retained.len()..])
328                        .with_language(language)
329                        .with_span(span)
330                        .with_flags(LinkFlags::error()),
331                );
332            }
333            let malformed_root = network
334                .links()
335                .filter(|link| {
336                    link.metadata().link_type() == Some(LinkType::Syntax)
337                        && link
338                            .metadata()
339                            .span()
340                            .is_some_and(|span| span.byte_range().start() == 0)
341                })
342                .max_by_key(|link| {
343                    link.metadata()
344                        .span()
345                        .map_or(0, |span| span.byte_range().end())
346                })
347                .map(Link::id);
348            if let Some(root) = malformed_root {
349                // NUL is prohibited input: the root reports that it contains an
350                // error without relabeling the grammar's own nodes as ERROR.
351                let flags = network
352                    .link(root)
353                    .map_or_else(LinkFlags::clean, |link| link.metadata().flags());
354                network.set_flags(root, flags.with_containing_error());
355                network.set_span(
356                    root,
357                    SourceSpan::new(
358                        ByteRange::new(0, text.len()),
359                        Point::new(0, 0),
360                        end_point_for_text(text),
361                    ),
362                );
363            }
364        }
365        if let Some(profile) = configuration.profile().and_then(LanguageProfile::builtin) {
366            profile.declare_in(&mut network);
367        }
368        network
369    }
370
371    /// Parses plain source text into a lossless token network.
372    ///
373    /// This parser boundary preserves source spans, trivia links, recovery
374    /// markers, and mixed-region metadata behind the same network
375    /// representation.
376    #[must_use]
377    pub fn parse_lossless_text(
378        text: &str,
379        language: &str,
380        configuration: ParseConfiguration,
381    ) -> Self {
382        let (mut network, document) = Self::new_parse_document(text, language);
383
384        let mut row = 0;
385        let mut column = 0;
386        let mut open_parentheses = Vec::new();
387        for (start, character) in text.char_indices() {
388            let start_point = Point::new(row, column);
389            let end = start + character.len_utf8();
390            if character == '\n' {
391                row += 1;
392                column = 0;
393            } else {
394                // Columns count UTF-8 bytes, as tree-sitter points do.
395                column += character.len_utf8();
396            }
397            let end_point = Point::new(row, column);
398            let span = SourceSpan::new(ByteRange::new(start, end), start_point, end_point);
399            let mut metadata = LinkMetadata::new()
400                .with_link_type(LinkType::Token)
401                .with_named(!character.is_whitespace())
402                .with_term(character.to_string())
403                .with_language(language)
404                .with_span(span);
405
406            if character.is_whitespace() {
407                metadata = metadata.with_flags(LinkFlags::extra());
408            }
409
410            let token = network.insert_link([document], metadata);
411            match character {
412                '(' => open_parentheses.push(token),
413                ')' if open_parentheses.pop().is_none() => {
414                    network.set_flags(token, LinkFlags::error());
415                }
416                _ => {}
417            }
418            if character.is_whitespace() {
419                network.attach_trivia(
420                    document,
421                    token,
422                    span,
423                    configuration.trivia_attachment_policy(),
424                );
425            }
426        }
427
428        let missing_span = SourceSpan::new(
429            ByteRange::new(text.len(), text.len()),
430            end_point_for_text(text),
431            end_point_for_text(text),
432        );
433        for open_parenthesis in open_parentheses {
434            network.set_flags(open_parenthesis, LinkFlags::containing_error());
435            network.insert_link(
436                [document],
437                LinkMetadata::new()
438                    .with_link_type(LinkType::Token)
439                    .with_named(false)
440                    .with_term(")")
441                    .with_language(language)
442                    .with_span(missing_span)
443                    .with_flags(LinkFlags::missing()),
444            );
445        }
446
447        network.attach_embedded_regions(document, text, language, configuration);
448        annotate_natural_language(&mut network, document, text, language, configuration);
449
450        network
451    }
452
453    pub(crate) fn new_parse_document(text: &str, language: &str) -> (Self, LinkId) {
454        let mut network = Self::self_describing();
455        let language_link = network.insert_typed_point(language, LinkType::Language, None);
456        network.record_grammars(language_link, language);
457        let document_span = SourceSpan::new(
458            ByteRange::new(0, text.len()),
459            Point::new(0, 0),
460            end_point_for_text(text),
461        );
462        let document = network.insert_link(
463            [language_link],
464            LinkMetadata::new()
465                .with_link_type(LinkType::Document)
466                .with_named(true)
467                .with_term(format!("{language} document"))
468                .with_language(language)
469                .with_span(document_span),
470        );
471        (network, document)
472    }
473
474    /// Number of links in the network.
475    #[must_use]
476    pub fn len(&self) -> usize {
477        self.links.len()
478    }
479
480    /// Whether the network contains no links.
481    #[must_use]
482    pub fn is_empty(&self) -> bool {
483        self.links.is_empty()
484    }
485
486    /// Number of network handles sharing one immutable link allocation.
487    #[must_use]
488    pub fn shared_link_count(&self, id: LinkId) -> Option<usize> {
489        self.links.get(&id).map(Arc::strong_count)
490    }
491
492    /// Number of internal handles sharing an interned string value.
493    #[must_use]
494    pub fn interned_string_count(&self, value: &str) -> Option<usize> {
495        self.strings.get(value).map(Arc::strong_count)
496    }
497
498    /// Iterates over links in identifier order.
499    pub fn links(&self) -> impl Iterator<Item = &Link> {
500        self.links.values().map(Arc::as_ref)
501    }
502
503    /// Iterates over links included in the selected projection.
504    pub fn projected_links(&self, projection: NetworkProjection) -> impl Iterator<Item = &Link> {
505        self.links().filter(move |link| projection.includes(link))
506    }
507
508    /// Reconstructs source text from non-missing token links ordered by span.
509    #[must_use]
510    pub fn reconstruct_text(&self) -> String {
511        let mut tokens = self
512            .links()
513            .filter(|link| link.metadata().link_type() == Some(LinkType::Token))
514            .filter(|link| !link.metadata().flags().is_missing())
515            .filter_map(|link| {
516                Some((
517                    link.metadata().span()?.byte_range(),
518                    link.id().as_u64(),
519                    link.metadata().term()?.to_string(),
520                ))
521            })
522            .collect::<Vec<_>>();
523
524        tokens.sort_by_key(|(range, id, _term)| (range.start(), *id));
525        let mut reconstructed = String::new();
526        let mut covered_until = 0;
527        for (range, _id, term) in tokens {
528            if range.start() < covered_until {
529                continue;
530            }
531            reconstructed.push_str(&term);
532            covered_until = range.end();
533        }
534        reconstructed
535    }
536
537    /// Returns embedded mixed-language regions discovered during parse.
538    #[must_use]
539    pub fn embedded_regions(&self) -> Vec<EmbeddedRegion> {
540        self.links()
541            .filter(|link| link.metadata().link_type() == Some(LinkType::Region))
542            .filter_map(|link| {
543                Some(EmbeddedRegion::new(
544                    link.metadata().language()?.to_string(),
545                    link.metadata().span()?,
546                ))
547            })
548            .collect()
549    }
550
551    /// Returns links matching a structural query.
552    #[must_use]
553    pub fn query_links(&self, query: &LinkQuery) -> Vec<&Link> {
554        self.query_matches(query)
555            .into_iter()
556            .filter_map(|query_match| self.link(query_match.link_id()))
557            .collect()
558    }
559
560    /// Returns query matches with capture bindings.
561    #[must_use]
562    pub fn query_matches(&self, query: &LinkQuery) -> Vec<QueryMatch> {
563        self.query_matches_with(query, &RejectPredicateHost)
564    }
565
566    /// Returns query matches with host-evaluated predicate support.
567    #[must_use]
568    pub fn query_matches_with(
569        &self,
570        query: &LinkQuery,
571        predicate_host: &impl QueryPredicateHost,
572    ) -> Vec<QueryMatch> {
573        self.links()
574            .flat_map(|link| query.matches_in_network(self, link, predicate_host))
575            .collect()
576    }
577
578    /// Inserts a self-referential point link for a term.
579    pub fn insert_point(&mut self, term: &str) -> LinkId {
580        self.insert_typed_point(term, LinkType::Concept, None)
581    }
582
583    /// Inserts an object-identity point link.
584    pub fn insert_object(&mut self, term: &str) -> LinkId {
585        self.insert_typed_point(term, LinkType::Object, None)
586    }
587
588    /// Inserts a relation link with source span metadata.
589    pub fn insert_relation<const N: usize>(
590        &mut self,
591        references: [LinkId; N],
592        link_type: LinkType,
593        span: SourceSpan,
594    ) -> LinkId {
595        self.insert_link(
596            references,
597            LinkMetadata::new()
598                .with_link_type(link_type)
599                .with_span(span),
600        )
601    }
602
603    /// Inserts a labeled field relation as a regular link.
604    pub fn insert_field(&mut self, parent: LinkId, label: &str, child: LinkId) -> LinkId {
605        let label_link = self.insert_typed_point(
606            label,
607            LinkType::Field,
608            Some("A field label names a relation between links."),
609        );
610        self.insert_link(
611            [parent, label_link, child],
612            LinkMetadata::new().with_link_type(LinkType::Field),
613        )
614    }
615
616    /// Inserts a link from references and metadata.
617    pub fn insert_link<const N: usize>(
618        &mut self,
619        references: [LinkId; N],
620        metadata: LinkMetadata,
621    ) -> LinkId {
622        let id = self.allocate_id();
623        let metadata = self.intern_metadata(metadata);
624        self.links.insert(
625            id,
626            Arc::new(Link {
627                id,
628                references: Arc::from(references.to_vec()),
629                metadata,
630            }),
631        );
632        id
633    }
634
635    /// Reconstructs a concept using a target language syntax mapping.
636    #[must_use]
637    pub fn reconstruct_concept(&self, concept: &str, language: &str) -> Option<&str> {
638        let key = (Arc::<str>::from(concept), Arc::<str>::from(language));
639        self.concept_syntax.get(&key).map(Arc::as_ref)
640    }
641
642    /// Applies a match-and-substitute rule over exact reference lists.
643    pub fn apply_substitution(&mut self, rule: &SubstitutionRule) -> SubstitutionReport {
644        let mut report = SubstitutionReport::default();
645
646        if rule.pattern().is_empty() {
647            if !rule.replacement().is_empty() {
648                let created = self.insert_dynamic_link(
649                    rule.replacement(),
650                    LinkMetadata::new().with_link_type(LinkType::Relation),
651                );
652                report.created.push(created);
653            }
654            return report;
655        }
656
657        let matched = self
658            .links()
659            .filter(|link| link.references() == rule.pattern())
660            .map(Link::id)
661            .collect::<Vec<_>>();
662
663        if rule.replacement().is_empty() {
664            for id in matched {
665                if self.links.remove(&id).is_some() {
666                    report.deleted.push(id);
667                }
668            }
669            return report;
670        }
671
672        for id in matched {
673            if let Some(link) = self.links.get_mut(&id) {
674                Arc::make_mut(link).references = Arc::from(rule.replacement().to_vec());
675                report.updated.push(id);
676            }
677        }
678
679        report
680    }
681
682    /// Applies a match-and-substitute rule with link-cli-style variables.
683    pub fn apply_variable_substitution(
684        &mut self,
685        rule: &VariableSubstitutionRule,
686    ) -> SubstitutionReport {
687        let mut report = SubstitutionReport::default();
688
689        if rule.pattern().is_empty() {
690            let bindings = SubstitutionBindings::default();
691            if let Some(references) = bindings.resolve_values(rule.replacement())
692                && !references.is_empty()
693            {
694                let created = self.insert_dynamic_link(
695                    &references,
696                    LinkMetadata::new().with_link_type(LinkType::Relation),
697                );
698                report.created.push(created);
699                report.bindings.push(bindings);
700            }
701            return report;
702        }
703
704        let matched = self
705            .links()
706            .filter_map(|link| rule.match_link(link).map(|bindings| (link.id(), bindings)))
707            .collect::<Vec<_>>();
708
709        if rule.replacement().is_empty() {
710            for (id, bindings) in matched {
711                if self.links.remove(&id).is_some() {
712                    report.deleted.push(id);
713                    report.bindings.push(bindings);
714                }
715            }
716            return report;
717        }
718
719        for (id, bindings) in matched {
720            let Some(references) = bindings.resolve_values(rule.replacement()) else {
721                continue;
722            };
723            if let Some(link) = self.links.get_mut(&id) {
724                Arc::make_mut(link).references = Arc::from(references);
725                report.updated.push(id);
726                report.bindings.push(bindings);
727            }
728        }
729
730        report
731    }
732
733    /// Returns a link by id.
734    #[must_use]
735    pub fn link(&self, id: LinkId) -> Option<&Link> {
736        self.links.get(&id).map(Arc::as_ref)
737    }
738
739    /// Records the grammars that parse `language` by default as Grammar links
740    /// below its Language link: term `<id>@<version>`, definition
741    /// `sha256:<parser digest>`. Recording the same grammar twice is a no-op.
742    pub(crate) fn record_grammars(&mut self, language_link: LinkId, language: &str) {
743        for grammar in grammar_provenance(language) {
744            let term = format!("{}@{}", grammar.id, grammar.version);
745            let recorded = self.links().any(|link| {
746                link.metadata().link_type() == Some(LinkType::Grammar)
747                    && link.references() == [language_link]
748                    && link.metadata().term() == Some(term.as_str())
749            });
750            if !recorded {
751                self.insert_link(
752                    [language_link],
753                    LinkMetadata::new()
754                        .with_link_type(LinkType::Grammar)
755                        .with_named(true)
756                        .with_term(term)
757                        .with_definition(format!("sha256:{}", grammar.parser_sha256))
758                        .with_language(language),
759                );
760            }
761        }
762    }
763
764    /// Grammars recorded for the languages this network parsed, in insertion
765    /// order, as `(language, grammar)` pairs.
766    #[must_use]
767    pub fn parse_grammars(&self) -> Vec<(String, GrammarProvenance)> {
768        self.links()
769            .filter(|link| link.metadata().link_type() == Some(LinkType::Grammar))
770            .filter_map(|link| {
771                let [language_link] = link.references() else {
772                    return None;
773                };
774                let language = self.link(*language_link)?;
775                if language.metadata().link_type() != Some(LinkType::Language) {
776                    return None;
777                }
778                let (id, version) = link.metadata().term()?.split_once('@')?;
779                let parser_sha256 = link.metadata().definition()?.strip_prefix("sha256:")?;
780                Some((
781                    language.metadata().term()?.to_string(),
782                    GrammarProvenance {
783                        id: id.to_string(),
784                        version: version.to_string(),
785                        parser_sha256: parser_sha256.to_string(),
786                    },
787                ))
788            })
789            .collect()
790    }
791
792    /// Finds a self-description or named term link.
793    #[must_use]
794    pub fn find_term(&self, term: &str) -> Option<LinkId> {
795        self.terms.get(term).copied()
796    }
797
798    /// Finds the definition attached to a term link.
799    #[must_use]
800    pub fn definition_for(&self, id: LinkId) -> Option<&str> {
801        self.link(id).and_then(|link| link.metadata().definition())
802    }
803
804    /// Sets a source span on an existing link.
805    pub fn set_span(&mut self, id: LinkId, span: SourceSpan) -> bool {
806        let Some(link) = self.links.get_mut(&id) else {
807            return false;
808        };
809        Arc::make_mut(link).metadata_mut().span = Some(span);
810        true
811    }
812
813    /// Sets parse flags on an existing link.
814    pub fn set_flags(&mut self, id: LinkId, flags: LinkFlags) -> bool {
815        let Some(link) = self.links.get_mut(&id) else {
816            return false;
817        };
818        Arc::make_mut(link).metadata_mut().flags = flags;
819        true
820    }
821
822    pub(crate) fn set_term(&mut self, id: LinkId, term: impl Into<String>) -> bool {
823        let term = self.intern_arc(Arc::from(term.into()));
824        let Some(link) = self.links.get_mut(&id) else {
825            return false;
826        };
827        Arc::make_mut(link).metadata_mut().term = Some(term);
828        true
829    }
830
831    pub(crate) fn insert_typed_point(
832        &mut self,
833        term: &str,
834        link_type: LinkType,
835        definition: Option<&str>,
836    ) -> LinkId {
837        let definition = definition.map(|definition| self.intern_arc(Arc::from(definition)));
838        if let Some(id) = self.terms.get(term).copied() {
839            if let Some(definition) = definition
840                && let Some(link) = self.links.get_mut(&id)
841            {
842                Arc::make_mut(link).metadata_mut().definition = Some(definition);
843            }
844            return id;
845        }
846
847        let id = self.allocate_id();
848        let term = self.intern_arc(Arc::from(term));
849        let mut metadata = LinkMetadata::new()
850            .with_link_type(link_type)
851            .with_named(true);
852        metadata.term = Some(Arc::clone(&term));
853        if let Some(definition) = definition {
854            metadata.definition = Some(definition);
855        }
856        self.links.insert(
857            id,
858            Arc::new(Link {
859                id,
860                references: Arc::from(vec![id]),
861                metadata,
862            }),
863        );
864        self.terms.insert(term, id);
865        id
866    }
867
868    pub(crate) fn cache_concept_syntax(
869        &mut self,
870        concept: &str,
871        language: &str,
872        syntax: &str,
873        update_reconstruction: bool,
874    ) {
875        let concept = self.intern_arc(Arc::from(concept));
876        let language = self.intern_arc(Arc::from(language));
877        let syntax = self.intern_arc(Arc::from(syntax));
878
879        if update_reconstruction
880            || !self
881                .concept_syntax
882                .contains_key(&(Arc::clone(&concept), Arc::clone(&language)))
883        {
884            self.concept_syntax.insert((concept, language), syntax);
885        }
886    }
887
888    /// Inserts a link whose reference count is known only at run time.
889    ///
890    /// This is the slice-based counterpart to [`Self::insert_link`], whose
891    /// array parameter is convenient when the arity is known at compile time.
892    pub fn insert_dynamic_link(&mut self, references: &[LinkId], metadata: LinkMetadata) -> LinkId {
893        let id = self.allocate_id();
894        let metadata = self.intern_metadata(metadata);
895        let term = metadata.term.clone();
896        self.links.insert(
897            id,
898            Arc::new(Link {
899                id,
900                references: Arc::from(references.to_vec()),
901                metadata,
902            }),
903        );
904        if let Some(term) = term {
905            self.terms.insert(term, id);
906        }
907        id
908    }
909
910    pub(crate) fn set_references(&mut self, id: LinkId, references: &[LinkId]) -> bool {
911        let Some(link) = self.links.get_mut(&id) else {
912            return false;
913        };
914        Arc::make_mut(link).references = Arc::from(references.to_vec());
915        true
916    }
917
918    pub(crate) fn attach_embedded_regions(
919        &mut self,
920        document: LinkId,
921        text: &str,
922        language: &str,
923        configuration: ParseConfiguration,
924    ) {
925        embedded_region_parser::attach_embedded_regions(
926            self,
927            document,
928            text,
929            language,
930            configuration,
931        );
932    }
933
934    fn intern_metadata(&mut self, mut metadata: LinkMetadata) -> LinkMetadata {
935        metadata.term = metadata.term.map(|value| self.intern_arc(value));
936        metadata.definition = metadata.definition.map(|value| self.intern_arc(value));
937        metadata.language = metadata.language.map(|value| self.intern_arc(value));
938        metadata
939    }
940
941    fn intern_arc(&mut self, value: Arc<str>) -> Arc<str> {
942        if let Some(interned) = self.strings.get(value.as_ref()) {
943            return Arc::clone(interned);
944        }
945
946        self.strings.insert(Arc::clone(&value));
947        value
948    }
949
950    const fn allocate_id(&mut self) -> LinkId {
951        let id = LinkId(self.next_id);
952        self.next_id += 1;
953        id
954    }
955}
956
957fn end_point_for_text(text: &str) -> Point {
958    let mut row = 0;
959    let mut column = 0;
960    for character in text.chars() {
961        if character == '\n' {
962            row += 1;
963            column = 0;
964        } else {
965            column += character.len_utf8();
966        }
967    }
968    Point::new(row, column)
969}