<commit_before>use super::super::{Grid, Coord, FiniteSearchQueue};
use super::*;

#[derive(Clone, Copy)]
struct FieldGrp {
    unmet_num: i32,
    unmet_sum: i32,
    unused: Cand,
}
pub struct Field<'a> {
    dic: &'a Dictionary,
    shape: FieldShape,
    grps: Vec<FieldGrp>,
    val: Vec<i32>,
    cand: Vec<Cand>,
    inconsistent: bool,
    solved: bool,
    undecided_cells: u32,
    total_cands: u32,
    queue: FiniteSearchQueue
}
impl<'a> Field<'a> {
    pub fn new(problem: &Grid<Clue>, dic: &'a Dictionary) -> Field<'a> {
        let n_cells = (problem.height() * problem.width()) as usize;
        let mut has_clue = Grid::new(problem.height(), problem.width(), false);
        let mut n_nonclue_cells = 0;
        for i in 0..n_cells {
            if let Clue::Clue { vertical: _, horizontal: _ } = problem[i] {
                has_clue[i] = true;
            } else {
                n_nonclue_cells += 1;
            }
        }
        let shape = FieldShape::new(&has_clue);

        let mut grps = vec![FieldGrp {
            unmet_num: 0,
            unmet_sum: 0,
            unused: 0,
        }; shape.group_to_cells.len()];
        let n_groups = grps.len();

        for i in 0..shape.group_to_cells.len() {
            let loc = shape.clue_locations[i];
            let clue_val = match loc {
                ClueLocation::Vertical(v) => match problem[v as usize] {
                    Clue::NoClue => panic!("unexpected condition"),
                    Clue::Clue { vertical: v, horizontal: _ } => v,
                },
                ClueLocation::Horizontal(h) => match problem[h as usize] {
                    Clue::NoClue => panic!("unexpected condition"),
                    Clue::Clue { vertical: _, horizontal: h } => h,
                },
            };
            let mut n_cells = 0;
            for _ in shape.group_to_cells[i] {
                n_cells += 1;
            }
            grps[i] = FieldGrp {
                unmet_num: n_cells,
                unmet_sum: clue_val,
                unused: CAND_ALL,
            };
        }

        Field {
            dic: dic,
            shape: shape,
            grps: grps,
            val: vec![UNDECIDED; n_cells],
            cand: vec![CAND_ALL; n_cells],
            inconsistent: false,
            solved: false,
            undecided_cells: n_nonclue_cells,
            total_cands: n_nonclue_cells * 9,
            queue: FiniteSearchQueue::new(n_groups as i32),
        }
    }
    pub fn inconsistent(&self) -> bool {
        self.inconsistent
    }
    pub fn solved(&self) -> bool {
        self.solved
    }
    pub fn undecided_cells(&self) -> u32 {
        self.undecided_cells
    }
    pub fn total_cands(&self) -> u32 {
        self.total_cands
    }
    pub fn height(&self) -> i32 {
        self.shape.has_clue.height()
    }
    pub fn width(&self) -> i32 {
        self.shape.has_clue.width()
    }
    pub fn val(&self, loc: Coord) -> i32 {
        self.val[self.location(loc)]
    }
    pub fn decide(&mut self, loc: Coord, val: i32) {
        let loc = self.location(loc);

        self.queue.start();
        self.decide_int(loc, val);
        while !self.queue.empty() {
            let g = self.queue.pop();
            self.check_group(g);
        }
        self.queue.finish();

    }
    pub fn check_all(&mut self) {
        self.queue.start();
        for i in 0..(self.grps.len() as i32) {
            self.queue.push(i);
        }
        while !self.queue.empty() {
            let g = self.queue.pop();
            self.check_group(g);
        }
        self.queue.finish();
    }
    fn location(&self, loc: Coord) -> usize {
        self.shape.has_clue.index(loc)
    }
    fn decide_int(&mut self, loc: usize, val: i32) {
        if self.val[loc] != UNDECIDED {
            if self.val[loc] != val {
                self.inconsistent = true;
            }
            return;
        }

        self.val[loc] = val;
        self.undecided_cells -= 1;
        if self.undecided_cells == 0 {
            self.solved = true;
        }
        if (self.cand[loc] & (1 << (val - 1))) == 0 {
            self.inconsistent = true;
            return;
        }
        self.total_cands -= self.cand[loc].count_ones() - 1;
        self.cand[loc] = 1 << (val - 1);

        let (g1, g2) = self.shape.cell_to_groups[loc];
        self.grps[g1 as usize].unmet_num -= 1;
        self.grps[g1 as usize].unmet_sum -= val;
        if (self.grps[g1 as usize].unused & (1 << (val - 1) as Cand)) == 0 {
            self.inconsistent = true;
            return;
        }
        self.grps[g1 as usize].unused &= !(1 << (val - 1) as Cand);
        self.grps[g2 as usize].unmet_num -= 1;
        self.grps[g2 as usize].unmet_sum -= val;
        if (self.grps[g2 as usize].unused & (1 << (val - 1) as Cand)) == 0 {
            self.inconsistent = true;
            return;
        }
        self.grps[g2 as usize].unused &= !(1 << (val - 1) as Cand);

        self.eliminate_cand_from_group(g1, val, loc as i32);
        self.eliminate_cand_from_group(g2, val, loc as i32);

        self.queue.push(g1);
        self.queue.push(g2);
    }
    fn eliminate_cand_from_group(&mut self, grp: i32, rem_cand: i32, cur: i32) {
        let cand = !(1 << (rem_cand - 1) as Cand);
        for c in self.shape.group_to_cells[grp as usize] {
            if c != cur {
                self.limit_cand(c as usize, cand);
            }
        }
    }
    fn limit_cand(&mut self, loc: usize, lim: Cand) {
        if self.cand[loc] & lim == self.cand[loc] {
            return;
        }
        self.total_cands -= (self.cand[loc] & !lim).count_ones();
        self.cand[loc] &= lim;

        let current_cand = self.cand[loc];
        if current_cand == 0 {
            self.inconsistent = true;
            return;
        }
        if current_cand.count_ones() == 1 {
            self.decide_int(loc, (current_cand.trailing_zeros() + 1) as i32);
        }

        let (g1, g2) = self.shape.cell_to_groups[loc];
        self.queue.push(g1);
        self.queue.push(g2);
    }
    fn check_group(&mut self, gid: i32) {
        let grp = self.grps[gid as usize];
        let (imperative, allowed) = self.dic.at(grp.unmet_num, grp.unmet_sum, grp.unused);
        if (imperative, allowed) == dictionary::IMPOSSIBLE {
            self.inconsistent = true;
            return;
        }

        // unique position technique
        if imperative != 0 {
            let mut uniq = 0;
            let mut mult = 0;
            for c in self.shape.group_to_cells[gid as usize] {
                if self.val[c as usize] == UNDECIDED {
                    mult |= uniq & self.cand[c as usize];
                    uniq |= self.cand[c as usize];
                }
            }
            uniq &= imperative & !mult;
            if uniq != 0 {
                for c in self.shape.group_to_cells[gid as usize] {
                    if self.val[c as usize] == UNDECIDED && (self.cand[c as usize] & uniq) != 0 {
                        let val = ((self.cand[c as usize] & uniq).trailing_zeros() + 1) as i32;
                        self.decide_int(c as usize, val);
                    }
                }
            }
        }

        // candidate limitation
        for c in self.shape.group_to_cells[gid as usize] {
            if self.val[c as usize] == UNDECIDED {
                self.limit_cand(c as usize, allowed);
            }
        }
        // two-cells propagation (TODO: improve complexity)
        let grp = self.grps[gid as usize];
        if grp.unmet_num == 2 {
            let mut c1 = -1;
            let mut c2 = -1;
            for c in self.shape.group_to_cells[gid as usize] {
                if self.val[c as usize] == UNDECIDED {
                    if c1 == -1 {
                        c1 = c;
                    } else {
                        c2 = c;
                    }
                }
            }
            let mut c1_lim = CAND_ALL;
            let mut c2_lim = CAND_ALL;
            for i in 1..(MAX_VAL + 1) {
                if (self.cand[c1 as usize] & (1 << (i - 1) as Cand) == 0) && 1 <= (grp.unmet_sum - i) && (grp.unmet_sum - i) <= MAX_VAL {
                    c2_lim &= !(1 << (grp.unmet_sum - i - 1) as Cand);
                }
                if (self.cand[c2 as usize] & (1 << (i - 1) as Cand) == 0) && 1 <= (grp.unmet_sum - i) && (grp.unmet_sum - i) <= MAX_VAL {
                    c1_lim &= !(1 << (grp.unmet_sum - i - 1) as Cand);
                }
            }
            self.limit_cand(c1 as usize, c1_lim);
            self.limit_cand(c2 as usize, c2_lim);
        }

        // naked pair (TODO: improve complexity)
        for c in self.shape.group_to_cells[gid as usize] {
            if self.val[c as usize] != -1 || self.cand[c as usize].count_ones() != 2 { continue; }
            for d in self.shape.group_to_cells[gid as usize] {
                if self.val[d as usize] != -1 { continue; }
                if c != d && self.cand[c as usize] == self.cand[d as usize] {
                    for e in self.shape.group_to_cells[gid as usize] {
                        if c != e && d != e {
                            let lim = !self.cand[c as usize];
                            self.limit_cand(e as usize, lim);
                        }
                    }
                }
            }
        }

        // min-max method
        let grp = self.grps[gid as usize];
        let mut min_sum = 0;
        let mut max_sum = 0;
        for c in self.shape.group_to_cells[gid as usize] {
            if self.val[c as usize] != -1 { continue; }
            let cand = self.cand[c as usize];
            min_sum += cand.trailing_zeros() + 1;
            max_sum += 32 - cand.leading_zeros();
        }
        for c in self.shape.group_to_cells[gid as usize] {
            if self.val[c as usize] != -1 { continue; }
            let cand = self.cand[c as usize];

            let current_max = grp.unmet_sum - (min_sum - (cand.trailing_zeros() + 1)) as i32;
            let current_min = grp.unmet_sum - (max_sum - (32 - cand.leading_zeros())) as i32;

            let mut lim = CAND_ALL;
            if current_max <= 8 {
                lim &= (1 << current_max as Cand) - 1;
            }
            if current_min >= 2 {
                lim &= !((1 << (current_min as Cand - 1)) - 1);
            }
            if lim != CAND_ALL {
                self.limit_cand(c as usize, lim);
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use common;

    #[test]
    fn test_field() {
        let dic = Dictionary::default();
        let mut problem_base = Grid::new(3, 3, Clue::NoClue);
        problem_base[Coord { y: 0, x: 0 }] = Clue::Clue { horizontal: -1, vertical: -1 };
        problem_base[Coord { y: 0, x: 1 }] = Clue::Clue { horizontal: -1, vertical: 3 };
        problem_base[Coord { y: 0, x: 2 }] = Clue::Clue { horizontal: -1, vertical: 8 };
        problem_base[Coord { y: 1, x: 0 }] = Clue::Clue { horizontal: 4, vertical: -1 };
        problem_base[Coord { y: 2, x: 0 }] = Clue::Clue { horizontal: 7, vertical: -1 };

        let mut field = Field::new(&problem_base, &dic);
        field.check_all();

        assert_eq!(field.val(Coord { y: 1, x: 1 }), 1);
        assert_eq!(field.val(Coord { y: 1, x: 2 }), 3);
        assert_eq!(field.val(Coord { y: 2, x: 1 }), 2);
        assert_eq!(field.val(Coord { y: 2, x: 2 }), 5);
        assert_eq!(field.solved(), true);
        assert_eq!(field.inconsistent(), false);
        assert_eq!(field.undecided_cells(), 0);
        assert_eq!(field.total_cands(), 4);
    }

    #[test]
    fn test_inconsistent_field() {
        let dic = Dictionary::default();
        let mut problem_base = Grid::new(3, 3, Clue::NoClue);
        problem_base[Coord { y: 0, x: 0 }] = Clue::Clue { horizontal: -1, vertical: -1 };
        problem_base[Coord { y: 0, x: 1 }] = Clue::Clue { horizontal: -1, vertical: 3 };
        problem_base[Coord { y: 0, x: 2 }] = Clue::Clue { horizontal: -1, vertical: 6 };
        problem_base[Coord { y: 1, x: 0 }] = Clue::Clue { horizontal: 4, vertical: -1 };
        problem_base[Coord { y: 2, x: 0 }] = Clue::Clue { horizontal: 5, vertical: -1 };

        let mut field = Field::new(&problem_base, &dic);
        field.check_all();

        assert_eq!(field.inconsistent(), true);
    }
}
<commit_msg>Kakuro: avoid checking unnecessary inconsistency condition in Field<commit_after>use super::super::{Grid, Coord, FiniteSearchQueue};
use super::*;

#[derive(Clone, Copy)]
struct FieldGrp {
    unmet_num: i32,
    unmet_sum: i32,
    unused: Cand,
}
pub struct Field<'a> {
    dic: &'a Dictionary,
    shape: FieldShape,
    grps: Vec<FieldGrp>,
    val: Vec<i32>,
    cand: Vec<Cand>,
    inconsistent: bool,
    solved: bool,
    undecided_cells: u32,
    total_cands: u32,
    queue: FiniteSearchQueue
}
impl<'a> Field<'a> {
    pub fn new(problem: &Grid<Clue>, dic: &'a Dictionary) -> Field<'a> {
        let n_cells = (problem.height() * problem.width()) as usize;
        let mut has_clue = Grid::new(problem.height(), problem.width(), false);
        let mut n_nonclue_cells = 0;
        for i in 0..n_cells {
            if let Clue::Clue { vertical: _, horizontal: _ } = problem[i] {
                has_clue[i] = true;
            } else {
                n_nonclue_cells += 1;
            }
        }
        let shape = FieldShape::new(&has_clue);

        let mut grps = vec![FieldGrp {
            unmet_num: 0,
            unmet_sum: 0,
            unused: 0,
        }; shape.group_to_cells.len()];
        let n_groups = grps.len();

        for i in 0..shape.group_to_cells.len() {
            let loc = shape.clue_locations[i];
            let clue_val = match loc {
                ClueLocation::Vertical(v) => match problem[v as usize] {
                    Clue::NoClue => panic!("unexpected condition"),
                    Clue::Clue { vertical: v, horizontal: _ } => v,
                },
                ClueLocation::Horizontal(h) => match problem[h as usize] {
                    Clue::NoClue => panic!("unexpected condition"),
                    Clue::Clue { vertical: _, horizontal: h } => h,
                },
            };
            let mut n_cells = 0;
            for _ in shape.group_to_cells[i] {
                n_cells += 1;
            }
            grps[i] = FieldGrp {
                unmet_num: n_cells,
                unmet_sum: clue_val,
                unused: CAND_ALL,
            };
        }

        Field {
            dic: dic,
            shape: shape,
            grps: grps,
            val: vec![UNDECIDED; n_cells],
            cand: vec![CAND_ALL; n_cells],
            inconsistent: false,
            solved: false,
            undecided_cells: n_nonclue_cells,
            total_cands: n_nonclue_cells * 9,
            queue: FiniteSearchQueue::new(n_groups as i32),
        }
    }
    pub fn inconsistent(&self) -> bool {
        self.inconsistent
    }
    pub fn solved(&self) -> bool {
        self.solved
    }
    pub fn undecided_cells(&self) -> u32 {
        self.undecided_cells
    }
    pub fn total_cands(&self) -> u32 {
        self.total_cands
    }
    pub fn height(&self) -> i32 {
        self.shape.has_clue.height()
    }
    pub fn width(&self) -> i32 {
        self.shape.has_clue.width()
    }
    pub fn val(&self, loc: Coord) -> i32 {
        self.val[self.location(loc)]
    }
    pub fn decide(&mut self, loc: Coord, val: i32) {
        let loc = self.location(loc);

        self.queue.start();
        self.decide_int(loc, val);
        while !self.queue.empty() {
            let g = self.queue.pop();
            self.check_group(g);
        }
        self.queue.finish();

    }
    pub fn check_all(&mut self) {
        self.queue.start();
        for i in 0..(self.grps.len() as i32) {
            self.queue.push(i);
        }
        while !self.queue.empty() {
            let g = self.queue.pop();
            self.check_group(g);
        }
        self.queue.finish();
    }
    fn location(&self, loc: Coord) -> usize {
        self.shape.has_clue.index(loc)
    }
    fn decide_int(&mut self, loc: usize, val: i32) {
        if self.val[loc] != UNDECIDED {
            if self.val[loc] != val {
                self.inconsistent = true;
            }
            return;
        }

        self.val[loc] = val;
        self.undecided_cells -= 1;
        if self.undecided_cells == 0 {
            self.solved = true;
        }
        if (self.cand[loc] & (1 << (val - 1))) == 0 {
            self.inconsistent = true;
            return;
        }
        self.total_cands -= self.cand[loc].count_ones() - 1;
        self.cand[loc] = 1 << (val - 1);

        let (g1, g2) = self.shape.cell_to_groups[loc];
        self.grps[g1 as usize].unmet_num -= 1;
        self.grps[g1 as usize].unmet_sum -= val;
        self.grps[g1 as usize].unused &= !(1 << (val - 1) as Cand);

        self.grps[g2 as usize].unmet_num -= 1;
        self.grps[g2 as usize].unmet_sum -= val;
        self.grps[g2 as usize].unused &= !(1 << (val - 1) as Cand);

        self.eliminate_cand_from_group(g1, val, loc as i32);
        self.eliminate_cand_from_group(g2, val, loc as i32);

        self.queue.push(g1);
        self.queue.push(g2);
    }
    fn eliminate_cand_from_group(&mut self, grp: i32, rem_cand: i32, cur: i32) {
        let cand = !(1 << (rem_cand - 1) as Cand);
        for c in self.shape.group_to_cells[grp as usize] {
            if c != cur {
                self.limit_cand(c as usize, cand);
            }
        }
    }
    fn limit_cand(&mut self, loc: usize, lim: Cand) {
        if self.cand[loc] & lim == self.cand[loc] {
            return;
        }
        self.total_cands -= (self.cand[loc] & !lim).count_ones();
        self.cand[loc] &= lim;

        let current_cand = self.cand[loc];
        if current_cand == 0 {
            self.inconsistent = true;
            return;
        }
        if current_cand.count_ones() == 1 {
            self.decide_int(loc, (current_cand.trailing_zeros() + 1) as i32);
        }

        let (g1, g2) = self.shape.cell_to_groups[loc];
        self.queue.push(g1);
        self.queue.push(g2);
    }
    fn check_group(&mut self, gid: i32) {
        let grp = self.grps[gid as usize];
        let (imperative, allowed) = self.dic.at(grp.unmet_num, grp.unmet_sum, grp.unused);
        if (imperative, allowed) == dictionary::IMPOSSIBLE {
            self.inconsistent = true;
            return;
        }

        // unique position technique
        if imperative != 0 {
            let mut uniq = 0;
            let mut mult = 0;
            for c in self.shape.group_to_cells[gid as usize] {
                if self.val[c as usize] == UNDECIDED {
                    mult |= uniq & self.cand[c as usize];
                    uniq |= self.cand[c as usize];
                }
            }
            uniq &= imperative & !mult;
            if uniq != 0 {
                for c in self.shape.group_to_cells[gid as usize] {
                    if self.val[c as usize] == UNDECIDED && (self.cand[c as usize] & uniq) != 0 {
                        let val = ((self.cand[c as usize] & uniq).trailing_zeros() + 1) as i32;
                        self.decide_int(c as usize, val);
                    }
                }
            }
        }

        // candidate limitation
        for c in self.shape.group_to_cells[gid as usize] {
            if self.val[c as usize] == UNDECIDED {
                self.limit_cand(c as usize, allowed);
            }
        }
        // two-cells propagation (TODO: improve complexity)
        let grp = self.grps[gid as usize];
        if grp.unmet_num == 2 {
            let mut c1 = -1;
            let mut c2 = -1;
            for c in self.shape.group_to_cells[gid as usize] {
                if self.val[c as usize] == UNDECIDED {
                    if c1 == -1 {
                        c1 = c;
                    } else {
                        c2 = c;
                    }
                }
            }
            let mut c1_lim = CAND_ALL;
            let mut c2_lim = CAND_ALL;
            for i in 1..(MAX_VAL + 1) {
                if (self.cand[c1 as usize] & (1 << (i - 1) as Cand) == 0) && 1 <= (grp.unmet_sum - i) && (grp.unmet_sum - i) <= MAX_VAL {
                    c2_lim &= !(1 << (grp.unmet_sum - i - 1) as Cand);
                }
                if (self.cand[c2 as usize] & (1 << (i - 1) as Cand) == 0) && 1 <= (grp.unmet_sum - i) && (grp.unmet_sum - i) <= MAX_VAL {
                    c1_lim &= !(1 << (grp.unmet_sum - i - 1) as Cand);
                }
            }
            self.limit_cand(c1 as usize, c1_lim);
            self.limit_cand(c2 as usize, c2_lim);
        }

        // naked pair (TODO: improve complexity)
        for c in self.shape.group_to_cells[gid as usize] {
            if self.val[c as usize] != -1 || self.cand[c as usize].count_ones() != 2 { continue; }
            for d in self.shape.group_to_cells[gid as usize] {
                if self.val[d as usize] != -1 { continue; }
                if c != d && self.cand[c as usize] == self.cand[d as usize] {
                    for e in self.shape.group_to_cells[gid as usize] {
                        if c != e && d != e {
                            let lim = !self.cand[c as usize];
                            self.limit_cand(e as usize, lim);
                        }
                    }
                }
            }
        }

        // min-max method
        let grp = self.grps[gid as usize];
        let mut min_sum = 0;
        let mut max_sum = 0;
        for c in self.shape.group_to_cells[gid as usize] {
            if self.val[c as usize] != -1 { continue; }
            let cand = self.cand[c as usize];
            min_sum += cand.trailing_zeros() + 1;
            max_sum += 32 - cand.leading_zeros();
        }
        for c in self.shape.group_to_cells[gid as usize] {
            if self.val[c as usize] != -1 { continue; }
            let cand = self.cand[c as usize];

            let current_max = grp.unmet_sum - (min_sum - (cand.trailing_zeros() + 1)) as i32;
            let current_min = grp.unmet_sum - (max_sum - (32 - cand.leading_zeros())) as i32;

            let mut lim = CAND_ALL;
            if current_max <= 8 {
                lim &= (1 << current_max as Cand) - 1;
            }
            if current_min >= 2 {
                lim &= !((1 << (current_min as Cand - 1)) - 1);
            }
            if lim != CAND_ALL {
                self.limit_cand(c as usize, lim);
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use common;

    #[test]
    fn test_field() {
        let dic = Dictionary::default();
        let mut problem_base = Grid::new(3, 3, Clue::NoClue);
        problem_base[Coord { y: 0, x: 0 }] = Clue::Clue { horizontal: -1, vertical: -1 };
        problem_base[Coord { y: 0, x: 1 }] = Clue::Clue { horizontal: -1, vertical: 3 };
        problem_base[Coord { y: 0, x: 2 }] = Clue::Clue { horizontal: -1, vertical: 8 };
        problem_base[Coord { y: 1, x: 0 }] = Clue::Clue { horizontal: 4, vertical: -1 };
        problem_base[Coord { y: 2, x: 0 }] = Clue::Clue { horizontal: 7, vertical: -1 };

        let mut field = Field::new(&problem_base, &dic);
        field.check_all();

        assert_eq!(field.val(Coord { y: 1, x: 1 }), 1);
        assert_eq!(field.val(Coord { y: 1, x: 2 }), 3);
        assert_eq!(field.val(Coord { y: 2, x: 1 }), 2);
        assert_eq!(field.val(Coord { y: 2, x: 2 }), 5);
        assert_eq!(field.solved(), true);
        assert_eq!(field.inconsistent(), false);
        assert_eq!(field.undecided_cells(), 0);
        assert_eq!(field.total_cands(), 4);
    }

    #[test]
    fn test_inconsistent_field() {
        let dic = Dictionary::default();
        let mut problem_base = Grid::new(3, 3, Clue::NoClue);
        problem_base[Coord { y: 0, x: 0 }] = Clue::Clue { horizontal: -1, vertical: -1 };
        problem_base[Coord { y: 0, x: 1 }] = Clue::Clue { horizontal: -1, vertical: 3 };
        problem_base[Coord { y: 0, x: 2 }] = Clue::Clue { horizontal: -1, vertical: 6 };
        problem_base[Coord { y: 1, x: 0 }] = Clue::Clue { horizontal: 4, vertical: -1 };
        problem_base[Coord { y: 2, x: 0 }] = Clue::Clue { horizontal: 5, vertical: -1 };

        let mut field = Field::new(&problem_base, &dic);
        field.check_all();

        assert_eq!(field.inconsistent(), true);
    }
}
<|endoftext|>


<commit_before>
use std::collections::HashMap;

use data;
use parser::{ParseError, Parser};
use parser;

pub fn compile(source: &str) -> Result<HashMap<String, parser::Entry>, ParseError> {
  let p = Parser::new(source.chars());
  let entries = try!(p.parse());

  // TODO: parse all imports

  let mut map = HashMap::new();

  for mut entry in entries.move_iter() {
    let id = match entry {
      parser::Comment(..) | parser::Import(..) => continue,
      parser::Macro(ref id, _, _) => id.clone(),
      parser::Entity(ref id, ref mut value, ref indices, ref mut attrs)  => {
        // while we're here, fix up and Hash values with default indices
        match value  {
          &parser::Hash(..) => {
            if indices.len() > 0 {
              add_default_indices(value, indices.as_slice());
            }
          },
          _ => {}
        };
        for &parser::Attr(_, ref mut value, ref indices) in attrs.mut_iter() {
          match value  {
            &parser::Hash(..) => {
              if indices.len() > 0 {
                add_default_indices(value, indices.as_slice());
              }
            },
            _ => {}
          };
        }

        id.clone()
      }
    };
    map.insert(id, entry);
  }

  Ok(map)
}

fn add_default_indices(value: &mut parser::Value, mut indices: &[parser::Expr]) {
  match value {
    &parser::Hash(ref mut map, _, ref mut def_index) => {
      match indices.shift_ref() {
        Some(idx) => {
          for (_k, v) in map.mut_iter() {
            add_default_indices(v, indices);
          }
          *def_index = Some(box idx.clone())
        },
        None => {}
      }
    },
    _ => {}
  }
}

pub type Env = HashMap<String, parser::Entry>;

pub struct ResolveContext<'a> {
  data: &'a data::Data,
  env: &'a Env,
  locals: Option<&'a data::Data>,
  index: Option<String>,
}

impl<'a> ResolveContext<'a> {
  pub fn new(env: &'a Env, data: &'a data::Data) -> ResolveContext<'a> {
    ResolveContext {
      env: env,
      data: data,
      locals: None,
      index: None,
    }
  }

  fn with_locals(&'a self, locals: &'a data::Data) -> ResolveContext<'a> {
    ResolveContext {
      env: self.env,
      data: self.data,
      locals: Some(locals),
      index: None,
    }
  }

  fn with_index(&'a self, index: Option<String>) -> ResolveContext<'a> {
    ResolveContext {
      env: self.env,
      data: self.data,
      locals: self.locals,
      index: index,
    }
  }
}

pub type ResolveResult = Result<ResolveTarget, ResolveError>;

pub enum ResolveTarget {
  Entry(parser::Entry),
  Value(parser::Value),
  Data(data::Data)
}

/// Errors that can occur when resolving a set of l20n resources into strings.
/// These errors are cause by problems in the l20n file, or incorrect Data
/// provided when localizing.
#[deriving(Show)]
pub enum ResolveError {
  /// A resource received a value of the wrong type.
  WrongType,
  /// A macro was called with the wrong number of arguments.
  WrongNumberOfArgs,
  /// Accessed an index of a Hash that does not exist.
  MissingIndex,
  /// Accessed an attribute of an entity that does not exist.
  MissingAttr,
  /// Tried to use a $var that did not exist in the provided Data.
  MissingVar(String),
  /// A string tried to use another string in the l20n resource that did not
  /// exist.
  MissingIdent(String),
}

/// Resolve an L20n resource into Data.
pub trait Resolve {

  /// Resolves this value a step. It could resolve to another Value, or
  /// resolve completely to a Data.
  fn resolve(&self, ctx: &ResolveContext) -> ResolveResult;

  /// Keeps resolving until a Data value is returned.
  fn resolve_data(&self, ctx: &ResolveContext) -> Result<data::Data, ResolveError> {
    match self.resolve(ctx) {
      Ok(Data(d)) => Ok(d),
      Ok(other) => other.resolve_data(ctx),
      Err(e) => Err(e)
    }
  }
}

impl Resolve for ResolveTarget {
  fn resolve(&self, ctx: &ResolveContext) -> ResolveResult {
    match *self {
      Data(ref d) => Ok(Data(d.clone())),
      Entry(ref e) => e.resolve(ctx),
      Value(ref v) => v.resolve(ctx)
    }
  }
}

impl Resolve for parser::Entry {
  fn resolve(&self, _ctx: &ResolveContext) -> ResolveResult {
    match *self {
      parser::Entity(_, ref value, _, _) => {
        Ok(Value(value.clone()))
      }
      _ => Ok(Data(data::Null))
    }
  }
}

impl Resolve for parser::Value {
  fn resolve(&self, ctx: &ResolveContext) -> ResolveResult {
    match *self {
      parser::Str(ref s) => Ok(Data(data::Str(s.clone()))),
      parser::ComplexStr(ref exprs) => {
        let mut vec = Vec::with_capacity(exprs.len());
        for expr in exprs.iter() {
          vec.push(match expr.resolve_data(ctx) {
            Ok(data::Str(s)) => s,
            Ok(data::Num(n)) => format!("{}", n),
            Ok(_) => return Err(WrongType),
            Err(e) => return Err(e)
          });
        }
        Ok(Data(data::Str(vec.concat())))
      }
      parser::Hash(ref map, ref def_key, ref def_index) => {
        match ctx.index {
          Some(ref s) => match map.find(s) {
              Some(v) => return Ok(Value(v.clone())),
              None => {}
          },
          None => {}
        };
        match *def_key {
          Some(ref s) => match map.find(s) {
              Some(v) => return Ok(Value(v.clone())),
              None => {}
          },
          None => {}
        };
        match *def_index {
          Some(ref e) => match e.resolve_data(ctx) {
              Ok(data::Str(ref s)) => match map.find(s) {
                Some(v) => return Ok(Value(v.clone())),
                None => {}
              },
              Ok(_) => return Err(WrongType),
              Err(e) => return Err(e)
          },
          None => {}
        };
        Err(MissingIndex)
      }
    }
  }
}

impl Resolve for parser::Expr {
  fn resolve(&self, ctx: &ResolveContext) -> ResolveResult {
    match *self {
      parser::ValExpr(ref val) => Ok(Value(val.clone())),
      parser::NumExpr(ref n) => Ok(Data(data::Num(*n))),
      parser::BinExpr(ref left, ref op, ref right) => {
        let left = try!(left.resolve_data(ctx));
        let right = try!(right.resolve_data(ctx));
        match (*op, left, right) {
          // math ops
          (parser::BiAdd, data::Num(l), data::Num(r)) => Ok(Data(data::Num(l + r))),
          (parser::BiSub, data::Num(l), data::Num(r)) => Ok(Data(data::Num(l - r))),
          (parser::BiMul, data::Num(l), data::Num(r)) => Ok(Data(data::Num(l * r))),
          (parser::BiDiv, data::Num(l), data::Num(r)) => Ok(Data(data::Num(l / r))),
          (parser::BiRem, data::Num(l), data::Num(r)) => Ok(Data(data::Num(l % r))),

          (parser::BiLt, data::Num(l), data::Num(r)) => Ok(Data(data::Bool(l < r))),
          (parser::BiLe, data::Num(l), data::Num(r)) => Ok(Data(data::Bool(l <= r))),
          (parser::BiGt, data::Num(l), data::Num(r)) => Ok(Data(data::Bool(l > r))),
          (parser::BiGe, data::Num(l), data::Num(r)) => Ok(Data(data::Bool(l >= r))),

          // logical ops
          (parser::BiAnd, data::Bool(l), data::Bool(r)) => Ok(Data(data::Bool(l && r))),
          (parser::BiOr, data::Bool(l), data::Bool(r)) => Ok(Data(data::Bool(l || r))),

          // equality ops. can be Num, Bool, or Str
          (parser::BiEq, data::Bool(l), data::Bool(r)) => Ok(Data(data::Bool(l == r))),
          (parser::BiEq, data::Str(l), data::Str(r)) => Ok(Data(data::Bool(l == r))),
          (parser::BiEq, data::Num(l), data::Num(r)) => Ok(Data(data::Bool(l == r))),
          (parser::BiNe, data::Bool(l), data::Bool(r)) => Ok(Data(data::Bool(l == r))),
          (parser::BiNe, data::Str(l), data::Str(r)) => Ok(Data(data::Bool(l == r))),
          (parser::BiNe, data::Num(l), data::Num(r)) => Ok(Data(data::Bool(l != r))),

          (_, _, _) => Err(WrongType)
        }
      }
      parser::UnExpr(ref op, ref expr) => {
        let expr = try!(expr.resolve_data(ctx));
        match (*op, expr) {
          (parser::UnAdd, data::Num(n)) => Ok(Data(data::Num(n))),
          (parser::UnSub, data::Num(n)) => Ok(Data(data::Num(-n))),
          (parser::UnNot, data::Bool(b)) => Ok(Data(data::Bool(!b))),
          _ => Err(WrongType)
        }
      }
      parser::VarExpr(ref name) => {
        match ctx.locals.and_then(|locals| locals.find_copy(name)) {
          Some(val) => return Ok(Data(val)),
          _ => {}
        };
        match ctx.data.find_copy(name) {
          Some(d) => Ok(Data(d)),
          None => Err(MissingVar(name.clone()))
        }
      }
      parser::IdentExpr(ref ident) => {
        match ctx.env.find(ident) {
          Some(e) => Ok(Entry(e.clone())),
          None => Err(MissingIdent(ident.clone()))
        }
      }
      parser::CondExpr(ref cond, ref consequent, ref alt) => {
        match try!(cond.resolve_data(ctx)) {
          data::Bool(b) => {
            if b {
              consequent.resolve(ctx)
            } else {
              alt.resolve(ctx)
            }
          },
          _ => Err(WrongType)
        }
      }
      parser::CallExpr(box parser::IdentExpr(ref ident), ref args) => {
        match ctx.env.find(ident) {
          Some(&parser::Macro(_, ref arg_names, ref body)) => {
            if args.len() == arg_names.len() {
              let mut map = HashMap::new();
              for (k, v) in arg_names.iter().zip(args.iter()) {
                let name = match k {
                  &parser::VarExpr(ref name) => name.clone(),
                  // not a VarExpr would be the parser going nuts
                  _ => unreachable!()
                };
                let arg = match v.resolve_data(ctx) {
                  Ok(val) => val,
                  Err(e) => return Err(e)
                };
                map.insert(name, arg);
              }
              let locals = data::Map(map);
              body.resolve(&ctx.with_locals(&locals))
            } else {
              Err(WrongNumberOfArgs)
            }
          }
          Some(_) => Err(WrongType),
          None => Err(MissingIdent(ident.clone()))
        }
      }
      parser::PropExpr(ref parent, ref prop, ref access) => {
        let prop = match *access {
          parser::Computed => match prop.resolve_data(ctx) {
            Ok(data::Str(s)) => s,
            Ok(_) => return Err(WrongType),
            Err(e) => return Err(e)
          },
          parser::Static => match *prop {
            box parser::IdentExpr(ref s) => s.clone(),
            _ => return Err(WrongType)
          }
        };

        match parent.resolve(ctx) {
          Ok(Data(data::Map(ref m))) => {
            match m.find_copy(&prop) {
              Some(d) => Ok(Data(d)),
              None => Err(MissingIndex)
            }
          },
          Ok(Entry(ref e)) => {
            match e.resolve(ctx) {
              Ok(Value(ref v)) => v.resolve(&ctx.with_index(Some(prop))),
              Ok(_) => Err(WrongType),
              Err(e) => Err(e)
            }
          },
          Ok(Value(ref v)) => {
            v.resolve(&ctx.with_index(Some(prop)))
          },
          Ok(_) => Err(WrongType),
          Err(e) => Err(e)
        }
      }
      parser::AttrExpr(ref parent, ref prop, ref access) => {
        let prop = match *access {
          parser::Computed => match prop.resolve_data(ctx) {
            Ok(data::Str(s)) => s,
            Ok(_) => return Err(WrongType),
            Err(e) => return Err(e)
          },
          parser::Static => match *prop {
            box parser::IdentExpr(ref s) => s.clone(),
            _ => return Err(WrongType)
          }
        };

        match parent.resolve(ctx) {
          Ok(Entry(parser::Entity(_, _, _, ref attrs))) => {
            for &parser::Attr(ref id, ref value, _) in attrs.iter() {
              if id.as_slice() == prop.as_slice() {
                return value.resolve(ctx)
              }
            }
            Err(MissingAttr)
          },
          Ok(_) => Err(WrongType),
          Err(e) => Err(e)
        }
      }
      ref e => fail!("{} not yet implemented", e)
    }
  }
}

#[cfg(test)]
mod tests {
  use super::{compile, Resolve, ResolveContext};
  use data::{Str, Null};

  #[test]
  fn test_compile() {
    let map = compile("<hi 'hello world'>").unwrap();
    let entity = map.get(&String::from_str("hi"));
    let data = Null;
    let ctx = ResolveContext::new(&map, &data);

    assert_eq!(entity.resolve_data(&ctx).unwrap(), Str(String::from_str("hello world")));

  }
}
<commit_msg>remove deprecated use of `std::slice::ImmutableSlice::shift_ref`<commit_after>
use std::collections::HashMap;

use data;
use parser::{ParseError, Parser};
use parser;

pub fn compile(source: &str) -> Result<HashMap<String, parser::Entry>, ParseError> {
  let p = Parser::new(source.chars());
  let entries = try!(p.parse());

  // TODO: parse all imports

  let mut map = HashMap::new();

  for mut entry in entries.move_iter() {
    let id = match entry {
      parser::Comment(..) | parser::Import(..) => continue,
      parser::Macro(ref id, _, _) => id.clone(),
      parser::Entity(ref id, ref mut value, ref indices, ref mut attrs)  => {
        // while we're here, fix up and Hash values with default indices
        match value  {
          &parser::Hash(..) => {
            if indices.len() > 0 {
              add_default_indices(value, indices.iter());
            }
          },
          _ => {}
        };
        for &parser::Attr(_, ref mut value, ref indices) in attrs.mut_iter() {
          match value  {
            &parser::Hash(..) => {
              if indices.len() > 0 {
                add_default_indices(value, indices.iter());
              }
            },
            _ => {}
          };
        }

        id.clone()
      }
    };
    map.insert(id, entry);
  }

  Ok(map)
}

fn add_default_indices<'r, I: Iterator<&'r parser::Expr> + Clone>(value: &mut parser::Value, mut indices: I) {
  match value {
    &parser::Hash(ref mut map, _, ref mut def_index) => {
      match indices.next() {
        Some(idx) => {
          for (_k, v) in map.mut_iter() {
            add_default_indices(v, indices.clone());
          }
          *def_index = Some(box idx.clone())
        },
        None => {}
      }
    },
    _ => {}
  }
}

pub type Env = HashMap<String, parser::Entry>;

pub struct ResolveContext<'a> {
  data: &'a data::Data,
  env: &'a Env,
  locals: Option<&'a data::Data>,
  index: Option<String>,
}

impl<'a> ResolveContext<'a> {
  pub fn new(env: &'a Env, data: &'a data::Data) -> ResolveContext<'a> {
    ResolveContext {
      env: env,
      data: data,
      locals: None,
      index: None,
    }
  }

  fn with_locals(&'a self, locals: &'a data::Data) -> ResolveContext<'a> {
    ResolveContext {
      env: self.env,
      data: self.data,
      locals: Some(locals),
      index: None,
    }
  }

  fn with_index(&'a self, index: Option<String>) -> ResolveContext<'a> {
    ResolveContext {
      env: self.env,
      data: self.data,
      locals: self.locals,
      index: index,
    }
  }
}

pub type ResolveResult = Result<ResolveTarget, ResolveError>;

pub enum ResolveTarget {
  Entry(parser::Entry),
  Value(parser::Value),
  Data(data::Data)
}

/// Errors that can occur when resolving a set of l20n resources into strings.
/// These errors are cause by problems in the l20n file, or incorrect Data
/// provided when localizing.
#[deriving(Show)]
pub enum ResolveError {
  /// A resource received a value of the wrong type.
  WrongType,
  /// A macro was called with the wrong number of arguments.
  WrongNumberOfArgs,
  /// Accessed an index of a Hash that does not exist.
  MissingIndex,
  /// Accessed an attribute of an entity that does not exist.
  MissingAttr,
  /// Tried to use a $var that did not exist in the provided Data.
  MissingVar(String),
  /// A string tried to use another string in the l20n resource that did not
  /// exist.
  MissingIdent(String),
}

/// Resolve an L20n resource into Data.
pub trait Resolve {

  /// Resolves this value a step. It could resolve to another Value, or
  /// resolve completely to a Data.
  fn resolve(&self, ctx: &ResolveContext) -> ResolveResult;

  /// Keeps resolving until a Data value is returned.
  fn resolve_data(&self, ctx: &ResolveContext) -> Result<data::Data, ResolveError> {
    match self.resolve(ctx) {
      Ok(Data(d)) => Ok(d),
      Ok(other) => other.resolve_data(ctx),
      Err(e) => Err(e)
    }
  }
}

impl Resolve for ResolveTarget {
  fn resolve(&self, ctx: &ResolveContext) -> ResolveResult {
    match *self {
      Data(ref d) => Ok(Data(d.clone())),
      Entry(ref e) => e.resolve(ctx),
      Value(ref v) => v.resolve(ctx)
    }
  }
}

impl Resolve for parser::Entry {
  fn resolve(&self, _ctx: &ResolveContext) -> ResolveResult {
    match *self {
      parser::Entity(_, ref value, _, _) => {
        Ok(Value(value.clone()))
      }
      _ => Ok(Data(data::Null))
    }
  }
}

impl Resolve for parser::Value {
  fn resolve(&self, ctx: &ResolveContext) -> ResolveResult {
    match *self {
      parser::Str(ref s) => Ok(Data(data::Str(s.clone()))),
      parser::ComplexStr(ref exprs) => {
        let mut vec = Vec::with_capacity(exprs.len());
        for expr in exprs.iter() {
          vec.push(match expr.resolve_data(ctx) {
            Ok(data::Str(s)) => s,
            Ok(data::Num(n)) => format!("{}", n),
            Ok(_) => return Err(WrongType),
            Err(e) => return Err(e)
          });
        }
        Ok(Data(data::Str(vec.concat())))
      }
      parser::Hash(ref map, ref def_key, ref def_index) => {
        match ctx.index {
          Some(ref s) => match map.find(s) {
              Some(v) => return Ok(Value(v.clone())),
              None => {}
          },
          None => {}
        };
        match *def_key {
          Some(ref s) => match map.find(s) {
              Some(v) => return Ok(Value(v.clone())),
              None => {}
          },
          None => {}
        };
        match *def_index {
          Some(ref e) => match e.resolve_data(ctx) {
              Ok(data::Str(ref s)) => match map.find(s) {
                Some(v) => return Ok(Value(v.clone())),
                None => {}
              },
              Ok(_) => return Err(WrongType),
              Err(e) => return Err(e)
          },
          None => {}
        };
        Err(MissingIndex)
      }
    }
  }
}

impl Resolve for parser::Expr {
  fn resolve(&self, ctx: &ResolveContext) -> ResolveResult {
    match *self {
      parser::ValExpr(ref val) => Ok(Value(val.clone())),
      parser::NumExpr(ref n) => Ok(Data(data::Num(*n))),
      parser::BinExpr(ref left, ref op, ref right) => {
        let left = try!(left.resolve_data(ctx));
        let right = try!(right.resolve_data(ctx));
        match (*op, left, right) {
          // math ops
          (parser::BiAdd, data::Num(l), data::Num(r)) => Ok(Data(data::Num(l + r))),
          (parser::BiSub, data::Num(l), data::Num(r)) => Ok(Data(data::Num(l - r))),
          (parser::BiMul, data::Num(l), data::Num(r)) => Ok(Data(data::Num(l * r))),
          (parser::BiDiv, data::Num(l), data::Num(r)) => Ok(Data(data::Num(l / r))),
          (parser::BiRem, data::Num(l), data::Num(r)) => Ok(Data(data::Num(l % r))),

          (parser::BiLt, data::Num(l), data::Num(r)) => Ok(Data(data::Bool(l < r))),
          (parser::BiLe, data::Num(l), data::Num(r)) => Ok(Data(data::Bool(l <= r))),
          (parser::BiGt, data::Num(l), data::Num(r)) => Ok(Data(data::Bool(l > r))),
          (parser::BiGe, data::Num(l), data::Num(r)) => Ok(Data(data::Bool(l >= r))),

          // logical ops
          (parser::BiAnd, data::Bool(l), data::Bool(r)) => Ok(Data(data::Bool(l && r))),
          (parser::BiOr, data::Bool(l), data::Bool(r)) => Ok(Data(data::Bool(l || r))),

          // equality ops. can be Num, Bool, or Str
          (parser::BiEq, data::Bool(l), data::Bool(r)) => Ok(Data(data::Bool(l == r))),
          (parser::BiEq, data::Str(l), data::Str(r)) => Ok(Data(data::Bool(l == r))),
          (parser::BiEq, data::Num(l), data::Num(r)) => Ok(Data(data::Bool(l == r))),
          (parser::BiNe, data::Bool(l), data::Bool(r)) => Ok(Data(data::Bool(l == r))),
          (parser::BiNe, data::Str(l), data::Str(r)) => Ok(Data(data::Bool(l == r))),
          (parser::BiNe, data::Num(l), data::Num(r)) => Ok(Data(data::Bool(l != r))),

          (_, _, _) => Err(WrongType)
        }
      }
      parser::UnExpr(ref op, ref expr) => {
        let expr = try!(expr.resolve_data(ctx));
        match (*op, expr) {
          (parser::UnAdd, data::Num(n)) => Ok(Data(data::Num(n))),
          (parser::UnSub, data::Num(n)) => Ok(Data(data::Num(-n))),
          (parser::UnNot, data::Bool(b)) => Ok(Data(data::Bool(!b))),
          _ => Err(WrongType)
        }
      }
      parser::VarExpr(ref name) => {
        match ctx.locals.and_then(|locals| locals.find_copy(name)) {
          Some(val) => return Ok(Data(val)),
          _ => {}
        };
        match ctx.data.find_copy(name) {
          Some(d) => Ok(Data(d)),
          None => Err(MissingVar(name.clone()))
        }
      }
      parser::IdentExpr(ref ident) => {
        match ctx.env.find(ident) {
          Some(e) => Ok(Entry(e.clone())),
          None => Err(MissingIdent(ident.clone()))
        }
      }
      parser::CondExpr(ref cond, ref consequent, ref alt) => {
        match try!(cond.resolve_data(ctx)) {
          data::Bool(b) => {
            if b {
              consequent.resolve(ctx)
            } else {
              alt.resolve(ctx)
            }
          },
          _ => Err(WrongType)
        }
      }
      parser::CallExpr(box parser::IdentExpr(ref ident), ref args) => {
        match ctx.env.find(ident) {
          Some(&parser::Macro(_, ref arg_names, ref body)) => {
            if args.len() == arg_names.len() {
              let mut map = HashMap::new();
              for (k, v) in arg_names.iter().zip(args.iter()) {
                let name = match k {
                  &parser::VarExpr(ref name) => name.clone(),
                  // not a VarExpr would be the parser going nuts
                  _ => unreachable!()
                };
                let arg = match v.resolve_data(ctx) {
                  Ok(val) => val,
                  Err(e) => return Err(e)
                };
                map.insert(name, arg);
              }
              let locals = data::Map(map);
              body.resolve(&ctx.with_locals(&locals))
            } else {
              Err(WrongNumberOfArgs)
            }
          }
          Some(_) => Err(WrongType),
          None => Err(MissingIdent(ident.clone()))
        }
      }
      parser::PropExpr(ref parent, ref prop, ref access) => {
        let prop = match *access {
          parser::Computed => match prop.resolve_data(ctx) {
            Ok(data::Str(s)) => s,
            Ok(_) => return Err(WrongType),
            Err(e) => return Err(e)
          },
          parser::Static => match *prop {
            box parser::IdentExpr(ref s) => s.clone(),
            _ => return Err(WrongType)
          }
        };

        match parent.resolve(ctx) {
          Ok(Data(data::Map(ref m))) => {
            match m.find_copy(&prop) {
              Some(d) => Ok(Data(d)),
              None => Err(MissingIndex)
            }
          },
          Ok(Entry(ref e)) => {
            match e.resolve(ctx) {
              Ok(Value(ref v)) => v.resolve(&ctx.with_index(Some(prop))),
              Ok(_) => Err(WrongType),
              Err(e) => Err(e)
            }
          },
          Ok(Value(ref v)) => {
            v.resolve(&ctx.with_index(Some(prop)))
          },
          Ok(_) => Err(WrongType),
          Err(e) => Err(e)
        }
      }
      parser::AttrExpr(ref parent, ref prop, ref access) => {
        let prop = match *access {
          parser::Computed => match prop.resolve_data(ctx) {
            Ok(data::Str(s)) => s,
            Ok(_) => return Err(WrongType),
            Err(e) => return Err(e)
          },
          parser::Static => match *prop {
            box parser::IdentExpr(ref s) => s.clone(),
            _ => return Err(WrongType)
          }
        };

        match parent.resolve(ctx) {
          Ok(Entry(parser::Entity(_, _, _, ref attrs))) => {
            for &parser::Attr(ref id, ref value, _) in attrs.iter() {
              if id.as_slice() == prop.as_slice() {
                return value.resolve(ctx)
              }
            }
            Err(MissingAttr)
          },
          Ok(_) => Err(WrongType),
          Err(e) => Err(e)
        }
      }
      ref e => fail!("{} not yet implemented", e)
    }
  }
}

#[cfg(test)]
mod tests {
  use super::{compile, Resolve, ResolveContext};
  use data::{Str, Null};

  #[test]
  fn test_compile() {
    let map = compile("<hi 'hello world'>").unwrap();
    let entity = map.get(&String::from_str("hi"));
    let data = Null;
    let ctx = ResolveContext::new(&map, &data);

    assert_eq!(entity.resolve_data(&ctx).unwrap(), Str(String::from_str("hello world")));

  }
}
<|endoftext|>


<commit_before>pub mod tag_command;
pub mod autotag;
pub mod update_tags;

pub use self::tag_command::TagCommand;
pub use self::autotag::AutoTagCommand;
pub use self::update_tags::UpdateTagsCommand;

use LalafellBot;
use database::AutotagUser;

use xivdb::error::*;

use discord::model::{UserId, LiveServer, Role, RoleId};

use std::sync::{Arc, Mutex};

lazy_static! {
  // Limbo roles are roles that may or may not be added to the Discord bot state.
  static ref LIMBO_ROLES: Mutex<Vec<Role>> = Mutex::default();
}

pub struct Tagger;

impl Tagger {
  pub fn search_tag(bot: Arc<LalafellBot>, who: UserId, on: &LiveServer, server: &str, character_name: &str, ignore_verified: bool) -> Result<Option<String>> {
    let params = &[
      ("one", "characters"),
      ("strict", "on"),
      ("server|et", server)
    ];

    let res = bot.xivdb.search(character_name, params).chain_err(|| "could not query XIVDB")?;

    let search_chars = res.characters.unwrap().results;
    if search_chars.is_empty() {
      return Ok(Some(format!("Could not find any character by the name {} on {}.", character_name, server)));
    }

    let char_id = match search_chars[0]["id"].as_u64() {
      Some(u) => u,
      None => return Err("character ID was not a u64".into())
    };

    let name = match search_chars[0]["name"].as_str() {
      Some(s) => s,
      None => return Err("character name was not a string".into())
    };

    if name.to_lowercase() != character_name.to_lowercase() {
      return Ok(Some(format!("Could not find any character by the name {} on {}.", character_name, server)));
    }

    Tagger::tag(bot, who, on, char_id, ignore_verified)
  }

  pub fn tag(bot: Arc<LalafellBot>, who: UserId, on: &LiveServer, char_id: u64, ignore_verified: bool) -> Result<Option<String>> {
    let is_verified = match bot.database.read().unwrap().autotags.users.iter().find(|u| u.user_id == who.0 && u.server_id == on.id.0) {
      Some(u) => {
        if u.verification.verified && !ignore_verified && char_id != u.character_id {
          return Ok(Some(format!("{} is verified as {} on {}, so they cannot switch to another account.", who.mention(), u.character, u.server)));
        }
        u.verification.verified
      },
      None => false
    };

    let member = bot.discord.get_member(on.id, who).chain_err(|| "could not get member for tagging")?;

    let character = bot.xivdb.character(char_id).chain_err(|| "could not look up character")?;

    bot.database.write().unwrap().autotags.update_or_add(AutotagUser::new(
      who.0,
      on.id.0,
      character.lodestone_id,
      &character.name,
      &character.server
    ));

    // Get a copy of the roles on the server.
    let mut roles = on.roles.clone();
    // Check for existing limbo roles.
    {
      let limbo = &mut *LIMBO_ROLES.lock().unwrap();
      for role in &roles {
        // If the server has updated to contain the limbo role, remove it.
        if let Some(i) = limbo.iter().position(|x| x.id == role.id) {
          limbo.remove(i);
        }
      }
    }
    // Get a copy of the limbo roles.
    let limbo = LIMBO_ROLES.lock().unwrap().clone();
    // Extend the server roles with the limbo roles.
    roles.extend(limbo);
    let mut created_roles = Vec::new();
    let mut add_roles = Vec::new();
    match roles.iter().find(|x| x.name.to_lowercase() == character.data.race.to_lowercase()) {
      Some(r) => add_roles.push(r),
      None => {
        let role = bot.discord.create_role(on.id, Some(&character.data.race.to_lowercase()), None, None, None, None).chain_err(|| "could not create role")?;
        created_roles.push(role);
      }
    }
    match roles.iter().find(|x| x.name.to_lowercase() == character.data.gender.to_lowercase()) {
      Some(r) => add_roles.push(r),
      None => {
        let role = bot.discord.create_role(on.id, Some(&character.data.gender.to_lowercase()), None, None, None, None).chain_err(|| "could not create role")?;
        created_roles.push(role);
      }
    }
    match roles.iter().find(|x| x.name.to_lowercase() == character.server.to_lowercase()) {
      Some(r) => add_roles.push(r),
      None => {
        let role = bot.discord.create_role(on.id, Some(&character.server.to_lowercase()), None, None, None, None).chain_err(|| "could not create role")?;
        created_roles.push(role);
      }
    }
    if is_verified {
      match roles.iter().find(|x| x.name.to_lowercase() == "verified") {
        Some(r) => add_roles.push(r),
        None => {
          let role = bot.discord.create_role(on.id, Some("Verified"), None, None, None, None).chain_err(|| "could not create role")?;
          created_roles.push(role);
        }
      }
    }

    // If we created any roles, the server may or may not update with them fast enough, so store a copy in the limbo
    // roles.
    {
      let mut limbo = &mut *LIMBO_ROLES.lock().unwrap();
      for created in &created_roles {
        limbo.push(created.clone());
      }
    }

    // Extend the roles to add with the roles we created.
    add_roles.extend(created_roles.iter());
    let all_group_roles: Vec<String> = bot.config.roles.groups.iter().flat_map(|x| x).map(|x| x.to_lowercase()).collect();
    let keep: Vec<&Role> = roles.iter().filter(|x| member.roles.contains(&x.id)).collect();
    let keep: Vec<&Role> = keep.into_iter().filter(|x| !all_group_roles.contains(&x.name.to_lowercase())).collect();
    let mut role_set: Vec<RoleId> = add_roles.iter().map(|r| r.id).chain(keep.into_iter().map(|r| r.id)).collect();
    role_set.sort();
    role_set.dedup();

    if !member.roles.iter().all(|r| role_set.contains(r)) {
      bot.discord.edit_member_roles(on.id, who, &role_set).chain_err(|| "could not add roles")?;
    }

    // cannot edit nickname of those with a higher role
    let nick = match member.nick {
      Some(n) => n,
      None => Default::default()
    };
    if nick != character.name {
      bot.discord.edit_member(on.id, who, |e| e.nickname(&character.name)).ok();
    }
    Ok(None)
  }
}
<commit_msg>feat: add some debug lines to tagging procedure<commit_after>pub mod tag_command;
pub mod autotag;
pub mod update_tags;

pub use self::tag_command::TagCommand;
pub use self::autotag::AutoTagCommand;
pub use self::update_tags::UpdateTagsCommand;

use LalafellBot;
use database::AutotagUser;

use xivdb::error::*;

use discord::model::{UserId, LiveServer, Role, RoleId};

use std::sync::{Arc, Mutex};

lazy_static! {
  // Limbo roles are roles that may or may not be added to the Discord bot state.
  static ref LIMBO_ROLES: Mutex<Vec<Role>> = Mutex::default();
}

pub struct Tagger;

impl Tagger {
  pub fn search_tag(bot: Arc<LalafellBot>, who: UserId, on: &LiveServer, server: &str, character_name: &str, ignore_verified: bool) -> Result<Option<String>> {
    let params = &[
      ("one", "characters"),
      ("strict", "on"),
      ("server|et", server)
    ];

    let res = bot.xivdb.search(character_name, params).chain_err(|| "could not query XIVDB")?;

    let search_chars = res.characters.unwrap().results;
    if search_chars.is_empty() {
      return Ok(Some(format!("Could not find any character by the name {} on {}.", character_name, server)));
    }

    let char_id = match search_chars[0]["id"].as_u64() {
      Some(u) => u,
      None => return Err("character ID was not a u64".into())
    };

    let name = match search_chars[0]["name"].as_str() {
      Some(s) => s,
      None => return Err("character name was not a string".into())
    };

    if name.to_lowercase() != character_name.to_lowercase() {
      return Ok(Some(format!("Could not find any character by the name {} on {}.", character_name, server)));
    }

    Tagger::tag(bot, who, on, char_id, ignore_verified)
  }

  pub fn tag(bot: Arc<LalafellBot>, who: UserId, on: &LiveServer, char_id: u64, ignore_verified: bool) -> Result<Option<String>> {
    let is_verified = match bot.database.read().unwrap().autotags.users.iter().find(|u| u.user_id == who.0 && u.server_id == on.id.0) {
      Some(u) => {
        if u.verification.verified && !ignore_verified && char_id != u.character_id {
          return Ok(Some(format!("{} is verified as {} on {}, so they cannot switch to another account.", who.mention(), u.character, u.server)));
        }
        u.verification.verified
      },
      None => false
    };

    let member = bot.discord.get_member(on.id, who).chain_err(|| "could not get member for tagging")?;

    let character = bot.xivdb.character(char_id).chain_err(|| "could not look up character")?;

    bot.database.write().unwrap().autotags.update_or_add(AutotagUser::new(
      who.0,
      on.id.0,
      character.lodestone_id,
      &character.name,
      &character.server
    ));

    // Get a copy of the roles on the server.
    let mut roles = on.roles.clone();
    // Check for existing limbo roles.
    {
      let limbo = &mut *LIMBO_ROLES.lock().unwrap();
      for role in &roles {
        // If the server has updated to contain the limbo role, remove it.
        if let Some(i) = limbo.iter().position(|x| x.id == role.id) {
          limbo.remove(i);
        }
      }
    }
    // Get a copy of the limbo roles.
    let limbo = LIMBO_ROLES.lock().unwrap().clone();
    // Extend the server roles with the limbo roles.
    roles.extend(limbo);
    let mut created_roles = Vec::new();
    let mut add_roles = Vec::new();
    match roles.iter().find(|x| x.name.to_lowercase() == character.data.race.to_lowercase()) {
      Some(r) => add_roles.push(r),
      None => {
        let role = bot.discord.create_role(on.id, Some(&character.data.race.to_lowercase()), None, None, None, None).chain_err(|| "could not create role")?;
        created_roles.push(role);
      }
    }
    match roles.iter().find(|x| x.name.to_lowercase() == character.data.gender.to_lowercase()) {
      Some(r) => add_roles.push(r),
      None => {
        let role = bot.discord.create_role(on.id, Some(&character.data.gender.to_lowercase()), None, None, None, None).chain_err(|| "could not create role")?;
        created_roles.push(role);
      }
    }
    match roles.iter().find(|x| x.name.to_lowercase() == character.server.to_lowercase()) {
      Some(r) => add_roles.push(r),
      None => {
        let role = bot.discord.create_role(on.id, Some(&character.server.to_lowercase()), None, None, None, None).chain_err(|| "could not create role")?;
        created_roles.push(role);
      }
    }
    if is_verified {
      match roles.iter().find(|x| x.name.to_lowercase() == "verified") {
        Some(r) => add_roles.push(r),
        None => {
          let role = bot.discord.create_role(on.id, Some("Verified"), None, None, None, None).chain_err(|| "could not create role")?;
          created_roles.push(role);
        }
      }
    }

    // If we created any roles, the server may or may not update with them fast enough, so store a copy in the limbo
    // roles.
    {
      let mut limbo = &mut *LIMBO_ROLES.lock().unwrap();
      for created in &created_roles {
        limbo.push(created.clone());
      }
    }

    debug!("Created the following roles:\n:{:#?}", created_roles);

    debug!("Existing roles:\n{:#?}", add_roles);

    // Extend the roles to add with the roles we created.
    add_roles.extend(created_roles.iter());
    // Get all the roles that are part of groups
    let all_group_roles: Vec<String> = bot.config.roles.groups.iter().flat_map(|x| x).map(|x| x.to_lowercase()).collect();
    // Filter all roles on the server to only the roles the member has
    let keep: Vec<&Role> = roles.iter().filter(|x| member.roles.contains(&x.id)).collect();
    // Filter all the roles the member has, keeping the ones not in a group. These roles will not be touched when
    // updating the tag.
    let keep: Vec<&Role> = keep.into_iter().filter(|x| !all_group_roles.contains(&x.name.to_lowercase())).collect();
    debug!("Roles to keep:\n{:#?}", keep);
    // Combine the two sets of roles and map them to IDs
    let mut role_set: Vec<RoleId> = add_roles.iter().map(|r| r.id).chain(keep.into_iter().map(|r| r.id)).collect();
    // Sort the IDs so we can dedup them
    role_set.sort();
    // Remove the duplicate roles, if any
    role_set.dedup();

    debug!("Final role set:\n{:#?}", role_set);

    // Only update the roles if the member is missing any or if any are different
    if !member.roles.iter().all(|r| role_set.contains(r)) {
      bot.discord.edit_member_roles(on.id, who, &role_set).chain_err(|| "could not add roles")?;
    }

    // cannot edit nickname of those with a higher role
    let nick = match member.nick {
      Some(n) => n,
      None => Default::default()
    };
    if nick != character.name {
      bot.discord.edit_member(on.id, who, |e| e.nickname(&character.name)).ok();
    }
    Ok(None)
  }
}
<|endoftext|>


<commit_before>import middle.trans;

import trans.decl_cdecl_fn;
import trans.type_names;
import trans.ModuleRef;
import trans.TypeRef;
import trans.ValueRef;

import trans.T_crate;
import trans.T_f32;
import trans.T_f64;
import trans.T_fn;
import trans.T_i8;
import trans.T_i32;
import trans.T_int;
import trans.T_opaque_chan_ptr;
import trans.T_opaque_port_ptr;
import trans.T_opaque_vec_ptr;
import trans.T_ptr;
import trans.T_size_t;
import trans.T_str;
import trans.T_taskptr;
import trans.T_tydesc;
import trans.T_void;

type upcalls = rec(
    ValueRef grow_task,
    ValueRef log_int,
    ValueRef log_float,
    ValueRef log_double,
    ValueRef log_str,
    ValueRef trace_word,
    ValueRef trace_str,
    ValueRef new_port,
    ValueRef del_port,
    ValueRef new_chan,
    ValueRef flush_chan,
    ValueRef del_chan,
    ValueRef clone_chan,
    ValueRef _yield,
    ValueRef sleep,
    ValueRef _join,
    ValueRef send,
    ValueRef recv,
    ValueRef _fail,
    ValueRef kill,
    ValueRef exit,
    ValueRef malloc,
    ValueRef free,
    ValueRef mark,
    ValueRef new_str,
    ValueRef new_vec,
    ValueRef vec_grow,
    ValueRef require_rust_sym,
    ValueRef require_c_sym,
    ValueRef get_type_desc,
    ValueRef new_task,
    ValueRef start_task,
    ValueRef new_thread,
    ValueRef start_thread
);

fn declare_upcalls(type_names tn, ModuleRef llmod) -> upcalls {
    fn decl(type_names tn, ModuleRef llmod, str name, vec[TypeRef] tys)
            -> ValueRef {
        let vec[TypeRef] arg_tys = vec(T_taskptr(tn));
        for (TypeRef t in tys) { arg_tys += vec(t); }
        auto fn_ty = T_fn(arg_tys, T_void());
        ret trans.decl_cdecl_fn(llmod, "upcall_" + name, fn_ty);
    }

    auto d = bind decl(tn, llmod, _, _);

    // FIXME: Sigh... remove this when I fix the typechecker pushdown.
    // --pcwalton
    let vec[TypeRef] empty_vec = vec();

    ret rec(
        grow_task=d("grow_task", vec(T_size_t())),
        log_int=d("log_int", vec(T_i32(), T_i32())),
        log_float=d("log_float", vec(T_i32(), T_f32())),
        log_double=d("log_double", vec(T_i32(), T_ptr(T_f64()))),
        log_str=d("log_str", vec(T_i32(), T_ptr(T_str()))),
        trace_word=d("trace_word", vec(T_int())),
        trace_str=d("trace_str", vec(T_ptr(T_i8()))),
        new_port=d("new_port", vec(T_size_t())),
        del_port=d("del_port", vec(T_opaque_port_ptr(tn))),
        new_chan=d("new_chan", vec(T_opaque_port_ptr(tn))),
        flush_chan=d("flush_chan", vec(T_opaque_chan_ptr(tn))),
        del_chan=d("del_chan", vec(T_opaque_chan_ptr(tn))),
        clone_chan=d("clone_chan", vec(T_taskptr(tn), T_opaque_chan_ptr(tn))),
        _yield=d("yield", empty_vec),
        sleep=d("sleep", vec(T_size_t())),
        _join=d("join", vec(T_taskptr(tn))),
        send=d("send", vec(T_opaque_chan_ptr(tn), T_ptr(T_i8()))),
        recv=d("recv", vec(T_ptr(T_int()), T_opaque_port_ptr(tn))),
        _fail=d("fail", vec(T_ptr(T_i8()), T_ptr(T_i8()), T_size_t())),
        kill=d("kill", vec(T_taskptr(tn))),
        exit=d("exit", empty_vec),
        malloc=d("malloc", vec(T_size_t(), T_ptr(T_tydesc(tn)))),
        free=d("free", vec(T_ptr(T_i8()), T_int())),
        mark=d("mark", vec(T_ptr(T_i8()))),
        new_str=d("new_str", vec(T_ptr(T_i8()), T_size_t())),
        new_vec=d("new_vec", vec(T_size_t(), T_ptr(T_tydesc(tn)))),
        vec_grow=d("vec_grow", vec(T_opaque_vec_ptr(), T_size_t(),
                                   T_ptr(T_int()), T_ptr(T_tydesc(tn)))),
        require_rust_sym=d("require_rust_sym",
                           vec(T_ptr(T_crate(tn)), T_size_t(), T_size_t(),
                               T_size_t(), T_ptr(T_i8()),
                               T_ptr(T_ptr(T_i8())))),
        require_c_sym=d("require_c_sym",
                        vec(T_ptr(T_crate(tn)), T_size_t(), T_size_t(),
                            T_ptr(T_i8()), T_ptr(T_i8()))),
        get_type_desc=d("get_type_desc",
                        vec(T_ptr(T_crate(tn)), T_size_t(), T_size_t(),
                            T_size_t(), T_ptr(T_ptr(T_tydesc(tn))))),
        new_task=d("new_task", vec(T_ptr(T_i8()))),
        start_task=d("start_task", vec(T_taskptr(tn), T_int(), T_int(),
                                       T_int(), T_size_t())),
        new_thread=d("new_thread", vec(T_ptr(T_i8()))),
        start_thread=d("start_thread", vec(T_taskptr(tn), T_int(), T_int(),
                                           T_int(), T_size_t()))
    );
}

<commit_msg>rustc: Give our upcall definitions the right values<commit_after>import middle.trans;

import trans.decl_cdecl_fn;
import trans.type_names;
import trans.ModuleRef;
import trans.TypeRef;
import trans.ValueRef;

import trans.T_crate;
import trans.T_f32;
import trans.T_f64;
import trans.T_fn;
import trans.T_i8;
import trans.T_i32;
import trans.T_int;
import trans.T_opaque_chan_ptr;
import trans.T_opaque_port_ptr;
import trans.T_opaque_vec_ptr;
import trans.T_ptr;
import trans.T_size_t;
import trans.T_str;
import trans.T_taskptr;
import trans.T_tydesc;
import trans.T_void;

type upcalls = rec(
    ValueRef grow_task,
    ValueRef log_int,
    ValueRef log_float,
    ValueRef log_double,
    ValueRef log_str,
    ValueRef trace_word,
    ValueRef trace_str,
    ValueRef new_port,
    ValueRef del_port,
    ValueRef new_chan,
    ValueRef flush_chan,
    ValueRef del_chan,
    ValueRef clone_chan,
    ValueRef _yield,
    ValueRef sleep,
    ValueRef _join,
    ValueRef send,
    ValueRef recv,
    ValueRef _fail,
    ValueRef kill,
    ValueRef exit,
    ValueRef malloc,
    ValueRef free,
    ValueRef mark,
    ValueRef new_str,
    ValueRef new_vec,
    ValueRef vec_grow,
    ValueRef require_rust_sym,
    ValueRef require_c_sym,
    ValueRef get_type_desc,
    ValueRef new_task,
    ValueRef start_task,
    ValueRef new_thread,
    ValueRef start_thread
);

fn declare_upcalls(type_names tn, ModuleRef llmod) -> @upcalls {
    fn decl(type_names tn, ModuleRef llmod, str name, vec[TypeRef] tys,
            TypeRef rv) -> ValueRef {
        let vec[TypeRef] arg_tys = vec(T_taskptr(tn));
        for (TypeRef t in tys) { arg_tys += vec(t); }
        auto fn_ty = T_fn(arg_tys, rv);
        ret trans.decl_cdecl_fn(llmod, "upcall_" + name, fn_ty);
    }

    auto dv = bind decl(tn, llmod, _, _, T_void());
    auto d = bind decl(tn, llmod, _, _, _);

    // FIXME: Sigh... remove this when I fix the typechecker pushdown.
    // --pcwalton
    let vec[TypeRef] empty_vec = vec();

    ret @rec(
        grow_task=dv("grow_task", vec(T_size_t())),
        log_int=dv("log_int", vec(T_i32(), T_i32())),
        log_float=dv("log_float", vec(T_i32(), T_f32())),
        log_double=dv("log_double", vec(T_i32(), T_ptr(T_f64()))),
        log_str=dv("log_str", vec(T_i32(), T_ptr(T_str()))),
        trace_word=dv("trace_word", vec(T_int())),
        trace_str=dv("trace_str", vec(T_ptr(T_i8()))),
        new_port=d("new_port", vec(T_size_t()), T_opaque_port_ptr(tn)),
        del_port=dv("del_port", vec(T_opaque_port_ptr(tn))),
        new_chan=dv("new_chan", vec(T_opaque_port_ptr(tn))),
        flush_chan=dv("flush_chan", vec(T_opaque_chan_ptr(tn))),
        del_chan=dv("del_chan", vec(T_opaque_chan_ptr(tn))),
        clone_chan=d("clone_chan", vec(T_taskptr(tn), T_opaque_chan_ptr(tn)),
                     T_opaque_chan_ptr(tn)),
        _yield=dv("yield", empty_vec),
        sleep=dv("sleep", vec(T_size_t())),
        _join=dv("join", vec(T_taskptr(tn))),
        send=dv("send", vec(T_opaque_chan_ptr(tn), T_ptr(T_i8()))),
        recv=dv("recv", vec(T_ptr(T_int()), T_opaque_port_ptr(tn))),
        _fail=dv("fail", vec(T_ptr(T_i8()), T_ptr(T_i8()), T_size_t())),
        kill=dv("kill", vec(T_taskptr(tn))),
        exit=dv("exit", empty_vec),
        malloc=d("malloc", vec(T_size_t(), T_ptr(T_tydesc(tn))),
                               T_ptr(T_i8())),
        free=dv("free", vec(T_ptr(T_i8()), T_int())),
        mark=d("mark", vec(T_ptr(T_i8())), T_int()),
        new_str=d("new_str", vec(T_ptr(T_i8()), T_size_t()), T_ptr(T_str())),
        new_vec=d("new_vec", vec(T_size_t(), T_ptr(T_tydesc(tn))),
                                 T_opaque_vec_ptr()),
        vec_grow=d("vec_grow", vec(T_opaque_vec_ptr(), T_size_t(),
                                   T_ptr(T_int()), T_ptr(T_tydesc(tn))),
                   T_opaque_vec_ptr()),
        require_rust_sym=d("require_rust_sym",
                           vec(T_ptr(T_crate(tn)), T_size_t(), T_size_t(),
                               T_size_t(), T_ptr(T_i8()),
                               T_ptr(T_ptr(T_i8()))),
                           T_int()),
        require_c_sym=d("require_c_sym",
                        vec(T_ptr(T_crate(tn)), T_size_t(), T_size_t(),
                            T_ptr(T_i8()), T_ptr(T_i8())),
                        T_int()),
        get_type_desc=d("get_type_desc",
                        vec(T_ptr(T_crate(tn)), T_size_t(), T_size_t(),
                            T_size_t(), T_ptr(T_ptr(T_tydesc(tn)))),
                        T_ptr(T_tydesc(tn))),
        new_task=d("new_task", vec(T_ptr(T_i8())), T_taskptr(tn)),
        start_task=d("start_task", vec(T_taskptr(tn), T_int(), T_int(),
                                       T_int(), T_size_t()),
                     T_taskptr(tn)),
        new_thread=d("new_thread", vec(T_ptr(T_i8())), T_taskptr(tn)),
        start_thread=d("start_thread", vec(T_taskptr(tn), T_int(), T_int(),
                                           T_int(), T_size_t()),
                       T_taskptr(tn))
    );
}

<|endoftext|>


<commit_before>use std::old_io::File;
use std::old_io::SeekStyle::SeekSet;
use std::old_io::IoErrorKind::EndOfFile;
use elem::{PerElem, Molecule};
use error::{CTError, CTResult};
use error::CTErrorKind::{SyntaxError, DatabaseError};

macro_rules! read_err (
    () => (Err(CTError {
        kind: DatabaseError,
        desc: "Error reading the database".to_string(),
        pos: None
    }));
);

#[derive(Show, PartialEq)]
pub struct ElemData {
    pub short_name: String,
    pub long_name: String,
    pub mass: f64,
    pub atomic_num: u16,
}

pub struct ElemDatabase {
    db: File,
}

impl ElemDatabase {
    pub fn open(path: &Path) -> CTResult<ElemDatabase> {
        match File::open(path) {
            Ok(db_file) => Ok(ElemDatabase { db: db_file }),
            Err(_) => Err(CTError {
                kind: DatabaseError,
                desc: format!("Could not open database file. Expected at: {:?}",
                              path.as_str().unwrap_or("same directory as the program")),
                pos: None,
            }),
        }
    }

    pub fn get_single_data(&mut self, elem: &PerElem) -> CTResult<ElemData> {
        // we return to the beginning of the underlying file, since the data
        // might lie on a line we have previously read past
        self.db.seek(0, SeekSet).ok().expect("Internal error reading database");

        loop {
            // TODO: make it so this function returns the 'not found' error
            let line = try!(self.read_line(elem));
            if line.starts_with(elem.name.as_slice()) {
                return decode_line(&line);
            }
        }
    }

    pub fn get_data(&mut self, elems: &Molecule) -> CTResult<Vec<ElemData>> {
        let mut out = Vec::new();
        for elem in elems.iter() {
            match self.get_single_data(elem) {
                Ok(data) => out.push(data),
                Err(e) => return Err(e),
            }
        }
        Ok(out)
    }

    fn read_line(&mut self, elem: &PerElem) -> CTResult<String> {
        // we know that no line in the database is more than 30 characters long
        let mut buf = Vec::with_capacity(30);
        loop {
            match self.db.read_byte() {
                Ok(b) if b == b'\n' => break,
                Ok(b) => buf.push(b),
                Err(ref e) if e.kind == EndOfFile => return Err(CTError {
                    kind: SyntaxError,
                    desc: format!("Could not find element: {:?}", elem.name),
                    pos: Some((elem.pos, elem.len)),
                }),
                Err(_) => return read_err!()
            }
        }
        String::from_utf8(buf).or_else(|_| read_err!())
    }
}

fn decode_line(line: &String) -> CTResult<ElemData> {
    let data: Vec<&str> = line.trim().split(';').collect();
    if data.len() < 4 {
        Err(CTError {
            kind: DatabaseError,
            desc: "Missing field in database".to_string(),
            pos: None
        })
    } else {
        let mass = data[1].parse::<f64>();
        let atomic_num = data[3].parse::<u16>();
        if let (Some(m), Some(an)) = (mass, atomic_num) {
            Ok(ElemData {
                short_name: data[0].to_string(),
                long_name: data[2].to_string(),
                mass: m,
                atomic_num: an,
            })
        } else {
            Err(CTError {
                kind: DatabaseError,
                desc: "Field in database corrupted".to_string(),
                pos: None,
            })
        }
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use std::old_io::File;
    use std::old_io::fs;
    use elem::PerElem;

    fn make_dummy_db(name: &str, contents: &str) -> ElemDatabase {
        if let Err(e) = File::create(&Path::new(name)).and_then(|mut f| f.write_str(contents)) {
            // if we can't make the database we can't test, so just abort here
            panic!("Could not create dummy database: {:?}", e.desc);
        }
        ElemDatabase::open(&Path::new(name)).unwrap()
    }

    fn remove_dummy_db(name: &str) {
        if let Err(e) = fs::unlink(&Path::new(name)) {
            // if we can't remove the database something is wrong, and we abort the test
            panic!("Could not remove dummy database: {:?}", e.desc);
        }
    }

    #[test]
    fn multiple_elems() {
        let db_name = "multiple_elems_db";
        let mut db = make_dummy_db(db_name,
            "A;1;Abba;2\n\
            B;3;Beta;4\n");
        let raw_result = db.get_data(&vec!(
            PerElem { name: "B".to_string(), coef: 1, pos: 0, len: 1 },
            PerElem { name: "A".to_string(), coef: 1, pos: 1, len: 1 }
        ));
        let expected = vec!(
            ElemData {
                short_name: "B".to_string(),
                long_name: "Beta".to_string(),
                mass: 3.0,
                atomic_num: 4,
            },
            ElemData {
                short_name: "A".to_string(),
                long_name: "Abba".to_string(),
                mass: 1.0,
                atomic_num: 2,
            }
        );
        remove_dummy_db(db_name);
        assert_eq!(Ok(expected), raw_result);
    }

    #[test]
    fn find_elem() {
        let db_name = "find_elem_db";
        let mut db = make_dummy_db(db_name,
            "A;0;Abba;0\n\
            B;123.456789;Beta;12\n\
            C;0;Coop;0\n");
        let raw_result = db.get_single_data(
            &PerElem { name: "B".to_string(), coef: 1, pos: 0, len: 2 }
        );
        let expected = ElemData {
            short_name: "B".to_string(),
            long_name: "Beta".to_string(),
            mass: 123.456789,
            atomic_num: 12,
        };
        remove_dummy_db(db_name);
        assert_eq!(Ok(expected), raw_result);
    }

    #[test]
    fn missing_elem() {
        let db_name = "missing_elem_db";
        let mut db = make_dummy_db(db_name, "A;123.456789;Abba;12\n");
        let result = db.get_single_data(
            &PerElem { name: "B".to_string(), coef: 1, pos: 0, len: 2 }
        );
        remove_dummy_db(db_name);
        assert!(result.is_err());
    }

    #[test]
    fn missing_field() {
        let db_name = "missing_field_db";
        let mut db = make_dummy_db(db_name, "A;");
        let result = db.get_single_data(
            &PerElem { name: "A".to_string(), coef: 1, pos: 0, len: 2 }
        );
        remove_dummy_db(db_name);
        assert!(result.is_err());
    }

    #[test]
    fn field_corrupted() {
        let db_name = "field_corrupted_db";
        let mut db = make_dummy_db(db_name, "A;not a number;Abba;12\n");
        let result = db.get_single_data(
            &PerElem { name: "A".to_string(), coef: 1, pos: 0, len: 2 }
        );
        remove_dummy_db(db_name);
        assert!(result.is_err());
    }
}<commit_msg>Rework the database data fetcher<commit_after>use std::old_io::File;
use std::old_io::SeekStyle::SeekSet;
use std::old_io::IoErrorKind::EndOfFile;
use elem::{PerElem, Molecule};
use error::{CTError, CTResult};
use error::CTErrorKind::{SyntaxError, DatabaseError};

macro_rules! read_err (
    () => (Err(CTError {
        kind: DatabaseError,
        desc: "Error reading the database".to_string(),
        pos: None
    }));
);

#[derive(Show, PartialEq)]
pub struct ElemData {
    pub short_name: String,
    pub long_name: String,
    pub mass: f64,
    pub atomic_num: u16,
}

pub struct ElemDatabase {
    db: File,
}

impl ElemDatabase {
    pub fn open(path: &Path) -> CTResult<ElemDatabase> {
        match File::open(path) {
            Ok(db_file) => Ok(ElemDatabase { db: db_file }),
            Err(_) => Err(CTError {
                kind: DatabaseError,
                desc: format!("Could not open database file. Expected at: {:?}",
                              path.as_str().unwrap_or("same directory as the program")),
                pos: None,
            }),
        }
    }

    pub fn get_single_data(&mut self, elem: &PerElem) -> CTResult<ElemData> {
        // since the elements should be sorted before we get their data from the database
        // we should never have to seek back to the beginning of the file
        if let Ok(data) = self.do_data_search(elem) {
            Ok(data)
        } else {
            // but in case they weren't, we return to the beginning of the underlying file, since
            // the data might lie on a line we have previously read past
            self.db.seek(0, SeekSet).ok().expect("Internal error reading database");
            self.do_data_search(elem)
        }
    }

    pub fn get_data(&mut self, elems: &Molecule) -> CTResult<Vec<ElemData>> {
        let mut out = Vec::new();
        for elem in elems.iter() {
            match self.get_single_data(elem) {
                Ok(data) => out.push(data),
                Err(e) => return Err(e),
            }
        }
        Ok(out)
    }

    fn do_data_search(&mut self, elem: &PerElem) -> CTResult<ElemData> {
        loop {
            // TODO: make it so this function returns the 'not found' error
            let line = try!(self.read_line(elem));
            if line.starts_with(elem.name.as_slice()) {
                return decode_line(&line);
            }
        }
    }

    fn read_line(&mut self, elem: &PerElem) -> CTResult<String> {
        // we know that no line in the database is more than 30 characters long
        let mut buf = Vec::with_capacity(30);
        loop {
            match self.db.read_byte() {
                Ok(b) if b == b'\n' => break,
                Ok(b) => buf.push(b),
                Err(ref e) if e.kind == EndOfFile => return Err(CTError {
                    kind: SyntaxError,
                    desc: format!("Could not find element: {:?}", elem.name),
                    pos: Some((elem.pos, elem.len)),
                }),
                Err(_) => return read_err!()
            }
        }
        String::from_utf8(buf).or_else(|_| read_err!())
    }
}

fn decode_line(line: &String) -> CTResult<ElemData> {
    let data: Vec<&str> = line.trim().split(';').collect();
    if data.len() < 4 {
        Err(CTError {
            kind: DatabaseError,
            desc: "Missing field in database".to_string(),
            pos: None
        })
    } else {
        let mass = data[1].parse::<f64>();
        let atomic_num = data[3].parse::<u16>();
        if let (Some(m), Some(an)) = (mass, atomic_num) {
            Ok(ElemData {
                short_name: data[0].to_string(),
                long_name: data[2].to_string(),
                mass: m,
                atomic_num: an,
            })
        } else {
            Err(CTError {
                kind: DatabaseError,
                desc: "Field in database corrupted".to_string(),
                pos: None,
            })
        }
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use std::old_io::File;
    use std::old_io::fs;
    use elem::PerElem;

    fn make_dummy_db(name: &str, contents: &str) -> ElemDatabase {
        if let Err(e) = File::create(&Path::new(name)).and_then(|mut f| f.write_str(contents)) {
            // if we can't make the database we can't test, so just abort here
            panic!("Could not create dummy database: {:?}", e.desc);
        }
        ElemDatabase::open(&Path::new(name)).unwrap()
    }

    fn remove_dummy_db(name: &str) {
        if let Err(e) = fs::unlink(&Path::new(name)) {
            // if we can't remove the database something is wrong, and we abort the test
            panic!("Could not remove dummy database: {:?}", e.desc);
        }
    }

    #[test]
    fn multiple_elems() {
        let db_name = "multiple_elems_db";
        let mut db = make_dummy_db(db_name,
            "A;1;Abba;2\n\
            B;3;Beta;4\n");
        let raw_result = db.get_data(&vec!(
            PerElem { name: "B".to_string(), coef: 1, pos: 0, len: 1 },
            PerElem { name: "A".to_string(), coef: 1, pos: 1, len: 1 }
        ));
        let expected = vec!(
            ElemData {
                short_name: "B".to_string(),
                long_name: "Beta".to_string(),
                mass: 3.0,
                atomic_num: 4,
            },
            ElemData {
                short_name: "A".to_string(),
                long_name: "Abba".to_string(),
                mass: 1.0,
                atomic_num: 2,
            }
        );
        remove_dummy_db(db_name);
        assert_eq!(Ok(expected), raw_result);
    }

    #[test]
    fn find_elem() {
        let db_name = "find_elem_db";
        let mut db = make_dummy_db(db_name,
            "A;0;Abba;0\n\
            B;123.456789;Beta;12\n\
            C;0;Coop;0\n");
        let raw_result = db.get_single_data(
            &PerElem { name: "B".to_string(), coef: 1, pos: 0, len: 2 }
        );
        let expected = ElemData {
            short_name: "B".to_string(),
            long_name: "Beta".to_string(),
            mass: 123.456789,
            atomic_num: 12,
        };
        remove_dummy_db(db_name);
        assert_eq!(Ok(expected), raw_result);
    }

    #[test]
    fn missing_elem() {
        let db_name = "missing_elem_db";
        let mut db = make_dummy_db(db_name, "A;123.456789;Abba;12\n");
        let result = db.get_single_data(
            &PerElem { name: "B".to_string(), coef: 1, pos: 0, len: 2 }
        );
        remove_dummy_db(db_name);
        assert!(result.is_err());
    }

    #[test]
    fn missing_field() {
        let db_name = "missing_field_db";
        let mut db = make_dummy_db(db_name, "A;");
        let result = db.get_single_data(
            &PerElem { name: "A".to_string(), coef: 1, pos: 0, len: 2 }
        );
        remove_dummy_db(db_name);
        assert!(result.is_err());
    }

    #[test]
    fn field_corrupted() {
        let db_name = "field_corrupted_db";
        let mut db = make_dummy_db(db_name, "A;not a number;Abba;12\n");
        let result = db.get_single_data(
            &PerElem { name: "A".to_string(), coef: 1, pos: 0, len: 2 }
        );
        remove_dummy_db(db_name);
        assert!(result.is_err());
    }
}<|endoftext|>


<commit_before>use super::*;

pub struct Iter<'a> {
    pub(super) log: &'a Log,
    pub(super) segment_iter: Box<Iterator<Item = (Lsn, LogID)>>,
    pub(super) segment_base: Option<LogID>,
    pub(super) max_lsn: Lsn,
    pub(super) cur_lsn: Lsn,
    pub(super) trailer: Option<Lsn>,
}

impl<'a> Iterator for Iter<'a> {
    type Item = (Lsn, LogID, Vec<u8>);

    fn next(&mut self) -> Option<Self::Item> {
        // If segment is None, get next on segment_iter, panic
        // if we can't read something we expect to be able to,
        // return None if there are no more remaining segments.
        let segment_len = self.log.config().get_io_buf_size();
        loop {
            if self.cur_lsn >= self.max_lsn {
                // all done
                return None;
            }
            if self.segment_base.is_none() ||
                self.segment_base.unwrap() + segment_len as LogID <=
                    self.cur_lsn + SEG_TRAILER_LEN as LogID
            {
                if let Some((next_lsn, next_lid)) = self.segment_iter.next() {
                    self.read_segment(next_lsn, next_lid).unwrap();
                } else {
                    return None;
                }
            }

            let lid = self.segment_base.unwrap() +
                (self.cur_lsn % self.log.config().get_io_buf_size() as LogID);

            println!("max is {}", self.max_lsn);
            println!("lid is {}", lid);
            match self.log.read(lid) {
                Ok(LogRead::Flush(lsn, buf, on_disk_len)) => {
                    println!("{}", on_disk_len);
                    println!("cur_lsn before: {}", self.cur_lsn);
                    self.cur_lsn += (MSG_HEADER_LEN + on_disk_len) as LogID;
                    println!("cur_lsn after: {}", self.cur_lsn);
                    return Some((lsn, lid, buf));
                }
                Ok(LogRead::Zeroed(on_disk_len)) => {
                    self.cur_lsn += on_disk_len as LogID;
                }
                _ => {
                    if self.trailer.is_none() {
                        // This segment was torn, nothing left to read.
                        return None;
                    }
                    self.segment_base.take();
                    self.trailer.take();
                    continue;
                }
            }
        }
    }
}

impl<'a> Iter<'a> {
    /// read a segment of log messages. Only call after
    /// pausing segment rewriting on the segment accountant!
    fn read_segment(&mut self, lsn: Lsn, offset: LogID) -> std::io::Result<()> {
        let segment_len = self.log.config().get_io_buf_size();
        // println!("reading segment {}", offset);

        let cached_f = self.log.config().cached_file();
        let mut f = cached_f.borrow_mut();

        let segment_header = f.read_segment_header(offset)?;
        // println!("read sh {:?} at {}", sh, offset);
        assert_eq!(offset % segment_len as Lsn, 0);
        assert_eq!(segment_header.lsn % segment_len as Lsn, 0);
        assert_eq!(segment_header.lsn, lsn);
        assert!(segment_header.lsn + self.log.config().get_io_buf_size() as LogID >= self.cur_lsn);
        // TODO turn those asserts into returned Errors? Torn pages or invariant?

        let segment_trailer = f.read_segment_trailer(offset);

        let trailer_lsn = segment_trailer.ok().and_then(|st| if st.ok &&
            st.lsn == segment_header.lsn
        {
            Some(st.lsn)
        } else {
            None
        });

        self.trailer = trailer_lsn;
        self.cur_lsn = segment_header.lsn + SEG_HEADER_LEN as Lsn;
        self.segment_base = Some(offset);

        Ok(())
    }
}
<commit_msg>fix bug in new reader implementation<commit_after>use super::*;

pub struct Iter<'a> {
    pub(super) log: &'a Log,
    pub(super) segment_iter: Box<Iterator<Item = (Lsn, LogID)>>,
    pub(super) segment_base: Option<LogID>,
    pub(super) max_lsn: Lsn,
    pub(super) cur_lsn: Lsn,
    pub(super) trailer: Option<Lsn>,
}

impl<'a> Iterator for Iter<'a> {
    type Item = (Lsn, LogID, Vec<u8>);

    fn next(&mut self) -> Option<Self::Item> {
        // If segment is None, get next on segment_iter, panic
        // if we can't read something we expect to be able to,
        // return None if there are no more remaining segments.
        let segment_len = self.log.config().get_io_buf_size();
        loop {
            println!("cur is {}", self.cur_lsn);
            println!("max is {}", self.max_lsn);
            if self.segment_base.is_none() ||
                self.segment_base.unwrap() + segment_len as LogID <=
                    self.cur_lsn + SEG_TRAILER_LEN as LogID
            {
                if let Some((next_lsn, next_lid)) = self.segment_iter.next() {
                    self.read_segment(next_lsn, next_lid).unwrap();
                } else {
                    return None;
                }
            }

            if self.cur_lsn >= self.max_lsn {
                // all done
                return None;
            }

            let lid = self.segment_base.unwrap() +
                (self.cur_lsn % self.log.config().get_io_buf_size() as LogID);

            println!("lid is {}", lid);
            match self.log.read(lid) {
                Ok(LogRead::Flush(lsn, buf, on_disk_len)) => {
                    println!("{}", on_disk_len);
                    println!("cur_lsn before: {}", self.cur_lsn);
                    self.cur_lsn += (MSG_HEADER_LEN + on_disk_len) as LogID;
                    println!("cur_lsn after: {}", self.cur_lsn);
                    return Some((lsn, lid, buf));
                }
                Ok(LogRead::Zeroed(on_disk_len)) => {
                    self.cur_lsn += on_disk_len as LogID;
                }
                _ => {
                    if self.trailer.is_none() {
                        // This segment was torn, nothing left to read.
                        return None;
                    }
                    self.segment_base.take();
                    self.trailer.take();
                    continue;
                }
            }
        }
    }
}

impl<'a> Iter<'a> {
    /// read a segment of log messages. Only call after
    /// pausing segment rewriting on the segment accountant!
    fn read_segment(&mut self, lsn: Lsn, offset: LogID) -> std::io::Result<()> {
        let segment_len = self.log.config().get_io_buf_size();
        // println!("reading segment {}", offset);

        let cached_f = self.log.config().cached_file();
        let mut f = cached_f.borrow_mut();

        let segment_header = f.read_segment_header(offset)?;
        // println!("read sh {:?} at {}", sh, offset);
        assert_eq!(offset % segment_len as Lsn, 0);
        assert_eq!(segment_header.lsn % segment_len as Lsn, 0);
        assert_eq!(segment_header.lsn, lsn);
        assert!(segment_header.lsn + self.log.config().get_io_buf_size() as LogID >= self.cur_lsn);
        // TODO turn those asserts into returned Errors? Torn pages or invariant?

        let segment_trailer = f.read_segment_trailer(offset);

        let trailer_lsn = segment_trailer.ok().and_then(|st| if st.ok &&
            st.lsn == segment_header.lsn
        {
            Some(st.lsn)
        } else {
            None
        });

        self.trailer = trailer_lsn;
        self.cur_lsn = segment_header.lsn + SEG_HEADER_LEN as Lsn;
        self.segment_base = Some(offset);

        Ok(())
    }
}
<|endoftext|>