artichoke_backend/state/
parser.rs
1use std::borrow::Cow;
2use std::ffi::{CStr, CString};
3use std::ptr::NonNull;
4
5use crate::core::IncrementLinenoError;
6use crate::sys;
7
8pub const TOP_FILENAME: &[u8] = b"(eval)";
10
11#[derive(Debug)]
12pub struct State {
13 context: NonNull<sys::mrbc_context>,
14 stack: Vec<Context>,
15}
16
17impl State {
18 pub fn new(mrb: &mut sys::mrb_state) -> Option<Self> {
19 let context = unsafe { sys::mrbc_context_new(mrb) };
20 let mut context = NonNull::new(context)?;
21 reset_context_filename(mrb, unsafe { context.as_mut() });
22 Some(Self { context, stack: vec![] })
23 }
24
25 pub fn close(mut self, mrb: &mut sys::mrb_state) {
26 unsafe {
27 let ctx = self.context.as_mut();
28 sys::mrbc_context_free(mrb, ctx);
29 }
30 }
31
32 pub fn context_mut(&mut self) -> &mut sys::mrbc_context {
33 unsafe { self.context.as_mut() }
34 }
35
36 pub fn reset(&mut self, mrb: &mut sys::mrb_state) {
38 unsafe {
39 let ctx = self.context.as_mut();
40 ctx.lineno = 1;
41 reset_context_filename(mrb, ctx);
42 }
43 self.stack.clear();
44 }
45
46 #[must_use]
48 pub fn fetch_lineno(&self) -> usize {
49 let ctx = unsafe { self.context.as_ref() };
50 usize::from(ctx.lineno)
51 }
52
53 pub fn add_fetch_lineno(&mut self, val: usize) -> Result<usize, IncrementLinenoError> {
60 let old = usize::from(unsafe { self.context.as_ref() }.lineno);
61 let new = old
62 .checked_add(val)
63 .ok_or_else(|| IncrementLinenoError::Overflow(usize::from(u16::MAX)))?;
64 let store = u16::try_from(new).map_err(|_| IncrementLinenoError::Overflow(usize::from(u16::MAX)))?;
65 unsafe {
66 self.context.as_mut().lineno = store;
67 }
68 Ok(new)
69 }
70
71 pub fn push_context(&mut self, mrb: &mut sys::mrb_state, context: Context) {
77 let filename = context.filename_as_c_str();
78 unsafe {
79 let ctx = self.context.as_mut();
80 sys::mrbc_filename(mrb, ctx, filename.as_ptr());
81 }
82 self.stack.push(context);
83 }
84
85 pub fn pop_context(&mut self, mrb: &mut sys::mrb_state) -> Option<Context> {
91 let context = self.stack.pop();
92 if let Some(current) = self.stack.last() {
93 let filename = current.filename_as_c_str();
94 unsafe {
95 let ctx = self.context.as_mut();
96 sys::mrbc_filename(mrb, ctx, filename.as_ptr());
97 }
98 } else {
99 unsafe {
100 let ctx = self.context.as_mut();
101 reset_context_filename(mrb, ctx);
102 }
103 }
104 context
105 }
106
107 #[must_use]
109 pub fn peek_context(&self) -> Option<&Context> {
110 self.stack.last()
111 }
112}
113
114fn reset_context_filename(mrb: &mut sys::mrb_state, context: &mut sys::mrbc_context) {
115 let frame = Context::root();
116 let filename = frame.filename_as_c_str();
117 unsafe {
118 sys::mrbc_filename(mrb, context, filename.as_ptr());
119 }
120}
121
122#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
129pub struct Context {
130 filename: Cow<'static, [u8]>,
133 filename_cstr: Box<CStr>,
135}
136
137impl Default for Context {
138 fn default() -> Self {
139 unsafe { Self::new_unchecked(TOP_FILENAME) }
141 }
142}
143
144impl Context {
145 pub fn new<T>(filename: T) -> Option<Self>
147 where
148 T: Into<Cow<'static, [u8]>>,
149 {
150 let filename = filename.into();
151 let cstring = CString::new(filename.clone()).ok()?;
152 Some(Self {
153 filename,
154 filename_cstr: cstring.into_boxed_c_str(),
155 })
156 }
157
158 pub unsafe fn new_unchecked<T>(filename: T) -> Self
165 where
166 T: Into<Cow<'static, [u8]>>,
167 {
168 let filename = filename.into();
169 let cstring = unsafe { CString::from_vec_unchecked(filename.clone().into_owned()) };
171 Self {
172 filename,
173 filename_cstr: cstring.into_boxed_c_str(),
174 }
175 }
176
177 #[must_use]
181 pub fn root() -> Self {
182 Self::default()
183 }
184
185 #[must_use]
187 pub fn filename(&self) -> &[u8] {
188 &self.filename
189 }
190
191 #[must_use]
195 pub fn filename_as_c_str(&self) -> &CStr {
196 &self.filename_cstr
197 }
198}
199
200#[cfg(test)]
201mod test {
202 use super::Context;
203
204 #[test]
205 fn top_filename_does_not_contain_nul_byte() {
206 let contains_nul_byte = super::TOP_FILENAME.iter().copied().any(|b| b == b'\0');
207 assert!(!contains_nul_byte);
208 }
209
210 #[test]
211 fn top_filename_context_new_unchecked_safety() {
212 Context::new(super::TOP_FILENAME).unwrap();
213 }
214}