Skip to main content

lady_deirdre/analysis/
entry.rs

1////////////////////////////////////////////////////////////////////////////////
2// This file is part of "Lady Deirdre", a compiler front-end foundation       //
3// technology.                                                                //
4//                                                                            //
5// This work is proprietary software with source-available code.              //
6//                                                                            //
7// To copy, use, distribute, or contribute to this work, you must agree to    //
8// the terms of the General License Agreement:                                //
9//                                                                            //
10// https://github.com/Eliah-Lakhin/lady-deirdre/blob/master/EULA.md           //
11//                                                                            //
12// The agreement grants a Basic Commercial License, allowing you to use       //
13// this work in non-commercial and limited commercial products with a total   //
14// gross revenue cap. To remove this commercial limit for one of your         //
15// products, you must acquire a Full Commercial License.                      //
16//                                                                            //
17// If you contribute to the source code, documentation, or related materials, //
18// you must grant me an exclusive license to these contributions.             //
19// Contributions are governed by the "Contributions" section of the General   //
20// License Agreement.                                                         //
21//                                                                            //
22// Copying the work in parts is strictly forbidden, except as permitted       //
23// under the General License Agreement.                                       //
24//                                                                            //
25// If you do not or cannot agree to the terms of this Agreement,              //
26// do not use this work.                                                      //
27//                                                                            //
28// This work is provided "as is", without any warranties, express or implied, //
29// except where such disclaimers are legally invalid.                         //
30//                                                                            //
31// Copyright (c) 2024 Ilya Lakhin (Илья Александрович Лахин).                 //
32// All rights reserved.                                                       //
33////////////////////////////////////////////////////////////////////////////////
34
35use std::{
36    collections::{hash_map, HashMap, HashSet},
37    hash::RandomState,
38    ops::{Deref, DerefMut},
39    sync::{Arc, Weak},
40};
41
42use crate::{
43    analysis::{
44        database::DocRecords,
45        AnalysisError,
46        AnalysisResult,
47        Analyzer,
48        Classifier,
49        Feature,
50        Grammar,
51        Initializer,
52        Invalidator,
53        Revision,
54        ScopeAttr,
55        TaskHandle,
56    },
57    arena::{Entry, Id, Identifiable},
58    lexis::ToSpan,
59    report::ld_unreachable,
60    sync::{Shared, SyncBuildHasher, TableReadGuard},
61    syntax::{ErrorRef, NodeRef, PolyRef, SyntaxTree},
62    units::{Document, Watcher},
63};
64
65/// A type of the [Analyzer]-wide event.
66///
67/// The values lesser than [CUSTOM_EVENT_START_RANGE] are reserved by the crate.
68/// Other values are custom user-defined events.
69pub type Event = u16;
70
71/// A built-in [Analyzer] event indicating the the document has been added
72/// to the Analyzer.
73pub const DOC_ADDED_EVENT: Event = 1;
74
75/// A built-in [Analyzer] event indicating that the document has been
76/// [removed](crate::analysis::MutationAccess::remove_doc) from the Analyzer.
77pub const DOC_REMOVED_EVENT: Event = 2;
78
79/// A built-in [Analyzer] event indicating that the document's content has been
80/// [edited](crate::analysis::MutationAccess::write_to_doc).
81pub const DOC_UPDATED_EVENT: Event = 3;
82
83/// A built-in [Analyzer] event indicating that
84/// the [syntax error](crate::syntax::SyntaxError) has occurred or been removed
85/// from the [syntax tree](SyntaxTree) of the Analyzer's document.
86pub const DOC_ERRORS_EVENT: Event = 4;
87
88/// A start of the custom user-defined [events](Event) range.
89pub const CUSTOM_EVENT_START_RANGE: Event = 0x100;
90
91/// A RAII guard that provides read-only access to the [Analyzer]'s document.
92///
93/// The underlying document can be accessed through the [Deref] implementation
94/// of this object.
95///
96/// The document is locked for read until the last remaining DocumentReadGuard
97/// is dropped. If a task attempts
98/// to [write](crate::analysis::MutationAccess::write_to_doc) into the document
99/// while the document is locked for read, the writer thread will be blocked.
100///
101/// Note the Analyzer allows reading document's
102/// [attributes](crate::analysis::Attr) while document is locked for read.
103///
104/// Also, the Analyzer allows parallel writing to independent documents
105/// without blocking if these documents are not locked for read.
106#[repr(transparent)]
107pub struct DocumentReadGuard<'a, N: Grammar, S: SyncBuildHasher = RandomState> {
108    guard: TableReadGuard<'a, Id, DocEntry<N, S>, S>,
109}
110
111impl<'a, N: Grammar, S: SyncBuildHasher> Deref for DocumentReadGuard<'a, N, S> {
112    type Target = Document<N>;
113
114    #[inline(always)]
115    fn deref(&self) -> &Self::Target {
116        &self.guard.deref().doc
117    }
118}
119
120impl<'a, N: Grammar, S: SyncBuildHasher> From<TableReadGuard<'a, Id, DocEntry<N, S>, S>>
121    for DocumentReadGuard<'a, N, S>
122{
123    #[inline(always)]
124    fn from(guard: TableReadGuard<'a, Id, DocEntry<N, S>, S>) -> Self {
125        Self { guard }
126    }
127}
128
129pub(super) struct DocEntry<N: Grammar, S: SyncBuildHasher> {
130    pub(super) doc: Document<N>,
131    pub(super) classes_to_nodes: HashMap<<N::Classifier as Classifier>::Class, ClassToNodes<S>, S>,
132    pub(super) nodes_to_classes: HashMap<Entry, NodeToClasses<N, S>, S>,
133}
134
135pub(super) struct ClassToNodes<S> {
136    pub(super) nodes: Shared<HashSet<NodeRef, S>>,
137    pub(super) revision: Revision,
138}
139
140pub(super) struct NodeToClasses<N: Grammar, S> {
141    pub(super) classes: HashSet<<N::Classifier as Classifier>::Class, S>,
142}
143
144impl<N: Grammar, H: TaskHandle, S: SyncBuildHasher> Analyzer<N, H, S> {
145    pub(super) fn register_doc(&self, mut doc: Document<N>) -> Id {
146        let id = doc.id();
147
148        let node_refs = doc.node_refs().collect::<Vec<_>>();
149        let mut records = DocRecords::with_capacity(node_refs.len());
150        let mut classes_to_nodes =
151            HashMap::<<N::Classifier as Classifier>::Class, ClassToNodes<S>, S>::default();
152        let mut nodes_to_classes = HashMap::<Entry, NodeToClasses<N, S>, S>::default();
153
154        let revision = self.db.commit_revision();
155
156        if !node_refs.is_empty() {
157            let mut initializer = Initializer {
158                id,
159                database: Arc::downgrade(&self.db) as Weak<_>,
160                records: &mut records,
161                inserts: false,
162            };
163
164            for node_ref in &node_refs {
165                let Some(node) = node_ref.deref_mut(&mut doc) else {
166                    continue;
167                };
168
169                node.init(&mut initializer);
170            }
171
172            for node_ref in node_refs {
173                let classes = <N::Classifier as Classifier>::classify(&doc, &node_ref);
174
175                if classes.is_empty() {
176                    continue;
177                }
178
179                for class in &classes {
180                    match classes_to_nodes.entry(class.clone()) {
181                        hash_map::Entry::Occupied(mut entry) => {
182                            let Some(nodes) = entry.get_mut().nodes.get_mut() else {
183                                // Shared is localized within this function during initialization.
184                                unsafe {
185                                    ld_unreachable!("Class nodes are shared during initialization")
186                                }
187                            };
188
189                            let _ = nodes.insert(node_ref);
190                        }
191
192                        hash_map::Entry::Vacant(entry) => {
193                            let mut nodes = HashSet::default();
194
195                            let _ = nodes.insert(node_ref);
196
197                            let _ = entry.insert(ClassToNodes {
198                                nodes: Shared::new(nodes),
199                                revision,
200                            });
201                        }
202                    }
203                }
204
205                let _ = nodes_to_classes.insert(node_ref.entry, NodeToClasses { classes });
206            }
207        }
208
209        let _ = self.docs.insert(
210            id,
211            DocEntry {
212                doc,
213                classes_to_nodes,
214                nodes_to_classes,
215            },
216        );
217
218        let _ = self.db.records.insert(id, records);
219
220        self.trigger_event(id, DOC_ADDED_EVENT, revision);
221
222        id
223    }
224
225    pub(super) fn write_to_doc(
226        &self,
227        handle: &H,
228        id: Id,
229        span: impl ToSpan,
230        text: impl AsRef<str>,
231    ) -> AnalysisResult<()> {
232        #[derive(Default)]
233        struct DocWatcher<S> {
234            node_refs: HashSet<NodeRef, S>,
235            errors_signal: bool,
236        }
237
238        impl<S: SyncBuildHasher> Watcher for DocWatcher<S> {
239            #[inline(always)]
240            fn report_node(&mut self, node_ref: &NodeRef) {
241                let _ = self.node_refs.insert(*node_ref);
242            }
243
244            #[inline(always)]
245            fn report_error(&mut self, _error_ref: &ErrorRef) {
246                self.errors_signal = true
247            }
248        }
249        let Some(mut guard) = self.docs.get_mut(&id) else {
250            return Err(AnalysisError::MissingDocument);
251        };
252
253        let DocEntry {
254            doc,
255            classes_to_nodes,
256            nodes_to_classes,
257        } = guard.deref_mut();
258
259        let Document::Mutable(unit) = doc else {
260            return Err(AnalysisError::ImmutableDocument);
261        };
262
263        let Some(span) = span.to_site_span(unit) else {
264            return Err(AnalysisError::InvalidSpan);
265        };
266
267        let mut report = DocWatcher::<S>::default();
268
269        unit.write_and_watch(span, text, &mut report);
270
271        if report.node_refs.is_empty() && !report.errors_signal {
272            return Ok(());
273        }
274
275        let revision = self.db.commit_revision();
276
277        self.trigger_event(id, DOC_UPDATED_EVENT, revision);
278
279        if report.errors_signal {
280            self.trigger_event(id, DOC_ERRORS_EVENT, revision);
281        }
282
283        let Some(mut records) = self.db.records.get_mut(&id) else {
284            // Safety:
285            //   1. Records are always in sync with documents.
286            //   2. Document is locked.
287            unsafe { ld_unreachable!("Missing database entry.") }
288        };
289
290        let mut initializer = Initializer {
291            id,
292            database: Arc::downgrade(&self.db) as Weak<_>,
293            records: records.deref_mut(),
294            inserts: false,
295        };
296
297        for node_ref in &report.node_refs {
298            let Some(node) = node_ref.deref_mut(doc) else {
299                let Some(node_to_classes) = nodes_to_classes.remove(&node_ref.entry) else {
300                    continue;
301                };
302
303                for class in node_to_classes.classes {
304                    let Some(class_to_nodes) = classes_to_nodes.get_mut(&class) else {
305                        // Safety
306                        //   1. Nodes and classes are always in sync.
307                        //   2. Both collections locked.
308                        unsafe {
309                            ld_unreachable!("Nodes and classes resynchronization.");
310                        }
311                    };
312
313                    class_to_nodes.revision = class_to_nodes.revision.max(revision);
314
315                    let nodes = class_to_nodes.nodes.make_mut();
316
317                    if !nodes.remove(node_ref) {
318                        // Safety
319                        //   1. Nodes and classes are always in sync.
320                        //   2. Both collections locked.
321                        unsafe {
322                            ld_unreachable!("Nodes and classes resynchronization.");
323                        }
324                    }
325                }
326
327                continue;
328            };
329
330            node.init(&mut initializer);
331        }
332
333        let mut invalidator = Invalidator {
334            id,
335            records: &mut records.attrs,
336        };
337
338        for node_ref in &report.node_refs {
339            let Some(node) = node_ref.deref(doc) else {
340                continue;
341            };
342
343            let scope_attr = node.scope_attr()?;
344
345            scope_attr.invalidate(&mut invalidator);
346
347            if nodes_to_classes.contains_key(&node_ref.entry) {
348                continue;
349            }
350
351            let classes = <N as Grammar>::Classifier::classify(doc, node_ref);
352
353            for class in &classes {
354                let Some(class_to_nodes) = classes_to_nodes.get_mut(class) else {
355                    let mut nodes = HashSet::default();
356
357                    if !nodes.insert(*node_ref) {
358                        // Safety: `nodes` is a fresh new collection.
359                        unsafe {
360                            ld_unreachable!("Duplicate entry.");
361                        }
362                    }
363
364                    let previous = classes_to_nodes.insert(
365                        class.clone(),
366                        ClassToNodes {
367                            nodes: Shared::new(nodes),
368                            revision,
369                        },
370                    );
371
372                    if previous.is_some() {
373                        // Safety: Existence checked above.
374                        unsafe {
375                            ld_unreachable!("Duplicate entry.");
376                        }
377                    }
378
379                    continue;
380                };
381
382                let nodes = class_to_nodes.nodes.make_mut();
383
384                if !nodes.insert(*node_ref) {
385                    // Safety
386                    //   1. Nodes and classes are always in sync.
387                    //   2. Both collections locked.
388                    unsafe {
389                        ld_unreachable!("Nodes and classes resynchronization.");
390                    }
391                }
392
393                class_to_nodes.revision = class_to_nodes.revision.max(revision);
394            }
395
396            if nodes_to_classes
397                .insert(node_ref.entry, NodeToClasses { classes })
398                .is_some()
399            {
400                // Safety: Existence checked above.
401                unsafe {
402                    ld_unreachable!("Duplicate entry.");
403                }
404            }
405        }
406
407        let mut scope_accumulator = HashSet::<NodeRef, S>::default();
408
409        for node_ref in &report.node_refs {
410            let Some(node) = node_ref.deref(doc) else {
411                continue;
412            };
413
414            let scope_attr = node.scope_attr()?;
415            let scope_attr_ref = scope_attr.as_ref();
416
417            // Safety: `scope_attr_ref` belongs to `scope_attr`.
418            let scope_ref = unsafe {
419                ScopeAttr::snapshot_manually(scope_attr_ref, handle, doc, &records.attrs, revision)?
420            };
421
422            if !scope_ref.is_nil() {
423                scope_accumulator.insert(scope_ref);
424            }
425        }
426
427        if !scope_accumulator.is_empty() {
428            let mut invalidator = Invalidator {
429                id,
430                records: &mut records.attrs,
431            };
432
433            for scope_ref in scope_accumulator {
434                let Some(node) = scope_ref.deref(doc) else {
435                    continue;
436                };
437
438                node.invalidate(&mut invalidator);
439            }
440        }
441
442        Ok(())
443    }
444
445    pub(super) fn remove_doc(&self, id: Id) -> bool {
446        if self.docs.remove(&id).is_none() {
447            return false;
448        }
449
450        if self.db.records.remove(&id).is_none() {
451            // Safety: records are always in sync with documents.
452            unsafe { ld_unreachable!("Missing database entry.") }
453        }
454
455        let revision = self.db.commit_revision();
456
457        self.trigger_event(id, DOC_REMOVED_EVENT, revision);
458
459        true
460    }
461
462    pub(super) fn trigger_event(&self, id: Id, event: Event, revision: Revision) {
463        {
464            let mut guard = self.events.entry(Id::nil()).or_default();
465
466            let event_revision = guard.entry(event).or_default();
467
468            *event_revision = revision.max(*event_revision);
469        }
470
471        if !id.is_nil() {
472            let mut guard = self.events.entry(id).or_default();
473
474            let event_revision = guard.entry(event).or_default();
475
476            *event_revision = revision.max(*event_revision);
477        }
478    }
479}