#![allow(missing_docs)]
use crate::cst::syntax_kind::{SyntaxKind, SyntaxNode, SyntaxToken};
pub use rowan::SyntaxText;
pub trait AstNode: Sized {
fn can_cast(kind: SyntaxKind) -> bool;
fn cast(syntax: SyntaxNode) -> Option<Self>;
fn syntax(&self) -> &SyntaxNode;
}
pub trait AstToken: Sized {
fn can_cast(kind: SyntaxKind) -> bool;
fn cast(token: SyntaxToken) -> Option<Self>;
fn syntax(&self) -> &SyntaxToken;
fn text(&self) -> &str {
self.syntax().text()
}
}
fn first_token(node: &SyntaxNode, kind: SyntaxKind) -> Option<SyntaxToken> {
node.children_with_tokens()
.filter_map(rowan::NodeOrToken::into_token)
.find(|t| t.kind() == kind)
}
fn nth_token(node: &SyntaxNode, kind: SyntaxKind, n: usize) -> Option<SyntaxToken> {
node.children_with_tokens()
.filter_map(rowan::NodeOrToken::into_token)
.filter(|t| t.kind() == kind)
.nth(n)
}
fn tokens_of_kind(node: &SyntaxNode, kind: SyntaxKind) -> impl Iterator<Item = SyntaxToken> + '_ {
node.children_with_tokens()
.filter_map(rowan::NodeOrToken::into_token)
.filter(move |t| t.kind() == kind)
}
fn first_child<N: AstNode>(node: &SyntaxNode) -> Option<N> {
node.children().find_map(N::cast)
}
fn children<'a, N: AstNode + 'a>(node: &'a SyntaxNode) -> impl Iterator<Item = N> + 'a {
node.children().filter_map(N::cast)
}
macro_rules! ast_node {
($(#[$meta:meta])* $name:ident, $kind:ident) => {
$(#[$meta])*
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct $name(SyntaxNode);
impl AstNode for $name {
fn can_cast(kind: SyntaxKind) -> bool {
kind == SyntaxKind::$kind
}
fn cast(syntax: SyntaxNode) -> Option<Self> {
Self::can_cast(syntax.kind()).then_some(Self(syntax))
}
fn syntax(&self) -> &SyntaxNode {
&self.0
}
}
};
}
macro_rules! ast_token {
($(#[$meta:meta])* $name:ident, $kind:ident) => {
$(#[$meta])*
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct $name(SyntaxToken);
impl AstToken for $name {
fn can_cast(kind: SyntaxKind) -> bool {
kind == SyntaxKind::$kind
}
fn cast(token: SyntaxToken) -> Option<Self> {
Self::can_cast(token.kind()).then_some(Self(token))
}
fn syntax(&self) -> &SyntaxToken {
&self.0
}
}
};
}
ast_token!(
Date, DATE
);
ast_token!(
Account, ACCOUNT
);
ast_token!(
CurrencyName, CURRENCY
);
ast_token!(
StringLit, STRING
);
impl StringLit {
pub fn text_unquoted(&self) -> Option<&str> {
let raw = self.text();
let bytes = raw.as_bytes();
if bytes.len() < 2 || bytes[0] != b'"' || bytes[bytes.len() - 1] != b'"' {
return None;
}
Some(&raw[1..raw.len() - 1])
}
}
ast_token!(
Number, NUMBER
);
ast_token!(
MetaKey, META_KEY
);
impl MetaKey {
pub fn text_without_colon(&self) -> &str {
let raw = self.text();
raw.strip_suffix(':').unwrap_or(raw)
}
}
ast_token!(
Tag, TAG
);
ast_token!(
Link, LINK
);
ast_token!(
BoolTrue, BOOL_TRUE
);
ast_token!(
BoolFalse, BOOL_FALSE
);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TransactionFlagKind {
Star,
Pending,
Letter,
Hash,
Txn,
CurrencyLetter,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct TransactionFlag {
token: SyntaxToken,
classification: TransactionFlagKind,
}
impl TransactionFlag {
pub fn cast(token: SyntaxToken) -> Option<Self> {
let classification = match token.kind() {
SyntaxKind::STAR => TransactionFlagKind::Star,
SyntaxKind::PENDING_KW => TransactionFlagKind::Pending,
SyntaxKind::FLAG => TransactionFlagKind::Letter,
SyntaxKind::HASH => TransactionFlagKind::Hash,
SyntaxKind::TXN_KW => TransactionFlagKind::Txn,
SyntaxKind::CURRENCY if token.text().len() == 1 => TransactionFlagKind::CurrencyLetter,
_ => return None,
};
Some(Self {
token,
classification,
})
}
pub const fn syntax(&self) -> &SyntaxToken {
&self.token
}
pub fn kind(&self) -> SyntaxKind {
self.token.kind()
}
pub fn text(&self) -> &str {
self.token.text()
}
pub const fn classify(&self) -> TransactionFlagKind {
self.classification
}
pub const fn is_star(&self) -> bool {
matches!(self.classification, TransactionFlagKind::Star)
}
pub const fn is_pending(&self) -> bool {
matches!(self.classification, TransactionFlagKind::Pending)
}
pub const fn is_hash(&self) -> bool {
matches!(self.classification, TransactionFlagKind::Hash)
}
pub const fn is_txn(&self) -> bool {
matches!(self.classification, TransactionFlagKind::Txn)
}
pub const fn is_letter_flag(&self) -> bool {
matches!(self.classification, TransactionFlagKind::Letter)
}
pub const fn is_currency_letter(&self) -> bool {
matches!(self.classification, TransactionFlagKind::CurrencyLetter)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum PostingFlagKind {
Star,
Pending,
Letter,
Hash,
CurrencyLetter,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct PostingFlag {
token: SyntaxToken,
classification: PostingFlagKind,
}
impl PostingFlag {
pub fn cast(token: SyntaxToken) -> Option<Self> {
let classification = match token.kind() {
SyntaxKind::STAR => PostingFlagKind::Star,
SyntaxKind::PENDING_KW => PostingFlagKind::Pending,
SyntaxKind::FLAG => PostingFlagKind::Letter,
SyntaxKind::HASH => PostingFlagKind::Hash,
SyntaxKind::CURRENCY if token.text().len() == 1 => PostingFlagKind::CurrencyLetter,
_ => return None,
};
Some(Self {
token,
classification,
})
}
pub const fn syntax(&self) -> &SyntaxToken {
&self.token
}
pub fn kind(&self) -> SyntaxKind {
self.token.kind()
}
pub fn text(&self) -> &str {
self.token.text()
}
pub const fn classify(&self) -> PostingFlagKind {
self.classification
}
pub const fn is_star(&self) -> bool {
matches!(self.classification, PostingFlagKind::Star)
}
pub const fn is_pending(&self) -> bool {
matches!(self.classification, PostingFlagKind::Pending)
}
pub const fn is_hash(&self) -> bool {
matches!(self.classification, PostingFlagKind::Hash)
}
pub const fn is_letter_flag(&self) -> bool {
matches!(self.classification, PostingFlagKind::Letter)
}
pub const fn is_currency_letter(&self) -> bool {
matches!(self.classification, PostingFlagKind::CurrencyLetter)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Sign(SyntaxToken);
impl Sign {
pub fn cast(token: SyntaxToken) -> Option<Self> {
matches!(token.kind(), SyntaxKind::PLUS | SyntaxKind::MINUS).then_some(Self(token))
}
pub const fn syntax(&self) -> &SyntaxToken {
&self.0
}
pub fn kind(&self) -> SyntaxKind {
self.0.kind()
}
pub fn text(&self) -> &str {
self.0.text()
}
pub fn is_plus(&self) -> bool {
self.kind() == SyntaxKind::PLUS
}
pub fn is_minus(&self) -> bool {
self.kind() == SyntaxKind::MINUS
}
}
ast_node!(
SourceFile, SOURCE_FILE
);
impl SourceFile {
#[must_use]
pub fn parse(source: &str) -> Self {
let node = crate::cst::parser::parse_structured(source);
Self::cast(node).expect("parse_structured always returns a SOURCE_FILE")
}
pub fn directives(&self) -> impl Iterator<Item = Directive> + '_ {
self.syntax().children().filter_map(Directive::cast)
}
pub fn errors(&self) -> impl Iterator<Item = ErrorNode> + '_ {
self.syntax().children().filter_map(ErrorNode::cast)
}
}
macro_rules! directive_enum {
($($(#[$variant_meta:meta])* $variant:ident($struct:ident, $kind:ident)),* $(,)?) => {
$(
$(#[$variant_meta])*
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct $struct(SyntaxNode);
impl AstNode for $struct {
fn can_cast(kind: SyntaxKind) -> bool {
kind == SyntaxKind::$kind
}
fn cast(syntax: SyntaxNode) -> Option<Self> {
Self::can_cast(syntax.kind()).then_some(Self(syntax))
}
fn syntax(&self) -> &SyntaxNode {
&self.0
}
}
)*
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Directive {
$($variant($struct),)*
}
impl AstNode for Directive {
fn can_cast(kind: SyntaxKind) -> bool {
matches!(kind, $(SyntaxKind::$kind)|*)
}
fn cast(node: SyntaxNode) -> Option<Self> {
Some(match node.kind() {
$(SyntaxKind::$kind => Self::$variant($struct(node)),)*
_ => return None,
})
}
fn syntax(&self) -> &SyntaxNode {
match self {
$(Self::$variant(d) => d.syntax(),)*
}
}
}
};
}
directive_enum!(
Open(OpenDirective, OPEN_DIRECTIVE),
Close(CloseDirective, CLOSE_DIRECTIVE),
Balance(BalanceDirective, BALANCE_DIRECTIVE),
Pad(PadDirective, PAD_DIRECTIVE),
Event(EventDirective, EVENT_DIRECTIVE),
Query(QueryDirective, QUERY_DIRECTIVE),
Note(NoteDirective, NOTE_DIRECTIVE),
Document(DocumentDirective, DOCUMENT_DIRECTIVE),
Price(PriceDirective, PRICE_DIRECTIVE),
Commodity(CommodityDirective, COMMODITY_DIRECTIVE),
Pushtag(PushtagDirective, PUSHTAG_DIRECTIVE),
Poptag(PoptagDirective, POPTAG_DIRECTIVE),
Pushmeta(PushmetaDirective, PUSHMETA_DIRECTIVE),
Popmeta(PopmetaDirective, POPMETA_DIRECTIVE),
Option(OptionDirective, OPTION_DIRECTIVE),
Include(IncludeDirective, INCLUDE_DIRECTIVE),
Plugin(PluginDirective, PLUGIN_DIRECTIVE),
Custom(CustomDirective, CUSTOM_DIRECTIVE),
Transaction(Transaction, TRANSACTION),
);
impl Directive {
pub fn meta_entries(&self) -> impl Iterator<Item = MetaEntry> + '_ {
children(self.syntax())
}
}
ast_node!(
ErrorNode, ERROR_NODE
);
impl ErrorNode {
#[must_use]
pub fn text(&self) -> SyntaxText {
self.syntax().text()
}
}
impl OpenDirective {
pub fn date(&self) -> Option<Date> {
first_token(self.syntax(), SyntaxKind::DATE).and_then(Date::cast)
}
pub fn account(&self) -> Option<Account> {
first_token(self.syntax(), SyntaxKind::ACCOUNT).and_then(Account::cast)
}
pub fn currencies(&self) -> impl Iterator<Item = CurrencyName> + '_ {
tokens_of_kind(self.syntax(), SyntaxKind::CURRENCY).filter_map(CurrencyName::cast)
}
pub fn booking_method(&self) -> Option<StringLit> {
first_token(self.syntax(), SyntaxKind::STRING).and_then(StringLit::cast)
}
}
impl CloseDirective {
pub fn date(&self) -> Option<Date> {
first_token(self.syntax(), SyntaxKind::DATE).and_then(Date::cast)
}
pub fn account(&self) -> Option<Account> {
first_token(self.syntax(), SyntaxKind::ACCOUNT).and_then(Account::cast)
}
}
impl BalanceDirective {
pub fn date(&self) -> Option<Date> {
first_token(self.syntax(), SyntaxKind::DATE).and_then(Date::cast)
}
pub fn account(&self) -> Option<Account> {
first_token(self.syntax(), SyntaxKind::ACCOUNT).and_then(Account::cast)
}
pub fn number(&self) -> Option<Number> {
first_token(self.syntax(), SyntaxKind::NUMBER).and_then(Number::cast)
}
pub fn currency(&self) -> Option<CurrencyName> {
first_token(self.syntax(), SyntaxKind::CURRENCY).and_then(CurrencyName::cast)
}
}
impl PadDirective {
pub fn date(&self) -> Option<Date> {
first_token(self.syntax(), SyntaxKind::DATE).and_then(Date::cast)
}
pub fn target_account(&self) -> Option<Account> {
first_token(self.syntax(), SyntaxKind::ACCOUNT).and_then(Account::cast)
}
pub fn source_account(&self) -> Option<Account> {
nth_token(self.syntax(), SyntaxKind::ACCOUNT, 1).and_then(Account::cast)
}
}
impl EventDirective {
pub fn date(&self) -> Option<Date> {
first_token(self.syntax(), SyntaxKind::DATE).and_then(Date::cast)
}
pub fn event_type(&self) -> Option<StringLit> {
first_token(self.syntax(), SyntaxKind::STRING).and_then(StringLit::cast)
}
pub fn value(&self) -> Option<StringLit> {
nth_token(self.syntax(), SyntaxKind::STRING, 1).and_then(StringLit::cast)
}
}
impl QueryDirective {
pub fn date(&self) -> Option<Date> {
first_token(self.syntax(), SyntaxKind::DATE).and_then(Date::cast)
}
pub fn name(&self) -> Option<StringLit> {
first_token(self.syntax(), SyntaxKind::STRING).and_then(StringLit::cast)
}
pub fn query(&self) -> Option<StringLit> {
nth_token(self.syntax(), SyntaxKind::STRING, 1).and_then(StringLit::cast)
}
}
impl NoteDirective {
pub fn date(&self) -> Option<Date> {
first_token(self.syntax(), SyntaxKind::DATE).and_then(Date::cast)
}
pub fn account(&self) -> Option<Account> {
first_token(self.syntax(), SyntaxKind::ACCOUNT).and_then(Account::cast)
}
pub fn text(&self) -> Option<StringLit> {
first_token(self.syntax(), SyntaxKind::STRING).and_then(StringLit::cast)
}
}
impl DocumentDirective {
pub fn date(&self) -> Option<Date> {
first_token(self.syntax(), SyntaxKind::DATE).and_then(Date::cast)
}
pub fn account(&self) -> Option<Account> {
first_token(self.syntax(), SyntaxKind::ACCOUNT).and_then(Account::cast)
}
pub fn path(&self) -> Option<StringLit> {
first_token(self.syntax(), SyntaxKind::STRING).and_then(StringLit::cast)
}
}
impl PriceDirective {
pub fn date(&self) -> Option<Date> {
first_token(self.syntax(), SyntaxKind::DATE).and_then(Date::cast)
}
pub fn base_currency(&self) -> Option<CurrencyName> {
first_token(self.syntax(), SyntaxKind::CURRENCY).and_then(CurrencyName::cast)
}
pub fn number(&self) -> Option<Number> {
first_token(self.syntax(), SyntaxKind::NUMBER).and_then(Number::cast)
}
pub fn quote_currency(&self) -> Option<CurrencyName> {
nth_token(self.syntax(), SyntaxKind::CURRENCY, 1).and_then(CurrencyName::cast)
}
}
impl CommodityDirective {
pub fn date(&self) -> Option<Date> {
first_token(self.syntax(), SyntaxKind::DATE).and_then(Date::cast)
}
pub fn currency(&self) -> Option<CurrencyName> {
first_token(self.syntax(), SyntaxKind::CURRENCY).and_then(CurrencyName::cast)
}
}
impl PushtagDirective {
pub fn tag(&self) -> Option<Tag> {
first_token(self.syntax(), SyntaxKind::TAG).and_then(Tag::cast)
}
}
impl PoptagDirective {
pub fn tag(&self) -> Option<Tag> {
first_token(self.syntax(), SyntaxKind::TAG).and_then(Tag::cast)
}
}
impl PushmetaDirective {
pub fn key(&self) -> Option<MetaKey> {
first_token(self.syntax(), SyntaxKind::META_KEY).and_then(MetaKey::cast)
}
}
impl PopmetaDirective {
pub fn key(&self) -> Option<MetaKey> {
first_token(self.syntax(), SyntaxKind::META_KEY).and_then(MetaKey::cast)
}
}
impl OptionDirective {
pub fn key(&self) -> Option<StringLit> {
first_token(self.syntax(), SyntaxKind::STRING).and_then(StringLit::cast)
}
pub fn value(&self) -> Option<StringLit> {
nth_token(self.syntax(), SyntaxKind::STRING, 1).and_then(StringLit::cast)
}
}
impl IncludeDirective {
pub fn path(&self) -> Option<StringLit> {
first_token(self.syntax(), SyntaxKind::STRING).and_then(StringLit::cast)
}
}
impl PluginDirective {
pub fn module(&self) -> Option<StringLit> {
first_token(self.syntax(), SyntaxKind::STRING).and_then(StringLit::cast)
}
pub fn config(&self) -> Option<StringLit> {
nth_token(self.syntax(), SyntaxKind::STRING, 1).and_then(StringLit::cast)
}
}
impl CustomDirective {
pub fn date(&self) -> Option<Date> {
first_token(self.syntax(), SyntaxKind::DATE).and_then(Date::cast)
}
pub fn custom_type(&self) -> Option<StringLit> {
first_token(self.syntax(), SyntaxKind::STRING).and_then(StringLit::cast)
}
}
impl Transaction {
fn header_tokens(&self) -> impl Iterator<Item = SyntaxToken> + '_ {
self.syntax()
.children_with_tokens()
.filter_map(rowan::NodeOrToken::into_token)
.skip_while(|t| {
matches!(
t.kind(),
SyntaxKind::WHITESPACE
| SyntaxKind::NEWLINE
| SyntaxKind::COMMENT
| SyntaxKind::PERCENT_COMMENT
| SyntaxKind::SHEBANG
| SyntaxKind::EMACS_DIRECTIVE
)
})
.take_while(|t| t.kind() != SyntaxKind::NEWLINE)
}
fn flag_region_tokens(&self) -> impl Iterator<Item = SyntaxToken> + '_ {
self.header_tokens().take_while(|t| {
!matches!(
t.kind(),
SyntaxKind::STRING | SyntaxKind::TAG | SyntaxKind::LINK
)
})
}
pub fn date(&self) -> Option<Date> {
self.header_tokens()
.find(|t| t.kind() == SyntaxKind::DATE)
.and_then(Date::cast)
}
pub fn flag(&self) -> Option<TransactionFlag> {
self.flag_region_tokens().find_map(TransactionFlag::cast)
}
pub fn strings(&self) -> impl Iterator<Item = StringLit> + '_ {
self.header_tokens()
.filter(|t| t.kind() == SyntaxKind::STRING)
.filter_map(StringLit::cast)
}
pub fn payee(&self) -> Option<StringLit> {
let mut iter = self.strings();
let first = iter.next()?;
let second = iter.next()?;
if iter.next().is_some() {
None
} else {
let _ = second;
Some(first)
}
}
pub fn narration(&self) -> Option<StringLit> {
let mut iter = self.strings();
let first = iter.next()?;
let second = iter.next();
let third = iter.next();
match (second, third) {
(None, _) => Some(first),
(Some(s2), None) => Some(s2),
_ => None,
}
}
pub fn tags(&self) -> impl Iterator<Item = Tag> + '_ {
self.header_tokens()
.filter(|t| t.kind() == SyntaxKind::TAG)
.filter_map(Tag::cast)
}
pub fn links(&self) -> impl Iterator<Item = Link> + '_ {
self.header_tokens()
.filter(|t| t.kind() == SyntaxKind::LINK)
.filter_map(Link::cast)
}
pub fn postings(&self) -> impl Iterator<Item = Posting> + '_ {
children(self.syntax())
}
pub fn meta_entries(&self) -> impl Iterator<Item = MetaEntry> + '_ {
children(self.syntax())
}
}
ast_node!(
Posting, POSTING
);
impl Posting {
pub fn flag(&self) -> Option<PostingFlag> {
for el in self.syntax().children_with_tokens() {
if let rowan::NodeOrToken::Token(t) = el {
match t.kind() {
SyntaxKind::WHITESPACE => {}
SyntaxKind::ACCOUNT => return None,
_ => return PostingFlag::cast(t),
}
}
}
None
}
pub fn account(&self) -> Option<Account> {
first_token(self.syntax(), SyntaxKind::ACCOUNT).and_then(Account::cast)
}
pub fn amount(&self) -> Option<Amount> {
first_child(self.syntax())
}
pub fn cost_spec(&self) -> Option<CostSpec> {
first_child(self.syntax())
}
pub fn price_annotation(&self) -> Option<PriceAnnotation> {
first_child(self.syntax())
}
pub fn meta_entries(&self) -> impl Iterator<Item = MetaEntry> + '_ {
children(self.syntax())
}
}
ast_node!(
Amount, AMOUNT
);
impl Amount {
pub fn sign(&self) -> Option<Sign> {
let first = self
.syntax()
.children_with_tokens()
.filter_map(rowan::NodeOrToken::into_token)
.find(|t| t.kind() != SyntaxKind::WHITESPACE)?;
Sign::cast(first)
}
pub fn number(&self) -> Option<Number> {
first_token(self.syntax(), SyntaxKind::NUMBER).and_then(Number::cast)
}
pub fn currency(&self) -> Option<CurrencyName> {
let mut depth: i32 = 0;
let mut last_at_depth_0: Option<SyntaxToken> = None;
for el in self.syntax().children_with_tokens() {
let rowan::NodeOrToken::Token(t) = el else {
continue;
};
match t.kind() {
SyntaxKind::L_PAREN => depth += 1,
SyntaxKind::R_PAREN => depth -= 1,
SyntaxKind::CURRENCY if depth == 0 => last_at_depth_0 = Some(t),
_ => {}
}
}
if depth != 0 {
return None;
}
last_at_depth_0.and_then(CurrencyName::cast)
}
#[must_use]
pub fn is_arithmetic(&self) -> bool {
let mut seen_first_operand = false;
for el in self.syntax().children_with_tokens() {
if let rowan::NodeOrToken::Token(t) = el {
match t.kind() {
SyntaxKind::NUMBER => seen_first_operand = true,
SyntaxKind::L_PAREN | SyntaxKind::R_PAREN => return true,
SyntaxKind::STAR | SyntaxKind::SLASH => return true,
SyntaxKind::PLUS | SyntaxKind::MINUS if seen_first_operand => return true,
_ => {}
}
}
}
false
}
}
ast_node!(
CostSpec, COST_SPEC
);
impl CostSpec {
#[must_use]
pub fn is_total(&self) -> bool {
first_token(self.syntax(), SyntaxKind::L_DOUBLE_BRACE).is_some()
}
#[must_use]
pub fn is_per_unit_plus_total(&self) -> bool {
first_token(self.syntax(), SyntaxKind::L_BRACE_HASH).is_some()
}
pub fn number(&self) -> Option<Number> {
first_token(self.syntax(), SyntaxKind::NUMBER).and_then(Number::cast)
}
pub fn currency(&self) -> Option<CurrencyName> {
first_token(self.syntax(), SyntaxKind::CURRENCY).and_then(CurrencyName::cast)
}
pub fn date(&self) -> Option<Date> {
first_token(self.syntax(), SyntaxKind::DATE).and_then(Date::cast)
}
pub fn label(&self) -> Option<StringLit> {
first_token(self.syntax(), SyntaxKind::STRING).and_then(StringLit::cast)
}
#[must_use]
pub fn is_merge(&self) -> bool {
let mut past_opener = false;
for el in self.syntax().children_with_tokens() {
if let rowan::NodeOrToken::Token(t) = el {
match t.kind() {
SyntaxKind::L_BRACE | SyntaxKind::L_DOUBLE_BRACE | SyntaxKind::L_BRACE_HASH => {
past_opener = true;
}
SyntaxKind::WHITESPACE if past_opener => {}
SyntaxKind::STAR if past_opener => return true,
_ if past_opener => return false,
_ => {}
}
}
}
false
}
}
ast_node!(
PriceAnnotation, PRICE_ANNOTATION
);
impl PriceAnnotation {
#[must_use]
pub fn is_total(&self) -> bool {
first_token(self.syntax(), SyntaxKind::AT_AT).is_some()
}
pub fn amount(&self) -> Option<Amount> {
first_child(self.syntax())
}
}
ast_node!(
MetaEntry, META_ENTRY
);
impl MetaEntry {
pub fn key(&self) -> Option<MetaKey> {
first_token(self.syntax(), SyntaxKind::META_KEY).and_then(MetaKey::cast)
}
pub fn value_string(&self) -> Option<StringLit> {
first_token(self.syntax(), SyntaxKind::STRING).and_then(StringLit::cast)
}
pub fn value_number(&self) -> Option<Number> {
first_token(self.syntax(), SyntaxKind::NUMBER).and_then(Number::cast)
}
pub fn value_date(&self) -> Option<Date> {
first_token(self.syntax(), SyntaxKind::DATE).and_then(Date::cast)
}
pub fn value_account(&self) -> Option<Account> {
first_token(self.syntax(), SyntaxKind::ACCOUNT).and_then(Account::cast)
}
pub fn value_currency(&self) -> Option<CurrencyName> {
first_token(self.syntax(), SyntaxKind::CURRENCY).and_then(CurrencyName::cast)
}
pub fn value_bool(&self) -> Option<bool> {
for el in self.syntax().children_with_tokens() {
if let rowan::NodeOrToken::Token(t) = el {
match t.kind() {
SyntaxKind::BOOL_TRUE => return Some(true),
SyntaxKind::BOOL_FALSE => return Some(false),
_ => {}
}
}
}
None
}
}