use crate::lexer;
use std::result::Result;
use std::fmt;
use std::boxed::Box;
use serde::{Deserialize, Serialize};
use serde::export::Formatter;
use std::error::Error;
pub fn with_default_config() -> Config {
Config{
default_unit: "count".parse().unwrap(),
valid_units: {
let mut v: Vec<String> = Vec::new();
v.push("kg".parse().unwrap());
v.push("lbs".parse().unwrap());
v.push("s".parse().unwrap());
v.push("min".parse().unwrap());
v.push("count".parse().unwrap());
v
},
rpe_range: (0f64, 11f64),
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Row {
pub sets: i64,
pub reps: i64,
pub rpe: Option<f64>,
pub weight: f64,
pub unit: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Exercise {
pub name: String,
pub rows: Vec<Row>,
pub comment: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Workout {
pub comment: Option<String>,
pub exercises: Vec<Exercise>,
}
#[derive(Debug, Clone)]
pub struct Config {
pub default_unit: String,
pub valid_units: Vec<String>,
pub rpe_range: (f64, f64),
}
pub trait ParserTrait {
fn parse(&mut self) -> Result<Workout, ParsingError>;
}
pub struct Parser {
l: Box<dyn lexer::LexerTrait>,
config: Config,
}
pub struct ParsingError {
msg: String,
tok: lexer::Token,
}
macro_rules! format_err {
($msg:literal) => {new_error($msg.parse().unwrap(), lexer::Token{token_type: lexer::Keyword::EOF, token_literal: "".parse().unwrap(), position: (0, 0)})};
($msg:literal, $tok:ident) => {new_error($msg.parse().unwrap(), $tok)};
($msg:literal, $tok:literal) => {new_error($msg.parse().unwrap(), $tok)};
}
fn new_error(msg: String, tok: lexer::Token) -> ParsingError {
ParsingError{msg, tok}
}
impl fmt::Debug for ParsingError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{} at position {}-{}: {}", self.msg, self.tok.position.0, self.tok.position.1, self.tok.token_literal)
}
}
impl fmt::Display for ParsingError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{} at position {}-{}: {}", self.msg, self.tok.position.0, self.tok.position.1, self.tok.token_literal)
}
}
impl Error for ParsingError {}
impl ParserTrait for Parser {
fn parse(&mut self) -> Result<Workout, ParsingError> {
let mut w = Workout{
comment: None,
exercises: Vec::new(),
};
loop {
let tok = self.l.next_token();
if tok.token_type == lexer::Keyword::EOF {
break
}
match tok.token_type {
lexer::Keyword::DELIMITER => continue,
lexer::Keyword::TEXT => w.comment = Some(tok.token_literal),
lexer::Keyword::HASH => {
let e: Exercise = self.parse_exercise()?;
w.exercises.push(e);
}
_ => return Err(format_err!("invalid token", tok)),
}
}
Ok(w)
}
}
impl Parser {
pub fn new(l: Box<dyn lexer::LexerTrait>, config: Config) -> impl ParserTrait {
Parser{l, config}
}
fn is_valid_unit(&self, u: &str) -> bool {
for unit in &self.config.valid_units {
if unit.as_str() == u {
return true;
}
}
return false;
}
fn is_valid_rpe(&self, rpe: f64) -> bool {
return rpe >= self.config.rpe_range.0 && rpe <= self.config.rpe_range.1;
}
fn parse_row(&mut self) -> Result<Row, ParsingError> {
let mut row: Row = Row {
sets: 1,
reps: 0,
rpe: None,
weight: 0.0,
unit: "".to_string()
};
if !self.is_valid_unit(self.config.default_unit.as_str()) {
return Err(format_err!("invalid unit was provided"));
}
row.unit = self.config.default_unit.clone();
let tok = self.l.next_token();
if tok.token_type != lexer::Keyword::NUMBER {
return Err(format_err!("token must be a NUMBER", tok));
}
row.weight = tok.token_literal.parse().unwrap();
let mut tok = self.l.next_token();
if tok.token_type == lexer::Keyword::LABEL {
if !self.is_valid_unit(tok.token_literal.as_str()) {
return Err(format_err!("invalid unit was provided"));
}
row.unit = tok.token_literal;
tok = self.l.next_token();
}
if tok.token_type == lexer::Keyword::CROSS {
tok = self.l.next_token();
} else {
return Err(format_err!("invalid token", tok));
}
if tok.token_type != lexer::Keyword::NUMBER {
return Err(format_err!("invalid token", tok));
}
row.reps = tok.token_literal.parse().unwrap();
tok = self.l.next_token();
match tok.token_type {
lexer::Keyword::CROSS => {
tok = self.l.next_token();
if tok.token_type != lexer::Keyword::NUMBER {
return Err(format_err!("invalid token", tok));
}
row.sets = tok.token_literal.parse().unwrap();
tok = self.l.next_token();
},
lexer::Keyword::AT => {
tok = self.l.next_token();
if tok.token_type != lexer::Keyword::NUMBER {
return Err(format_err!("invalid token"));
}
let rpe: f64 = tok.token_literal.parse().unwrap();
if !self.is_valid_rpe(rpe) {
return Err(format_err!("invalid rpe value was provided", tok));
}
row.rpe = Some(rpe);
tok = self.l.next_token();
}
_ => {}
}
if tok.token_type == lexer::Keyword::AT {
tok = self.l.next_token();
if tok.token_type != lexer::Keyword::NUMBER {
return Err(format_err!("invalid token"));
}
let rpe: f64 = tok.token_literal.parse().unwrap();
if !self.is_valid_rpe(rpe) {
return Err(format_err!("invalid rpe value was provided", tok));
}
row.rpe = Some(rpe);
tok = self.l.next_token();
}
if tok.token_type != lexer::Keyword::DELIMITER && tok.token_type != lexer::Keyword::EOF {
return Err(format_err!("invalid token", tok));
}
if row.unit.as_str() == "count" {
row.sets = row.reps.clone();
row.reps = row.weight as i64;
row.weight = 1f64;
}
Ok(row)
}
fn parse_rows(&mut self) -> Result<Vec<Row>, ParsingError> {
let mut rows = Vec::new();
loop {
while self.l.peek_token().token_type == lexer::Keyword::DELIMITER {
self.l.next_token();
}
match self.l.peek_token().token_type {
lexer::Keyword::TEXT |
lexer::Keyword::HASH |
lexer::Keyword::EOF => return Ok(rows),
_ => {},
}
match self.parse_row() {
Ok(row) => rows.push(row),
Err(e) => return Err(e),
}
}
}
fn parse_exercise(&mut self) -> Result<Exercise, ParsingError> {
let mut e = Exercise{
name: "".to_string(),
rows: Vec::new(),
comment: None,
};
let name = self.l.next_token();
if name.token_type != lexer::Keyword::IDENT {
return Err(format_err!("token must be an IDENT", name));
}
e.name = name.token_literal.clone();
self.l.next_token();
let rows: Vec<Row> = self.parse_rows()?;
e.rows = rows;
let mut tok = self.l.next_token();
while tok.token_type == lexer::Keyword::DELIMITER {
tok = self.l.next_token();
}
if tok.token_type == lexer::Keyword::TEXT {
e.comment = Some(tok.token_literal);
}
Ok(e)
}
}
impl std::str::FromStr for Workout {
type Err = ParsingError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let l = lexer::Lexer::new(s);
let mut p = Parser::new(Box::new(l), with_default_config());
p.parse()
}
}