extern crate nom;
use nom::{
branch::alt,
bytes::complete::{is_a, tag},
character::complete::{char, digit0, digit1, space0},
combinator::opt,
multi::fold_many1,
sequence::{delimited, preceded, separated_pair},
IResult, Parser,
};
use super::generators::{
ArithOp, ArithTermGenerator, ComparisonOp, ExprGenerator, Generator, HitsGenerator,
MulDivGenerator, MulDivOp, PoolGenerator, PoolOp, SuccGenerator, SuccessOp, TargetOp,
TermGenerator,
};
pub fn generator_parser(input: &str) -> IResult<&str, Generator> {
match (mul_div_parser, opt(comparison_op_parser)).parse(input) {
Ok((input, (mul_div, op))) => Ok((input, Generator { mul_div, op })),
Err(e) => Err(e),
}
}
pub fn mul_div_parser(input: &str) -> IResult<&str, MulDivGenerator> {
match (succ_gen_parser, opt(mul_div_op_parser)).parse(input) {
Ok((input, (succ, op))) => Ok((input, MulDivGenerator { succ, op })),
Err(e) => Err(e),
}
}
pub fn succ_gen_parser(input: &str) -> IResult<&str, SuccGenerator> {
match (hits_parser, opt(alt((succ_op_parser, succ_next_op_parser)))).parse(input) {
Ok((input, (hits, op))) => Ok((input, SuccGenerator { hits, op })),
Err(e) => Err(e),
}
}
pub fn hits_parser(input: &str) -> IResult<&str, HitsGenerator> {
match (pare_parser, opt(tgt_op_parser)).parse(input) {
Ok((input, (expr, op))) => Ok((input, HitsGenerator { expr, op })),
Err(e) => Err(e),
}
}
fn pare_parser(input: &str) -> IResult<&str, ExprGenerator> {
alt((
delimited(
(space0, char('('), space0),
expr_parser,
(space0, char(')'), space0),
),
expr_parser,
))
.parse(input)
}
pub fn expr_parser(input: &str) -> IResult<&str, ExprGenerator> {
let result = fold_many1(
arith_term_parser,
Vec::new,
|mut acc: Vec<_>, arith_term| {
acc.push(arith_term);
acc
},
)
.parse(input);
match result {
Ok((input, terms)) => Ok((input, ExprGenerator { terms })),
Err(e) => Err(e),
}
}
fn implicit_term_parser(input: &str) -> IResult<&str, ArithTermGenerator> {
match preceded(space0, term_parser).parse(input) {
Ok((input, term)) => Ok((
input,
ArithTermGenerator {
op: ArithOp::ImplicitAdd,
term,
},
)),
Err(e) => Err(e),
}
}
fn add_term_parser(input: &str) -> IResult<&str, ArithTermGenerator> {
match preceded(delimited(space0, char('+'), space0), term_parser).parse(input) {
Ok((input, term)) => Ok((
input,
ArithTermGenerator {
op: ArithOp::Add,
term,
},
)),
Err(e) => Err(e),
}
}
fn sub_term_parser(input: &str) -> IResult<&str, ArithTermGenerator> {
match preceded(delimited(space0, char('-'), space0), term_parser).parse(input) {
Ok((input, term)) => Ok((
input,
ArithTermGenerator {
op: ArithOp::Sub,
term,
},
)),
Err(e) => Err(e),
}
}
fn arith_term_parser(input: &str) -> IResult<&str, ArithTermGenerator> {
alt((implicit_term_parser, add_term_parser, sub_term_parser)).parse(input)
}
pub fn term_parser(input: &str) -> IResult<&str, TermGenerator> {
alt((pool_parser, const_parser)).parse(input)
}
fn const_parser(input: &str) -> IResult<&str, TermGenerator> {
match preceded(space0, digit1).parse(input) {
Ok((input, chars)) => Ok((
input,
TermGenerator::Constant(chars.parse::<i32>().unwrap()),
)),
Err(e) => Err(e),
}
}
fn pool_parser(input: &str) -> IResult<&str, TermGenerator> {
match (opt(digit1), is_a("dD"), range_parser, opt(pool_op_parser)).parse(input) {
Ok((input, (count, _, range, op))) => {
let count = match count {
Some(chars) => {
let n = chars.parse::<i32>().unwrap();
if n > 100 {
100
} else {
n
}
}
None => 1,
};
Ok((
input,
TermGenerator::Pool(PoolGenerator { count, range, op }),
))
}
Err(e) => Err(e),
}
}
pub fn range_parser(input: &str) -> IResult<&str, i32> {
match alt((digit1, is_a("%"))).parse(input) {
Ok((input, chars)) => {
if chars.starts_with('%') {
let base = 10i32;
let exp = chars.len() as u32;
let n = match base.checked_pow(exp) {
Some(n) => clamp(10 * n, 100, 10000),
None => 100,
};
Ok((input, n))
} else {
Ok((input, clamp(chars.parse::<i32>().unwrap(), 1, 2000)))
}
}
Err(e) => Err(e),
}
}
fn tgt_high_parser(input: &str) -> IResult<&str, TargetOp> {
match delimited((space0, char('['), space0), digit1, (space0, char(']'))).parse(input) {
Ok((input, chars)) => Ok((
input,
TargetOp::TargetHigh(clamp(chars.parse::<i32>().unwrap(), 1, 1000)),
)),
Err(e) => Err(e),
}
}
fn tgt_low_parser(input: &str) -> IResult<&str, TargetOp> {
match delimited((space0, char('('), space0), digit1, (space0, char(')'))).parse(input) {
Ok((input, chars)) => Ok((
input,
TargetOp::TargetLow(clamp(chars.parse::<i32>().unwrap(), 1, 1000)),
)),
Err(e) => Err(e),
}
}
pub fn tgt_op_parser(input: &str) -> IResult<&str, TargetOp> {
alt((tgt_high_parser, tgt_low_parser)).parse(input)
}
pub fn succ_op_parser(input: &str) -> IResult<&str, SuccessOp> {
match delimited((space0, char('{'), space0), digit1, (space0, char('}'))).parse(input) {
Ok((input, chars)) => Ok((
input,
SuccessOp::TargetSucc(clamp(chars.parse::<i32>().unwrap(), 1, 1000)),
)),
Err(e) => Err(e),
}
}
pub fn succ_next_op_parser(input: &str) -> IResult<&str, SuccessOp> {
match delimited(
(char('{'), space0),
separated_pair(digit1, (space0, char(','), space0), digit1),
(space0, char('}')),
)
.parse(input)
{
Ok((input, (n, m))) => Ok((
input,
SuccessOp::TargetSuccNext(
clamp(n.parse::<i32>().unwrap(), 1, 1000),
clamp(m.parse::<i32>().unwrap(), 1, 1000),
),
)),
Err(e) => Err(e),
}
}
pub fn pool_op_parser(input: &str) -> IResult<&str, PoolOp> {
alt((
explode_until_op_parser,
explode_op_parser,
explode_each_until_op_parser,
explode_each_op_parser,
add_op_parser,
sub_op_parser,
take_mid_op_parser,
take_high_op_parser,
take_low_op_parser,
command_op_parser,
))
.parse(input)
}
pub fn optional_num_parser(input: &str) -> IResult<&str, Option<i32>> {
match (space0, digit0).parse(input) {
Ok((input, (_, chars))) => {
if !chars.is_empty() {
Ok((input, Some(clamp(chars.parse::<i32>().unwrap(), 0, 1000))))
} else {
Ok((input, None))
}
}
Err(e) => Err(e),
}
}
fn explode_op_parser(input: &str) -> IResult<&str, PoolOp> {
match (tag("!"), optional_num_parser).parse(input) {
Ok((input, (_, num))) => Ok((input, PoolOp::Explode(num))),
Err(e) => Err(e),
}
}
fn explode_until_op_parser(input: &str) -> IResult<&str, PoolOp> {
match (tag("!!"), optional_num_parser).parse(input) {
Ok((input, (_, num))) => Ok((input, PoolOp::ExplodeUntil(num))),
Err(e) => Err(e),
}
}
fn explode_each_op_parser(input: &str) -> IResult<&str, PoolOp> {
match (tag("*"), optional_num_parser).parse(input) {
Ok((input, (_, num))) => Ok((input, PoolOp::ExplodeEach(num))),
Err(e) => Err(e),
}
}
fn explode_each_until_op_parser(input: &str) -> IResult<&str, PoolOp> {
match (tag("**"), optional_num_parser).parse(input) {
Ok((input, (_, num))) => Ok((input, PoolOp::ExplodeEachUntil(num))),
Err(e) => Err(e),
}
}
fn add_op_parser(input: &str) -> IResult<&str, PoolOp> {
match (space0, tag("++"), space0, optional_num_parser).parse(input) {
Ok((input, (_, _, _, num))) => Ok((input, PoolOp::AddEach(num))),
Err(e) => Err(e),
}
}
fn sub_op_parser(input: &str) -> IResult<&str, PoolOp> {
match (space0, tag("--"), space0, optional_num_parser).parse(input) {
Ok((input, (_, _, _, num))) => Ok((input, PoolOp::SubEach(num))),
Err(e) => Err(e),
}
}
fn take_mid_op_parser(input: &str) -> IResult<&str, PoolOp> {
match (char('~'), digit1).parse(input) {
Ok((input, (_, chars))) => Ok((input, PoolOp::TakeMid(chars.parse::<i32>().unwrap()))),
Err(e) => Err(e),
}
}
fn take_high_op_parser(input: &str) -> IResult<&str, PoolOp> {
match (char('^'), digit1).parse(input) {
Ok((input, (_, chars))) => Ok((input, PoolOp::TakeHigh(chars.parse::<i32>().unwrap()))),
Err(e) => Err(e),
}
}
fn take_low_op_parser(input: &str) -> IResult<&str, PoolOp> {
match (char('`'), digit1).parse(input) {
Ok((input, (_, chars))) => Ok((input, PoolOp::TakeLow(chars.parse::<i32>().unwrap()))),
Err(e) => Err(e),
}
}
fn command_op_parser(input: &str) -> IResult<&str, PoolOp> {
match delimited(space0, alt((tag("ADV"), tag("DIS"), tag("Y"))), space0).parse(input) {
Ok((input, op)) => match op {
"ADV" => Ok((input, PoolOp::Advantage)),
"DIS" => Ok((input, PoolOp::Disadvantage)),
"Y" => Ok((input, PoolOp::BestGroup)),
_ => panic!("unexpected tag in reroll op parser"),
},
Err(e) => Err(e),
}
}
fn comparison_op_parser(input: &str) -> IResult<&str, ComparisonOp> {
match alt((
(delimited(space0, tag("<=>"), space0), succ_gen_parser),
(delimited(space0, tag(">="), space0), succ_gen_parser),
(delimited(space0, tag("<="), space0), succ_gen_parser),
(delimited(space0, tag(">"), space0), succ_gen_parser),
(delimited(space0, tag("<"), space0), succ_gen_parser),
(delimited(space0, tag("="), space0), succ_gen_parser),
))
.parse(input)
{
Ok((input, (tag, succ))) => match tag {
"<=>" => Ok((input, ComparisonOp::CMP(succ))),
">=" => Ok((input, ComparisonOp::GE(succ))),
"<=" => Ok((input, ComparisonOp::LE(succ))),
">" => Ok((input, ComparisonOp::GT(succ))),
"<" => Ok((input, ComparisonOp::LT(succ))),
"=" => Ok((input, ComparisonOp::EQ(succ))),
_ => panic!("unexpected tag"),
},
Err(e) => Err(e),
}
}
fn mul_div_op_parser(input: &str) -> IResult<&str, MulDivOp> {
match alt((
(space0, tag("x"), space0, digit1),
(space0, tag("/"), space0, digit1),
))
.parse(input)
{
Ok((input, (_, op, _, num))) => match op {
"x" => Ok((
input,
MulDivOp::Mul(clamp(num.parse::<i32>().unwrap(), 0, 100)),
)),
"/" => Ok((
input,
MulDivOp::Div(clamp(num.parse::<i32>().unwrap(), 1, 100)),
)),
_ => panic!("unexpected op"),
},
Err(e) => Err(e),
}
}
fn clamp(n: i32, min: i32, max: i32) -> i32 {
if n < min {
min
} else if n > max {
max
} else {
n
}
}