| 1 | use std::num::NonZeroUsize; |
| 2 | |
| 3 | use crate::{ |
| 4 | cow_mut::CowMut, |
| 5 | green::{node_cache::NodeCache, GreenElement, GreenNode, SyntaxKind}, |
| 6 | NodeOrToken, |
| 7 | }; |
| 8 | |
| 9 | /// A checkpoint for maybe wrapping a node. See `GreenNodeBuilder::checkpoint` for details. |
| 10 | #[derive (Clone, Copy, Debug)] |
| 11 | pub struct Checkpoint(NonZeroUsize); |
| 12 | |
| 13 | impl Checkpoint { |
| 14 | fn new(inner: usize) -> Self { |
| 15 | Self(NonZeroUsize::new(inner + 1).unwrap()) |
| 16 | } |
| 17 | |
| 18 | fn into_inner(self) -> usize { |
| 19 | self.0.get() - 1 |
| 20 | } |
| 21 | } |
| 22 | |
| 23 | /// A builder for a green tree. |
| 24 | #[derive (Default, Debug)] |
| 25 | pub struct GreenNodeBuilder<'cache> { |
| 26 | cache: CowMut<'cache, NodeCache>, |
| 27 | parents: Vec<(SyntaxKind, usize)>, |
| 28 | children: Vec<(u64, GreenElement)>, |
| 29 | } |
| 30 | |
| 31 | impl GreenNodeBuilder<'_> { |
| 32 | /// Creates new builder. |
| 33 | pub fn new() -> GreenNodeBuilder<'static> { |
| 34 | GreenNodeBuilder::default() |
| 35 | } |
| 36 | |
| 37 | /// Reusing `NodeCache` between different `GreenNodeBuilder`s saves memory. |
| 38 | /// It allows to structurally share underlying trees. |
| 39 | pub fn with_cache(cache: &mut NodeCache) -> GreenNodeBuilder<'_> { |
| 40 | GreenNodeBuilder { |
| 41 | cache: CowMut::Borrowed(cache), |
| 42 | parents: Vec::new(), |
| 43 | children: Vec::new(), |
| 44 | } |
| 45 | } |
| 46 | |
| 47 | /// Adds new token to the current branch. |
| 48 | #[inline ] |
| 49 | pub fn token(&mut self, kind: SyntaxKind, text: &str) { |
| 50 | let (hash, token) = self.cache.token(kind, text); |
| 51 | self.children.push((hash, token.into())); |
| 52 | } |
| 53 | |
| 54 | /// Start new node and make it current. |
| 55 | #[inline ] |
| 56 | pub fn start_node(&mut self, kind: SyntaxKind) { |
| 57 | let len = self.children.len(); |
| 58 | self.parents.push((kind, len)); |
| 59 | } |
| 60 | |
| 61 | /// Finish current branch and restore previous |
| 62 | /// branch as current. |
| 63 | #[inline ] |
| 64 | pub fn finish_node(&mut self) { |
| 65 | let (kind, first_child) = self.parents.pop().unwrap(); |
| 66 | let (hash, node) = self.cache.node(kind, &mut self.children, first_child); |
| 67 | self.children.push((hash, node.into())); |
| 68 | } |
| 69 | |
| 70 | /// Prepare for maybe wrapping the next node. |
| 71 | /// The way wrapping works is that you first of all get a checkpoint, |
| 72 | /// then you place all tokens you want to wrap, and then *maybe* call |
| 73 | /// `start_node_at`. |
| 74 | /// Example: |
| 75 | /// ```rust |
| 76 | /// # use rowan::{GreenNodeBuilder, SyntaxKind}; |
| 77 | /// # const PLUS: SyntaxKind = SyntaxKind(0); |
| 78 | /// # const OPERATION: SyntaxKind = SyntaxKind(1); |
| 79 | /// # struct Parser; |
| 80 | /// # impl Parser { |
| 81 | /// # fn peek(&self) -> Option<SyntaxKind> { None } |
| 82 | /// # fn parse_expr(&mut self) {} |
| 83 | /// # } |
| 84 | /// # let mut builder = GreenNodeBuilder::new(); |
| 85 | /// # let mut parser = Parser; |
| 86 | /// let checkpoint = builder.checkpoint(); |
| 87 | /// parser.parse_expr(); |
| 88 | /// if parser.peek() == Some(PLUS) { |
| 89 | /// // 1 + 2 = Add(1, 2) |
| 90 | /// builder.start_node_at(checkpoint, OPERATION); |
| 91 | /// parser.parse_expr(); |
| 92 | /// builder.finish_node(); |
| 93 | /// } |
| 94 | /// ``` |
| 95 | #[inline ] |
| 96 | pub fn checkpoint(&self) -> Checkpoint { |
| 97 | Checkpoint::new(self.children.len()) |
| 98 | } |
| 99 | |
| 100 | /// Wrap the previous branch marked by `checkpoint` in a new branch and |
| 101 | /// make it current. |
| 102 | #[inline ] |
| 103 | pub fn start_node_at(&mut self, checkpoint: Checkpoint, kind: SyntaxKind) { |
| 104 | let checkpoint = checkpoint.into_inner(); |
| 105 | assert!( |
| 106 | checkpoint <= self.children.len(), |
| 107 | "checkpoint no longer valid, was finish_node called early?" |
| 108 | ); |
| 109 | |
| 110 | if let Some(&(_, first_child)) = self.parents.last() { |
| 111 | assert!( |
| 112 | checkpoint >= first_child, |
| 113 | "checkpoint no longer valid, was an unmatched start_node_at called?" |
| 114 | ); |
| 115 | } |
| 116 | |
| 117 | self.parents.push((kind, checkpoint)); |
| 118 | } |
| 119 | |
| 120 | /// Complete tree building. Make sure that |
| 121 | /// `start_node_at` and `finish_node` calls |
| 122 | /// are paired! |
| 123 | #[inline ] |
| 124 | pub fn finish(mut self) -> GreenNode { |
| 125 | assert_eq!(self.children.len(), 1); |
| 126 | match self.children.pop().unwrap().1 { |
| 127 | NodeOrToken::Node(node) => node, |
| 128 | NodeOrToken::Token(_) => panic!(), |
| 129 | } |
| 130 | } |
| 131 | } |
| 132 | |