<commit_before>//! Trap codes describing the reason for a trap.

use std::fmt::{self, Display, Formatter};
use std::str::FromStr;

/// A trap code describing the reason for a trap.
///
/// All trap instructions have an explicit trap code.
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
pub enum TrapCode {
    /// The current stack space was exhausted.
    ///
    /// On some platforms, a stack overflow may also be indicated by a segmentation fault from the
    /// stack guard page.
    StackOverflow,

    /// A `heap_addr` instruction detected an out-of-bounds error.
    ///
    /// Some out-of-bounds heap accesses are detected by a segmentation fault on the heap guard
    /// pages.
    HeapOutOfBounds,

    /// Other bounds checking error.
    OutOfBounds,

    /// Indirect call to a null table entry.
    IndirectCallToNull,

    /// Signature mismatch on indirect call.
    BadSignature,

    /// An integer arithmetic operation caused an overflow.
    IntegerOverflow,

    /// An integer division by zero.
    IntegerDivisionByZero,

    /// Failed float-to-int conversion.
    BadConversionToInteger,

    /// A user-defined trap code.
    User(u16),
}

impl Display for TrapCode {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        use self::TrapCode::*;
        let identifier = match *self {
            StackOverflow => "stk_ovf",
            HeapOutOfBounds => "heap_oob",
            OutOfBounds => "oob",
            IndirectCallToNull => "icall_null",
            BadSignature => "bad_sig",
            IntegerOverflow => "int_ovf",
            IntegerDivisionByZero => "int_divz",
            BadConversionToInteger => "bad_toint",
            User(x) => return write!(f, "user{}", x),
        };
        f.write_str(identifier)
    }
}

impl FromStr for TrapCode {
    type Err = ();

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        use self::TrapCode::*;
        match s {
            "stk_ovf" => Ok(StackOverflow),
            "heap_oob" => Ok(HeapOutOfBounds),
            "oob" => Ok(OutOfBounds),
            "icall_null" => Ok(IndirectCallToNull),
            "bad_sig" => Ok(BadSignature),
            "int_ovf" => Ok(IntegerOverflow),
            "int_divz" => Ok(IntegerDivisionByZero),
            "bad_toint" => Ok(BadConversionToInteger),
            _ if s.starts_with("user") => s[4..].parse().map(User).map_err(|_| ()),
            _ => Err(()),
        }
    }
}

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

    // Everything but user-defined codes.
    const CODES: [TrapCode; 8] = [
        TrapCode::StackOverflow,
        TrapCode::HeapOutOfBounds,
        TrapCode::OutOfBounds,
        TrapCode::IndirectCallToNull,
        TrapCode::BadSignature,
        TrapCode::IntegerOverflow,
        TrapCode::IntegerDivisionByZero,
        TrapCode::BadConversionToInteger,
    ];

    #[test]
    fn display() {
        for r in &CODES {
            let tc = *r;
            assert_eq!(tc.to_string().parse(), Ok(tc));
        }
        assert_eq!("bogus".parse::<TrapCode>(), Err(()));

        assert_eq!(TrapCode::User(17).to_string(), "user17");
        assert_eq!("user22".parse(), Ok(TrapCode::User(22)));
        assert_eq!("user".parse::<TrapCode>(), Err(()));
        assert_eq!("user-1".parse::<TrapCode>(), Err(()));
        assert_eq!("users".parse::<TrapCode>(), Err(()));
    }
}
<commit_msg>Define an "interrupt" trap code.<commit_after>//! Trap codes describing the reason for a trap.

use std::fmt::{self, Display, Formatter};
use std::str::FromStr;

/// A trap code describing the reason for a trap.
///
/// All trap instructions have an explicit trap code.
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
pub enum TrapCode {
    /// The current stack space was exhausted.
    ///
    /// On some platforms, a stack overflow may also be indicated by a segmentation fault from the
    /// stack guard page.
    StackOverflow,

    /// A `heap_addr` instruction detected an out-of-bounds error.
    ///
    /// Some out-of-bounds heap accesses are detected by a segmentation fault on the heap guard
    /// pages.
    HeapOutOfBounds,

    /// Other bounds checking error.
    OutOfBounds,

    /// Indirect call to a null table entry.
    IndirectCallToNull,

    /// Signature mismatch on indirect call.
    BadSignature,

    /// An integer arithmetic operation caused an overflow.
    IntegerOverflow,

    /// An integer division by zero.
    IntegerDivisionByZero,

    /// Failed float-to-int conversion.
    BadConversionToInteger,

    /// Execution has potentially run too long and may be interrupted.
    /// This trap is resumable.
    Interrupt,

    /// A user-defined trap code.
    User(u16),
}

impl Display for TrapCode {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        use self::TrapCode::*;
        let identifier = match *self {
            StackOverflow => "stk_ovf",
            HeapOutOfBounds => "heap_oob",
            OutOfBounds => "oob",
            IndirectCallToNull => "icall_null",
            BadSignature => "bad_sig",
            IntegerOverflow => "int_ovf",
            IntegerDivisionByZero => "int_divz",
            BadConversionToInteger => "bad_toint",
            Interrupt => "interrupt",
            User(x) => return write!(f, "user{}", x),
        };
        f.write_str(identifier)
    }
}

impl FromStr for TrapCode {
    type Err = ();

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        use self::TrapCode::*;
        match s {
            "stk_ovf" => Ok(StackOverflow),
            "heap_oob" => Ok(HeapOutOfBounds),
            "oob" => Ok(OutOfBounds),
            "icall_null" => Ok(IndirectCallToNull),
            "bad_sig" => Ok(BadSignature),
            "int_ovf" => Ok(IntegerOverflow),
            "int_divz" => Ok(IntegerDivisionByZero),
            "bad_toint" => Ok(BadConversionToInteger),
            "interrupt" => Ok(Interrupt),
            _ if s.starts_with("user") => s[4..].parse().map(User).map_err(|_| ()),
            _ => Err(()),
        }
    }
}

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

    // Everything but user-defined codes.
    const CODES: [TrapCode; 8] = [
        TrapCode::StackOverflow,
        TrapCode::HeapOutOfBounds,
        TrapCode::OutOfBounds,
        TrapCode::IndirectCallToNull,
        TrapCode::BadSignature,
        TrapCode::IntegerOverflow,
        TrapCode::IntegerDivisionByZero,
        TrapCode::BadConversionToInteger,
    ];

    #[test]
    fn display() {
        for r in &CODES {
            let tc = *r;
            assert_eq!(tc.to_string().parse(), Ok(tc));
        }
        assert_eq!("bogus".parse::<TrapCode>(), Err(()));

        assert_eq!(TrapCode::User(17).to_string(), "user17");
        assert_eq!("user22".parse(), Ok(TrapCode::User(22)));
        assert_eq!("user".parse::<TrapCode>(), Err(()));
        assert_eq!("user-1".parse::<TrapCode>(), Err(()));
        assert_eq!("users".parse::<TrapCode>(), Err(()));
    }
}
<|endoftext|>


<commit_before>extern crate env_logger;
extern crate comm;

use comm::address::Address;
use comm::client;
use comm::client::{Client, Event, Task, TaskSender};
use comm::network::Network;
use comm::node::{Node, UdpNode};
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
use std::sync::mpsc;
use std::thread;

#[no_mangle]
pub extern "C" fn comm_initialize() {
    env_logger::init().expect("Couldn't initialize logger");
}

#[no_mangle]
pub extern "C" fn comm_address_for_content(content: *const c_char) -> *const Address {
    let c_str: &CStr = unsafe { CStr::from_ptr(content) };
    let address = Address::for_content(c_str.to_str().unwrap());
    Box::into_raw(Box::new(address))
}

#[no_mangle]
pub extern "C" fn comm_address_from_str(string: *const c_char) -> *const Address {
    let c_str: &CStr = unsafe { CStr::from_ptr(string) };
    let address = Address::from_str(c_str.to_str().unwrap());
    Box::into_raw(Box::new(address))
}

#[no_mangle]
pub extern "C" fn comm_address_null() -> *const Address {
    Box::into_raw(Box::new(Address::null()))
}

#[no_mangle]
pub extern "C" fn comm_address_copy(address: *const Address) -> *const Address {
    let copy = unsafe { *address }.clone();
    Box::into_raw(Box::new(copy))
}

// TODO: impl API to clean up leaked strings
#[no_mangle]
pub extern "C" fn comm_address_to_str(address: *const Address) -> *const c_char {
    let string = unsafe { *address }.to_str();
    let string = CString::new(string).unwrap();
    string.into_raw()
}

#[no_mangle]
pub unsafe extern "C" fn comm_address_destroy(address: *mut Address) {
    let _ = Box::from_raw(address);
}

#[no_mangle]
pub extern "C" fn comm_udp_node_new(
    address: *const Address, socket_address: *const c_char) -> *const UdpNode {
    let socket_address: &CStr = unsafe { CStr::from_ptr(socket_address) };
    let node = UdpNode::new(unsafe { *address }, socket_address.to_str().unwrap());
    Box::into_raw(Box::new(node))
}

#[no_mangle]
pub unsafe extern "C" fn comm_udp_node_destroy(udp_node: *mut UdpNode) {
    let _ = Box::from_raw(udp_node);
}

#[no_mangle]
pub extern "C" fn comm_network_new(
    self_address: *mut Address, host: *const c_char,
    routers: *mut *mut UdpNode, routers_count: usize) -> *mut Network {
    let self_address = unsafe { *self_address };
    let host: &CStr = unsafe { CStr::from_ptr(host) };
    let routers = unsafe { Vec::from_raw_parts(routers, routers_count, routers_count) };
    let routers: Vec<Box<Node>> = routers.into_iter().map(|r| unsafe {
        Box::from_raw(r) as Box<Node>
    }).collect();
    let network = Network::new(self_address, host.to_str().unwrap(), routers);
    Box::into_raw(Box::new(network))
}

#[no_mangle]
pub unsafe extern "C" fn comm_network_run(network: *mut Network) {
    let network = Box::from_raw(network);
    network.run();
}

#[no_mangle]
pub unsafe extern "C" fn comm_network_destroy(network: *mut Network) {
    let _ = Box::from_raw(network);
}

#[no_mangle]
pub extern "C" fn comm_text_message_new(sender: *mut Address, text: *const c_char) -> *const client::messages::TextMessage {
    let sender = unsafe { *Box::from_raw(sender) };
    let c_str: &CStr = unsafe { CStr::from_ptr(text) };
    let message_text = c_str.to_str().unwrap().to_string();

    Box::into_raw(Box::new(client::messages::TextMessage::new(sender, message_text)))
}

// TODO: impl API to clean up leaked strings
#[no_mangle]
pub extern "C" fn comm_text_message_text(text_message: *const client::messages::TextMessage) -> *const c_char {
    let text_message = unsafe { &*text_message };
    let string = CString::new(text_message.text.clone()).unwrap();
    string.into_raw()
}

#[no_mangle]
pub extern "C" fn comm_text_message_sender(text_message: *const client::messages::TextMessage) -> *const Address {
    let text_message = unsafe { &*text_message };
    Box::into_raw(Box::new(text_message.sender))
}

#[no_mangle]
pub unsafe extern "C" fn comm_client_new(address: *const Address) -> *mut Client {
    Box::into_raw(Box::new(Client::new(*address)))
}

#[no_mangle]
pub unsafe extern "C" fn comm_client_run(
    client: *mut Client, network: *mut Network) -> *mut TaskSender {
    let client = Box::from_raw(client);
    let network = *Box::from_raw(network);
    Box::into_raw(Box::new(client.run(network)))
}

#[no_mangle]
pub extern "C" fn comm_client_register_text_message_received_callback(
    client: *mut Client, callback: extern fn(*const client::messages::TextMessage) -> ()) {

    let client = unsafe { &mut *client };
    let (event_sender, events) = mpsc::channel();
    client.register_event_listener(event_sender);

    thread::spawn(move || {
        for event in events {
            if let Event::ReceivedTextMessage(text_message) = event {
                callback(Box::into_raw(Box::new(text_message)));
            }
        }
    });
}

#[no_mangle]
pub extern "C" fn comm_client_commands_send_text_message(
    commands: *mut TaskSender,
    recipient: *mut Address,
    text_message: *mut client::messages::TextMessage) {
    let commands = unsafe { &mut *commands };
    let recipient = unsafe { *Box::from_raw(recipient) };
    let text_message = unsafe { *Box::from_raw(text_message) };
    commands.send(Task::ScheduleMessageDelivery(recipient, text_message)).unwrap();
}

#[no_mangle]
pub unsafe extern "C" fn comm_client_destroy(client: *mut Client) {
    let _ = Box::from_raw(client);
}
<commit_msg>Forget passed-in array so that it doesn't get dealloc'd<commit_after>extern crate env_logger;
extern crate comm;

use comm::address::Address;
use comm::client;
use comm::client::{Client, Event, Task, TaskSender};
use comm::network::Network;
use comm::node::{Node, UdpNode};
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
use std::sync::mpsc;
use std::thread;

#[no_mangle]
pub extern "C" fn comm_initialize() {
    env_logger::init().expect("Couldn't initialize logger");
}

#[no_mangle]
pub extern "C" fn comm_address_for_content(content: *const c_char) -> *const Address {
    let c_str: &CStr = unsafe { CStr::from_ptr(content) };
    let address = Address::for_content(c_str.to_str().unwrap());
    Box::into_raw(Box::new(address))
}

#[no_mangle]
pub extern "C" fn comm_address_from_str(string: *const c_char) -> *const Address {
    let c_str: &CStr = unsafe { CStr::from_ptr(string) };
    let address = Address::from_str(c_str.to_str().unwrap());
    Box::into_raw(Box::new(address))
}

#[no_mangle]
pub extern "C" fn comm_address_null() -> *const Address {
    Box::into_raw(Box::new(Address::null()))
}

#[no_mangle]
pub extern "C" fn comm_address_copy(address: *const Address) -> *const Address {
    let copy = unsafe { *address }.clone();
    Box::into_raw(Box::new(copy))
}

// TODO: impl API to clean up leaked strings
#[no_mangle]
pub extern "C" fn comm_address_to_str(address: *const Address) -> *const c_char {
    let string = unsafe { *address }.to_str();
    let string = CString::new(string).unwrap();
    string.into_raw()
}

#[no_mangle]
pub unsafe extern "C" fn comm_address_destroy(address: *mut Address) {
    let _ = Box::from_raw(address);
}

#[no_mangle]
pub extern "C" fn comm_udp_node_new(
    address: *const Address, socket_address: *const c_char) -> *const UdpNode {
    let socket_address: &CStr = unsafe { CStr::from_ptr(socket_address) };
    let node = UdpNode::new(unsafe { *address }, socket_address.to_str().unwrap());
    Box::into_raw(Box::new(node))
}

#[no_mangle]
pub unsafe extern "C" fn comm_udp_node_destroy(udp_node: *mut UdpNode) {
    let _ = Box::from_raw(udp_node);
}

#[no_mangle]
pub extern "C" fn comm_network_new(
    self_address: *mut Address, host: *const c_char,
    routers: *mut *mut UdpNode, routers_count: usize) -> *mut Network {

    use std::mem;

    let self_address = unsafe { *self_address };
    let host: &CStr = unsafe { CStr::from_ptr(host) };
    let raw_routers = unsafe { Vec::from_raw_parts(routers, routers_count, routers_count) };
    let routers: Vec<Box<Node>> = raw_routers.iter().map(|&r| unsafe {
        Box::from_raw(r) as Box<Node>
    }).collect();
    mem::forget(raw_routers);
    let network = Network::new(self_address, host.to_str().unwrap(), routers);
    Box::into_raw(Box::new(network))
}

#[no_mangle]
pub unsafe extern "C" fn comm_network_run(network: *mut Network) {
    let network = Box::from_raw(network);
    network.run();
}

#[no_mangle]
pub unsafe extern "C" fn comm_network_destroy(network: *mut Network) {
    let _ = Box::from_raw(network);
}

#[no_mangle]
pub extern "C" fn comm_text_message_new(sender: *mut Address, text: *const c_char) -> *const client::messages::TextMessage {
    let sender = unsafe { *Box::from_raw(sender) };
    let c_str: &CStr = unsafe { CStr::from_ptr(text) };
    let message_text = c_str.to_str().unwrap().to_string();

    Box::into_raw(Box::new(client::messages::TextMessage::new(sender, message_text)))
}

// TODO: impl API to clean up leaked strings
#[no_mangle]
pub extern "C" fn comm_text_message_text(text_message: *const client::messages::TextMessage) -> *const c_char {
    let text_message = unsafe { &*text_message };
    let string = CString::new(text_message.text.clone()).unwrap();
    string.into_raw()
}

#[no_mangle]
pub extern "C" fn comm_text_message_sender(text_message: *const client::messages::TextMessage) -> *const Address {
    let text_message = unsafe { &*text_message };
    Box::into_raw(Box::new(text_message.sender))
}

#[no_mangle]
pub unsafe extern "C" fn comm_client_new(address: *const Address) -> *mut Client {
    Box::into_raw(Box::new(Client::new(*address)))
}

#[no_mangle]
pub unsafe extern "C" fn comm_client_run(
    client: *mut Client, network: *mut Network) -> *mut TaskSender {
    let client = Box::from_raw(client);
    let network = *Box::from_raw(network);
    Box::into_raw(Box::new(client.run(network)))
}

#[no_mangle]
pub extern "C" fn comm_client_register_text_message_received_callback(
    client: *mut Client, callback: extern fn(*const client::messages::TextMessage) -> ()) {

    let client = unsafe { &mut *client };
    let (event_sender, events) = mpsc::channel();
    client.register_event_listener(event_sender);

    thread::spawn(move || {
        for event in events {
            if let Event::ReceivedTextMessage(text_message) = event {
                callback(Box::into_raw(Box::new(text_message)));
            }
        }
    });
}

#[no_mangle]
pub extern "C" fn comm_client_commands_send_text_message(
    commands: *mut TaskSender,
    recipient: *mut Address,
    text_message: *mut client::messages::TextMessage) {
    let commands = unsafe { &mut *commands };
    let recipient = unsafe { *Box::from_raw(recipient) };
    let text_message = unsafe { *Box::from_raw(text_message) };
    commands.send(Task::ScheduleMessageDelivery(recipient, text_message)).unwrap();
}

#[no_mangle]
pub unsafe extern "C" fn comm_client_destroy(client: *mut Client) {
    let _ = Box::from_raw(client);
}
<|endoftext|>


<commit_before>use util;
use ::core::marker::PhantomData;
use vspace::VSpaceWindow;
/* These constants describe our kernel virtual address layout.
 * The boot code and any vspace creation code is expected to
 * ensure these are true */
/* Low boot window that is mapped 1-1 by the boot code. This
 * window only exists during early kernel boot */
const LOW_BOOT_MAPPING_BASE: usize = 0x0;
const LOW_BOOT_MAPPING_SIZE: usize = util::GB * 4;
/* Early high boot window overlaps with the final kernel
 * window */
const HIGH_BOOT_MAPPING_BASE: usize = 0xffffffff80000000;
const HIGH_BOOT_MAPPING_SIZE: usize = util::GB;
/* Actual kernel window covers the top 2^39 bits of memory */
const KERNEL_MAPPING_BASE: usize = 0xffffff8000000000;
const KERNEL_MAPPING_SIZE: usize = 0x800000000;

pub struct BootLowWindow<'a>(PhantomData<&'a usize>);
pub struct BootHighWindow<'a>(PhantomData<&'a usize>);
pub struct KernelWindow<'a>(PhantomData<&'a usize>);

impl<'a> Default for BootLowWindow<'a> {
    fn default() -> BootLowWindow<'a> {
        BootLowWindow(PhantomData)
    }
}

impl<'a> Default for BootHighWindow<'a> {
    fn default() -> BootHighWindow<'a> {
        BootHighWindow(PhantomData)
    }
}

impl<'a> Default for KernelWindow<'a> {
    fn default() -> KernelWindow<'a> {
        KernelWindow(PhantomData)
    }
}

unsafe impl<'a> VSpaceWindow<'a> for BootHighWindow<'a> {
    fn base(&self) -> usize { HIGH_BOOT_MAPPING_BASE }
    fn size(&self) -> usize { HIGH_BOOT_MAPPING_SIZE }
}

unsafe impl<'a> VSpaceWindow<'a> for KernelWindow<'a> {
    fn base(&self) -> usize { KERNEL_MAPPING_BASE }
    fn size(&self) -> usize { KERNEL_MAPPING_SIZE }
}

unsafe impl<'a> VSpaceWindow<'a> for BootLowWindow<'a> {
    /* Technically our low window does start at 1, but we are
     * never going to gie our the NULL address as this results
     * in undefined behaviour */
    fn base(&self) -> usize { LOW_BOOT_MAPPING_BASE + 1 }
    /* our window is 4gb, but because we are skipping the first
     * address (see the comment on base) we need to subtract 1
     * from the size */
    fn size(&self) -> usize { LOW_BOOT_MAPPING_SIZE - 1 }
}

<commit_msg>Correct final kernel window to be the correct size<commit_after>use util;
use ::core::marker::PhantomData;
use vspace::VSpaceWindow;
/* These constants describe our kernel virtual address layout.
 * The boot code and any vspace creation code is expected to
 * ensure these are true */
/* Low boot window that is mapped 1-1 by the boot code. This
 * window only exists during early kernel boot */
const LOW_BOOT_MAPPING_BASE: usize = 0x0;
const LOW_BOOT_MAPPING_SIZE: usize = util::GB * 4;
/* Early high boot window overlaps with the final kernel
 * window */
const HIGH_BOOT_MAPPING_BASE: usize = 0xffffffff80000000;
const HIGH_BOOT_MAPPING_SIZE: usize = util::GB;
/* Actual kernel window covers the top 2^39 bits of memory */
const KERNEL_MAPPING_BASE: usize = 0xffffff8000000000;
const KERNEL_MAPPING_SIZE: usize = 0x8000000000;

pub struct BootLowWindow<'a>(PhantomData<&'a usize>);
pub struct BootHighWindow<'a>(PhantomData<&'a usize>);
pub struct KernelWindow<'a>(PhantomData<&'a usize>);

impl<'a> Default for BootLowWindow<'a> {
    fn default() -> BootLowWindow<'a> {
        BootLowWindow(PhantomData)
    }
}

impl<'a> Default for BootHighWindow<'a> {
    fn default() -> BootHighWindow<'a> {
        BootHighWindow(PhantomData)
    }
}

impl<'a> Default for KernelWindow<'a> {
    fn default() -> KernelWindow<'a> {
        KernelWindow(PhantomData)
    }
}

unsafe impl<'a> VSpaceWindow<'a> for BootHighWindow<'a> {
    fn base(&self) -> usize { HIGH_BOOT_MAPPING_BASE }
    fn size(&self) -> usize { HIGH_BOOT_MAPPING_SIZE }
}

unsafe impl<'a> VSpaceWindow<'a> for KernelWindow<'a> {
    fn base(&self) -> usize { KERNEL_MAPPING_BASE }
    fn size(&self) -> usize { KERNEL_MAPPING_SIZE }
}

unsafe impl<'a> VSpaceWindow<'a> for BootLowWindow<'a> {
    /* Technically our low window does start at 1, but we are
     * never going to gie our the NULL address as this results
     * in undefined behaviour */
    fn base(&self) -> usize { LOW_BOOT_MAPPING_BASE + 1 }
    /* our window is 4gb, but because we are skipping the first
     * address (see the comment on base) we need to subtract 1
     * from the size */
    fn size(&self) -> usize { LOW_BOOT_MAPPING_SIZE - 1 }
}

<|endoftext|>


<commit_before>use std::fs::File;
use std::io::{BufReader};
use std::path::PathBuf;

use cgmath::{Matrix4, Deg, Rad, Vector4, SquareMatrix, Vector3};
use soto::task::{task_log};
use soto::Error;
use sotolib_fbx::{RawFbx, SimpleFbx, FbxObject, id_name, friendly_name, FbxObjectTreeNode};
use sotolib_smd::{Smd, SmdVertex, SmdTriangle, SmdExportExt, SmdAnimationFrameBone, SmdBone};

pub fn create_reference_smd(fbx: &PathBuf, target_smd: &PathBuf) -> Result<(), Error> {
    // Read in the fbx we got told to convert
    let file = BufReader::new(File::open(&fbx).unwrap());
    let fbx = SimpleFbx::from_raw(&RawFbx::parse(file).unwrap());
    let fbx_tree = FbxObjectTreeNode::from_simple(&fbx);

    // Go over all FBX root nodes and turn them into SMD data
    let mut smd = Smd::new();
    let matrix = Matrix4::identity();
    process_fbx_node(&fbx_tree, &matrix, &mut smd, None)?;

    // Export the SMD
    let export_file = File::create(target_smd)?;
    smd.export(export_file).unwrap();

    Ok(())
}

fn process_fbx_node(
    fbx_node: &FbxObjectTreeNode, matrix: &Matrix4<f32>,
    mut smd: &mut Smd, current_bone: Option<&SmdBone>,
) -> Result<(), Error> {
    // Perform node type specific information
    match fbx_node.object {
        FbxObject::Geometry(ref geometry) => {
            // Add triangles to parent node
            let tris = geometry.triangles();
            for tri in tris {
                // Turn the vertices in this triangle to SMD vertices
                let mut smd_verts: [SmdVertex; 3] = Default::default();
                for (i, vert) in tri.iter().enumerate() {
                    // Multiply the vectors that need to be multiplied
                    let pos = matrix * Vector4::new(vert.0[0], vert.0[1], vert.0[2], 1.0);
                    let norm = matrix * Vector4::new(vert.1[0], vert.1[1], vert.1[2], 0.0);

                    smd_verts[i] = SmdVertex {
                        parent_bone: current_bone.unwrap().id, // This is overwritten by links
                        position: pos.truncate().into(),
                        normal: norm.truncate().into(),
                        uv: vert.2,
                        links: vec!(
                            /*Not needed, we aren't using weights anyways so this done by parent_bone
                            SmdLink {
                                bone: bone_id,
                                weight: 1.0,
                            }*/
                        )
                    };
                }

                // Add the actual SMD triangle
                smd.triangles.push(SmdTriangle {
                    material: "layl_test_texture".into(),
                    vertices: smd_verts,
                });
            }
        },
        FbxObject::Model(ref model) => {
            task_log(format!("Adding model \"{}\" to SMD data", friendly_name(&model.name)));

            // Create a new bone and set the transformations
            let new_bone = smd.new_bone(&id_name(&model.name).unwrap(), current_bone.map(|b| b.id))
                .ok_or_else(|| Error::Task(format!("Bone \"{}\" exists multiple times in the FBX", &model.name)))?
                .clone(); // Clone needed to avoid a borrow since we need to mut borrow later
            let first_frame = SmdAnimationFrameBone {
                translation: model.translation,
                rotation: [
                    Rad::from(Deg(model.rotation[0])).0,
                    Rad::from(Deg(model.rotation[1])).0,
                    Rad::from(Deg(model.rotation[2])).0,
                ],
            };
            smd.set_animation(0, new_bone.id, first_frame);

            // Create a new transformation matrix for child nodes
            let rot_pivot: Vector3<_> = model.rotation_pivot.into();
            let rot_pivot_mat = Matrix4::from_translation(rot_pivot);
            let matrix =
                matrix *
                Matrix4::from_translation(model.translation.into()) *
                rot_pivot_mat *
                Matrix4::from_angle_z(Deg(model.rotation[2])) *
                Matrix4::from_angle_y(Deg(model.rotation[1])) *
                Matrix4::from_angle_x(Deg(model.rotation[0])) *
                rot_pivot_mat.invert().unwrap() * // This may need to be rotated in reverse
                Matrix4::from_nonuniform_scale(model.scale[0], model.scale[1], model.scale[2]);

            // Make sure the child nodes will receive this new bone
            for node in &fbx_node.nodes {
                process_fbx_node(node, &matrix, smd, Some(&new_bone))?;
            }
        },
        FbxObject::Root | FbxObject::NotSupported(_) => {
            // Just go straight to the children
            for node in &fbx_node.nodes {
                process_fbx_node(node, matrix, smd, current_bone)?;
            }
        }
    }

    Ok(())
}
<commit_msg>Fix bones being placed in incorrect locations<commit_after>use std::fs::File;
use std::io::{BufReader};
use std::path::PathBuf;

use cgmath::{Matrix4, Deg, Rad, Vector4, SquareMatrix, Vector3};
use soto::task::{task_log};
use soto::Error;
use sotolib_fbx::{RawFbx, SimpleFbx, FbxObject, id_name, friendly_name, FbxObjectTreeNode};
use sotolib_smd::{Smd, SmdVertex, SmdTriangle, SmdExportExt, SmdAnimationFrameBone, SmdBone};

pub fn create_reference_smd(fbx: &PathBuf, target_smd: &PathBuf) -> Result<(), Error> {
    // Read in the fbx we got told to convert
    let file = BufReader::new(File::open(&fbx).unwrap());
    let fbx = SimpleFbx::from_raw(&RawFbx::parse(file).unwrap());
    let fbx_tree = FbxObjectTreeNode::from_simple(&fbx);

    // Go over all FBX root nodes and turn them into SMD data
    let mut smd = Smd::new();
    let matrix = Matrix4::identity();
    process_fbx_node(&fbx_tree, &matrix, &mut smd, None)?;

    // Export the SMD
    let export_file = File::create(target_smd)?;
    smd.export(export_file).unwrap();

    Ok(())
}

fn process_fbx_node(
    fbx_node: &FbxObjectTreeNode, matrix: &Matrix4<f32>,
    mut smd: &mut Smd, current_bone: Option<&SmdBone>,
) -> Result<(), Error> {
    // Perform node type specific information
    match fbx_node.object {
        FbxObject::Geometry(ref geometry) => {
            // Add triangles to parent node
            let tris = geometry.triangles();
            for tri in tris {
                // Turn the vertices in this triangle to SMD vertices
                let mut smd_verts: [SmdVertex; 3] = Default::default();
                for (i, vert) in tri.iter().enumerate() {
                    // Multiply the vectors that need to be multiplied
                    let pos = matrix * Vector4::new(vert.0[0], vert.0[1], vert.0[2], 1.0);
                    let norm = matrix * Vector4::new(vert.1[0], vert.1[1], vert.1[2], 0.0);

                    smd_verts[i] = SmdVertex {
                        parent_bone: current_bone.unwrap().id, // This is overwritten by links
                        position: pos.truncate().into(),
                        normal: norm.truncate().into(),
                        uv: vert.2,
                        links: vec!(
                            /*Not needed, we aren't using weights anyways so this done by parent_bone
                            SmdLink {
                                bone: bone_id,
                                weight: 1.0,
                            }*/
                        )
                    };
                }

                // Add the actual SMD triangle
                smd.triangles.push(SmdTriangle {
                    material: "layl_test_texture".into(),
                    vertices: smd_verts,
                });
            }
        },
        FbxObject::Model(ref model) => {
            task_log(format!("Adding model \"{}\" to SMD data", friendly_name(&model.name)));

            // Create a new transformation matrix
            let rot_pivot: Vector3<_> = model.rotation_pivot.into();
            let rot_pivot_mat = Matrix4::from_translation(rot_pivot);
            let local_matrix =
                Matrix4::from_translation(model.translation.into()) *
                rot_pivot_mat *
                Matrix4::from_angle_z(Deg(model.rotation[2])) *
                Matrix4::from_angle_y(Deg(model.rotation[1])) *
                Matrix4::from_angle_x(Deg(model.rotation[0])) *
                rot_pivot_mat.invert().unwrap() * // This may need to be rotated in reverse (Bug?)
                Matrix4::from_nonuniform_scale(model.scale[0], model.scale[1], model.scale[2]);

            // Special matrix for bone pivot (Perhaps also for child nodes, and the other is only
            // for vertices? See above (Bug?))
            let local_matrix_for_bone_pivot =
                Matrix4::from_translation(model.translation.into()) *
                rot_pivot_mat *
                Matrix4::from_angle_z(Deg(model.rotation[2])) *
                Matrix4::from_angle_y(Deg(model.rotation[1])) *
                Matrix4::from_angle_x(Deg(model.rotation[0])) *
                Matrix4::from_nonuniform_scale(model.scale[0], model.scale[1], model.scale[2]);

            // Create a new bone and set the transformations
            let new_bone = smd.new_bone(&id_name(&model.name).unwrap(), current_bone.map(|b| b.id))
                .ok_or_else(|| Error::Task(format!("Bone \"{}\" exists multiple times in the FBX", &model.name)))?
                .clone(); // Clone needed to avoid a borrow since we need to mut borrow later
            let first_frame = SmdAnimationFrameBone {
                // This needs to be derived from the matrix to get the right location
                translation: (
                    local_matrix_for_bone_pivot *
                    Vector4::new(0.0, 0.0, 0.0, 1.0)
                ).truncate().into(),
                rotation: [
                    Rad::from(Deg(model.rotation[0])).0,
                    Rad::from(Deg(model.rotation[1])).0,
                    Rad::from(Deg(model.rotation[2])).0,
                ],
            };
            smd.set_animation(0, new_bone.id, first_frame);

            // Make a new matrix for children
            let matrix = matrix * local_matrix;

            // Make sure the child nodes will receive this new bone
            for node in &fbx_node.nodes {
                process_fbx_node(node, &matrix, smd, Some(&new_bone))?;
            }
        },
        FbxObject::Root | FbxObject::NotSupported(_) => {
            // Just go straight to the children
            for node in &fbx_node.nodes {
                process_fbx_node(node, matrix, smd, current_bone)?;
            }
        }
    }

    Ok(())
}
<|endoftext|>


<commit_before>use std::path::PathBuf;
use std::path::Path;
use std::fmt::{Debug, Formatter, Error as FmtError};
use std::result::Result as RResult;

use toml::Value;

use libimagerror::trace::trace_error;
use libimagstore::hook::Hook;
use libimagstore::hook::accessor::StoreIdAccessor;
use libimagstore::hook::accessor::{HookDataAccessor, HookDataAccessorProvider};
use libimagstore::hook::position::HookPosition;
use libimagstore::hook::result::HookResult;
use libimagstore::storeid::StoreId;
use libimagutil::debug_result::*;

use vcs::git::error::GitHookErrorKind as GHEK;
use vcs::git::error::MapErrInto;
use vcs::git::runtime::Runtime as GRuntime;

/// The `UpdateHook` type
///
/// Represents a hook which is executed whenever a entry in the store is updated (written to disk).
///
/// # Time of execution
///
/// This hook is executed _after_ the store operation succeeded, so _after_ the file is written to
/// disk.
pub struct UpdateHook {
    storepath: PathBuf,

    runtime: GRuntime,

    position: HookPosition,
}

impl UpdateHook {

    pub fn new(storepath: PathBuf, p: HookPosition) -> UpdateHook {
        UpdateHook {
            runtime: GRuntime::new(&storepath),
            storepath: storepath,
            position: p,
        }
    }

}

impl Debug for UpdateHook {
    fn fmt(&self, fmt: &mut Formatter) -> RResult<(), FmtError> {
        write!(fmt, "UpdateHook(storepath={:?}, repository={}, pos={:?}, cfg={:?})",
               self.storepath,
               (if self.runtime.has_repository() { "Some(_)" } else { "None" }),
               self.position,
               self.runtime.has_config())
    }
}

impl Hook for UpdateHook {

    fn name(&self) -> &'static str {
        "stdhook_git_update"
    }

    /// Set the configuration of the hook. See
    /// `libimagstorestdhook::vcs::git::runtime::Runtime::set_config()`.
    ///
    /// This function traces the error (using `trace_error()`) that
    /// `libimagstorestdhook::vcs::git::runtime::Runtime::set_config()`
    /// returns, if any.
    fn set_config(&mut self, config: &Value) {
        if let Err(e) = self.runtime.set_config(config) {
            trace_error(&e);
        }
    }

}

impl HookDataAccessorProvider for UpdateHook {

    fn accessor(&self) -> HookDataAccessor {
        HookDataAccessor::StoreIdAccess(self)
    }
}

impl StoreIdAccessor for UpdateHook {

    /// The implementation of the UpdateHook
    ///
    /// # Scope
    ///
    /// This hook takes the git index and commits it either interactively or with a default message,
    /// if there is no configuration for an interactive commit.
    ///
    fn access(&self, id: &StoreId) -> HookResult<()> {
        use vcs::git::action::StoreAction;
        use vcs::git::config::commit_message;
        use vcs::git::error::MapIntoHookError;
        use vcs::git::util::fetch_index;
        use git2::{ADD_DEFAULT, STATUS_WT_NEW, STATUS_WT_MODIFIED, IndexMatchedPath};

        debug!("[GIT UPDATE HOOK]: {:?}", id);

        let action    = StoreAction::Update;
        let cfg       = try!(self.runtime.config_value_or_err(&action));
        let repo      = try!(self.runtime.repository(&action));
        let mut index = try!(fetch_index(repo, &action));

        if !self.runtime.repo_is_dirty(&index) {
            debug!("[GIT UPDATE HOOK]: Repository seems to be clean. I'm done.");
            return Ok(())
        } else {
            debug!("[GIT UPDATE HOOK]: Repository seems to be dirty. Continuing.");
        }

        let signature = try!(
            repo.signature()
                .map_err_into(GHEK::MkSignature)
                .map_dbg_err_str("Failed to fetch signature")
                .map_dbg_str("[GIT UPDATE HOOK]: Fetched signature object")
                .map_into_hook_error()
        );

        let head = try!(
            repo.head()
                .map_err_into(GHEK::HeadFetchError)
                .map_dbg_err_str("Failed to fetch HEAD")
                .map_dbg_str("[GIT UPDATE HOOK]: Fetched HEAD")
                .map_into_hook_error()
        );

        let file_status = try!(
            repo
                .status_file(id.local())
                .map_dbg_err_str("Failed to fetch file status")
                .map_dbg_err(|e| format!("\t->  {:?}", e))
                .map_dbg_str("[GIT UPDATE HOOK]: Fetched file status")
                .map_err_into(GHEK::RepositoryFileStatusError)
                .map_into_hook_error()
        );

        let cb = &mut |path: &Path, _matched_spec: &[u8]| -> i32 {
            if file_status.contains(STATUS_WT_NEW) || file_status.contains(STATUS_WT_MODIFIED) {
                debug!("[GIT CREATE HOOK]: File is modified/new: {}", path.display());
                0
            } else {
                debug!("[GIT CREATE HOOK]: Ignoring file: {}", path.display());
                1
            }
        };

        try!(
            index.add_all(&[id.local()], ADD_DEFAULT, Some(cb as &mut IndexMatchedPath))
                .map_err_into(GHEK::RepositoryPathAddingError)
                .map_dbg_err_str("Failed to add to index")
                .map_dbg(|_| format!("[GIT UPDATE HOOK]: Added id ({:?}) to index", id))
                .map_into_hook_error()
        );

        let tree_id = try!(
            index.write_tree()
                .map_err_into(GHEK::RepositoryIndexWritingError)
                .map_dbg_err_str("Failed to write tree")
                .map_dbg_str("[GIT UPDATE HOOK]: Wrote tree")
                .map_into_hook_error()
        );

        let mut parents = Vec::new();
        {
            let commit = try!(
                repo.find_commit(head.target().unwrap())
                    .map_err_into(GHEK::RepositoryParentFetchingError)
                    .map_dbg_err_str("Failed to find commit HEAD")
                    .map_dbg_str("[GIT UPDATE HOOK]: Fetched commit parents of HEAD")
                    .map_into_hook_error()
            );
            parents.push(commit);
        }

        // for converting from Vec<Commit> to Vec<&Commit>
        let parents = parents.iter().collect::<Vec<_>>();

        let tree = try!(
            repo.find_tree(tree_id)
                .map_err_into(GHEK::RepositoryParentFetchingError)
                .map_dbg_err_str("Failed to find tree")
                .map_dbg_str("[GIT UPDATE HOOK]: Found Tree for parents")
                .map_into_hook_error()
        );

        let message = try!(commit_message(&repo, cfg, StoreAction::Update, &id)
                .map_dbg_err_str("Failed to get commit message")
                .map_dbg_str("[GIT UPDATE HOOK]: Fetched commit message"));

        try!(repo.commit(Some("HEAD"), &signature, &signature, &message, &tree, &parents)
            .map_dbg_str("Committed")
            .map_dbg_err_str("Failed to commit")
            .map_err_into(GHEK::RepositoryCommittingError)
            .map_into_hook_error()
        );

        index.write()
            .map_err_into(GHEK::RepositoryIndexWritingError)
            .map_dbg_err_str("Failed to write tree")
            .map_into_hook_error()
            .map(|_| ())
    }

}

<commit_msg>UpdateHook: Add config-based abort if repo init failed<commit_after>use std::path::PathBuf;
use std::path::Path;
use std::fmt::{Debug, Formatter, Error as FmtError};
use std::result::Result as RResult;

use toml::Value;

use libimagerror::trace::trace_error;
use libimagstore::hook::Hook;
use libimagstore::hook::accessor::StoreIdAccessor;
use libimagstore::hook::accessor::{HookDataAccessor, HookDataAccessorProvider};
use libimagstore::hook::position::HookPosition;
use libimagstore::hook::result::HookResult;
use libimagstore::storeid::StoreId;
use libimagutil::debug_result::*;

use vcs::git::error::GitHookErrorKind as GHEK;
use vcs::git::error::MapErrInto;
use vcs::git::runtime::Runtime as GRuntime;

/// The `UpdateHook` type
///
/// Represents a hook which is executed whenever a entry in the store is updated (written to disk).
///
/// # Time of execution
///
/// This hook is executed _after_ the store operation succeeded, so _after_ the file is written to
/// disk.
pub struct UpdateHook {
    storepath: PathBuf,

    runtime: GRuntime,

    position: HookPosition,
}

impl UpdateHook {

    pub fn new(storepath: PathBuf, p: HookPosition) -> UpdateHook {
        UpdateHook {
            runtime: GRuntime::new(&storepath),
            storepath: storepath,
            position: p,
        }
    }

}

impl Debug for UpdateHook {
    fn fmt(&self, fmt: &mut Formatter) -> RResult<(), FmtError> {
        write!(fmt, "UpdateHook(storepath={:?}, repository={}, pos={:?}, cfg={:?})",
               self.storepath,
               (if self.runtime.has_repository() { "Some(_)" } else { "None" }),
               self.position,
               self.runtime.has_config())
    }
}

impl Hook for UpdateHook {

    fn name(&self) -> &'static str {
        "stdhook_git_update"
    }

    /// Set the configuration of the hook. See
    /// `libimagstorestdhook::vcs::git::runtime::Runtime::set_config()`.
    ///
    /// This function traces the error (using `trace_error()`) that
    /// `libimagstorestdhook::vcs::git::runtime::Runtime::set_config()`
    /// returns, if any.
    fn set_config(&mut self, config: &Value) {
        if let Err(e) = self.runtime.set_config(config) {
            trace_error(&e);
        }
    }

}

impl HookDataAccessorProvider for UpdateHook {

    fn accessor(&self) -> HookDataAccessor {
        HookDataAccessor::StoreIdAccess(self)
    }
}

impl StoreIdAccessor for UpdateHook {

    /// The implementation of the UpdateHook
    ///
    /// # Scope
    ///
    /// This hook takes the git index and commits it either interactively or with a default message,
    /// if there is no configuration for an interactive commit.
    ///
    fn access(&self, id: &StoreId) -> HookResult<()> {
        use libimagerror::into::IntoError;
        use vcs::git::action::StoreAction;
        use vcs::git::config::commit_message;
        use vcs::git::error::MapIntoHookError;
        use vcs::git::util::fetch_index;
        use vcs::git::config::abort_on_repo_init_err;
        use git2::{ADD_DEFAULT, STATUS_WT_NEW, STATUS_WT_MODIFIED, IndexMatchedPath};

        debug!("[GIT UPDATE HOOK]: {:?}", id);

        let action = StoreAction::Update;

        if !self.runtime.has_repository() {
            debug!("[GIT UPDATE HOOK]: Runtime has no repository...");
            if try!(self.runtime.config_value_or_err(&action).map(|c| abort_on_repo_init_err(c))) {
                // Abort on repo init failure
                debug!("[GIT UPDATE HOOK]: Config says we should abort if we have no repository");
                debug!("[GIT UPDATE HOOK]: Returing Err(_)");
                return Err(GHEK::RepositoryInitError.into_error())
                    .map_err_into(GHEK::RepositoryError)
                    .map_into_hook_error()
            } else {
                debug!("[GIT UPDATE HOOK]: Config says it is okay to not have a repository");
                debug!("[GIT UPDATE HOOK]: Returing Ok(())");
                return Ok(())
            }
        }

        let cfg       = try!(self.runtime.config_value_or_err(&action));
        let repo      = try!(self.runtime.repository(&action));
        let mut index = try!(fetch_index(repo, &action));

        if !self.runtime.repo_is_dirty(&index) {
            debug!("[GIT UPDATE HOOK]: Repository seems to be clean. I'm done.");
            return Ok(())
        } else {
            debug!("[GIT UPDATE HOOK]: Repository seems to be dirty. Continuing.");
        }

        let signature = try!(
            repo.signature()
                .map_err_into(GHEK::MkSignature)
                .map_dbg_err_str("Failed to fetch signature")
                .map_dbg_str("[GIT UPDATE HOOK]: Fetched signature object")
                .map_into_hook_error()
        );

        let head = try!(
            repo.head()
                .map_err_into(GHEK::HeadFetchError)
                .map_dbg_err_str("Failed to fetch HEAD")
                .map_dbg_str("[GIT UPDATE HOOK]: Fetched HEAD")
                .map_into_hook_error()
        );

        let file_status = try!(
            repo
                .status_file(id.local())
                .map_dbg_err_str("Failed to fetch file status")
                .map_dbg_err(|e| format!("\t->  {:?}", e))
                .map_dbg_str("[GIT UPDATE HOOK]: Fetched file status")
                .map_err_into(GHEK::RepositoryFileStatusError)
                .map_into_hook_error()
        );

        let cb = &mut |path: &Path, _matched_spec: &[u8]| -> i32 {
            if file_status.contains(STATUS_WT_NEW) || file_status.contains(STATUS_WT_MODIFIED) {
                debug!("[GIT CREATE HOOK]: File is modified/new: {}", path.display());
                0
            } else {
                debug!("[GIT CREATE HOOK]: Ignoring file: {}", path.display());
                1
            }
        };

        try!(
            index.add_all(&[id.local()], ADD_DEFAULT, Some(cb as &mut IndexMatchedPath))
                .map_err_into(GHEK::RepositoryPathAddingError)
                .map_dbg_err_str("Failed to add to index")
                .map_dbg(|_| format!("[GIT UPDATE HOOK]: Added id ({:?}) to index", id))
                .map_into_hook_error()
        );

        let tree_id = try!(
            index.write_tree()
                .map_err_into(GHEK::RepositoryIndexWritingError)
                .map_dbg_err_str("Failed to write tree")
                .map_dbg_str("[GIT UPDATE HOOK]: Wrote tree")
                .map_into_hook_error()
        );

        let mut parents = Vec::new();
        {
            let commit = try!(
                repo.find_commit(head.target().unwrap())
                    .map_err_into(GHEK::RepositoryParentFetchingError)
                    .map_dbg_err_str("Failed to find commit HEAD")
                    .map_dbg_str("[GIT UPDATE HOOK]: Fetched commit parents of HEAD")
                    .map_into_hook_error()
            );
            parents.push(commit);
        }

        // for converting from Vec<Commit> to Vec<&Commit>
        let parents = parents.iter().collect::<Vec<_>>();

        let tree = try!(
            repo.find_tree(tree_id)
                .map_err_into(GHEK::RepositoryParentFetchingError)
                .map_dbg_err_str("Failed to find tree")
                .map_dbg_str("[GIT UPDATE HOOK]: Found Tree for parents")
                .map_into_hook_error()
        );

        let message = try!(commit_message(&repo, cfg, StoreAction::Update, &id)
                .map_dbg_err_str("Failed to get commit message")
                .map_dbg_str("[GIT UPDATE HOOK]: Fetched commit message"));

        try!(repo.commit(Some("HEAD"), &signature, &signature, &message, &tree, &parents)
            .map_dbg_str("Committed")
            .map_dbg_err_str("Failed to commit")
            .map_err_into(GHEK::RepositoryCommittingError)
            .map_into_hook_error()
        );

        index.write()
            .map_err_into(GHEK::RepositoryIndexWritingError)
            .map_dbg_err_str("Failed to write tree")
            .map_into_hook_error()
            .map(|_| ())
    }

}

<|endoftext|>


<commit_before>use std::path::PathBuf;
use std::path::Path;
use std::fmt::{Debug, Formatter, Error as FmtError};
use std::result::Result as RResult;

use toml::Value;

use libimagerror::trace::trace_error;
use libimagstore::hook::Hook;
use libimagstore::hook::accessor::StoreIdAccessor;
use libimagstore::hook::accessor::{HookDataAccessor, HookDataAccessorProvider};
use libimagstore::hook::position::HookPosition;
use libimagstore::hook::result::HookResult;
use libimagstore::storeid::StoreId;
use libimagutil::debug_result::*;

use vcs::git::error::GitHookErrorKind as GHEK;
use vcs::git::error::MapErrInto;
use vcs::git::runtime::Runtime as GRuntime;

/// The `UpdateHook` type
///
/// Represents a hook which is executed whenever a entry in the store is updated (written to disk).
///
/// # Time of execution
///
/// This hook is executed _after_ the store operation succeeded, so _after_ the file is written to
/// disk.
pub struct UpdateHook {
    storepath: PathBuf,

    runtime: GRuntime,

    position: HookPosition,
}

impl UpdateHook {

    pub fn new(storepath: PathBuf, p: HookPosition) -> UpdateHook {
        UpdateHook {
            runtime: GRuntime::new(&storepath),
            storepath: storepath,
            position: p,
        }
    }

}

impl Debug for UpdateHook {
    fn fmt(&self, fmt: &mut Formatter) -> RResult<(), FmtError> {
        write!(fmt, "UpdateHook(storepath={:?}, repository={}, pos={:?}, cfg={:?})",
               self.storepath,
               (if self.runtime.has_repository() { "Some(_)" } else { "None" }),
               self.position,
               self.runtime.has_config())
    }
}

impl Hook for UpdateHook {

    fn name(&self) -> &'static str {
        "stdhook_git_update"
    }

    /// Set the configuration of the hook. See
    /// `libimagstorestdhook::vcs::git::runtime::Runtime::set_config()`.
    ///
    /// This function traces the error (using `trace_error()`) that
    /// `libimagstorestdhook::vcs::git::runtime::Runtime::set_config()`
    /// returns, if any.
    fn set_config(&mut self, config: &Value) {
        if let Err(e) = self.runtime.set_config(config) {
            trace_error(&e);
        }
    }

}

impl HookDataAccessorProvider for UpdateHook {

    fn accessor(&self) -> HookDataAccessor {
        HookDataAccessor::StoreIdAccess(self)
    }
}

impl StoreIdAccessor for UpdateHook {

    /// The implementation of the UpdateHook
    ///
    /// # Scope
    ///
    /// This hook takes the git index and commits it either interactively or with a default message,
    /// if there is no configuration for an interactive commit.
    ///
    fn access(&self, id: &StoreId) -> HookResult<()> {
        use libimagerror::into::IntoError;
        use vcs::git::action::StoreAction;
        use vcs::git::config::commit_message;
        use vcs::git::error::MapIntoHookError;
        use vcs::git::util::fetch_index;
        use vcs::git::config::abort_on_repo_init_err;
        use git2::{ADD_DEFAULT, STATUS_WT_NEW, STATUS_WT_MODIFIED, IndexMatchedPath};

        debug!("[GIT UPDATE HOOK]: {:?}", id);

        let action = StoreAction::Update;

        if !self.runtime.has_repository() {
            debug!("[GIT UPDATE HOOK]: Runtime has no repository...");
            if try!(self.runtime.config_value_or_err(&action).map(|c| abort_on_repo_init_err(c))) {
                // Abort on repo init failure
                debug!("[GIT UPDATE HOOK]: Config says we should abort if we have no repository");
                debug!("[GIT UPDATE HOOK]: Returing Err(_)");
                return Err(GHEK::RepositoryInitError.into_error())
                    .map_err_into(GHEK::RepositoryError)
                    .map_into_hook_error()
            } else {
                debug!("[GIT UPDATE HOOK]: Config says it is okay to not have a repository");
                debug!("[GIT UPDATE HOOK]: Returing Ok(())");
                return Ok(())
            }
        }

        let _         = try!(self.runtime.ensure_cfg_branch_is_checked_out(&action));
        let cfg       = try!(self.runtime.config_value_or_err(&action));
        let repo      = try!(self.runtime.repository(&action));
        let mut index = try!(fetch_index(repo, &action));

        if !self.runtime.repo_is_dirty(&index) {
            debug!("[GIT UPDATE HOOK]: Repository seems to be clean. I'm done.");
            return Ok(())
        } else {
            debug!("[GIT UPDATE HOOK]: Repository seems to be dirty. Continuing.");
        }

        let signature = try!(
            repo.signature()
                .map_err_into(GHEK::MkSignature)
                .map_dbg_err_str("Failed to fetch signature")
                .map_dbg_str("[GIT UPDATE HOOK]: Fetched signature object")
                .map_into_hook_error()
        );

        let head = try!(
            repo.head()
                .map_err_into(GHEK::HeadFetchError)
                .map_dbg_err_str("Failed to fetch HEAD")
                .map_dbg_str("[GIT UPDATE HOOK]: Fetched HEAD")
                .map_into_hook_error()
        );

        let file_status = try!(
            repo
                .status_file(id.local())
                .map_dbg_err_str("Failed to fetch file status")
                .map_dbg_err(|e| format!("\t->  {:?}", e))
                .map_dbg_str("[GIT UPDATE HOOK]: Fetched file status")
                .map_err_into(GHEK::RepositoryFileStatusError)
                .map_into_hook_error()
        );

        let cb = &mut |path: &Path, _matched_spec: &[u8]| -> i32 {
            if file_status.contains(STATUS_WT_NEW) || file_status.contains(STATUS_WT_MODIFIED) {
                debug!("[GIT CREATE HOOK]: File is modified/new: {}", path.display());
                0
            } else {
                debug!("[GIT CREATE HOOK]: Ignoring file: {}", path.display());
                1
            }
        };

        try!(
            index.add_all(&[id.local()], ADD_DEFAULT, Some(cb as &mut IndexMatchedPath))
                .map_err_into(GHEK::RepositoryPathAddingError)
                .map_dbg_err_str("Failed to add to index")
                .map_dbg(|_| format!("[GIT UPDATE HOOK]: Added id ({:?}) to index", id))
                .map_into_hook_error()
        );

        let tree_id = try!(
            index.write_tree()
                .map_err_into(GHEK::RepositoryIndexWritingError)
                .map_dbg_err_str("Failed to write tree")
                .map_dbg_str("[GIT UPDATE HOOK]: Wrote tree")
                .map_into_hook_error()
        );

        let mut parents = Vec::new();
        {
            let commit = try!(
                repo.find_commit(head.target().unwrap())
                    .map_err_into(GHEK::RepositoryParentFetchingError)
                    .map_dbg_err_str("Failed to find commit HEAD")
                    .map_dbg_str("[GIT UPDATE HOOK]: Fetched commit parents of HEAD")
                    .map_into_hook_error()
            );
            parents.push(commit);
        }

        // for converting from Vec<Commit> to Vec<&Commit>
        let parents = parents.iter().collect::<Vec<_>>();

        let tree = try!(
            repo.find_tree(tree_id)
                .map_err_into(GHEK::RepositoryParentFetchingError)
                .map_dbg_err_str("Failed to find tree")
                .map_dbg_str("[GIT UPDATE HOOK]: Found Tree for parents")
                .map_into_hook_error()
        );

        let message = try!(commit_message(&repo, cfg, StoreAction::Update, &id)
                .map_dbg_err_str("Failed to get commit message")
                .map_dbg_str("[GIT UPDATE HOOK]: Fetched commit message"));

        try!(repo.commit(Some("HEAD"), &signature, &signature, &message, &tree, &parents)
            .map_dbg_str("Committed")
            .map_dbg_err_str("Failed to commit")
            .map_err_into(GHEK::RepositoryCommittingError)
            .map_into_hook_error()
        );

        index.write()
            .map_err_into(GHEK::RepositoryIndexWritingError)
            .map_dbg_err_str("Failed to write tree")
            .map_into_hook_error()
            .map(|_| ())
    }

}

<commit_msg>UpdateHook: return Ok(()) if disabled<commit_after>use std::path::PathBuf;
use std::path::Path;
use std::fmt::{Debug, Formatter, Error as FmtError};
use std::result::Result as RResult;

use toml::Value;

use libimagerror::trace::trace_error;
use libimagstore::hook::Hook;
use libimagstore::hook::accessor::StoreIdAccessor;
use libimagstore::hook::accessor::{HookDataAccessor, HookDataAccessorProvider};
use libimagstore::hook::position::HookPosition;
use libimagstore::hook::result::HookResult;
use libimagstore::storeid::StoreId;
use libimagutil::debug_result::*;

use vcs::git::error::GitHookErrorKind as GHEK;
use vcs::git::error::MapErrInto;
use vcs::git::runtime::Runtime as GRuntime;

/// The `UpdateHook` type
///
/// Represents a hook which is executed whenever a entry in the store is updated (written to disk).
///
/// # Time of execution
///
/// This hook is executed _after_ the store operation succeeded, so _after_ the file is written to
/// disk.
pub struct UpdateHook {
    storepath: PathBuf,

    runtime: GRuntime,

    position: HookPosition,
}

impl UpdateHook {

    pub fn new(storepath: PathBuf, p: HookPosition) -> UpdateHook {
        UpdateHook {
            runtime: GRuntime::new(&storepath),
            storepath: storepath,
            position: p,
        }
    }

}

impl Debug for UpdateHook {
    fn fmt(&self, fmt: &mut Formatter) -> RResult<(), FmtError> {
        write!(fmt, "UpdateHook(storepath={:?}, repository={}, pos={:?}, cfg={:?})",
               self.storepath,
               (if self.runtime.has_repository() { "Some(_)" } else { "None" }),
               self.position,
               self.runtime.has_config())
    }
}

impl Hook for UpdateHook {

    fn name(&self) -> &'static str {
        "stdhook_git_update"
    }

    /// Set the configuration of the hook. See
    /// `libimagstorestdhook::vcs::git::runtime::Runtime::set_config()`.
    ///
    /// This function traces the error (using `trace_error()`) that
    /// `libimagstorestdhook::vcs::git::runtime::Runtime::set_config()`
    /// returns, if any.
    fn set_config(&mut self, config: &Value) {
        if let Err(e) = self.runtime.set_config(config) {
            trace_error(&e);
        }
    }

}

impl HookDataAccessorProvider for UpdateHook {

    fn accessor(&self) -> HookDataAccessor {
        HookDataAccessor::StoreIdAccess(self)
    }
}

impl StoreIdAccessor for UpdateHook {

    /// The implementation of the UpdateHook
    ///
    /// # Scope
    ///
    /// This hook takes the git index and commits it either interactively or with a default message,
    /// if there is no configuration for an interactive commit.
    ///
    fn access(&self, id: &StoreId) -> HookResult<()> {
        use libimagerror::into::IntoError;
        use vcs::git::action::StoreAction;
        use vcs::git::config::commit_message;
        use vcs::git::error::MapIntoHookError;
        use vcs::git::util::fetch_index;
        use vcs::git::config::abort_on_repo_init_err;
        use vcs::git::config::is_enabled;
        use git2::{ADD_DEFAULT, STATUS_WT_NEW, STATUS_WT_MODIFIED, IndexMatchedPath};

        debug!("[GIT UPDATE HOOK]: {:?}", id);

        let action = StoreAction::Update;
        let cfg    = try!(self.runtime.config_value_or_err(&action));

        if !is_enabled(cfg) {
            return Ok(());
        }

        if !self.runtime.has_repository() {
            debug!("[GIT UPDATE HOOK]: Runtime has no repository...");
            if try!(self.runtime.config_value_or_err(&action).map(|c| abort_on_repo_init_err(c))) {
                // Abort on repo init failure
                debug!("[GIT UPDATE HOOK]: Config says we should abort if we have no repository");
                debug!("[GIT UPDATE HOOK]: Returing Err(_)");
                return Err(GHEK::RepositoryInitError.into_error())
                    .map_err_into(GHEK::RepositoryError)
                    .map_into_hook_error()
            } else {
                debug!("[GIT UPDATE HOOK]: Config says it is okay to not have a repository");
                debug!("[GIT UPDATE HOOK]: Returing Ok(())");
                return Ok(())
            }
        }

        let _         = try!(self.runtime.ensure_cfg_branch_is_checked_out(&action));
        let repo      = try!(self.runtime.repository(&action));
        let mut index = try!(fetch_index(repo, &action));

        if !self.runtime.repo_is_dirty(&index) {
            debug!("[GIT UPDATE HOOK]: Repository seems to be clean. I'm done.");
            return Ok(())
        } else {
            debug!("[GIT UPDATE HOOK]: Repository seems to be dirty. Continuing.");
        }

        let signature = try!(
            repo.signature()
                .map_err_into(GHEK::MkSignature)
                .map_dbg_err_str("Failed to fetch signature")
                .map_dbg_str("[GIT UPDATE HOOK]: Fetched signature object")
                .map_into_hook_error()
        );

        let head = try!(
            repo.head()
                .map_err_into(GHEK::HeadFetchError)
                .map_dbg_err_str("Failed to fetch HEAD")
                .map_dbg_str("[GIT UPDATE HOOK]: Fetched HEAD")
                .map_into_hook_error()
        );

        let file_status = try!(
            repo
                .status_file(id.local())
                .map_dbg_err_str("Failed to fetch file status")
                .map_dbg_err(|e| format!("\t->  {:?}", e))
                .map_dbg_str("[GIT UPDATE HOOK]: Fetched file status")
                .map_err_into(GHEK::RepositoryFileStatusError)
                .map_into_hook_error()
        );

        let cb = &mut |path: &Path, _matched_spec: &[u8]| -> i32 {
            if file_status.contains(STATUS_WT_NEW) || file_status.contains(STATUS_WT_MODIFIED) {
                debug!("[GIT CREATE HOOK]: File is modified/new: {}", path.display());
                0
            } else {
                debug!("[GIT CREATE HOOK]: Ignoring file: {}", path.display());
                1
            }
        };

        try!(
            index.add_all(&[id.local()], ADD_DEFAULT, Some(cb as &mut IndexMatchedPath))
                .map_err_into(GHEK::RepositoryPathAddingError)
                .map_dbg_err_str("Failed to add to index")
                .map_dbg(|_| format!("[GIT UPDATE HOOK]: Added id ({:?}) to index", id))
                .map_into_hook_error()
        );

        let tree_id = try!(
            index.write_tree()
                .map_err_into(GHEK::RepositoryIndexWritingError)
                .map_dbg_err_str("Failed to write tree")
                .map_dbg_str("[GIT UPDATE HOOK]: Wrote tree")
                .map_into_hook_error()
        );

        let mut parents = Vec::new();
        {
            let commit = try!(
                repo.find_commit(head.target().unwrap())
                    .map_err_into(GHEK::RepositoryParentFetchingError)
                    .map_dbg_err_str("Failed to find commit HEAD")
                    .map_dbg_str("[GIT UPDATE HOOK]: Fetched commit parents of HEAD")
                    .map_into_hook_error()
            );
            parents.push(commit);
        }

        // for converting from Vec<Commit> to Vec<&Commit>
        let parents = parents.iter().collect::<Vec<_>>();

        let tree = try!(
            repo.find_tree(tree_id)
                .map_err_into(GHEK::RepositoryParentFetchingError)
                .map_dbg_err_str("Failed to find tree")
                .map_dbg_str("[GIT UPDATE HOOK]: Found Tree for parents")
                .map_into_hook_error()
        );

        let message = try!(commit_message(&repo, cfg, StoreAction::Update, &id)
                .map_dbg_err_str("Failed to get commit message")
                .map_dbg_str("[GIT UPDATE HOOK]: Fetched commit message"));

        try!(repo.commit(Some("HEAD"), &signature, &signature, &message, &tree, &parents)
            .map_dbg_str("Committed")
            .map_dbg_err_str("Failed to commit")
            .map_err_into(GHEK::RepositoryCommittingError)
            .map_into_hook_error()
        );

        index.write()
            .map_err_into(GHEK::RepositoryIndexWritingError)
            .map_dbg_err_str("Failed to write tree")
            .map_into_hook_error()
            .map(|_| ())
    }

}

<|endoftext|>


<commit_before>extern crate "parser-combinators" as parser_combinators;

use self::parser_combinators::{try, between, spaces, parser, many, many1, digit, optional, hex_digit, not_followed_by, satisfy, Parser, ParserExt, ParseResult};
use self::parser_combinators::primitives::{State, Stream};
use super::ast::*;
use super::ast::ExprNode::*;
use std::str::FromStr;
use std::num::FromStrRadix;

fn sint_const<I>(input: State<I>) -> ParseResult<NumNode, I>
    where I: Stream<Item=char> {
        optional(satisfy(|c| c == '-'))
            .and(
                (satisfy(|c| c == '0')
                    .and(satisfy(|c| c == 'x' || c == 'X')))
                    .with(many1::<Vec<_>, _>(hex_digit()))
                    .map(|x| {
                        isize::from_str_radix(
                            x.iter()
                             .fold(
                                String::new(),
                                |mut s: String, i| { s.push(*i); s })
                             .as_slice(),
                        16).unwrap()
                    })
                .or( many1::<Vec<_>, _>(digit()).map(|x|
                    isize::from_str(x.iter().fold(
                        String::new(), |mut s: String, i| { s.push(*i); s })
                        .as_slice()
                    ).unwrap()
                    )
                )
                )
            .map(|x| {
                if let Some(sign) = x.0 {
                    let mut s = String::new();
                    s.push(sign);
                    s.push('1');
                    x.1 * isize::from_str(s.as_slice()).unwrap()
                } else {
                    x.1
                }
                })
            .skip(not_followed_by(satisfy(|c|
                c == 'u' || c == 'U' || c == '.' || c == 'f' || c == 'F')
            ))
            .map(|x: isize| NumNode::IntConst(IntNode{value: x}))
            .parse_state(input)
}

fn uint_const<I>(input: State<I>) -> ParseResult<NumNode, I>
    where I: Stream<Item=char> {
        (satisfy(|c| c == '0')
            .and(satisfy(|c| c == 'x' || c == 'X')))
            .with(many1::<Vec<_>, _>(hex_digit()))
            .map(|x| usize::from_str_radix(
                    x.iter()
                     .fold(
                        String::new(),
                        |mut s: String, i| { s.push(*i); s })
                     .as_slice(),
                16).unwrap()
            )
        .or( many1::<Vec<_>, _>(digit())
            .map(|x|usize::from_str(x.iter().fold(
                String::new(), |mut s: String, i| { s.push(*i); s })
                .as_slice()
            ).unwrap())
        )
        .skip(satisfy(|c| c == 'u' || c == 'U'))
        .map(|x: usize| NumNode::UIntConst(UIntNode{value: x}))
        .parse_state(input)
}

fn float_const<I>(input: State<I>) -> ParseResult<NumNode, I>
    where I: Stream<Item=char> {
        many1::<Vec<_>, _>(digit())
            .and(satisfy(|c| c == '.'))
            .and(many1::<Vec<_>, _>(digit()))
            .map(|x| {
                let mut s = String::new();
                for i in (x.0).0.iter() { s.push(*i); } ;
                s.push((x.0).1);

                for i in x.1.iter() { s.push(*i); };
                NumNode::FloatConst(FloatNode{
                    value: f64::from_str(s.as_slice()).unwrap()
                })
            })
            .skip(optional(satisfy(|c| c == 'f' || c == 'F')))
            .parse_state(input)
}

pub fn number<I>(input: State<I>) -> ParseResult<NumNode, I>
    where I: Stream<Item=char> {
        try(parser(sint_const))
            .or(try(parser(uint_const)))
            .or(try(parser(float_const)))
            .parse_state(input)
}

pub fn name<I>(input: State<I>) -> ParseResult<NameNode, I>
    where I: Stream<Item=char> {
         let initial = satisfy(|c|
            c.is_alphabetic()
                // R6RS 'special initial' characters
                || c == '!' || c == '$' || c == '%' || c == ':' || c == '^'
                || c == '<' || c == '>' || c == '_' || c == '~' || c == '\\'
                || c == '?');
        let subsequent = satisfy(|c|
            c.is_alphanumeric()
                // R6RS 'special initial' characters
                || c == '!' || c == '$' || c == '%' || c == ':' || c == '^'
                || c == '<' || c == '>' || c == '_' || c == '~' || c == '\\'
                || c == '?'
                // R6RS 'special subsequent' characters
                || c == '+' || c == '-' || c == '.' || c == '@');
        initial
            .and(many::<Vec<_>, _>(subsequent).map(|it|
                it.iter().fold(
                    String::new(),
                    |mut s: String, i| {
                        s.push(*i);
                        s
                    }
                    )
                ))
            .parse_state(input)
            .map(|x| {
                let mut s = String::new();
                s.push((x.0).0);
                s.push_str(&(x.0).1);
                (NameNode{ name: s}, x.1)
            })
}

pub fn expr<I>(input: State<I>) -> ParseResult<ExprNode, I>
    where I: Stream<Item=char> {
        let spaces = spaces();
        let sexpr = between(
            satisfy(|c| c == '('),
            satisfy(|c| c == ')'),
            parser(name)
                .and(many(parser(expr)))
                .map(|x| {
                    SExpr(SExprNode {
                        operator: x.0,
                        operands: x.1
                    })
                })
            );
        spaces.clone().with(
            sexpr
                .or(parser(name).map(Name))
            ).parse_state(input)
}

#[cfg(test)]
mod tests {
    use ::ast::*;
    use ::ast::ExprNode::*;
    use super::*;
    use super::parser_combinators::{Parser,parser};

    #[test]
    fn test_basic_ident() {
        assert_eq!(
            parser(expr).parse("ident").unwrap(),
            (Name(NameNode { name: "ident".to_string() }), "")
            );
    }

    #[test]
    fn test_basic_sexpr() {
        assert_eq!(
            parser(expr).parse("(ident arg1 arg2)").unwrap(),
            (SExpr(SExprNode {
                operator: NameNode { name: "ident".to_string() },
                operands: vec![
                    Name(NameNode { name: "arg1".to_string() }),
                    Name(NameNode { name: "arg2".to_string() })
                ]
            }), "")
            );
    }

    #[test]
    fn test_parse_sint_pos() {
        assert_eq!(
            parser(number).parse("1234"),
            Ok((NumNode::IntConst(IntNode { value: 1234isize }), ""))
            );
    }

    #[test]
    fn test_parse_sint_neg() {
        assert_eq!(
            parser(number).parse("-1234"),
            Ok((NumNode::IntConst(IntNode { value: -1234isize }), ""))
            );
    }

    #[test]
    fn test_parse_sint_hex() {
        assert_eq!(
            parser(number).parse("0x0ff"),
            Ok((NumNode::IntConst(IntNode { value: 0x0ffisize }), ""))
            );
        assert_eq!(
            parser(number).parse("0X0FF"),
            Ok((NumNode::IntConst(IntNode { value: 0x0ffisize }), ""))
            );
    }
    /* // Currently unsupported
    #[test]
    fn test_parse_sint_bin_upper() {
        assert_eq!(
            parser(number).parse("0B01"),
            Ok((NumNode::IntConst(IntNode { value: 0b01isize }), ""))
            );
        assert_eq!(
            parser(number).parse("0b01"),
            Ok((NumNode::IntConst(IntNode { value: 0b01isize }), ""))
            );
    }*/

    #[test]
    fn test_parse_uint() {
        assert_eq!(
            parser(number).parse("1234u"),
            Ok((NumNode::UIntConst(UIntNode { value: 1234usize }), ""))
            );
        assert_eq!(
            parser(number).parse("4321U"),
            Ok((NumNode::UIntConst(UIntNode { value: 4321usize }), ""))
            );
    }

    #[test]
    fn test_parse_uint_hex() {
        assert_eq!(
            parser(number).parse("0x0ffu"),
            Ok((NumNode::UIntConst(UIntNode { value: 0x0ffusize }), ""))
            );
        assert_eq!(
            parser(number).parse("0X0FFu"),
            Ok((NumNode::UIntConst(UIntNode { value: 0x0ffusize }), ""))
            );
    }

    #[test]
    fn test_parse_float() {
        assert_eq!(
            parser(number).parse("1.0").unwrap(),
            (NumNode::FloatConst(FloatNode { value: 1.0f64 }), "")
            );/* // Unsupported
        assert_eq!(
            parser(number).parse("1f").unwrap(),
            (NumNode::FloatConst(FloatNode { value: 1.0f64 }), "")
            );*/
        assert_eq!(
            parser(number).parse("22.2222").unwrap(),
            (NumNode::FloatConst(FloatNode { value: 22.2222f64 }), "")
            );
        assert_eq!(
            parser(number).parse("22.2222f").unwrap(),
            (NumNode::FloatConst(FloatNode { value: 22.2222f64 }), "")
            );
    }

}
<commit_msg>Add numeric constants to expression parser<commit_after>extern crate "parser-combinators" as parser_combinators;

use self::parser_combinators::{try, between, spaces, parser, many, many1, digit, optional, hex_digit, not_followed_by, satisfy, Parser, ParserExt, ParseResult};
use self::parser_combinators::primitives::{State, Stream};
use super::ast::*;
use super::ast::ExprNode::*;
use std::str::FromStr;
use std::num::FromStrRadix;

fn sint_const<I>(input: State<I>) -> ParseResult<NumNode, I>
    where I: Stream<Item=char> {
        optional(satisfy(|c| c == '-'))
            .and(
                (satisfy(|c| c == '0')
                    .and(satisfy(|c| c == 'x' || c == 'X')))
                    .with(many1::<Vec<_>, _>(hex_digit()))
                    .map(|x| {
                        isize::from_str_radix(
                            x.iter()
                             .fold(
                                String::new(),
                                |mut s: String, i| { s.push(*i); s })
                             .as_slice(),
                        16).unwrap()
                    })
                .or( many1::<Vec<_>, _>(digit()).map(|x|
                    isize::from_str(x.iter().fold(
                        String::new(), |mut s: String, i| { s.push(*i); s })
                        .as_slice()
                    ).unwrap()
                    )
                )
                )
            .map(|x| {
                if let Some(sign) = x.0 {
                    let mut s = String::new();
                    s.push(sign);
                    s.push('1');
                    x.1 * isize::from_str(s.as_slice()).unwrap()
                } else {
                    x.1
                }
                })
            .skip(not_followed_by(satisfy(|c|
                c == 'u' || c == 'U' || c == '.' || c == 'f' || c == 'F')
            ))
            .map(|x: isize| NumNode::IntConst(IntNode{value: x}))
            .parse_state(input)
}

fn uint_const<I>(input: State<I>) -> ParseResult<NumNode, I>
    where I: Stream<Item=char> {
        (satisfy(|c| c == '0')
            .and(satisfy(|c| c == 'x' || c == 'X')))
            .with(many1::<Vec<_>, _>(hex_digit()))
            .map(|x| usize::from_str_radix(
                    x.iter()
                     .fold(
                        String::new(),
                        |mut s: String, i| { s.push(*i); s })
                     .as_slice(),
                16).unwrap()
            )
        .or( many1::<Vec<_>, _>(digit())
            .map(|x|usize::from_str(x.iter().fold(
                String::new(), |mut s: String, i| { s.push(*i); s })
                .as_slice()
            ).unwrap())
        )
        .skip(satisfy(|c| c == 'u' || c == 'U'))
        .map(|x: usize| NumNode::UIntConst(UIntNode{value: x}))
        .parse_state(input)
}

fn float_const<I>(input: State<I>) -> ParseResult<NumNode, I>
    where I: Stream<Item=char> {
        many1::<Vec<_>, _>(digit())
            .and(satisfy(|c| c == '.'))
            .and(many1::<Vec<_>, _>(digit()))
            .map(|x| {
                let mut s = String::new();
                for i in (x.0).0.iter() { s.push(*i); } ;
                s.push((x.0).1);

                for i in x.1.iter() { s.push(*i); };
                NumNode::FloatConst(FloatNode{
                    value: f64::from_str(s.as_slice()).unwrap()
                })
            })
            .skip(optional(satisfy(|c| c == 'f' || c == 'F')))
            .parse_state(input)
}

pub fn number<I>(input: State<I>) -> ParseResult<NumNode, I>
    where I: Stream<Item=char> {
        try(parser(sint_const))
            .or(try(parser(uint_const)))
            .or(try(parser(float_const)))
            .parse_state(input)
}

pub fn name<I>(input: State<I>) -> ParseResult<NameNode, I>
    where I: Stream<Item=char> {
         let initial = satisfy(|c|
            c.is_alphabetic()
                // R6RS 'special initial' characters
                || c == '!' || c == '$' || c == '%' || c == ':' || c == '^'
                || c == '<' || c == '>' || c == '_' || c == '~' || c == '\\'
                || c == '?');
        let subsequent = satisfy(|c|
            c.is_alphanumeric()
                // R6RS 'special initial' characters
                || c == '!' || c == '$' || c == '%' || c == ':' || c == '^'
                || c == '<' || c == '>' || c == '_' || c == '~' || c == '\\'
                || c == '?'
                // R6RS 'special subsequent' characters
                || c == '+' || c == '-' || c == '.' || c == '@');
        initial
            .and(many::<Vec<_>, _>(subsequent).map(|it|
                it.iter().fold(
                    String::new(),
                    |mut s: String, i| {
                        s.push(*i);
                        s
                    }
                    )
                ))
            .parse_state(input)
            .map(|x| {
                let mut s = String::new();
                s.push((x.0).0);
                s.push_str(&(x.0).1);
                (NameNode{ name: s}, x.1)
            })
}

pub fn expr<I>(input: State<I>) -> ParseResult<ExprNode, I>
    where I: Stream<Item=char> {
        let spaces = spaces();
        let sexpr = between(
            satisfy(|c| c == '('),
            satisfy(|c| c == ')'),
            parser(name)
                .and(many(parser(expr)))
                .map(|x| {
                    SExpr(SExprNode {
                        operator: x.0,
                        operands: x.1
                    })
                })
            );
        spaces.clone().with(
            try(sexpr)
                .or(try(parser(name).map(Name)))
                .or(try(parser(number).map(NumConst)))
            ).parse_state(input)
}

#[cfg(test)]
mod tests {
    use ::ast::*;
    use ::ast::ExprNode::*;
    use super::*;
    use super::parser_combinators::{Parser,parser};

    #[test]
    fn test_basic_ident() {
        assert_eq!(
            parser(expr).parse("ident").unwrap(),
            (Name(NameNode { name: "ident".to_string() }), "")
            );
    }

    #[test]
    fn test_basic_sexpr() {
        assert_eq!(
            parser(expr).parse("(ident arg1 arg2)").unwrap(),
            (SExpr(SExprNode {
                operator: NameNode { name: "ident".to_string() },
                operands: vec![
                    Name(NameNode { name: "arg1".to_string() }),
                    Name(NameNode { name: "arg2".to_string() })
                ]
            }), "")
            );
    }

    #[test]
    fn test_parse_sint_pos() {
        assert_eq!(
            parser(number).parse("1234"),
            Ok((NumNode::IntConst(IntNode { value: 1234isize }), ""))
            );
    }

    #[test]
    fn test_parse_sint_neg() {
        assert_eq!(
            parser(number).parse("-1234"),
            Ok((NumNode::IntConst(IntNode { value: -1234isize }), ""))
            );
    }

    #[test]
    fn test_parse_sint_hex() {
        assert_eq!(
            parser(number).parse("0x0ff"),
            Ok((NumNode::IntConst(IntNode { value: 0x0ffisize }), ""))
            );
        assert_eq!(
            parser(number).parse("0X0FF"),
            Ok((NumNode::IntConst(IntNode { value: 0x0ffisize }), ""))
            );
    }
    /* // Currently unsupported
    #[test]
    fn test_parse_sint_bin_upper() {
        assert_eq!(
            parser(number).parse("0B01"),
            Ok((NumNode::IntConst(IntNode { value: 0b01isize }), ""))
            );
        assert_eq!(
            parser(number).parse("0b01"),
            Ok((NumNode::IntConst(IntNode { value: 0b01isize }), ""))
            );
    }*/

    #[test]
    fn test_parse_uint() {
        assert_eq!(
            parser(number).parse("1234u"),
            Ok((NumNode::UIntConst(UIntNode { value: 1234usize }), ""))
            );
        assert_eq!(
            parser(number).parse("4321U"),
            Ok((NumNode::UIntConst(UIntNode { value: 4321usize }), ""))
            );
    }

    #[test]
    fn test_parse_uint_hex() {
        assert_eq!(
            parser(number).parse("0x0ffu"),
            Ok((NumNode::UIntConst(UIntNode { value: 0x0ffusize }), ""))
            );
        assert_eq!(
            parser(number).parse("0X0FFu"),
            Ok((NumNode::UIntConst(UIntNode { value: 0x0ffusize }), ""))
            );
    }

    #[test]
    fn test_parse_float() {
        assert_eq!(
            parser(number).parse("1.0").unwrap(),
            (NumNode::FloatConst(FloatNode { value: 1.0f64 }), "")
            );/* // Unsupported
        assert_eq!(
            parser(number).parse("1f").unwrap(),
            (NumNode::FloatConst(FloatNode { value: 1.0f64 }), "")
            );*/
        assert_eq!(
            parser(number).parse("22.2222").unwrap(),
            (NumNode::FloatConst(FloatNode { value: 22.2222f64 }), "")
            );
        assert_eq!(
            parser(number).parse("22.2222f").unwrap(),
            (NumNode::FloatConst(FloatNode { value: 22.2222f64 }), "")
            );
    }

}
<|endoftext|>


<commit_before>//! # Worker queue
//!
//! This is a simple wrapper around a thread-safe _Multi Producer, Multi Consumer_ queue.
//! The actual queue implementation is provided by [`crossbeam`]().
//!
//! If no maximum length is set then this queue is completely non-blocking.
//! If a maximum length is set then each push and pop operation will lock
//! a counter with the current length.
//!
//! This queue provides a simple [back-pressure]() mechanism by exposing a max length parameter.
//! The length isn't enforced when pushing messages to keep things simple, but it's expected that
//! a caller will check the length and decide what to do about it before pushing a message.
//! It turns out this is a good enough approach when you're dealing with a closed system,
//! like our API where it's easy to just write an Iron [middleware]()
//! to check queues on each request.
//!
//! # Examples
//!
//! Build a queue with a recommended max length of `500`:
//!
//! ```
//! # use ::worker::queue::*;
//! let (tx, rx) = QueueBuilder::new().with_max_len(500).build();
//! ```
//!
//! Build a queue with no recommended max length:
//!
//! ```
//! # use ::worker::queue::*;
//! let (tx, rx) = QueueBuilder::new().build();
//! ```
//!
//! Some things that would be nice to support here would be resilient queues.
//! So that way messages are also written to disk, and a message isn't
//! considered _pushed_ until it's sitting on disk.
//! In that case, you'd also have to make your message handlers _idempotent_,
//! so they can deal with a system failure that causes them to handle the
//! same message multiple times.

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

/// The producing end of a queue.
///
/// This structure can be safely cloned and shared amongst threads.
#[derive(Debug, Clone)]
pub struct Producer<T>(Arc<SegQueue<T>>, Option<(usize, Arc<Mutex<usize>>)>);

/// The consuming end of a queue.
///
/// This structure can be safely cloned and shared amongst threads.
#[derive(Debug, Clone)]
pub struct Consumer<T>(Arc<SegQueue<T>>, Option<Arc<Mutex<usize>>>);

/// Builder for a new queue.
pub struct QueueBuilder<T> {
    max_len: Option<usize>,
    _t: ::std::marker::PhantomData<T>,
}

impl<T> QueueBuilder<T> {
    /// Create a new queue builder.
    pub fn new() -> Self {
        QueueBuilder {
            max_len: None,
            _t: ::std::marker::PhantomData,
        }
    }

    /// Set a recommended maximum length for this queue.
    pub fn with_max_len(mut self, max_len: usize) -> Self {
        self.max_len = Some(max_len);

        self
    }

    /// Convert this builder into a `Producer`, `Consumer` pair.
    pub fn build(self) -> (Producer<T>, Consumer<T>) {
        let queue = Arc::new(SegQueue::new());

        let len_ctr = self.max_len.map(|max_len| (max_len, Arc::new(Mutex::new(0))));

        let tx = Producer(queue.clone(), len_ctr.clone());
        let rx = Consumer(queue, len_ctr.map(|(_, len)| len));

        (tx, rx)
    }
}

pub trait IsFull
    where Self: Send + Sync
{
    /// Check whether the queue is 'full'.
    ///
    /// The idea here is to keep the queues as simple as possible.
    /// So at the start of a request, you can poll the worker queues and see if any are full.
    /// At that stage you can decide what to do about it, which could be one of a number of things:
    ///
    /// 1. Ignore it and keep pushing messages
    /// 1. Block until the length goes down
    /// 1. Bork the request
    fn is_full(&self) -> bool;
}

impl<T> Producer<T> {
    /// Push a message onto the queue.
    ///
    /// This doesn't enforce the maximum length before pushing, so if you don't check `is_full`
    /// then the queue length can grow indefinitely.
    pub fn push(&self, msg: T) {
        if let Some((_, ref len)) = self.1 {
            let mut len = len.lock().unwrap();
            *len += 1;
        }

        self.0.push(msg)
    }
}

impl<T: Send + Sync> IsFull for Producer<T> {
    fn is_full(&self) -> bool {
        if let Some((max_len, ref len)) = self.1 {
            let len = len.lock().unwrap();
            *len >= max_len
        } else {
            false
        }
    }
}

impl<T> Consumer<T> {
    /// Try pop a message from the queue.
    pub fn try_pop(&self) -> Option<T> {
        if let Some(msg) = self.0.try_pop() {
            if let Some(ref len) = self.1 {
                let mut len = len.lock().unwrap();
                *len -= 1;
            }

            Some(msg)
        } else {
            None
        }
    }
}

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

    #[test]
    fn mpmc_push_pop_max_len() {
        let (tx, rx) = QueueBuilder::new().with_max_len(1).build();
        let (tx2, rx2) = (tx.clone(), rx.clone());

        tx.push(());
        tx2.push(());

        assert_eq!(true, tx.is_full());
        assert_eq!(true, tx2.is_full());

        let _ = rx.try_pop().unwrap();
        let _ = rx2.try_pop().unwrap();

        assert_eq!(false, tx.is_full());
        assert_eq!(false, tx2.is_full());
    }

    #[test]
    fn mpmc_push_pop() {
        let (tx, rx) = QueueBuilder::new().build();
        let (tx2, rx2) = (tx.clone(), rx.clone());

        tx.push(());
        tx2.push(());

        let pop1 = rx.try_pop();
        let pop2 = rx2.try_pop();

        assert!(pop1.is_some() && pop2.is_some());
    }
}
<commit_msg>use rwlock instead of mutex<commit_after>//! # Worker queue
//!
//! This is a simple wrapper around a thread-safe _Multi Producer, Multi Consumer_ queue.
//! The actual queue implementation is provided by [`crossbeam`]().
//!
//! If no maximum length is set then this queue is completely non-blocking.
//! If a maximum length is set then each push and pop operation will lock
//! a counter with the current length.
//!
//! This queue provides a simple [back-pressure]() mechanism by exposing a max length parameter.
//! The length isn't enforced when pushing messages to keep things simple, but it's expected that
//! a caller will check the length and decide what to do about it before pushing a message.
//! It turns out this is a good enough approach when you're dealing with a closed system,
//! like our API where it's easy to just write an Iron [middleware]()
//! to check queues on each request.
//!
//! # Examples
//!
//! Build a queue with a recommended max length of `500`:
//!
//! ```
//! # use ::worker::queue::*;
//! let (tx, rx) = QueueBuilder::new().with_max_len(500).build();
//! ```
//!
//! Build a queue with no recommended max length:
//!
//! ```
//! # use ::worker::queue::*;
//! let (tx, rx) = QueueBuilder::new().build();
//! ```
//!
//! Some things that would be nice to support here would be resilient queues.
//! So that way messages are also written to disk, and a message isn't
//! considered _pushed_ until it's sitting on disk.
//! In that case, you'd also have to make your message handlers _idempotent_,
//! so they can deal with a system failure that causes them to handle the
//! same message multiple times.

use std::sync::{Arc, RwLock};
use crossbeam::sync::SegQueue;

/// The producing end of a queue.
///
/// This structure can be safely cloned and shared amongst threads.
#[derive(Debug, Clone)]
pub struct Producer<T>(Arc<SegQueue<T>>, Option<(usize, Arc<RwLock<usize>>)>);

/// The consuming end of a queue.
///
/// This structure can be safely cloned and shared amongst threads.
#[derive(Debug, Clone)]
pub struct Consumer<T>(Arc<SegQueue<T>>, Option<Arc<RwLock<usize>>>);

/// Builder for a new queue.
pub struct QueueBuilder<T> {
    max_len: Option<usize>,
    _t: ::std::marker::PhantomData<T>,
}

impl<T> QueueBuilder<T> {
    /// Create a new queue builder.
    pub fn new() -> Self {
        QueueBuilder {
            max_len: None,
            _t: ::std::marker::PhantomData,
        }
    }

    /// Set a recommended maximum length for this queue.
    pub fn with_max_len(mut self, max_len: usize) -> Self {
        self.max_len = Some(max_len);

        self
    }

    /// Convert this builder into a `Producer`, `Consumer` pair.
    pub fn build(self) -> (Producer<T>, Consumer<T>) {
        let queue = Arc::new(SegQueue::new());

        let len_ctr = self.max_len.map(|max_len| (max_len, Arc::new(RwLock::new(0))));

        let tx = Producer(queue.clone(), len_ctr.clone());
        let rx = Consumer(queue, len_ctr.map(|(_, len)| len));

        (tx, rx)
    }
}

pub trait IsFull
    where Self: Send + Sync
{
    /// Check whether the queue is 'full'.
    ///
    /// The idea here is to keep the queues as simple as possible.
    /// So at the start of a request, you can poll the worker queues and see if any are full.
    /// At that stage you can decide what to do about it, which could be one of a number of things:
    ///
    /// 1. Ignore it and keep pushing messages
    /// 1. Block until the length goes down
    /// 1. Bork the request
    fn is_full(&self) -> bool;
}

impl<T> Producer<T> {
    /// Push a message onto the queue.
    ///
    /// This doesn't enforce the maximum length before pushing, so if you don't check `is_full`
    /// then the queue length can grow indefinitely.
    pub fn push(&self, msg: T) {
        if let Some((_, ref len)) = self.1 {
            let mut len = len.write().unwrap();
            *len += 1;
        }

        self.0.push(msg)
    }
}

impl<T: Send + Sync> IsFull for Producer<T> {
    fn is_full(&self) -> bool {
        if let Some((max_len, ref len)) = self.1 {
            let len = len.read().unwrap();
            *len >= max_len
        } else {
            false
        }
    }
}

impl<T> Consumer<T> {
    /// Try pop a message from the queue.
    pub fn try_pop(&self) -> Option<T> {
        if let Some(msg) = self.0.try_pop() {
            if let Some(ref len) = self.1 {
                let mut len = len.write().unwrap();
                *len -= 1;
            }

            Some(msg)
        } else {
            None
        }
    }
}

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

    #[test]
    fn mpmc_push_pop_max_len() {
        let (tx, rx) = QueueBuilder::new().with_max_len(1).build();
        let (tx2, rx2) = (tx.clone(), rx.clone());

        tx.push(());
        tx2.push(());

        assert_eq!(true, tx.is_full());
        assert_eq!(true, tx2.is_full());

        let _ = rx.try_pop().unwrap();
        let _ = rx2.try_pop().unwrap();

        assert_eq!(false, tx.is_full());
        assert_eq!(false, tx2.is_full());
    }

    #[test]
    fn mpmc_push_pop() {
        let (tx, rx) = QueueBuilder::new().build();
        let (tx2, rx2) = (tx.clone(), rx.clone());

        tx.push(());
        tx2.push(());

        let pop1 = rx.try_pop();
        let pop2 = rx2.try_pop();

        assert!(pop1.is_some() && pop2.is_some());
    }
}
<|endoftext|>