1use std::{collections::BTreeMap, fmt, ops::Range};
2
3use crate::Effect;
4
5#[derive(Debug)]
13pub struct Script {
14 operators: Vec<Operator>,
15 labels: Vec<Label>,
16 source_map: BTreeMap<OperatorIndex, Range<usize>>,
17}
18
19impl Script {
20 pub fn compile(script: &str) -> Self {
22 let mut next_index = OperatorIndex::default();
23
24 let mut operators = Vec::new();
25 let mut labels = Vec::new();
26 let mut source_map = BTreeMap::new();
27
28 enum State {
29 Initial,
30 Comment,
31 Token { start: usize },
32 }
33 let mut state = State::Initial;
34
35 for (i, ch) in script.char_indices() {
36 match (&state, ch) {
37 (State::Initial, '#') => {
38 state = State::Comment;
39 }
40 (State::Initial, ch) if !ch.is_whitespace() => {
41 state = State::Token { start: i };
42 }
43 (State::Initial, _) => {
44 }
46 (State::Comment, '\n') => {
47 state = State::Initial;
48 }
49 (State::Comment, _) => {
50 }
52 (State::Token { start }, ch) if ch.is_whitespace() => {
53 parse_token(
54 script,
55 *start..i,
56 &mut operators,
57 &mut labels,
58 &mut next_index,
59 &mut source_map,
60 );
61 state = State::Initial;
62 }
63 (State::Token { start: _ }, _) => {
64 }
67 }
68 }
69
70 if let State::Token { start } = state {
71 parse_token(
72 script,
73 start..script.len(),
74 &mut operators,
75 &mut labels,
76 &mut next_index,
77 &mut source_map,
78 );
79 }
80
81 Self {
82 operators,
83 labels,
84 source_map,
85 }
86 }
87
88 pub(crate) fn get_operator(
89 &self,
90 index: OperatorIndex,
91 ) -> Result<&Operator, InvalidOperatorIndex> {
92 let Ok(index): Result<usize, _> = index.value.try_into() else {
93 return Err(InvalidOperatorIndex);
97 };
98
99 let Some(operator) = self.operators.get(index) else {
100 return Err(InvalidOperatorIndex);
101 };
102
103 Ok(operator)
104 }
105
106 pub(crate) fn resolve_reference(
107 &self,
108 name: &str,
109 ) -> Result<OperatorIndex, InvalidReference> {
110 let label = self.labels.iter().find(|label| label.name == name);
111
112 let Some(&Label { name: _, operator }) = label else {
113 return Err(InvalidReference);
114 };
115
116 Ok(operator)
117 }
118
119 pub fn map_operator_to_source(
128 &self,
129 operator: &OperatorIndex,
130 ) -> Result<Range<usize>, InvalidOperatorIndex> {
131 let Some(range) = self.source_map.get(operator).cloned() else {
132 return Err(InvalidOperatorIndex);
133 };
134
135 Ok(range)
136 }
137
138 #[cfg(test)]
139 fn operators(&self) -> impl Iterator<Item = (OperatorIndex, &Operator)> {
140 use std::iter;
141
142 let indices =
143 iter::successors(Some(OperatorIndex::default()), |index| {
144 Some(OperatorIndex {
145 value: index.value + 1,
146 })
147 });
148
149 indices.zip(&self.operators)
150 }
151}
152
153fn parse_token(
154 script: &str,
155 range: Range<usize>,
156 operators: &mut Vec<Operator>,
157 labels: &mut Vec<Label>,
158 next_index: &mut OperatorIndex,
159 source_map: &mut BTreeMap<OperatorIndex, Range<usize>>,
160) {
161 let token = &script[range.clone()];
162
163 let operator = if let Some((name, "")) = token.rsplit_once(":") {
164 let Ok(index) = operators.len().try_into() else {
165 panic!(
166 "Trying to create a label for an operator whose index can't be \
167 represented as `u32`. This is only possible on 64-bit \
168 platforms, when there are more than `u32::MAX` operators in a \
169 script.\n\
170 \n\
171 That this limit can practically be reached with the language \
172 as it currently is, seems highly unlikely. This makes this \
173 panic an acceptable outcome.\n\
174 \n\
175 Long-term, once the API supports compiler errors, this case \
176 should result in an such an error instead."
177 );
178 };
179
180 labels.push(Label {
181 name: name.to_string(),
182 operator: OperatorIndex { value: index },
183 });
184
185 return;
186 } else if let Some(("", name)) = token.split_once("@") {
187 Operator::Reference {
188 name: name.to_string(),
189 }
190 } else if let Some(("", value)) = token.split_once("0x")
191 && let Ok(value) = i32::from_str_radix(value, 16)
192 {
193 Operator::Integer { value }
194 } else if let Some(("", value)) = token.split_once("0x")
195 && let Ok(value) = u32::from_str_radix(value, 16)
196 {
197 Operator::integer_u32(value)
198 } else if let Ok(value) = token.parse::<i32>() {
199 Operator::Integer { value }
200 } else if let Ok(value) = token.parse::<u32>() {
201 Operator::integer_u32(value)
202 } else {
203 Operator::Identifier {
204 value: token.to_string(),
205 }
206 };
207
208 operators.push(operator);
209
210 source_map.insert(*next_index, range);
211 next_index.value += 1;
212}
213
214#[derive(Debug)]
215pub enum Operator {
216 Identifier { value: String },
217 Integer { value: i32 },
218 Reference { name: String },
219}
220
221impl Operator {
222 pub fn integer_u32(value: u32) -> Self {
223 Self::Integer {
224 value: i32::from_le_bytes(value.to_le_bytes()),
225 }
226 }
227}
228
229#[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd)]
233pub struct OperatorIndex {
234 pub(crate) value: u32,
235}
236
237impl fmt::Display for OperatorIndex {
238 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
239 write!(f, "{}", self.value)
240 }
241}
242
243#[derive(Debug)]
244pub struct Label {
245 pub name: String,
246 pub operator: OperatorIndex,
247}
248
249#[derive(Debug)]
250pub struct InvalidOperatorIndex;
251
252impl From<InvalidOperatorIndex> for Effect {
253 fn from(InvalidOperatorIndex: InvalidOperatorIndex) -> Self {
254 Effect::OutOfOperators
255 }
256}
257
258#[derive(Debug)]
259pub struct InvalidReference;
260
261impl From<InvalidReference> for Effect {
262 fn from(InvalidReference: InvalidReference) -> Self {
263 Effect::InvalidReference
264 }
265}
266
267#[cfg(test)]
268mod tests {
269 use crate::Script;
270
271 #[test]
272 fn map_operator_to_source() {
273 let source = "0 loop: 1 + @loop jump";
274 let script = Script::compile(source);
275
276 let operators = script
277 .operators()
278 .map(|(operator, _)| {
279 let Ok(range) = script.map_operator_to_source(&operator) else {
280 unreachable!(
281 "Using `OperatorIndex` that definitely refers to an \
282 operator, as it was returned by `Script::operators`."
283 );
284 };
285 &source[range]
286 })
287 .collect::<Vec<_>>();
288
289 assert_eq!(operators, vec!["0", "1", "+", "@loop", "jump"]);
290 }
291}