1#![allow(missing_docs)] use crate::cst::syntax_kind::{SyntaxKind, SyntaxNode, SyntaxToken};
35
36pub use rowan::SyntaxText;
41
42pub trait AstNode: Sized {
45 fn can_cast(kind: SyntaxKind) -> bool;
48
49 fn cast(syntax: SyntaxNode) -> Option<Self>;
51
52 fn syntax(&self) -> &SyntaxNode;
56}
57
58pub trait AstToken: Sized {
61 fn can_cast(kind: SyntaxKind) -> bool;
62 fn cast(token: SyntaxToken) -> Option<Self>;
63 fn syntax(&self) -> &SyntaxToken;
64
65 fn text(&self) -> &str {
69 self.syntax().text()
70 }
71}
72
73fn first_token(node: &SyntaxNode, kind: SyntaxKind) -> Option<SyntaxToken> {
77 node.children_with_tokens()
78 .filter_map(rowan::NodeOrToken::into_token)
79 .find(|t| t.kind() == kind)
80}
81
82fn nth_token(node: &SyntaxNode, kind: SyntaxKind, n: usize) -> Option<SyntaxToken> {
84 node.children_with_tokens()
85 .filter_map(rowan::NodeOrToken::into_token)
86 .filter(|t| t.kind() == kind)
87 .nth(n)
88}
89
90fn tokens_of_kind(node: &SyntaxNode, kind: SyntaxKind) -> impl Iterator<Item = SyntaxToken> + '_ {
92 node.children_with_tokens()
93 .filter_map(rowan::NodeOrToken::into_token)
94 .filter(move |t| t.kind() == kind)
95}
96
97fn first_child<N: AstNode>(node: &SyntaxNode) -> Option<N> {
99 node.children().find_map(N::cast)
100}
101
102fn children<'a, N: AstNode + 'a>(node: &'a SyntaxNode) -> impl Iterator<Item = N> + 'a {
104 node.children().filter_map(N::cast)
105}
106
107macro_rules! ast_node {
110 ($(#[$meta:meta])* $name:ident, $kind:ident) => {
111 $(#[$meta])*
112 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
113 pub struct $name(SyntaxNode);
114
115 impl AstNode for $name {
116 fn can_cast(kind: SyntaxKind) -> bool {
117 kind == SyntaxKind::$kind
118 }
119 fn cast(syntax: SyntaxNode) -> Option<Self> {
120 Self::can_cast(syntax.kind()).then_some(Self(syntax))
121 }
122 fn syntax(&self) -> &SyntaxNode {
123 &self.0
124 }
125 }
126 };
127}
128
129macro_rules! ast_token {
130 ($(#[$meta:meta])* $name:ident, $kind:ident) => {
131 $(#[$meta])*
132 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
133 pub struct $name(SyntaxToken);
134
135 impl AstToken for $name {
136 fn can_cast(kind: SyntaxKind) -> bool {
137 kind == SyntaxKind::$kind
138 }
139 fn cast(token: SyntaxToken) -> Option<Self> {
140 Self::can_cast(token.kind()).then_some(Self(token))
141 }
142 fn syntax(&self) -> &SyntaxToken {
143 &self.0
144 }
145 }
146 };
147}
148
149ast_token!(
152 Date, DATE
154);
155ast_token!(
156 Account, ACCOUNT
158);
159ast_token!(
160 CurrencyName, CURRENCY
162);
163ast_token!(
164 StringLit, STRING
167);
168
169impl StringLit {
170 pub fn text_unquoted(&self) -> Option<&str> {
174 let raw = self.text();
175 let bytes = raw.as_bytes();
176 if bytes.len() < 2 || bytes[0] != b'"' || bytes[bytes.len() - 1] != b'"' {
177 return None;
178 }
179 Some(&raw[1..raw.len() - 1])
180 }
181}
182
183ast_token!(
184 Number, NUMBER
186);
187ast_token!(
188 MetaKey, META_KEY
191);
192
193impl MetaKey {
194 pub fn text_without_colon(&self) -> &str {
197 let raw = self.text();
198 raw.strip_suffix(':').unwrap_or(raw)
199 }
200}
201
202ast_token!(
203 Tag, TAG
205);
206ast_token!(
207 Link, LINK
209);
210ast_token!(
211 BoolTrue, BOOL_TRUE
213);
214ast_token!(
215 BoolFalse, BOOL_FALSE
217);
218
219#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
236pub enum TransactionFlagKind {
237 Star,
239 Pending,
241 Letter,
243 Hash,
245 Txn,
247 CurrencyLetter,
250}
251
252#[derive(Debug, Clone, PartialEq, Eq, Hash)]
266pub struct TransactionFlag {
267 token: SyntaxToken,
268 classification: TransactionFlagKind,
269}
270
271impl TransactionFlag {
272 pub fn cast(token: SyntaxToken) -> Option<Self> {
279 let classification = match token.kind() {
280 SyntaxKind::STAR => TransactionFlagKind::Star,
281 SyntaxKind::PENDING_KW => TransactionFlagKind::Pending,
282 SyntaxKind::FLAG => TransactionFlagKind::Letter,
283 SyntaxKind::HASH => TransactionFlagKind::Hash,
284 SyntaxKind::TXN_KW => TransactionFlagKind::Txn,
285 SyntaxKind::CURRENCY if token.text().len() == 1 => TransactionFlagKind::CurrencyLetter,
286 _ => return None,
287 };
288 Some(Self {
289 token,
290 classification,
291 })
292 }
293
294 pub const fn syntax(&self) -> &SyntaxToken {
295 &self.token
296 }
297 pub fn kind(&self) -> SyntaxKind {
298 self.token.kind()
299 }
300 pub fn text(&self) -> &str {
301 self.token.text()
302 }
303
304 pub const fn classify(&self) -> TransactionFlagKind {
308 self.classification
309 }
310
311 pub const fn is_star(&self) -> bool {
312 matches!(self.classification, TransactionFlagKind::Star)
313 }
314 pub const fn is_pending(&self) -> bool {
315 matches!(self.classification, TransactionFlagKind::Pending)
316 }
317 pub const fn is_hash(&self) -> bool {
318 matches!(self.classification, TransactionFlagKind::Hash)
319 }
320 pub const fn is_txn(&self) -> bool {
321 matches!(self.classification, TransactionFlagKind::Txn)
322 }
323 pub const fn is_letter_flag(&self) -> bool {
324 matches!(self.classification, TransactionFlagKind::Letter)
325 }
326 pub const fn is_currency_letter(&self) -> bool {
327 matches!(self.classification, TransactionFlagKind::CurrencyLetter)
328 }
329}
330
331#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
335pub enum PostingFlagKind {
336 Star,
337 Pending,
338 Letter,
339 Hash,
340 CurrencyLetter,
341}
342
343#[derive(Debug, Clone, PartialEq, Eq, Hash)]
352pub struct PostingFlag {
353 token: SyntaxToken,
354 classification: PostingFlagKind,
355}
356
357impl PostingFlag {
358 pub fn cast(token: SyntaxToken) -> Option<Self> {
360 let classification = match token.kind() {
361 SyntaxKind::STAR => PostingFlagKind::Star,
362 SyntaxKind::PENDING_KW => PostingFlagKind::Pending,
363 SyntaxKind::FLAG => PostingFlagKind::Letter,
364 SyntaxKind::HASH => PostingFlagKind::Hash,
365 SyntaxKind::CURRENCY if token.text().len() == 1 => PostingFlagKind::CurrencyLetter,
366 _ => return None,
367 };
368 Some(Self {
369 token,
370 classification,
371 })
372 }
373
374 pub const fn syntax(&self) -> &SyntaxToken {
375 &self.token
376 }
377 pub fn kind(&self) -> SyntaxKind {
378 self.token.kind()
379 }
380 pub fn text(&self) -> &str {
381 self.token.text()
382 }
383
384 pub const fn classify(&self) -> PostingFlagKind {
387 self.classification
388 }
389
390 pub const fn is_star(&self) -> bool {
391 matches!(self.classification, PostingFlagKind::Star)
392 }
393 pub const fn is_pending(&self) -> bool {
394 matches!(self.classification, PostingFlagKind::Pending)
395 }
396 pub const fn is_hash(&self) -> bool {
397 matches!(self.classification, PostingFlagKind::Hash)
398 }
399 pub const fn is_letter_flag(&self) -> bool {
400 matches!(self.classification, PostingFlagKind::Letter)
401 }
402 pub const fn is_currency_letter(&self) -> bool {
403 matches!(self.classification, PostingFlagKind::CurrencyLetter)
404 }
405}
406
407#[derive(Debug, Clone, PartialEq, Eq, Hash)]
417pub struct Sign(SyntaxToken);
418
419impl Sign {
420 pub fn cast(token: SyntaxToken) -> Option<Self> {
421 matches!(token.kind(), SyntaxKind::PLUS | SyntaxKind::MINUS).then_some(Self(token))
422 }
423 pub const fn syntax(&self) -> &SyntaxToken {
424 &self.0
425 }
426 pub fn kind(&self) -> SyntaxKind {
427 self.0.kind()
428 }
429 pub fn text(&self) -> &str {
430 self.0.text()
431 }
432 pub fn is_plus(&self) -> bool {
433 self.kind() == SyntaxKind::PLUS
434 }
435 pub fn is_minus(&self) -> bool {
436 self.kind() == SyntaxKind::MINUS
437 }
438}
439
440ast_node!(
443 SourceFile, SOURCE_FILE
446);
447
448impl SourceFile {
449 #[must_use]
451 pub fn parse(source: &str) -> Self {
452 let node = crate::cst::parser::parse_structured(source);
453 Self::cast(node).expect("parse_structured always returns a SOURCE_FILE")
454 }
455
456 pub fn directives(&self) -> impl Iterator<Item = Directive> + '_ {
458 self.syntax().children().filter_map(Directive::cast)
459 }
460
461 pub fn errors(&self) -> impl Iterator<Item = ErrorNode> + '_ {
463 self.syntax().children().filter_map(ErrorNode::cast)
464 }
465}
466
467macro_rules! directive_enum {
478 ($($(#[$variant_meta:meta])* $variant:ident($struct:ident, $kind:ident)),* $(,)?) => {
479 $(
483 $(#[$variant_meta])*
484 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
485 pub struct $struct(SyntaxNode);
486
487 impl AstNode for $struct {
488 fn can_cast(kind: SyntaxKind) -> bool {
489 kind == SyntaxKind::$kind
490 }
491 fn cast(syntax: SyntaxNode) -> Option<Self> {
492 Self::can_cast(syntax.kind()).then_some(Self(syntax))
493 }
494 fn syntax(&self) -> &SyntaxNode {
495 &self.0
496 }
497 }
498 )*
499
500 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
502 pub enum Directive {
503 $($variant($struct),)*
504 }
505
506 impl AstNode for Directive {
507 fn can_cast(kind: SyntaxKind) -> bool {
508 matches!(kind, $(SyntaxKind::$kind)|*)
509 }
510
511 fn cast(node: SyntaxNode) -> Option<Self> {
512 Some(match node.kind() {
513 $(SyntaxKind::$kind => Self::$variant($struct(node)),)*
514 _ => return None,
515 })
516 }
517
518 fn syntax(&self) -> &SyntaxNode {
519 match self {
520 $(Self::$variant(d) => d.syntax(),)*
521 }
522 }
523 }
524 };
525}
526
527directive_enum!(
528 Open(OpenDirective, OPEN_DIRECTIVE),
530 Close(CloseDirective, CLOSE_DIRECTIVE),
532 Balance(BalanceDirective, BALANCE_DIRECTIVE),
536 Pad(PadDirective, PAD_DIRECTIVE),
538 Event(EventDirective, EVENT_DIRECTIVE),
540 Query(QueryDirective, QUERY_DIRECTIVE),
542 Note(NoteDirective, NOTE_DIRECTIVE),
544 Document(DocumentDirective, DOCUMENT_DIRECTIVE),
546 Price(PriceDirective, PRICE_DIRECTIVE),
548 Commodity(CommodityDirective, COMMODITY_DIRECTIVE),
550 Pushtag(PushtagDirective, PUSHTAG_DIRECTIVE),
552 Poptag(PoptagDirective, POPTAG_DIRECTIVE),
554 Pushmeta(PushmetaDirective, PUSHMETA_DIRECTIVE),
556 Popmeta(PopmetaDirective, POPMETA_DIRECTIVE),
558 Option(OptionDirective, OPTION_DIRECTIVE),
560 Include(IncludeDirective, INCLUDE_DIRECTIVE),
562 Plugin(PluginDirective, PLUGIN_DIRECTIVE),
564 Custom(CustomDirective, CUSTOM_DIRECTIVE),
568 Transaction(Transaction, TRANSACTION),
572);
573
574impl Directive {
575 pub fn meta_entries(&self) -> impl Iterator<Item = MetaEntry> + '_ {
579 children(self.syntax())
580 }
581}
582
583ast_node!(
584 ErrorNode, ERROR_NODE
588);
589
590impl ErrorNode {
591 #[must_use]
596 pub fn text(&self) -> SyntaxText {
597 self.syntax().text()
598 }
599}
600
601impl OpenDirective {
608 pub fn date(&self) -> Option<Date> {
609 first_token(self.syntax(), SyntaxKind::DATE).and_then(Date::cast)
610 }
611 pub fn account(&self) -> Option<Account> {
612 first_token(self.syntax(), SyntaxKind::ACCOUNT).and_then(Account::cast)
613 }
614 pub fn currencies(&self) -> impl Iterator<Item = CurrencyName> + '_ {
616 tokens_of_kind(self.syntax(), SyntaxKind::CURRENCY).filter_map(CurrencyName::cast)
617 }
618 pub fn booking_method(&self) -> Option<StringLit> {
620 first_token(self.syntax(), SyntaxKind::STRING).and_then(StringLit::cast)
621 }
622}
623
624impl CloseDirective {
625 pub fn date(&self) -> Option<Date> {
626 first_token(self.syntax(), SyntaxKind::DATE).and_then(Date::cast)
627 }
628 pub fn account(&self) -> Option<Account> {
629 first_token(self.syntax(), SyntaxKind::ACCOUNT).and_then(Account::cast)
630 }
631}
632
633impl BalanceDirective {
634 pub fn date(&self) -> Option<Date> {
635 first_token(self.syntax(), SyntaxKind::DATE).and_then(Date::cast)
636 }
637 pub fn account(&self) -> Option<Account> {
638 first_token(self.syntax(), SyntaxKind::ACCOUNT).and_then(Account::cast)
639 }
640 pub fn number(&self) -> Option<Number> {
641 first_token(self.syntax(), SyntaxKind::NUMBER).and_then(Number::cast)
642 }
643 pub fn currency(&self) -> Option<CurrencyName> {
644 first_token(self.syntax(), SyntaxKind::CURRENCY).and_then(CurrencyName::cast)
645 }
646}
647
648impl PadDirective {
649 pub fn date(&self) -> Option<Date> {
650 first_token(self.syntax(), SyntaxKind::DATE).and_then(Date::cast)
651 }
652 pub fn target_account(&self) -> Option<Account> {
653 first_token(self.syntax(), SyntaxKind::ACCOUNT).and_then(Account::cast)
654 }
655 pub fn source_account(&self) -> Option<Account> {
656 nth_token(self.syntax(), SyntaxKind::ACCOUNT, 1).and_then(Account::cast)
657 }
658}
659
660impl EventDirective {
661 pub fn date(&self) -> Option<Date> {
662 first_token(self.syntax(), SyntaxKind::DATE).and_then(Date::cast)
663 }
664 pub fn event_type(&self) -> Option<StringLit> {
665 first_token(self.syntax(), SyntaxKind::STRING).and_then(StringLit::cast)
666 }
667 pub fn value(&self) -> Option<StringLit> {
668 nth_token(self.syntax(), SyntaxKind::STRING, 1).and_then(StringLit::cast)
669 }
670}
671
672impl QueryDirective {
673 pub fn date(&self) -> Option<Date> {
674 first_token(self.syntax(), SyntaxKind::DATE).and_then(Date::cast)
675 }
676 pub fn name(&self) -> Option<StringLit> {
677 first_token(self.syntax(), SyntaxKind::STRING).and_then(StringLit::cast)
678 }
679 pub fn query(&self) -> Option<StringLit> {
680 nth_token(self.syntax(), SyntaxKind::STRING, 1).and_then(StringLit::cast)
681 }
682}
683
684impl NoteDirective {
685 pub fn date(&self) -> Option<Date> {
686 first_token(self.syntax(), SyntaxKind::DATE).and_then(Date::cast)
687 }
688 pub fn account(&self) -> Option<Account> {
689 first_token(self.syntax(), SyntaxKind::ACCOUNT).and_then(Account::cast)
690 }
691 pub fn text(&self) -> Option<StringLit> {
692 first_token(self.syntax(), SyntaxKind::STRING).and_then(StringLit::cast)
693 }
694}
695
696impl DocumentDirective {
697 pub fn date(&self) -> Option<Date> {
698 first_token(self.syntax(), SyntaxKind::DATE).and_then(Date::cast)
699 }
700 pub fn account(&self) -> Option<Account> {
701 first_token(self.syntax(), SyntaxKind::ACCOUNT).and_then(Account::cast)
702 }
703 pub fn path(&self) -> Option<StringLit> {
704 first_token(self.syntax(), SyntaxKind::STRING).and_then(StringLit::cast)
705 }
706}
707
708impl PriceDirective {
709 pub fn date(&self) -> Option<Date> {
710 first_token(self.syntax(), SyntaxKind::DATE).and_then(Date::cast)
711 }
712 pub fn base_currency(&self) -> Option<CurrencyName> {
713 first_token(self.syntax(), SyntaxKind::CURRENCY).and_then(CurrencyName::cast)
714 }
715 pub fn number(&self) -> Option<Number> {
716 first_token(self.syntax(), SyntaxKind::NUMBER).and_then(Number::cast)
717 }
718 pub fn quote_currency(&self) -> Option<CurrencyName> {
719 nth_token(self.syntax(), SyntaxKind::CURRENCY, 1).and_then(CurrencyName::cast)
720 }
721}
722
723impl CommodityDirective {
724 pub fn date(&self) -> Option<Date> {
725 first_token(self.syntax(), SyntaxKind::DATE).and_then(Date::cast)
726 }
727 pub fn currency(&self) -> Option<CurrencyName> {
728 first_token(self.syntax(), SyntaxKind::CURRENCY).and_then(CurrencyName::cast)
729 }
730}
731
732impl PushtagDirective {
735 pub fn tag(&self) -> Option<Tag> {
736 first_token(self.syntax(), SyntaxKind::TAG).and_then(Tag::cast)
737 }
738}
739
740impl PoptagDirective {
741 pub fn tag(&self) -> Option<Tag> {
742 first_token(self.syntax(), SyntaxKind::TAG).and_then(Tag::cast)
743 }
744}
745
746impl PushmetaDirective {
747 pub fn key(&self) -> Option<MetaKey> {
748 first_token(self.syntax(), SyntaxKind::META_KEY).and_then(MetaKey::cast)
749 }
750}
751
752impl PopmetaDirective {
753 pub fn key(&self) -> Option<MetaKey> {
754 first_token(self.syntax(), SyntaxKind::META_KEY).and_then(MetaKey::cast)
755 }
756}
757
758impl OptionDirective {
761 pub fn key(&self) -> Option<StringLit> {
762 first_token(self.syntax(), SyntaxKind::STRING).and_then(StringLit::cast)
763 }
764 pub fn value(&self) -> Option<StringLit> {
765 nth_token(self.syntax(), SyntaxKind::STRING, 1).and_then(StringLit::cast)
766 }
767}
768
769impl IncludeDirective {
770 pub fn path(&self) -> Option<StringLit> {
771 first_token(self.syntax(), SyntaxKind::STRING).and_then(StringLit::cast)
772 }
773}
774
775impl PluginDirective {
776 pub fn module(&self) -> Option<StringLit> {
777 first_token(self.syntax(), SyntaxKind::STRING).and_then(StringLit::cast)
778 }
779 pub fn config(&self) -> Option<StringLit> {
780 nth_token(self.syntax(), SyntaxKind::STRING, 1).and_then(StringLit::cast)
781 }
782}
783
784impl CustomDirective {
785 pub fn date(&self) -> Option<Date> {
786 first_token(self.syntax(), SyntaxKind::DATE).and_then(Date::cast)
787 }
788 pub fn custom_type(&self) -> Option<StringLit> {
791 first_token(self.syntax(), SyntaxKind::STRING).and_then(StringLit::cast)
792 }
793}
794
795impl Transaction {
798 fn header_tokens(&self) -> impl Iterator<Item = SyntaxToken> + '_ {
807 self.syntax()
808 .children_with_tokens()
809 .filter_map(rowan::NodeOrToken::into_token)
810 .skip_while(|t| {
822 matches!(
823 t.kind(),
824 SyntaxKind::WHITESPACE
825 | SyntaxKind::NEWLINE
826 | SyntaxKind::COMMENT
827 | SyntaxKind::PERCENT_COMMENT
828 | SyntaxKind::SHEBANG
829 | SyntaxKind::EMACS_DIRECTIVE
830 )
831 })
832 .take_while(|t| t.kind() != SyntaxKind::NEWLINE)
833 }
834
835 fn flag_region_tokens(&self) -> impl Iterator<Item = SyntaxToken> + '_ {
839 self.header_tokens().take_while(|t| {
840 !matches!(
841 t.kind(),
842 SyntaxKind::STRING | SyntaxKind::TAG | SyntaxKind::LINK
843 )
844 })
845 }
846
847 pub fn date(&self) -> Option<Date> {
848 self.header_tokens()
851 .find(|t| t.kind() == SyntaxKind::DATE)
852 .and_then(Date::cast)
853 }
854
855 pub fn flag(&self) -> Option<TransactionFlag> {
866 self.flag_region_tokens().find_map(TransactionFlag::cast)
867 }
868
869 pub fn strings(&self) -> impl Iterator<Item = StringLit> + '_ {
882 self.header_tokens()
883 .filter(|t| t.kind() == SyntaxKind::STRING)
884 .filter_map(StringLit::cast)
885 }
886
887 pub fn payee(&self) -> Option<StringLit> {
894 let mut iter = self.strings();
897 let first = iter.next()?;
898 let second = iter.next()?;
899 if iter.next().is_some() {
900 None
901 } else {
902 let _ = second;
904 Some(first)
905 }
906 }
907
908 pub fn narration(&self) -> Option<StringLit> {
914 let mut iter = self.strings();
915 let first = iter.next()?;
916 let second = iter.next();
917 let third = iter.next();
918 match (second, third) {
919 (None, _) => Some(first),
920 (Some(s2), None) => Some(s2),
921 _ => None,
922 }
923 }
924
925 pub fn tags(&self) -> impl Iterator<Item = Tag> + '_ {
928 self.header_tokens()
929 .filter(|t| t.kind() == SyntaxKind::TAG)
930 .filter_map(Tag::cast)
931 }
932
933 pub fn links(&self) -> impl Iterator<Item = Link> + '_ {
936 self.header_tokens()
937 .filter(|t| t.kind() == SyntaxKind::LINK)
938 .filter_map(Link::cast)
939 }
940
941 pub fn postings(&self) -> impl Iterator<Item = Posting> + '_ {
943 children(self.syntax())
944 }
945
946 pub fn meta_entries(&self) -> impl Iterator<Item = MetaEntry> + '_ {
949 children(self.syntax())
950 }
951}
952
953ast_node!(
954 Posting, POSTING
956);
957
958impl Posting {
959 pub fn flag(&self) -> Option<PostingFlag> {
963 for el in self.syntax().children_with_tokens() {
966 if let rowan::NodeOrToken::Token(t) = el {
967 match t.kind() {
968 SyntaxKind::WHITESPACE => {}
969 SyntaxKind::ACCOUNT => return None,
970 _ => return PostingFlag::cast(t),
971 }
972 }
973 }
974 None
975 }
976
977 pub fn account(&self) -> Option<Account> {
978 first_token(self.syntax(), SyntaxKind::ACCOUNT).and_then(Account::cast)
979 }
980
981 pub fn amount(&self) -> Option<Amount> {
983 first_child(self.syntax())
984 }
985
986 pub fn cost_spec(&self) -> Option<CostSpec> {
988 first_child(self.syntax())
989 }
990
991 pub fn price_annotation(&self) -> Option<PriceAnnotation> {
993 first_child(self.syntax())
994 }
995
996 pub fn meta_entries(&self) -> impl Iterator<Item = MetaEntry> + '_ {
999 children(self.syntax())
1000 }
1001}
1002
1003ast_node!(
1006 Amount, AMOUNT
1010);
1011
1012impl Amount {
1013 pub fn sign(&self) -> Option<Sign> {
1018 let first = self
1019 .syntax()
1020 .children_with_tokens()
1021 .filter_map(rowan::NodeOrToken::into_token)
1022 .find(|t| t.kind() != SyntaxKind::WHITESPACE)?;
1023 Sign::cast(first)
1024 }
1025
1026 pub fn number(&self) -> Option<Number> {
1030 first_token(self.syntax(), SyntaxKind::NUMBER).and_then(Number::cast)
1031 }
1032
1033 pub fn currency(&self) -> Option<CurrencyName> {
1048 let mut depth: i32 = 0;
1049 let mut last_at_depth_0: Option<SyntaxToken> = None;
1050 for el in self.syntax().children_with_tokens() {
1051 let rowan::NodeOrToken::Token(t) = el else {
1052 continue;
1053 };
1054 match t.kind() {
1055 SyntaxKind::L_PAREN => depth += 1,
1056 SyntaxKind::R_PAREN => depth -= 1,
1057 SyntaxKind::CURRENCY if depth == 0 => last_at_depth_0 = Some(t),
1058 _ => {}
1059 }
1060 }
1061 if depth != 0 {
1064 return None;
1065 }
1066 last_at_depth_0.and_then(CurrencyName::cast)
1067 }
1068
1069 #[must_use]
1074 pub fn is_arithmetic(&self) -> bool {
1075 let mut seen_first_operand = false;
1076 for el in self.syntax().children_with_tokens() {
1077 if let rowan::NodeOrToken::Token(t) = el {
1078 match t.kind() {
1079 SyntaxKind::NUMBER => seen_first_operand = true,
1080 SyntaxKind::L_PAREN | SyntaxKind::R_PAREN => return true,
1081 SyntaxKind::STAR | SyntaxKind::SLASH => return true,
1082 SyntaxKind::PLUS | SyntaxKind::MINUS if seen_first_operand => return true,
1083 _ => {}
1084 }
1085 }
1086 }
1087 false
1088 }
1089}
1090
1091ast_node!(
1092 CostSpec, COST_SPEC
1096);
1097
1098impl CostSpec {
1099 #[must_use]
1101 pub fn is_total(&self) -> bool {
1102 first_token(self.syntax(), SyntaxKind::L_DOUBLE_BRACE).is_some()
1103 }
1104
1105 #[must_use]
1108 pub fn is_per_unit_plus_total(&self) -> bool {
1109 first_token(self.syntax(), SyntaxKind::L_BRACE_HASH).is_some()
1110 }
1111
1112 pub fn number(&self) -> Option<Number> {
1114 first_token(self.syntax(), SyntaxKind::NUMBER).and_then(Number::cast)
1115 }
1116
1117 pub fn currency(&self) -> Option<CurrencyName> {
1119 first_token(self.syntax(), SyntaxKind::CURRENCY).and_then(CurrencyName::cast)
1120 }
1121
1122 pub fn date(&self) -> Option<Date> {
1124 first_token(self.syntax(), SyntaxKind::DATE).and_then(Date::cast)
1125 }
1126
1127 pub fn label(&self) -> Option<StringLit> {
1129 first_token(self.syntax(), SyntaxKind::STRING).and_then(StringLit::cast)
1130 }
1131
1132 #[must_use]
1138 pub fn is_merge(&self) -> bool {
1139 let mut past_opener = false;
1140 for el in self.syntax().children_with_tokens() {
1141 if let rowan::NodeOrToken::Token(t) = el {
1142 match t.kind() {
1143 SyntaxKind::L_BRACE | SyntaxKind::L_DOUBLE_BRACE | SyntaxKind::L_BRACE_HASH => {
1144 past_opener = true;
1145 }
1146 SyntaxKind::WHITESPACE if past_opener => {}
1147 SyntaxKind::STAR if past_opener => return true,
1148 _ if past_opener => return false,
1149 _ => {}
1150 }
1151 }
1152 }
1153 false
1154 }
1155}
1156
1157ast_node!(
1158 PriceAnnotation, PRICE_ANNOTATION
1161);
1162
1163impl PriceAnnotation {
1164 #[must_use]
1166 pub fn is_total(&self) -> bool {
1167 first_token(self.syntax(), SyntaxKind::AT_AT).is_some()
1168 }
1169
1170 pub fn amount(&self) -> Option<Amount> {
1172 first_child(self.syntax())
1173 }
1174}
1175
1176ast_node!(
1177 MetaEntry, META_ENTRY
1181);
1182
1183impl MetaEntry {
1184 pub fn key(&self) -> Option<MetaKey> {
1185 first_token(self.syntax(), SyntaxKind::META_KEY).and_then(MetaKey::cast)
1186 }
1187
1188 pub fn value_string(&self) -> Option<StringLit> {
1190 first_token(self.syntax(), SyntaxKind::STRING).and_then(StringLit::cast)
1191 }
1192
1193 pub fn value_number(&self) -> Option<Number> {
1195 first_token(self.syntax(), SyntaxKind::NUMBER).and_then(Number::cast)
1196 }
1197
1198 pub fn value_date(&self) -> Option<Date> {
1200 first_token(self.syntax(), SyntaxKind::DATE).and_then(Date::cast)
1201 }
1202
1203 pub fn value_account(&self) -> Option<Account> {
1205 first_token(self.syntax(), SyntaxKind::ACCOUNT).and_then(Account::cast)
1206 }
1207
1208 pub fn value_currency(&self) -> Option<CurrencyName> {
1210 first_token(self.syntax(), SyntaxKind::CURRENCY).and_then(CurrencyName::cast)
1211 }
1212
1213 pub fn value_bool(&self) -> Option<bool> {
1215 for el in self.syntax().children_with_tokens() {
1216 if let rowan::NodeOrToken::Token(t) = el {
1217 match t.kind() {
1218 SyntaxKind::BOOL_TRUE => return Some(true),
1219 SyntaxKind::BOOL_FALSE => return Some(false),
1220 _ => {}
1221 }
1222 }
1223 }
1224 None
1225 }
1226}