arithmetic_parser/parser/
helpers.rs1use 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
89pub(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
120pub(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
134pub 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}