use nom::{
branch::alt,
bytes::{
complete::{tag, take_until, take_while, take_while1, take_while_m_n},
streaming,
},
character::complete::char as tag_char,
combinator::{cut, not, peek, recognize},
error::context,
multi::many0,
sequence::{delimited, preceded},
};
use crate::{grammars::Features, BinaryOp, Context, InputSpan, NomResult, Spanned, UnaryOp};
pub(super) trait GrammarType {
const COMPLETE: bool;
}
#[derive(Debug)]
pub(super) struct Complete(());
impl GrammarType for Complete {
const COMPLETE: bool = true;
}
#[derive(Debug)]
pub(super) struct Streaming(());
impl GrammarType for Streaming {
const COMPLETE: bool = false;
}
impl UnaryOp {
pub(super) fn from_span(span: Spanned<'_, char>) -> Spanned<'_, Self> {
match span.extra {
'-' => span.copy_with_extra(UnaryOp::Neg),
'!' => span.copy_with_extra(UnaryOp::Not),
_ => unreachable!(),
}
}
pub(super) fn try_from_byte(byte: u8) -> Option<Self> {
match byte {
b'-' => Some(Self::Neg),
b'!' => Some(Self::Not),
_ => None,
}
}
}
impl BinaryOp {
pub(super) fn from_span(span: InputSpan<'_>) -> Spanned<'_, Self> {
Spanned::new(
span,
match *span.fragment() {
"+" => Self::Add,
"-" => Self::Sub,
"*" => Self::Mul,
"/" => Self::Div,
"^" => Self::Power,
"==" => Self::Eq,
"!=" => Self::NotEq,
"&&" => Self::And,
"||" => Self::Or,
">" => Self::Gt,
"<" => Self::Lt,
">=" => Self::Ge,
"<=" => Self::Le,
_ => unreachable!(),
},
)
}
pub(super) fn is_supported(self, features: Features) -> bool {
match self {
Self::Add | Self::Sub | Self::Mul | Self::Div | Self::Power => true,
Self::Eq | Self::NotEq | Self::And | Self::Or => {
features.contains(Features::BOOLEAN_OPS_BASIC)
}
Self::Gt | Self::Lt | Self::Ge | Self::Le => features.contains(Features::BOOLEAN_OPS),
}
}
}
pub(super) fn ws<Ty: GrammarType>(input: InputSpan<'_>) -> NomResult<'_, InputSpan<'_>> {
fn narrow_ws<T: GrammarType>(input: InputSpan<'_>) -> NomResult<'_, InputSpan<'_>> {
if T::COMPLETE {
take_while1(|c: char| c.is_ascii_whitespace())(input)
} else {
streaming::take_while1(|c: char| c.is_ascii_whitespace())(input)
}
}
fn long_comment_body<T: GrammarType>(input: InputSpan<'_>) -> NomResult<'_, InputSpan<'_>> {
if T::COMPLETE {
context(Context::Comment.to_str(), cut(take_until("*/")))(input)
} else {
streaming::take_until("*/")(input)
}
}
let comment = preceded(tag("//"), take_while(|c: char| c != '\n'));
let long_comment = delimited(tag("/*"), long_comment_body::<Ty>, tag("*/"));
let ws_line = alt((narrow_ws::<Ty>, comment, long_comment));
recognize(many0(ws_line))(input)
}
pub(super) fn mandatory_ws<Ty: GrammarType>(input: InputSpan<'_>) -> NomResult<'_, InputSpan<'_>> {
let not_ident_char = peek(not(take_while_m_n(1, 1, |c: char| {
c.is_ascii_alphanumeric() || c == '_'
})));
preceded(not_ident_char, ws::<Ty>)(input)
}
pub(super) fn var_name(input: InputSpan<'_>) -> NomResult<'_, InputSpan<'_>> {
context(
Context::Var.to_str(),
preceded(
peek(take_while_m_n(1, 1, |c: char| {
c.is_ascii_alphabetic() || c == '_'
})),
take_while1(|c: char| c.is_ascii_alphanumeric() || c == '_'),
),
)(input)
}
pub fn is_valid_variable_name(name: &str) -> bool {
if name.is_empty() || !name.is_ascii() {
return false;
}
match var_name(InputSpan::new(name)) {
Ok((rest, _)) => rest.fragment().is_empty(),
Err(_) => false,
}
}
pub(super) fn comma_sep<Ty: GrammarType>(input: InputSpan<'_>) -> NomResult<'_, char> {
delimited(ws::<Ty>, tag_char(','), ws::<Ty>)(input)
}