1pub fn parse_import(
2 c: &Option<String>,
3 doc_id: &str,
4 line_number: usize,
5) -> ftd::p1::Result<(String, String)> {
6 let v = match c {
7 Some(v) => v.trim(),
8 None => {
9 return ftd::p2::utils::e2(
10 "caption is missing in import statement",
11 doc_id,
12 line_number,
13 )
14 }
15 };
16
17 if v.contains(" as ") {
18 let mut parts = v.splitn(2, " as ");
19 return match (parts.next(), parts.next()) {
20 (Some(n), Some(a)) => Ok((n.to_string(), a.to_string())),
21 _ => ftd::p2::utils::e2(
22 "invalid use of keyword as in import statement",
23 doc_id,
24 line_number,
25 ),
26 };
27 }
28
29 if v.contains('/') {
30 let mut parts = v.rsplitn(2, '/');
31 return match (parts.next(), parts.next()) {
32 (Some(t), Some(_)) => Ok((v.to_string(), t.to_string())),
33 _ => ftd::p2::utils::e2("doc id must contain /", doc_id, line_number),
34 };
35 }
36
37 if let Some((t, _)) = v.split_once('.') {
38 return Ok((v.to_string(), t.to_string()));
39 }
40
41 Ok((v.to_string(), v.to_string()))
42}
43
44pub fn get_name<'a, 'b>(prefix: &'a str, s: &'b str, doc_id: &str) -> ftd::p1::Result<&'b str> {
45 match s.split_once(' ') {
46 Some((p1, p2)) => {
47 if p1 != prefix {
48 return ftd::p2::utils::e2(format!("must start with {}", prefix), doc_id, 0);
49 }
51 Ok(p2)
52 }
53 None => ftd::p2::utils::e2(
54 format!("{} does not contain space (prefix={})", s, prefix),
55 doc_id,
56 0, ),
58 }
59}
60
61pub fn boolean_and_ref(
62 line_number: usize,
63 name: &str,
64 properties: &ftd::Map<ftd::component::Property>,
65 doc: &ftd::p2::TDoc,
66 condition: &Option<ftd::p2::Boolean>, ) -> ftd::p1::Result<(bool, Option<String>)> {
68 let properties = ftd::component::resolve_properties_with_ref(line_number, properties, doc)?;
69 match properties.get(name) {
70 Some((ftd::Value::Boolean { value }, reference)) => {
71 Ok((value.to_owned(), complete_reference(reference)))
72 }
73 Some((ftd::Value::Optional { data, kind }, reference)) => {
74 if !matches!(kind, ftd::p2::Kind::Boolean { .. })
75 && !matches!(kind, ftd::p2::Kind::Element)
76 {
77 return ftd::p2::utils::e2(
78 format!("expected boolean, found: {:?}", kind),
79 doc.name,
80 line_number,
81 );
82 };
83 match data.as_ref() {
84 None => {
85 let reference = match reference {
86 Some(reference) => reference,
87 None => {
88 return ftd::p2::utils::e2(
89 format!("expected boolean, found: {:?}", kind),
90 doc.name,
91 line_number,
92 )
93 }
94 };
95
96 if let Some(ftd::p2::Boolean::IsNotNull { value }) = condition {
97 match value {
98 ftd::PropertyValue::Reference { name, .. }
99 | ftd::PropertyValue::Variable { name, .. } => {
100 if name.eq(reference) {
101 return Ok((
102 false,
103 complete_reference(&Some(reference.to_owned())),
104 ));
105 }
106 }
107 _ => {}
108 }
109 }
110
111 Ok((false, complete_reference(&Some(reference.to_owned()))))
115 }
116 Some(ftd::Value::Boolean { value }) => {
117 Ok((value.to_owned(), complete_reference(reference)))
118 }
119 _ => ftd::p2::utils::e2(
120 format!("expected boolean, found: {:?}", kind),
121 doc.name,
122 line_number,
123 ),
124 }
125 }
126 Some((ftd::Value::None { kind }, reference)) if condition.is_some() => {
127 let kind = kind.inner();
128 if !matches!(kind, ftd::p2::Kind::Boolean { .. })
129 && !matches!(kind, ftd::p2::Kind::Element)
130 {
131 return ftd::p2::utils::e2(
132 format!("expected boolean, found: {:?}", kind),
133 doc.name,
134 line_number,
135 );
136 };
137
138 let reference = match reference {
139 Some(reference) => reference,
140 None => {
141 return ftd::p2::utils::e2(
142 format!("expected integer, found 7: {:?}", kind),
143 doc.name,
144 line_number,
145 )
146 }
147 };
148 if let Some(ftd::p2::Boolean::IsNotNull { value }) = condition {
149 match value {
150 ftd::PropertyValue::Reference { name, .. }
151 | ftd::PropertyValue::Variable { name, .. } => {
152 if name.eq({
153 if let Some(reference) = reference.strip_prefix('@') {
154 reference
155 } else {
156 reference
157 }
158 }) {
159 return Ok((false, complete_reference(&Some(reference.to_owned()))));
160 }
161 }
162 _ => {}
163 }
164 }
165 ftd::p2::utils::e2(
166 format!("expected boolean, found: {:?}", kind),
167 doc.name,
168 line_number,
169 )
170 }
171 Some(v) => ftd::p2::utils::e2(
172 format!("expected boolean, found: {:?}", v),
173 doc.name,
174 line_number,
175 ),
176 None => ftd::p2::utils::e2(format!("'{}' not found", name), doc.name, line_number),
177 }
178}
179
180pub fn integer_and_ref(
181 line_number: usize,
182 name: &str,
183 properties: &ftd::Map<ftd::component::Property>,
184 doc: &ftd::p2::TDoc,
185 condition: &Option<ftd::p2::Boolean>, ) -> ftd::p1::Result<(i64, Option<String>)> {
187 let properties = ftd::component::resolve_properties_with_ref(line_number, properties, doc)?;
188 match properties.get(name) {
189 Some((ftd::Value::Integer { value }, reference)) => {
190 Ok((value.to_owned(), complete_reference(reference)))
191 }
192 Some((ftd::Value::Optional { data, kind }, reference)) => {
193 if !matches!(kind, ftd::p2::Kind::Integer { .. })
194 && !matches!(kind, ftd::p2::Kind::Element)
195 {
196 return ftd::p2::utils::e2(
197 format!("expected integer, found 8: {:?}", kind),
198 doc.name,
199 line_number,
200 );
201 };
202 match data.as_ref() {
203 None => {
204 let reference = match reference {
205 Some(reference) => reference,
206 None => {
207 return ftd::p2::utils::e2(
208 format!("expected integer, found 9: {:?}", kind),
209 doc.name,
210 line_number,
211 )
212 }
213 };
214
215 if let Some(ftd::p2::Boolean::IsNotNull { value }) = condition {
216 match value {
217 ftd::PropertyValue::Reference { name, .. }
218 | ftd::PropertyValue::Variable { name, .. } => {
219 if name.eq(reference) {
220 return Ok((
221 0,
222 complete_reference(&Some(reference.to_owned())),
223 ));
224 }
225 }
226 _ => {}
227 }
228 }
229
230 Ok((0, complete_reference(&Some(reference.to_owned()))))
234 }
235 Some(ftd::Value::Integer { value }) => {
236 Ok((value.to_owned(), complete_reference(reference)))
237 }
238 _ => ftd::p2::utils::e2(
239 format!("expected integer, found 10: {:?}", kind),
240 doc.name,
241 line_number,
242 ),
243 }
244 }
245 Some((ftd::Value::None { kind }, reference)) if condition.is_some() => {
246 let kind = kind.inner();
247 if !matches!(kind, ftd::p2::Kind::Integer { .. })
248 && !matches!(kind, ftd::p2::Kind::Element)
249 {
250 return ftd::p2::utils::e2(
251 format!("expected integer, found 11: {:?}", kind),
252 doc.name,
253 line_number,
254 );
255 };
256
257 let reference = match reference {
258 Some(reference) => reference,
259 None => {
260 return ftd::p2::utils::e2(
261 format!("expected integer, found 1: {:?}", kind),
262 doc.name,
263 line_number,
264 )
265 }
266 };
267 if let Some(ftd::p2::Boolean::IsNotNull { value }) = condition {
268 match value {
269 ftd::PropertyValue::Reference { name, .. }
270 | ftd::PropertyValue::Variable { name, .. } => {
271 if name.eq({
272 if let Some(reference) = reference.strip_prefix('@') {
273 reference
274 } else {
275 reference
276 }
277 }) {
278 return Ok((0, complete_reference(&Some(reference.to_owned()))));
279 }
280 }
281 _ => {}
282 }
283 }
284 ftd::p2::utils::e2(
285 format!("expected integer, found 2: {:?}", kind),
286 doc.name,
287 line_number,
288 )
289 }
290 Some(v) => ftd::p2::utils::e2(
291 format!("expected integer, found 3: {:?}", v),
292 doc.name,
293 line_number,
294 ),
295 None => ftd::p2::utils::e2(format!("'{}' not found", name), doc.name, line_number),
296 }
297}
298
299pub fn decimal_and_ref(
300 line_number: usize,
301 name: &str,
302 properties: &ftd::Map<ftd::component::Property>,
303 doc: &ftd::p2::TDoc,
304 condition: &Option<ftd::p2::Boolean>, ) -> ftd::p1::Result<(f64, Option<String>)> {
306 let properties = ftd::component::resolve_properties_with_ref(line_number, properties, doc)?;
307 match properties.get(name) {
308 Some((ftd::Value::Decimal { value }, reference)) => {
309 Ok((value.to_owned(), complete_reference(reference)))
310 }
311 Some((ftd::Value::Optional { data, kind }, reference)) => {
312 if !matches!(kind, ftd::p2::Kind::Decimal { .. })
313 && !matches!(kind, ftd::p2::Kind::Element)
314 {
315 return ftd::p2::utils::e2(
316 format!("expected decimal, found: {:?}", kind),
317 doc.name,
318 line_number,
319 );
320 };
321 match data.as_ref() {
322 None => {
323 let reference = match reference {
324 Some(reference) => reference,
325 None => {
326 return ftd::p2::utils::e2(
327 format!("expected decimal, found: {:?}", kind),
328 doc.name,
329 line_number,
330 )
331 }
332 };
333
334 if let Some(ftd::p2::Boolean::IsNotNull { value }) = condition {
335 match value {
336 ftd::PropertyValue::Reference { name, .. }
337 | ftd::PropertyValue::Variable { name, .. } => {
338 if name.eq(reference) {
339 return Ok((
340 0.0,
341 complete_reference(&Some(reference.to_owned())),
342 ));
343 }
344 }
345 _ => {}
346 }
347 }
348
349 Ok((0.0, complete_reference(&Some(reference.to_owned()))))
353 }
354 Some(ftd::Value::Decimal { value }) => {
355 Ok((value.to_owned(), complete_reference(reference)))
356 }
357 _ => ftd::p2::utils::e2(
358 format!("expected decimal, found: {:?}", kind),
359 doc.name,
360 line_number,
361 ),
362 }
363 }
364 Some((ftd::Value::None { kind }, reference)) if condition.is_some() => {
365 let kind = kind.inner();
366 if !matches!(kind, ftd::p2::Kind::Decimal { .. })
367 && !matches!(kind, ftd::p2::Kind::Element)
368 {
369 return ftd::p2::utils::e2(
370 format!("expected integer, found 4: {:?}", kind),
371 doc.name,
372 line_number,
373 );
374 };
375
376 let reference = match reference {
377 Some(reference) => reference,
378 None => {
379 return ftd::p2::utils::e2(
380 format!("expected integer, found 5: {:?}", kind),
381 doc.name,
382 line_number,
383 )
384 }
385 };
386 if let Some(ftd::p2::Boolean::IsNotNull { value }) = condition {
387 match value {
388 ftd::PropertyValue::Reference { name, .. }
389 | ftd::PropertyValue::Variable { name, .. } => {
390 if name.eq({
391 if let Some(reference) = reference.strip_prefix('@') {
392 reference
393 } else {
394 reference
395 }
396 }) {
397 return Ok((0.0, complete_reference(&Some(reference.to_owned()))));
398 }
399 }
400 _ => {}
401 }
402 }
403 ftd::p2::utils::e2(
404 format!("expected decimal, found: {:?}", kind),
405 doc.name,
406 line_number,
407 )
408 }
409 Some(v) => ftd::p2::utils::e2(
410 format!("expected decimal, found: {:?}", v),
411 doc.name,
412 line_number,
413 ),
414 None => ftd::p2::utils::e2(format!("'{}' not found", name), doc.name, line_number),
415 }
416}
417
418pub fn string_and_source_and_ref(
419 line_number: usize,
420 name: &str,
421 properties: &ftd::Map<ftd::component::Property>,
422 doc: &ftd::p2::TDoc,
423 condition: &Option<ftd::p2::Boolean>,
424) -> ftd::p1::Result<(String, ftd::TextSource, Option<String>)> {
425 let properties = ftd::component::resolve_properties_with_ref(line_number, properties, doc)?;
426 match properties.get(name) {
427 Some((ftd::Value::String { text, source }, reference)) => {
428 Ok((text.to_string(), source.to_owned(), (*reference).to_owned()))
429 }
430 Some((ftd::Value::Optional { data, kind }, reference)) => {
431 let source = match kind {
432 _ if matches!(kind, ftd::p2::Kind::String { .. })
433 || matches!(kind, ftd::p2::Kind::Element) =>
434 {
435 ftd::TextSource::from_kind(kind, doc.name, line_number)?
436 }
437 _ => {
438 return ftd::p2::utils::e2(
439 format!("expected string, found 1: {:?}", kind),
440 doc.name,
441 line_number,
442 )
443 }
444 };
445
446 match data.as_ref() {
447 None => {
448 let reference = match reference {
449 Some(reference) => reference,
450 None => {
451 return ftd::p2::utils::e2(
452 format!("expected string, found 2: {:?}", kind),
453 doc.name,
454 line_number,
455 )
456 }
457 };
458
459 if let Some(ftd::p2::Boolean::IsNotNull { value }) = condition {
460 match value {
461 ftd::PropertyValue::Reference { name, .. }
462 | ftd::PropertyValue::Variable { name, .. } => {
463 if name.eq(reference) {
464 return Ok((
465 "".to_string(),
466 source,
467 complete_reference(&Some(reference.to_owned())),
468 ));
469 }
470 }
471 _ => {}
472 }
473 }
474
475 Ok((
479 "".to_string(),
480 source,
481 complete_reference(&Some(reference.to_owned())),
482 ))
483 }
484 Some(ftd::Value::String { text, source }) => Ok((
485 text.to_string(),
486 source.to_owned(),
487 complete_reference(reference),
488 )),
489 _ => ftd::p2::utils::e2(
490 format!("expected string, found 3: {:?}", kind),
491 doc.name,
492 line_number,
493 ),
494 }
495 }
496 Some((ftd::Value::None { kind }, reference)) if condition.is_some() => {
497 let kind = kind.inner();
498 let source = match kind {
499 _ if matches!(kind, ftd::p2::Kind::String { .. })
500 || matches!(kind, ftd::p2::Kind::Element) =>
501 {
502 ftd::TextSource::from_kind(kind, doc.name, line_number)?
503 }
504 _ => {
505 return ftd::p2::utils::e2(
506 format!("expected string, found 4: {:?}", kind),
507 doc.name,
508 line_number,
509 )
510 }
511 };
512
513 let reference = match reference {
514 Some(reference) => reference,
515 None => {
516 return ftd::p2::utils::e2(
517 format!("expected string, found 5: {:?}", kind),
518 doc.name,
519 line_number,
520 )
521 }
522 };
523 if let Some(ftd::p2::Boolean::IsNotNull { value }) = condition {
524 match value {
525 ftd::PropertyValue::Reference { name, .. }
526 | ftd::PropertyValue::Variable { name, .. } => {
527 if name.eq({
528 if let Some(reference) = reference.strip_prefix('@') {
529 reference
530 } else {
531 reference
532 }
533 }) {
534 return Ok((
535 "".to_string(),
536 source,
537 complete_reference(&Some(reference.to_owned())),
538 ));
539 }
540 }
541 _ => {}
542 }
543 }
544 ftd::p2::utils::e2(
545 format!("expected string, found 6: {:?}", kind),
546 doc.name,
547 line_number,
548 )
549 }
550 Some(v) => ftd::p2::utils::e2(
551 format!("expected string, found 7: {:?}", v),
552 doc.name,
553 line_number,
554 ),
555 None => ftd::p2::utils::e2(format!("'{}' not found", name), doc.name, line_number),
556 }
557}
558
559pub fn complete_reference(reference: &Option<String>) -> Option<String> {
561 let mut reference = (*reference).to_owned();
562 if let Some(ref r) = reference {
563 if let Some(name) = r.strip_prefix('@') {
564 if name.eq("$loop$") {
565 return None;
566 } else if name.eq("MOUSE-IN") {
567 reference = Some("$MOUSE-IN".to_string());
568 }
569 }
570 }
571 reference
572}
573
574pub fn record_and_ref(
575 line_number: usize,
576 name: &str,
577 properties: &ftd::Map<ftd::component::Property>,
578 doc: &ftd::p2::TDoc,
579 condition: &Option<ftd::p2::Boolean>,
580) -> ftd::p1::Result<(ftd::Map<ftd::PropertyValue>, Option<String>)> {
581 let properties = ftd::component::resolve_properties_with_ref(line_number, properties, doc)?;
582 match properties.get(name) {
583 Some((ftd::Value::Record { fields, .. }, reference)) => {
584 Ok((fields.to_owned(), (*reference).to_owned()))
585 }
586 Some((ftd::Value::Optional { data, kind }, reference)) => {
587 if !matches!(kind, ftd::p2::Kind::Record { .. })
588 && !matches!(kind, ftd::p2::Kind::Element)
589 {
590 return ftd::p2::utils::e2(
591 format!("expected record, found: {:?}", kind),
592 doc.name,
593 line_number,
594 );
595 }
596
597 match data.as_ref() {
598 None => {
599 let reference = match reference {
600 Some(reference) => reference,
601 None => {
602 return ftd::p2::utils::e2(
603 format!("expected record, found: {:?}", kind),
604 doc.name,
605 line_number,
606 )
607 }
608 };
609
610 if let Some(ftd::p2::Boolean::IsNotNull { value }) = condition {
611 match value {
612 ftd::PropertyValue::Reference { name, .. }
613 | ftd::PropertyValue::Variable { name, .. } => {
614 if name.eq(reference) {
615 return Ok((
616 Default::default(),
617 complete_reference(&Some(reference.to_owned())),
618 ));
619 }
620 }
621 _ => {}
622 }
623 }
624
625 Ok((
629 Default::default(),
630 complete_reference(&Some(reference.to_owned())),
631 ))
632 }
633 Some(ftd::Value::Record { fields, .. }) => {
634 Ok((fields.to_owned(), complete_reference(reference)))
635 }
636 _ => ftd::p2::utils::e2(
637 format!("expected record, found: {:?}", kind),
638 doc.name,
639 line_number,
640 ),
641 }
642 }
643 Some((ftd::Value::None { kind }, reference)) if condition.is_some() => {
644 let kind = kind.inner();
645 if !matches!(kind, ftd::p2::Kind::Record { .. })
646 && !matches!(kind, ftd::p2::Kind::Element)
647 {
648 return ftd::p2::utils::e2(
649 format!("expected record, found: {:?}", kind),
650 doc.name,
651 line_number,
652 );
653 }
654
655 let reference = match reference {
656 Some(reference) => reference,
657 None => {
658 return ftd::p2::utils::e2(
659 format!("expected record, found: {:?}", kind),
660 doc.name,
661 line_number,
662 )
663 }
664 };
665 if let Some(ftd::p2::Boolean::IsNotNull { value }) = condition {
666 match value {
667 ftd::PropertyValue::Reference { name, .. }
668 | ftd::PropertyValue::Variable { name, .. } => {
669 if name.eq({
670 if let Some(reference) = reference.strip_prefix('@') {
671 reference
672 } else {
673 reference
674 }
675 }) {
676 return Ok((
677 Default::default(),
678 complete_reference(&Some(reference.to_owned())),
679 ));
680 }
681 }
682 _ => {}
683 }
684 }
685 ftd::p2::utils::e2(
686 format!("expected record, found: {:?}", kind),
687 doc.name,
688 line_number,
689 )
690 }
691 Some(v) => ftd::p2::utils::e2(
692 format!("expected record, found: {:?}", v),
693 doc.name,
694 line_number,
695 ),
696 None => ftd::p2::utils::e2(format!("'{}' not found", name), doc.name, line_number),
697 }
698}
699
700#[allow(clippy::type_complexity)]
701pub fn record_optional_with_ref(
702 name: &str,
703 properties: &ftd::Map<ftd::component::Property>,
704 doc: &ftd::p2::TDoc,
705 line_number: usize,
706) -> ftd::p1::Result<(Option<ftd::Map<ftd::PropertyValue>>, Option<String>)> {
707 let properties = ftd::component::resolve_properties_with_ref(line_number, properties, doc)?;
708 match properties.get(name) {
709 Some((ftd::Value::Record { fields, .. }, reference)) => {
710 Ok((Some(fields.to_owned()), (*reference).to_owned()))
711 }
712 Some((
713 ftd::Value::None {
714 kind: ftd::p2::Kind::Record { .. },
715 },
716 _,
717 )) => Ok((None, None)),
718 Some((ftd::Value::None { .. }, _)) => Ok((None, None)),
719 Some((
720 ftd::Value::Optional {
721 data,
722 kind: ftd::p2::Kind::Record { .. },
723 },
724 reference,
725 )) => match data.as_ref() {
726 Some(ftd::Value::Record { fields, .. }) => {
727 Ok((Some(fields.to_owned()), (*reference).to_owned()))
728 }
729 None => Ok((None, None)),
730 v => ftd::p2::utils::e2(
731 format!("expected record, for: `{}` found: {:?}", name, v),
732 doc.name,
733 line_number,
734 ),
735 },
736 Some(v) => ftd::p2::utils::e2(
737 format!("expected record, for: `{}` found: {:?}", name, v),
738 doc.name,
739 line_number,
740 ),
741 None => Ok((None, None)),
742 }
743}
744
745pub fn record_optional(
746 name: &str,
747 properties: &ftd::Map<ftd::Value>,
748 doc_id: &str,
749 line_number: usize,
750) -> ftd::p1::Result<Option<ftd::Map<ftd::PropertyValue>>> {
751 match properties.get(name) {
752 Some(ftd::Value::Record { fields, .. }) => Ok(Some(fields.to_owned())),
753 Some(ftd::Value::None {
754 kind: ftd::p2::Kind::Record { .. },
755 }) => Ok(None),
756 Some(ftd::Value::None { .. }) => Ok(None),
757 Some(ftd::Value::Optional {
758 data,
759 kind: ftd::p2::Kind::Record { .. },
760 }) => match data.as_ref() {
761 Some(ftd::Value::Record { fields, .. }) => Ok(Some(fields.to_owned())),
762 None => Ok(None),
763 v => ftd::p2::utils::e2(
764 format!("expected record, for: `{}` found: {:?}", name, v),
765 doc_id,
766 line_number,
767 ),
768 },
769 Some(v) => ftd::p2::utils::e2(
770 format!("expected record, for: `{}` found: {:?}", name, v),
771 doc_id,
772 line_number,
773 ),
774 None => Ok(None),
775 }
776}
777
778pub fn string_optional(
779 name: &str,
780 properties: &ftd::Map<ftd::Value>,
781 doc_id: &str,
782 line_number: usize,
783) -> ftd::p1::Result<Option<String>> {
784 match properties.get(name) {
785 Some(ftd::Value::String { text: v, .. }) => Ok(Some(v.to_string())),
786 Some(ftd::Value::None {
787 kind: ftd::p2::Kind::String { .. },
788 }) => Ok(None),
789 Some(ftd::Value::None { .. }) => Ok(None),
790 Some(ftd::Value::Optional {
791 data,
792 kind: ftd::p2::Kind::String { .. },
793 }) => match data.as_ref() {
794 Some(ftd::Value::String { text: v, .. }) => Ok(Some(v.to_string())),
795 None => Ok(None),
796 v => ftd::p2::utils::e2(
797 format!("expected string, for: `{}` found: {:?}", name, v),
798 doc_id,
799 line_number,
800 ),
801 },
802 Some(v) => ftd::p2::utils::e2(
803 format!("expected string, for: `{}` found: {:?}", name, v),
804 doc_id,
805 line_number,
806 ),
807 None => Ok(None),
808 }
809}
810
811pub fn string(
812 name: &str,
813 properties: &ftd::Map<ftd::Value>,
814 doc_id: &str,
815 line_number: usize,
816) -> ftd::p1::Result<String> {
817 match properties.get(name) {
818 Some(ftd::Value::String { text: v, .. }) => Ok(v.to_string()),
819 Some(ftd::Value::Optional {
820 data,
821 kind: ftd::p2::Kind::String { .. },
822 }) => match data.as_ref() {
823 Some(ftd::Value::String { text: v, .. }) => Ok(v.to_string()),
824 v => ftd::p2::utils::e2(
825 format!("expected string, for: `{}` found: {:?}", name, v),
826 doc_id,
827 line_number,
828 ),
829 },
830 v => ftd::p2::utils::e2(
831 format!("expected string, for: `{}` found: {:?}", name, v),
832 doc_id,
833 line_number,
834 ),
835 }
836}
837
838pub fn string_with_default(
839 name: &str,
840 def: &str,
841 properties: &ftd::Map<ftd::Value>,
842 doc_id: &str,
843 line_number: usize,
844) -> ftd::p1::Result<String> {
845 match properties.get(name) {
846 Some(ftd::Value::String { text: v, .. }) => Ok(v.to_string()),
847 Some(ftd::Value::None {
848 kind: ftd::p2::Kind::String { .. },
849 }) => Ok(def.to_string()),
850 Some(ftd::Value::None { .. }) => Ok(def.to_string()),
851 Some(v) => ftd::p2::utils::e2(
852 format!("expected bool, found: {:?}", v),
853 doc_id,
854 line_number,
855 ),
856 None => Ok(def.to_string()),
857 }
858}
859
860pub fn int(
861 name: &str,
862 properties: &ftd::Map<ftd::Value>,
863 doc_id: &str,
864 line_number: usize,
865) -> ftd::p1::Result<i64> {
866 match properties.get(name) {
867 Some(ftd::Value::Integer { value: v, .. }) => Ok(*v),
868 Some(v) => ftd::p2::utils::e2(
869 format!("[{}] expected int, found1: {:?}", name, v),
870 doc_id,
871 line_number,
872 ),
873 None => ftd::p2::utils::e2(format!("'{}' not found", name), doc_id, line_number),
874 }
875}
876
877pub fn int_optional(
878 name: &str,
879 properties: &ftd::Map<ftd::Value>,
880 doc_id: &str,
881 line_number: usize,
882) -> ftd::p1::Result<Option<i64>> {
883 match properties.get(name) {
884 Some(ftd::Value::Integer { value: v }) => Ok(Some(*v)),
885 Some(ftd::Value::None {
886 kind: ftd::p2::Kind::Integer { .. },
887 }) => Ok(None),
888 Some(ftd::Value::None { .. }) => Ok(None),
889 Some(ftd::Value::Optional {
890 data,
891 kind: ftd::p2::Kind::Integer { .. },
892 }) => match data.as_ref() {
893 Some(ftd::Value::Integer { value }) => Ok(Some(*value)),
894 None => Ok(None),
895 v => ftd::p2::utils::e2(
896 format!("expected integer, for: `{}` found: {:?}", name, v),
897 doc_id,
898 line_number,
899 ),
900 },
901 Some(v) => ftd::p2::utils::e2(
902 format!("expected integer, found 6: {:?}", v),
903 doc_id,
904 line_number,
905 ),
906 None => Ok(None),
907 }
908}
909
910pub fn int_with_default(
911 name: &str,
912 def: i64,
913 properties: &ftd::Map<ftd::Value>,
914 doc_id: &str,
915 line_number: usize,
916) -> ftd::p1::Result<i64> {
917 match properties.get(name) {
918 Some(ftd::Value::Integer { value: v }) => Ok(*v),
919 Some(ftd::Value::None {
920 kind: ftd::p2::Kind::Integer { .. },
921 }) => Ok(def),
922 Some(ftd::Value::None { .. }) => Ok(def),
923 Some(v) => ftd::p2::utils::e2(
924 format!("expected int, found2: {:?}", v),
925 doc_id,
926 line_number,
927 ),
928 None => Ok(def),
929 }
930}
931
932pub fn bool_with_default(
944 name: &str,
945 def: bool,
946 properties: &ftd::Map<ftd::Value>,
947 doc_id: &str,
948 line_number: usize,
949) -> ftd::p1::Result<bool> {
950 match properties.get(name) {
951 Some(ftd::Value::Boolean { value: v }) => Ok(*v),
952 Some(ftd::Value::None {
953 kind: ftd::p2::Kind::Boolean { .. },
954 }) => Ok(def),
955 Some(ftd::Value::None { .. }) => Ok(def),
956 Some(v) => ftd::p2::utils::e2(
957 format!("expected bool, found: {:?}", v),
958 doc_id,
959 line_number,
960 ),
961 None => Ok(def),
962 }
963}
964
965pub fn bool_(
966 name: &str,
967 properties: &ftd::Map<ftd::Value>,
968 doc_id: &str,
969 line_number: usize,
970) -> ftd::p1::Result<bool> {
971 match properties.get(name) {
972 Some(ftd::Value::Boolean { value: v, .. }) => Ok(*v),
973 Some(v) => ftd::p2::utils::e2(
974 format!("[{}] expected bool, found: {:?}", name, v),
975 doc_id,
976 line_number,
977 ),
978 None => ftd::p2::utils::e2(format!("'{}' not found", name), doc_id, line_number),
979 }
980}
981
982pub fn bool_optional(
983 name: &str,
984 properties: &ftd::Map<ftd::Value>,
985 doc_id: &str,
986 line_number: usize,
987) -> ftd::p1::Result<Option<bool>> {
988 match properties.get(name) {
989 Some(ftd::Value::Boolean { value: v }) => Ok(Some(*v)),
990 Some(ftd::Value::None {
991 kind: ftd::p2::Kind::Boolean { .. },
992 }) => Ok(None),
993 Some(ftd::Value::Optional {
994 data,
995 kind: ftd::p2::Kind::Boolean { .. },
996 }) => match data.as_ref() {
997 Some(ftd::Value::Boolean { value: v }) => Ok(Some(*v)),
998 None => Ok(None),
999 v => ftd::p2::utils::e2(
1000 format!("expected bool, for: `{}` found: {:?}", name, v),
1001 doc_id,
1002 line_number,
1003 ),
1004 },
1005 Some(v) => ftd::p2::utils::e2(
1006 format!("expected bool, found: {:?}", v),
1007 doc_id,
1008 line_number,
1009 ),
1010 None => Ok(None),
1011 }
1012}
1013
1014#[cfg(test)]
1015mod test {
1016 macro_rules! p {
1017 ($s:expr, $id: expr, $alias: expr) => {
1018 assert_eq!(
1019 super::parse_import(&Some($s.to_string()), $id, 0)
1020 .unwrap_or_else(|e| panic!("{}", e)),
1021 ($id.to_string(), $alias.to_string())
1022 )
1023 };
1024 }
1025
1026 #[test]
1027 fn parse_import() {
1028 p!("a/b/c as foo", "a/b/c", "foo");
1029 p!("a/b as foo", "a/b", "foo");
1030 p!("a/b/c", "a/b/c", "c");
1031 p!("a/b", "a/b", "b");
1032 p!("a", "a", "a");
1033 p!("a as b", "a", "b");
1034 }
1035}
1036
1037pub fn decimal(
1038 name: &str,
1039 properties: &ftd::Map<ftd::Value>,
1040 doc_id: &str,
1041 line_number: usize,
1042) -> ftd::p1::Result<f64> {
1043 match properties.get(name) {
1044 Some(ftd::Value::Decimal { value: v, .. }) => Ok(*v),
1045 Some(v) => ftd::p2::utils::e2(
1046 format!("[{}] expected Decimal, found: {:?}", name, v),
1047 doc_id,
1048 line_number,
1049 ),
1050 None => ftd::p2::utils::e2(format!("'{}' not found", name), doc_id, line_number),
1051 }
1052}
1053
1054pub fn decimal_optional(
1055 name: &str,
1056 properties: &ftd::Map<ftd::Value>,
1057 doc_id: &str,
1058 line_number: usize,
1059) -> ftd::p1::Result<Option<f64>> {
1060 match properties.get(name) {
1061 Some(ftd::Value::Decimal { value: v }) => Ok(Some(*v)),
1062 Some(ftd::Value::None {
1063 kind: ftd::p2::Kind::Decimal { .. },
1064 }) => Ok(None),
1065 Some(ftd::Value::None { .. }) => Ok(None),
1066 Some(ftd::Value::Optional {
1067 data,
1068 kind: ftd::p2::Kind::Decimal { .. },
1069 }) => match data.as_ref() {
1070 Some(ftd::Value::Decimal { value: v }) => Ok(Some(*v)),
1071 None => Ok(None),
1072 v => ftd::p2::utils::e2(
1073 format!("expected decimal, for: `{}` found: {:?}", name, v),
1074 doc_id,
1075 line_number,
1076 ),
1077 },
1078 Some(v) => ftd::p2::utils::e2(
1079 format!("expected decimal, found: {:?}", v),
1080 doc_id,
1081 line_number,
1082 ),
1083 None => Ok(None),
1084 }
1085}
1086
1087pub(crate) fn get_string_container(local_container: &[usize]) -> String {
1088 local_container
1089 .iter()
1090 .map(|v| v.to_string())
1091 .collect::<Vec<String>>()
1092 .join(",")
1093}
1094
1095pub(crate) fn get_doc_name_and_remaining(s: &str) -> ftd::p1::Result<(String, Option<String>)> {
1096 let mut part1 = "".to_string();
1097 let mut pattern_to_split_at = s.to_string();
1098 if let Some((p1, p2)) = s.split_once('#') {
1099 part1 = format!("{}#", p1);
1100 pattern_to_split_at = p2.to_string();
1101 }
1102 Ok(if pattern_to_split_at.contains('.') {
1103 let (p1, p2) = ftd::p2::utils::split(pattern_to_split_at, ".")?;
1104 (format!("{}{}", part1, p1), Some(p2))
1105 } else {
1106 (s.to_string(), None)
1107 })
1108}
1109
1110pub(crate) fn resolve_local_variable_name(
1111 line_number: usize,
1112 name: &str,
1113 container: &str,
1114 doc_name: &str,
1115 aliases: &ftd::Map<String>,
1116) -> ftd::p1::Result<String> {
1117 if name.contains('@') {
1118 return Ok(name.to_string());
1119 }
1120 let (part1, part2) = ftd::p2::utils::get_doc_name_and_remaining(name)?;
1121 Ok(if let Some(ref p2) = part2 {
1122 ftd::p2::utils::resolve_name(
1123 line_number,
1124 format!("{}@{}.{}", part1, container, p2).as_str(),
1125 doc_name,
1126 aliases,
1127 )?
1128 } else {
1129 ftd::p2::utils::resolve_name(
1130 line_number,
1131 format!("{}@{}", part1, container).as_str(),
1132 doc_name,
1133 aliases,
1134 )?
1135 })
1136}
1137
1138pub fn resolve_name(
1139 line_number: usize,
1140 name: &str,
1141 doc_name: &str,
1142 aliases: &ftd::Map<String>,
1143) -> ftd::p1::Result<String> {
1144 if name.contains('#') {
1145 return Ok(name.to_string());
1146 }
1147 Ok(
1148 match ftd::p2::utils::split_module(name, doc_name, line_number)? {
1149 (Some(m), v, None) => match aliases.get(m) {
1150 Some(m) => format!("{}#{}", m, v),
1151 None => format!("{}#{}.{}", doc_name, m, v),
1152 },
1153 (Some(m), v, Some(c)) => match aliases.get(m) {
1154 Some(m) => format!("{}#{}.{}", m, v, c),
1155 None => format!("{}#{}.{}.{}", doc_name, m, v, c),
1156 },
1157 (None, v, None) => format!("{}#{}", doc_name, v),
1158 _ => unimplemented!(),
1159 },
1160 )
1161}
1162
1163pub fn split(name: String, split_at: &str) -> ftd::p1::Result<(String, String)> {
1164 if !name.contains(split_at) {
1165 return ftd::p2::utils::e2(format!("{} is not found in {}", split_at, name), "", 0);
1166 }
1167 let mut part = name.splitn(2, split_at);
1168 let part_1 = part.next().unwrap().trim();
1169 let part_2 = part.next().unwrap().trim();
1170 Ok((part_1.to_string(), part_2.to_string()))
1171}
1172
1173pub fn reorder(
1174 p1: &[ftd::p1::Section],
1175 doc: &ftd::p2::TDoc,
1176) -> ftd::p1::Result<(Vec<ftd::p1::Section>, Vec<String>)> {
1177 fn is_kernel_component(comp: String) -> bool {
1178 if ["ftd.row", "ftd.column"].contains(&comp.as_str()) {
1179 return true;
1180 }
1181 false
1182 }
1183
1184 fn reorder_component(
1185 p1_map: &ftd::Map<ftd::p1::Section>,
1186 new_p1: &mut Vec<ftd::p1::Section>,
1187 dependent_p1: Option<String>,
1188 inserted: &mut Vec<String>,
1189 doc: &ftd::p2::TDoc,
1190 var_types: &[String],
1191 ) -> ftd::p1::Result<()> {
1192 if let Some(p1) = dependent_p1 {
1193 if inserted.contains(&p1) {
1194 return Ok(());
1195 }
1196 if let Some(v) = p1_map.get(&p1) {
1197 for sub_section in v.sub_sections.0.iter() {
1198 if inserted.contains(&sub_section.name) || p1 == sub_section.name {
1199 continue;
1200 }
1201 reorder_component(
1202 p1_map,
1203 new_p1,
1204 Some(sub_section.name.to_string()),
1205 inserted,
1206 doc,
1207 var_types,
1208 )?;
1209 }
1210 let var_data = ftd::variable::VariableData::get_name_kind(
1211 &v.name,
1212 doc,
1213 v.line_number,
1214 var_types,
1215 )?;
1216 if !is_kernel_component(var_data.kind.to_string())
1217 && !inserted.contains(&var_data.kind)
1218 {
1219 reorder_component(
1220 p1_map,
1221 new_p1,
1222 Some(var_data.kind),
1223 inserted,
1224 doc,
1225 var_types,
1226 )?;
1227 }
1228 new_p1.push(v.to_owned());
1229 inserted.push(p1.to_string());
1230 }
1231 return Ok(());
1232 }
1233
1234 for (k, v) in p1_map {
1235 if inserted.contains(k) {
1236 continue;
1237 }
1238 for sub_section in v.sub_sections.0.iter() {
1239 for (_, _, v) in sub_section.header.0.iter() {
1240 if v.contains(':') {
1241 let (name, _) = ftd::p2::utils::split(v.to_string(), ":")?;
1242 if inserted.contains(&name) || k == &name {
1243 continue;
1244 }
1245 reorder_component(
1246 p1_map,
1247 new_p1,
1248 Some(name.to_string()),
1249 inserted,
1250 doc,
1251 var_types,
1252 )?;
1253 }
1254 }
1255 if inserted.contains(&sub_section.name) || k == &sub_section.name {
1256 continue;
1257 }
1258 reorder_component(
1259 p1_map,
1260 new_p1,
1261 Some(sub_section.name.to_string()),
1262 inserted,
1263 doc,
1264 var_types,
1265 )?;
1266 }
1267 for (_, _, v) in v.header.0.iter() {
1268 if v.contains(':') {
1269 let (name, _) = ftd::p2::utils::split(v.to_string(), ":")?;
1270 if inserted.contains(&name) || k == &name {
1271 continue;
1272 }
1273 reorder_component(
1274 p1_map,
1275 new_p1,
1276 Some(name.to_string()),
1277 inserted,
1278 doc,
1279 var_types,
1280 )?;
1281 }
1282 }
1283 let var_data =
1284 ftd::variable::VariableData::get_name_kind(&v.name, doc, v.line_number, var_types)?;
1285 if !is_kernel_component(var_data.kind.to_string()) && !inserted.contains(&var_data.kind)
1286 {
1287 reorder_component(
1288 p1_map,
1289 new_p1,
1290 Some(var_data.kind),
1291 inserted,
1292 doc,
1293 var_types,
1294 )?;
1295 }
1296
1297 new_p1.push(v.to_owned());
1298 inserted.push(k.to_string());
1299 }
1300 Ok(())
1301 }
1302
1303 let mut p1_map: ftd::Map<ftd::p1::Section> = Default::default();
1304 let mut inserted_p1 = vec![];
1305 let mut new_p1 = vec![];
1306 let mut list_or_var = vec![];
1307 let mut var_types = vec![];
1308 for (idx, p1) in p1.iter().enumerate() {
1309 let var_data =
1310 ftd::variable::VariableData::get_name_kind(&p1.name, doc, p1.line_number, &var_types);
1311 if p1.name == "import"
1312 || p1.name.starts_with("record ")
1313 || p1.name.starts_with("or-type ")
1314 || p1.name.starts_with("map ")
1315 {
1316 inserted_p1.push(idx);
1317 new_p1.push(p1.to_owned());
1318 }
1319 if let Ok(ftd::variable::VariableData {
1320 type_: ftd::variable::Type::Variable,
1321 ref name,
1322 ..
1323 }) = var_data
1324 {
1325 inserted_p1.push(idx);
1326 new_p1.push(p1.to_owned());
1327 list_or_var.push(name.to_string());
1328 }
1329
1330 if p1.name.starts_with("record ") {
1331 let name = ftd::p2::utils::get_name("record", &p1.name, "")?;
1332 var_types.push(name.to_string());
1333 }
1334
1335 if p1.name.starts_with("or-type ") {
1336 let name = ftd::p2::utils::get_name("or-type", &p1.name, "")?;
1337 var_types.push(name.to_string());
1338 for s in &p1.sub_sections.0 {
1339 var_types.push(format!("{}.{}", name, s.name));
1340 }
1341 }
1342
1343 if list_or_var.contains(&p1.name) {
1344 inserted_p1.push(idx);
1345 new_p1.push(p1.to_owned());
1346 }
1347
1348 if let Ok(ftd::variable::VariableData {
1349 type_: ftd::variable::Type::Component,
1350 ref name,
1351 ..
1352 }) = var_data
1353 {
1354 if p1_map.contains_key(name) {
1355 return ftd::p2::utils::e2(
1356 format!("{} is already declared", name),
1357 doc.name,
1358 p1.line_number,
1359 );
1360 }
1361 p1_map.insert(name.to_string(), p1.to_owned());
1362 inserted_p1.push(idx);
1363 }
1364 }
1365 let mut new_p1_component = vec![];
1366 reorder_component(
1367 &p1_map,
1368 &mut new_p1_component,
1369 None,
1370 &mut vec![],
1371 doc,
1372 &var_types,
1373 )?;
1374 new_p1.extend(new_p1_component);
1375
1376 for (idx, p1) in p1.iter().enumerate() {
1377 if inserted_p1.contains(&idx) {
1378 continue;
1379 }
1380 new_p1.push(p1.to_owned());
1381 }
1382
1383 Ok((new_p1, var_types))
1384}
1385
1386pub(crate) fn get_root_component_name(
1387 doc: &ftd::p2::TDoc,
1388 name: &str,
1389 line_number: usize,
1390) -> ftd::p1::Result<String> {
1391 let mut name = name.to_string();
1392 let mut root_name = name.to_string();
1393 while name != "ftd.kernel" {
1394 let component = doc.get_component(line_number, name.as_str())?;
1395 name = component.root;
1396 root_name = component.full_name;
1397 }
1398 Ok(root_name)
1399}
1400
1401pub(crate) fn get_markup_child(
1402 sub: &ftd::p1::SubSection,
1403 doc: &ftd::p2::TDoc,
1404 arguments: &ftd::Map<ftd::p2::Kind>,
1405) -> ftd::p1::Result<ftd::ChildComponent> {
1406 let (sub_name, ref_name) = match sub.name.split_once(' ') {
1407 Some((sub_name, ref_name)) => (sub_name.trim(), ref_name.trim()),
1408 _ => {
1409 return ftd::p2::utils::e2("the component should have name", doc.name, sub.line_number)
1410 }
1411 };
1412 let sub_caption = if sub.caption.is_none() && sub.body.is_none() {
1413 Some(ref_name.to_string())
1414 } else {
1415 sub.caption.clone()
1416 };
1417 let mut child = ftd::ChildComponent::from_p1(
1418 sub.line_number,
1419 sub_name,
1420 &sub.header,
1421 &sub_caption,
1422 &sub.body,
1423 doc,
1424 arguments,
1425 )?;
1426 child.root = format!("{} {}", child.root, ref_name);
1427 Ok(child)
1428}
1429
1430pub fn structure_header_to_properties(
1431 s: &str,
1432 arguments: &ftd::Map<crate::p2::Kind>,
1433 doc: &ftd::p2::TDoc,
1434 line_number: usize,
1435 p1: &ftd::p1::Header,
1436) -> ftd::p1::Result<ftd::Map<ftd::component::Property>> {
1437 let (name, caption) = ftd::p2::utils::split(s.to_string(), ":")?;
1438 match doc.get_thing(line_number, &name) {
1439 Ok(ftd::p2::Thing::Component(c)) => ftd::component::read_properties(
1440 line_number,
1441 p1,
1442 &if caption.is_empty() {
1443 None
1444 } else {
1445 Some(caption)
1446 },
1447 &None,
1448 "",
1449 "",
1450 &c.arguments,
1451 arguments,
1452 doc,
1453 &Default::default(),
1454 false,
1455 ),
1456 t => ftd::p2::utils::e2(
1457 format!("expected component, found: {:?}", t),
1458 doc.name,
1459 line_number,
1460 ),
1461 }
1462}
1463
1464pub fn arguments_on_condition(
1465 condition: &ftd::p2::Boolean,
1466 line_number: usize,
1467 doc: &ftd::p2::TDoc,
1468) -> ftd::p1::Result<(ftd::Map<ftd::Value>, bool)> {
1469 let mut arguments: ftd::Map<ftd::Value> = Default::default();
1470 let mut is_visible = true;
1471 if let ftd::p2::Boolean::IsNotNull { ref value } = condition {
1472 match value {
1473 ftd::PropertyValue::Value { .. } => {}
1474 ftd::PropertyValue::Reference { name, kind }
1475 | ftd::PropertyValue::Variable { name, kind } => {
1476 if let ftd::p2::Kind::Optional { kind, .. } = kind {
1477 if doc.get_value(line_number, name).is_err() {
1478 is_visible = false;
1479 arguments.insert(
1480 name.to_string(),
1481 kind_to_value(kind, line_number, doc.name)?,
1482 );
1483 }
1484 }
1485 }
1494 }
1495 }
1496 return Ok((arguments, is_visible));
1497
1498 fn kind_to_value(
1499 kind: &ftd::p2::Kind,
1500 line_number: usize,
1501 doc_id: &str,
1502 ) -> ftd::p1::Result<ftd::Value> {
1503 if let Ok(value) = kind.to_value(line_number, doc_id) {
1504 return Ok(value);
1505 }
1506 Ok(match kind {
1508 ftd::p2::Kind::String { .. } => ftd::Value::String {
1509 text: "".to_string(),
1510 source: ftd::TextSource::Header,
1511 },
1512 ftd::p2::Kind::Integer { .. } => ftd::Value::Integer { value: 0 },
1513 ftd::p2::Kind::Decimal { .. } => ftd::Value::Decimal { value: 0.0 },
1514 ftd::p2::Kind::Boolean { .. } => ftd::Value::Boolean { value: false },
1515 _ => {
1516 return ftd::p2::utils::e2(
1517 format!(
1518 "implemented for string, integer, decimal and boolean, found: {:?}",
1519 kind
1520 ),
1521 doc_id,
1522 line_number,
1523 )
1524 }
1525 })
1526 }
1527}
1528
1529pub fn split_module<'a>(
1530 id: &'a str,
1531 _doc_id: &str,
1532 _line_number: usize,
1533) -> ftd::p1::Result<(Option<&'a str>, &'a str, Option<&'a str>)> {
1534 match id.split_once('.') {
1535 Some((p1, p2)) => match p2.split_once('.') {
1536 Some((p21, p22)) => Ok((Some(p1), p21, Some(p22))),
1537 None => Ok((Some(p1), p2, None)),
1538 },
1539 None => Ok((None, id, None)),
1540 }
1541}
1542
1543pub fn e2<T, S1>(m: S1, doc_id: &str, line_number: usize) -> ftd::p1::Result<T>
1544where
1545 S1: Into<String>,
1546{
1547 Err(ftd::p1::Error::ParseError {
1548 message: m.into(),
1549 doc_id: doc_id.to_string(),
1550 line_number,
1551 })
1552}
1553
1554pub fn unknown_processor_error<T, S>(m: S, doc_id: String, line_number: usize) -> ftd::p1::Result<T>
1555where
1556 S: Into<String>,
1557{
1558 Err(ftd::p1::Error::ParseError {
1559 message: m.into(),
1560 doc_id,
1561 line_number,
1562 })
1563}