1use crate::lang::constraints::{
4 Atom, BaseSelection, Comparison, Conjunction, Constraint, Disjunction, Expr, Implies,
5 Inversion, Quantifier, QuantifierSpecification, RsPair, RsPairs, RsSlices, Selection, Selector,
6 SelectorLength,
7};
8use crate::lang::{
9 Alternative, Concatenation, FandangoNode, Nonterminal, Operator, Production, Program,
10 Statement, Symbol,
11};
12use alloc::collections::VecDeque;
13use alloc::vec::Vec;
14use core::iter;
15use core::ops::Deref;
16use hashbrown::HashMap;
17use pest::Span;
18use petgraph::graph::{DiGraph, NodeIndex};
19use petgraph::graphmap::NodeTrait;
20use petgraph::prelude::EdgeRef;
21use petgraph::visit::IntoNodeReferences;
22use petgraph::{Direction, graph};
23
24#[allow(unused_variables)]
26pub trait GraphTraverse<'program>: Sized {
27 type Node: NodeTrait + GraphTraverse<'program, Node = Self::Node> + From<Self>;
29
30 fn recurse<F>(self, mut consumer: F)
33 where
34 F: FnMut(Self::Node, Self::Node, Span<'program>),
35 {
36 self.traverse(|n1, n2, w| {
37 consumer(n1, n2, w);
38 n2.traverse(&mut consumer);
39 });
40 }
41
42 fn traverse<F>(self, consumer: F)
45 where
46 F: FnMut(Self::Node, Self::Node, Span<'program>),
47 {
48 }
49}
50
51pub fn traverse_children<'program, 'source, F, T>(
53 parent: T,
54 children: impl Iterator<Item = (T::Node, Span<'source>)>,
55 mut consumer: F,
56) where
57 F: FnMut(T::Node, T::Node, Span<'source>),
58 T: GraphTraverse<'program>,
59 'source: 'program,
60{
61 let node = parent.into();
62 for (child, weight) in children {
63 consumer(node, child, weight);
64 }
65}
66
67#[macro_export]
69macro_rules! chain_field_iter {
70 ($node:ty $(=> $from:tt)?, $current:expr) => {
71 $current
72 };
73
74 ($node:ty $(=> $from:tt)?, $current:expr, $field:tt) => {
75 $current.chain($crate::field_iter!($node $(=> $from)?, $field))
76 };
77
78 ($node:ty $(=> $from:tt)?, $current:expr, $field:tt, $($fields:tt),+) => {{
79 let next = $crate::chain_field_iter!($node $(=> $from)?, $current, $field);
80 $crate::chain_field_iter!($node $(=> $from)?, next, $($fields),+)
81 }};
82}
83
84#[macro_export]
86macro_rules! field_iter {
87 ($node:ty $(=> $from:tt)?) => {
88 ::core::iter::empty()
89 };
90
91 ($node:ty $(=> $from:tt)?, [ $field:tt ]) => {
92 $($from.)? $field.iter().map(::core::convert::From::from)
93 };
94
95 ($node:ty $(=> $from:tt)?, $field:tt) => {
96 ::core::iter::once(&$($from.)? $field).map(::core::convert::From::from)
97 };
98
99 ($node:ty $(=> $from:tt)?, $field:tt, $($fields:tt),+) => {{
100 let next = $crate::field_iter!($node $(=> $from)?, $field);
101 $crate::chain_field_iter!($node $(=> $from)?, next, $($fields),+)
102 }};
103}
104
105#[macro_export]
107macro_rules! variant_traverse {
108 ($from:tt, $consumer:tt, $node:ty, @($variant:tt { $($bindings:tt),+ } { $($iteration:tt)+ } { } $(, $($variants:tt)+)?), $($emitted:tt)*) => {
109 $crate::variant_traverse!(
110 $from,
111 $consumer,
112 $node,
113 @($($($variants)+)?),
114 $($emitted)*
115 $variant($($bindings),+) => { $crate::graph::traverse_children($from, $($iteration)+, $consumer) }
116 )
117 };
118
119 ($from:tt, $consumer:tt, $node:ty, @($variant:tt { $($bindings:tt),+ } { $($iteration:tt)+ } { _ $(, $($remaining:tt),+)? } $(, $($variants:tt)+)?), $($emitted:tt)*) => {
120 $crate::variant_traverse!(
121 $from,
122 $consumer,
123 $node,
124 @(
125 $variant
126 { $($bindings),+, _ }
127 { $($iteration)+ }
128 { $($($remaining),+)? }
129 $(, $($variants)+)?
130 ),
131 $($emitted)*
132 )
133 };
134
135 ($from:tt, $consumer:tt, $node:ty, @($variant:tt { $($bindings:tt),+ } { $($iteration:tt)+ } { $next:tt $(, $($remaining:tt),+)? } $(, $($variants:tt)+)?), $($emitted:tt)*) => {
136 $crate::variant_traverse!(
137 $from,
138 $consumer,
139 $node,
140 @(
141 $variant
142 { $($bindings),+, $next }
143 { $($iteration)+.chain(::core::iter::once($next).map(::core::convert::From::from)) }
144 { $($($remaining),+)? }
145 $(, $($variants)+)?
146 ),
147 $($emitted)*
148 )
149 };
150
151 ($from:tt, $consumer:tt, $node:ty, @($variant:tt { $($bindings:tt),+ } { $($iteration:tt)+ } { [ $next:tt ] $(, $($remaining:tt),+)? } $(, $($variants:tt)+)?), $($emitted:tt)*) => {
152 $crate::variant_traverse!(
153 $from,
154 $consumer,
155 $node,
156 @(
157 $variant
158 { $($bindings),+, $next }
159 { $($iteration)+.chain($next.iter().map(::core::convert::From::from)) }
160 { $($($remaining),+)? }
161 $(, $($variants)+)?
162 ),
163 $($emitted)*
164 )
165 };
166
167 ($from:tt, $consumer:tt, $node:ty, @($variant:tt { } { } { $next:tt $(, $($remaining:tt),+)? } $(, $($variants:tt)+)?), $($emitted:tt)*) => {
168 $crate::variant_traverse!(
169 $from,
170 $consumer,
171 $node,
172 @(
173 $variant
174 { $next }
175 { ::core::iter::once($next).map(::core::convert::From::from) }
176 { $($($remaining),+)? }
177 $(, $($variants)+)?
178 ),
179 $($emitted)*
180 )
181 };
182
183 ($from:tt, $consumer:tt, $node:ty, @($variant:tt { } { } { _ $(, $($remaining:tt),+)? } $(, $($variants:tt)+)?), $($emitted:tt)*) => {
184 $crate::variant_traverse!(
185 $from,
186 $consumer,
187 $node,
188 @(
189 $variant
190 { }
191 { }
192 { $($($remaining),+)? }
193 $(, $($variants)+)?
194 ),
195 $($emitted)*
196 )
197 };
198
199 ($from:tt, $consumer:tt, $node:ty, @($variant:tt { } { } { [ $next:tt ] $(, $($remaining:tt),+)? } $(, $($variants:tt)+)?), $($emitted:tt)*) => {
200 $crate::variant_traverse!(
201 $from,
202 $consumer,
203 $node,
204 @(
205 $variant
206 { $next }
207 { $next.iter().map(::core::convert::From::from) }
208 { $($($remaining),+)? }
209 $(, $($variants)+)?
210 ),
211 $($emitted)*
212 )
213 };
214
215 ($from:tt, $consumer:tt, $node:ty, @($variant:tt $(, $($variants:tt)+)?), $($emitted:tt)*) => {
216 $crate::variant_traverse!(
217 $from,
218 $consumer,
219 $node,
220 @($($($variants)+)?),
221 $($emitted)*
222 $variant => { }
223 )
224 };
225
226 ($from:tt, $consumer:tt, $node:ty, @($variant:tt ( $($options:tt),+ ) $(, $($variants:tt)+)?), $($emitted:tt)*) => {
227 $crate::variant_traverse!(
228 $from,
229 $consumer,
230 $node,
231 @(
232 $variant
233 { }
234 { }
235 { $($options),+ }
236 $(, $($variants)+)?
237 ),
238 $($emitted)*
239 )
240 };
241
242 ($from:tt, $consumer:tt, $node:ty, { $($variants:tt)+ }) => {
243 $crate::variant_traverse!(
244 $from,
245 $consumer,
246 $node,
247 @($($variants)+),
248 )
249 };
250
251 ($from:tt, $consumer:tt, $node:ty, @(), $($emitted:tt)*) => {
252 match $from {
253 $($emitted)*
254 }
255 };
256}
257
258#[macro_export]
303macro_rules! impl_traverse {
304 ($target:ty, $name:ty, $node:ty, < $($generics:tt $(: $constraints:tt)?),* >, match { $($variants:tt)+ }) => {
305 impl<'program, $($generics $(: $constraints)?),*> $crate::graph::GraphTraverse<'program> for $target {
306 type Node = $node;
307
308 fn traverse<F>(self, consumer: F)
309 where
310 F: ::core::ops::FnMut(Self::Node, Self::Node, $crate::lang::Span<'program>),
311 {
312 #![allow(unused_imports)]
313 use $name::*;
314 $crate::variant_traverse!(self, consumer, $node, { $($variants)+ })
315 }
316 }
317 };
318
319 ($target:ty, $name:ty, $node:ty, < $($generics:tt $(: $constraints:tt)?),* >, $($fields:tt),*) => {
320 impl<'program, $($generics $(: $constraints)?),*> $crate::graph::GraphTraverse<'program> for $target {
321 type Node = $node;
322
323 fn traverse<F>(self, consumer: F)
324 where
325 F: ::core::ops::FnMut(Self::Node, Self::Node, $crate::lang::Span<'program>),
326 {
327 $crate::graph::traverse_children(self, $crate::field_iter!($node => self, $($fields),*), consumer);
328 }
329 }
330 };
331
332 ($target:ty, $name:ty, $node:ty, match { $($variants:tt)+ }) => {
333 $crate::impl_traverse!($target, $name, $node, <>, { $($variants)+ })
334 };
335
336 ($target:ty, $name:ty, $node:ty, $($fields:tt),*) => {
337 $crate::impl_traverse!($target, $name, $node, <>, $($fields),*)
338 };
339}
340
341impl<'program, 'source> GraphTraverse<'program> for &'program Nonterminal<'source> {
342 type Node = FandangoNode<'program, 'source>;
343}
344
345impl<'program, 'source> GraphTraverse<'program> for FandangoNode<'program, 'source>
346where
347 'source: 'program,
348{
349 type Node = Self;
350
351 fn traverse<F>(self, consumer: F)
352 where
353 F: FnMut(Self::Node, Self::Node, Span<'program>),
354 {
355 match self {
356 FandangoNode::Program(s) => s.traverse(consumer),
357 FandangoNode::Statement(s) => s.traverse(consumer),
358 FandangoNode::Production(s) => s.traverse(consumer),
359 FandangoNode::Alternative(s) => s.traverse(consumer),
360 FandangoNode::Concatenation(s) => s.traverse(consumer),
361 FandangoNode::Operator(s) => s.traverse(consumer),
362 FandangoNode::Symbol(s) => s.traverse(consumer),
363 FandangoNode::Nonterminal(s) => s.traverse(consumer),
364 FandangoNode::Constraint(s) => s.traverse(consumer),
365 FandangoNode::Implies(s) => s.traverse(consumer),
366 FandangoNode::Quantifier(s) => s.traverse(consumer),
367 FandangoNode::QuantifierSpecification(s) => s.traverse(consumer),
368 FandangoNode::Disjunction(s) => s.traverse(consumer),
369 FandangoNode::Conjunction(s) => s.traverse(consumer),
370 FandangoNode::Atom(s) => s.traverse(consumer),
371 FandangoNode::Comparison(s) => s.traverse(consumer),
372 FandangoNode::Expr(s) => s.traverse(consumer),
373 FandangoNode::SelectorLength(s) => s.traverse(consumer),
374 FandangoNode::Selection(s) => s.traverse(consumer),
375 FandangoNode::Selector(s) => s.traverse(consumer),
376 FandangoNode::BaseSelection(s) => s.traverse(consumer),
377 FandangoNode::RsPairs(s) => s.traverse(consumer),
378 FandangoNode::RsPair(s) => s.traverse(consumer),
379 FandangoNode::RsSlices(s) => s.traverse(consumer),
380 FandangoNode::Inversion(s) => s.traverse(consumer),
381 FandangoNode::String(_)
383 | FandangoNode::ConstraintOperator(_)
384 | FandangoNode::RsSlice(_) => {}
385 }
386 }
387}
388
389impl<'program, 'source: 'program> GraphTraverse<'program> for &'program Selector<'source> {
390 type Node = FandangoNode<'program, 'source>;
391
392 fn traverse<F>(self, consumer: F)
393 where
394 F: FnMut(Self::Node, Self::Node, Span<'program>),
395 {
396 #![allow(unused_imports)]
397 use Selector::{Basic, ChildSelector, PathSelector};
398 match self {
399 ChildSelector(basic, child) => traverse_children(
400 self,
401 iter::once(basic)
402 .map(From::from)
403 .chain(iter::once(&**child).map(From::from)),
404 consumer,
405 ),
406 PathSelector(basic, descendent) => traverse_children(
407 self,
408 iter::once(basic)
409 .map(From::from)
410 .chain(iter::once(&**descendent).map(From::from)),
411 consumer,
412 ),
413 Basic(basic) => traverse_children(self, iter::once(basic).map(From::from), consumer),
414 }
415 }
416}
417impl<'program, 'source: 'program> GraphTraverse<'program> for &'program BaseSelection<'source> {
418 type Node = FandangoNode<'program, 'source>;
419
420 fn traverse<F>(self, consumer: F)
421 where
422 F: FnMut(Self::Node, Self::Node, Span<'program>),
423 {
424 #![allow(unused_imports)]
425 use BaseSelection::{Nonterminal, Selector};
426 match self {
427 Nonterminal(nonterminal) => {
428 traverse_children(self, iter::once(nonterminal).map(From::from), consumer);
429 }
430 Selector(selector) => {
431 traverse_children(self, iter::once(&**selector).map(From::from), consumer);
432 }
433 }
434 }
435}
436
437impl<'program, 'source: 'program> GraphTraverse<'program>
438 for &'program QuantifierSpecification<'source>
439{
440 type Node = FandangoNode<'program, 'source>;
441
442 fn traverse<F>(self, consumer: F)
443 where
444 F: FnMut(Self::Node, Self::Node, Span<'program>),
445 {
446 traverse_children(
447 self,
448 {
449 let next = iter::once(&self.nonterminal).map(From::from);
450 {
451 let next = next.chain(iter::once(&self.selector).map(From::from));
452 next.chain(
453 iter::once(&self.quantifier)
454 .map(Deref::deref)
455 .map(From::from),
456 )
457 }
458 },
459 consumer,
460 );
461 }
462}
463
464impl<'program, 'source: 'program> GraphTraverse<'program> for &'program Implies<'source> {
465 type Node = FandangoNode<'program, 'source>;
466
467 fn traverse<F>(self, consumer: F)
468 where
469 F: FnMut(Self::Node, Self::Node, Span<'program>),
470 {
471 traverse_children(
472 self,
473 {
474 let next = iter::once(&self.quantifier).map(From::from);
475 next.chain(self.implies.iter().map(Deref::deref).map(From::from))
476 },
477 consumer,
478 );
479 }
480}
481
482macro_rules! impl_fandango_traverse {
483 ($target:tt, match { $($variants:tt)+ }) => {
484 impl_traverse!(
485 &'program $target<'source>,
486 $target,
487 FandangoNode<'program, 'source>,
488 <'source: 'program>,
489 match { $($variants)+ }
490 );
491 };
492
493 ($target:tt, $($fields:tt),*) => {
494 impl_traverse!(
495 &'program $target<'source>,
496 $target,
497 FandangoNode<'program, 'source>,
498 <'source: 'program>,
499 $($fields),*
500 );
501 };
502}
503
504impl_fandango_traverse!(Program, [statements]);
505impl_fandango_traverse!(Statement, match { Production(prod), Constraint(constraint), Python });
506impl_fandango_traverse!(Production, nonterminal, alternative);
507impl_fandango_traverse!(Alternative, [concatenations]);
508impl_fandango_traverse!(Concatenation, [operators]);
509impl_fandango_traverse!(Operator, match { Kleene(sym), Plus(sym), Option(sym), Repeat(sym, _, _), Symbol(sym) });
510impl_fandango_traverse!(Symbol, match { Nonterminal(nt), String(s), Alternative(alt) });
511
512impl_fandango_traverse!(Constraint, match { Fitness(fitness), Implies(implies) });
513impl_fandango_traverse!(Quantifier, match { Forall(forall), Exists(exists), Disjunction(disjunction) });
514impl_fandango_traverse!(Disjunction, [conjunctions]);
515impl_fandango_traverse!(Conjunction, [atoms]);
516impl_fandango_traverse!(Atom, match { Comparison(comp), Implies(implies), Expr(expr) });
517impl_fandango_traverse!(Comparison, left, right, operator);
518impl_fandango_traverse!(Expr, match { Selector(selector), Inversion(inversion) });
519impl_fandango_traverse!(SelectorLength, match { WithLength(selector), NoLength(selector) });
520impl_fandango_traverse!(Selection, match { OverSlices(basic, slices), OverPairs(basic, pairs), Basic(basic) });
521impl_fandango_traverse!(RsPairs, [pairs]);
522impl_fandango_traverse!(RsPair, nonterminal, [slice]);
523impl_fandango_traverse!(RsSlices, [slices]);
524impl_fandango_traverse!(Inversion, match { Selector(selector), Stringified(selector) });
525
526pub trait IntoGraph<'program>: GraphTraverse<'program> {
528 fn into_graph(
530 self,
531 ) -> (
532 HashMap<Self::Node, NodeIndex>,
533 DiGraph<Self::Node, Span<'program>>,
534 );
535}
536
537impl<'program, 'source, T> IntoGraph<'program> for T
538where
539 T: GraphTraverse<'program, Node = FandangoNode<'program, 'source>>,
540 'source: 'program,
541{
542 fn into_graph(
543 self,
544 ) -> (
545 HashMap<FandangoNode<'program, 'source>, graph::NodeIndex>,
546 DiGraph<Self::Node, Span<'program>>,
547 ) {
548 let mut graph = DiGraph::new();
549 let mut work = VecDeque::new();
550 self.traverse(|n1, n2, w| work.push_back((n1, n2, w)));
551
552 let mut node_indices: HashMap<FandangoNode<'program, 'source>, graph::NodeIndex> =
553 HashMap::new();
554 let mut idx = |g: &mut DiGraph<FandangoNode<'program, 'source>, _>, n| {
555 *node_indices.entry(n).or_insert_with(|| g.add_node(n))
556 };
557
558 while let Some((n1, n2, w)) = work.pop_front() {
559 match n1 {
560 FandangoNode::Production(_) if matches!(n2, FandangoNode::Nonterminal(_)) => {
561 let n1 = idx(&mut graph, n1);
562 let n2 = idx(&mut graph, n2);
563 graph.add_edge(n1, n2, w);
564 }
565 FandangoNode::Production(prod) => {
566 work.push_back((prod.nonterminal().into(), n2, w));
567 }
568 FandangoNode::Alternative(alt) if alt.concatenations().len() == 1 => {
569 n2.traverse(|n1, n2, w| work.push_back((n1, n2, w)));
570 }
571 FandangoNode::Concatenation(concats) if concats.operators().len() == 1 => {
572 n2.traverse(|n1, n2, w| work.push_back((n1, n2, w)));
573 }
574 FandangoNode::Nonterminal(_)
575 | FandangoNode::Alternative(_)
576 | FandangoNode::Concatenation(_)
577 | FandangoNode::Operator(_) => match n2 {
578 FandangoNode::Alternative(alt) if alt.concatenations().len() == 1 => {
579 n2.traverse(|_, n2, w| work.push_back((n1, n2, w)));
580 }
581 FandangoNode::Concatenation(concats) if concats.operators().len() == 1 => {
582 n2.traverse(|_, n2, w| work.push_back((n1, n2, w)));
583 }
584 FandangoNode::Alternative(_)
585 | FandangoNode::Concatenation(_)
586 | FandangoNode::Operator(_)
587 if !matches!(n2, FandangoNode::Operator(Operator::Symbol(_))) =>
588 {
589 {
590 let n1 = idx(&mut graph, n1);
591 let n2 = idx(&mut graph, n2);
592 graph.add_edge(n1, n2, w);
593 }
594 n2.traverse(|n1, n2, w| work.push_back((n1, n2, w)));
595 }
596 FandangoNode::Nonterminal(_) | FandangoNode::String(_) => {
597 let n1 = idx(&mut graph, n1);
598 let n2 = idx(&mut graph, n2);
599 graph.add_edge(n1, n2, w);
600 }
601 _ => n2.traverse(|_, n2, w| work.push_back((n1, n2, w))),
602 },
603 _ => n2.traverse(|n1, n2, w| work.push_back((n1, n2, w))),
604 }
605 }
606
607 (node_indices, graph)
608 }
609}
610
611pub trait IntoNode {
613 type Node;
615
616 fn into_node(self) -> Self::Node;
618}
619
620#[must_use]
623#[allow(clippy::missing_panics_doc)]
624pub fn shortest_path<'program, 'source>(
625 graph: &DiGraph<FandangoNode<'program, 'source>, Span<'source>>,
626) -> HashMap<FandangoNode<'program, 'source>, Vec<usize>> {
627 let mut depths = graph
628 .node_references()
629 .filter_map(|(idx, node)| matches!(node, FandangoNode::String(_)).then_some((idx, 0usize)))
630 .collect::<HashMap<_, _>>();
631 let mut queue = depths
632 .keys()
633 .copied()
634 .flat_map(|term| graph.edges_directed(term, Direction::Incoming))
635 .map(|e| e.source())
636 .collect::<VecDeque<_>>();
637 let mut alternatives = HashMap::new();
638
639 loop {
640 let mut unchanged = true;
641 let mut next_queue = VecDeque::new();
642
643 for next in queue {
644 if depths.contains_key(&next) {
645 continue;
646 }
647 let mut children = graph
648 .edges(next)
649 .map(|e| (e.weight().start(), depths.get(&e.target()).copied()))
650 .collect::<Vec<_>>();
651 children.sort_by_key(|(s, _)| *s);
652
653 let mut depth = None;
654 match graph.node_weight(next).unwrap() {
655 FandangoNode::Alternative(_) => {
656 let (choices, alt_depth) = children.into_iter().enumerate().fold(
657 (Vec::new(), usize::MAX),
658 |(mut current, max_len), (idx, (_, len))| {
659 if let Some(len) = len {
660 if len < max_len {
661 current.clear();
662 current.push(idx);
663 return (current, len);
664 } else if len == max_len {
665 current.push(idx);
666 }
667 }
668 (current, max_len)
669 },
670 );
671 alternatives.insert(next, choices);
672 depth = Some(alt_depth);
673 }
674 _ => {
675 if let Some(children) = children
676 .into_iter()
677 .map(|(_, c)| c)
678 .collect::<Option<Vec<_>>>()
679 {
680 depth = children.into_iter().min();
681 }
682 }
683 }
684 if let Some(depth) = depth {
685 next_queue.extend(
686 graph
687 .edges_directed(next, Direction::Incoming)
688 .map(|e| e.source()),
689 );
690 unchanged &= depths.insert(next, depth) == Some(depth);
691 }
692 }
693
694 queue = next_queue;
695 if unchanged {
696 break;
697 }
698 }
699
700 alternatives
701 .into_iter()
702 .map(|(idx, paths)| (*graph.node_weight(idx).unwrap(), paths))
703 .collect()
704}
705
706#[cfg(test)]
707mod test {
708 use crate::graph::IntoGraph;
709 use crate::lang::Program;
710 use crate::lang::test::SIMPLE_GRAMMAR;
711
712 use alloc::format;
713
714 use petgraph::data::{Element, FromElements};
715
716 use petgraph::graph::DiGraph;
717
718 extern crate std;
719
720 #[test]
722 fn test_graph() {
723 let program = Program::try_from(SIMPLE_GRAMMAR).unwrap();
724
725 let (_, graph) = (&program).into_graph();
726
727 let _renderable = DiGraph::<_, _>::from_elements(
728 graph
729 .raw_nodes()
730 .iter()
731 .map(|n| Element::Node { weight: n.weight })
732 .chain(graph.raw_edges().iter().map(|e| {
733 let (start_line, start_col) = e.weight.start_pos().line_col();
734 let (end_line, end_col) = e.weight.end_pos().line_col();
735 let rendered = if start_line == end_line {
736 format!("{start_line}:{start_col}-{end_col}")
737 } else {
738 format!("{start_line}:{start_col}-{end_line}:{end_col}")
739 };
740 Element::Edge {
741 source: e.source().index(),
742 target: e.target().index(),
743 weight: rendered,
744 }
745 })),
746 );
747 }
748}