1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
//! rolling result

use std::sync::atomic::{AtomicPtr, Ordering};

use crate::{
    checker::Checker,
    expr::{Operator, PostProcessor},
    tree::{BinaryTree, BinaryTreeNode},
};

// Safety:
// 1. You should only change `cache` value by calling this method
unsafe fn cache_it<T, F>(cache: &AtomicPtr<T>, f: F) -> T
where
    T: Copy,
    F: FnOnce() -> T,
{
    let x = cache.load(Ordering::SeqCst);
    if x.is_null() {
        let value = f();
        let p = Box::into_raw(Box::new(value));
        match cache.compare_exchange(
            std::ptr::null::<T>() as *mut T,
            p,
            Ordering::SeqCst,
            Ordering::SeqCst,
        ) {
            // a success exchange, return value is a null ptr, so no need to deallocate
            Ok(_) => {}
            // Safety:
            // Because of function safety requirement,
            // cache value is stored only in this method, by `Box::into_raw`, so the ptr is valid
            Err(p) => drop(Box::from_raw(p)),
        }
        value
    } else {
        // Safety:
        // Because of function safety requirement,
        // cache value is stored only in this method, by `Box::into_raw`, so ptr is valid.
        // And if cache has a value, it will not change again, so gotten value is alive(until cache itself be dropped),
        // so it's ok to dereference it.
        *x
    }
}

/// Rolling result of a gurgle [`Dice`]
///
/// [`Dice`]: ../struct.Dice.html
#[derive(Debug)]
pub struct DiceRoll {
    points: Vec<u64>,
    pp: PostProcessor,
    cache: AtomicPtr<u64>,
}

impl DiceRoll {
    pub(crate) fn new(points: Vec<u64>, pp: PostProcessor) -> Self {
        Self {
            points,
            pp,
            cache: AtomicPtr::default(),
        }
    }

    /// Get post processor
    #[must_use]
    pub const fn post_processor(&self) -> PostProcessor {
        self.pp
    }

    /// Get rolling dice output points
    #[must_use]
    pub fn points(&self) -> &[u64] {
        &self.points
    }

    /// Get points count(rolling dice times)
    #[allow(clippy::len_without_is_empty)] // because it can't be empty
    #[must_use]
    pub fn len(&self) -> usize {
        self.points.len()
    }

    #[allow(clippy::missing_panics_doc)] // because this can't panic
    #[must_use]
    fn calculate_value(&self) -> u64 {
        match self.pp {
            PostProcessor::Sum => self.points.iter().sum(),
            PostProcessor::Avg => self.points.iter().sum::<u64>() / self.points.len() as u64,
            PostProcessor::Max => *self.points.iter().max().unwrap(),
            PostProcessor::Min => *self.points.iter().min().unwrap(),
        }
    }

    /// Get the final rolling result value, with post processor executed
    pub fn value(&self) -> u64 {
        // Safety: `cache` only used in `cache_it` function
        unsafe { cache_it(&self.cache, || self.calculate_value()) }
    }
}

/// Rolling result of a gurgle expression tree [`Item`]
///
/// [`Item`]: ../ast/enum.Item.html
#[derive(Debug)]
pub enum ItemRoll {
    /// rolling result of a dice item
    Dice(DiceRoll),
    /// number item, rolling result is itself
    Number(i64),
    /// rolling result of another sub expr, which is commonly wrapped by parentheses
    Parentheses(Box<RollTreeNode>),
}

impl ItemRoll {
    /// Get rolling result value
    #[must_use]
    pub fn value(&self) -> i64 {
        match self {
            #[allow(clippy::cast_possible_wrap)] // because out number can't be so big
            Self::Dice(dice) => dice.value() as i64,
            Self::Number(x) => *x,
            Self::Parentheses(e) => e.value(),
        }
    }
}

/// Rolling result tree
pub type RollTree = BinaryTree<ItemRoll, Operator, AtomicPtr<i64>>;

impl RollTree {
    fn calculate_value(&self) -> i64 {
        match self.mid {
            Operator::Add => self.left.value() + self.right.value(),
            Operator::Minus => self.left.value() - self.right.value(),
            Operator::Multiply => self.left.value() * self.right.value(),
        }
    }

    /// Get rolling result value
    pub fn value(&self) -> i64 {
        // Safety: `cache` only used in `cache_it` function
        unsafe { cache_it(&self.extra, || self.calculate_value()) }
    }
}

/// Rolling result tree node, can be a leaf or a sub tree
pub type RollTreeNode = BinaryTreeNode<ItemRoll, Operator, AtomicPtr<i64>>;

impl RollTreeNode {
    /// Get rolling result value
    pub fn value(&self) -> i64 {
        match self {
            Self::Leaf(leaf) => leaf.value(),
            Self::Tree(tree) => tree.value(),
        }
    }
}

/// Rolling result of [`Gurgle`] command
///
/// [`Gurgle`]: ../struct.Gurgle.html
#[derive(Debug)]
pub struct GurgleRoll<'g> {
    result: RollTreeNode,
    checker: Option<&'g Checker>,
    cache: AtomicPtr<i64>,
}

impl<'g> GurgleRoll<'g> {
    pub(crate) fn new(result: RollTreeNode, checker: Option<&'g Checker>) -> Self {
        Self {
            result,
            checker,
            cache: AtomicPtr::default(),
        }
    }

    /// Get rolling result expression
    #[must_use]
    pub const fn expr(&self) -> &RollTreeNode {
        &self.result
    }

    /// Get the checker
    pub const fn checker(&self) -> Option<&'g Checker> {
        self.checker
    }

    /// Get rolling result value
    #[must_use]
    pub fn value(&self) -> i64 {
        // Safety: cache only used in cache_it
        unsafe { cache_it(&self.cache, || self.result.value()) }
    }

    /// Check if this rolling result is success(passed)
    pub fn success(&self) -> Option<bool> {
        self.checker.map(|c| c.check(self.value()))
    }
}