tackler_core/
parser.rs

1/*
2 * Tackler-NG 2022-2025
3 * SPDX-License-Identifier: Apache-2.0
4 */
5pub use crate::parser::pricedb_parser::{pricedb_from_file, pricedb_from_str};
6pub use crate::parser::tackler_txns::GitInputSelector;
7pub use crate::parser::tackler_txns::git_to_txns;
8pub use crate::parser::tackler_txns::paths_to_txns;
9pub use crate::parser::tackler_txns::string_to_txns;
10use winnow::error::{ErrMode, FromExternalError};
11
12mod error;
13mod pricedb_parser;
14mod tackler_parser;
15mod tackler_txns;
16
17use crate::kernel::settings::Settings;
18use crate::parser::error::TacklerTxnError;
19use winnow::Stateful;
20
21pub(crate) mod parts;
22
23pub(crate) type Stream<'is> = Stateful<&'is str, &'is mut Settings>;
24
25pub(crate) fn make_semantic_error<
26    'is,
27    E: winnow::error::FromExternalError<Stream<'is>, TacklerTxnError>,
28>(
29    is: &mut Stream<'is>,
30    msg: &str,
31) -> ErrMode<E> {
32    ErrMode::from_external_error(is, TacklerTxnError::semantic_error(msg)).cut()
33}
34
35pub(crate) fn from_error<
36    'is,
37    E: winnow::error::FromExternalError<Stream<'is>, TacklerTxnError>,
38    SE: std::error::Error + ?Sized,
39>(
40    is: &mut Stream<'is>,
41    err: &SE,
42) -> ErrMode<E> {
43    ErrMode::from_external_error(
44        is,
45        TacklerTxnError::semantic_error(err.to_string().as_str()),
46    )
47    .cut()
48}
49
50/*
51 * TODO: This logic should be 1:1 with TxnLexer.g4
52 *       (ID, SUBID and NameChar + NameStartChar)
53 * Real account names are coming through parser+lexer,
54 * So these are validating Chart-of-Xyz config/settings
55 * entries (accounts, tags, commodities).
56 * E.g. checking these is a nicety for user
57 * (warn about invalid Chart-Of-Xyz).
58 */
59#[inline]
60fn illegal_characters(c: char) -> bool {
61    c == ':' || c.is_whitespace()
62}
63
64// todo: this is too relaxed
65fn is_valid_id_start_char(c: char) -> bool {
66    !(c.is_ascii_digit() || c == ':' || c == '-' || c == '_' || c == 'ยท' || c.is_whitespace())
67}
68
69// this is fine, once is_valid_id_start_char is fixed
70fn is_valid_sub_id_start_char(c: char) -> bool {
71    c.is_numeric() || is_valid_id_start_char(c)
72}
73
74// todo: this is too relaxed
75pub fn is_valid_id(token: &str) -> bool {
76    !token.is_empty()
77        && token.starts_with(is_valid_id_start_char)
78        && !token.contains(illegal_characters)
79}
80
81// todo: this is too relaxed
82pub fn is_valid_sub_id(token: &str) -> bool {
83    !token.is_empty()
84        && token.starts_with(is_valid_sub_id_start_char)
85        && !token.contains(illegal_characters)
86}
87
88#[cfg(test)]
89mod tests;