Skip to main content

lady_deirdre/syntax/
session.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::{marker::PhantomData, mem::replace};
36
37use crate::{
38    arena::{Entry, EntryIndex, Id, Identifiable},
39    lexis::{Length, Site, SiteRef, TokenCount, TokenCursor, TokenRef},
40    report::ld_unreachable,
41    syntax::{ErrorRef, Node, NodeRef, NodeRule, SyntaxError},
42};
43
44/// A communication channel of the syntax tree parsing process.
45///
46/// Lady Deirdre distinguishes two independent sides of the syntax parsing process:
47///
48/// - The parsing environment side (e.g., [Document](crate::units::Document))
49///   that manages the parsing input (tokens), and manages the parsing output
50///   (syntax tree).
51///
52/// - The parsing algorithm implementation side (via the [Node::parse] function).
53///
54/// Both sides unaware of each other, and they use SyntaxSession
55/// for intercommunication.
56///
57///  1. Whenever the parsing environment side wants to parse a particular node
58///     of the syntax tree, it gives control flow to the parser by providing
59///     it's own implementation of the SyntaxSession to the [Node::parse]
60///     function.
61///
62///  2. The parse function reads particular tokens of the token input stream
63///     and advances the token cursor using SyntaxSession's functions.
64///
65///  3. If the parse algorithm needs to further descend into the syntax tree,
66///     it calls [SyntaxSession::descend] function returning control flow back
67///     to the parsing environment side.
68///
69///  4. The parsing environment side decides whether to give control flow to
70///     the Node's parse function again, continuing recursive descending,
71///     or returning a result from its own cache.
72///
73/// Since both sides of the parsing process are isolated from each other,
74/// it opens up a lot of implementation configurations.
75///
76/// ## As the user of the SyntaxSession instances, you can
77///
78///   - Implement a custom parser that inherently suit the needs
79///     of both one-time parsing of the entire file and the incremental
80///     reparsing process.
81///
82///   - Adopt a 3rd party parsing library to Lady Deirdre by adopting
83///     its interface to the SyntaxSession interface.
84///
85///   - Implement a parsing algorithm of almost any programming
86///     language as long as its grammar does not surpass a class of
87///     the context free grammars with unlimited lookahead.
88///
89/// In particular, the [Node derive macro](lady_deirdre_derive::Node)
90/// implements Node's parse function for LL(1) grammars, but you are free to
91/// implement your own parsers and alternative recursive-descending parsing
92/// libraries utilizing the SyntaxSession interface.
93///
94/// ## As the author of the SyntaxSession implementations
95///
96/// You have control over arbitrary programming language parser steps
97/// without the need to know the details of the language's grammar.
98///
99/// Therefore, you can implement a wide set of the parsing environments with
100/// different capabilities.
101///
102/// In particular, Lady Deirdre provides the following parsing environments
103/// through custom implementations of the SyntaxSession trait:
104///
105///  - The immutable [Document](crate::units::Document) and
106///    the [ImmutableSyntaxTree](crate::syntax::ImmutableSyntaxTree) objects
107///    provide one-time parsing capabilities, always returning control-flow
108///    back to the Node's parse function when the function is trying to descend.
109///    The performance characteristics of this approach are close to ordinary
110///    non-incremental parsers.
111///
112///  - The mutable Document and the [MutableUnit](crate::units::MutableUnit)
113///    have their own reusable caches of the syntax tree nodes that they utilize
114///    during incremental reparsing by returning previously parsed node cache
115///    whenever possible when the Node's parse function is trying to descend.
116///
117///  - [Node::debug] function under the hood uses special implementation of the
118///    SyntaxSession trait that does not store any nodes in the syntax tree
119///    but instead just prints parser's actions to the terminal for debugging
120///    purposes.
121///
122///  - [ParseTree](crate::syntax::ParseTree) tracks parser's interactions
123///    with the SyntaxSession to reconstruct concrete parsing trees.
124///
125/// ## Parsing algorithm considerations
126///
127/// When implementing the [Node::parse] function, several considerations should
128/// be taken into account:
129///
130///  1. The parsing algorithm should handle a **context-free grammar**.
131///     Each rule of the grammar should be parseable from any **arbitrary**
132///     token sequence, and the algorithm should not rely on parsing rules
133///     applied previously.
134///
135///  2. The [Node::parse] function must be infallible regardless of the input
136///     token sequence or the `rule` parameter. If the token sequence provided
137///     to the parser by the SyntaxSession does not fit the parsing rule at any
138///     step (including the first step) of the rule’s algorithm, **the algorithm
139///     must attempt to recover from syntax errors** by skipping or ignoring
140///     parts of the input tokens and by reporting syntax errors using the
141///     [SyntaxSession::failure] function.
142///
143///  3. In the end, the [Node::parse] function must advance the SyntaxSession
144///     token cursor by consuming **at least one token** from the input
145///     sequence, and it must return an instance of the [Node] that corresponds
146///     to the requested rule.
147///
148/// ## Parse rules
149///
150/// Both [Node::parse] and the [SyntaxSession::descend] functions have a
151/// `rule` parameter of the [NodeRule] type, which is an arbitrary number used
152/// to distinguish between concrete parsing rules.
153///
154/// In the context of parsing rules:
155///
156///  1. Rule `0` ([ROOT_RULE](crate::syntax::ROOT_RULE)), denoting the root node
157///     of the syntax tree, should be used only once per the entire parsing
158///     process, as there can only be one root in the syntax tree.
159///     Therefore, the [Node::parse] function itself **should never call**
160///     [descend](SyntaxSession::descend) or [enter](SyntaxSession::enter)
161///     functions with the rule `0`.
162///
163///  2. Rule `u16::MAX` ([NON_RULE](crate::syntax::NON_RULE)) is a reserved rule
164///     that denotes an invalid rule, indicating a rule that does not parse
165///     anything. Therefore, it should never be supplied to either
166///     the [Node::parse] function or any of the SyntaxSession functions that
167///     accept a NodeRule type.
168///
169///  3. Any other rule number in a range of `1..u16::MAX` is a valid rule
170///     uniquely denotes a parsing rule of a particular syntax tree node.
171///
172///  4. The SyntaxSession trait is unaware of the mapping between these numbers
173///     and the node types upfront. They are determined by each implementer of
174///     the programming language parsers. However, the SyntaxSession can rely on
175///     the fact that **passing the same rule number to the [Node::parse]
176///     function would produce a node of the same type**.
177///
178/// ## Cache control
179///
180/// The parser algorithm typically descends into the sub-rules of the currently
181/// parsed rule by calling the [SyntaxSession::descend] function, which gives
182/// control flow to the SyntaxSession and leaves the decision about the node’s
183/// caching to the SyntaxSession implementation.
184///
185/// The nodes computed this way are called _primary nodes_.
186///
187/// Alternatively, the algorithm could compute a sub-rule in place, without
188/// returning control flow back to the SyntaxSession.
189///
190/// These nodes are called _secondary nodes_. As the secondary nodes
191/// are computed in places, they cannot be cached by the SyntaxSession.
192///
193/// Whenever the parser starts in-place parsing of the secondary node it should
194/// call the [SyntaxSession::enter] function specifying the node's parsing rule.
195///
196/// When the the parsing finishes, the parser should call the
197/// [SyntaxSession::leave] function specifying an instance of the [Node] as
198/// a product of this sub-rule.
199///
200/// ## Nodes and rules nesting
201///
202/// The SyntaxSession tracks rules and their products ([Nodes](Node)) nesting.
203///
204/// It is the parser's implementor responsibility to balance the
205/// [enter](SyntaxSession::enter) and [leave](SyntaxSession::leave) functions
206/// properly when parsing the secondary nodes in place, always leaving the nodes
207/// entered before.
208///
209/// When the algorithm uses the [descend](SyntaxSession::descend) function,
210/// the nesting process is controlled by the SyntaxSession implementation.
211///
212/// [SyntaxSession::node_ref] returns a [NodeRef] of the node currently being
213/// parsed. Using this function, you can fetch the node's reference while it is
214/// being parsed.
215///
216/// [SyntaxSession::parent_ref] returns a NodeRef reference of the parent rule's
217/// node.
218///
219/// The parsing algorithm cannot access either of the syntax tree node
220/// instances during the parsing process, but it can use these NodeRef
221/// references to set up the [parent_ref](crate::syntax::AbstractNode::parent_ref)
222/// and the [node_ref](crate::syntax::AbstractNode::node_ref) values of
223/// the resulting node instance.
224///
225/// These values of the Node instance establish back references from the child
226/// nodes to their parents and are useful for the resulting syntax tree
227/// ascending traverse.
228///
229/// ## Left recursion
230///
231/// To handle left recursion, the parser could utilize either lookahead
232/// capabilities of the SyntaxSession or to use the node lifting feature.
233///
234/// For example, to parse infix expressions such as `a + b`, `a * b`, or
235/// just `a`, where the binary operator is not known upfront or could absent,
236/// you can descend into the operand parsing rule first, receiving its
237/// [NodeRef] reference.
238///
239/// Then, if the parser encounters an operator token, you can
240/// [enter](SyntaxSession::enter) the corresponding binary operation rule and
241/// immediately call the [lift](SyntaxSession::lift) function, providing
242/// the operand's NodeRef. Then, parse the rest of the expression normally.
243///
244/// The lift function would "transplant" the operand's node parsed outside of
245/// the operator's rule to the context of the operator's rule, rearranging
246/// operand's node nesting.
247///
248/// ## Tokens access
249///
250/// The SyntaxSession trait is a super-trait of the [TokenCursor] trait.
251///
252/// Functions of the TokenCursor grant access to the current state of the token
253/// sequence being parsed.
254///
255/// The [TokenCursor::advance] and the [TokenCursor::skip] functions advance
256/// the SyntaxSession parsing cursor, consuming corresponding tokens.
257///
258/// The access functions of the TokenCursor grant potentially unlimited
259/// lookahead capabilities.
260///
261/// The SyntaxSession could track the lookahead distance used by the parsing
262/// algorithm. The lookahead distance could limit the underlying parsing
263/// environment caching capabilities.
264///
265/// Therefore, the parser's implementor **should prefer to limit the lookahead**
266/// whenever possible.
267///
268/// ## Safety and Panic
269///
270/// The SyntaxSession trait and all of it's functions **are safe**.
271///
272/// Violations of the above specification **are not** undefined behavior, but
273/// the failure to follow the specified contract could lead to bugs and panics
274/// depending on the implementation.
275pub trait SyntaxSession<'code>: TokenCursor<'code, Token = <Self::Node as Node>::Token> {
276    /// Specifies a type of the Node that is currently being parsed.
277    type Node: Node;
278
279    /// Instructs the parsing environment to parse the parsing rule denoted by
280    /// the `rule` parameter starting from the current token.
281    ///
282    /// The valid values of the `rule` are any values within the [NodeRule]
283    /// range except the [ROOT_RULE](crate::syntax::ROOT_RULE) and
284    /// the [NON_RULE](crate::syntax::NON_RULE).
285    ///
286    /// Returns a [NodeRef] reference of the node inside the
287    /// [SyntaxTree](crate::syntax::SyntaxTree) that would be parsed by this
288    /// rule and consumes the sequence of tokens required to apply the rule.
289    fn descend(&mut self, rule: NodeRule) -> NodeRef;
290
291    /// Begins parsing of the parsing rule denoted by the `rule` parameter
292    /// from the current token.
293    ///
294    /// The valid values of the `rule` are any values within the [NodeRule]
295    /// range except the [ROOT_RULE](crate::syntax::ROOT_RULE) and
296    /// the [NON_RULE](crate::syntax::NON_RULE).
297    ///
298    /// Returns a [NodeRef] reference of the node inside the
299    /// [SyntaxTree](crate::syntax::SyntaxTree) that will be placed to
300    /// the syntax tree when the rule parsing finishes.
301    ///
302    /// Each enter function must be paired with
303    /// the [leave](SyntaxSession::leave) function that finises the rule.
304    ///
305    /// After entering into the rule parser, the parsing algorithm must consume
306    /// at least one token directly or indirectly by entering another sub-rule.
307    fn enter(&mut self, rule: NodeRule) -> NodeRef;
308
309    /// Completes parsing of the rule started previously by
310    /// the [enter](SyntaxSession::enter) function.
311    ///
312    /// The `node` parameter specifies rule's parsing result.
313    ///
314    /// Returns a [NodeRef] reference of the `node` inside
315    /// the [SyntaxTree](crate::syntax::SyntaxTree).
316    fn leave(&mut self, node: Self::Node) -> NodeRef;
317
318    /// Reinterprets the previously parsed sibling node of the current node
319    /// as the current node's child.
320    ///
321    /// See the [Left recursion](SyntaxSession#left-recursion) section of
322    /// the parsing process specification for details.
323    fn lift(&mut self, node_ref: &NodeRef);
324
325    /// Returns the [NodeRef] reference of the node inside
326    /// the [SyntaxTree](crate::syntax::SyntaxTree) being parsed by the current
327    /// parsing rule.
328    fn node_ref(&self) -> NodeRef;
329
330    /// Returns the [NodeRef] reference of the node inside
331    /// the [SyntaxTree](crate::syntax::SyntaxTree) being parsed by the parental
332    /// parsing rule.
333    ///
334    /// If the current rule is the root, this function returns [NodeRef::nil].
335    fn parent_ref(&self) -> NodeRef;
336
337    /// Reports a syntax error occur during the syntax recovery.
338    ///
339    /// Returns an [ErrorRef] reference of the error object inside
340    /// the [SyntaxTree](crate::syntax::SyntaxTree).
341    ///
342    /// The SyntaxSession implementation may decide to ignore the provided error
343    /// object. In this case, the failure function returns [ErrorRef::nil].
344    fn failure(&mut self, error: SyntaxError) -> ErrorRef;
345}
346
347pub(super) struct ImmutableSyntaxSession<
348    'code,
349    N: Node,
350    C: TokenCursor<'code, Token = <N as Node>::Token>,
351> {
352    pub(super) id: Id,
353    pub(super) context: Vec<EntryIndex>,
354    pub(super) nodes: Vec<Option<N>>,
355    pub(super) errors: Vec<SyntaxError>,
356    pub(super) failing: bool,
357    pub(super) token_cursor: C,
358    pub(super) _phantom: PhantomData<&'code ()>,
359}
360
361impl<'code, N, C> Identifiable for ImmutableSyntaxSession<'code, N, C>
362where
363    N: Node,
364    C: TokenCursor<'code, Token = <N as Node>::Token>,
365{
366    #[inline(always)]
367    fn id(&self) -> Id {
368        self.id
369    }
370}
371
372impl<'code, N, C> TokenCursor<'code> for ImmutableSyntaxSession<'code, N, C>
373where
374    N: Node,
375    C: TokenCursor<'code, Token = <N as Node>::Token>,
376{
377    type Token = <N as Node>::Token;
378
379    #[inline(always)]
380    fn advance(&mut self) -> bool {
381        let advanced = self.token_cursor.advance();
382
383        self.failing = self.failing && !advanced;
384
385        advanced
386    }
387
388    #[inline(always)]
389    fn skip(&mut self, distance: TokenCount) {
390        let start = self.token_cursor.site(0);
391
392        self.token_cursor.skip(distance);
393
394        self.failing = self.failing && start == self.token_cursor.site(0);
395    }
396
397    #[inline(always)]
398    fn token(&mut self, distance: TokenCount) -> Self::Token {
399        self.token_cursor.token(distance)
400    }
401
402    #[inline(always)]
403    fn site(&mut self, distance: TokenCount) -> Option<Site> {
404        self.token_cursor.site(distance)
405    }
406
407    #[inline(always)]
408    fn length(&mut self, distance: TokenCount) -> Option<Length> {
409        self.token_cursor.length(distance)
410    }
411
412    #[inline(always)]
413    fn string(&mut self, distance: TokenCount) -> Option<&'code str> {
414        self.token_cursor.string(distance)
415    }
416
417    #[inline(always)]
418    fn token_ref(&mut self, distance: TokenCount) -> TokenRef {
419        self.token_cursor.token_ref(distance)
420    }
421
422    #[inline(always)]
423    fn site_ref(&mut self, distance: TokenCount) -> SiteRef {
424        self.token_cursor.site_ref(distance)
425    }
426
427    #[inline(always)]
428    fn end_site_ref(&mut self) -> SiteRef {
429        self.token_cursor.end_site_ref()
430    }
431}
432
433impl<'code, N, C> SyntaxSession<'code> for ImmutableSyntaxSession<'code, N, C>
434where
435    N: Node,
436    C: TokenCursor<'code, Token = <N as Node>::Token>,
437{
438    type Node = N;
439
440    fn descend(&mut self, rule: NodeRule) -> NodeRef {
441        let _ = self.enter(rule);
442
443        let node = N::parse(self, rule);
444
445        self.leave(node)
446    }
447
448    #[inline]
449    fn enter(&mut self, _rule: NodeRule) -> NodeRef {
450        let index = self.nodes.len();
451
452        self.nodes.push(None);
453
454        self.context.push(index);
455
456        NodeRef {
457            id: self.id,
458            entry: Entry { index, version: 0 },
459        }
460    }
461
462    #[inline]
463    fn leave(&mut self, node: Self::Node) -> NodeRef {
464        let Some(index) = self.context.pop() else {
465            #[cfg(debug_assertions)]
466            {
467                panic!("Nesting imbalance.");
468            }
469
470            #[cfg(not(debug_assertions))]
471            {
472                return NodeRef::nil();
473            }
474        };
475
476        let Some(item) = self.nodes.get_mut(index) else {
477            unsafe { ld_unreachable!("Bad context index.") }
478        };
479
480        if replace(item, Some(node)).is_some() {
481            unsafe { ld_unreachable!("Bad context index.") }
482        }
483
484        NodeRef {
485            id: self.id,
486            entry: Entry { index, version: 0 },
487        }
488    }
489
490    #[inline]
491    fn lift(&mut self, node_ref: &NodeRef) {
492        if self.id != node_ref.id {
493            #[cfg(debug_assertions)]
494            {
495                panic!("Cannot lift a node that does not belong to this compilation session.");
496            }
497
498            #[cfg(not(debug_assertions))]
499            {
500                return;
501            }
502        }
503
504        let parent_ref = self.node_ref();
505
506        let Some(Some(node)) = self.nodes.get_mut(node_ref.entry.index) else {
507            #[cfg(debug_assertions)]
508            {
509                panic!("Cannot lift a node that does not belong to this compilation session.");
510            }
511
512            #[cfg(not(debug_assertions))]
513            {
514                return;
515            }
516        };
517
518        node.set_parent_ref(parent_ref);
519    }
520
521    #[inline(always)]
522    fn node_ref(&self) -> NodeRef {
523        let Some(index) = self.context.last() else {
524            #[cfg(debug_assertions)]
525            {
526                panic!("Nesting imbalance.");
527            }
528
529            #[cfg(not(debug_assertions))]
530            {
531                return NodeRef::nil();
532            }
533        };
534
535        NodeRef {
536            id: self.id,
537            entry: Entry {
538                index: *index,
539                version: 0,
540            },
541        }
542    }
543
544    #[inline(always)]
545    fn parent_ref(&self) -> NodeRef {
546        let Some(depth) = self.context.len().checked_sub(2) else {
547            return NodeRef::nil();
548        };
549
550        let index = *unsafe { self.context.get_unchecked(depth) };
551
552        NodeRef {
553            id: self.id,
554            entry: Entry { index, version: 0 },
555        }
556    }
557
558    #[inline(always)]
559    fn failure(&mut self, error: SyntaxError) -> ErrorRef {
560        if self.failing {
561            return ErrorRef::nil();
562        }
563
564        self.failing = true;
565
566        let index = self.errors.len();
567
568        self.errors.push(error);
569
570        ErrorRef {
571            id: self.id,
572            entry: Entry { index, version: 0 },
573        }
574    }
575}