ruby_prism/parse_result/
mod.rs

1//! Parse result types for the prism parser.
2//!
3//! This module contains types related to the result of parsing, including
4//! the main `ParseResult` struct, location tracking, comments, and diagnostics.
5
6mod comments;
7mod diagnostics;
8
9use std::ptr::NonNull;
10
11use ruby_prism_sys::{pm_comment_t, pm_diagnostic_t, pm_location_t, pm_magic_comment_t, pm_node_destroy, pm_node_t, pm_parser_free, pm_parser_t};
12
13pub use self::comments::{Comment, CommentType, Comments, MagicComment, MagicComments};
14pub use self::diagnostics::{Diagnostic, Diagnostics};
15
16use crate::Node;
17
18/// A range in the source file, represented as a start offset and length.
19pub struct Location<'pr> {
20    pub(crate) parser: NonNull<pm_parser_t>,
21    pub(crate) start: u32,
22    pub(crate) length: u32,
23    marker: std::marker::PhantomData<&'pr [u8]>,
24}
25
26impl<'pr> Location<'pr> {
27    /// Returns a byte slice for the range.
28    #[must_use]
29    pub fn as_slice(&self) -> &'pr [u8] {
30        unsafe {
31            let parser_start = (*self.parser.as_ptr()).start;
32            std::slice::from_raw_parts(parser_start.add(self.start as usize), self.length as usize)
33        }
34    }
35
36    /// Return a Location from the given `pm_location_t`.
37    #[must_use]
38    pub(crate) const fn new(parser: NonNull<pm_parser_t>, location: &'pr pm_location_t) -> Self {
39        Location {
40            parser,
41            start: location.start,
42            length: location.length,
43            marker: std::marker::PhantomData,
44        }
45    }
46
47    /// Returns the end offset from the beginning of the parsed source.
48    #[must_use]
49    pub const fn end(&self) -> u32 {
50        self.start + self.length
51    }
52
53    /// Return a Location starting at self and ending at the end of other.
54    /// Returns None if both locations did not originate from the same parser,
55    /// or if self starts after other.
56    #[must_use]
57    pub fn join(&self, other: &Self) -> Option<Self> {
58        if self.parser != other.parser || self.start > other.start {
59            None
60        } else {
61            Some(Location {
62                parser: self.parser,
63                start: self.start,
64                length: other.end() - self.start,
65                marker: std::marker::PhantomData,
66            })
67        }
68    }
69}
70
71impl std::fmt::Debug for Location<'_> {
72    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
73        let slice: &[u8] = self.as_slice();
74
75        let mut visible = String::new();
76        visible.push('"');
77
78        for &byte in slice {
79            let part: Vec<u8> = std::ascii::escape_default(byte).collect();
80            visible.push_str(std::str::from_utf8(&part).unwrap());
81        }
82
83        visible.push('"');
84        write!(f, "{visible}")
85    }
86}
87
88/// The result of parsing a source string.
89#[derive(Debug)]
90pub struct ParseResult<'pr> {
91    source: &'pr [u8],
92    parser: NonNull<pm_parser_t>,
93    node: NonNull<pm_node_t>,
94}
95
96impl<'pr> ParseResult<'pr> {
97    pub(crate) const unsafe fn new(source: &'pr [u8], parser: NonNull<pm_parser_t>, node: NonNull<pm_node_t>) -> Self {
98        ParseResult { source, parser, node }
99    }
100
101    /// Returns the source string that was parsed.
102    #[must_use]
103    pub const fn source(&self) -> &'pr [u8] {
104        self.source
105    }
106
107    /// Returns whether we found a `frozen_string_literal` magic comment with a true value.
108    #[must_use]
109    pub fn frozen_string_literals(&self) -> bool {
110        unsafe { (*self.parser.as_ptr()).frozen_string_literal == 1 }
111    }
112
113    /// Returns a slice of the source string that was parsed using the given
114    /// slice range.
115    #[must_use]
116    pub fn as_slice(&self, location: &Location<'pr>) -> &'pr [u8] {
117        let start = location.start as usize;
118        let end = start + location.length as usize;
119        &self.source[start..end]
120    }
121
122    /// Returns an iterator that can be used to iterate over the errors in the
123    /// parse result.
124    #[must_use]
125    pub fn errors(&self) -> Diagnostics<'_> {
126        unsafe {
127            let list = &mut (*self.parser.as_ptr()).error_list;
128            Diagnostics::new(list.head.cast::<pm_diagnostic_t>(), self.parser)
129        }
130    }
131
132    /// Returns an iterator that can be used to iterate over the warnings in the
133    /// parse result.
134    #[must_use]
135    pub fn warnings(&self) -> Diagnostics<'_> {
136        unsafe {
137            let list = &mut (*self.parser.as_ptr()).warning_list;
138            Diagnostics::new(list.head.cast::<pm_diagnostic_t>(), self.parser)
139        }
140    }
141
142    /// Returns an iterator that can be used to iterate over the comments in the
143    /// parse result.
144    #[must_use]
145    pub fn comments(&self) -> Comments<'_> {
146        unsafe {
147            let list = &mut (*self.parser.as_ptr()).comment_list;
148            Comments::new(list.head.cast::<pm_comment_t>(), self.parser)
149        }
150    }
151
152    /// Returns an iterator that can be used to iterate over the magic comments in the
153    /// parse result.
154    #[must_use]
155    pub fn magic_comments(&self) -> MagicComments<'_> {
156        unsafe {
157            let list = &mut (*self.parser.as_ptr()).magic_comment_list;
158            MagicComments::new(self.parser, list.head.cast::<pm_magic_comment_t>())
159        }
160    }
161
162    /// Returns an optional location of the __END__ marker and the rest of the content of the file.
163    #[must_use]
164    pub fn data_loc(&self) -> Option<Location<'_>> {
165        let location = unsafe { &(*self.parser.as_ptr()).data_loc };
166        if location.length == 0 {
167            None
168        } else {
169            Some(Location::new(self.parser, location))
170        }
171    }
172
173    /// Returns the root node of the parse result.
174    #[must_use]
175    pub fn node(&self) -> Node<'_> {
176        Node::new(self.parser, self.node.as_ptr())
177    }
178}
179
180impl Drop for ParseResult<'_> {
181    fn drop(&mut self) {
182        unsafe {
183            pm_node_destroy(self.parser.as_ptr(), self.node.as_ptr());
184            pm_parser_free(self.parser.as_ptr());
185            drop(Box::from_raw(self.parser.as_ptr()));
186        }
187    }
188}