1use crate::lexer::Token;
57use crate::span::Span;
58
59pub fn is_plurality_keyword(token: &Token) -> bool {
65 matches!(
66 token,
67 Token::Ident(s) if matches!(s.as_str(),
68 "alter" | "switch" | "headspace" | "cocon" |
69 "reality" | "split" | "trigger" | "location" |
70 "layer" | "transform" | "states" | "anima"
71 )
72 )
73}
74
75pub fn is_alter_category(token: &Token) -> bool {
77 matches!(
78 token,
79 Token::Ident(s) if matches!(s.as_str(),
80 "Council" | "Servant" | "Fragment" | "Hidden" | "Persecutor"
81 )
82 )
83}
84
85pub fn is_alter_state(token: &Token) -> bool {
87 matches!(
88 token,
89 Token::Ident(s) if matches!(s.as_str(),
90 "Dormant" | "Stirring" | "CoConscious" | "Emerging" |
91 "Fronting" | "Receding" | "Triggered" | "Dissociating"
92 )
93 )
94}
95
96pub fn check_alter_source_sequence(tokens: &[(Token, Span)]) -> Option<AlterSourceMarker> {
99 if tokens.is_empty() {
100 return None;
101 }
102
103 if let Token::At = &tokens[0].0 {
105 if tokens.len() >= 2 {
106 match &tokens[1].0 {
107 Token::Bang => Some(AlterSourceMarker::Fronting),
108 Token::Tilde => Some(AlterSourceMarker::CoCon),
109 Token::Question => Some(AlterSourceMarker::Dormant),
110 Token::Interrobang => Some(AlterSourceMarker::Blended),
111 Token::Ident(name) if name == "Fronting" => Some(AlterSourceMarker::Fronting),
112 Token::Ident(name) if name == "CoCon" => Some(AlterSourceMarker::CoCon),
113 Token::Ident(name) if name == "Dormant" => Some(AlterSourceMarker::Dormant),
114 Token::Ident(name) if name == "Blended" => Some(AlterSourceMarker::Blended),
115 _ => None,
116 }
117 } else {
118 None
119 }
120 } else {
121 None
122 }
123}
124
125pub fn check_forced_operation(tokens: &[(Token, Span)]) -> Option<ForcedOperation> {
128 if tokens.len() < 2 {
129 return None;
130 }
131
132 if let Token::Ident(name) = &tokens[0].0 {
133 if let Token::Bang = &tokens[1].0 {
134 match name.as_str() {
135 "switch" => return Some(ForcedOperation::Switch),
136 "split" => return Some(ForcedOperation::Split),
137 _ => {}
138 }
139 }
140 }
141
142 None
143}
144
145#[derive(Debug, Clone, Copy, PartialEq, Eq)]
151pub enum AlterSourceMarker {
152 Fronting,
154 CoCon,
156 Dormant,
158 Blended,
160}
161
162impl AlterSourceMarker {
163 pub fn to_evidentiality(&self) -> &'static str {
165 match self {
166 AlterSourceMarker::Fronting => "!",
167 AlterSourceMarker::CoCon => "~",
168 AlterSourceMarker::Dormant => "?",
169 AlterSourceMarker::Blended => "‽",
170 }
171 }
172
173 pub fn symbol(&self) -> &'static str {
175 match self {
176 AlterSourceMarker::Fronting => "@!",
177 AlterSourceMarker::CoCon => "@~",
178 AlterSourceMarker::Dormant => "@?",
179 AlterSourceMarker::Blended => "@‽",
180 }
181 }
182}
183
184#[derive(Debug, Clone, Copy, PartialEq, Eq)]
186pub enum ForcedOperation {
187 Switch,
189 Split,
191}
192
193pub struct PluralityTokenStream<'a> {
199 tokens: &'a [(Token, Span)],
200 position: usize,
201}
202
203impl<'a> PluralityTokenStream<'a> {
204 pub fn new(tokens: &'a [(Token, Span)]) -> Self {
205 Self {
206 tokens,
207 position: 0,
208 }
209 }
210
211 pub fn peek(&self) -> Option<&(Token, Span)> {
213 self.tokens.get(self.position)
214 }
215
216 pub fn peek_n(&self, n: usize) -> &[(Token, Span)] {
218 let end = (self.position + n).min(self.tokens.len());
219 &self.tokens[self.position..end]
220 }
221
222 pub fn advance(&mut self, n: usize) {
224 self.position = (self.position + n).min(self.tokens.len());
225 }
226
227 pub fn at_plurality_keyword(&self) -> bool {
229 self.peek()
230 .map(|(t, _)| is_plurality_keyword(t))
231 .unwrap_or(false)
232 }
233
234 pub fn try_alter_source(&mut self) -> Option<(AlterSourceMarker, Span)> {
236 let lookahead = self.peek_n(2);
237 if let Some(marker) = check_alter_source_sequence(lookahead) {
238 let start = lookahead[0].1.start;
239 let end = lookahead[1].1.end;
240 self.advance(2);
241 Some((marker, Span::new(start, end)))
242 } else {
243 None
244 }
245 }
246
247 pub fn try_forced_operation(&mut self) -> Option<(ForcedOperation, Span)> {
249 let lookahead = self.peek_n(2);
250 if let Some(op) = check_forced_operation(lookahead) {
251 let start = lookahead[0].1.start;
252 let end = lookahead[1].1.end;
253 self.advance(2);
254 Some((op, Span::new(start, end)))
255 } else {
256 None
257 }
258 }
259
260 pub fn at_alter_def(&self) -> bool {
263 let lookahead = self.peek_n(4);
264 if lookahead.is_empty() {
265 return false;
266 }
267
268 matches!(&lookahead[0].0, Token::Ident(s) if s == "alter")
270 && lookahead.len() > 1
271 && matches!(&lookahead[1].0, Token::Ident(_))
272 }
273
274 pub fn at_switch_expr(&self) -> bool {
277 let lookahead = self.peek_n(3);
278 if lookahead.is_empty() {
279 return false;
280 }
281
282 match &lookahead[0].0 {
283 Token::Ident(s) if s == "switch" => {
284 if lookahead.len() > 1 {
286 match &lookahead[1].0 {
287 Token::Bang => {
288 lookahead.len() > 2
290 && matches!(&lookahead[2].0, Token::Ident(s) if s == "to")
291 }
292 Token::Ident(s) if s == "to" => true,
293 _ => false,
294 }
295 } else {
296 false
297 }
298 }
299 _ => false,
300 }
301 }
302
303 pub fn at_headspace_def(&self) -> bool {
305 let lookahead = self.peek_n(2);
306 !lookahead.is_empty()
307 && matches!(&lookahead[0].0, Token::Ident(s) if s == "headspace")
308 && lookahead.len() > 1
309 && matches!(&lookahead[1].0, Token::Ident(_))
310 }
311
312 pub fn at_reality_def(&self) -> bool {
314 let lookahead = self.peek_n(3);
315 if lookahead.len() < 3 {
316 return false;
317 }
318
319 matches!(&lookahead[0].0, Token::Ident(s) if s == "reality")
320 && matches!(&lookahead[1].0, Token::Ident(s) if s == "entity")
321 && matches!(&lookahead[2].0, Token::Ident(_))
322 }
323
324 pub fn at_cocon_channel(&self) -> bool {
327 let lookahead = self.peek_n(2);
328 !lookahead.is_empty()
329 && matches!(&lookahead[0].0, Token::Ident(s) if s == "cocon")
330 && lookahead.len() > 1
331 && matches!(&lookahead[1].0, Token::Lt)
332 }
333
334 pub fn at_trigger_handler(&self) -> bool {
337 let lookahead = self.peek_n(3);
338 if lookahead.len() < 3 {
339 return false;
340 }
341
342 matches!(&lookahead[0].0, Token::On)
343 && matches!(&lookahead[1].0, Token::Ident(s) if s == "trigger")
344 && matches!(&lookahead[2].0, Token::Ident(_))
345 }
346
347 pub fn at_split_expr(&self) -> bool {
350 let lookahead = self.peek_n(3);
351 if lookahead.is_empty() {
352 return false;
353 }
354
355 matches!(&lookahead[0].0, Token::Ident(s) if s == "split")
356 && lookahead.len() > 1
357 && matches!(&lookahead[1].0, Token::Bang)
358 }
359}
360
361#[cfg(test)]
366mod tests {
367 use super::*;
368
369 fn make_token(token: Token) -> (Token, Span) {
370 (token, Span::default())
371 }
372
373 #[test]
374 fn test_alter_source_markers() {
375 let tokens = vec![make_token(Token::At), make_token(Token::Bang)];
377 assert_eq!(
378 check_alter_source_sequence(&tokens),
379 Some(AlterSourceMarker::Fronting)
380 );
381
382 let tokens = vec![make_token(Token::At), make_token(Token::Tilde)];
384 assert_eq!(
385 check_alter_source_sequence(&tokens),
386 Some(AlterSourceMarker::CoCon)
387 );
388
389 let tokens = vec![make_token(Token::At), make_token(Token::Question)];
391 assert_eq!(
392 check_alter_source_sequence(&tokens),
393 Some(AlterSourceMarker::Dormant)
394 );
395
396 let tokens = vec![make_token(Token::At), make_token(Token::Interrobang)];
398 assert_eq!(
399 check_alter_source_sequence(&tokens),
400 Some(AlterSourceMarker::Blended)
401 );
402 }
403
404 #[test]
405 fn test_forced_operations() {
406 let tokens = vec![
408 make_token(Token::Ident("switch".to_string())),
409 make_token(Token::Bang),
410 ];
411 assert_eq!(
412 check_forced_operation(&tokens),
413 Some(ForcedOperation::Switch)
414 );
415
416 let tokens = vec![
418 make_token(Token::Ident("split".to_string())),
419 make_token(Token::Bang),
420 ];
421 assert_eq!(
422 check_forced_operation(&tokens),
423 Some(ForcedOperation::Split)
424 );
425 }
426
427 #[test]
428 fn test_plurality_keywords() {
429 assert!(is_plurality_keyword(&Token::Ident("alter".to_string())));
430 assert!(is_plurality_keyword(&Token::Ident("switch".to_string())));
431 assert!(is_plurality_keyword(&Token::Ident("headspace".to_string())));
432 assert!(is_plurality_keyword(&Token::Ident("cocon".to_string())));
433 assert!(!is_plurality_keyword(&Token::Ident("struct".to_string())));
434 }
435
436 #[test]
437 fn test_alter_categories() {
438 assert!(is_alter_category(&Token::Ident("Council".to_string())));
439 assert!(is_alter_category(&Token::Ident("Servant".to_string())));
440 assert!(is_alter_category(&Token::Ident("Fragment".to_string())));
441 assert!(!is_alter_category(&Token::Ident("Other".to_string())));
442 }
443
444 #[test]
445 fn test_alter_states() {
446 assert!(is_alter_state(&Token::Ident("Dormant".to_string())));
447 assert!(is_alter_state(&Token::Ident("Fronting".to_string())));
448 assert!(is_alter_state(&Token::Ident("CoConscious".to_string())));
449 assert!(!is_alter_state(&Token::Ident("Running".to_string())));
450 }
451
452 #[test]
453 fn test_token_stream_alter_def() {
454 let tokens = vec![
455 make_token(Token::Ident("alter".to_string())),
456 make_token(Token::Ident("Abaddon".to_string())),
457 make_token(Token::Colon),
458 make_token(Token::Ident("Council".to_string())),
459 ];
460 let stream = PluralityTokenStream::new(&tokens);
461 assert!(stream.at_alter_def());
462 }
463
464 #[test]
465 fn test_token_stream_switch_expr() {
466 let tokens = vec![
468 make_token(Token::Ident("switch".to_string())),
469 make_token(Token::Ident("to".to_string())),
470 make_token(Token::Ident("Beleth".to_string())),
471 ];
472 let stream = PluralityTokenStream::new(&tokens);
473 assert!(stream.at_switch_expr());
474
475 let tokens = vec![
477 make_token(Token::Ident("switch".to_string())),
478 make_token(Token::Bang),
479 make_token(Token::Ident("to".to_string())),
480 make_token(Token::Ident("Abaddon".to_string())),
481 ];
482 let stream = PluralityTokenStream::new(&tokens);
483 assert!(stream.at_switch_expr());
484 }
485
486 #[test]
487 fn test_token_stream_headspace() {
488 let tokens = vec![
489 make_token(Token::Ident("headspace".to_string())),
490 make_token(Token::Ident("InnerWorld".to_string())),
491 make_token(Token::LBrace),
492 ];
493 let stream = PluralityTokenStream::new(&tokens);
494 assert!(stream.at_headspace_def());
495 }
496
497 #[test]
498 fn test_token_stream_cocon() {
499 let tokens = vec![
500 make_token(Token::Ident("cocon".to_string())),
501 make_token(Token::Lt),
502 make_token(Token::Ident("Stolas".to_string())),
503 make_token(Token::Comma),
504 make_token(Token::Ident("Paimon".to_string())),
505 make_token(Token::Gt),
506 ];
507 let stream = PluralityTokenStream::new(&tokens);
508 assert!(stream.at_cocon_channel());
509 }
510}