<commit_before>use std::io;

#[derive(Copy, Clone, Debug)]
enum Player {
    YeonSeung,
    JunSeok,
}

impl Player {
    fn opponent(&self) -> Player {
        match *self {
            Player::YeonSeung => Player::JunSeok,
            Player::JunSeok => Player::YeonSeung,
        }
    }
}

#[derive(Copy, Clone, Eq, PartialEq, Debug)]
struct Coordinate {
    row: usize,
    col: usize,
}

impl Coordinate {
    fn move_in(&self, dir: Direction, delta: usize) -> Coordinate {
        match dir {
            Direction::Up => Coordinate{row: self.row - delta, col: self.col},
            Direction::Down => Coordinate{row: self.row + delta, col: self.col},
            Direction::Left => Coordinate{row: self.row, col: self.col - delta},
            Direction::Right => Coordinate{row: self.row, col: self.col + delta},
        }
    }
    fn induces_board_type(&self) -> bool {
        *self == Coordinate{row: 1, col: 1} ||
        *self == Coordinate{row: 2, col: 1} ||
        *self == Coordinate{row: 2, col: 0} ||
        *self == Coordinate{row: 3, col: 0}
    }
}

#[derive(Copy, Clone)]
enum Direction {
    Up,
    Down,
    Left,
    Right,
}
const POSSIBLE_DIRECTIONS: [Direction; 4] = [
    Direction::Up,
    Direction::Down,
    Direction::Left,
    Direction::Right,
];

#[derive(Copy, Clone, Debug)]
enum MoveType {
    Single,
    OneUp,
    OneDown,
    OneLeft,
    OneRight,
    TwoUp,
    TwoDown,
    TwoLeft,
    TwoRight,
    UpAndDown,
    LeftAndRight,
}
const POSSIBLE_MOVE_TYPES: [MoveType; 11] = [
    MoveType::Single,
    MoveType::OneUp,
    MoveType::OneDown,
    MoveType::OneLeft,
    MoveType::OneRight,
    MoveType::TwoUp,
    MoveType::TwoDown,
    MoveType::TwoLeft,
    MoveType::TwoRight,
    MoveType::UpAndDown,
    MoveType::LeftAndRight,
];

#[derive(Copy, Clone, Debug)]
struct Move {
    coord: Coordinate,
    move_type: MoveType,
    board_type: Option<BoardType>,
}
impl Move {
    fn coords(&self) -> Vec<Coordinate> {
        match self.move_type {
            MoveType::Single => vec![],
            MoveType::OneUp => vec![self.coord.move_in(Direction::Up, 1)],
            MoveType::OneDown => vec![self.coord.move_in(Direction::Down, 1)],
            MoveType::OneLeft => vec![self.coord.move_in(Direction::Left, 1)],
            MoveType::OneRight => vec![self.coord.move_in(Direction::Right, 1)],
            MoveType::TwoUp => vec![self.coord.move_in(Direction::Up, 1), self.coord.move_in(Direction::Up, 2)],
            MoveType::TwoDown => vec![self.coord.move_in(Direction::Down, 1), self.coord.move_in(Direction::Down, 2)],
            MoveType::TwoLeft => vec![self.coord.move_in(Direction::Left, 1), self.coord.move_in(Direction::Left, 2)],
            MoveType::TwoRight => vec![self.coord.move_in(Direction::Right, 1), self.coord.move_in(Direction::Right, 2)],
            MoveType::UpAndDown => vec![self.coord.move_in(Direction::Up, 1), self.coord.move_in(Direction::Down, 1)],
            MoveType::LeftAndRight => vec![self.coord.move_in(Direction::Left, 1), self.coord.move_in(Direction::Right, 1)],
        }
    }
}

const NUM_COLS: usize = 5;
const NUM_ROWS: usize = 4;

// Hacks for the three states of the lower-left of the board in JunSeok vs YeonSeung game
#[derive(Copy, Clone, Debug)]
enum BoardType {
    Left,
    Middle,
    Right,
}
const POSSIBLE_BOARD_TYPES: [BoardType; 3] = [
    BoardType::Left,
    BoardType::Middle,
    BoardType::Right,
];

type BoardArray = [[bool; NUM_COLS]; NUM_ROWS];
struct Board {
    board: BoardArray,
    board_type: Option<BoardType>,
}

impl Board {
    fn make_move(&mut self, m: Move) {
        match m.board_type {
            Some(_) => self.board_type = m.board_type,
            None => (),
        }
        self.set_squares(m, true)
    }

    fn undo_move(&mut self, m: Move, bt: Option<BoardType>) {
        self.board_type = bt;
        self.set_squares(m, false)
    }

    fn set_squares(&mut self, m: Move, mode: bool) {
        self.board[m.coord.row][m.coord.col] = mode;
        for other_space in m.coords().iter() {
            self.board[other_space.row][other_space.col] = mode;
        }
    }

    fn occupied(&self, c: Coordinate) -> bool {
        self.in_bounds(c) && self.board[c.row][c.col]
    }

    fn in_bounds(&self, c: Coordinate) -> bool {
        // >= 0 is always true due to type limits.
        // c.row >= 0 && c.row < NUM_ROWS && c.col >= 0 && c.col < NUM_COLS
        c.row < NUM_ROWS && c.col < NUM_COLS
    }

    // Assuming that m is a move with an unoccupied coordinate!
    // This doesn't check whether the target squares are occupied.
    // Advantage: It's quicker. Disadvantage: It allows some illegal moves.
    fn move_in_bounds(&self, m: Move) -> bool {
        match m.move_type {
            MoveType::Single => true,
            MoveType::OneUp => m.coord.row >= 1,
            MoveType::OneDown => m.coord.row < NUM_ROWS - 1,
            MoveType::OneLeft => m.coord.col >= 1,
            MoveType::OneRight => m.coord.col < NUM_COLS - 1,
            MoveType::TwoUp => m.coord.row >= 2,
            MoveType::TwoDown => m.coord.row < NUM_ROWS - 2,
            MoveType::TwoLeft => m.coord.col >= 2,
            MoveType::TwoRight => m.coord.col < NUM_COLS - 2,
            MoveType::UpAndDown => m.coord.row >= 1 && m.coord.row < NUM_ROWS - 1,
            MoveType::LeftAndRight => m.coord.col >= 1 && m.coord.col < NUM_COLS - 1,
        }
    }

    // Unfortunately I kind of have to hard-code this.
    fn makes_unsolvable(c: Coordinate, b: Option<BoardType>) -> bool {
        match b {
            Some(BoardType::Left)   => c == Coordinate{row: 2, col: 1} || c == Coordinate{row: 1, col: 1},
            Some(BoardType::Middle) => c == Coordinate{row: 3, col: 0} || c == Coordinate{row: 1, col: 1},
            Some(BoardType::Right)  => c == Coordinate{row: 3, col: 0} || c == Coordinate{row: 2, col: 0},
            None => false,
        }
    }

    fn frontier(&self) -> Vec<Coordinate> {
        let mut results = Vec::new();
        for row in 0..NUM_ROWS {
            for col in 0..NUM_COLS {
                let coord = Coordinate{row: row, col: col};
                if self.occupied(coord) || Board::makes_unsolvable(coord, self.board_type) {
                    continue;
                }
                let mut have_neighbor = false;
                for dir in POSSIBLE_DIRECTIONS.iter() {
                    let new_coord = coord.move_in(*dir, 1);
                    if self.occupied(new_coord) {
                        have_neighbor = true;
                        break;
                    }
                }
                if have_neighbor {
                    results.push(coord);
                }
            }
        }
        results
    }

    fn legal_moves(&self) -> Vec<Move> {
        let mut results = Vec::new();
        for frontier_space in self.frontier().iter() {
            for move_type in POSSIBLE_MOVE_TYPES.iter() {
                let mov = Move{coord: *frontier_space, move_type: *move_type, board_type: None};
                if !self.move_in_bounds(mov) {
                    continue;
                }
                let mut other_space_taken = false;
                let mut induces_board_type = frontier_space.induces_board_type();
                for other_space in mov.coords().iter() {
                    if other_space.induces_board_type() {
                        induces_board_type = true;
                    }
                    if self.occupied(*other_space) || Board::makes_unsolvable(*other_space, self.board_type) {
                        other_space_taken = true;
                        break;
                    }
                }
                if !other_space_taken {
                    if induces_board_type && self.board_type.is_none() {
                        for board_type in POSSIBLE_BOARD_TYPES.iter() {
                            if Board::makes_unsolvable(*frontier_space, Some(*board_type)) {
                                continue;
                            }
                            let mut other_spaces_ok = true;
                            for other_space in mov.coords().iter() {
                                if Board::makes_unsolvable(*other_space, Some(*board_type)) {
                                    other_spaces_ok = false;
                                    break;
                                }
                            }
                            if other_spaces_ok {
                                results.push(Move{coord: mov.coord, move_type: mov.move_type, board_type: Some(*board_type)});
                            }
                        }

                    } else {
                        results.push(mov);
                    }
                }
            }
        }
        results
    }

    fn print(&self) {
        for row in self.board.iter() {
            for col in row.iter() {
                print!("{: >5} ", col);
            }
            println!("");
        }
        println!("{:?}", self.board_type);
    }
}

#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug)]
enum GameResult {
    PlaceholderJunSeok,
    JunSeokWin,
    YeonSeungWin,
    PlaceholderYeonSeung,
}

fn minimax_alpha_beta(player: Player, board: &mut Board, initial_alpha: GameResult, initial_beta: GameResult) -> (GameResult, Option<Move>) {
    let moves = board.legal_moves();
    // There are no more moves, which means my opponent completed the railroad.
    // So I lose.
    if moves.is_empty() {
        return match player {
            Player::YeonSeung => (GameResult::JunSeokWin, None),
            Player::JunSeok => (GameResult::YeonSeungWin, None),
        }
    }
    let mut best = match player {
        Player::YeonSeung => initial_alpha,
        Player::JunSeok => initial_beta,
    };
    let mut alpha = initial_alpha;
    let mut beta = initial_beta;
    let mut best_move = None;

    for possible_move in moves.iter() {
        let bt = board.board_type;
        board.make_move(*possible_move);
        let (reply, _) = minimax_alpha_beta(player.opponent(), board, alpha, beta);
        board.undo_move(*possible_move, bt);

        match player {
            Player::YeonSeung => {
                if reply > best {
                    best = reply;
                    alpha = reply;
                    best_move = Some(*possible_move);
                }
                if best >= GameResult::YeonSeungWin {
                    return (best, best_move);
                }
            },
            Player::JunSeok => {
                if reply < best {
                    best = reply;
                    beta = reply;
                    best_move = Some(*possible_move);
                }
                if best <= GameResult::JunSeokWin {
                    return (best, best_move);
                }
            },
        }

        if alpha >= beta {
            return (best, best_move);
        }
    }

    (best, best_move)
}

fn main() {
    let mut starting_board = Board{
        board: [
            [false,  true,  true,  true, false],
            [false, false, false,  true, false],
            [false, false, false,  true, false],
            [false, false, false, false, false],
        ],
        board_type: None,
    };
    let starting_player = Player::YeonSeung;

    let all_responses = false;
    let best_move = false;
    let legal_moves = false;
    let interactive = true;

    if legal_moves {
        for legal_move in starting_board.legal_moves().iter() {
            println!("{:?}", legal_move);
        }
    }

    if best_move {
        let (result, best_move) = minimax_alpha_beta(starting_player, &mut starting_board, GameResult::PlaceholderJunSeok, GameResult::PlaceholderYeonSeung);
        println!("{:?}", result);
        println!("{:?}", best_move);
        match best_move {
            Some(x) => {
                starting_board.make_move(x);
                starting_board.print();
            },
            None => (),
        }
    }

    if all_responses {
        for legal_move in starting_board.legal_moves().iter() {
            print!("{:?} does: {:?}, ", starting_player, legal_move);
            let bt = starting_board.board_type;
            starting_board.make_move(*legal_move);
            let (result, best_move) = minimax_alpha_beta(starting_player.opponent(), &mut starting_board, GameResult::PlaceholderJunSeok, GameResult::PlaceholderYeonSeung);
            match best_move {
                Some(x) => {
                    println!("{:?} does: {:?}, {:?}", starting_player.opponent(), x, result);
                    starting_board.make_move(x);
                    starting_board.print();
                    starting_board.undo_move(x, bt);
                }
                None => (),
            }
            starting_board.undo_move(*legal_move, bt);
        }
    }

    if interactive {
        let mut player = starting_player;
        loop {
            let (result, best_move) = minimax_alpha_beta(player, &mut starting_board, GameResult::PlaceholderJunSeok, GameResult::PlaceholderYeonSeung);
            println!("{:?}", result);
            println!("{:?}", best_move);
            match best_move {
                Some(x) => {
                    starting_board.make_move(x);
                    starting_board.print();
                },
                None => println!("No move, guess {:?} wins?", player.opponent()),
            }

            player = player.opponent();

            let moves = starting_board.legal_moves();
            if moves.is_empty() {
                println!("No moves left, {:?} wins", player.opponent());
                break;
            }
            for (i, legal_move) in moves.iter().enumerate() {
                println!("{} {:?}", i, legal_move);
            }
            println!("It's {:?}'s turn. What move?", player);
            let mut input_move = String::new();
            io::stdin().read_line(&mut input_move).ok().expect("Failed to read line");
            let input_move: usize = input_move.trim().parse().ok().expect("Give me a number");
            let mut found = false;
            for (i, legal_move) in moves.iter().enumerate() {
                if i == input_move {
                    starting_board.make_move(*legal_move);
                    player = player.opponent();
                    found = true;
                }
            }
            if !found {
                println!("Not found. Auto-calculating a move.")
            }
        }
    }
}
<commit_msg>Extract print_all_responses into a function<commit_after>use std::io;

#[derive(Copy, Clone, Debug)]
enum Player {
    YeonSeung,
    JunSeok,
}

impl Player {
    fn opponent(&self) -> Player {
        match *self {
            Player::YeonSeung => Player::JunSeok,
            Player::JunSeok => Player::YeonSeung,
        }
    }
}

#[derive(Copy, Clone, Eq, PartialEq, Debug)]
struct Coordinate {
    row: usize,
    col: usize,
}

impl Coordinate {
    fn move_in(&self, dir: Direction, delta: usize) -> Coordinate {
        match dir {
            Direction::Up => Coordinate{row: self.row - delta, col: self.col},
            Direction::Down => Coordinate{row: self.row + delta, col: self.col},
            Direction::Left => Coordinate{row: self.row, col: self.col - delta},
            Direction::Right => Coordinate{row: self.row, col: self.col + delta},
        }
    }
    fn induces_board_type(&self) -> bool {
        *self == Coordinate{row: 1, col: 1} ||
        *self == Coordinate{row: 2, col: 1} ||
        *self == Coordinate{row: 2, col: 0} ||
        *self == Coordinate{row: 3, col: 0}
    }
}

#[derive(Copy, Clone)]
enum Direction {
    Up,
    Down,
    Left,
    Right,
}
const POSSIBLE_DIRECTIONS: [Direction; 4] = [
    Direction::Up,
    Direction::Down,
    Direction::Left,
    Direction::Right,
];

#[derive(Copy, Clone, Debug)]
enum MoveType {
    Single,
    OneUp,
    OneDown,
    OneLeft,
    OneRight,
    TwoUp,
    TwoDown,
    TwoLeft,
    TwoRight,
    UpAndDown,
    LeftAndRight,
}
const POSSIBLE_MOVE_TYPES: [MoveType; 11] = [
    MoveType::Single,
    MoveType::OneUp,
    MoveType::OneDown,
    MoveType::OneLeft,
    MoveType::OneRight,
    MoveType::TwoUp,
    MoveType::TwoDown,
    MoveType::TwoLeft,
    MoveType::TwoRight,
    MoveType::UpAndDown,
    MoveType::LeftAndRight,
];

#[derive(Copy, Clone, Debug)]
struct Move {
    coord: Coordinate,
    move_type: MoveType,
    board_type: Option<BoardType>,
}
impl Move {
    fn coords(&self) -> Vec<Coordinate> {
        match self.move_type {
            MoveType::Single => vec![],
            MoveType::OneUp => vec![self.coord.move_in(Direction::Up, 1)],
            MoveType::OneDown => vec![self.coord.move_in(Direction::Down, 1)],
            MoveType::OneLeft => vec![self.coord.move_in(Direction::Left, 1)],
            MoveType::OneRight => vec![self.coord.move_in(Direction::Right, 1)],
            MoveType::TwoUp => vec![self.coord.move_in(Direction::Up, 1), self.coord.move_in(Direction::Up, 2)],
            MoveType::TwoDown => vec![self.coord.move_in(Direction::Down, 1), self.coord.move_in(Direction::Down, 2)],
            MoveType::TwoLeft => vec![self.coord.move_in(Direction::Left, 1), self.coord.move_in(Direction::Left, 2)],
            MoveType::TwoRight => vec![self.coord.move_in(Direction::Right, 1), self.coord.move_in(Direction::Right, 2)],
            MoveType::UpAndDown => vec![self.coord.move_in(Direction::Up, 1), self.coord.move_in(Direction::Down, 1)],
            MoveType::LeftAndRight => vec![self.coord.move_in(Direction::Left, 1), self.coord.move_in(Direction::Right, 1)],
        }
    }
}

const NUM_COLS: usize = 5;
const NUM_ROWS: usize = 4;

// Hacks for the three states of the lower-left of the board in JunSeok vs YeonSeung game
#[derive(Copy, Clone, Debug)]
enum BoardType {
    Left,
    Middle,
    Right,
}
const POSSIBLE_BOARD_TYPES: [BoardType; 3] = [
    BoardType::Left,
    BoardType::Middle,
    BoardType::Right,
];

type BoardArray = [[bool; NUM_COLS]; NUM_ROWS];
struct Board {
    board: BoardArray,
    board_type: Option<BoardType>,
}

impl Board {
    fn make_move(&mut self, m: Move) {
        match m.board_type {
            Some(_) => self.board_type = m.board_type,
            None => (),
        }
        self.set_squares(m, true)
    }

    fn undo_move(&mut self, m: Move, bt: Option<BoardType>) {
        self.board_type = bt;
        self.set_squares(m, false)
    }

    fn set_squares(&mut self, m: Move, mode: bool) {
        self.board[m.coord.row][m.coord.col] = mode;
        for other_space in m.coords().iter() {
            self.board[other_space.row][other_space.col] = mode;
        }
    }

    fn occupied(&self, c: Coordinate) -> bool {
        self.in_bounds(c) && self.board[c.row][c.col]
    }

    fn in_bounds(&self, c: Coordinate) -> bool {
        // >= 0 is always true due to type limits.
        // c.row >= 0 && c.row < NUM_ROWS && c.col >= 0 && c.col < NUM_COLS
        c.row < NUM_ROWS && c.col < NUM_COLS
    }

    // Assuming that m is a move with an unoccupied coordinate!
    // This doesn't check whether the target squares are occupied.
    // Advantage: It's quicker. Disadvantage: It allows some illegal moves.
    fn move_in_bounds(&self, m: Move) -> bool {
        match m.move_type {
            MoveType::Single => true,
            MoveType::OneUp => m.coord.row >= 1,
            MoveType::OneDown => m.coord.row < NUM_ROWS - 1,
            MoveType::OneLeft => m.coord.col >= 1,
            MoveType::OneRight => m.coord.col < NUM_COLS - 1,
            MoveType::TwoUp => m.coord.row >= 2,
            MoveType::TwoDown => m.coord.row < NUM_ROWS - 2,
            MoveType::TwoLeft => m.coord.col >= 2,
            MoveType::TwoRight => m.coord.col < NUM_COLS - 2,
            MoveType::UpAndDown => m.coord.row >= 1 && m.coord.row < NUM_ROWS - 1,
            MoveType::LeftAndRight => m.coord.col >= 1 && m.coord.col < NUM_COLS - 1,
        }
    }

    // Unfortunately I kind of have to hard-code this.
    fn makes_unsolvable(c: Coordinate, b: Option<BoardType>) -> bool {
        match b {
            Some(BoardType::Left)   => c == Coordinate{row: 2, col: 1} || c == Coordinate{row: 1, col: 1},
            Some(BoardType::Middle) => c == Coordinate{row: 3, col: 0} || c == Coordinate{row: 1, col: 1},
            Some(BoardType::Right)  => c == Coordinate{row: 3, col: 0} || c == Coordinate{row: 2, col: 0},
            None => false,
        }
    }

    fn frontier(&self) -> Vec<Coordinate> {
        let mut results = Vec::new();
        for row in 0..NUM_ROWS {
            for col in 0..NUM_COLS {
                let coord = Coordinate{row: row, col: col};
                if self.occupied(coord) || Board::makes_unsolvable(coord, self.board_type) {
                    continue;
                }
                let mut have_neighbor = false;
                for dir in POSSIBLE_DIRECTIONS.iter() {
                    let new_coord = coord.move_in(*dir, 1);
                    if self.occupied(new_coord) {
                        have_neighbor = true;
                        break;
                    }
                }
                if have_neighbor {
                    results.push(coord);
                }
            }
        }
        results
    }

    fn legal_moves(&self) -> Vec<Move> {
        let mut results = Vec::new();
        for frontier_space in self.frontier().iter() {
            for move_type in POSSIBLE_MOVE_TYPES.iter() {
                let mov = Move{coord: *frontier_space, move_type: *move_type, board_type: None};
                if !self.move_in_bounds(mov) {
                    continue;
                }
                let mut other_space_taken = false;
                let mut induces_board_type = frontier_space.induces_board_type();
                for other_space in mov.coords().iter() {
                    if other_space.induces_board_type() {
                        induces_board_type = true;
                    }
                    if self.occupied(*other_space) || Board::makes_unsolvable(*other_space, self.board_type) {
                        other_space_taken = true;
                        break;
                    }
                }
                if !other_space_taken {
                    if induces_board_type && self.board_type.is_none() {
                        for board_type in POSSIBLE_BOARD_TYPES.iter() {
                            if Board::makes_unsolvable(*frontier_space, Some(*board_type)) {
                                continue;
                            }
                            let mut other_spaces_ok = true;
                            for other_space in mov.coords().iter() {
                                if Board::makes_unsolvable(*other_space, Some(*board_type)) {
                                    other_spaces_ok = false;
                                    break;
                                }
                            }
                            if other_spaces_ok {
                                results.push(Move{coord: mov.coord, move_type: mov.move_type, board_type: Some(*board_type)});
                            }
                        }

                    } else {
                        results.push(mov);
                    }
                }
            }
        }
        results
    }

    fn print(&self) {
        for row in self.board.iter() {
            for col in row.iter() {
                print!("{: >5} ", col);
            }
            println!("");
        }
        println!("{:?}", self.board_type);
    }
}

#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug)]
enum GameResult {
    PlaceholderJunSeok,
    JunSeokWin,
    YeonSeungWin,
    PlaceholderYeonSeung,
}

fn minimax_alpha_beta(player: Player, board: &mut Board, initial_alpha: GameResult, initial_beta: GameResult) -> (GameResult, Option<Move>) {
    let moves = board.legal_moves();
    // There are no more moves, which means my opponent completed the railroad.
    // So I lose.
    if moves.is_empty() {
        return match player {
            Player::YeonSeung => (GameResult::JunSeokWin, None),
            Player::JunSeok => (GameResult::YeonSeungWin, None),
        }
    }
    let mut best = match player {
        Player::YeonSeung => initial_alpha,
        Player::JunSeok => initial_beta,
    };
    let mut alpha = initial_alpha;
    let mut beta = initial_beta;
    let mut best_move = None;

    for possible_move in moves.iter() {
        let bt = board.board_type;
        board.make_move(*possible_move);
        let (reply, _) = minimax_alpha_beta(player.opponent(), board, alpha, beta);
        board.undo_move(*possible_move, bt);

        match player {
            Player::YeonSeung => {
                if reply > best {
                    best = reply;
                    alpha = reply;
                    best_move = Some(*possible_move);
                }
                if best >= GameResult::YeonSeungWin {
                    return (best, best_move);
                }
            },
            Player::JunSeok => {
                if reply < best {
                    best = reply;
                    beta = reply;
                    best_move = Some(*possible_move);
                }
                if best <= GameResult::JunSeokWin {
                    return (best, best_move);
                }
            },
        }

        if alpha >= beta {
            return (best, best_move);
        }
    }

    (best, best_move)
}

fn print_all_responses(player: Player, starting_board: &mut Board) {
    for legal_move in starting_board.legal_moves().iter() {
        print!("If {:?} does: {:?}, ", player, legal_move);
        let bt = starting_board.board_type;
        starting_board.make_move(*legal_move);
        let (result, best_move) = minimax_alpha_beta(player.opponent(), starting_board, GameResult::PlaceholderJunSeok, GameResult::PlaceholderYeonSeung);
        match best_move {
            Some(x) => {
                println!("{:?} does: {:?}, {:?}", player.opponent(), x, result);
                starting_board.make_move(x);
                starting_board.print();
                starting_board.undo_move(x, bt);
            }
            None => (),
        }
        starting_board.undo_move(*legal_move, bt);
    }
}

fn main() {
    let mut starting_board = Board{
        board: [
            [false,  true,  true,  true, false],
            [false, false, false,  true, false],
            [false, false, false,  true, false],
            [false, false, false, false, false],
        ],
        board_type: None,
    };
    let starting_player = Player::YeonSeung;

    let all_responses = false;
    let best_move = false;
    let legal_moves = false;
    let interactive = true;

    if legal_moves {
        for legal_move in starting_board.legal_moves().iter() {
            println!("{:?}", legal_move);
        }
    }

    if best_move {
        let (result, best_move) = minimax_alpha_beta(starting_player, &mut starting_board, GameResult::PlaceholderJunSeok, GameResult::PlaceholderYeonSeung);
        println!("{:?}", result);
        println!("{:?}", best_move);
        match best_move {
            Some(x) => {
                starting_board.make_move(x);
                starting_board.print();
            },
            None => (),
        }
    }

    if all_responses {
        print_all_responses(starting_player, &mut starting_board);
    }

    if interactive {
        let mut player = starting_player;
        loop {
            let (result, best_move) = minimax_alpha_beta(player, &mut starting_board, GameResult::PlaceholderJunSeok, GameResult::PlaceholderYeonSeung);
            println!("{:?}", result);
            println!("{:?}", best_move);
            match best_move {
                Some(x) => {
                    starting_board.make_move(x);
                    starting_board.print();
                },
                None => println!("No move, guess {:?} wins?", player.opponent()),
            }

            player = player.opponent();

            let moves = starting_board.legal_moves();
            if moves.is_empty() {
                println!("No moves left, {:?} wins", player.opponent());
                break;
            }
            for (i, legal_move) in moves.iter().enumerate() {
                println!("{} {:?}", i, legal_move);
            }
            println!("It's {:?}'s turn. What move?", player);
            let mut input_move = String::new();
            io::stdin().read_line(&mut input_move).ok().expect("Failed to read line");
            let input_move: usize = input_move.trim().parse().ok().expect("Give me a number");
            let mut found = false;
            for (i, legal_move) in moves.iter().enumerate() {
                if i == input_move {
                    starting_board.make_move(*legal_move);
                    player = player.opponent();
                    found = true;
                }
            }
            if !found {
                println!("Not found. Auto-calculating a move.")
            }
        }
    }
}
<|endoftext|>


<commit_before>use iron::IronError;
use iron::prelude::*;
use iron::status;
use iron::headers::Cookie;
use hyper::header::{ContentType, SetCookie};
use cookie::Cookie as CookiePair;
use router::Router;
use serde_json;
use serde::{Serialize, Serializer};
use serde::de::{self, Visitor, Deserialize, Deserializer};

use std::ops::Deref;
use std::marker::PhantomData;
use std::io;
use std::collections::BTreeMap;
use std::fmt;

use events::Error as EventsError;
use crypto::{PublicKey, SecretKey, HexValue, FromHexError, Hash};
use serialize::ToHex;
use storage::{Result as StorageResult, Error as StorageError};

#[derive(Debug)]
pub enum ApiError {
    Service(Box<::std::error::Error + Send + Sync>),
    Storage(StorageError),
    Events(EventsError),
    FromHex(FromHexError),
    Io(::std::io::Error),
    FileNotFound(Hash),
    NotFound,
    FileTooBig,
    FileExists(Hash),
    IncorrectRequest(Box<::std::error::Error + Send + Sync>),
    Unauthorized,
}

impl ::std::fmt::Display for ApiError {
    fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
        write!(f, "{:?}", self)
    }
}

impl ::std::error::Error for ApiError {
    fn description(&self) -> &str {
        match *self {
            ApiError::Service(ref error) |
            ApiError::IncorrectRequest(ref error) => error.description(),
            ApiError::Storage(ref error) => error.description(),
            ApiError::Events(ref error) => error.description(),
            ApiError::FromHex(ref error) => error.description(),
            ApiError::Io(ref error) => error.description(),
            ApiError::FileNotFound(_) => "FileNotFound",
            ApiError::NotFound => "NotFound",
            ApiError::FileTooBig => "FileToBig",
            ApiError::FileExists(_) => "FileExists",
            ApiError::Unauthorized => "Unauthorized",
        }
    }
}

impl From<io::Error> for ApiError {
    fn from(e: io::Error) -> ApiError {
        ApiError::Io(e)
    }
}

impl From<StorageError> for ApiError {
    fn from(e: StorageError) -> ApiError {
        ApiError::Storage(e)
    }
}

impl From<EventsError> for ApiError {
    fn from(e: EventsError) -> ApiError {
        ApiError::Events(e)
    }
}

impl From<FromHexError> for ApiError {
    fn from(e: FromHexError) -> ApiError {
        ApiError::FromHex(e)
    }
}

impl From<ApiError> for IronError {
    fn from(e: ApiError) -> IronError {
        use std::error::Error;

        let mut body = BTreeMap::new();
        body.insert("debug", format!("{:?}", e));
        body.insert("description", e.description().to_string());
        let code = match e {
            ApiError::FileExists(hash) |
            ApiError::FileNotFound(hash) => {
                body.insert("hash", ToHex::to_hex(&hash));
                status::Conflict
            }
            _ => status::Conflict,
        };
        IronError {
            error: Box::new(e),
            response: Response::with((code, ::serde_json::to_string_pretty(&body).unwrap())),
        }
    }
}

#[derive(Clone, Debug)]
pub struct HexField<T: AsRef<[u8]> + Clone>(pub T);

impl<T> Deref for HexField<T>
    where T: AsRef<[u8]> + Clone
{
    type Target = T;

    fn deref(&self) -> &T {
        &self.0
    }
}

impl<T> Serialize for HexField<T>
    where T: AsRef<[u8]> + Clone
{
    fn serialize<S>(&self, ser: S) -> Result<S::Ok, S::Error>
        where S: Serializer
    {
        ser.serialize_str(&self.0.as_ref().to_hex())
    }
}

struct HexVisitor<T>
    where T: AsRef<[u8]> + HexValue
{
    _p: PhantomData<T>,
}

impl<'v, T> Visitor<'v> for HexVisitor<T>
    where T: AsRef<[u8]> + HexValue + Clone
{
    type Value = HexField<T>;

    fn expecting(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
        write!(fmt, "expected hex represented string")
    }

    fn visit_str<E>(self, s: &str) -> Result<HexField<T>, E>
        where E: de::Error
    {
        let v = T::from_hex(s)
            .map_err(|_| de::Error::custom("Invalid hex"))?;
        Ok(HexField(v))
    }
}

impl<'de, T> Deserialize<'de> for HexField<T>
    where T: AsRef<[u8]> + HexValue + Clone
{
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
        where D: Deserializer<'de>
    {
        deserializer.deserialize_str(HexVisitor { _p: PhantomData })
    }
}

pub trait Api {
    fn load_hex_value_from_cookie<'a>(&self,
                                      request: &'a Request,
                                      key: &str)
                                      -> StorageResult<Vec<u8>> {
        if let Some(&Cookie(ref cookies)) = request.headers.get() {
            for cookie in cookies.iter() {
                if let Ok(c) = CookiePair::parse(cookie.as_str()) {
                    if c.name() == key {
                        if let Ok(value) = HexValue::from_hex(c.value()) {
                            return Ok(value);
                        }
                    }
                }
            }
        }
        Err(StorageError::new(format!("Unable to find value with given key {}", key)))
    }

    fn load_keypair_from_cookies(&self,
                                 request: &Request)
                                 -> Result<(PublicKey, SecretKey), ApiError> {
        let public_key = PublicKey::from_slice(self.load_hex_value_from_cookie(request,
                                                                               "public_key")?
                                                   .as_ref());
        let secret_key = SecretKey::from_slice(self.load_hex_value_from_cookie(request,
                                                                               "secret_key")?
                                                   .as_ref());

        let public_key = public_key.ok_or(ApiError::Unauthorized)?;
        let secret_key = secret_key.ok_or(ApiError::Unauthorized)?;
        Ok((public_key, secret_key))
    }

    fn ok_response_with_cookies(&self,
                                json: &serde_json::Value,
                                cookies: Option<Vec<String>>)
                                -> IronResult<Response> {
        let mut resp = Response::with((status::Ok, serde_json::to_string_pretty(json).unwrap()));
        resp.headers.set(ContentType::json());
        if let Some(cookies) = cookies {
            resp.headers.set(SetCookie(cookies));
        }
        Ok(resp)
    }

    fn ok_response(&self, json: &serde_json::Value) -> IronResult<Response> {
        self.ok_response_with_cookies(json, None)
    }

    fn wire<'b>(&self, router: &'b mut Router);
}

#[cfg(test)]
mod tests {
    use router::Router;
    use serde_json;

    use blockchain::Block;
    use crypto::Hash;

    use super::*;

    #[test]
    fn test_json_response_for_complex_val() {
        let str_val = "sghdkgskgskldghshgsd";
        let complex_val = Block::new(24,
                                     2,
                                     &Hash::new([24; 32]),
                                     &Hash::new([34; 32]),
                                     &Hash::new([38; 32]));
        struct SampleAPI;
        impl Api for SampleAPI {
            fn wire<'b>(&self, _: &'b mut Router) {
                return;
            }
        }
        let stub = SampleAPI;
        let result = stub.ok_response(&serde_json::to_value(str_val).unwrap());
        assert!(result.is_ok());
        let result = stub.ok_response(&serde_json::to_value(&complex_val).unwrap());
        assert!(result.is_ok());
        print!("{:?}", result);
    }
}
<commit_msg>fix tests<commit_after>use iron::IronError;
use iron::prelude::*;
use iron::status;
use iron::headers::Cookie;
use hyper::header::{ContentType, SetCookie};
use cookie::Cookie as CookiePair;
use router::Router;
use serde_json;
use serde::{Serialize, Serializer};
use serde::de::{self, Visitor, Deserialize, Deserializer};

use std::ops::Deref;
use std::marker::PhantomData;
use std::io;
use std::collections::BTreeMap;
use std::fmt;

use events::Error as EventsError;
use crypto::{PublicKey, SecretKey, HexValue, FromHexError, Hash};
use serialize::ToHex;
use storage::{Result as StorageResult, Error as StorageError};

#[derive(Debug)]
pub enum ApiError {
    Service(Box<::std::error::Error + Send + Sync>),
    Storage(StorageError),
    Events(EventsError),
    FromHex(FromHexError),
    Io(::std::io::Error),
    FileNotFound(Hash),
    NotFound,
    FileTooBig,
    FileExists(Hash),
    IncorrectRequest(Box<::std::error::Error + Send + Sync>),
    Unauthorized,
}

impl ::std::fmt::Display for ApiError {
    fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
        write!(f, "{:?}", self)
    }
}

impl ::std::error::Error for ApiError {
    fn description(&self) -> &str {
        match *self {
            ApiError::Service(ref error) |
            ApiError::IncorrectRequest(ref error) => error.description(),
            ApiError::Storage(ref error) => error.description(),
            ApiError::Events(ref error) => error.description(),
            ApiError::FromHex(ref error) => error.description(),
            ApiError::Io(ref error) => error.description(),
            ApiError::FileNotFound(_) => "FileNotFound",
            ApiError::NotFound => "NotFound",
            ApiError::FileTooBig => "FileToBig",
            ApiError::FileExists(_) => "FileExists",
            ApiError::Unauthorized => "Unauthorized",
        }
    }
}

impl From<io::Error> for ApiError {
    fn from(e: io::Error) -> ApiError {
        ApiError::Io(e)
    }
}

impl From<StorageError> for ApiError {
    fn from(e: StorageError) -> ApiError {
        ApiError::Storage(e)
    }
}

impl From<EventsError> for ApiError {
    fn from(e: EventsError) -> ApiError {
        ApiError::Events(e)
    }
}

impl From<FromHexError> for ApiError {
    fn from(e: FromHexError) -> ApiError {
        ApiError::FromHex(e)
    }
}

impl From<ApiError> for IronError {
    fn from(e: ApiError) -> IronError {
        use std::error::Error;

        let mut body = BTreeMap::new();
        body.insert("debug", format!("{:?}", e));
        body.insert("description", e.description().to_string());
        let code = match e {
            ApiError::FileExists(hash) |
            ApiError::FileNotFound(hash) => {
                body.insert("hash", ToHex::to_hex(&hash));
                status::Conflict
            }
            _ => status::Conflict,
        };
        IronError {
            error: Box::new(e),
            response: Response::with((code, ::serde_json::to_string_pretty(&body).unwrap())),
        }
    }
}

#[derive(Clone, Debug)]
pub struct HexField<T: AsRef<[u8]> + Clone>(pub T);

impl<T> Deref for HexField<T>
    where T: AsRef<[u8]> + Clone
{
    type Target = T;

    fn deref(&self) -> &T {
        &self.0
    }
}

impl<T> Serialize for HexField<T>
    where T: AsRef<[u8]> + Clone
{
    fn serialize<S>(&self, ser: S) -> Result<S::Ok, S::Error>
        where S: Serializer
    {
        ser.serialize_str(&self.0.as_ref().to_hex())
    }
}

struct HexVisitor<T>
    where T: AsRef<[u8]> + HexValue
{
    _p: PhantomData<T>,
}

impl<'a, T> Visitor<'a> for HexVisitor<T>
    where T: AsRef<[u8]> + HexValue + Clone
{
    type Value = HexField<T>;

    fn expecting(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
        write!(fmt, "expected hex represented string")
    }

    fn visit_str<E>(self, s: &str) -> Result<HexField<T>, E>
        where E: de::Error
    {
        let v = T::from_hex(s)
            .map_err(|_| de::Error::custom("Invalid hex"))?;
        Ok(HexField(v))
    }
}

impl<'de, T> Deserialize<'de> for HexField<T>
    where T: AsRef<[u8]> + HexValue + Clone
{
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
        where D: Deserializer<'de>
    {
        deserializer.deserialize_str(HexVisitor { _p: PhantomData })
    }
}

pub trait Api {
    fn load_hex_value_from_cookie<'a>(&self,
                                      request: &'a Request,
                                      key: &str)
                                      -> StorageResult<Vec<u8>> {
        if let Some(&Cookie(ref cookies)) = request.headers.get() {
            for cookie in cookies.iter() {
                if let Ok(c) = CookiePair::parse(cookie.as_str()) {
                    if c.name() == key {
                        if let Ok(value) = HexValue::from_hex(c.value()) {
                            return Ok(value);
                        }
                    }
                }
            }
        }
        Err(StorageError::new(format!("Unable to find value with given key {}", key)))
    }

    fn load_keypair_from_cookies(&self,
                                 request: &Request)
                                 -> Result<(PublicKey, SecretKey), ApiError> {
        let public_key = PublicKey::from_slice(self.load_hex_value_from_cookie(request,
                                                                               "public_key")?
                                                   .as_ref());
        let secret_key = SecretKey::from_slice(self.load_hex_value_from_cookie(request,
                                                                               "secret_key")?
                                                   .as_ref());

        let public_key = public_key.ok_or(ApiError::Unauthorized)?;
        let secret_key = secret_key.ok_or(ApiError::Unauthorized)?;
        Ok((public_key, secret_key))
    }

    fn ok_response_with_cookies(&self,
                                json: &serde_json::Value,
                                cookies: Option<Vec<String>>)
                                -> IronResult<Response> {
        let mut resp = Response::with((status::Ok, serde_json::to_string_pretty(json).unwrap()));
        resp.headers.set(ContentType::json());
        if let Some(cookies) = cookies {
            resp.headers.set(SetCookie(cookies));
        }
        Ok(resp)
    }

    fn ok_response(&self, json: &serde_json::Value) -> IronResult<Response> {
        self.ok_response_with_cookies(json, None)
    }

    fn wire<'b>(&self, router: &'b mut Router);
}

#[cfg(test)]
mod tests {
    use router::Router;
    use serde_json;

    use blockchain::Block;
    use crypto::Hash;

    use super::*;

    #[test]
    fn test_json_response_for_complex_val() {
        let str_val = "sghdkgskgskldghshgsd";
        let complex_val = Block::new(24,
                                     2,
                                     &Hash::new([24; 32]),
                                     &Hash::new([34; 32]),
                                     &Hash::new([38; 32]));
        struct SampleAPI;
        impl Api for SampleAPI {
            fn wire<'b>(&self, _: &'b mut Router) {
                return;
            }
        }
        let stub = SampleAPI;
        let result = stub.ok_response(&serde_json::to_value(str_val).unwrap());
        assert!(result.is_ok());
        let result = stub.ok_response(&serde_json::to_value(&complex_val).unwrap());
        assert!(result.is_ok());
        print!("{:?}", result);
    }
}
<|endoftext|>


<commit_before><commit_msg>Leap year<commit_after>

pub fn is_leap_year(year: i32) -> bool
{
    if year%4 == 0 { true } else { false }

}
<|endoftext|>


<commit_before>// Copyright 2015 Nathan Sizemore <nathanrsizemore@gmail.com>
//
// This Source Code Form is subject to the terms of the
// Mozilla Public License, v. 2.0. If a copy of the MPL was not
// distributed with this file, You can obtain one at
// http://mozilla.org/MPL/2.0/.

use std::mem;
use std::os::unix::io::{RawFd, AsRawFd};
use std::io::{Read, Write, Error, ErrorKind};

use libc;
use errno::errno;
use libc::{c_int, c_void};

#[derive(Clone)]
pub struct Socket {
    /// This socket's file descriptor
    pub fd: c_int,
}

impl Socket {

    pub fn set_tcp_keepalive(&self, keepalive: bool) {
        let optval: c_int = match keepalive {
            true => 1,
            false => 0
        };
        let opt_result = libc::setsockopt(self.fd,
                                          libc::SOL_SOCKET,
                                          libc::SO_KEEPALIVE,
                                          &optval as *const _ as *const c_void,
                                          mem::size_of::<c_int>() as u32);
        if opt_result < 0 {
            error!("Setting SO_KEEPALIVE: {}",
                   Error::from_raw_os_error(errno().0 as i32));
        }
    }

    pub fn set_tcp_nodelay(&self, nodelay: bool) {
        const SOL_TCP: c_int = 6;

        let optval: c_int = match nodelay {
            true => 1,
            false => 0
        };
        let opt_result = libc::setsockopt(self.fd,
                                          libc::SOL_TCP,
                                          libc::SO_KEEPALIVE,
                                          &optval as *const _ as *const c_void,
                                          mem::size_of::<c_int>() as u32);
        if opt_result < 0 {
            error!("Setting TCP_NODELAY: {}",
                   Error::from_raw_os_error(errno().0 as i32));
        }
    }
}

impl Read for Socket {
    fn read(&mut self, buf: &mut [u8]) -> Result<usize, Error> {
        if buf.len() < 1 {
            return Err(Error::new(ErrorKind::Other, "Invalid buffer"));
        }

        let result = unsafe { libc::read(self.fd, buf as *mut _ as *mut c_void, buf.len()) };

        if result < 0 {
            return Err(Error::from_raw_os_error(errno().0 as i32));
        }

        if result == 0 {
            return Err(Error::new(ErrorKind::Other, "EOF"));
        }

        Ok(result as usize)
    }
}

impl Write for Socket {
    fn write(&mut self, buf: &[u8]) -> Result<usize, Error> {
        let result = unsafe { libc::write(self.fd, buf as *const _ as *const c_void, buf.len()) };

        if result < 0 {
            return Err(Error::from_raw_os_error(errno().0 as i32));
        }

        Ok(result as usize)
    }

    fn flush(&mut self) -> Result<(), Error> {
        Ok(())
    }
}

impl AsRawFd for Socket {
    fn as_raw_fd(&self) -> RawFd {
        self.fd
    }
}
<commit_msg>Remote debugging<commit_after>// Copyright 2015 Nathan Sizemore <nathanrsizemore@gmail.com>
//
// This Source Code Form is subject to the terms of the
// Mozilla Public License, v. 2.0. If a copy of the MPL was not
// distributed with this file, You can obtain one at
// http://mozilla.org/MPL/2.0/.

use std::mem;
use std::os::unix::io::{RawFd, AsRawFd};
use std::io::{Read, Write, Error, ErrorKind};

use libc;
use errno::errno;
use libc::{c_int, c_void};

#[derive(Clone)]
pub struct Socket {
    /// This socket's file descriptor
    pub fd: c_int,
}

impl Socket {

    pub fn set_tcp_keepalive(&self, keepalive: bool) {
        let optval: c_int = match keepalive {
            true => 1,
            false => 0
        };
        let opt_result = libc::setsockopt(self.fd,
                                          libc::SOL_SOCKET,
                                          libc::SO_KEEPALIVE,
                                          &optval as *const _ as *const c_void,
                                          mem::size_of::<c_int>() as u32);
        if opt_result < 0 {
            error!("Setting SO_KEEPALIVE: {}",
                   Error::from_raw_os_error(errno().0 as i32));
        }
    }

    pub fn set_tcp_nodelay(&self, nodelay: bool) {
        const SOL_TCP: c_int = 6;

        let optval: c_int = match nodelay {
            true => 1,
            false => 0
        };
        let opt_result = libc::setsockopt(self.fd,
                                          SOL_TCP,
                                          libc::SO_KEEPALIVE,
                                          &optval as *const _ as *const c_void,
                                          mem::size_of::<c_int>() as u32);
        if opt_result < 0 {
            error!("Setting TCP_NODELAY: {}",
                   Error::from_raw_os_error(errno().0 as i32));
        }
    }
}

impl Read for Socket {
    fn read(&mut self, buf: &mut [u8]) -> Result<usize, Error> {
        if buf.len() < 1 {
            return Err(Error::new(ErrorKind::Other, "Invalid buffer"));
        }

        let result = unsafe { libc::read(self.fd, buf as *mut _ as *mut c_void, buf.len()) };

        if result < 0 {
            return Err(Error::from_raw_os_error(errno().0 as i32));
        }

        if result == 0 {
            return Err(Error::new(ErrorKind::Other, "EOF"));
        }

        Ok(result as usize)
    }
}

impl Write for Socket {
    fn write(&mut self, buf: &[u8]) -> Result<usize, Error> {
        let result = unsafe { libc::write(self.fd, buf as *const _ as *const c_void, buf.len()) };

        if result < 0 {
            return Err(Error::from_raw_os_error(errno().0 as i32));
        }

        Ok(result as usize)
    }

    fn flush(&mut self) -> Result<(), Error> {
        Ok(())
    }
}

impl AsRawFd for Socket {
    fn as_raw_fd(&self) -> RawFd {
        self.fd
    }
}
<|endoftext|>


<commit_before>use libc;

use std::ffi::CStr;
use std::slice;
use std::str;

use markup5ever::LocalName;

enum Attribute<'a> {
    CStr(&'a CStr),
    Unterminated(&'a str),
}

pub struct PropertyBag<'a>(Vec<(LocalName, Attribute<'a>)>);

pub struct PropertyBagIter<'a>(slice::Iter<'a, (LocalName, Attribute<'a>)>);

trait Utf8CStrToStr {
    fn to_str_utf8(&self) -> &str;
}

impl Utf8CStrToStr for CStr {
    fn to_str_utf8(&self) -> &str {
        // We can *only* do this when the CStr comes from a C string that was validated
        // as UTF-8 on the C side of things.  In our case, the C strings from libxml2 and
        // are valid UTF-8.
        unsafe { str::from_utf8_unchecked(self.to_bytes()) }
    }
}

impl<'a> PropertyBag<'a> {
    /// Creates an iterable `PropertyBag` from a C array of borrowed C strings.
    ///
    /// With libxml2's SAX parser, the caller's callback for "element start"
    /// gets passed a `xmlChar **` of attribute/value pairs.  Even indices
    /// in the array are pointers to attribute names; odd indices are
    /// pointers to attribute values.  The array terminates with a NULL
    /// element in an even index.
    ///
    /// This function is unsafe because the caller must guarantee the following:
    ///
    /// * `pairs` is a valid pointer, or NULL for an empty array
    ///
    /// * `pairs` has key/value pairs and is NULL terminated
    ///
    /// * Both keys and values are valid UTF-8, nul-terminated C strings
    ///
    /// The lifetime of the `PropertyBag` should be considered the same as the lifetime of the
    /// `pairs` array, as the property bag does not copy the strings - it directly stores pointers
    /// into that array's strings.
    pub unsafe fn new_from_key_value_pairs(pairs: *const *const libc::c_char) -> PropertyBag<'a> {
        let mut array = Vec::new();

        if !pairs.is_null() {
            let mut i = 0;
            loop {
                let key = *pairs.offset(i);
                if !key.is_null() {
                    let val = *pairs.offset(i + 1);
                    assert!(!val.is_null());

                    let key_str = CStr::from_ptr(key);
                    let val_str = CStr::from_ptr(val);

                    let attr = LocalName::from(key_str.to_str_utf8());
                    array.push((attr, Attribute::CStr(val_str)));
                } else {
                    break;
                }

                i += 2;
            }
        }

        PropertyBag(array)
    }

    pub fn len(&self) -> usize {
        self.0.len()
    }

    pub fn iter(&self) -> PropertyBagIter<'_> {
        PropertyBagIter(self.0.iter())
    }
}

impl<'a> Iterator for PropertyBagIter<'a> {
    type Item = (LocalName, &'a str);

    fn next(&mut self) -> Option<Self::Item> {
        self.0.next().map(|(a, v)| {
            match *v {
                Attribute::CStr(ref v) => (a.clone(), v.to_str_utf8()),
                Attribute::Unterminated(v) => (a.clone(), v),
            }
        })
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use markup5ever::local_name;
    use std::ffi::CString;
    use std::ptr;

    #[test]
    fn empty_property_bag() {
        let map = unsafe { PropertyBag::new_from_key_value_pairs(ptr::null()) };
        assert_eq!(map.len(), 0);
    }

    #[test]
    fn property_bag_iters() {
        let pairs = [
            CString::new("rx").unwrap(),
            CString::new("1").unwrap(),
            CString::new("ry").unwrap(),
            CString::new("2").unwrap(),
        ];

        let mut v = Vec::new();

        for x in &pairs {
            v.push(x.as_ptr() as *const libc::c_char);
        }

        v.push(ptr::null());

        let pbag = unsafe { PropertyBag::new_from_key_value_pairs(v.as_ptr()) };

        let mut had_rx: bool = false;
        let mut had_ry: bool = false;

        for (a, v) in pbag.iter() {
            match a {
                local_name!("rx") => {
                    assert!(v == "1");
                    had_rx = true;
                }
                local_name!("ry") => {
                    assert!(v == "2");
                    had_ry = true;
                }
                _ => unreachable!(),
            }
        }

        assert!(had_rx);
        assert!(had_ry);
    }
}
<commit_msg>PropertyBag: Add constructor for namespaced attributes from startElementNsSAX2Func<commit_after>use libc;

use std::ffi::CStr;
use std::mem;
use std::slice;
use std::str;

use markup5ever::LocalName;

enum Attribute<'a> {
    CStr(&'a CStr),
    Unterminated(&'a str),
}

pub struct PropertyBag<'a>(Vec<(LocalName, Attribute<'a>)>);

pub struct PropertyBagIter<'a>(slice::Iter<'a, (LocalName, Attribute<'a>)>);

trait Utf8CStrToStr {
    fn to_str_utf8(&self) -> &str;
}

impl Utf8CStrToStr for CStr {
    fn to_str_utf8(&self) -> &str {
        // We can *only* do this when the CStr comes from a C string that was validated
        // as UTF-8 on the C side of things.  In our case, the C strings from libxml2 and
        // are valid UTF-8.
        unsafe { str::from_utf8_unchecked(self.to_bytes()) }
    }
}

impl<'a> PropertyBag<'a> {
    /// Creates an iterable `PropertyBag` from a C array of borrowed C strings.
    ///
    /// With libxml2's SAX parser, the caller's callback for "element start"
    /// gets passed a `xmlChar **` of attribute/value pairs.  Even indices
    /// in the array are pointers to attribute names; odd indices are
    /// pointers to attribute values.  The array terminates with a NULL
    /// element in an even index.
    ///
    /// This function is unsafe because the caller must guarantee the following:
    ///
    /// * `pairs` is a valid pointer, or NULL for an empty array
    ///
    /// * `pairs` has key/value pairs and is NULL terminated
    ///
    /// * Both keys and values are valid UTF-8, nul-terminated C strings
    ///
    /// The lifetime of the `PropertyBag` should be considered the same as the lifetime of the
    /// `pairs` array, as the property bag does not copy the strings - it directly stores pointers
    /// into that array's strings.
    pub unsafe fn new_from_key_value_pairs(pairs: *const *const libc::c_char) -> PropertyBag<'a> {
        let mut array = Vec::new();

        if !pairs.is_null() {
            let mut i = 0;
            loop {
                let key = *pairs.offset(i);
                if !key.is_null() {
                    let val = *pairs.offset(i + 1);
                    assert!(!val.is_null());

                    let key_str = CStr::from_ptr(key);
                    let val_str = CStr::from_ptr(val);

                    let attr = LocalName::from(key_str.to_str_utf8());
                    array.push((attr, Attribute::CStr(val_str)));
                } else {
                    break;
                }

                i += 2;
            }
        }

        PropertyBag(array)
    }

    /// Creates an iterable `PropertyBag` from a C array of borrowed C strings.
    ///
    /// With libxml2's SAX parser, the caller's startElementNsSAX2Func
    /// callback gets passed a `xmlChar **` for attributes, which
    /// comes in groups of /// (localname/prefix/URI/value_start/value_end).
    /// In those, localname/prefix/URI are NUL-terminated strings;
    /// value_start and value_end point to the start-inclusive and
    /// end-exclusive bytes in the attribute's value.
    ///
    /// This function is unsafe because the caller must guarantee the following:
    ///
    /// * `attrs` is a valid pointer, with (n_attributes * 5) elements.
    ///
    /// * All strings are valid UTF-8.
    ///
    /// The lifetime of the `PropertyBag` should be considered the same as the lifetime of the
    /// `attrs` array, as the property bag does not copy the strings - it directly stores pointers
    /// into that array's strings.
    pub unsafe fn new_from_namespaced_attributes(
        n_attributes: usize,
        attrs: *const *const libc::c_char,
    ) -> PropertyBag<'a> {
        let mut array = Vec::new();

        if n_attributes > 0 && !attrs.is_null() {
            let attrs = slice::from_raw_parts(attrs, n_attributes * 5);

            let mut i = 0;
            while i < n_attributes * 5 {
                let localname = attrs[i];
                let _prefix = attrs[i + 1];
                let _uri = attrs[i + 2];
                let value_start = attrs[i + 3];
                let value_end = attrs[i + 4];

                assert!(!localname.is_null());

                if !value_start.is_null() && !value_end.is_null() {
                    assert!(value_end > value_start);

                    // FIXME: ptr::offset_from() is nightly-only.
                    // We'll do the computation of the length by hand.
                    let start: usize = mem::transmute(value_start);
                    let end: usize = mem::transmute(value_end);
                    let len = end - start;

                    let value_slice = slice::from_raw_parts(value_start as *const u8, len);
                    let value_str = str::from_utf8_unchecked(value_slice);

                    let key_str = CStr::from_ptr(localname);
                    let attr = LocalName::from(key_str.to_str_utf8());
                    array.push((attr, Attribute::Unterminated(value_str)));
                }

                i += 5;
            }
        }

        PropertyBag(array)
    }

    pub fn len(&self) -> usize {
        self.0.len()
    }

    pub fn iter(&self) -> PropertyBagIter<'_> {
        PropertyBagIter(self.0.iter())
    }
}

impl<'a> Iterator for PropertyBagIter<'a> {
    type Item = (LocalName, &'a str);

    fn next(&mut self) -> Option<Self::Item> {
        self.0.next().map(|(a, v)| match *v {
            Attribute::CStr(ref v) => (a.clone(), v.to_str_utf8()),
            Attribute::Unterminated(v) => (a.clone(), v),
        })
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use markup5ever::local_name;
    use std::ffi::CString;
    use std::ptr;

    #[test]
    fn empty_property_bag() {
        let map = unsafe { PropertyBag::new_from_key_value_pairs(ptr::null()) };
        assert_eq!(map.len(), 0);
    }

    #[test]
    fn property_bag_iters() {
        let pairs = [
            CString::new("rx").unwrap(),
            CString::new("1").unwrap(),
            CString::new("ry").unwrap(),
            CString::new("2").unwrap(),
        ];

        let mut v = Vec::new();

        for x in &pairs {
            v.push(x.as_ptr() as *const libc::c_char);
        }

        v.push(ptr::null());

        let pbag = unsafe { PropertyBag::new_from_key_value_pairs(v.as_ptr()) };

        let mut had_rx: bool = false;
        let mut had_ry: bool = false;

        for (a, v) in pbag.iter() {
            match a {
                local_name!("rx") => {
                    assert!(v == "1");
                    had_rx = true;
                }
                local_name!("ry") => {
                    assert!(v == "2");
                    had_ry = true;
                }
                _ => unreachable!(),
            }
        }

        assert!(had_rx);
        assert!(had_ry);
    }

    #[test]
    fn property_bag_with_namespaces() {
        let attrs = [
            (CString::new("rx").unwrap(), CString::new("1").unwrap()),
            (CString::new("ry").unwrap(), CString::new("2").unwrap()),
        ];

        let mut v: Vec<*const libc::c_char> = Vec::new();

        for (key, val) in &attrs {
            v.push(key.as_ptr() as *const libc::c_char); // localname
            v.push(ptr::null()); // prefix
            v.push(ptr::null()); // uri

            let val_start = val.as_ptr() as *const libc::c_char;
            let val_end = unsafe { val_start.offset(val.as_bytes().len() as isize) };
            v.push(val_start); // value_start
            v.push(val_end); // value_end
        }

        let pbag = unsafe { PropertyBag::new_from_namespaced_attributes(2, v.as_ptr()) };

        let mut had_rx: bool = false;
        let mut had_ry: bool = false;

        for (a, v) in pbag.iter() {
            match a {
                local_name!("rx") => {
                    assert!(v == "1");
                    had_rx = true;
                }
                local_name!("ry") => {
                    assert!(v == "2");
                    had_ry = true;
                }
                _ => unreachable!(),
            }
        }

        assert!(had_rx);
        assert!(had_ry);
    }
}
<|endoftext|>


<commit_before>// Copyright 2014 The Servo Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use crate::{Atom, StaticAtomSet};
#[cfg(feature = "serde_support")]
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::borrow::Cow;
use std::fmt;

impl<Static: StaticAtomSet> ::precomputed_hash::PrecomputedHash for Atom<Static> {
    fn precomputed_hash(&self) -> u32 {
        self.get_hash()
    }
}

impl<'a, Static: StaticAtomSet> From<&'a Atom<Static>> for Atom<Static> {
    fn from(atom: &'a Self) -> Self {
        atom.clone()
    }
}

impl<Static: StaticAtomSet> PartialEq<str> for Atom<Static> {
    fn eq(&self, other: &str) -> bool {
        &self[..] == other
    }
}

impl<Static: StaticAtomSet> PartialEq<Atom<Static>> for str {
    fn eq(&self, other: &Atom<Static>) -> bool {
        self == &other[..]
    }
}

impl<Static: StaticAtomSet> PartialEq<String> for Atom<Static> {
    fn eq(&self, other: &String) -> bool {
        &self[..] == &other[..]
    }
}

impl<'a, Static: StaticAtomSet> From<&'a str> for Atom<Static> {
    #[inline]
    fn from(string_to_add: &str) -> Self {
        Atom::from(Cow::Borrowed(string_to_add))
    }
}

impl<Static: StaticAtomSet> From<String> for Atom<Static> {
    #[inline]
    fn from(string_to_add: String) -> Self {
        Atom::from(Cow::Owned(string_to_add))
    }
}

impl<Static: StaticAtomSet> fmt::Display for Atom<Static> {
    #[inline]
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        <str as fmt::Display>::fmt(self, f)
    }
}

impl<Static: StaticAtomSet> AsRef<str> for Atom<Static> {
    fn as_ref(&self) -> &str {
        &self
    }
}

#[cfg(feature = "serde_support")]
impl<Static: StaticAtomSet> Serialize for Atom<Static> {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let string: &str = self.as_ref();
        string.serialize(serializer)
    }
}

#[cfg(feature = "serde_support")]
impl<'a, Static: StaticAtomSet> Deserialize<'a> for Atom<Static> {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'a>,
    {
        use serde::de;
        use std::marker::PhantomData;

        struct AtomVisitor<Static: StaticAtomSet>(PhantomData<Static>);

        impl<'de, Static: StaticAtomSet> de::Visitor<'de> for AtomVisitor<Static> {
            type Value = Atom<Static>;

            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
                write!(formatter, "an Atom")
            }

            fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
            where
                E: de::Error,
            {
                Ok(Atom::from(v))
            }

            fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
            where
                E: de::Error,
            {
                Ok(Atom::from(v))
            }
        }

        deserializer.deserialize_string(AtomVisitor(PhantomData))
    }
}
<commit_msg>Use deserialize_str instead of deserialize_string<commit_after>// Copyright 2014 The Servo Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use crate::{Atom, StaticAtomSet};
#[cfg(feature = "serde_support")]
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::borrow::Cow;
use std::fmt;

impl<Static: StaticAtomSet> ::precomputed_hash::PrecomputedHash for Atom<Static> {
    fn precomputed_hash(&self) -> u32 {
        self.get_hash()
    }
}

impl<'a, Static: StaticAtomSet> From<&'a Atom<Static>> for Atom<Static> {
    fn from(atom: &'a Self) -> Self {
        atom.clone()
    }
}

impl<Static: StaticAtomSet> PartialEq<str> for Atom<Static> {
    fn eq(&self, other: &str) -> bool {
        &self[..] == other
    }
}

impl<Static: StaticAtomSet> PartialEq<Atom<Static>> for str {
    fn eq(&self, other: &Atom<Static>) -> bool {
        self == &other[..]
    }
}

impl<Static: StaticAtomSet> PartialEq<String> for Atom<Static> {
    fn eq(&self, other: &String) -> bool {
        &self[..] == &other[..]
    }
}

impl<'a, Static: StaticAtomSet> From<&'a str> for Atom<Static> {
    #[inline]
    fn from(string_to_add: &str) -> Self {
        Atom::from(Cow::Borrowed(string_to_add))
    }
}

impl<Static: StaticAtomSet> From<String> for Atom<Static> {
    #[inline]
    fn from(string_to_add: String) -> Self {
        Atom::from(Cow::Owned(string_to_add))
    }
}

impl<Static: StaticAtomSet> fmt::Display for Atom<Static> {
    #[inline]
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        <str as fmt::Display>::fmt(self, f)
    }
}

impl<Static: StaticAtomSet> AsRef<str> for Atom<Static> {
    fn as_ref(&self) -> &str {
        &self
    }
}

#[cfg(feature = "serde_support")]
impl<Static: StaticAtomSet> Serialize for Atom<Static> {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let string: &str = self.as_ref();
        string.serialize(serializer)
    }
}

#[cfg(feature = "serde_support")]
impl<'a, Static: StaticAtomSet> Deserialize<'a> for Atom<Static> {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'a>,
    {
        use serde::de;
        use std::marker::PhantomData;

        struct AtomVisitor<Static: StaticAtomSet>(PhantomData<Static>);

        impl<'de, Static: StaticAtomSet> de::Visitor<'de> for AtomVisitor<Static> {
            type Value = Atom<Static>;

            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
                write!(formatter, "an Atom")
            }

            fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
            where
                E: de::Error,
            {
                Ok(Atom::from(v))
            }

            fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
            where
                E: de::Error,
            {
                Ok(Atom::from(v))
            }
        }

        deserializer.deserialize_str(AtomVisitor(PhantomData))
    }
}
<|endoftext|>


<commit_before>// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use std::{
    fs::File,
    io::{BufRead, BufReader},
    path::PathBuf,
};

use proc_macro2::TokenStream;
use quote::ToTokens;
use syn::Item;

use autocxx_integration_tests::{CodeChecker, CodeCheckerFns, TestError};

/// Generates a closure which can be used to ensure that the given symbol
/// is mentioned in the output and has documentation attached.
/// The idea is that this is what we do in cases where we can't generate code properly.
pub(crate) fn make_error_finder(error_symbol: &'static str) -> CodeChecker {
    Box::new(ErrorFinder(error_symbol))
}
struct ErrorFinder(&'static str);

impl CodeCheckerFns for ErrorFinder {
    fn check_rust(&self, rs: syn::File) -> Result<(), TestError> {
        let ffi_items = find_ffi_items(rs)?;
        // Ensure there's some kind of struct entry for this symbol
        let error_item = ffi_items
            .into_iter()
            .filter_map(|i| match i {
                Item::Struct(its) if its.ident == self.0 => Some(its),
                _ => None,
            })
            .next()
            .ok_or(TestError::RsCodeExaminationFail)?;
        // Ensure doc attribute
        error_item
            .attrs
            .into_iter()
            .find(|a| a.path.get_ident().filter(|p| *p == "doc").is_some())
            .ok_or(TestError::RsCodeExaminationFail)?;
        Ok(())
    }
}

fn find_ffi_items(f: syn::File) -> Result<Vec<Item>, TestError> {
    Ok(f.items
        .into_iter()
        .filter_map(|i| match i {
            Item::Mod(itm) => Some(itm),
            _ => None,
        })
        .next()
        .ok_or(TestError::RsCodeExaminationFail)?
        .content
        .ok_or(TestError::RsCodeExaminationFail)?
        .1)
}

struct StringFinder(Vec<&'static str>);

impl CodeCheckerFns for StringFinder {
    fn check_rust(&self, rs: syn::File) -> Result<(), TestError> {
        let mut ts = TokenStream::new();
        rs.to_tokens(&mut ts);
        let toks = ts.to_string();
        for msg in &self.0 {
            if !toks.contains(msg) {
                return Err(TestError::RsCodeExaminationFail);
            };
        }
        Ok(())
    }
}

/// Returns a code checker which simply hunts for a given string in the results
pub(crate) fn make_string_finder(error_texts: Vec<&'static str>) -> CodeChecker {
    Box::new(StringFinder(error_texts))
}

/// Counts the number of generated C++ files.
pub(crate) struct CppCounter {
    cpp_count: usize,
}

impl CppCounter {
    pub(crate) fn new(cpp_count: usize) -> Self {
        Self { cpp_count }
    }
}

impl CodeCheckerFns for CppCounter {
    fn check_cpp(&self, cpp: &[PathBuf]) -> Result<(), TestError> {
        if cpp.len() == self.cpp_count {
            Ok(())
        } else {
            Err(TestError::CppCodeExaminationFail)
        }
    }

    fn skip_build(&self) -> bool {
        true
    }
}

/// Searches generated C++ for strings we want to find, or want _not_ to find,
/// or both.
pub(crate) struct CppMatcher<'a> {
    positive_matches: &'a [&'a str],
    negative_matches: &'a [&'a str],
}

impl<'a> CppMatcher<'a> {
    pub(crate) fn new(positive_matches: &'a [&'a str], negative_matches: &'a [&'a str]) -> Self {
        Self {
            positive_matches,
            negative_matches,
        }
    }
}

impl<'a> CodeCheckerFns for CppMatcher<'a> {
    fn check_cpp(&self, cpp: &[PathBuf]) -> Result<(), TestError> {
        let mut positives_needed = self.positive_matches.to_vec();
        for filename in cpp {
            let file = File::open(filename).unwrap();
            let lines = BufReader::new(file).lines();
            for l in lines.filter_map(|l| l.ok()) {
                if self.negative_matches.iter().any(|neg| l.contains(neg)) {
                    return Err(TestError::CppCodeExaminationFail);
                }
                positives_needed.retain(|pos| !l.contains(pos));
            }
        }
        if positives_needed.is_empty() {
            Ok(())
        } else {
            Err(TestError::CppCodeExaminationFail)
        }
    }
}

pub(crate) struct NoSystemHeadersChecker;

impl CodeCheckerFns for NoSystemHeadersChecker {
    fn check_cpp(&self, cpp: &[PathBuf]) -> Result<(), TestError> {
        for filename in cpp {
            let file = File::open(filename).unwrap();
            if BufReader::new(file)
                .lines()
                .any(|l| l.as_ref().unwrap().starts_with("#include <"))
            {
                return Err(TestError::CppCodeExaminationFail);
            }
        }
        Ok(())
    }
    fn skip_build(&self) -> bool {
        true
    }
}
<commit_msg>Code checker improvement.<commit_after>// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use std::{
    fs::File,
    io::{BufRead, BufReader},
    path::PathBuf,
};

use itertools::{Either, Itertools};
use proc_macro2::TokenStream;
use quote::ToTokens;
use syn::Item;

use autocxx_integration_tests::{CodeChecker, CodeCheckerFns, TestError};

/// Generates a closure which can be used to ensure that the given symbol
/// is mentioned in the output and has documentation attached.
/// The idea is that this is what we do in cases where we can't generate code properly.
pub(crate) fn make_error_finder(error_symbol: &'static str) -> CodeChecker {
    Box::new(ErrorFinder(error_symbol))
}
struct ErrorFinder(&'static str);

impl CodeCheckerFns for ErrorFinder {
    fn check_rust(&self, rs: syn::File) -> Result<(), TestError> {
        let ffi_items = find_ffi_items(rs)?;
        // Ensure there's some kind of struct entry for this symbol
        let error_item = ffi_items
            .into_iter()
            .filter_map(|i| match i {
                Item::Struct(its) if its.ident == self.0 => Some(its),
                _ => None,
            })
            .next()
            .ok_or(TestError::RsCodeExaminationFail)?;
        // Ensure doc attribute
        error_item
            .attrs
            .into_iter()
            .find(|a| a.path.get_ident().filter(|p| *p == "doc").is_some())
            .ok_or(TestError::RsCodeExaminationFail)?;
        Ok(())
    }
}

fn find_ffi_items(f: syn::File) -> Result<Vec<Item>, TestError> {
    let md = f
        .items
        .into_iter()
        .filter_map(|i| match i {
            Item::Mod(itm) => Some(itm),
            _ => None,
        })
        .next()
        .ok_or(TestError::RsCodeExaminationFail)?;
    let mut items = Vec::new();
    find_all_non_mod_items(md, &mut items);
    Ok(items)
}

fn find_all_non_mod_items(md: syn::ItemMod, items: &mut Vec<Item>) {
    let (more_mods, mut these_items): (Vec<_>, Vec<_>) = md
        .content
        .into_iter()
        .flat_map(|(_, more_items)| more_items.into_iter())
        .partition_map(|i| match i {
            Item::Mod(itm) => Either::Left(itm),
            _ => Either::Right(i),
        });
    items.append(&mut these_items);
    for md in more_mods.into_iter() {
        find_all_non_mod_items(md, items);
    }
}

struct StringFinder(Vec<&'static str>);

impl CodeCheckerFns for StringFinder {
    fn check_rust(&self, rs: syn::File) -> Result<(), TestError> {
        let mut ts = TokenStream::new();
        rs.to_tokens(&mut ts);
        let toks = ts.to_string();
        for msg in &self.0 {
            if !toks.contains(msg) {
                return Err(TestError::RsCodeExaminationFail);
            };
        }
        Ok(())
    }
}

/// Returns a code checker which simply hunts for a given string in the results
pub(crate) fn make_string_finder(error_texts: Vec<&'static str>) -> CodeChecker {
    Box::new(StringFinder(error_texts))
}

/// Counts the number of generated C++ files.
pub(crate) struct CppCounter {
    cpp_count: usize,
}

impl CppCounter {
    pub(crate) fn new(cpp_count: usize) -> Self {
        Self { cpp_count }
    }
}

impl CodeCheckerFns for CppCounter {
    fn check_cpp(&self, cpp: &[PathBuf]) -> Result<(), TestError> {
        if cpp.len() == self.cpp_count {
            Ok(())
        } else {
            Err(TestError::CppCodeExaminationFail)
        }
    }

    fn skip_build(&self) -> bool {
        true
    }
}

/// Searches generated C++ for strings we want to find, or want _not_ to find,
/// or both.
pub(crate) struct CppMatcher<'a> {
    positive_matches: &'a [&'a str],
    negative_matches: &'a [&'a str],
}

impl<'a> CppMatcher<'a> {
    pub(crate) fn new(positive_matches: &'a [&'a str], negative_matches: &'a [&'a str]) -> Self {
        Self {
            positive_matches,
            negative_matches,
        }
    }
}

impl<'a> CodeCheckerFns for CppMatcher<'a> {
    fn check_cpp(&self, cpp: &[PathBuf]) -> Result<(), TestError> {
        let mut positives_needed = self.positive_matches.to_vec();
        for filename in cpp {
            let file = File::open(filename).unwrap();
            let lines = BufReader::new(file).lines();
            for l in lines.filter_map(|l| l.ok()) {
                if self.negative_matches.iter().any(|neg| l.contains(neg)) {
                    return Err(TestError::CppCodeExaminationFail);
                }
                positives_needed.retain(|pos| !l.contains(pos));
            }
        }
        if positives_needed.is_empty() {
            Ok(())
        } else {
            Err(TestError::CppCodeExaminationFail)
        }
    }
}

pub(crate) struct NoSystemHeadersChecker;

impl CodeCheckerFns for NoSystemHeadersChecker {
    fn check_cpp(&self, cpp: &[PathBuf]) -> Result<(), TestError> {
        for filename in cpp {
            let file = File::open(filename).unwrap();
            if BufReader::new(file)
                .lines()
                .any(|l| l.as_ref().unwrap().starts_with("#include <"))
            {
                return Err(TestError::CppCodeExaminationFail);
            }
        }
        Ok(())
    }
    fn skip_build(&self) -> bool {
        true
    }
}
<|endoftext|>