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 a slice containing the offsets of the start of each line in the source string
123    /// that was parsed.
124    #[must_use]
125    pub fn line_offsets(&self) -> &'pr [u32] {
126        unsafe {
127            let list = &(*self.parser.as_ptr()).line_offsets;
128            std::slice::from_raw_parts(list.offsets, list.size)
129        }
130    }
131    /// Returns an iterator that can be used to iterate over the errors in the
132    /// parse result.
133    #[must_use]
134    pub fn errors(&self) -> Diagnostics<'_> {
135        unsafe {
136            let list = &mut (*self.parser.as_ptr()).error_list;
137            Diagnostics::new(list.head.cast::<pm_diagnostic_t>(), self.parser)
138        }
139    }
140
141    /// Returns an iterator that can be used to iterate over the warnings in the
142    /// parse result.
143    #[must_use]
144    pub fn warnings(&self) -> Diagnostics<'_> {
145        unsafe {
146            let list = &mut (*self.parser.as_ptr()).warning_list;
147            Diagnostics::new(list.head.cast::<pm_diagnostic_t>(), self.parser)
148        }
149    }
150
151    /// Returns an iterator that can be used to iterate over the comments in the
152    /// parse result.
153    #[must_use]
154    pub fn comments(&self) -> Comments<'_> {
155        unsafe {
156            let list = &mut (*self.parser.as_ptr()).comment_list;
157            Comments::new(list.head.cast::<pm_comment_t>(), self.parser)
158        }
159    }
160
161    /// Returns an iterator that can be used to iterate over the magic comments in the
162    /// parse result.
163    #[must_use]
164    pub fn magic_comments(&self) -> MagicComments<'_> {
165        unsafe {
166            let list = &mut (*self.parser.as_ptr()).magic_comment_list;
167            MagicComments::new(self.parser, list.head.cast::<pm_magic_comment_t>())
168        }
169    }
170
171    /// Returns an optional location of the __END__ marker and the rest of the content of the file.
172    #[must_use]
173    pub fn data_loc(&self) -> Option<Location<'_>> {
174        let location = unsafe { &(*self.parser.as_ptr()).data_loc };
175        if location.length == 0 {
176            None
177        } else {
178            Some(Location::new(self.parser, location))
179        }
180    }
181
182    /// Returns the root node of the parse result.
183    #[must_use]
184    pub fn node(&self) -> Node<'_> {
185        Node::new(self.parser, self.node.as_ptr())
186    }
187
188    /// Returns true if there were no errors during parsing and false if there
189    /// were.
190    #[must_use]
191    pub fn is_success(&self) -> bool {
192        self.errors().next().is_none()
193    }
194
195    /// Returns true if there were errors during parsing and false if there were
196    /// not.
197    #[must_use]
198    pub fn is_failure(&self) -> bool {
199        !self.is_success()
200    }
201}
202
203impl Drop for ParseResult<'_> {
204    fn drop(&mut self) {
205        unsafe {
206            pm_node_destroy(self.parser.as_ptr(), self.node.as_ptr());
207            pm_parser_free(self.parser.as_ptr());
208            drop(Box::from_raw(self.parser.as_ptr()));
209        }
210    }
211}
212
213#[cfg(test)]
214mod tests {
215    use crate::parse;
216
217    #[test]
218    fn test_is_success() {
219        let result = parse(b"1 + 1");
220        assert!(result.is_success());
221        assert!(!result.is_failure());
222    }
223
224    #[test]
225    fn test_is_failure() {
226        let result = parse(b"<>");
227        assert!(result.is_failure());
228        assert!(!result.is_success());
229    }
230}