Skip to content

Instantly share code, notes, and snippets.

@MarinPostma
Created September 8, 2021 18:51
Show Gist options
  • Save MarinPostma/78454ea2bc8a18b5707ab81248a8f0a7 to your computer and use it in GitHub Desktop.
Save MarinPostma/78454ea2bc8a18b5707ab81248a8f0a7 to your computer and use it in GitHub Desktop.
meili nom parser
use nom::{IResult, branch::alt, bytes::complete::{take_while1, tag}, character::complete::{char, multispace0}, combinator::map, error::ParseError, multi::many0, sequence::{delimited, tuple, preceded}};
fn is_key_component(c: char) -> bool {
c.is_alphanumeric() || ['_', '-', '.'].contains(&c)
}
fn parse_key(input: &str) -> IResult<&str, &str> {
let key = |input| take_while1(is_key_component)(input);
alt((key, delimited(char('"'), key, char('"'))))(input)
}
#[derive(Debug)]
enum Expression<'a> {
And(Box<Expression<'a>>, Box<Expression<'a>>),
Or(Box<Expression<'a>>, Box<Expression<'a>>),
Not(Box<Expression<'a>>),
Condition(Condition<'a>),
}
#[derive(Debug)]
enum Condition<'a> {
Greater {
key: &'a str,
value: &'a str,
},
GreaterEq {
key: &'a str,
value: &'a str,
},
Lower {
key: &'a str,
value: &'a str,
},
LowerEq {
key: &'a str,
value: &'a str,
},
Neq {
key: &'a str,
value: &'a str,
},
Eq {
key: &'a str,
value: &'a str,
},
Range {
key: &'a str,
from: &'a str,
to: &'a str,
}
}
fn ws<'a, F: 'a, O, E: ParseError<&'a str>>(inner: F) -> impl FnMut(&'a str) -> IResult<&'a str, O, E>
where
F: Fn(&'a str) -> IResult<&'a str, O, E>,
{
delimited(
multispace0,
inner,
multispace0
)
}
fn parse_condition(input: &str) -> IResult<&str, Condition> {
let parse_simple_condition = |input| {
let operator = alt((tag(">"), tag(">="), tag("="), tag("<"), tag("!="), tag("<=")));
let (input, (key, op, value)) = tuple((ws(parse_key), operator, ws(parse_key)))(input)?;
let res = match op {
">" => Condition::Greater { key, value },
"<" => Condition::Lower { key, value },
"<=" => Condition::LowerEq { key, value },
">=" => Condition::GreaterEq { key, value },
"=" => Condition::Eq { key, value },
"!=" => Condition::Neq { key, value },
_ => unreachable!(),
};
Ok((input, res))
};
let parse_range_condition = |input| {
let (input, (key, from, _, to)) = tuple((ws(parse_key), ws(parse_key), tag("TO"), ws(parse_key)))(input)?;
Ok((input, Condition::Range { key, from, to }))
};
let (input, condition) = alt((parse_simple_condition, parse_range_condition))(input)?;
Ok((input, condition))
}
fn parse_or(input: &str) -> IResult<&str, Expression> {
let (input, lhs) = parse_and(input)?;
let (input, ors) = many0(preceded(tag("OR"), parse_and))(input)?;
let expr = ors.into_iter().fold(lhs, |acc, branch| Expression::Or(Box::new(acc), Box::new(branch)));
Ok((input, expr))
}
fn parse_and(input: &str) -> IResult<&str, Expression> {
let (input, lhs) = parse_not(input)?;
let (input, ors) = many0(preceded(tag("AND"), parse_and))(input)?;
let expr = ors.into_iter().fold(lhs, |acc, branch| Expression::And(Box::new(acc), Box::new(branch)));
Ok((input, expr))
}
fn parse_not(input: &str) -> IResult<&str, Expression> {
alt((
map(preceded(ws(char('!')), parse_condition_expression), |e| Expression::Not(Box::new(e))),
parse_condition_expression,
))(input)
}
fn parse_condition_expression(input: &str) -> IResult<&str, Expression> {
alt((
delimited(ws(char('(')), parse_expression, ws(char(')'))),
map(parse_condition, Expression::Condition)
))(input)
}
fn parse_expression(input: &str) -> IResult<&str, Expression> {
parse_or(input)
}
fn main() {
let (input, parsed) = parse_expression(" !\"hello-mama\" 10 TO blabla OR hello = 19 AND lala = 12").unwrap();
println!("parsed: {:?}", parsed);
println!("input: {}", input);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment