<commit_before>use std::rand::Rng;
use calx::dijkstra::Dijkstra;
use world;
use location::{Location};
use dir6::Dir6;
use flags;
use mob;
use mob::{Status, Intrinsic, MobType};
use components::{Kind};
use geom::HexGeom;
use spatial::Place;
use action;
use fov::Fov;
use rng;
use msg;

/// Game object handle.
#[deriving(Copy, PartialEq, Eq, Clone, Hash, Show, Decodable, Encodable)]
pub struct Entity(pub uint);

impl Entity {
    /// Place the entity in a location in the game world.
    pub fn place(self, loc: Location) {
        world::with_mut(|w| w.spatial.insert_at(self, loc));
        self.on_move_to(loc);
    }

    /// Remove the entity from all the game world systems. THE ENTITY VALUE
    /// WILL BE INVALID FROM HERE ON AND USING IT WILL LEAD TO BUGS. THE
    /// CALLER IS RESPONSIBLE FOR ENSUING THAT AN ENTITY WILL NOT BE
    /// USED FROM ANYWHERE AFTER THE DELETE OPERATION.
    pub fn delete(self) {
        // COMPONENTS CHECKPOINT
        // This needs to call every component system.
        world::with_mut(|w| w.descs_mut().remove(self));
        world::with_mut(|w| w.kinds_mut().remove(self));
        world::with_mut(|w| w.map_memories_mut().remove(self));
        world::with_mut(|w| w.mobs_mut().remove(self));
        world::with_mut(|w| w.spawns_mut().remove(self));

        world::with_mut(|w| w.spatial.remove(self));

        world::with_mut(|w| w.ecs.delete(self));
    }

    pub fn blocks_walk(self) -> bool { self.is_mob() }

    /// Return the kind of the entity.
    pub fn kind(self) -> Kind {
        // XXX: Will crash if an entity has no kind specified.
        world::with(|w| *w.kinds().get(self).unwrap())
    }

    pub fn name(self) -> String {
        if let Some(spec) = self.mob_spec() {
            return spec.name.to_string();
        }
        return "".to_string();
    }

    /// Create a cloned entity that uses the current entity as a parent
    /// prototype. Components not defined in the clone entity will be read
    /// from the current entity.
    pub fn clone_at(self, loc: Location) -> Entity {
        world::with_mut(|w| {
            let e = w.ecs.new_entity(Some(self));
            w.spatial.insert_at(e, loc);
            e
        })
    }

// Spatial methods /////////////////////////////////////////////////////

    pub fn can_enter(self, loc: Location) -> bool {
        if self.is_mob() && loc.has_mobs() { return false; }
        if loc.terrain().is_door() && !self.has_intrinsic(Intrinsic::Hands) {
            // Can't open doors without hands.
            return false;
        }
        if loc.blocks_walk() { return false; }
        true
    }

    /// Return whether the entity can move in a direction.
    pub fn can_step(self, dir: Dir6) -> bool {
        let place = world::with(|w| w.spatial.get(self));
        if let Some(Place::At(loc)) = place {
            let new_loc = loc + dir.to_v2();
            return self.can_enter(new_loc);
        }
        return false;
    }

    /// Try to move the entity in direction.
    pub fn step(self, dir: Dir6) {
        let place = world::with(|w| w.spatial.get(self));
        if let Some(Place::At(loc)) = place {
            let new_loc = loc + dir.to_v2();
            if self.can_enter(new_loc) {
                world::with_mut(|w| w.spatial.insert_at(self, new_loc));
                self.on_move_to(new_loc);
            }
        }
    }

    pub fn location(self) -> Option<Location> {
        match world::with(|w| w.spatial.get(self)) {
            Some(Place::At(loc)) => Some(loc),
            Some(Place::In(e)) => e.location(),
            _ => None
        }
    }

    pub fn distance_from(self, other: Entity) -> Option<int> {
        if let (Some(loc1), Some(loc2)) = (self.location(), other.location()) {
            loc1.distance_from(loc2)
        } else {
            None
        }
    }

// Damage and lifetime /////////////////////////////////////////////////

    pub fn damage(self, amount: int) {
        if amount == 0 { return; }

        let (_amount, kill) = world::with_mut(|w| {
            if let Some(ref mut mob) = w.mobs_mut().get(self) {
                mob.hp -= amount;
                if mob.hp <= 0 {
                    // Remember this when we're out of the borrow.
                    return (amount, true);
                }
            } else {
                panic!("Damaging a non-mob");
            };
            (amount, false)
        });

        msg::push_msg(::Msg::Damage(self));
        if kill {
            self.kill();
        }
    }

    /// Do any game logic stuff related to this entity dying violently before
    /// deleting it.
    pub fn kill(self) {
        msgln!("{} dies.", self.name());
        msg::push_msg(::Msg::Gib(self.location().unwrap()));
        self.delete();
    }

// Mob methods /////////////////////////////////////////////////////////

    pub fn is_mob(self) -> bool {
        world::with(|w| {
            if let Some(&Kind::Mob(_)) = w.kinds().get(self) {
                return true;
            }
            return false;
        })
    }

    pub fn mob_spec(self) -> Option<&'static mob::MobSpec> {
        world::with(|w| {
            if let Some(&Kind::Mob(mt)) = w.kinds().get(self) {
                Some(&mob::MOB_SPECS[mt as uint])
            } else {
                None
            }
        })
    }

    /// Return whether this mob is the player avatar.
    pub fn is_player(self) -> bool {
        world::with(|w| {
            if let Some(&Kind::Mob(MobType::Player)) = w.kinds().get(self) {
                return true;
            }
            return false;
        })
    }

    pub fn has_status(self, status: Status) -> bool {
        world::with(|w| {
            if let Some(&mob) = w.mobs().get(self) {
                return mob.has_status(status);
            }
            return false;
        })
    }

    pub fn add_status(self, status: Status) {
        if !self.is_mob() { return; }
        world::with_mut(|w| w.mobs_mut().get(self).unwrap().add_status(status));
        assert!(self.has_status(status));
    }

    pub fn remove_status(self, status: Status) {
        if !self.is_mob() { return; }
        world::with_mut(|w| w.mobs_mut().get(self).unwrap().remove_status(status));
        assert!(!self.has_status(status));
    }

    pub fn has_intrinsic(self, intrinsic: Intrinsic) -> bool {
        if let Some(spec) = self.mob_spec() {
            return spec.intrinsics & intrinsic as int != 0;
        }
        return false;
    }

    /// Return whether this entity is an awake mob.
    pub fn is_active(self) -> bool {
        self.is_mob() && !self.has_status(Status::Asleep)
    }

    /// Return if the entity is a mob that should get an update this frame
    /// based on its speed properties. Does not check for status effects like
    /// sleep that might prevent actual action.
    pub fn ticks_this_frame(self) -> bool {
        if !self.is_mob() { return false; }

        let tick = flags::get_tick();
        // Go through a cycle of 5 phases to get 4 possible speeds.
        // System idea from Jeff Lait.
        let phase = tick % 5;
        match phase {
            0 => return true,
            1 => return self.has_intrinsic(Intrinsic::Fast),
            2 => return true,
            3 => return self.has_status(Status::Quick),
            4 => return !self.has_intrinsic(Intrinsic::Slow)
                        && !self.has_status(Status::Slow),
            _ => panic!("Invalid action phase"),
        }
    }

    /// Return whether the entity is a mob that will act this frame.
    pub fn acts_this_frame(self) -> bool {
        if !self.is_active() { return false; }
        return self.ticks_this_frame();
    }

    /// Return whether the entity is an awake non-player mob and should be
    /// animated with a bob.
    pub fn is_bobbing(self) -> bool {
        self.is_active() && !self.is_player()
    }

    pub fn melee(self, dir: Dir6) {
        let loc = self.location().unwrap() + dir.to_v2();
        if let Some(e) = loc.mob_at() {
            // Attack power
            let p = world::with(|w| w.mobs().get(self).unwrap().power);
            if p == 0 {
                // No fight capacity.
                return;
            }

            // Every five points of power is one certain hit.
            let full = p / 5;
            // The fractional points are one probabilistic hit.
            let partial = (p % 5) as f64 / 5.0;

            let damage = full + if rng::p(partial) { 1 } else { 0 };
            e.damage(damage)
        }
    }

    pub fn hp(self) -> int {
        world::with(|w| w.mobs().get(self).unwrap().hp)
    }

    pub fn max_hp(self) -> int {
        self.mob_spec().unwrap().power
    }

// AI methods /////////////////////////////////////////////////////////

    /// Top-level method called each frame to update the entity.
    pub fn update(self) {
        if self.is_mob() && !self.is_player() && self.ticks_this_frame() {
            self.mob_ai();
        }
    }

    /// AI routine for autonomous mobs.
    fn mob_ai(self) {
        assert!(self.is_mob());
        assert!(!self.is_player());
        assert!(self.ticks_this_frame());

        if self.has_status(Status::Asleep) {
            if let Some(p) = action::player() {
                // TODO: Line-of-sight, stealth concerns, other enemies than
                // player etc.
                if let Some(d) = p.distance_from(self) {
                    if d < 6 {
                        self.remove_status(Status::Asleep);
                    }
                }
            }

            return;
        }

        if let Some(p) = action::player() {
            let loc = self.location().unwrap();

            let vec_to_enemy = loc.v2_at(p.location().unwrap());
            if let Some(v) = vec_to_enemy {
                if v.hex_dist() == 1 {
                    // Melee range, hit.
                    self.melee(Dir6::from_v2(v));
                } else {
                    // Walk towards.
                    let pathing_depth = 16;
                    let pathing = Dijkstra::new(
                        vec![p.location().unwrap()], |&loc| !loc.blocks_walk(),
                        pathing_depth);

                    let steps = pathing.sorted_neighbors(&loc);
                    if steps.len() > 0 {
                        self.step(loc.dir6_towards(steps[0]).unwrap());
                    } else {
                        self.step(rng::with(|ref mut rng| rng.gen::<Dir6>()));
                        // TODO: Fall asleep if things get boring.
                    }
                }
            }
        }
    }

    /// Return whether this thing wants to fight the other thing.
    fn is_hostile_to(self, other: Entity) -> bool {
        if let Some(p) = action::player() {
            if self.is_mob() && other.is_mob() && self != other &&
                (self == p || other == p) {
                return true;
            }
        }
        return false;
    }

    /// Return whether the mob has hostiles in its immediate vicinity.
    pub fn is_threatened(self) -> bool {
        // XXX: Expensive.
        let range = 12u;
        let loc = self.location().unwrap();
        let seen: Vec<Location> = Fov::new(
            |pt| (loc + pt).blocks_sight(), range)
            .map(|pt| loc + pt)
            .collect();
        for loc in seen.iter() {
            if let Some(m) = loc.mob_at() {
                if m.is_hostile_to(self) {
                    return true;
                }
            }
        }
        false
    }

// Callbacks ///////////////////////////////////////////////////////////

    /// Called after the entity is moved to a new location.
    pub fn on_move_to(self, loc: Location) {
        self.do_fov();

        if self.is_player() {
            if loc.terrain().is_exit() {
                action::next_level();
            }
        }
    }

// FOV and map memory //////////////////////////////////////////////////

    fn has_map_memory(self) -> bool {
        world::with(|w| w.map_memories().get(self).is_some())
    }

    fn do_fov(self) {
        let range = 12u;
        if let Some(loc) = self.location() {
            if self.has_map_memory() {
                let seen: Vec<Location> = Fov::new(
                    |pt| (loc + pt).blocks_sight(), range)
                    .map(|pt| loc + pt)
                    .collect();
                world::with_mut(|w| {
                    if let Some(ref mut mm) = w.map_memories_mut().get(self) {
                        mm.seen.clear();
                        mm.seen.extend(seen.clone().into_iter());
                        mm.remembered.extend(seen.iter().map(|&x| x));
                    } else {
                        panic!("Couldn't bind map memory");
                    }
                });
            }
        }
    }

    pub fn forget_map(self) {
        if self.has_map_memory() {
            world::with_mut(|w| {
                if let Some(ref mut mm) = w.map_memories_mut().get(self) {
                    mm.seen.clear();
                    mm.remembered.clear();
                } else {
                    panic!("Couldn't bind map memory");
                }
            });
        }
    }
}
<commit_msg>Name method using Desc component<commit_after>use std::rand::Rng;
use calx::dijkstra::Dijkstra;
use world;
use location::{Location};
use dir6::Dir6;
use flags;
use mob;
use mob::{Status, Intrinsic, MobType};
use components::{Kind};
use geom::HexGeom;
use spatial::Place;
use action;
use fov::Fov;
use rng;
use msg;

/// Game object handle.
#[deriving(Copy, PartialEq, Eq, Clone, Hash, Show, Decodable, Encodable)]
pub struct Entity(pub uint);

impl Entity {
    /// Place the entity in a location in the game world.
    pub fn place(self, loc: Location) {
        world::with_mut(|w| w.spatial.insert_at(self, loc));
        self.on_move_to(loc);
    }

    /// Remove the entity from all the game world systems. THE ENTITY VALUE
    /// WILL BE INVALID FROM HERE ON AND USING IT WILL LEAD TO BUGS. THE
    /// CALLER IS RESPONSIBLE FOR ENSUING THAT AN ENTITY WILL NOT BE
    /// USED FROM ANYWHERE AFTER THE DELETE OPERATION.
    pub fn delete(self) {
        // COMPONENTS CHECKPOINT
        // This needs to call every component system.
        world::with_mut(|w| w.descs_mut().remove(self));
        world::with_mut(|w| w.kinds_mut().remove(self));
        world::with_mut(|w| w.map_memories_mut().remove(self));
        world::with_mut(|w| w.mobs_mut().remove(self));
        world::with_mut(|w| w.spawns_mut().remove(self));

        world::with_mut(|w| w.spatial.remove(self));

        world::with_mut(|w| w.ecs.delete(self));
    }

    pub fn blocks_walk(self) -> bool { self.is_mob() }

    /// Return the kind of the entity.
    pub fn kind(self) -> Kind {
        // XXX: Will crash if an entity has no kind specified.
        world::with(|w| *w.kinds().get(self).unwrap())
    }

    pub fn name(self) -> String {
        world::with(|w|
            match w.descs().get(self) {
                Some(desc) => desc.name.clone(),
                None => "".to_string()
            }
        )
    }

    /// Create a cloned entity that uses the current entity as a parent
    /// prototype. Components not defined in the clone entity will be read
    /// from the current entity.
    pub fn clone_at(self, loc: Location) -> Entity {
        world::with_mut(|w| {
            let e = w.ecs.new_entity(Some(self));
            w.spatial.insert_at(e, loc);
            e
        })
    }

// Spatial methods /////////////////////////////////////////////////////

    pub fn can_enter(self, loc: Location) -> bool {
        if self.is_mob() && loc.has_mobs() { return false; }
        if loc.terrain().is_door() && !self.has_intrinsic(Intrinsic::Hands) {
            // Can't open doors without hands.
            return false;
        }
        if loc.blocks_walk() { return false; }
        true
    }

    /// Return whether the entity can move in a direction.
    pub fn can_step(self, dir: Dir6) -> bool {
        let place = world::with(|w| w.spatial.get(self));
        if let Some(Place::At(loc)) = place {
            let new_loc = loc + dir.to_v2();
            return self.can_enter(new_loc);
        }
        return false;
    }

    /// Try to move the entity in direction.
    pub fn step(self, dir: Dir6) {
        let place = world::with(|w| w.spatial.get(self));
        if let Some(Place::At(loc)) = place {
            let new_loc = loc + dir.to_v2();
            if self.can_enter(new_loc) {
                world::with_mut(|w| w.spatial.insert_at(self, new_loc));
                self.on_move_to(new_loc);
            }
        }
    }

    pub fn location(self) -> Option<Location> {
        match world::with(|w| w.spatial.get(self)) {
            Some(Place::At(loc)) => Some(loc),
            Some(Place::In(e)) => e.location(),
            _ => None
        }
    }

    pub fn distance_from(self, other: Entity) -> Option<int> {
        if let (Some(loc1), Some(loc2)) = (self.location(), other.location()) {
            loc1.distance_from(loc2)
        } else {
            None
        }
    }

// Damage and lifetime /////////////////////////////////////////////////

    pub fn damage(self, amount: int) {
        if amount == 0 { return; }

        let (_amount, kill) = world::with_mut(|w| {
            if let Some(ref mut mob) = w.mobs_mut().get(self) {
                mob.hp -= amount;
                if mob.hp <= 0 {
                    // Remember this when we're out of the borrow.
                    return (amount, true);
                }
            } else {
                panic!("Damaging a non-mob");
            };
            (amount, false)
        });

        msg::push_msg(::Msg::Damage(self));
        if kill {
            self.kill();
        }
    }

    /// Do any game logic stuff related to this entity dying violently before
    /// deleting it.
    pub fn kill(self) {
        msgln!("{} dies.", self.name());
        msg::push_msg(::Msg::Gib(self.location().unwrap()));
        self.delete();
    }

// Mob methods /////////////////////////////////////////////////////////

    pub fn is_mob(self) -> bool {
        world::with(|w| {
            if let Some(&Kind::Mob(_)) = w.kinds().get(self) {
                return true;
            }
            return false;
        })
    }

    pub fn mob_spec(self) -> Option<&'static mob::MobSpec> {
        world::with(|w| {
            if let Some(&Kind::Mob(mt)) = w.kinds().get(self) {
                Some(&mob::MOB_SPECS[mt as uint])
            } else {
                None
            }
        })
    }

    /// Return whether this mob is the player avatar.
    pub fn is_player(self) -> bool {
        world::with(|w| {
            if let Some(&Kind::Mob(MobType::Player)) = w.kinds().get(self) {
                return true;
            }
            return false;
        })
    }

    pub fn has_status(self, status: Status) -> bool {
        world::with(|w| {
            if let Some(&mob) = w.mobs().get(self) {
                return mob.has_status(status);
            }
            return false;
        })
    }

    pub fn add_status(self, status: Status) {
        if !self.is_mob() { return; }
        world::with_mut(|w| w.mobs_mut().get(self).unwrap().add_status(status));
        assert!(self.has_status(status));
    }

    pub fn remove_status(self, status: Status) {
        if !self.is_mob() { return; }
        world::with_mut(|w| w.mobs_mut().get(self).unwrap().remove_status(status));
        assert!(!self.has_status(status));
    }

    pub fn has_intrinsic(self, intrinsic: Intrinsic) -> bool {
        if let Some(spec) = self.mob_spec() {
            return spec.intrinsics & intrinsic as int != 0;
        }
        return false;
    }

    /// Return whether this entity is an awake mob.
    pub fn is_active(self) -> bool {
        self.is_mob() && !self.has_status(Status::Asleep)
    }

    /// Return if the entity is a mob that should get an update this frame
    /// based on its speed properties. Does not check for status effects like
    /// sleep that might prevent actual action.
    pub fn ticks_this_frame(self) -> bool {
        if !self.is_mob() { return false; }

        let tick = flags::get_tick();
        // Go through a cycle of 5 phases to get 4 possible speeds.
        // System idea from Jeff Lait.
        let phase = tick % 5;
        match phase {
            0 => return true,
            1 => return self.has_intrinsic(Intrinsic::Fast),
            2 => return true,
            3 => return self.has_status(Status::Quick),
            4 => return !self.has_intrinsic(Intrinsic::Slow)
                        && !self.has_status(Status::Slow),
            _ => panic!("Invalid action phase"),
        }
    }

    /// Return whether the entity is a mob that will act this frame.
    pub fn acts_this_frame(self) -> bool {
        if !self.is_active() { return false; }
        return self.ticks_this_frame();
    }

    /// Return whether the entity is an awake non-player mob and should be
    /// animated with a bob.
    pub fn is_bobbing(self) -> bool {
        self.is_active() && !self.is_player()
    }

    pub fn melee(self, dir: Dir6) {
        let loc = self.location().unwrap() + dir.to_v2();
        if let Some(e) = loc.mob_at() {
            // Attack power
            let p = world::with(|w| w.mobs().get(self).unwrap().power);
            if p == 0 {
                // No fight capacity.
                return;
            }

            // Every five points of power is one certain hit.
            let full = p / 5;
            // The fractional points are one probabilistic hit.
            let partial = (p % 5) as f64 / 5.0;

            let damage = full + if rng::p(partial) { 1 } else { 0 };
            e.damage(damage)
        }
    }

    pub fn hp(self) -> int {
        world::with(|w| w.mobs().get(self).unwrap().hp)
    }

    pub fn max_hp(self) -> int {
        self.mob_spec().unwrap().power
    }

// AI methods /////////////////////////////////////////////////////////

    /// Top-level method called each frame to update the entity.
    pub fn update(self) {
        if self.is_mob() && !self.is_player() && self.ticks_this_frame() {
            self.mob_ai();
        }
    }

    /// AI routine for autonomous mobs.
    fn mob_ai(self) {
        assert!(self.is_mob());
        assert!(!self.is_player());
        assert!(self.ticks_this_frame());

        if self.has_status(Status::Asleep) {
            if let Some(p) = action::player() {
                // TODO: Line-of-sight, stealth concerns, other enemies than
                // player etc.
                if let Some(d) = p.distance_from(self) {
                    if d < 6 {
                        self.remove_status(Status::Asleep);
                    }
                }
            }

            return;
        }

        if let Some(p) = action::player() {
            let loc = self.location().unwrap();

            let vec_to_enemy = loc.v2_at(p.location().unwrap());
            if let Some(v) = vec_to_enemy {
                if v.hex_dist() == 1 {
                    // Melee range, hit.
                    self.melee(Dir6::from_v2(v));
                } else {
                    // Walk towards.
                    let pathing_depth = 16;
                    let pathing = Dijkstra::new(
                        vec![p.location().unwrap()], |&loc| !loc.blocks_walk(),
                        pathing_depth);

                    let steps = pathing.sorted_neighbors(&loc);
                    if steps.len() > 0 {
                        self.step(loc.dir6_towards(steps[0]).unwrap());
                    } else {
                        self.step(rng::with(|ref mut rng| rng.gen::<Dir6>()));
                        // TODO: Fall asleep if things get boring.
                    }
                }
            }
        }
    }

    /// Return whether this thing wants to fight the other thing.
    fn is_hostile_to(self, other: Entity) -> bool {
        if let Some(p) = action::player() {
            if self.is_mob() && other.is_mob() && self != other &&
                (self == p || other == p) {
                return true;
            }
        }
        return false;
    }

    /// Return whether the mob has hostiles in its immediate vicinity.
    pub fn is_threatened(self) -> bool {
        // XXX: Expensive.
        let range = 12u;
        let loc = self.location().unwrap();
        let seen: Vec<Location> = Fov::new(
            |pt| (loc + pt).blocks_sight(), range)
            .map(|pt| loc + pt)
            .collect();
        for loc in seen.iter() {
            if let Some(m) = loc.mob_at() {
                if m.is_hostile_to(self) {
                    return true;
                }
            }
        }
        false
    }

// Callbacks ///////////////////////////////////////////////////////////

    /// Called after the entity is moved to a new location.
    pub fn on_move_to(self, loc: Location) {
        self.do_fov();

        if self.is_player() {
            if loc.terrain().is_exit() {
                action::next_level();
            }
        }
    }

// FOV and map memory //////////////////////////////////////////////////

    fn has_map_memory(self) -> bool {
        world::with(|w| w.map_memories().get(self).is_some())
    }

    fn do_fov(self) {
        let range = 12u;
        if let Some(loc) = self.location() {
            if self.has_map_memory() {
                let seen: Vec<Location> = Fov::new(
                    |pt| (loc + pt).blocks_sight(), range)
                    .map(|pt| loc + pt)
                    .collect();
                world::with_mut(|w| {
                    if let Some(ref mut mm) = w.map_memories_mut().get(self) {
                        mm.seen.clear();
                        mm.seen.extend(seen.clone().into_iter());
                        mm.remembered.extend(seen.iter().map(|&x| x));
                    } else {
                        panic!("Couldn't bind map memory");
                    }
                });
            }
        }
    }

    pub fn forget_map(self) {
        if self.has_map_memory() {
            world::with_mut(|w| {
                if let Some(ref mut mm) = w.map_memories_mut().get(self) {
                    mm.seen.clear();
                    mm.remembered.clear();
                } else {
                    panic!("Couldn't bind map memory");
                }
            });
        }
    }
}
<|endoftext|>


<commit_before>use std::fs::File;
use std::io::prelude::*;
use std::path::Path;
use std::{env, mem, str};

use psl_lexer::request;
use publicsuffix::{List, Psl, Type};
use rspec::report::ExampleResult;

lazy_static::lazy_static! {
    static ref ROOT: String = env::var("CARGO_MANIFEST_DIR").unwrap();
    static ref LIST: List = {
        let path = Path::new(ROOT.as_str())
            .join("tests")
            .join("public_suffix_list.dat");
        let mut file = File::open(path).unwrap();
        let mut contents = String::new();
        file.read_to_string(&mut contents).unwrap();
        contents.parse().unwrap()
    };
}

#[test]
fn list_behaviour() {
    rspec::run(&rspec::describe("the official test", (), |ctx| {
        let tests = "https://raw.githubusercontent.com/publicsuffix/list/master/tests/tests.txt";
        let body = request(tests).unwrap_or_else(|_| {
            let path = Path::new(ROOT.as_str()).join("tests").join("tests.txt");
            let mut file = File::open(path).unwrap();
            let mut contents = String::new();
            file.read_to_string(&mut contents).unwrap();
            contents
        });

        let mut parse = false;

        for (i, line) in body.lines().enumerate() {
            match line {
                line if line.trim().is_empty() => {
                    parse = true;
                    continue;
                }
                line if line.starts_with("//") => {
                    continue;
                }
                line => {
                    if !parse {
                        continue;
                    }
                    let mut test = line.split_whitespace().peekable();
                    if test.peek().is_none() {
                        continue;
                    }
                    let input = match test.next() {
                        Some("null") => "",
                        Some(res) => res,
                        None => {
                            panic!(format!(
                                "line {} of the test file doesn't seem to be valid",
                                i
                            ));
                        }
                    };
                    if !expected_tld(input) {
                        continue;
                    }
                    let (expected_root, expected_suffix) = match test.next() {
                        Some("null") => (None, None),
                        Some(root) => {
                            let suffix = {
                                let parts: Vec<&str> = root.split('.').rev().collect();
                                (&parts[..parts.len() - 1])
                                    .iter()
                                    .rev()
                                    .map(|part| *part)
                                    .collect::<Vec<_>>()
                                    .join(".")
                            };
                            (Some(root.to_string()), Some(suffix.to_string()))
                        }
                        None => {
                            panic!(format!(
                                "line {} of the test file doesn't seem to be valid",
                                i
                            ));
                        }
                    };
                    let (found_root, found_suffix) =
                        if input.starts_with(".") || input.contains("..") {
                            (None, None)
                        } else {
                            LIST.domain(input.to_lowercase().as_bytes())
                                .map(|d| {
                                    let domain = str::from_utf8(d.as_bytes()).unwrap().to_string();
                                    let suffix =
                                        str::from_utf8(d.suffix().as_bytes()).unwrap().to_string();
                                    (Some(domain), Some(suffix))
                                })
                                .unwrap_or((None, None))
                        };
                    ctx.when(msg(format!("input is `{}`", input)), |ctx| {
                        let full_domain = expected_root.is_some();

                        ctx.it(msg(format!("means the root domain {}", val(&expected_root))), move |_| {
                            if expected_root == found_root {
                                ExampleResult::Success
                            } else {
                                let msg = format!("expected `{:?}` but found `{:?}` on line {} of `test_psl.txt`", expected_root, found_root, i+1);
                                ExampleResult::Failure(Some(msg))
                            }
                        });

                        if full_domain {
                            ctx.it(msg(format!("also means the suffix {}", val(&expected_suffix))), move |_| {
                                if expected_suffix == found_suffix {
                                    ExampleResult::Success
                                } else {
                                    let msg = format!("expected `{:?}` but found `{:?}` on line {} of `test_psl.txt`", expected_suffix, found_suffix, i+1);
                                    ExampleResult::Failure(Some(msg))
                                }
                            });
                        }
                    });
                }
            }
        }
    }));

    rspec::run(&rspec::describe("suffix tests", (), |ctx| {
        let extra = vec![
            (
                "gp-id-ter-acc-1.to.gp-kl-cas-11-ses001-ses-1.wdsl.5m.za",
                "za",
            ),
            ("yokohama.jp", "jp"),
            ("kobe.jp", "jp"),
            #[cfg(feature = "anycase")]
            ("foo.bar.platformsh.Site", "bar.platformsh.Site"),
            ("bar.platformsh.site", "bar.platformsh.site"),
            ("platform.sh", "sh"),
            ("sh", "sh"),
            (".", "."),
            ("example.com.", "com."),
            ("www.食狮.中国", "中国"),
            ("www.xn--85x722f.xn--55qx5d.cn", "xn--55qx5d.cn"),
            ("a.b.example.uk.com", "uk.com"),
            ("_tcp.example.com.", "com."),
        ];

        for (input, expected) in extra {
            if !expected_tld(input) {
                continue;
            }
            ctx.when(msg(format!("input is `{}`", input)), |ctx| {
                let expected_suffix = Some(expected);
                ctx.it(
                    msg(format!(
                        "means the suffix {}",
                        val(&expected_suffix.map(ToString::to_string))
                    )),
                    move |_| {
                        let suffix = LIST.suffix(input.as_bytes()).unwrap();
                        if suffix == expected {
                            ExampleResult::Success
                        } else {
                            let msg = format!(
                                "expected `{:?}` but found `{:?}`",
                                expected_suffix,
                                Some(str::from_utf8(suffix.as_bytes()).unwrap().to_string())
                            );
                            ExampleResult::Failure(Some(msg))
                        }
                    },
                );
            });
        }
    }));

    rspec::run(&rspec::describe("suffix type tests", (), |ctx| {
        let extra = vec![
            (
                "gp-id-ter-acc-1.to.gp-kl-cas-11-ses001-ses-1.wdsl.5m.za",
                false,
                None,
            ),
            ("yokohama.jp", true, Some(Type::Icann)),
            ("kobe.jp", true, Some(Type::Icann)),
            ("foo.bar.platformsh.site", true, Some(Type::Private)),
            ("bar.platformsh.site", true, Some(Type::Private)),
            ("platform.sh", true, Some(Type::Icann)),
            ("sh", true, Some(Type::Icann)),
            (".", false, None),
            ("example.gafregsrse", false, None),
            ("www.食狮.中国", true, Some(Type::Icann)),
            ("www.xn--85x722f.xn--55qx5d.cn", true, Some(Type::Icann)),
        ];

        for (input, known_suffix, typ) in extra {
            if !expected_tld(input) {
                continue;
            }
            ctx.when(msg(format!("input is `{}`", input)), |ctx| {
                ctx.it(
                    msg(format!(
                        "means known suffix {}",
                        val(&Some(known_suffix.to_string()))
                    )),
                    move |_| {
                        let suffix = LIST.suffix(input.as_bytes()).unwrap();
                        assert_eq!(suffix.typ(), typ);
                        if suffix.is_known() == known_suffix {
                            ExampleResult::Success
                        } else {
                            let msg = format!(
                                "expected `{:?}` but found `{:?}`",
                                known_suffix,
                                suffix.is_known()
                            );
                            ExampleResult::Failure(Some(msg))
                        }
                    },
                );
            });
        }
    }));
}

// Converts a String to &'static str
//
// This will leak memory but that's OK for our testing purposes
fn msg(s: String) -> &'static str {
    unsafe {
        let ret = mem::transmute(&s as &str);
        mem::forget(s);
        ret
    }
}

fn val(s: &Option<String>) -> String {
    match *s {
        Some(ref v) => format!("should be `{}`", v),
        None => format!("is invalid"),
    }
}

fn expected_tld(input: &str) -> bool {
    let var = if let Ok(var) = env::var("PSL_TLD") {
        var
    } else {
        String::new()
    };
    var.trim().is_empty() || input.trim().trim_end_matches('.').ends_with(&var)
}
<commit_msg>allow tests to run without the punycode feature<commit_after>use std::fs::File;
use std::io::prelude::*;
use std::path::Path;
use std::{env, mem, str};

use psl_lexer::request;
use publicsuffix::{List, Psl, Type};
use rspec::report::ExampleResult;

lazy_static::lazy_static! {
    static ref ROOT: String = env::var("CARGO_MANIFEST_DIR").unwrap();
    static ref LIST: List = {
        let path = Path::new(ROOT.as_str())
            .join("tests")
            .join("public_suffix_list.dat");
        let mut file = File::open(path).unwrap();
        let mut contents = String::new();
        file.read_to_string(&mut contents).unwrap();
        contents.parse().unwrap()
    };
}

#[test]
fn list_behaviour() {
    rspec::run(&rspec::describe("the official test", (), |ctx| {
        let tests = "https://raw.githubusercontent.com/publicsuffix/list/master/tests/tests.txt";
        let body = request(tests).unwrap_or_else(|_| {
            let path = Path::new(ROOT.as_str()).join("tests").join("tests.txt");
            let mut file = File::open(path).unwrap();
            let mut contents = String::new();
            file.read_to_string(&mut contents).unwrap();
            contents
        });

        let mut parse = false;

        for (i, line) in body.lines().enumerate() {
            match line {
                line if line.trim().is_empty() => {
                    parse = true;
                    continue;
                }
                line if line.starts_with("//") => {
                    continue;
                }
                line => {
                    if !parse {
                        continue;
                    }
                    let mut test = line.split_whitespace().peekable();
                    if test.peek().is_none() {
                        continue;
                    }
                    let input = match test.next() {
                        Some("null") => "",
                        Some(res) => res,
                        None => {
                            panic!(format!(
                                "line {} of the test file doesn't seem to be valid",
                                i
                            ));
                        }
                    };
                    if !expected_tld(input)
                        || (cfg!(not(feature = "punycode")) && input.contains("xn--"))
                    {
                        continue;
                    }
                    let (expected_root, expected_suffix) = match test.next() {
                        Some("null") => (None, None),
                        Some(root) => {
                            let suffix = {
                                let parts: Vec<&str> = root.split('.').rev().collect();
                                (&parts[..parts.len() - 1])
                                    .iter()
                                    .rev()
                                    .map(|part| *part)
                                    .collect::<Vec<_>>()
                                    .join(".")
                            };
                            (Some(root.to_string()), Some(suffix.to_string()))
                        }
                        None => {
                            panic!(format!(
                                "line {} of the test file doesn't seem to be valid",
                                i
                            ));
                        }
                    };
                    let (found_root, found_suffix) =
                        if input.starts_with(".") || input.contains("..") {
                            (None, None)
                        } else {
                            LIST.domain(input.to_lowercase().as_bytes())
                                .map(|d| {
                                    let domain = str::from_utf8(d.as_bytes()).unwrap().to_string();
                                    let suffix =
                                        str::from_utf8(d.suffix().as_bytes()).unwrap().to_string();
                                    (Some(domain), Some(suffix))
                                })
                                .unwrap_or((None, None))
                        };
                    ctx.when(msg(format!("input is `{}`", input)), |ctx| {
                        let full_domain = expected_root.is_some();

                        ctx.it(msg(format!("means the root domain {}", val(&expected_root))), move |_| {
                            if expected_root == found_root {
                                ExampleResult::Success
                            } else {
                                let msg = format!("expected `{:?}` but found `{:?}` on line {} of `test_psl.txt`", expected_root, found_root, i+1);
                                ExampleResult::Failure(Some(msg))
                            }
                        });

                        if full_domain {
                            ctx.it(msg(format!("also means the suffix {}", val(&expected_suffix))), move |_| {
                                if expected_suffix == found_suffix {
                                    ExampleResult::Success
                                } else {
                                    let msg = format!("expected `{:?}` but found `{:?}` on line {} of `test_psl.txt`", expected_suffix, found_suffix, i+1);
                                    ExampleResult::Failure(Some(msg))
                                }
                            });
                        }
                    });
                }
            }
        }
    }));

    rspec::run(&rspec::describe("suffix tests", (), |ctx| {
        let extra = vec![
            (
                "gp-id-ter-acc-1.to.gp-kl-cas-11-ses001-ses-1.wdsl.5m.za",
                "za",
            ),
            ("yokohama.jp", "jp"),
            ("kobe.jp", "jp"),
            #[cfg(feature = "anycase")]
            ("foo.bar.platformsh.Site", "bar.platformsh.Site"),
            ("bar.platformsh.site", "bar.platformsh.site"),
            ("platform.sh", "sh"),
            ("sh", "sh"),
            (".", "."),
            ("example.com.", "com."),
            ("www.食狮.中国", "中国"),
            #[cfg(feature = "punycode")]
            ("www.xn--85x722f.xn--55qx5d.cn", "xn--55qx5d.cn"),
            ("a.b.example.uk.com", "uk.com"),
            ("_tcp.example.com.", "com."),
        ];

        for (input, expected) in extra {
            if !expected_tld(input) {
                continue;
            }
            ctx.when(msg(format!("input is `{}`", input)), |ctx| {
                let expected_suffix = Some(expected);
                ctx.it(
                    msg(format!(
                        "means the suffix {}",
                        val(&expected_suffix.map(ToString::to_string))
                    )),
                    move |_| {
                        let suffix = LIST.suffix(input.as_bytes()).unwrap();
                        if suffix == expected {
                            ExampleResult::Success
                        } else {
                            let msg = format!(
                                "expected `{:?}` but found `{:?}`",
                                expected_suffix,
                                Some(str::from_utf8(suffix.as_bytes()).unwrap().to_string())
                            );
                            ExampleResult::Failure(Some(msg))
                        }
                    },
                );
            });
        }
    }));

    rspec::run(&rspec::describe("suffix type tests", (), |ctx| {
        let extra = vec![
            (
                "gp-id-ter-acc-1.to.gp-kl-cas-11-ses001-ses-1.wdsl.5m.za",
                false,
                None,
            ),
            ("yokohama.jp", true, Some(Type::Icann)),
            ("kobe.jp", true, Some(Type::Icann)),
            ("foo.bar.platformsh.site", true, Some(Type::Private)),
            ("bar.platformsh.site", true, Some(Type::Private)),
            ("platform.sh", true, Some(Type::Icann)),
            ("sh", true, Some(Type::Icann)),
            (".", false, None),
            ("example.gafregsrse", false, None),
            ("www.食狮.中国", true, Some(Type::Icann)),
            #[cfg(feature = "punycode")]
            ("www.xn--85x722f.xn--55qx5d.cn", true, Some(Type::Icann)),
        ];

        for (input, known_suffix, typ) in extra {
            if !expected_tld(input) {
                continue;
            }
            ctx.when(msg(format!("input is `{}`", input)), |ctx| {
                ctx.it(
                    msg(format!(
                        "means known suffix {}",
                        val(&Some(known_suffix.to_string()))
                    )),
                    move |_| {
                        let suffix = LIST.suffix(input.as_bytes()).unwrap();
                        assert_eq!(suffix.typ(), typ);
                        if suffix.is_known() == known_suffix {
                            ExampleResult::Success
                        } else {
                            let msg = format!(
                                "expected `{:?}` but found `{:?}`",
                                known_suffix,
                                suffix.is_known()
                            );
                            ExampleResult::Failure(Some(msg))
                        }
                    },
                );
            });
        }
    }));
}

// Converts a String to &'static str
//
// This will leak memory but that's OK for our testing purposes
fn msg(s: String) -> &'static str {
    unsafe {
        let ret = mem::transmute(&s as &str);
        mem::forget(s);
        ret
    }
}

fn val(s: &Option<String>) -> String {
    match *s {
        Some(ref v) => format!("should be `{}`", v),
        None => format!("is invalid"),
    }
}

fn expected_tld(input: &str) -> bool {
    let var = if let Ok(var) = env::var("PSL_TLD") {
        var
    } else {
        String::new()
    };
    var.trim().is_empty() || input.trim().trim_end_matches('.').ends_with(&var)
}
<|endoftext|>


<commit_before>#[macro_use] extern crate match_any;

#[test]
fn empty() {
    let a: Box<std::any::Any> = Box::new(10);
    println!("{:?}", a);

    match_any!(a =>
        x: i32 => {
            assert_eq!(x, 10);
        }
    );
}

// TODO: Add.
// #[bench]
<commit_msg>Build fix<commit_after>#[macro_use] extern crate match_any;

#[test]
fn empty() {
    let a: Box<std::any::Any> = Box::new(10);
    println!("{:?}", a);

    match_any!(a =>
        x: i32 => {
            assert_eq!(*x, 10);
        }
    );
}

// TODO: Add.
// #[bench]
<|endoftext|>


<commit_before>use std::collections::{BTreeMap, HashSet};
use std::ops::Not;

use cranelift_codegen::cursor::{Cursor, FuncCursor};
use cranelift_codegen::entity::EntitySet;
use cranelift_codegen::ir::{InstructionData, Opcode, ProgramOrder, ValueDef};
use cranelift_codegen::ir::immediates::Offset32;

use crate::prelude::*;

/// Workaround for `StackSlot` not implementing `Ord`.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
struct OrdStackSlot(StackSlot);

impl PartialOrd for OrdStackSlot {
    fn partial_cmp(&self, rhs: &Self) -> Option<std::cmp::Ordering> {
        self.0.as_u32().partial_cmp(&rhs.0.as_u32())
    }
}

impl Ord for OrdStackSlot {
    fn cmp(&self, rhs: &Self) -> std::cmp::Ordering {
        self.0.as_u32().cmp(&rhs.0.as_u32())
    }
}

#[derive(Debug, Default)]
struct StackSlotUsage {
    stack_addr: HashSet<Inst>,
    stack_load: HashSet<Inst>,
    stack_store: HashSet<Inst>,
}

pub(super) fn optimize_function(
    ctx: &mut Context,
    clif_comments: &mut crate::pretty_clif::CommentWriter,
    name: String, // FIXME remove
) {
    ctx.flowgraph(); // Compute cfg and domtree.

    combine_stack_addr_with_load_store(&mut ctx.func);

    // Record all stack_addr, stack_load and stack_store instructions.
    let mut stack_slot_usage_map = BTreeMap::<OrdStackSlot, StackSlotUsage>::new();

    let mut cursor = FuncCursor::new(&mut ctx.func);
    while let Some(_ebb) = cursor.next_ebb() {
        while let Some(inst) = cursor.next_inst() {
            match cursor.func.dfg[inst] {
                InstructionData::StackLoad {
                    opcode: Opcode::StackAddr,
                    stack_slot,
                    offset: _,
                } => {
                    stack_slot_usage_map.entry(OrdStackSlot(stack_slot)).or_insert_with(StackSlotUsage::default).stack_addr.insert(inst);
                }
                InstructionData::StackLoad {
                    opcode: Opcode::StackLoad,
                    stack_slot,
                    offset: _,
                } => {
                    stack_slot_usage_map.entry(OrdStackSlot(stack_slot)).or_insert_with(StackSlotUsage::default).stack_load.insert(inst);
                }
                InstructionData::StackStore {
                    opcode: Opcode::StackStore,
                    arg: _,
                    stack_slot,
                    offset: _,
                } => {
                    stack_slot_usage_map.entry(OrdStackSlot(stack_slot)).or_insert_with(StackSlotUsage::default).stack_store.insert(inst);
                }
                _ => {}
            }
        }
    }

    println!("{}:\nstack slot usage: {:?}", name, stack_slot_usage_map);

    remove_unused_stack_addr_and_stack_load(&mut ctx.func, &mut stack_slot_usage_map);

    println!("stack slot usage (after): {:?}", stack_slot_usage_map);

    for (stack_slot, users) in stack_slot_usage_map.iter_mut() {
        if users.stack_addr.is_empty().not() {
            // Stack addr leaked; there may be unknown loads and stores.
            // FIXME use stacked borrows to optimize
            continue;
        }

        for load in users.stack_load.clone().drain() {
            let load_ebb = ctx.func.layout.inst_ebb(load).unwrap();
            let loaded_value = ctx.func.dfg.inst_results(load)[0];
            let loaded_type = ctx.func.dfg.value_type(loaded_value);

            let potential_stores = users.stack_store.iter().cloned().filter(|&store| {
                // Check if the store modified some memory accessed by the load.

                match spatial_overlap(&ctx.func, load, store) {
                    SpatialOverlap::No => false, // Can never be the source of the loaded value.
                    SpatialOverlap::Partial | SpatialOverlap::Full => true,
                }
            }).filter(|&store| {
                // Check if the store may have happened before the load.

                let store_ebb = ctx.func.layout.inst_ebb(store).unwrap();
                if load_ebb == store_ebb {
                    ctx.func.layout.cmp(store, load) == std::cmp::Ordering::Less
                } else {
                    // FIXME O(stack_load count * ebb count)
                    // FIXME reuse memory allocations
                    let mut visited = EntitySet::new();
                    let mut todo = EntitySet::new();
                    todo.insert(load_ebb);
                    while let Some(ebb) = todo.pop() {
                        if visited.contains(ebb) {
                            continue;
                        }
                        visited.insert(ebb);
                        if ebb == store_ebb {
                            return true;
                        }
                        for bb in ctx.cfg.pred_iter(ebb) {
                            todo.insert(bb.ebb);
                        }
                    }
                    false
                }
            }).collect::<Vec<Inst>>();
            for &store in &potential_stores {
                println!("Potential store -> load forwarding {} -> {} ({:?})", ctx.func.dfg.display_inst(store, None), ctx.func.dfg.display_inst(load, None), spatial_overlap(ctx.func, load, store));
            }
            match *potential_stores {
                [] => println!("[{}] [BUG?] Reading uninitialized memory", name),
                [store] if spatial_overlap(&ctx.func, load, store) == SpatialOverlap::Full => {
                    let store_ebb = ctx.func.layout.inst_ebb(store).unwrap();
                    let stored_value = ctx.func.dfg.inst_args(store)[0];
                    let stored_type = ctx.func.dfg.value_type(stored_value);
                    if stored_type == loaded_type && store_ebb == load_ebb {
                        println!("Store to load forward {} -> {}", store, load);
                        ctx.func.dfg.detach_results(load);
                        ctx.func.dfg.replace(load).nop();
                        ctx.func.dfg.change_to_alias(loaded_value, stored_value);
                        users.stack_load.remove(&load);
                    }
                }
                _ => {} // FIXME implement this
            }
        }

        if users.stack_load.is_empty() {
            // Never loaded; can safely remove all stores and the stack slot.
            for user in users.stack_store.drain() {
                println!("[{}] Remove dead stack store {} of {}", name, user, stack_slot.0);
                ctx.func.dfg.replace(user).nop();
            }
        }

        if users.stack_store.is_empty() && users.stack_load.is_empty() {
            // FIXME make stack_slot zero sized.
        }
    }

    println!();
}

fn combine_stack_addr_with_load_store(func: &mut Function) {
    // Turn load and store into stack_load and stack_store when possible.
    let mut cursor = FuncCursor::new(func);
    while let Some(_ebb) = cursor.next_ebb() {
        while let Some(inst) = cursor.next_inst() {
            match cursor.func.dfg[inst] {
                InstructionData::Load { opcode: Opcode::Load, arg: addr, flags: _, offset } => {
                    if cursor.func.dfg.ctrl_typevar(inst) == types::I128 || cursor.func.dfg.ctrl_typevar(inst).is_vector() {
                        continue; // WORKAROUD: stack_load.i128 not yet implemented
                    }
                    if let Some((stack_slot, stack_addr_offset)) = try_get_stack_slot_and_offset_for_addr(cursor.func, addr) {
                        if let Some(combined_offset) = offset.try_add_i64(stack_addr_offset.into()) {
                            let ty = cursor.func.dfg.ctrl_typevar(inst);
                            cursor.func.dfg.replace(inst).stack_load(ty, stack_slot, combined_offset);
                        }
                    }
                }
                InstructionData::Store { opcode: Opcode::Store, args: [value, addr], flags: _, offset } => {
                    if cursor.func.dfg.ctrl_typevar(inst) == types::I128 || cursor.func.dfg.ctrl_typevar(inst).is_vector() {
                        continue; // WORKAROUND: stack_store.i128 not yet implemented
                    }
                    if let Some((stack_slot, stack_addr_offset)) = try_get_stack_slot_and_offset_for_addr(cursor.func, addr) {
                        if let Some(combined_offset) = offset.try_add_i64(stack_addr_offset.into()) {
                            cursor.func.dfg.replace(inst).stack_store(value, stack_slot, combined_offset);
                        }
                    }
                }
                _ => {}
            }
        }
    }
}

fn remove_unused_stack_addr_and_stack_load(func: &mut Function, stack_slot_usage_map: &mut BTreeMap<OrdStackSlot, StackSlotUsage>) {
    // FIXME incrementally rebuild on each call?
    let mut stack_addr_load_insts_users = HashMap::<Inst, HashSet<Inst>>::new();

    let mut cursor = FuncCursor::new(func);
    while let Some(_ebb) = cursor.next_ebb() {
        while let Some(inst) = cursor.next_inst() {
            for &arg in cursor.func.dfg.inst_args(inst) {
                if let ValueDef::Result(arg_origin, 0) = cursor.func.dfg.value_def(arg) {
                    match cursor.func.dfg[arg_origin].opcode() {
                        Opcode::StackAddr | Opcode::StackLoad => {
                            stack_addr_load_insts_users.entry(arg_origin).or_insert_with(HashSet::new).insert(inst);
                        }
                        _ => {}
                    }
                }
            }
        }
    }

    println!("stack_addr/stack_load users: {:?}", stack_addr_load_insts_users);

    for inst in stack_addr_load_insts_users.keys() {
        let mut is_recorded_stack_addr_or_stack_load = false;
        for stack_slot_users in stack_slot_usage_map.values() {
            is_recorded_stack_addr_or_stack_load |= stack_slot_users.stack_addr.contains(inst) || stack_slot_users.stack_load.contains(inst);
        }
        assert!(is_recorded_stack_addr_or_stack_load);
    }

    // Replace all unused stack_addr and stack_load instructions with nop.
    for stack_slot_users in stack_slot_usage_map.values_mut() {
        // FIXME remove clone
        for &inst in stack_slot_users.stack_addr.clone().iter() {
            if stack_addr_load_insts_users.get(&inst).map(|users| users.is_empty()).unwrap_or(true) {
                println!("Removing unused stack_addr {}", inst);
                func.dfg.detach_results(inst);
                func.dfg.replace(inst).nop();
                stack_slot_users.stack_addr.remove(&inst);
            }
        }

        for &inst in stack_slot_users.stack_load.clone().iter() {
            if stack_addr_load_insts_users.get(&inst).map(|users| users.is_empty()).unwrap_or(true) {
                println!("Removing unused stack_addr {}", inst);
                func.dfg.detach_results(inst);
                func.dfg.replace(inst).nop();
                stack_slot_users.stack_load.remove(&inst);
            }
        }
    }
}

fn try_get_stack_slot_and_offset_for_addr(func: &Function, addr: Value) -> Option<(StackSlot, Offset32)> {
    if let ValueDef::Result(addr_inst, 0) = func.dfg.value_def(addr) {
        if let InstructionData::StackLoad {
            opcode: Opcode::StackAddr,
            stack_slot,
            offset,
        } = func.dfg[addr_inst] {
            return Some((stack_slot, offset));
        }
    }
    None
}

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
enum SpatialOverlap {
    No,
    Partial,
    Full,
}

fn spatial_overlap(func: &Function, src: Inst, dest: Inst) -> SpatialOverlap {
    fn inst_info(func: &Function, inst: Inst) -> (StackSlot, Offset32, u32) {
        match func.dfg[inst] {
            InstructionData::StackLoad {
                opcode: Opcode::StackAddr,
                stack_slot,
                offset,
            }
            | InstructionData::StackLoad {
                opcode: Opcode::StackLoad,
                stack_slot,
                offset,
            }
            | InstructionData::StackStore {
                opcode: Opcode::StackStore,
                stack_slot,
                offset,
                arg: _,
            } => (stack_slot, offset, func.dfg.ctrl_typevar(inst).bytes()),
            _ => unreachable!("{:?}", func.dfg[inst]),
        }
    }

    let (src_ss, src_offset, src_size) = inst_info(func, src);
    let (dest_ss, dest_offset, dest_size) = inst_info(func, dest);

    if src_ss != dest_ss {
        return SpatialOverlap::No;
    }

    if src_offset == dest_offset && src_size == dest_size {
        return SpatialOverlap::Full;
    }

    let src_end: i64 = src_offset.try_add_i64(i64::from(src_size)).unwrap().into();
    let dest_end: i64 = dest_offset.try_add_i64(i64::from(dest_size)).unwrap().into();
    if src_end <= dest_offset.into() || dest_end <= src_offset.into() {
        return SpatialOverlap::No;
    }

    SpatialOverlap::Partial
}
<commit_msg>Extract temporal_order function<commit_after>use std::collections::{BTreeMap, HashSet};
use std::ops::Not;

use cranelift_codegen::cursor::{Cursor, FuncCursor};
use cranelift_codegen::entity::EntitySet;
use cranelift_codegen::ir::{InstructionData, Opcode, ProgramOrder, ValueDef};
use cranelift_codegen::ir::immediates::Offset32;

use crate::prelude::*;

/// Workaround for `StackSlot` not implementing `Ord`.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
struct OrdStackSlot(StackSlot);

impl PartialOrd for OrdStackSlot {
    fn partial_cmp(&self, rhs: &Self) -> Option<std::cmp::Ordering> {
        self.0.as_u32().partial_cmp(&rhs.0.as_u32())
    }
}

impl Ord for OrdStackSlot {
    fn cmp(&self, rhs: &Self) -> std::cmp::Ordering {
        self.0.as_u32().cmp(&rhs.0.as_u32())
    }
}

#[derive(Debug, Default)]
struct StackSlotUsage {
    stack_addr: HashSet<Inst>,
    stack_load: HashSet<Inst>,
    stack_store: HashSet<Inst>,
}

pub(super) fn optimize_function(
    ctx: &mut Context,
    clif_comments: &mut crate::pretty_clif::CommentWriter,
    name: String, // FIXME remove
) {
    ctx.flowgraph(); // Compute cfg and domtree.

    combine_stack_addr_with_load_store(&mut ctx.func);

    // Record all stack_addr, stack_load and stack_store instructions.
    let mut stack_slot_usage_map = BTreeMap::<OrdStackSlot, StackSlotUsage>::new();

    let mut cursor = FuncCursor::new(&mut ctx.func);
    while let Some(_ebb) = cursor.next_ebb() {
        while let Some(inst) = cursor.next_inst() {
            match cursor.func.dfg[inst] {
                InstructionData::StackLoad {
                    opcode: Opcode::StackAddr,
                    stack_slot,
                    offset: _,
                } => {
                    stack_slot_usage_map.entry(OrdStackSlot(stack_slot)).or_insert_with(StackSlotUsage::default).stack_addr.insert(inst);
                }
                InstructionData::StackLoad {
                    opcode: Opcode::StackLoad,
                    stack_slot,
                    offset: _,
                } => {
                    stack_slot_usage_map.entry(OrdStackSlot(stack_slot)).or_insert_with(StackSlotUsage::default).stack_load.insert(inst);
                }
                InstructionData::StackStore {
                    opcode: Opcode::StackStore,
                    arg: _,
                    stack_slot,
                    offset: _,
                } => {
                    stack_slot_usage_map.entry(OrdStackSlot(stack_slot)).or_insert_with(StackSlotUsage::default).stack_store.insert(inst);
                }
                _ => {}
            }
        }
    }

    // FIXME Repeat following instructions until fixpoint.

    println!("{}:\nstack slot usage: {:?}", name, stack_slot_usage_map);

    remove_unused_stack_addr_and_stack_load(&mut ctx.func, &mut stack_slot_usage_map);

    println!("stack slot usage (after): {:?}", stack_slot_usage_map);

    for (stack_slot, users) in stack_slot_usage_map.iter_mut() {
        if users.stack_addr.is_empty().not() {
            // Stack addr leaked; there may be unknown loads and stores.
            // FIXME use stacked borrows to optimize
            continue;
        }

        for load in users.stack_load.clone().drain() {
            let load_ebb = ctx.func.layout.inst_ebb(load).unwrap();
            let loaded_value = ctx.func.dfg.inst_results(load)[0];
            let loaded_type = ctx.func.dfg.value_type(loaded_value);

            let potential_stores = users.stack_store.iter().cloned().filter(|&store| {
                // Check if the store modified some memory accessed by the load.

                match spatial_overlap(&ctx.func, load, store) {
                    SpatialOverlap::No => false, // Can never be the source of the loaded value.
                    SpatialOverlap::Partial | SpatialOverlap::Full => true,
                }
            }).filter(|&store| {
                match temporal_order(&*ctx, load, store) {
                    TemporalOrder::NeverBefore => false, // Can never be the source of the loaded value.
                    TemporalOrder::MaybeBefore | TemporalOrder::DefinitivelyBefore => true,
                }
            }).collect::<Vec<Inst>>();
            for &store in &potential_stores {
                println!(
                    "Potential store -> load forwarding {} -> {} ({:?}, {:?})",
                    ctx.func.dfg.display_inst(store, None),
                    ctx.func.dfg.display_inst(load, None),
                    spatial_overlap(&ctx.func, store, load),
                    temporal_order(&*ctx, store, load),
                );
            }
            match *potential_stores {
                [] => println!("[{}] [BUG?] Reading uninitialized memory", name),
                [store] if spatial_overlap(&ctx.func, load, store) == SpatialOverlap::Full && temporal_order(&ctx, load, store) == TemporalOrder::DefinitivelyBefore => {
                    let store_ebb = ctx.func.layout.inst_ebb(store).unwrap();
                    let stored_value = ctx.func.dfg.inst_args(store)[0];
                    let stored_type = ctx.func.dfg.value_type(stored_value);
                    if stored_type == loaded_type && store_ebb == load_ebb {
                        println!("Store to load forward {} -> {}", store, load);
                        ctx.func.dfg.detach_results(load);
                        ctx.func.dfg.replace(load).nop();
                        ctx.func.dfg.change_to_alias(loaded_value, stored_value);
                        users.stack_load.remove(&load);
                    }
                }
                _ => {} // FIXME implement this
            }
        }

        if users.stack_load.is_empty() {
            // Never loaded; can safely remove all stores and the stack slot.
            // FIXME also remove stores when there is always a next store before a load.
            for user in users.stack_store.drain() {
                println!("[{}] Remove dead stack store {} of {}", name, user, stack_slot.0);
                ctx.func.dfg.replace(user).nop();
            }
        }

        if users.stack_store.is_empty() && users.stack_load.is_empty() {
            // FIXME make stack_slot zero sized.
        }
    }

    println!();
}

fn combine_stack_addr_with_load_store(func: &mut Function) {
    // Turn load and store into stack_load and stack_store when possible.
    let mut cursor = FuncCursor::new(func);
    while let Some(_ebb) = cursor.next_ebb() {
        while let Some(inst) = cursor.next_inst() {
            match cursor.func.dfg[inst] {
                InstructionData::Load { opcode: Opcode::Load, arg: addr, flags: _, offset } => {
                    if cursor.func.dfg.ctrl_typevar(inst) == types::I128 || cursor.func.dfg.ctrl_typevar(inst).is_vector() {
                        continue; // WORKAROUD: stack_load.i128 not yet implemented
                    }
                    if let Some((stack_slot, stack_addr_offset)) = try_get_stack_slot_and_offset_for_addr(cursor.func, addr) {
                        if let Some(combined_offset) = offset.try_add_i64(stack_addr_offset.into()) {
                            let ty = cursor.func.dfg.ctrl_typevar(inst);
                            cursor.func.dfg.replace(inst).stack_load(ty, stack_slot, combined_offset);
                        }
                    }
                }
                InstructionData::Store { opcode: Opcode::Store, args: [value, addr], flags: _, offset } => {
                    if cursor.func.dfg.ctrl_typevar(inst) == types::I128 || cursor.func.dfg.ctrl_typevar(inst).is_vector() {
                        continue; // WORKAROUND: stack_store.i128 not yet implemented
                    }
                    if let Some((stack_slot, stack_addr_offset)) = try_get_stack_slot_and_offset_for_addr(cursor.func, addr) {
                        if let Some(combined_offset) = offset.try_add_i64(stack_addr_offset.into()) {
                            cursor.func.dfg.replace(inst).stack_store(value, stack_slot, combined_offset);
                        }
                    }
                }
                _ => {}
            }
        }
    }
}

fn remove_unused_stack_addr_and_stack_load(func: &mut Function, stack_slot_usage_map: &mut BTreeMap<OrdStackSlot, StackSlotUsage>) {
    // FIXME incrementally rebuild on each call?
    let mut stack_addr_load_insts_users = HashMap::<Inst, HashSet<Inst>>::new();

    let mut cursor = FuncCursor::new(func);
    while let Some(_ebb) = cursor.next_ebb() {
        while let Some(inst) = cursor.next_inst() {
            for &arg in cursor.func.dfg.inst_args(inst) {
                if let ValueDef::Result(arg_origin, 0) = cursor.func.dfg.value_def(arg) {
                    match cursor.func.dfg[arg_origin].opcode() {
                        Opcode::StackAddr | Opcode::StackLoad => {
                            stack_addr_load_insts_users.entry(arg_origin).or_insert_with(HashSet::new).insert(inst);
                        }
                        _ => {}
                    }
                }
            }
        }
    }

    println!("stack_addr/stack_load users: {:?}", stack_addr_load_insts_users);

    for inst in stack_addr_load_insts_users.keys() {
        let mut is_recorded_stack_addr_or_stack_load = false;
        for stack_slot_users in stack_slot_usage_map.values() {
            is_recorded_stack_addr_or_stack_load |= stack_slot_users.stack_addr.contains(inst) || stack_slot_users.stack_load.contains(inst);
        }
        assert!(is_recorded_stack_addr_or_stack_load);
    }

    // Replace all unused stack_addr and stack_load instructions with nop.
    for stack_slot_users in stack_slot_usage_map.values_mut() {
        // FIXME remove clone
        for &inst in stack_slot_users.stack_addr.clone().iter() {
            if stack_addr_load_insts_users.get(&inst).map(|users| users.is_empty()).unwrap_or(true) {
                println!("Removing unused stack_addr {}", inst);
                func.dfg.detach_results(inst);
                func.dfg.replace(inst).nop();
                stack_slot_users.stack_addr.remove(&inst);
            }
        }

        for &inst in stack_slot_users.stack_load.clone().iter() {
            if stack_addr_load_insts_users.get(&inst).map(|users| users.is_empty()).unwrap_or(true) {
                println!("Removing unused stack_addr {}", inst);
                func.dfg.detach_results(inst);
                func.dfg.replace(inst).nop();
                stack_slot_users.stack_load.remove(&inst);
            }
        }
    }
}

fn try_get_stack_slot_and_offset_for_addr(func: &Function, addr: Value) -> Option<(StackSlot, Offset32)> {
    if let ValueDef::Result(addr_inst, 0) = func.dfg.value_def(addr) {
        if let InstructionData::StackLoad {
            opcode: Opcode::StackAddr,
            stack_slot,
            offset,
        } = func.dfg[addr_inst] {
            return Some((stack_slot, offset));
        }
    }
    None
}

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
enum SpatialOverlap {
    No,
    Partial,
    Full,
}

fn spatial_overlap(func: &Function, src: Inst, dest: Inst) -> SpatialOverlap {
    fn inst_info(func: &Function, inst: Inst) -> (StackSlot, Offset32, u32) {
        match func.dfg[inst] {
            InstructionData::StackLoad {
                opcode: Opcode::StackAddr,
                stack_slot,
                offset,
            }
            | InstructionData::StackLoad {
                opcode: Opcode::StackLoad,
                stack_slot,
                offset,
            }
            | InstructionData::StackStore {
                opcode: Opcode::StackStore,
                stack_slot,
                offset,
                arg: _,
            } => (stack_slot, offset, func.dfg.ctrl_typevar(inst).bytes()),
            _ => unreachable!("{:?}", func.dfg[inst]),
        }
    }

    debug_assert_ne!(src, dest);

    let (src_ss, src_offset, src_size) = inst_info(func, src);
    let (dest_ss, dest_offset, dest_size) = inst_info(func, dest);

    if src_ss != dest_ss {
        return SpatialOverlap::No;
    }

    if src_offset == dest_offset && src_size == dest_size {
        return SpatialOverlap::Full;
    }

    let src_end: i64 = src_offset.try_add_i64(i64::from(src_size)).unwrap().into();
    let dest_end: i64 = dest_offset.try_add_i64(i64::from(dest_size)).unwrap().into();
    if src_end <= dest_offset.into() || dest_end <= src_offset.into() {
        return SpatialOverlap::No;
    }

    SpatialOverlap::Partial
}

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
enum TemporalOrder {
    /// `src` will never be executed before `dest`.
    NeverBefore,

    /// `src` may be executed before `dest`.
    MaybeBefore,

    /// `src` will always be executed before `dest`.
    /// There may still be other instructions in between.
    DefinitivelyBefore,
}

fn temporal_order(ctx: &Context, src: Inst, dest: Inst) -> TemporalOrder {
    debug_assert_ne!(src, dest);

    let src_ebb = ctx.func.layout.inst_ebb(src).unwrap();
    let dest_ebb = ctx.func.layout.inst_ebb(dest).unwrap();
    if src_ebb == dest_ebb {
        use std::cmp::Ordering::*;
        match ctx.func.layout.cmp(src, dest) {
            Less => TemporalOrder::DefinitivelyBefore,
            Equal => unreachable!(),
            Greater => TemporalOrder::MaybeBefore, // FIXME use dominator to check for loops
        }
    } else {
        // FIXME O(stack_load count * ebb count)
        // FIXME reuse memory allocations
        // FIXME return DefinitivelyBefore is src dominates dest
        let mut visited = EntitySet::new();
        let mut todo = EntitySet::new();
        todo.insert(dest_ebb);
        while let Some(ebb) = todo.pop() {
            if visited.contains(ebb) {
                continue;
            }
            visited.insert(ebb);
            if ebb == src_ebb {
                return TemporalOrder::MaybeBefore;
            }
            for bb in ctx.cfg.pred_iter(ebb) {
                todo.insert(bb.ebb);
            }
        }
        TemporalOrder::NeverBefore
    }
}
<|endoftext|>


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

use library::*;
use parser::is_empty_c_type;

impl Namespace {
    fn unresolved(&self) -> Vec<&str> {
        self.index
            .iter()
            .filter_map(|(name, &id)| if self.types[id as usize].is_none() {
                Some(&name[..])
            } else {
                None
            })
            .collect()
    }
}

type DetectedCTypes = HashMap<TypeId, String>;

impl Library {
    pub fn postprocessing(&mut self) {
        self.fix_gtype();
        self.check_resolved();
        self.fill_empty_signals_c_types();
        self.resolve_class_structs();
        self.correlate_class_structs();
    }

    fn fix_gtype(&mut self) {
        if let Some(ns_id) = self.find_namespace("GObject") {
            // hide the `GType` type alias in `GObject`
            self.add_type(ns_id, "Type", Type::Fundamental(Fundamental::Unsupported));
        }
    }

    fn check_resolved(&self) {
        let list: Vec<_> = self.index
            .iter()
            .flat_map(|(name, &id)| {
                let name = name.clone();
                self.namespace(id)
                    .unresolved()
                    .into_iter()
                    .map(move |s| format!("{}.{}", name, s))
            })
            .collect();

        if !list.is_empty() {
            panic!("Incomplete library, unresolved: {:?}", list);
        }
    }

    fn fill_empty_signals_c_types(&mut self) {
        let mut tids = Vec::new();
        let mut c_types = DetectedCTypes::new();
        for (ns_id, ns) in self.namespaces.iter().enumerate() {
            for (id, type_) in ns.types.iter().enumerate() {
                let type_ = type_.as_ref().unwrap(); //Always contains something
                let tid = TypeId {
                    ns_id: ns_id as u16,
                    id: id as u32,
                };
                match *type_ {
                    Type::Class(ref klass) => {
                        if self.detect_empty_signals_c_types(&klass.signals, &mut c_types) {
                            tids.push(tid);
                        }
                    }
                    Type::Interface(ref iface) => {
                        if self.detect_empty_signals_c_types(&iface.signals, &mut c_types) {
                            tids.push(tid);
                        }
                    }
                    _ => (),
                }
            }
        }

        fn update_empty_signals_c_types(signals: &mut [Signal], c_types: &DetectedCTypes) {
            for signal in signals {
                update_empty_signal_c_types(signal, c_types);
            }
        }

        fn update_empty_signal_c_types(signal: &mut Signal, c_types: &DetectedCTypes) {
            for par in &mut signal.parameters {
                update_empty_c_type(&mut par.c_type, par.typ, c_types);
            }
            update_empty_c_type(&mut signal.ret.c_type, signal.ret.typ, c_types);
        }

        fn update_empty_c_type(c_type: &mut String, tid: TypeId, c_types: &DetectedCTypes) {
            if !is_empty_c_type(c_type) {
                return;
            }
            if let Some(ref mut s) = c_types.get(&tid) {
                *c_type = s.clone();
            }
        }

        for tid in tids {
            match *self.type_mut(tid) {
                Type::Class(ref mut klass) => {
                    update_empty_signals_c_types(&mut klass.signals, &c_types)
                }
                Type::Interface(ref mut iface) => {
                    update_empty_signals_c_types(&mut iface.signals, &c_types)
                }
                _ => (),
            }
        }
    }

    fn detect_empty_signals_c_types(
        &self,
        signals: &[Signal],
        c_types: &mut DetectedCTypes,
    ) -> bool {
        let mut detected = false;
        for signal in signals {
            if self.detect_empty_signal_c_types(signal, c_types) {
                detected = true;
            }
        }
        detected
    }

    fn detect_empty_signal_c_types(&self, signal: &Signal, c_types: &mut DetectedCTypes) -> bool {
        let mut detected = false;
        for par in &signal.parameters {
            if self.detect_empty_c_type(&par.c_type, par.typ, c_types) {
                detected = true;
            }
        }
        if self.detect_empty_c_type(&signal.ret.c_type, signal.ret.typ, c_types) {
            detected = true;
        }
        detected
    }

    fn detect_empty_c_type(&self, c_type: &str, tid: TypeId, c_types: &mut DetectedCTypes) -> bool {
        if !is_empty_c_type(c_type) {
            return false;
        }
        if !c_types.contains_key(&tid) {
            if let Some(detected_c_type) = self.c_type_by_type_id(tid) {
                c_types.insert(tid, detected_c_type);
            }
        }
        true
    }

    fn c_type_by_type_id(&self, tid: TypeId) -> Option<String> {
        let type_ = self.type_(tid);
        type_.get_glib_name().map(
            |glib_name| if self.is_referenced_type(type_) {
                format!("{}*", glib_name)
            } else {
                glib_name.to_string()
            },
        )
    }

    fn is_referenced_type(&self, type_: &Type) -> bool {
        use library::Type::*;
        match *type_ {
            Alias(ref alias) => self.is_referenced_type(self.type_(alias.typ)),
            Record(..) | Union(..) | Class(..) | Interface(..) => true,
            _ => false,
        }
    }

    fn resolve_class_structs(&mut self) {
        // stores pairs of (gtype-struct-c-name, type-name)
        let mut structs_and_types = Vec::new();

        for (ns_id, ns) in self.namespaces.iter().enumerate() {
            for type_ in ns.types.iter() {
                let type_ = type_.as_ref().unwrap(); //Always contains something

                if let Type::Record(ref record) = *type_ {
                    if let Some(ref struct_for) = record.gtype_struct_for {
                        if let Some(struct_for_tid) = self.find_type(ns_id as u16, struct_for) {
                            structs_and_types.push ((record.c_type.clone(), struct_for_tid));
                        }
                    }
                }
            }
        }

        for (gtype_struct_c_type, struct_for_tid) in structs_and_types {
            match *self.type_mut(struct_for_tid) {
                Type::Class(ref mut klass) => {
                    klass.c_class_type = Some(gtype_struct_c_type);
                },

                Type::Interface(ref mut iface) => {
                    iface.c_class_type = Some(gtype_struct_c_type);
                },

                ref x @ _ => unreachable!("Something other than a class or interface has a class struct: {:?}", x)
            }
        }
    }

    fn correlate_class_structs(&self) {
        for (ns_id, ns) in self.namespaces.iter().enumerate() {
            for type_ in ns.types.iter() {
                let type_ = type_.as_ref().unwrap(); //Always contains something
                let name;
                let type_struct;
                let c_class_type;

                match *type_ {
                    Type::Class(ref klass) => {
                        name         = &klass.name;
                        type_struct  = &klass.type_struct;
                        c_class_type = &klass.c_class_type;
                    },

                    Type::Interface(ref iface) => {
                        name         = &iface.name;
                        type_struct  = &iface.type_struct;
                        c_class_type = &iface.c_class_type;
                    },

                    _ => { continue; }
                }

                if let Some(ref type_struct) = *type_struct {
                    let type_struct_tid = self.find_type(ns_id as u16, type_struct);
                    if type_struct_tid.is_none() {
                        panic!("\"{}\" has glib:type-struct=\"{}\" but there is no such record",
                               name,
                               type_struct);
                    }

                    let type_struct_type = self.type_(type_struct_tid.unwrap());

                    if let Type::Record(ref r) = *type_struct_type {
                        if r.gtype_struct_for.as_ref() != Some(name) {
                            if let Some(ref gtype_struct_for) = r.gtype_struct_for {
                                panic!("\"{}\" has glib:type-struct=\"{}\" but the corresponding record \"{}\" has glib:is-gtype-struct-for={:?}",
                                       name,
                                       type_struct,
                                       r.name,
                                       gtype_struct_for);
                            } else {
                                panic!("\"{}\" has glib:type-struct=\"{}\" but the corresponding record \"{}\" has no glib:is-gtype-struct-for attribute",
                                       name,
                                       type_struct,
                                       r.name);
                            }
                        }
                    } else {
                        panic!("Element with name=\"{}\" should be a record but it isn't",
                               type_struct);
                    }
                } else if let Some(ref c) = *c_class_type {
                    panic!("\"{}\" has no glib:type-struct but there is an element with glib:is-gtype-struct-for=\"{}\"",
                           name,
                           c);
                }
                // else both type_struct and c_class_type are None,
                // and that's fine because they don't reference each
                // other.
            }
        }
    }
}
<commit_msg>Stylistic fixes<commit_after>use std::collections::HashMap;

use library::*;
use parser::is_empty_c_type;

impl Namespace {
    fn unresolved(&self) -> Vec<&str> {
        self.index
            .iter()
            .filter_map(|(name, &id)| if self.types[id as usize].is_none() {
                Some(&name[..])
            } else {
                None
            })
            .collect()
    }
}

type DetectedCTypes = HashMap<TypeId, String>;

impl Library {
    pub fn postprocessing(&mut self) {
        self.fix_gtype();
        self.check_resolved();
        self.fill_empty_signals_c_types();
        self.resolve_class_structs();
        self.correlate_class_structs();
    }

    fn fix_gtype(&mut self) {
        if let Some(ns_id) = self.find_namespace("GObject") {
            // hide the `GType` type alias in `GObject`
            self.add_type(ns_id, "Type", Type::Fundamental(Fundamental::Unsupported));
        }
    }

    fn check_resolved(&self) {
        let list: Vec<_> = self.index
            .iter()
            .flat_map(|(name, &id)| {
                let name = name.clone();
                self.namespace(id)
                    .unresolved()
                    .into_iter()
                    .map(move |s| format!("{}.{}", name, s))
            })
            .collect();

        if !list.is_empty() {
            panic!("Incomplete library, unresolved: {:?}", list);
        }
    }

    fn fill_empty_signals_c_types(&mut self) {
        let mut tids = Vec::new();
        let mut c_types = DetectedCTypes::new();
        for (ns_id, ns) in self.namespaces.iter().enumerate() {
            for (id, type_) in ns.types.iter().enumerate() {
                let type_ = type_.as_ref().unwrap(); //Always contains something
                let tid = TypeId {
                    ns_id: ns_id as u16,
                    id: id as u32,
                };
                match *type_ {
                    Type::Class(ref klass) => {
                        if self.detect_empty_signals_c_types(&klass.signals, &mut c_types) {
                            tids.push(tid);
                        }
                    }
                    Type::Interface(ref iface) => {
                        if self.detect_empty_signals_c_types(&iface.signals, &mut c_types) {
                            tids.push(tid);
                        }
                    }
                    _ => (),
                }
            }
        }

        fn update_empty_signals_c_types(signals: &mut [Signal], c_types: &DetectedCTypes) {
            for signal in signals {
                update_empty_signal_c_types(signal, c_types);
            }
        }

        fn update_empty_signal_c_types(signal: &mut Signal, c_types: &DetectedCTypes) {
            for par in &mut signal.parameters {
                update_empty_c_type(&mut par.c_type, par.typ, c_types);
            }
            update_empty_c_type(&mut signal.ret.c_type, signal.ret.typ, c_types);
        }

        fn update_empty_c_type(c_type: &mut String, tid: TypeId, c_types: &DetectedCTypes) {
            if !is_empty_c_type(c_type) {
                return;
            }
            if let Some(ref mut s) = c_types.get(&tid) {
                *c_type = s.clone();
            }
        }

        for tid in tids {
            match *self.type_mut(tid) {
                Type::Class(ref mut klass) => {
                    update_empty_signals_c_types(&mut klass.signals, &c_types)
                }
                Type::Interface(ref mut iface) => {
                    update_empty_signals_c_types(&mut iface.signals, &c_types)
                }
                _ => (),
            }
        }
    }

    fn detect_empty_signals_c_types(
        &self,
        signals: &[Signal],
        c_types: &mut DetectedCTypes,
    ) -> bool {
        let mut detected = false;
        for signal in signals {
            if self.detect_empty_signal_c_types(signal, c_types) {
                detected = true;
            }
        }
        detected
    }

    fn detect_empty_signal_c_types(&self, signal: &Signal, c_types: &mut DetectedCTypes) -> bool {
        let mut detected = false;
        for par in &signal.parameters {
            if self.detect_empty_c_type(&par.c_type, par.typ, c_types) {
                detected = true;
            }
        }
        if self.detect_empty_c_type(&signal.ret.c_type, signal.ret.typ, c_types) {
            detected = true;
        }
        detected
    }

    fn detect_empty_c_type(&self, c_type: &str, tid: TypeId, c_types: &mut DetectedCTypes) -> bool {
        if !is_empty_c_type(c_type) {
            return false;
        }
        if !c_types.contains_key(&tid) {
            if let Some(detected_c_type) = self.c_type_by_type_id(tid) {
                c_types.insert(tid, detected_c_type);
            }
        }
        true
    }

    fn c_type_by_type_id(&self, tid: TypeId) -> Option<String> {
        let type_ = self.type_(tid);
        type_.get_glib_name().map(
            |glib_name| if self.is_referenced_type(type_) {
                format!("{}*", glib_name)
            } else {
                glib_name.to_string()
            },
        )
    }

    fn is_referenced_type(&self, type_: &Type) -> bool {
        use library::Type::*;
        match *type_ {
            Alias(ref alias) => self.is_referenced_type(self.type_(alias.typ)),
            Record(..) | Union(..) | Class(..) | Interface(..) => true,
            _ => false,
        }
    }

    fn resolve_class_structs(&mut self) {
        // stores pairs of (gtype-struct-c-name, type-name)
        let mut structs_and_types = Vec::new();

        for (ns_id, ns) in self.namespaces.iter().enumerate() {
            for type_ in &ns.types {
                let type_ = type_.as_ref().unwrap(); //Always contains something

                if let Type::Record(ref record) = *type_ {
                    if let Some(ref struct_for) = record.gtype_struct_for {
                        if let Some(struct_for_tid) = self.find_type(ns_id as u16, struct_for) {
                            structs_and_types.push ((record.c_type.clone(), struct_for_tid));
                        }
                    }
                }
            }
        }

        for (gtype_struct_c_type, struct_for_tid) in structs_and_types {
            match *self.type_mut(struct_for_tid) {
                Type::Class(ref mut klass) => {
                    klass.c_class_type = Some(gtype_struct_c_type);
                },

                Type::Interface(ref mut iface) => {
                    iface.c_class_type = Some(gtype_struct_c_type);
                },

                ref x => unreachable!("Something other than a class or interface has a class struct: {:?}", x)
            }
        }
    }

    fn correlate_class_structs(&self) {
        for (ns_id, ns) in self.namespaces.iter().enumerate() {
            for type_ in &ns.types {
                let type_ = type_.as_ref().unwrap(); //Always contains something
                let name;
                let type_struct;
                let c_class_type;

                match *type_ {
                    Type::Class(ref klass) => {
                        name         = &klass.name;
                        type_struct  = &klass.type_struct;
                        c_class_type = &klass.c_class_type;
                    },

                    Type::Interface(ref iface) => {
                        name         = &iface.name;
                        type_struct  = &iface.type_struct;
                        c_class_type = &iface.c_class_type;
                    },

                    _ => { continue; }
                }

                if let Some(ref type_struct) = *type_struct {
                    let type_struct_tid = self.find_type(ns_id as u16, type_struct);
                    if type_struct_tid.is_none() {
                        panic!("\"{}\" has glib:type-struct=\"{}\" but there is no such record",
                               name,
                               type_struct);
                    }

                    let type_struct_type = self.type_(type_struct_tid.unwrap());

                    if let Type::Record(ref r) = *type_struct_type {
                        if r.gtype_struct_for.as_ref() != Some(name) {
                            if let Some(ref gtype_struct_for) = r.gtype_struct_for {
                                panic!("\"{}\" has glib:type-struct=\"{}\" but the corresponding record \"{}\" has glib:is-gtype-struct-for={:?}",
                                       name,
                                       type_struct,
                                       r.name,
                                       gtype_struct_for);
                            } else {
                                panic!("\"{}\" has glib:type-struct=\"{}\" but the corresponding record \"{}\" has no glib:is-gtype-struct-for attribute",
                                       name,
                                       type_struct,
                                       r.name);
                            }
                        }
                    } else {
                        panic!("Element with name=\"{}\" should be a record but it isn't",
                               type_struct);
                    }
                } else if let Some(ref c) = *c_class_type {
                    panic!("\"{}\" has no glib:type-struct but there is an element with glib:is-gtype-struct-for=\"{}\"",
                           name,
                           c);
                }
                // else both type_struct and c_class_type are None,
                // and that's fine because they don't reference each
                // other.
            }
        }
    }
}
<|endoftext|>


<commit_before>
extern crate env_logger;
extern crate gluon;

use gluon::{new_vm, Compiler, Thread};
use gluon::vm::internal::Value;
use gluon::vm::api::{Hole, OpaqueValue, IO};

#[macro_use]
mod support;

use support::*;

#[test]
fn read_file() {
    let _ = ::env_logger::init();

    let thread = new_vm();
    let text = r#"
        let prelude = import! std.prelude
        let array = import! std.array
        let { assert }  = import! std.test
        let io = import! std.io
        let { wrap } = io.applicative
        let { (>>=) } = prelude.make_Monad io.monad

        io.open_file "Cargo.toml" >>= \file ->
            io.read_file file 9 >>= \bytes ->
            assert (array.len bytes == 9)
            assert (array.index bytes 0 #Byte== 91b) // [
            assert (array.index bytes 1 #Byte== 112b) // p
            wrap (array.index bytes 8)
        "#;
    let result = Compiler::new()
        .run_io(true)
        .run_expr_async::<IO<u8>>(&thread, "<top>", text)
        .sync_or_error();

    match result {
        Ok((IO::Value(value), _)) => assert_eq!(value, b']'),
        Ok((IO::Exception(err), _)) => assert!(false, "{}", err),
        Err(err) => assert!(false, "{}", err),
    }
}

test_expr!{ no_io_eval,
r#"
let { error } = import! std.prim
let io = import! std.io.prim
let x = io.flat_map (\x -> error "NOOOOOOOO") (io.println "1")
in { x }
"#
}

test_expr!{ io_print,
r#"
let io = import! std.io
io.print "123"
"#
}

#[test]
fn run_expr_int() {
    let _ = ::env_logger::init();

    let text = r#"
        let io = import! std.io
        io.run_expr "123"
    "#;
    let mut vm = make_vm();
    let (result, _) = Compiler::new()
        .run_io(true)
        .run_expr_async::<IO<String>>(&mut vm, "<top>", text)
        .sync_or_error()
        .unwrap();
    match result {
        IO::Value(result) => {
            let expected = "123 : Int";
            assert_eq!(result, expected);
        }
        IO::Exception(err) => panic!("{}", err),
    }
}

test_expr!{ io run_expr_io,
r#"
let io = import! std.io.prim
io.flat_map (\x -> io.wrap 100)
            (io.run_expr "
                let io = import! \"std/io.glu\"
                io.print \"123\"
            ")
"#,
100i32
}

#[test]
fn dont_execute_io_in_run_expr_async() {
    let _ = ::env_logger::init();
    let vm = make_vm();
    let expr = r#"
let prelude  = import! std.prelude
let io = import! std.io
let { wrap } = io.applicative
wrap 123
"#;
    let value = Compiler::new()
        .run_expr_async::<OpaqueValue<&Thread, Hole>>(&vm, "example", expr)
        .sync_or_error()
        .unwrap_or_else(|err| panic!("{}", err));
    assert!(
        value.0.get_ref() != Value::Int(123),
        "Unexpected {:?}",
        value.0
    );
}
<commit_msg>Cleanup the io tests<commit_after>extern crate env_logger;
extern crate gluon;

use gluon::{new_vm, Compiler, Thread};
use gluon::vm::internal::Value;
use gluon::vm::api::{Hole, OpaqueValue, IO};

#[macro_use]
mod support;

use support::*;

#[test]
fn read_file() {
    let _ = ::env_logger::init();

    let thread = new_vm();
    let text = r#"
        let prelude = import! std.prelude
        let array = import! std.array
        let { assert }  = import! std.test
        let io = import! std.io
        let { wrap } = io.applicative
        let { flat_map, (>>=) } = prelude.make_Monad io.monad

        do file = io.open_file "Cargo.toml"
        do bytes = io.read_file file 9

        assert (array.len bytes == 9)
        assert (array.index bytes 0 #Byte== 91b) // [
        assert (array.index bytes 1 #Byte== 112b) // p

        wrap (array.index bytes 8)
        "#;
    let result = Compiler::new()
        .run_io(true)
        .run_expr_async::<IO<u8>>(&thread, "<top>", text)
        .sync_or_error();

    match result {
        Ok((IO::Value(value), _)) => assert_eq!(value, b']'),
        Ok((IO::Exception(err), _)) => assert!(false, "{}", err),
        Err(err) => assert!(false, "{}", err),
    }
}

test_expr!{ no_io_eval,
r#"
let { error } = import! std.prim
let io = import! std.io.prim
let x = io.flat_map (\x -> error "NOOOOOOOO") (io.println "1")
in { x }
"#
}

test_expr!{ io_print,
r#"
let io = import! std.io
io.print "123"
"#
}

#[test]
fn run_expr_int() {
    let _ = ::env_logger::init();

    let text = r#"
        let io = import! std.io
        io.run_expr "123"
    "#;
    let mut vm = make_vm();
    let (result, _) = Compiler::new()
        .run_io(true)
        .run_expr_async::<IO<String>>(&mut vm, "<top>", text)
        .sync_or_error()
        .unwrap();
    match result {
        IO::Value(result) => {
            let expected = "123 : Int";
            assert_eq!(result, expected);
        }
        IO::Exception(err) => panic!("{}", err),
    }
}

test_expr!{ io run_expr_io,
r#"
let io = import! std.io.prim
io.flat_map (\x -> io.wrap 100)
            (io.run_expr "
                let io = import! \"std/io.glu\"
                io.print \"123\"
            ")
"#,
100i32
}

#[test]
fn dont_execute_io_in_run_expr_async() {
    let _ = ::env_logger::init();
    let vm = make_vm();
    let expr = r#"
let prelude  = import! std.prelude
let io = import! std.io
let { wrap } = io.applicative
wrap 123
"#;
    let value = Compiler::new()
        .run_expr_async::<OpaqueValue<&Thread, Hole>>(&vm, "example", expr)
        .sync_or_error()
        .unwrap_or_else(|err| panic!("{}", err));
    assert!(
        value.0.get_ref() != Value::Int(123),
        "Unexpected {:?}",
        value.0
    );
}
<|endoftext|>