use crate::tree::Tree;
use ::core::convert::TryFrom;
use ::core_extensions::SliceExt;
use ::id_arena::{Arena, Id};
use ::mbot_proc_macro_helpers::decl_ops;
decl_ops! {
#[non_exhaustive]
#[derive(Debug, Clone, Copy)]
pub enum
Op =
UnaryOp
BinOp {
Plus { unary: 4, binary: (3, 4) },
Minus { unary: 4, binary: (3, 4) },
}
unary =>
fn unary_binding_power(op) -> u8;
binary =>
fn infix_binding_power(op) -> (u8, u8);
}
#[derive(Debug)]
pub enum Token {
Int(i64),
D,
K,
Kl,
Exclaim,
Op(Op),
Whitespace,
}
#[derive(Debug)]
struct TooLarge;
fn lex(input: &[u8]) -> (&[u8], Result<Vec<Token>, TooLarge>) {
enum State<'a> {
Normal,
Int(&'a [u8]),
Whitespace,
}
let mut tokens = Vec::with_capacity(input.len());
let mut state = State::Normal;
let mut cursor = input;
loop {
match state {
State::Normal => match cursor {
[b'0'..=b'9', rest @ ..] => {
state = State::Int(cursor);
cursor = rest;
}
[b'd', rest @ ..] => {
tokens.push(Token::D);
cursor = rest;
}
[b'k', b'l', rest @ ..] => {
tokens.push(Token::Kl);
cursor = rest;
}
[b'k', b'h', rest @ ..] | [b'k', rest @ ..] => {
tokens.push(Token::K);
cursor = rest;
}
[b'!', rest @ ..] => {
tokens.push(Token::Exclaim);
cursor = rest;
}
[b'+', rest @ ..] => {
tokens.push(Token::Op(Op::Plus));
cursor = rest;
}
[b'-', rest @ ..] => {
tokens.push(Token::Op(Op::Minus));
cursor = rest;
}
[b'\t' | b' ', rest @ ..] => {
tokens.push(Token::Whitespace);
state = State::Whitespace;
cursor = rest;
}
[_, _rest @ ..] => break,
[] => break,
},
State::Int(start) => match cursor {
[b'0'..=b'9', rest @ ..] => cursor = rest,
[..] => {
let slice = &start[..start.offset_of_slice(cursor)];
tokens.push(Token::Int(
match slice.iter().try_fold(0i64, |a, b| {
a.checked_mul(10)?.checked_add((b - b'0') as i64)
}) {
Some(x) => x,
None => return (cursor, Err(TooLarge)),
},
));
state = State::Normal;
}
},
State::Whitespace => match cursor {
[b'\t' | b' ', rest @ ..] => cursor = rest,
[..] => state = State::Normal,
},
}
}
(cursor, Ok(tokens))
}
#[non_exhaustive]
#[derive(Clone, Debug, ::derive_more::Unwrap, PartialEq)]
pub enum Term {
Constant(i64),
DiceRoll(i64, i64),
KeepHigh(Id<Term>, i64),
KeepLow(Id<Term>, i64),
Explode(Id<Term>),
Add(Id<Term>, Id<Term>),
Subtract(Id<Term>, Id<Term>),
UnarySubtract(Id<Term>),
UnaryAdd(Id<Term>),
}
#[derive(Debug)]
pub struct Program {
pub(crate) tree: Tree<Term>,
}
impl ::core::ops::Deref for Program {
type Target = Tree<Term>;
fn deref(&self) -> &Self::Target {
&self.tree
}
}
fn write_sexpr(terms: &Arena<Term>, top: Id<Term>, buf: &mut String) {
let mut write_op = |op: &str, lhs, rhs| {
buf.push('(');
buf.push_str(op);
buf.push(' ');
write_sexpr(terms, lhs, &mut *buf);
buf.push(' ');
write_sexpr(terms, rhs, &mut *buf);
buf.push(')');
};
match terms[top] {
Term::Constant(n) => {
let mut itoa_buf = itoa::Buffer::new();
buf.push_str(itoa_buf.format(n));
}
Term::DiceRoll(count, faces) => {
let mut itoa_buf = itoa::Buffer::new();
buf.push_str(itoa_buf.format(count));
buf.push('d');
let mut itoa_buf = itoa::Buffer::new();
buf.push_str(itoa_buf.format(faces));
}
Term::KeepHigh(roll, count) => {
buf.push('(');
buf.push('k');
buf.push(' ');
write_sexpr(terms, roll, &mut *buf);
buf.push(' ');
let mut itoa_buf = itoa::Buffer::new();
buf.push_str(itoa_buf.format(count));
buf.push(')');
}
Term::KeepLow(roll, count) => {
buf.push('(');
buf.push_str("kl");
buf.push(' ');
write_sexpr(terms, roll, &mut *buf);
buf.push(' ');
let mut itoa_buf = itoa::Buffer::new();
buf.push_str(itoa_buf.format(count));
buf.push(')');
}
Term::Explode(roll) => {
buf.push('(');
buf.push('!');
buf.push(' ');
write_sexpr(terms, roll, &mut *buf);
buf.push(')');
}
Term::Add(lhs, rhs) => write_op("+", lhs, rhs),
Term::Subtract(lhs, rhs) => write_op("-", lhs, rhs),
Term::UnaryAdd(arg) => {
buf.push('+');
write_sexpr(terms, arg, &mut *buf);
}
Term::UnarySubtract(arg) => {
buf.push('-');
write_sexpr(terms, arg, &mut *buf);
}
}
}
impl Program {
pub fn fmt_sexpr(&self) -> String {
let mut buf = String::new();
write_sexpr(&self.arena, self.top, &mut buf);
buf
}
pub fn terms(&self) -> &Arena<Term> {
&self.arena
}
pub fn is_single(&self) -> bool {
let mut count = 0;
crate::tree::for_! { (term, _) in self.postorder() => {
match term {
Term::DiceRoll(dice_count, _sides) => count += dice_count,
_ => count += 1,
}
}}
count == 1
}
}
type ParseResult<I, O, E> = Result<(I, O), (I, E)>;
struct InvalidTokenInExpr;
struct UnexpectedEof;
#[derive(Debug)]
pub enum ExprError {
TooLarge,
InvalidTokenInExpr,
InvalidDie,
Eof,
InvalidBinOp,
InvalidTokenInBinOp,
InvalidTokenInUnaryOp,
UnsupportedFeature,
}
impl From<InvalidTokenInExpr> for ExprError {
fn from(InvalidTokenInExpr: InvalidTokenInExpr) -> Self {
Self::InvalidTokenInExpr
}
}
impl From<UnexpectedEof> for ExprError {
fn from(UnexpectedEof: UnexpectedEof) -> Self {
Self::Eof
}
}
use crate::backend_support::TyTarget::Target;
pub fn parse_expression<T: Target>(
input: &[u8],
) -> ParseResult<&[u8], (Vec<Token>, Program), ExprError> {
let mut arena = Arena::<Term>::new();
let (rest, tokens) = lex(input);
let tokens = match tokens {
Ok(x) => x,
Err(TooLarge) => return Err((rest, ExprError::TooLarge)),
};
fn ignore_whitespace(input: &[Token]) -> &[Token] {
let mut cursor = input;
while let [Token::Whitespace, rest @ ..] = cursor {
cursor = rest;
}
cursor
}
fn consume_expr<'a>(
terms: &mut Arena<Term>,
min_bp: u8,
input: &'a [Token],
) -> Result<(&'a [Token], Id<Term>), ExprError> {
macro_rules! check_faces {
($faces:expr) => {
if $faces <= 0 {
return Err(ExprError::InvalidDie);
}
};
}
let (mut cursor, mut lhs) = match ignore_whitespace(input) {
[Token::Int(count), Token::D, Token::Int(faces), Token::Exclaim, rest @ ..] => {
check_faces!(*faces);
let roll = Term::DiceRoll(*count, *faces);
let explode = Term::Explode(terms.alloc(roll));
(rest, explode)
}
[Token::D, Token::Int(faces), Token::Exclaim, rest @ ..] => {
check_faces!(*faces);
let roll = Term::DiceRoll(1, *faces);
let explode = Term::Explode(terms.alloc(roll));
(rest, explode)
}
[Token::Int(count), Token::D, Token::Int(faces), keep @ (Token::K | Token::Kl), Token::Int(keep_count), rest @ ..] =>
{
check_faces!(*faces);
let roll = Term::DiceRoll(*count, *faces);
let keep_by = match keep {
Token::K => Term::KeepHigh(terms.alloc(roll), *keep_count),
Token::Kl => Term::KeepLow(terms.alloc(roll), *keep_count),
_ => unreachable!(),
};
(rest, keep_by)
}
[Token::D, Token::Int(faces), keep @ (Token::K | Token::Kl), Token::Int(keep_count), rest @ ..] =>
{
check_faces!(*faces);
let roll = Term::DiceRoll(1, *faces);
let keep_high = match keep {
Token::K => Term::KeepHigh(terms.alloc(roll), *keep_count),
Token::Kl => Term::KeepLow(terms.alloc(roll), *keep_count),
_ => unreachable!(),
};
(rest, keep_high)
}
[Token::Int(count), Token::D, Token::Int(faces), rest @ ..] => {
check_faces!(*faces);
(rest, Term::DiceRoll(*count, *faces))
}
[Token::D, Token::Int(faces), rest @ ..] => {
check_faces!(*faces);
(rest, Term::DiceRoll(1, *faces))
}
[Token::Int(n), rest @ ..] => (rest, Term::Constant(*n)),
[Token::Op(op), rest @ ..] => {
if terms.len() > 0 || min_bp != 2 {
Err(InvalidTokenInExpr)?
}
let op = UnaryOp::try_from(*op).map_err(|()| ExprError::InvalidTokenInUnaryOp)?;
let r_bp = unary_binding_power(op);
let (rest, expr) = consume_expr(&mut *terms, r_bp, rest)?;
let term = match op {
UnaryOp::Plus => Term::UnaryAdd(expr),
UnaryOp::Minus => Term::UnarySubtract(expr),
};
(rest, term)
}
[_x, ..] => Err(InvalidTokenInExpr)?,
[] => Err(UnexpectedEof)?,
};
loop {
let (rest, op) = match cursor {
[Token::Op(op), rest @ ..] => (
rest,
BinOp::try_from(*op).map_err(|()| ExprError::InvalidBinOp)?,
),
[Token::Whitespace, rest @ ..] => {
cursor = rest;
continue;
}
[_x, ..] => Err(ExprError::InvalidTokenInBinOp)?,
[] => break,
};
let (l_bp, r_bp) = infix_binding_power(op);
if l_bp < min_bp {
break;
}
cursor = rest;
let (rest, rhs) = consume_expr(&mut *terms, r_bp, cursor)?;
cursor = rest;
match op {
BinOp::Plus => lhs = Term::Add(terms.alloc(lhs), rhs),
BinOp::Minus => lhs = Term::Subtract(terms.alloc(lhs), rhs),
}
}
Ok((cursor, terms.alloc(lhs)))
}
let mut cursor = &tokens[..];
let result = loop {
match cursor {
[Token::Whitespace, rest @ ..] => cursor = rest,
[Token::K | Token::Kl | Token::Exclaim, ..] => {
break Err(ExprError::InvalidTokenInUnaryOp)
}
all @ [Token::Int(_) | Token::D | Token::Op(_), ..] => {
break consume_expr(&mut arena, 2, all).map(|(_, x)| x)
}
[] => break Err(ExprError::Eof),
};
};
match result {
Ok(top) => {
use crate::backend_support::Features;
let program = Program {
tree: Tree { arena, top },
};
let features = Features::of(&program);
if T::reify().supports(features) {
Ok((rest, (tokens, program)))
} else {
Err((rest, ExprError::UnsupportedFeature))
}
}
Err(e) => Err((rest, e)),
}
}