arithmetic_parser/parser/
helpers.rs

1//! Passing helpers.
2
3use nom::{
4    branch::alt,
5    bytes::{
6        complete::{tag, take_until, take_while, take_while1, take_while_m_n},
7        streaming,
8    },
9    character::complete::char as tag_char,
10    combinator::{cut, not, peek, recognize},
11    error::context,
12    multi::many0,
13    sequence::{delimited, preceded},
14    Parser as _,
15};
16
17use crate::{grammars::Features, BinaryOp, Context, InputSpan, NomResult, Spanned, UnaryOp};
18
19pub(super) trait GrammarType {
20    const COMPLETE: bool;
21}
22
23#[derive(Debug)]
24pub(super) struct Complete(());
25
26impl GrammarType for Complete {
27    const COMPLETE: bool = true;
28}
29
30#[derive(Debug)]
31pub(super) struct Streaming(());
32
33impl GrammarType for Streaming {
34    const COMPLETE: bool = false;
35}
36
37impl UnaryOp {
38    pub(super) fn from_span(span: Spanned<'_, char>) -> Spanned<'_, Self> {
39        match span.extra {
40            '-' => span.copy_with_extra(UnaryOp::Neg),
41            '!' => span.copy_with_extra(UnaryOp::Not),
42            _ => unreachable!(),
43        }
44    }
45
46    pub(super) fn try_from_byte(byte: u8) -> Option<Self> {
47        match byte {
48            b'-' => Some(Self::Neg),
49            b'!' => Some(Self::Not),
50            _ => None,
51        }
52    }
53}
54
55impl BinaryOp {
56    pub(super) fn from_span(span: InputSpan<'_>) -> Spanned<'_, Self> {
57        Spanned::new(
58            span,
59            match *span.fragment() {
60                "+" => Self::Add,
61                "-" => Self::Sub,
62                "*" => Self::Mul,
63                "/" => Self::Div,
64                "^" => Self::Power,
65                "==" => Self::Eq,
66                "!=" => Self::NotEq,
67                "&&" => Self::And,
68                "||" => Self::Or,
69                ">" => Self::Gt,
70                "<" => Self::Lt,
71                ">=" => Self::Ge,
72                "<=" => Self::Le,
73                _ => unreachable!(),
74            },
75        )
76    }
77
78    pub(super) fn is_supported(self, features: Features) -> bool {
79        match self {
80            Self::Add | Self::Sub | Self::Mul | Self::Div | Self::Power => true,
81            Self::Eq | Self::NotEq | Self::And | Self::Or => {
82                features.contains(Features::BOOLEAN_OPS_BASIC)
83            }
84            Self::Gt | Self::Lt | Self::Ge | Self::Le => features.contains(Features::BOOLEAN_OPS),
85        }
86    }
87}
88
89/// Whitespace and comments.
90pub(super) fn ws<Ty: GrammarType>(input: InputSpan<'_>) -> NomResult<'_, InputSpan<'_>> {
91    fn narrow_ws<T: GrammarType>(input: InputSpan<'_>) -> NomResult<'_, InputSpan<'_>> {
92        if T::COMPLETE {
93            take_while1(|c: char| c.is_ascii_whitespace())(input)
94        } else {
95            streaming::take_while1(|c: char| c.is_ascii_whitespace())(input)
96        }
97    }
98
99    fn long_comment_body<T: GrammarType>(input: InputSpan<'_>) -> NomResult<'_, InputSpan<'_>> {
100        if T::COMPLETE {
101            context(Context::Comment.to_str(), cut(take_until("*/"))).parse(input)
102        } else {
103            streaming::take_until("*/")(input)
104        }
105    }
106
107    let comment = preceded(tag("//"), take_while(|c: char| c != '\n'));
108    let long_comment = delimited(tag("/*"), long_comment_body::<Ty>, tag("*/"));
109    let ws_line = alt((narrow_ws::<Ty>, comment, long_comment));
110    recognize(many0(ws_line)).parse(input)
111}
112
113pub(super) fn mandatory_ws<Ty: GrammarType>(input: InputSpan<'_>) -> NomResult<'_, InputSpan<'_>> {
114    let not_ident_char = peek(not(take_while_m_n(1, 1, |c: char| {
115        c.is_ascii_alphanumeric() || c == '_'
116    })));
117    preceded(not_ident_char, ws::<Ty>).parse(input)
118}
119
120/// Variable name, like `a_foo` or `Bar`.
121pub(super) fn var_name(input: InputSpan<'_>) -> NomResult<'_, InputSpan<'_>> {
122    context(
123        Context::Var.to_str(),
124        preceded(
125            peek(take_while_m_n(1, 1, |c: char| {
126                c.is_ascii_alphabetic() || c == '_'
127            })),
128            take_while1(|c: char| c.is_ascii_alphanumeric() || c == '_'),
129        ),
130    )
131    .parse(input)
132}
133
134/// Checks if the provided string is a valid variable name.
135pub fn is_valid_variable_name(name: &str) -> bool {
136    if name.is_empty() || !name.is_ascii() {
137        return false;
138    }
139
140    match var_name(InputSpan::new(name)) {
141        Ok((rest, _)) => rest.fragment().is_empty(),
142        Err(_) => false,
143    }
144}
145
146pub(super) fn comma_sep<Ty: GrammarType>(input: InputSpan<'_>) -> NomResult<'_, char> {
147    delimited(ws::<Ty>, tag_char(','), ws::<Ty>).parse(input)
148}