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 { tokens, position: 0 }
206 }
207
208 pub fn peek(&self) -> Option<&(Token, Span)> {
210 self.tokens.get(self.position)
211 }
212
213 pub fn peek_n(&self, n: usize) -> &[(Token, Span)] {
215 let end = (self.position + n).min(self.tokens.len());
216 &self.tokens[self.position..end]
217 }
218
219 pub fn advance(&mut self, n: usize) {
221 self.position = (self.position + n).min(self.tokens.len());
222 }
223
224 pub fn at_plurality_keyword(&self) -> bool {
226 self.peek().map(|(t, _)| is_plurality_keyword(t)).unwrap_or(false)
227 }
228
229 pub fn try_alter_source(&mut self) -> Option<(AlterSourceMarker, Span)> {
231 let lookahead = self.peek_n(2);
232 if let Some(marker) = check_alter_source_sequence(lookahead) {
233 let start = lookahead[0].1.start;
234 let end = lookahead[1].1.end;
235 self.advance(2);
236 Some((marker, Span::new(start, end)))
237 } else {
238 None
239 }
240 }
241
242 pub fn try_forced_operation(&mut self) -> Option<(ForcedOperation, Span)> {
244 let lookahead = self.peek_n(2);
245 if let Some(op) = check_forced_operation(lookahead) {
246 let start = lookahead[0].1.start;
247 let end = lookahead[1].1.end;
248 self.advance(2);
249 Some((op, Span::new(start, end)))
250 } else {
251 None
252 }
253 }
254
255 pub fn at_alter_def(&self) -> bool {
258 let lookahead = self.peek_n(4);
259 if lookahead.is_empty() {
260 return false;
261 }
262
263 matches!(&lookahead[0].0, Token::Ident(s) if s == "alter")
265 && lookahead.len() > 1
266 && matches!(&lookahead[1].0, Token::Ident(_))
267 }
268
269 pub fn at_switch_expr(&self) -> bool {
272 let lookahead = self.peek_n(3);
273 if lookahead.is_empty() {
274 return false;
275 }
276
277 match &lookahead[0].0 {
278 Token::Ident(s) if s == "switch" => {
279 if lookahead.len() > 1 {
281 match &lookahead[1].0 {
282 Token::Bang => {
283 lookahead.len() > 2
285 && matches!(&lookahead[2].0, Token::Ident(s) if s == "to")
286 }
287 Token::Ident(s) if s == "to" => true,
288 _ => false,
289 }
290 } else {
291 false
292 }
293 }
294 _ => false,
295 }
296 }
297
298 pub fn at_headspace_def(&self) -> bool {
300 let lookahead = self.peek_n(2);
301 !lookahead.is_empty()
302 && matches!(&lookahead[0].0, Token::Ident(s) if s == "headspace")
303 && lookahead.len() > 1
304 && matches!(&lookahead[1].0, Token::Ident(_))
305 }
306
307 pub fn at_reality_def(&self) -> bool {
309 let lookahead = self.peek_n(3);
310 if lookahead.len() < 3 {
311 return false;
312 }
313
314 matches!(&lookahead[0].0, Token::Ident(s) if s == "reality")
315 && matches!(&lookahead[1].0, Token::Ident(s) if s == "entity")
316 && matches!(&lookahead[2].0, Token::Ident(_))
317 }
318
319 pub fn at_cocon_channel(&self) -> bool {
322 let lookahead = self.peek_n(2);
323 !lookahead.is_empty()
324 && matches!(&lookahead[0].0, Token::Ident(s) if s == "cocon")
325 && lookahead.len() > 1
326 && matches!(&lookahead[1].0, Token::Lt)
327 }
328
329 pub fn at_trigger_handler(&self) -> bool {
332 let lookahead = self.peek_n(3);
333 if lookahead.len() < 3 {
334 return false;
335 }
336
337 matches!(&lookahead[0].0, Token::On)
338 && matches!(&lookahead[1].0, Token::Ident(s) if s == "trigger")
339 && matches!(&lookahead[2].0, Token::Ident(_))
340 }
341
342 pub fn at_split_expr(&self) -> bool {
345 let lookahead = self.peek_n(3);
346 if lookahead.is_empty() {
347 return false;
348 }
349
350 matches!(&lookahead[0].0, Token::Ident(s) if s == "split")
351 && lookahead.len() > 1
352 && matches!(&lookahead[1].0, Token::Bang)
353 }
354}
355
356#[cfg(test)]
361mod tests {
362 use super::*;
363
364 fn make_token(token: Token) -> (Token, Span) {
365 (token, Span::default())
366 }
367
368 #[test]
369 fn test_alter_source_markers() {
370 let tokens = vec![
372 make_token(Token::At),
373 make_token(Token::Bang),
374 ];
375 assert_eq!(
376 check_alter_source_sequence(&tokens),
377 Some(AlterSourceMarker::Fronting)
378 );
379
380 let tokens = vec![
382 make_token(Token::At),
383 make_token(Token::Tilde),
384 ];
385 assert_eq!(
386 check_alter_source_sequence(&tokens),
387 Some(AlterSourceMarker::CoCon)
388 );
389
390 let tokens = vec![
392 make_token(Token::At),
393 make_token(Token::Question),
394 ];
395 assert_eq!(
396 check_alter_source_sequence(&tokens),
397 Some(AlterSourceMarker::Dormant)
398 );
399
400 let tokens = vec![
402 make_token(Token::At),
403 make_token(Token::Interrobang),
404 ];
405 assert_eq!(
406 check_alter_source_sequence(&tokens),
407 Some(AlterSourceMarker::Blended)
408 );
409 }
410
411 #[test]
412 fn test_forced_operations() {
413 let tokens = vec![
415 make_token(Token::Ident("switch".to_string())),
416 make_token(Token::Bang),
417 ];
418 assert_eq!(
419 check_forced_operation(&tokens),
420 Some(ForcedOperation::Switch)
421 );
422
423 let tokens = vec![
425 make_token(Token::Ident("split".to_string())),
426 make_token(Token::Bang),
427 ];
428 assert_eq!(
429 check_forced_operation(&tokens),
430 Some(ForcedOperation::Split)
431 );
432 }
433
434 #[test]
435 fn test_plurality_keywords() {
436 assert!(is_plurality_keyword(&Token::Ident("alter".to_string())));
437 assert!(is_plurality_keyword(&Token::Ident("switch".to_string())));
438 assert!(is_plurality_keyword(&Token::Ident("headspace".to_string())));
439 assert!(is_plurality_keyword(&Token::Ident("cocon".to_string())));
440 assert!(!is_plurality_keyword(&Token::Ident("struct".to_string())));
441 }
442
443 #[test]
444 fn test_alter_categories() {
445 assert!(is_alter_category(&Token::Ident("Council".to_string())));
446 assert!(is_alter_category(&Token::Ident("Servant".to_string())));
447 assert!(is_alter_category(&Token::Ident("Fragment".to_string())));
448 assert!(!is_alter_category(&Token::Ident("Other".to_string())));
449 }
450
451 #[test]
452 fn test_alter_states() {
453 assert!(is_alter_state(&Token::Ident("Dormant".to_string())));
454 assert!(is_alter_state(&Token::Ident("Fronting".to_string())));
455 assert!(is_alter_state(&Token::Ident("CoConscious".to_string())));
456 assert!(!is_alter_state(&Token::Ident("Running".to_string())));
457 }
458
459 #[test]
460 fn test_token_stream_alter_def() {
461 let tokens = vec![
462 make_token(Token::Ident("alter".to_string())),
463 make_token(Token::Ident("Abaddon".to_string())),
464 make_token(Token::Colon),
465 make_token(Token::Ident("Council".to_string())),
466 ];
467 let stream = PluralityTokenStream::new(&tokens);
468 assert!(stream.at_alter_def());
469 }
470
471 #[test]
472 fn test_token_stream_switch_expr() {
473 let tokens = vec![
475 make_token(Token::Ident("switch".to_string())),
476 make_token(Token::Ident("to".to_string())),
477 make_token(Token::Ident("Beleth".to_string())),
478 ];
479 let stream = PluralityTokenStream::new(&tokens);
480 assert!(stream.at_switch_expr());
481
482 let tokens = vec![
484 make_token(Token::Ident("switch".to_string())),
485 make_token(Token::Bang),
486 make_token(Token::Ident("to".to_string())),
487 make_token(Token::Ident("Abaddon".to_string())),
488 ];
489 let stream = PluralityTokenStream::new(&tokens);
490 assert!(stream.at_switch_expr());
491 }
492
493 #[test]
494 fn test_token_stream_headspace() {
495 let tokens = vec![
496 make_token(Token::Ident("headspace".to_string())),
497 make_token(Token::Ident("InnerWorld".to_string())),
498 make_token(Token::LBrace),
499 ];
500 let stream = PluralityTokenStream::new(&tokens);
501 assert!(stream.at_headspace_def());
502 }
503
504 #[test]
505 fn test_token_stream_cocon() {
506 let tokens = vec![
507 make_token(Token::Ident("cocon".to_string())),
508 make_token(Token::Lt),
509 make_token(Token::Ident("Stolas".to_string())),
510 make_token(Token::Comma),
511 make_token(Token::Ident("Paimon".to_string())),
512 make_token(Token::Gt),
513 ];
514 let stream = PluralityTokenStream::new(&tokens);
515 assert!(stream.at_cocon_channel());
516 }
517}