tml_parser 1.0.6

The official parser for the TML language
Documentation
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()
    }
}