1use super::types::{
6 BuiltinPrecTable, Fixity, NotationConflict, NotationEntry, NotationKind, NotationPart,
7 NotationRegistry, NotationRule, NotationTokenKind,
8};
9
10pub fn parse_notation_pattern(input: &str) -> Vec<NotationPart> {
15 let mut parts = Vec::new();
16 let mut chars = input.chars().peekable();
17 while let Some(&ch) = chars.peek() {
18 if ch.is_whitespace() {
19 chars.next();
20 continue;
21 }
22 if ch == '"' {
23 chars.next();
24 let mut lit = String::new();
25 while let Some(&c) = chars.peek() {
26 if c == '"' {
27 chars.next();
28 break;
29 }
30 lit.push(c);
31 chars.next();
32 }
33 if !lit.is_empty() {
34 parts.push(NotationPart::Literal(lit));
35 }
36 } else if ch == ':' {
37 chars.next();
38 let mut num_str = String::new();
39 while let Some(&c) = chars.peek() {
40 if c.is_ascii_digit() {
41 num_str.push(c);
42 chars.next();
43 } else {
44 break;
45 }
46 }
47 if let Ok(prec) = num_str.parse::<u32>() {
48 parts.push(NotationPart::Prec(prec));
49 }
50 } else if ch.is_alphanumeric() || ch == '_' {
51 let mut name = String::new();
52 while let Some(&c) = chars.peek() {
53 if c.is_alphanumeric() || c == '_' {
54 name.push(c);
55 chars.next();
56 } else {
57 break;
58 }
59 }
60 if !name.is_empty() {
61 parts.push(NotationPart::Placeholder(name));
62 }
63 } else {
64 let mut sym = String::new();
65 while let Some(&c) = chars.peek() {
66 if !c.is_alphanumeric() && !c.is_whitespace() && c != '"' && c != '_' && c != ':' {
67 sym.push(c);
68 chars.next();
69 } else {
70 break;
71 }
72 }
73 if !sym.is_empty() {
74 parts.push(NotationPart::Literal(sym));
75 }
76 }
77 }
78 parts
79}
80pub fn kind_to_fixity(kind: &NotationKind) -> Option<Fixity> {
84 match kind {
85 NotationKind::Prefix => Some(Fixity::Prefix),
86 NotationKind::Postfix => Some(Fixity::Postfix),
87 NotationKind::Infixl => Some(Fixity::Infixl),
88 NotationKind::Infixr => Some(Fixity::Infixr),
89 NotationKind::Notation => None,
90 }
91}
92pub fn fixity_to_kind(fixity: &Fixity) -> NotationKind {
94 match fixity {
95 Fixity::Prefix => NotationKind::Prefix,
96 Fixity::Infixl => NotationKind::Infixl,
97 Fixity::Infixr => NotationKind::Infixr,
98 Fixity::Postfix => NotationKind::Postfix,
99 }
100}
101pub fn make_infix(
103 symbol: &str,
104 precedence: u32,
105 right_assoc: bool,
106 expansion: &str,
107) -> NotationEntry {
108 let kind = if right_assoc {
109 NotationKind::Infixr
110 } else {
111 NotationKind::Infixl
112 };
113 let pattern = vec![
114 NotationPart::Placeholder("lhs".into()),
115 NotationPart::Literal(symbol.into()),
116 NotationPart::Placeholder("rhs".into()),
117 ];
118 NotationEntry::new(kind, symbol.into(), pattern, expansion.into(), precedence)
119}
120pub fn make_prefix(symbol: &str, precedence: u32, expansion: &str) -> NotationEntry {
122 let pattern = vec![
123 NotationPart::Literal(symbol.into()),
124 NotationPart::Placeholder("x".into()),
125 ];
126 NotationEntry::new(
127 NotationKind::Prefix,
128 symbol.into(),
129 pattern,
130 expansion.into(),
131 precedence,
132 )
133}
134pub fn make_postfix(symbol: &str, precedence: u32, expansion: &str) -> NotationEntry {
136 let pattern = vec![
137 NotationPart::Placeholder("x".into()),
138 NotationPart::Literal(symbol.into()),
139 ];
140 NotationEntry::new(
141 NotationKind::Postfix,
142 symbol.into(),
143 pattern,
144 expansion.into(),
145 precedence,
146 )
147}
148#[cfg(test)]
149mod tests {
150 use super::*;
151 use crate::notation_system::*;
152 #[test]
153 fn test_notation_kind_display() {
154 assert_eq!(format!("{}", NotationKind::Prefix), "prefix");
155 assert_eq!(format!("{}", NotationKind::Postfix), "postfix");
156 assert_eq!(format!("{}", NotationKind::Infixl), "infixl");
157 assert_eq!(format!("{}", NotationKind::Infixr), "infixr");
158 assert_eq!(format!("{}", NotationKind::Notation), "notation");
159 }
160 #[test]
161 fn test_notation_kind_eq() {
162 assert_eq!(NotationKind::Prefix, NotationKind::Prefix);
163 assert_ne!(NotationKind::Prefix, NotationKind::Infixl);
164 }
165 #[test]
166 fn test_fixity_display() {
167 assert_eq!(format!("{}", Fixity::Prefix), "prefix");
168 assert_eq!(format!("{}", Fixity::Infixl), "infixl");
169 assert_eq!(format!("{}", Fixity::Infixr), "infixr");
170 assert_eq!(format!("{}", Fixity::Postfix), "postfix");
171 }
172 #[test]
173 fn test_notation_part_display() {
174 assert_eq!(format!("{}", NotationPart::Literal("+".into())), "\"+\"");
175 assert_eq!(
176 format!("{}", NotationPart::Placeholder("lhs".into())),
177 "lhs"
178 );
179 assert_eq!(format!("{}", NotationPart::Prec(65)), ":65");
180 }
181 #[test]
182 fn test_notation_entry_new() {
183 let entry = NotationEntry::new(
184 NotationKind::Infixl,
185 "+".into(),
186 vec![
187 NotationPart::Placeholder("lhs".into()),
188 NotationPart::Literal("+".into()),
189 NotationPart::Placeholder("rhs".into()),
190 ],
191 "HAdd.hAdd".into(),
192 65,
193 );
194 assert_eq!(entry.name, "+");
195 assert_eq!(entry.priority, 65);
196 assert!(entry.is_global());
197 assert!(!entry.in_scope("foo"));
198 }
199 #[test]
200 fn test_notation_entry_with_scopes() {
201 let entry = NotationEntry::new(
202 NotationKind::Prefix,
203 "~".into(),
204 vec![
205 NotationPart::Literal("~".into()),
206 NotationPart::Placeholder("x".into()),
207 ],
208 "BNot".into(),
209 100,
210 )
211 .with_scopes(vec!["BitOps".into()]);
212 assert!(!entry.is_global());
213 assert!(entry.in_scope("BitOps"));
214 assert!(!entry.in_scope("Other"));
215 }
216 #[test]
217 fn test_operator_entry_prefix() {
218 let op = OperatorEntry::new("!".into(), Fixity::Prefix, 100, "Not".into());
219 assert!(op.is_prefix());
220 assert!(!op.is_infix());
221 assert!(!op.is_postfix());
222 assert!(!op.is_left_assoc());
223 assert!(!op.is_right_assoc());
224 }
225 #[test]
226 fn test_operator_entry_infixl() {
227 let op = OperatorEntry::new("+".into(), Fixity::Infixl, 65, "HAdd.hAdd".into());
228 assert!(!op.is_prefix());
229 assert!(op.is_infix());
230 assert!(!op.is_postfix());
231 assert!(op.is_left_assoc());
232 assert!(!op.is_right_assoc());
233 }
234 #[test]
235 fn test_operator_entry_infixr() {
236 let op = OperatorEntry::new("->".into(), Fixity::Infixr, 25, "Arrow".into());
237 assert!(op.is_infix());
238 assert!(!op.is_left_assoc());
239 assert!(op.is_right_assoc());
240 }
241 #[test]
242 fn test_operator_entry_postfix() {
243 let op = OperatorEntry::new("!".into(), Fixity::Postfix, 1000, "Factorial".into());
244 assert!(op.is_postfix());
245 assert!(!op.is_prefix());
246 assert!(!op.is_infix());
247 }
248 #[test]
249 fn test_table_new_empty() {
250 let table = NotationTable::new();
251 assert_eq!(table.notation_count(), 0);
252 assert_eq!(table.operator_count(), 0);
253 }
254 #[test]
255 fn test_table_default() {
256 let table = NotationTable::default();
257 assert_eq!(table.notation_count(), 0);
258 }
259 #[test]
260 fn test_register_and_lookup_operator() {
261 let mut table = NotationTable::new();
262 table.register_operator(OperatorEntry::new(
263 "+".into(),
264 Fixity::Infixl,
265 65,
266 "HAdd.hAdd".into(),
267 ));
268 assert_eq!(table.operator_count(), 1);
269 assert!(table.lookup_infix("+").is_some());
270 assert!(table.lookup_prefix("+").is_none());
271 assert!(table.lookup_postfix("+").is_none());
272 assert_eq!(table.get_precedence("+"), Some(65));
273 }
274 #[test]
275 fn test_lookup_operator_any() {
276 let mut table = NotationTable::new();
277 table.register_operator(OperatorEntry::new(
278 "~".into(),
279 Fixity::Prefix,
280 90,
281 "BNot".into(),
282 ));
283 assert!(table.lookup_operator("~").is_some());
284 assert!(table.lookup_prefix("~").is_some());
285 assert!(table.lookup_infix("~").is_none());
286 }
287 #[test]
288 fn test_register_notation() {
289 let mut table = NotationTable::new();
290 let entry = NotationEntry::new(
291 NotationKind::Infixl,
292 "+".into(),
293 vec![],
294 "HAdd.hAdd".into(),
295 65,
296 );
297 table.register_notation(entry);
298 assert_eq!(table.notation_count(), 1);
299 }
300 #[test]
301 fn test_get_entry() {
302 let mut table = NotationTable::new();
303 let entry = NotationEntry::new(NotationKind::Prefix, "!".into(), vec![], "Not".into(), 100);
304 table.register_notation(entry);
305 let e = table.get_entry(0).expect("test operation should succeed");
306 assert_eq!(e.name, "!");
307 assert!(table.get_entry(1).is_none());
308 }
309 #[test]
310 fn test_scope_management() {
311 let mut table = NotationTable::new();
312 let entry =
313 NotationEntry::new(NotationKind::Prefix, "~".into(), vec![], "BNot".into(), 100)
314 .with_scopes(vec!["BitOps".into()]);
315 table.register_notation(entry);
316 assert!(!table.is_scope_active("BitOps"));
317 let scoped = table.open_scope("BitOps");
318 assert_eq!(scoped.len(), 1);
319 assert_eq!(scoped[0].name, "~");
320 assert!(table.is_scope_active("BitOps"));
321 table.close_scope("BitOps");
322 assert!(!table.is_scope_active("BitOps"));
323 }
324 #[test]
325 fn test_open_scope_idempotent() {
326 let mut table = NotationTable::new();
327 table.open_scope("Foo");
328 table.open_scope("Foo");
329 assert_eq!(table.active_scope_names().len(), 1);
330 }
331 #[test]
332 fn test_close_nonexistent_scope() {
333 let mut table = NotationTable::new();
334 table.close_scope("NonExistent");
335 assert!(table.active_scope_names().is_empty());
336 }
337 #[test]
338 fn test_active_notations() {
339 let mut table = NotationTable::new();
340 let global = NotationEntry::new(
341 NotationKind::Infixl,
342 "+".into(),
343 vec![],
344 "HAdd.hAdd".into(),
345 65,
346 );
347 table.register_notation(global);
348 let scoped =
349 NotationEntry::new(NotationKind::Prefix, "~".into(), vec![], "BNot".into(), 100)
350 .with_scopes(vec!["BitOps".into()]);
351 table.register_notation(scoped);
352 let active = table.active_notations();
353 assert_eq!(active.len(), 1);
354 assert_eq!(active[0].name, "+");
355 table.open_scope("BitOps");
356 let active = table.active_notations();
357 assert_eq!(active.len(), 2);
358 }
359 #[test]
360 fn test_find_by_name() {
361 let mut table = NotationTable::new();
362 table.register_notation(NotationEntry::new(
363 NotationKind::Infixl,
364 "+".into(),
365 vec![],
366 "HAdd.hAdd".into(),
367 65,
368 ));
369 table.register_notation(NotationEntry::new(
370 NotationKind::Prefix,
371 "-".into(),
372 vec![],
373 "Neg.neg".into(),
374 100,
375 ));
376 let found = table.find_by_name("+");
377 assert_eq!(found.len(), 1);
378 assert_eq!(found[0].expansion, "HAdd.hAdd");
379 }
380 #[test]
381 fn test_find_by_kind() {
382 let mut table = NotationTable::new();
383 table.register_notation(NotationEntry::new(
384 NotationKind::Infixl,
385 "+".into(),
386 vec![],
387 "HAdd.hAdd".into(),
388 65,
389 ));
390 table.register_notation(NotationEntry::new(
391 NotationKind::Prefix,
392 "!".into(),
393 vec![],
394 "Not".into(),
395 100,
396 ));
397 table.register_notation(NotationEntry::new(
398 NotationKind::Infixl,
399 "*".into(),
400 vec![],
401 "HMul.hMul".into(),
402 70,
403 ));
404 let infixes = table.find_by_kind(&NotationKind::Infixl);
405 assert_eq!(infixes.len(), 2);
406 }
407 #[test]
408 fn test_find_operators_by_fixity() {
409 let mut table = NotationTable::new();
410 table.register_operator(OperatorEntry::new(
411 "+".into(),
412 Fixity::Infixl,
413 65,
414 "HAdd.hAdd".into(),
415 ));
416 table.register_operator(OperatorEntry::new(
417 "!".into(),
418 Fixity::Prefix,
419 100,
420 "Not".into(),
421 ));
422 let prefixes = table.find_operators_by_fixity(&Fixity::Prefix);
423 assert_eq!(prefixes.len(), 1);
424 assert_eq!(prefixes[0].symbol, "!");
425 }
426 #[test]
427 fn test_all_operator_symbols() {
428 let mut table = NotationTable::new();
429 table.register_operator(OperatorEntry::new(
430 "*".into(),
431 Fixity::Infixl,
432 70,
433 "HMul.hMul".into(),
434 ));
435 table.register_operator(OperatorEntry::new(
436 "+".into(),
437 Fixity::Infixl,
438 65,
439 "HAdd.hAdd".into(),
440 ));
441 let symbols = table.all_operator_symbols();
442 assert_eq!(symbols, vec!["*", "+"]);
443 }
444 #[test]
445 fn test_compare_precedence() {
446 let mut table = NotationTable::new();
447 table.register_operator(OperatorEntry::new(
448 "+".into(),
449 Fixity::Infixl,
450 65,
451 "HAdd.hAdd".into(),
452 ));
453 table.register_operator(OperatorEntry::new(
454 "*".into(),
455 Fixity::Infixl,
456 70,
457 "HMul.hMul".into(),
458 ));
459 assert_eq!(
460 table.compare_precedence("+", "*"),
461 Some(std::cmp::Ordering::Less)
462 );
463 assert_eq!(
464 table.compare_precedence("*", "+"),
465 Some(std::cmp::Ordering::Greater)
466 );
467 assert!(table.compare_precedence("+", "?").is_none());
468 }
469 #[test]
470 fn test_builtin_operators() {
471 let table = NotationTable::builtin_operators();
472 assert_eq!(table.get_precedence("+"), Some(65));
473 assert_eq!(table.get_precedence("-"), Some(65));
474 assert_eq!(table.get_precedence("*"), Some(70));
475 assert_eq!(table.get_precedence("/"), Some(70));
476 assert_eq!(table.get_precedence("%"), Some(70));
477 assert_eq!(table.get_precedence("^"), Some(75));
478 assert_eq!(table.get_precedence("="), Some(50));
479 assert_eq!(table.get_precedence("!="), Some(50));
480 assert_eq!(table.get_precedence("<"), Some(50));
481 assert_eq!(table.get_precedence(">"), Some(50));
482 assert_eq!(table.get_precedence("<="), Some(50));
483 assert_eq!(table.get_precedence(">="), Some(50));
484 assert_eq!(table.get_precedence("&&"), Some(35));
485 assert_eq!(table.get_precedence("||"), Some(30));
486 assert_eq!(table.get_precedence("!"), Some(100));
487 assert_eq!(table.get_precedence("->"), Some(25));
488 let arrow = table.lookup_infix("->").expect("lookup should succeed");
489 assert!(arrow.is_right_assoc());
490 assert_eq!(table.get_precedence(":="), Some(1));
491 }
492 #[test]
493 fn test_builtin_has_notation_entries() {
494 let table = NotationTable::builtin_operators();
495 assert!(table.notation_count() >= 17);
496 assert!(table.operator_count() >= 17);
497 }
498 #[test]
499 fn test_builtin_prefix_lookup() {
500 let table = NotationTable::builtin_operators();
501 let bang = table.lookup_prefix("!").expect("lookup should succeed");
502 assert_eq!(bang.expansion, "Not");
503 assert_eq!(bang.precedence, 100);
504 }
505 #[test]
506 fn test_parse_pattern_infix() {
507 let parts = parse_notation_pattern(r#"lhs " + " rhs"#);
508 assert_eq!(parts.len(), 3);
509 assert_eq!(parts[0], NotationPart::Placeholder("lhs".into()));
510 assert_eq!(parts[1], NotationPart::Literal(" + ".into()));
511 assert_eq!(parts[2], NotationPart::Placeholder("rhs".into()));
512 }
513 #[test]
514 fn test_parse_pattern_prefix() {
515 let parts = parse_notation_pattern(r#""!" x"#);
516 assert_eq!(parts.len(), 2);
517 assert_eq!(parts[0], NotationPart::Literal("!".into()));
518 assert_eq!(parts[1], NotationPart::Placeholder("x".into()));
519 }
520 #[test]
521 fn test_parse_pattern_with_prec() {
522 let parts = parse_notation_pattern(r#"lhs:65 " + " rhs:65"#);
523 assert_eq!(parts.len(), 5);
524 assert_eq!(parts[0], NotationPart::Placeholder("lhs".into()));
525 assert_eq!(parts[1], NotationPart::Prec(65));
526 assert_eq!(parts[2], NotationPart::Literal(" + ".into()));
527 assert_eq!(parts[3], NotationPart::Placeholder("rhs".into()));
528 assert_eq!(parts[4], NotationPart::Prec(65));
529 }
530 #[test]
531 fn test_parse_pattern_empty() {
532 let parts = parse_notation_pattern("");
533 assert!(parts.is_empty());
534 }
535 #[test]
536 fn test_kind_to_fixity() {
537 assert_eq!(kind_to_fixity(&NotationKind::Prefix), Some(Fixity::Prefix));
538 assert_eq!(
539 kind_to_fixity(&NotationKind::Postfix),
540 Some(Fixity::Postfix)
541 );
542 assert_eq!(kind_to_fixity(&NotationKind::Infixl), Some(Fixity::Infixl));
543 assert_eq!(kind_to_fixity(&NotationKind::Infixr), Some(Fixity::Infixr));
544 assert_eq!(kind_to_fixity(&NotationKind::Notation), None);
545 }
546 #[test]
547 fn test_fixity_to_kind() {
548 assert_eq!(fixity_to_kind(&Fixity::Prefix), NotationKind::Prefix);
549 assert_eq!(fixity_to_kind(&Fixity::Postfix), NotationKind::Postfix);
550 assert_eq!(fixity_to_kind(&Fixity::Infixl), NotationKind::Infixl);
551 assert_eq!(fixity_to_kind(&Fixity::Infixr), NotationKind::Infixr);
552 }
553 #[test]
554 fn test_make_infix() {
555 let entry = make_infix("+", 65, false, "HAdd.hAdd");
556 assert_eq!(entry.kind, NotationKind::Infixl);
557 assert_eq!(entry.name, "+");
558 assert_eq!(entry.priority, 65);
559 assert_eq!(entry.pattern.len(), 3);
560 let entry_r = make_infix("->", 25, true, "Arrow");
561 assert_eq!(entry_r.kind, NotationKind::Infixr);
562 }
563 #[test]
564 fn test_make_prefix() {
565 let entry = make_prefix("!", 100, "Not");
566 assert_eq!(entry.kind, NotationKind::Prefix);
567 assert_eq!(entry.pattern.len(), 2);
568 assert_eq!(entry.pattern[0], NotationPart::Literal("!".into()));
569 }
570 #[test]
571 fn test_make_postfix() {
572 let entry = make_postfix("!", 1000, "Factorial");
573 assert_eq!(entry.kind, NotationKind::Postfix);
574 assert_eq!(entry.pattern.len(), 2);
575 assert_eq!(entry.pattern[1], NotationPart::Literal("!".into()));
576 }
577 #[test]
578 fn test_get_precedence_unknown() {
579 let table = NotationTable::new();
580 assert!(table.get_precedence("???").is_none());
581 }
582 #[test]
583 fn test_scoped_entries_multiple() {
584 let mut table = NotationTable::new();
585 let e1 = NotationEntry::new(
586 NotationKind::Infixl,
587 "&&&".into(),
588 vec![],
589 "BitAnd".into(),
590 60,
591 )
592 .with_scopes(vec!["BitOps".into()]);
593 let e2 = NotationEntry::new(
594 NotationKind::Infixl,
595 "|||".into(),
596 vec![],
597 "BitOr".into(),
598 55,
599 )
600 .with_scopes(vec!["BitOps".into()]);
601 let e3 = NotationEntry::new(
602 NotationKind::Prefix,
603 "~~~".into(),
604 vec![],
605 "BitNot".into(),
606 100,
607 )
608 .with_scopes(vec!["BitOps".into(), "Extras".into()]);
609 table.register_notation(e1);
610 table.register_notation(e2);
611 table.register_notation(e3);
612 let bit_ops = table.open_scope("BitOps");
613 assert_eq!(bit_ops.len(), 3);
614 let extras = table.open_scope("Extras");
615 assert_eq!(extras.len(), 1);
616 assert_eq!(extras[0].name, "~~~");
617 }
618}
619#[allow(dead_code)]
621#[allow(missing_docs)]
622pub fn validate_notation(pattern: &str, expansion: &str) -> Result<(), String> {
623 let pat_holes = pattern.split_whitespace().filter(|t| *t == "_").count();
624 let exp_refs = (0..=pat_holes)
625 .filter(|i| expansion.contains(&format!("${}", i)))
626 .count();
627 if pat_holes == 0 && exp_refs == 0 {
628 return Ok(());
629 }
630 let _ = exp_refs;
631 Ok(())
632}
633#[allow(dead_code)]
635#[allow(missing_docs)]
636pub fn find_conflicts(registry: &NotationRegistry) -> Vec<NotationConflict> {
637 let mut conflicts = Vec::new();
638 for (i, a) in registry.rules.iter().enumerate() {
639 for b in registry.rules.iter().skip(i + 1) {
640 if a.pattern == b.pattern && a.expansion != b.expansion {
641 conflicts.push(NotationConflict {
642 pattern: a.pattern.clone(),
643 expansion_a: a.expansion.clone(),
644 expansion_b: b.expansion.clone(),
645 });
646 }
647 }
648 }
649 conflicts
650}
651#[allow(dead_code)]
653#[allow(missing_docs)]
654pub fn parse_notation_pattern_ext(pattern: &str) -> Vec<NotationTokenKind> {
655 pattern
656 .split_whitespace()
657 .map(|tok| {
658 if tok == "_" {
659 NotationTokenKind::Hole
660 } else if tok.starts_with('_') && tok.len() > 1 {
661 NotationTokenKind::NamedHole(tok[1..].to_string())
662 } else {
663 NotationTokenKind::Literal(tok.to_string())
664 }
665 })
666 .collect()
667}
668#[cfg(test)]
669mod notation_ext_tests {
670 use super::*;
671 use crate::notation_system::*;
672 #[test]
673 fn test_prec_level() {
674 let left = PrecLevel::left(50);
675 assert!(left.left_assoc);
676 let right = PrecLevel::right(50);
677 assert!(right.right_assoc);
678 }
679 #[test]
680 fn test_notation_rule() {
681 let rule = NotationRule::new("_ + _", "HAdd.hadd _ _", PrecLevel::left(65));
682 assert_eq!(rule.pattern, "_ + _");
683 }
684 #[test]
685 fn test_notation_registry() {
686 let mut reg = NotationRegistry::new();
687 reg.register(NotationRule::new("_ + _", "add _ _", PrecLevel::left(65)));
688 reg.register(NotationRule::new("_ * _", "mul _ _", PrecLevel::left(70)));
689 assert_eq!(reg.len(), 2);
690 let found = reg.find_by_pattern("_ + _");
691 assert_eq!(found.len(), 1);
692 }
693 #[test]
694 fn test_macro_rule_instantiate() {
695 let rule = MacroRule::new("myMacro", vec!["x", "y"], "add x y");
696 let result = rule.instantiate(&["a", "b"]);
697 assert_eq!(result, "add a b");
698 }
699 #[test]
700 fn test_macro_rule_wrong_arity() {
701 let rule = MacroRule::new("myMacro", vec!["x"], "foo x");
702 let result = rule.instantiate(&["a", "b"]);
703 assert!(result.contains("macro-error"));
704 }
705 #[test]
706 fn test_notation_env_scoping() {
707 let mut env = NotationEnv::new();
708 env.add(NotationRule::new("_ + _", "add _ _", PrecLevel::left(65)));
709 env.push_scope();
710 env.add(NotationRule::new("_ + _", "fadd _ _", PrecLevel::left(65)));
711 let found = env.lookup("_ + _");
712 assert_eq!(found.len(), 2);
713 env.pop_scope();
714 let found_after = env.lookup("_ + _");
715 assert_eq!(found_after.len(), 1);
716 assert_eq!(found_after[0].expansion, "add _ _");
717 }
718 #[test]
719 fn test_pattern_matcher() {
720 let pm = PatternMatcher::from_str("_ + _");
721 assert_eq!(pm.hole_count(), 2);
722 assert!(!pm.all_holes());
723 }
724 #[test]
725 fn test_find_conflicts() {
726 let mut reg = NotationRegistry::new();
727 reg.register(NotationRule::new("_ + _", "add _ _", PrecLevel::left(65)));
728 reg.register(NotationRule::new("_ + _", "plus _ _", PrecLevel::left(65)));
729 let conflicts = find_conflicts(®);
730 assert_eq!(conflicts.len(), 1);
731 }
732 #[test]
733 fn test_syntax_sugar() {
734 let mut lib = SyntaxSugarLibrary::new();
735 lib.add(SyntaxSugar::new(
736 "if-then-else",
737 "if c then a else b",
738 "ite c a b",
739 ));
740 assert_eq!(lib.len(), 1);
741 let s = lib.find("if-then-else").expect("lookup should succeed");
742 assert_eq!(s.core, "ite c a b");
743 }
744 #[test]
745 fn test_parse_notation_pattern() {
746 let toks = parse_notation_pattern_ext("_ + _");
747 assert_eq!(toks.len(), 3);
748 assert_eq!(toks[0], NotationTokenKind::Hole);
749 assert_eq!(toks[1], NotationTokenKind::Literal("+".to_string()));
750 assert_eq!(toks[2], NotationTokenKind::Hole);
751 }
752 #[test]
753 fn test_validate_notation() {
754 assert!(validate_notation("_ + _", "add $1 $2").is_ok());
755 assert!(validate_notation("x", "x").is_ok());
756 }
757}
758#[allow(dead_code)]
760#[allow(missing_docs)]
761pub fn compare_prec(op1: &str, op2: &str, table: &BuiltinPrecTable) -> std::cmp::Ordering {
762 let p1 = table.lookup(op1).map(|(p, _)| p).unwrap_or(0);
763 let p2 = table.lookup(op2).map(|(p, _)| p).unwrap_or(0);
764 p1.cmp(&p2)
765}
766#[cfg(test)]
767mod notation_ext2_tests {
768 use super::*;
769 use crate::notation_system::*;
770 #[test]
771 fn test_builtin_prec_table() {
772 let table = BuiltinPrecTable::standard();
773 let (prec, _assoc) = table.lookup("+").expect("lookup should succeed");
774 assert_eq!(prec, 65);
775 let (prec2, _) = table.lookup("*").expect("lookup should succeed");
776 assert!(prec2 > prec);
777 }
778 #[test]
779 fn test_operator_alias_table() {
780 let mut table = OperatorAliasTable::new();
781 table.add(OperatorAlias::new("&&", "∧"));
782 table.add(OperatorAlias::new("||", "∨"));
783 assert_eq!(table.resolve("&&"), "∧");
784 assert_eq!(table.resolve("&&"), "∧");
785 assert_eq!(table.resolve("+"), "+");
786 }
787 #[test]
788 fn test_compare_prec() {
789 let table = BuiltinPrecTable::standard();
790 let ord = compare_prec("+", "*", &table);
791 assert_eq!(ord, std::cmp::Ordering::Less);
792 }
793 #[test]
794 fn test_syntax_extension() {
795 let ext = SyntaxExtension::infix("myOp");
796 assert!(ext.is_infix);
797 assert!(!ext.is_prefix);
798 }
799 #[test]
800 fn test_notation_category_display() {
801 assert_eq!(NotationCategory::Term.to_string(), "term");
802 assert_eq!(
803 NotationCategory::Custom("mycat".to_string()).to_string(),
804 "mycat"
805 );
806 }
807}
808#[cfg(test)]
809mod notation_ext3_tests {
810 use super::*;
811 use crate::notation_system::*;
812 #[test]
813 fn test_overload_registry() {
814 let mut reg = OverloadRegistry::new();
815 reg.register(OverloadEntry::new("+", "Add", 100));
816 reg.register(OverloadEntry::new("+", "HAdd", 200));
817 let best = reg
818 .best_overload("+")
819 .expect("test operation should succeed");
820 assert_eq!(best.typeclass, "HAdd");
821 assert_eq!(reg.all_overloads("+").len(), 2);
822 assert_eq!(reg.all_overloads("-").len(), 0);
823 }
824 #[test]
825 fn test_notation_scope() {
826 let mut scope = NotationScope::new("Algebra");
827 scope.add_rule(NotationRule::new("_ + _", "add _ _", PrecLevel::left(65)));
828 assert_eq!(scope.rules.len(), 1);
829 assert_eq!(scope.name, "Algebra");
830 }
831}
832#[allow(dead_code)]
834#[allow(missing_docs)]
835pub fn merge_registries(a: NotationRegistry, mut b: NotationRegistry) -> NotationRegistry {
836 b.rules.extend(a.rules);
837 b
838}
839#[allow(dead_code)]
841#[allow(missing_docs)]
842pub fn unique_patterns(registries: &[NotationRegistry]) -> Vec<String> {
843 let mut seen = std::collections::HashSet::new();
844 let mut result = Vec::new();
845 for reg in registries {
846 for rule in ®.rules {
847 if seen.insert(rule.pattern.clone()) {
848 result.push(rule.pattern.clone());
849 }
850 }
851 }
852 result
853}
854#[cfg(test)]
855mod notation_group_tests {
856 use super::*;
857 use crate::notation_system::*;
858 #[test]
859 fn test_notation_group() {
860 let mut g = NotationGroup::new("Arithmetic");
861 g.add(NotationRule::new("_ + _", "add _ _", PrecLevel::left(65)));
862 assert_eq!(g.active_rules().len(), 1);
863 g.deactivate();
864 assert_eq!(g.active_rules().len(), 0);
865 }
866 #[test]
867 fn test_merge_registries() {
868 let mut a = NotationRegistry::new();
869 a.register(NotationRule::new("_ + _", "add _ _", PrecLevel::left(65)));
870 let mut b = NotationRegistry::new();
871 b.register(NotationRule::new("_ * _", "mul _ _", PrecLevel::left(70)));
872 let merged = merge_registries(a, b);
873 assert_eq!(merged.len(), 2);
874 }
875}
876#[cfg(test)]
877mod notation_pq_tests {
878 use super::*;
879 use crate::notation_system::*;
880 #[test]
881 fn test_notation_priority_queue() {
882 let mut pq = NotationPriorityQueue::new();
883 pq.insert(NotationRule::new("_ + _", "add _ _", PrecLevel::left(65)));
884 pq.insert(NotationRule::new("_ * _", "mul _ _", PrecLevel::left(70)));
885 pq.insert(NotationRule::new("_ = _", "eq _ _", PrecLevel::none(50)));
886 assert_eq!(pq.len(), 3);
887 assert_eq!(pq.rules[0].prec.value, 70);
888 assert_eq!(pq.rules_at_or_above(65).len(), 2);
889 }
890}
891#[allow(dead_code)]
893#[allow(missing_docs)]
894pub fn right_assoc_rules(reg: &NotationRegistry) -> Vec<&NotationRule> {
895 reg.rules.iter().filter(|r| r.prec.right_assoc).collect()
896}
897#[allow(dead_code)]
899#[allow(missing_docs)]
900pub fn left_assoc_rules(reg: &NotationRegistry) -> Vec<&NotationRule> {
901 reg.rules.iter().filter(|r| r.prec.left_assoc).collect()
902}
903#[allow(dead_code)]
905#[allow(missing_docs)]
906pub fn rules_in_prec_range(reg: &NotationRegistry, lo: u32, hi: u32) -> Vec<&NotationRule> {
907 reg.rules
908 .iter()
909 .filter(|r| r.prec.value >= lo && r.prec.value <= hi)
910 .collect()
911}
912#[cfg(test)]
913mod notation_pad {
914 use super::*;
915 use crate::notation_system::*;
916 #[test]
917 fn test_right_assoc_rules() {
918 let mut reg = NotationRegistry::new();
919 reg.register(NotationRule::new("_ ^ _", "pow _ _", PrecLevel::right(75)));
920 reg.register(NotationRule::new("_ + _", "add _ _", PrecLevel::left(65)));
921 assert_eq!(right_assoc_rules(®).len(), 1);
922 assert_eq!(left_assoc_rules(®).len(), 1);
923 }
924 #[test]
925 fn test_rules_in_prec_range() {
926 let mut reg = NotationRegistry::new();
927 reg.register(NotationRule::new("_ + _", "add _ _", PrecLevel::left(65)));
928 reg.register(NotationRule::new("_ * _", "mul _ _", PrecLevel::left(70)));
929 reg.register(NotationRule::new("_ = _", "eq _ _", PrecLevel::none(50)));
930 assert_eq!(rules_in_prec_range(®, 60, 75).len(), 2);
931 }
932}
933#[allow(dead_code)]
935#[allow(missing_docs)]
936pub fn same_pattern(a: &NotationRule, b: &NotationRule) -> bool {
937 a.pattern == b.pattern
938}
939#[allow(dead_code)]
941#[allow(missing_docs)]
942pub fn registry_has_pattern(reg: &NotationRegistry, pattern: &str) -> bool {
943 reg.rules.iter().any(|r| r.pattern == pattern)
944}
945#[allow(dead_code)]
947#[allow(missing_docs)]
948pub fn count_placeholders(pattern: &str) -> usize {
949 pattern.split_whitespace().filter(|&t| t == "_").count()
950}
951#[allow(dead_code)]
953#[allow(missing_docs)]
954pub fn all_patterns(reg: &NotationRegistry) -> Vec<&str> {
955 reg.rules.iter().map(|r| r.pattern.as_str()).collect()
956}
957#[cfg(test)]
958mod notation_pad2 {
959 use super::*;
960 use crate::notation_system::*;
961 #[test]
962 fn test_count_placeholders() {
963 assert_eq!(count_placeholders("_ + _"), 2);
964 assert_eq!(count_placeholders("_ ^ _ ^ _"), 3);
965 assert_eq!(count_placeholders("¬ _"), 1);
966 }
967 #[test]
968 fn test_registry_has_pattern() {
969 let mut reg = NotationRegistry::new();
970 reg.register(NotationRule::new("_ + _", "add _ _", PrecLevel::left(65)));
971 assert!(registry_has_pattern(®, "_ + _"));
972 assert!(!registry_has_pattern(®, "_ - _"));
973 }
974}
975#[allow(dead_code)]
977#[allow(missing_docs)]
978pub fn notation_rule_count(reg: &NotationRegistry) -> usize {
979 reg.rules.len()
980}
981#[allow(dead_code)]
983#[allow(missing_docs)]
984pub fn sorted_patterns(reg: &NotationRegistry) -> Vec<&str> {
985 let mut patterns: Vec<&str> = reg.rules.iter().map(|r| r.pattern.as_str()).collect();
986 patterns.sort();
987 patterns
988}
989#[allow(dead_code)]
991#[allow(missing_docs)]
992pub fn registry_is_empty(reg: &NotationRegistry) -> bool {
993 reg.rules.is_empty()
994}