ctoolbox/formats/eite/
eite_state.rs

1use anyhow::{Result, anyhow, ensure};
2use serde::Serialize;
3
4use crate::formats::eite::util::{
5    array::{print_arr, set_element, str_print_arr},
6    string::{
7        int_arr_from_str_printed_arr, str_join_esc_no_trailing,
8        str_split_escaped,
9    },
10};
11
12/// Environment / shared state.
13/// In JS this lived on the global object with many independent keys;
14/// here we consolidate into a struct.
15#[derive(Default, Serialize)]
16pub struct EiteState {
17    // Preferences / environment
18    pub debug_level: i32,
19    pub env_preferred_format: String,
20    pub env_char_encoding: String,
21    pub env_terminal_type: String,
22    pub env_language: String,
23    pub env_locale_config: String,
24    pub env_code_language: String,
25    pub env_resolution_w: i32,
26    pub env_resolution_h: i32,
27
28    // Execution-related state (present in original snippet but not yet used here)
29    pub document_exec_data: Vec<String>,
30    pub document_exec_symbol_index: Vec<String>,
31    pub document_exec_ptrs: Vec<String>,
32    pub document_exec_frames: Vec<String>,
33    pub document_exec_events: Vec<String>,
34    pub document_exec_logs: Vec<String>,
35    pub document_exec_settings: Vec<String>,
36
37    // Test counters
38    pub tests_passed: u32,
39    pub tests_failed: u32,
40    pub tests_total: u32,
41
42    // Import / export configuration
43    pub import_settings: Vec<String>,
44    pub export_settings: Vec<String>,
45    pub import_deferred_settings_stack: Vec<String>,
46    pub export_deferred_settings_stack: Vec<String>,
47
48    // Storage config
49    pub storage_cfg: Vec<String>,
50}
51
52impl EiteState {
53    pub fn new() -> Self {
54        Self {
55            debug_level: 0,
56            env_preferred_format: String::new(),
57            env_char_encoding: "asciiSafeSubset".into(),
58            env_terminal_type: "vt100".into(),
59            env_language: "en-US".into(),
60            env_locale_config: "inherit:usa,".into(),
61            env_code_language: "javascript".into(),
62            env_resolution_w: 0,
63            env_resolution_h: 0,
64            ..Default::default()
65        }
66    }
67
68    /* -------------------- Document Exec State Management ---------------------- */
69    /// Prepare a new document execution context. Returns exec ID.
70    pub fn prepare_document_exec(&mut self, contents: &[u32]) -> usize {
71        let exec_id = self.document_exec_ptrs.len();
72        self.document_exec_data.push(str_print_arr(contents));
73        self.document_exec_symbol_index.push(String::new());
74        // Pointer stack stored as escaped join; initial current pointer = 0 (trailing comma).
75        self.document_exec_ptrs.push("0,".to_string());
76        self.document_exec_frames.push(String::new());
77        self.document_exec_events.push(String::new());
78        self.document_exec_logs.push(String::new());
79        self.document_exec_settings.push(",".to_string()); // empty kv join
80        exec_id
81    }
82
83    pub fn is_exec_id(&self, exec_id: usize) -> bool {
84        exec_id < self.document_exec_ptrs.len()
85    }
86
87    /// NOTE: Minimal kv split: split on ',' dropping empty trailing segments.
88    pub fn get_exec_settings(&self, exec_id: usize) -> Result<Vec<String>> {
89        if !self.is_exec_id(exec_id) {
90            return Err(anyhow!("Exec id out of range"));
91        }
92        let settings = self
93            .document_exec_settings
94            .get(exec_id)
95            .ok_or_else(|| anyhow!("Exec id not found"))?;
96        Ok(settings
97            .split(',')
98            .filter(|s| !s.is_empty())
99            .map(std::string::ToString::to_string)
100            .collect())
101    }
102
103    /// Replace full settings (joined with trailing comma for symmetry with original usage).
104    pub fn set_exec_settings(
105        &mut self,
106        exec_id: usize,
107        kv: &[String],
108    ) -> Result<()> {
109        if !self.is_exec_id(exec_id) {
110            return Err(anyhow!("Exec id out of range"));
111        }
112        let mut joined = String::new();
113        for k in kv {
114            joined.push_str(k);
115            joined.push(',');
116        }
117        self.document_exec_settings[exec_id] = joined;
118        Ok(())
119    }
120
121    pub fn get_exec_ptrs(&self, exec_id: usize) -> Result<Vec<String>> {
122        if !self.is_exec_id(exec_id) {
123            return Err(anyhow!("Exec id out of range"));
124        }
125        Ok(str_split_escaped(&self.document_exec_ptrs[exec_id], ","))
126    }
127
128    pub fn set_exec_ptrs(
129        &mut self,
130        exec_id: usize,
131        ptrs: &[String],
132    ) -> Result<()> {
133        if !self.is_exec_id(exec_id) {
134            return Err(anyhow!("Exec id out of range"));
135        }
136        self.document_exec_ptrs[exec_id] = str_join_esc_no_trailing(ptrs, ",");
137        Ok(())
138    }
139
140    fn parse_ptr(val: &str) -> u32 {
141        if val.is_empty() {
142            0
143        } else {
144            val.parse::<u32>().unwrap_or(0)
145        }
146    }
147
148    pub fn get_current_exec_ptr_pos(&self, exec_id: usize) -> Result<u32> {
149        let ptrs = self.get_exec_ptrs(exec_id)?;
150        if let Some(last) = ptrs.last() {
151            Ok(Self::parse_ptr(last))
152        } else {
153            Ok(0)
154        }
155    }
156
157    pub fn set_exec_ptr_pos(
158        &mut self,
159        exec_id: usize,
160        new_pos: u32,
161    ) -> Result<()> {
162        let mut ptrs = self.get_exec_ptrs(exec_id)?;
163        set_element(&mut ptrs, -1, new_pos.to_string());
164        self.set_exec_ptrs(exec_id, &ptrs)?;
165        Ok(())
166    }
167
168    pub fn incr_exec_ptr_pos(&mut self, exec_id: usize) -> Result<()> {
169        let cur = self.get_current_exec_ptr_pos(exec_id)?;
170        self.set_exec_ptr_pos(exec_id, cur + 1)
171    }
172
173    pub fn get_next_level_exec_ptr_pos(&self, exec_id: usize) -> Result<u32> {
174        let ptrs = self.get_exec_ptrs(exec_id)?;
175        if ptrs.len() >= 2 {
176            Ok(Self::parse_ptr(&ptrs[ptrs.len() - 2]))
177        } else {
178            Ok(0)
179        }
180    }
181
182    pub fn get_current_exec_data(&self, exec_id: usize) -> Result<Vec<u32>> {
183        if !self.is_exec_id(exec_id) {
184            return Err(anyhow!("Exec id out of range"));
185        }
186        int_arr_from_str_printed_arr(&self.document_exec_data[exec_id])
187    }
188
189    pub fn get_current_exec_frame(&self, exec_id: usize) -> Result<Vec<u32>> {
190        if !self.is_exec_id(exec_id) {
191            return Err(anyhow!("Exec id out of range"));
192        }
193        int_arr_from_str_printed_arr(&self.document_exec_frames[exec_id])
194    }
195
196    /// Retrieve a single exec option (key -> value) from settings KV.
197    pub fn get_exec_option(
198        &self,
199        exec_id: usize,
200        key: &str,
201    ) -> Result<Option<String>> {
202        let kv = self.get_exec_settings(exec_id)?;
203        for pair in kv {
204            if let Some((k, v)) = pair.split_once('=') {
205                if k == key {
206                    return Ok(Some(v.to_string()));
207                }
208            }
209        }
210        Ok(None)
211    }
212
213    /// Set (or insert) an exec option key=value pair.
214    pub fn set_exec_option(
215        &mut self,
216        exec_id: usize,
217        key: &str,
218        val: &str,
219    ) -> Result<()> {
220        let mut kv = self.get_exec_settings(exec_id)?;
221        let mut found = false;
222        for item in &mut kv {
223            if let Some((k, _)) = item.split_once('=') {
224                if k == key {
225                    *item = format!("{key}={val}");
226                    found = true;
227                    break;
228                }
229            }
230        }
231        if !found {
232            kv.push(format!("{key}={val}"));
233        }
234        self.set_exec_settings(exec_id, &kv)
235    }
236
237    /// Overwrite current frame output.
238    pub fn set_exec_frame(
239        &mut self,
240        exec_id: usize,
241        frame: &[u32],
242    ) -> Result<()> {
243        ensure!(self.is_exec_id(exec_id), "Invalid exec id {exec_id}");
244        if exec_id >= self.document_exec_frames.len() {
245            self.document_exec_frames.resize(exec_id + 1, String::new());
246        }
247        self.document_exec_frames[exec_id] = print_arr(frame);
248        Ok(())
249    }
250
251    pub fn get_env_preferred_format(&self) -> &str {
252        if self.env_preferred_format.is_empty() {
253            "utf8"
254        } else {
255            &self.env_preferred_format
256        }
257    }
258}