wp-lang 0.2.2

WPL language crate with AST, parser, evaluator, builtins, and generators.
Documentation
use super::super::prelude::*;
use crate::ast::group::WplGroupType;
use crate::generator::{FieldGenConf, GenScopeEnum};
use crate::generator::{GenChannel, ParserValue};
use crate::parser::error::WplCodeResult;
use rand::RngExt;
use winnow::ascii::{dec_int, float, multispace0};
use winnow::combinator::preceded;
use wp_model_core::model::DataField;
use wp_model_core::model::DataType;
use wp_model_core::model::FNameStr;
use wp_model_core::model::{DigitValue, FloatValue};
use wp_primitives::Parser;
use wp_primitives::WResult as ModalResult;
use wp_primitives::symbol::ctx_desc;

use crate::eval::runtime::field::FieldEvalUnit;
use crate::eval::value::parse_def::PatternParser;
#[derive(Default)]
pub struct DigitP {}
#[derive(Default)]
pub struct FloatP {}

impl ParserValue<DigitValue> for DigitP {
    fn parse_value<'a>(data: &mut &str) -> ModalResult<DigitValue> {
        preceded(multispace0, dec_int).parse_next(data)
    }
}

impl PatternParser for DigitP {
    fn pattern_parse<'a>(
        &self,
        _e_id: u64,
        fpu: &FieldEvalUnit,
        _ups_sep: &WplSep,
        data: &mut &str,
        name: FNameStr,
        out: &mut Vec<DataField>,
    ) -> ModalResult<()> {
        match fpu.group_enum() {
            WplGroupType::Seq(_) => {
                let obj = Self::parse_value
                    .context(ctx_desc("<digit>"))
                    .parse_next(data)?;
                out.push(DataField::new_opt(DataType::Digit, Some(name), obj.into()));
            }
            _ => {
                let obj = Self::parse_value
                    .context(ctx_desc("<digit>"))
                    .parse_next(data)?;
                out.push(DataField::new_opt(DataType::Digit, Some(name), obj.into()));
            }
        }
        Ok(())
    }

    fn patten_gen(
        &self,
        gnc: &mut GenChannel,
        f_conf: &WplField,
        g_conf: Option<&FieldGenConf>,
    ) -> WplCodeResult<DataField> {
        let range = if let Some(Some(GenScopeEnum::Digit(digit))) = g_conf.map(|c| &c.scope) {
            let beg: i64 = digit.beg;
            let end: i64 = digit.end;
            beg..end
        } else {
            0..2000
        };
        let dat = gnc.rng.random_range(range);
        Ok(DataField::from_digit(f_conf.safe_name(), dat))
    }
}

impl ParserValue<FloatValue> for FloatP {
    fn parse_value<'a>(data: &mut &str) -> ModalResult<FloatValue> {
        preceded(multispace0, float).parse_next(data)
    }
}

impl PatternParser for FloatP {
    fn pattern_parse<'a>(
        &self,
        _e_id: u64,
        _fpu: &FieldEvalUnit,
        _ups_sep: &WplSep,
        data: &mut &str,
        name: FNameStr,
        out: &mut Vec<DataField>,
    ) -> ModalResult<()> {
        let obj = Self::parse_value.parse_next(data)?;
        out.push(DataField::new_opt(DataType::Float, Some(name), obj.into()));
        Ok(())
    }

    fn patten_gen(
        &self,
        gnc: &mut GenChannel,
        f_conf: &WplField,
        _g_conf: Option<&FieldGenConf>,
    ) -> WplCodeResult<DataField> {
        let fst = gnc.rng.random_range(0..2000);
        let sec = gnc.rng.random_range(100..999);

        Ok(DataField::from_float(
            f_conf.safe_name(),
            (fst + sec / 1000) as f64,
        ))
    }
}

#[cfg(test)]
mod tests {
    use crate::ast::WplField;
    use crate::eval::runtime::vm_unit::WplEvaluator;
    use crate::eval::value::parser::protocol::json::JsonP;
    use crate::eval::value::test_utils::ParserTUnit;
    use crate::parser::error::WplCodeResult;
    use crate::parser::parse_code::wpl_express;
    use orion_error::testcase::TestAssert;
    use wp_model_core::model::{DataRecord, Value};
    use wp_model_core::raw::RawData;

    use super::*;
    #[allow(clippy::approx_constant)]
    const PI: f64 = 3.14;

    use crate::parser::error::IntoWplCodeError;
    #[test]
    fn test_digit() -> WplCodeResult<()> {
        let mut data = "-99";
        let conf = WplField::try_parse("digit").assert();
        let field = ParserTUnit::new(DigitP::default(), conf)
            .verify_parse_suc_end(&mut data)
            .assert();
        assert_eq!(field[0], DataField::from_digit("digit", -99));

        let mut data = r#"{"num":-99}"#;
        let conf = WplField::try_parse("json(digit@num)").assert();
        let field = ParserTUnit::new(JsonP::default(), conf)
            .verify_parse_suc(&mut data)
            .assert();
        assert_eq!(field[0], DataField::from_digit("num", -99));

        let mut data = "[3.14]";
        let conf = WplField::try_parse("float<[,]>").assert();
        let field = ParserTUnit::new(FloatP::default(), conf)
            .verify_parse_suc_end(&mut data)
            .assert();
        assert_eq!(field[0], DataField::from_float("float", PI));

        let mut data = "3.14,";
        let conf = WplField::try_parse(r#"float\,"#).assert();
        let _ = ParserTUnit::new(FloatP::default(), conf)
            .verify_parse_suc_end(&mut data)
            .assert();
        //assert_eq!(field, "3.14");
        let mut data = "<188>May";
        let conf = WplField::try_parse(r#"digit<<,>>"#).assert();
        let field = ParserTUnit::new(DigitP::default(), conf)
            .verify_parse_suc(&mut data)
            .assert();
        match field[0].get_value() {
            Value::Digit(digit) => {
                assert_eq!(*digit, 188);
            }
            _ => panic!("not digit"),
        }
        Ok(())
    }

    #[test]
    fn test_float() -> WplCodeResult<()> {
        let mut data = "[ 3.14]";
        let conf = WplField::try_parse("float<[,]>").assert();
        let field = ParserTUnit::new(FloatP::default(), conf)
            .verify_parse_suc_end(&mut data)
            .assert();
        assert_eq!(field[0], DataField::from_float("float", PI));

        let mut data = "  3.14,";
        let conf = WplField::try_parse(r#"float\,"#).assert();
        let field = ParserTUnit::new(FloatP::default(), conf)
            .verify_parse_suc_end(&mut data)
            .assert();
        assert_eq!(field[0], DataField::from_float("float", PI));
        Ok(())
    }

    #[test]
    fn test_digit_gen() -> WplCodeResult<()> {
        let conf = WplField::try_parse("digit<[,]>").assert();
        ParserTUnit::new(DigitP::default(), conf).verify_gen_parse_suc();

        let conf = WplField::try_parse("digit").assert();
        ParserTUnit::new(DigitP::default(), conf).verify_gen_parse_suc();

        let conf = WplField::try_parse("digit\\,").assert();
        ParserTUnit::new(DigitP::default(), conf).verify_gen_parse_suc();
        Ok(())
    }

    #[test]
    fn test_digit_empty() -> WplCodeResult<()> {
        let data = r#"1||3"#;
        let wpl = r#"(digit)\|,alt(digit:x,chars:x)\|,(digit)"#;
        let express = wpl_express.parse(wpl).assert();
        let lpp = WplEvaluator::from(&express, None).assert();
        let raw = RawData::from_string(data.to_string());
        let (tdc, _) = lpp.proc(0, raw, 0).map_err(|e| e.into_wpl_err())?;
        let tdc_assert = DataRecord::from(vec![
            DataField {
                meta: DataType::Digit,
                name: "digit".into(),
                value: Value::Digit(1.into()),
            },
            DataField {
                meta: DataType::Chars,
                name: "x".into(),
                value: Value::Chars("".into()),
            },
            DataField {
                meta: DataType::Digit,
                name: "digit".into(),
                value: Value::Digit(3.into()),
            },
        ]);
        assert_eq!(tdc, tdc_assert);
        Ok(())
    }
    #[test]
    fn test_digit_opt() -> WplCodeResult<()> {
        let data = r#"1|2|3"#;
        let wpl = r#"(digit)\|,opt(digit)\|,(digit)"#;
        let express = wpl_express.parse(wpl).assert();
        let lpp = WplEvaluator::from(&express, None).assert();
        let raw = RawData::from_string(data.to_string());
        let (tdc, _) = lpp.proc(0, raw, 0).map_err(|e| e.into_wpl_err())?;
        let tdc_assert = DataRecord::from(vec![
            DataField {
                meta: DataType::Digit,
                name: "digit".into(),
                value: Value::Digit(1.into()),
            },
            DataField {
                meta: DataType::Digit,
                name: "digit".into(),
                value: Value::Digit(2.into()),
            },
            DataField {
                meta: DataType::Digit,
                name: "digit".into(),
                value: Value::Digit(3.into()),
            },
        ]);
        assert_eq!(tdc, tdc_assert);
        Ok(())
    }
}