<commit_before>use byteorder::{BigEndian, ReadBytesExt};
use std::io::{Read, Seek, SeekFrom};
use std::mem::size_of;
use std::string::{FromUtf16Error, FromUtf8Error};

use {Date, Error, PlistEvent, Result, u64_to_usize};

impl From<FromUtf8Error> for Error {
    fn from(_: FromUtf8Error) -> Error {
        Error::InvalidData
    }
}

impl From<FromUtf16Error> for Error {
    fn from(_: FromUtf16Error) -> Error {
        Error::InvalidData
    }
}

struct StackItem {
    object_refs: Vec<u64>,
    ty: StackType,
}

enum StackType {
    Array,
    Dict,
    Root,
}

/// https://opensource.apple.com/source/CF/CF-550/CFBinaryPList.c
/// https://hg.python.org/cpython/file/3.4/Lib/plistlib.py
pub struct EventReader<R> {
    stack: Vec<StackItem>,
    object_offsets: Vec<u64>,
    reader: R,
    ref_size: u8,
    finished: bool,
    // The largest single allocation allowed for this Plist.
    // Equal to the number of bytes in the Plist minus the magic and trailer.
    max_allocation_bytes: usize,
    // The maximum number of nested arrays and dicts allowed in the plist.
    max_stack_depth: usize,
    // The maximum number of objects that can be created. Default 10 * object_offsets.len().
    // Binary plists can contain circular references.
    max_objects: usize,
    // The number of objects created so far.
    current_objects: usize,
}

impl<R: Read + Seek> EventReader<R> {
    pub fn new(reader: R) -> EventReader<R> {
        EventReader {
            stack: Vec::new(),
            object_offsets: Vec::new(),
            reader: reader,
            ref_size: 0,
            finished: false,
            max_allocation_bytes: 0,
            max_stack_depth: 200,
            max_objects: 0,
            current_objects: 0,
        }
    }

    fn can_allocate(&self, len: u64, size: usize) -> bool {
        let byte_len = len.saturating_mul(size as u64);
        byte_len <= self.max_allocation_bytes as u64
    }

    fn allocate_vec<T>(&self, len: u64, size: usize) -> Result<Vec<T>> {
        if self.can_allocate(len, size) {
            Ok(Vec::with_capacity(len as usize))
        } else {
            Err(Error::InvalidData)
        }
    }

    fn read_trailer(&mut self) -> Result<()> {
        self.reader.seek(SeekFrom::Start(0))?;
        let mut magic = [0; 8];
        self.reader.read_exact(&mut magic)?;
        if &magic != b"bplist00" {
            return Err(Error::InvalidData);
        }

        // Trailer starts with 6 bytes of padding
        let trailer_start = self.reader.seek(SeekFrom::End(-32 + 6))?;

        let offset_size = self.reader.read_u8()?;
        match offset_size {
            1 | 2 | 4 | 8 => (),
            _ => return Err(Error::InvalidData),
        }

        self.ref_size = self.reader.read_u8()?;
        match self.ref_size {
            1 | 2 | 4 | 8 => (),
            _ => return Err(Error::InvalidData),
        }

        let num_objects = self.reader.read_u64::<BigEndian>()?;
        let top_object = self.reader.read_u64::<BigEndian>()?;
        let offset_table_offset = self.reader.read_u64::<BigEndian>()?;

        // File size minus trailer and header
        // Truncated to max(usize)
        self.max_allocation_bytes = trailer_start.saturating_sub(8) as usize;

        // Read offset table
        self.reader.seek(SeekFrom::Start(offset_table_offset))?;
        self.object_offsets = self.read_ints(num_objects, offset_size)?;

        self.max_objects = self.object_offsets.len() * 10;

        // Seek to top object
        self.stack.push(StackItem {
            object_refs: vec![top_object],
            ty: StackType::Root,
        });

        Ok(())
    }

    fn read_ints(&mut self, len: u64, size: u8) -> Result<Vec<u64>> {
        let mut ints = self.allocate_vec(len, size as usize)?;
        for _ in 0..len {
            match size {
                1 => ints.push(self.reader.read_u8()? as u64),
                2 => ints.push(self.reader.read_u16::<BigEndian>()? as u64),
                4 => ints.push(self.reader.read_u32::<BigEndian>()? as u64),
                8 => ints.push(self.reader.read_u64::<BigEndian>()? as u64),
                _ => return Err(Error::InvalidData),
            }
        }
        Ok(ints)
    }

    fn read_refs(&mut self, len: u64) -> Result<Vec<u64>> {
        let ref_size = self.ref_size;
        self.read_ints(len, ref_size)
    }

    fn read_object_len(&mut self, len: u8) -> Result<u64> {
        if (len & 0x0f) == 0x0f {
            let len_power_of_two = self.reader.read_u8()? & 0x03;
            Ok(match len_power_of_two {
                0 => self.reader.read_u8()? as u64,
                1 => self.reader.read_u16::<BigEndian>()? as u64,
                2 => self.reader.read_u32::<BigEndian>()? as u64,
                3 => self.reader.read_u64::<BigEndian>()?,
                _ => return Err(Error::InvalidData),
            })
        } else {
            Ok(len as u64)
        }
    }

    fn read_data(&mut self, len: u64) -> Result<Vec<u8>> {
        let mut data = self.allocate_vec(len, size_of::<u8>())?;
        // Safe as u8 is a Copy type and we have already know len has been allocated.
        unsafe { data.set_len(len as usize) }
        self.reader.read_exact(&mut data)?;
        Ok(data)
    }

    fn seek_to_object(&mut self, object_ref: u64) -> Result<u64> {
        let object_ref = u64_to_usize(object_ref)?;
        let offset = *self.object_offsets
            .get(object_ref)
            .ok_or(Error::InvalidData)?;
        Ok(self.reader.seek(SeekFrom::Start(offset))?)
    }

    fn read_next(&mut self) -> Result<Option<PlistEvent>> {
        if self.ref_size == 0 {
            // Initialise here rather than in new
            self.read_trailer()?;
        }

        let object_ref = match self.stack.last_mut() {
            Some(stack_item) => stack_item.object_refs.pop(),
            // Reached the end of the plist
            None => return Ok(None),
        };

        match object_ref {
            Some(object_ref) => {
                if self.current_objects > self.max_objects {
                    return Err(Error::InvalidData);
                }
                self.current_objects += 1;
                self.seek_to_object(object_ref)?;
            }
            None => {
                // We're at the end of an array or dict. Pop the top stack item and return
                let item = self.stack.pop().unwrap();
                match item.ty {
                    StackType::Array => return Ok(Some(PlistEvent::EndArray)),
                    StackType::Dict => return Ok(Some(PlistEvent::EndDictionary)),
                    // We're at the end of the plist
                    StackType::Root => return Ok(None),
                }
            }
        }

        let token = self.reader.read_u8()?;
        let ty = (token & 0xf0) >> 4;
        let size = token & 0x0f;

        let result = match (ty, size) {
            (0x0, 0x00) => return Err(Error::InvalidData), // null
            (0x0, 0x08) => Some(PlistEvent::BooleanValue(false)),
            (0x0, 0x09) => Some(PlistEvent::BooleanValue(true)),
            (0x0, 0x0f) => return Err(Error::InvalidData), // fill
            (0x1, 0) => Some(PlistEvent::IntegerValue(self.reader.read_u8()? as i64)),
            (0x1, 1) => Some(PlistEvent::IntegerValue(
                self.reader.read_u16::<BigEndian>()? as i64,
            )),
            (0x1, 2) => Some(PlistEvent::IntegerValue(
                self.reader.read_u32::<BigEndian>()? as i64,
            )),
            (0x1, 3) => Some(PlistEvent::IntegerValue(self.reader.read_i64::<BigEndian>()?)),
            (0x1, 4) => return Err(Error::InvalidData), // 128 bit int
            (0x1, _) => return Err(Error::InvalidData), // variable length int
            (0x2, 2) => Some(PlistEvent::RealValue(self.reader.read_f32::<BigEndian>()? as f64)),
            (0x2, 3) => Some(PlistEvent::RealValue(self.reader.read_f64::<BigEndian>()?)),
            (0x2, _) => return Err(Error::InvalidData), // odd length float
            (0x3, 3) => {
                // Date. Seconds since 1/1/2001 00:00:00.
                let secs = self.reader.read_f64::<BigEndian>()?;
                Some(PlistEvent::DateValue(
                    Date::from_seconds_since_plist_epoch(secs)?,
                ))
            }
            (0x4, n) => {
                // Data
                let len = self.read_object_len(n)?;
                Some(PlistEvent::DataValue(self.read_data(len)?))
            }
            (0x5, n) => {
                // ASCII string
                let len = self.read_object_len(n)?;
                let raw = self.read_data(len)?;
                let string = String::from_utf8(raw)?;
                Some(PlistEvent::StringValue(string))
            }
            (0x6, n) => {
                // UTF-16 string
                let len_utf16_codepoints = self.read_object_len(n)?;
                let mut raw_utf16 = self.allocate_vec(len_utf16_codepoints, size_of::<u16>())?;

                for _ in 0..len_utf16_codepoints {
                    raw_utf16.push(self.reader.read_u16::<BigEndian>()?);
                }

                let string = String::from_utf16(&raw_utf16)?;
                Some(PlistEvent::StringValue(string))
            }
            (0xa, n) => {
                // Array
                let len = self.read_object_len(n)?;
                let mut object_refs = self.read_refs(len)?;
                // Reverse so we can pop off the end of the stack in order
                object_refs.reverse();

                self.stack.push(StackItem {
                    ty: StackType::Array,
                    object_refs: object_refs,
                });

                Some(PlistEvent::StartArray(Some(len)))
            }
            (0xd, n) => {
                // Dict
                let len = self.read_object_len(n)?;
                let key_refs = self.read_refs(len)?;
                let value_refs = self.read_refs(len)?;

                let mut object_refs = self.allocate_vec(len * 2, self.ref_size as usize)?;
                let len = key_refs.len();
                for i in 1..len + 1 {
                    // Reverse so we can pop off the end of the stack in order
                    object_refs.push(value_refs[len - i]);
                    object_refs.push(key_refs[len - i]);
                }

                self.stack.push(StackItem {
                    ty: StackType::Dict,
                    object_refs: object_refs,
                });

                Some(PlistEvent::StartDictionary(Some(len as u64)))
            }
            (_, _) => return Err(Error::InvalidData),
        };

        // Prevent stack overflows when recursively parsing plist.
        if self.stack.len() > self.max_stack_depth {
            return Err(Error::InvalidData);
        }

        Ok(result)
    }
}

impl<R: Read + Seek> Iterator for EventReader<R> {
    type Item = Result<PlistEvent>;

    fn next(&mut self) -> Option<Result<PlistEvent>> {
        if self.finished {
            None
        } else {
            match self.read_next() {
                Ok(Some(event)) => Some(Ok(event)),
                Err(err) => {
                    self.finished = true;
                    Some(Err(err))
                }
                Ok(None) => {
                    self.finished = true;
                    None
                }
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use humantime::parse_rfc3339_weak;
    use std::fs::File;
    use std::path::Path;

    use super::*;
    use PlistEvent;

    #[test]
    fn streaming_parser() {
        use PlistEvent::*;

        let reader = File::open(&Path::new("./tests/data/binary.plist")).unwrap();
        let streaming_parser = EventReader::new(reader);
        let events: Vec<PlistEvent> = streaming_parser.map(|e| e.unwrap()).collect();

        let comparison = &[
            StartDictionary(Some(6)),
            StringValue("Lines".to_owned()),
            StartArray(Some(2)),
            StringValue("It is a tale told by an idiot,".to_owned()),
            StringValue("Full of sound and fury, signifying nothing.".to_owned()),
            EndArray,
            StringValue("Death".to_owned()),
            IntegerValue(1564),
            StringValue("Height".to_owned()),
            RealValue(1.60),
            StringValue("Birthdate".to_owned()),
            DateValue(parse_rfc3339_weak("1981-05-16 11:32:06").unwrap().into()),
            StringValue("Author".to_owned()),
            StringValue("William Shakespeare".to_owned()),
            StringValue("Data".to_owned()),
            DataValue(vec![0, 0, 0, 190, 0, 0, 0, 3, 0, 0, 0, 30, 0, 0, 0]),
            EndDictionary,
        ];

        assert_eq!(events, comparison);
    }

    #[test]
    fn utf16_plist() {
        use PlistEvent::*;

        let reader = File::open(&Path::new("./tests/data/utf16_bplist.plist")).unwrap();
        let streaming_parser = EventReader::new(reader);
        let mut events: Vec<PlistEvent> = streaming_parser.map(|e| e.unwrap()).collect();

        assert_eq!(events[2], StringValue("\u{2605} or better".to_owned()));

        let poem = if let StringValue(ref mut poem) = events[4] {
            poem
        } else {
            panic!("not a string")
        };
        assert_eq!(poem.len(), 643);
        assert_eq!(poem.pop().unwrap(), '\u{2605}');
    }
}
<commit_msg>Remove last bit of unsafe code.<commit_after>use byteorder::{BigEndian, ReadBytesExt};
use std::io::{Read, Seek, SeekFrom};
use std::mem::size_of;
use std::string::{FromUtf16Error, FromUtf8Error};

use {Date, Error, PlistEvent, Result, u64_to_usize};

impl From<FromUtf8Error> for Error {
    fn from(_: FromUtf8Error) -> Error {
        Error::InvalidData
    }
}

impl From<FromUtf16Error> for Error {
    fn from(_: FromUtf16Error) -> Error {
        Error::InvalidData
    }
}

struct StackItem {
    object_refs: Vec<u64>,
    ty: StackType,
}

enum StackType {
    Array,
    Dict,
    Root,
}

/// https://opensource.apple.com/source/CF/CF-550/CFBinaryPList.c
/// https://hg.python.org/cpython/file/3.4/Lib/plistlib.py
pub struct EventReader<R> {
    stack: Vec<StackItem>,
    object_offsets: Vec<u64>,
    reader: R,
    ref_size: u8,
    finished: bool,
    // The largest single allocation allowed for this Plist.
    // Equal to the number of bytes in the Plist minus the magic and trailer.
    max_allocation_bytes: usize,
    // The maximum number of nested arrays and dicts allowed in the plist.
    max_stack_depth: usize,
    // The maximum number of objects that can be created. Default 10 * object_offsets.len().
    // Binary plists can contain circular references.
    max_objects: usize,
    // The number of objects created so far.
    current_objects: usize,
}

impl<R: Read + Seek> EventReader<R> {
    pub fn new(reader: R) -> EventReader<R> {
        EventReader {
            stack: Vec::new(),
            object_offsets: Vec::new(),
            reader: reader,
            ref_size: 0,
            finished: false,
            max_allocation_bytes: 0,
            max_stack_depth: 200,
            max_objects: 0,
            current_objects: 0,
        }
    }

    fn can_allocate(&self, len: u64, size: usize) -> bool {
        let byte_len = len.saturating_mul(size as u64);
        byte_len <= self.max_allocation_bytes as u64
    }

    fn allocate_vec<T>(&self, len: u64, size: usize) -> Result<Vec<T>> {
        if self.can_allocate(len, size) {
            Ok(Vec::with_capacity(len as usize))
        } else {
            Err(Error::InvalidData)
        }
    }

    fn read_trailer(&mut self) -> Result<()> {
        self.reader.seek(SeekFrom::Start(0))?;
        let mut magic = [0; 8];
        self.reader.read_exact(&mut magic)?;
        if &magic != b"bplist00" {
            return Err(Error::InvalidData);
        }

        // Trailer starts with 6 bytes of padding
        let trailer_start = self.reader.seek(SeekFrom::End(-32 + 6))?;

        let offset_size = self.reader.read_u8()?;
        match offset_size {
            1 | 2 | 4 | 8 => (),
            _ => return Err(Error::InvalidData),
        }

        self.ref_size = self.reader.read_u8()?;
        match self.ref_size {
            1 | 2 | 4 | 8 => (),
            _ => return Err(Error::InvalidData),
        }

        let num_objects = self.reader.read_u64::<BigEndian>()?;
        let top_object = self.reader.read_u64::<BigEndian>()?;
        let offset_table_offset = self.reader.read_u64::<BigEndian>()?;

        // File size minus trailer and header
        // Truncated to max(usize)
        self.max_allocation_bytes = trailer_start.saturating_sub(8) as usize;

        // Read offset table
        self.reader.seek(SeekFrom::Start(offset_table_offset))?;
        self.object_offsets = self.read_ints(num_objects, offset_size)?;

        self.max_objects = self.object_offsets.len() * 10;

        // Seek to top object
        self.stack.push(StackItem {
            object_refs: vec![top_object],
            ty: StackType::Root,
        });

        Ok(())
    }

    fn read_ints(&mut self, len: u64, size: u8) -> Result<Vec<u64>> {
        let mut ints = self.allocate_vec(len, size as usize)?;
        for _ in 0..len {
            match size {
                1 => ints.push(self.reader.read_u8()? as u64),
                2 => ints.push(self.reader.read_u16::<BigEndian>()? as u64),
                4 => ints.push(self.reader.read_u32::<BigEndian>()? as u64),
                8 => ints.push(self.reader.read_u64::<BigEndian>()? as u64),
                _ => return Err(Error::InvalidData),
            }
        }
        Ok(ints)
    }

    fn read_refs(&mut self, len: u64) -> Result<Vec<u64>> {
        let ref_size = self.ref_size;
        self.read_ints(len, ref_size)
    }

    fn read_object_len(&mut self, len: u8) -> Result<u64> {
        if (len & 0x0f) == 0x0f {
            let len_power_of_two = self.reader.read_u8()? & 0x03;
            Ok(match len_power_of_two {
                0 => self.reader.read_u8()? as u64,
                1 => self.reader.read_u16::<BigEndian>()? as u64,
                2 => self.reader.read_u32::<BigEndian>()? as u64,
                3 => self.reader.read_u64::<BigEndian>()?,
                _ => return Err(Error::InvalidData),
            })
        } else {
            Ok(len as u64)
        }
    }

    fn read_data(&mut self, len: u64) -> Result<Vec<u8>> {
        let mut data = self.allocate_vec(len, size_of::<u8>())?;
        data.resize(len as usize, 0);
        self.reader.read_exact(&mut data)?;
        Ok(data)
    }

    fn seek_to_object(&mut self, object_ref: u64) -> Result<u64> {
        let object_ref = u64_to_usize(object_ref)?;
        let offset = *self.object_offsets
            .get(object_ref)
            .ok_or(Error::InvalidData)?;
        Ok(self.reader.seek(SeekFrom::Start(offset))?)
    }

    fn read_next(&mut self) -> Result<Option<PlistEvent>> {
        if self.ref_size == 0 {
            // Initialise here rather than in new
            self.read_trailer()?;
        }

        let object_ref = match self.stack.last_mut() {
            Some(stack_item) => stack_item.object_refs.pop(),
            // Reached the end of the plist
            None => return Ok(None),
        };

        match object_ref {
            Some(object_ref) => {
                if self.current_objects > self.max_objects {
                    return Err(Error::InvalidData);
                }
                self.current_objects += 1;
                self.seek_to_object(object_ref)?;
            }
            None => {
                // We're at the end of an array or dict. Pop the top stack item and return
                let item = self.stack.pop().unwrap();
                match item.ty {
                    StackType::Array => return Ok(Some(PlistEvent::EndArray)),
                    StackType::Dict => return Ok(Some(PlistEvent::EndDictionary)),
                    // We're at the end of the plist
                    StackType::Root => return Ok(None),
                }
            }
        }

        let token = self.reader.read_u8()?;
        let ty = (token & 0xf0) >> 4;
        let size = token & 0x0f;

        let result = match (ty, size) {
            (0x0, 0x00) => return Err(Error::InvalidData), // null
            (0x0, 0x08) => Some(PlistEvent::BooleanValue(false)),
            (0x0, 0x09) => Some(PlistEvent::BooleanValue(true)),
            (0x0, 0x0f) => return Err(Error::InvalidData), // fill
            (0x1, 0) => Some(PlistEvent::IntegerValue(self.reader.read_u8()? as i64)),
            (0x1, 1) => Some(PlistEvent::IntegerValue(
                self.reader.read_u16::<BigEndian>()? as i64,
            )),
            (0x1, 2) => Some(PlistEvent::IntegerValue(
                self.reader.read_u32::<BigEndian>()? as i64,
            )),
            (0x1, 3) => Some(PlistEvent::IntegerValue(self.reader.read_i64::<BigEndian>()?)),
            (0x1, 4) => return Err(Error::InvalidData), // 128 bit int
            (0x1, _) => return Err(Error::InvalidData), // variable length int
            (0x2, 2) => Some(PlistEvent::RealValue(self.reader.read_f32::<BigEndian>()? as f64)),
            (0x2, 3) => Some(PlistEvent::RealValue(self.reader.read_f64::<BigEndian>()?)),
            (0x2, _) => return Err(Error::InvalidData), // odd length float
            (0x3, 3) => {
                // Date. Seconds since 1/1/2001 00:00:00.
                let secs = self.reader.read_f64::<BigEndian>()?;
                Some(PlistEvent::DateValue(Date::from_seconds_since_plist_epoch(
                    secs,
                )?))
            }
            (0x4, n) => {
                // Data
                let len = self.read_object_len(n)?;
                Some(PlistEvent::DataValue(self.read_data(len)?))
            }
            (0x5, n) => {
                // ASCII string
                let len = self.read_object_len(n)?;
                let raw = self.read_data(len)?;
                let string = String::from_utf8(raw)?;
                Some(PlistEvent::StringValue(string))
            }
            (0x6, n) => {
                // UTF-16 string
                let len_utf16_codepoints = self.read_object_len(n)?;
                let mut raw_utf16 = self.allocate_vec(len_utf16_codepoints, size_of::<u16>())?;

                for _ in 0..len_utf16_codepoints {
                    raw_utf16.push(self.reader.read_u16::<BigEndian>()?);
                }

                let string = String::from_utf16(&raw_utf16)?;
                Some(PlistEvent::StringValue(string))
            }
            (0xa, n) => {
                // Array
                let len = self.read_object_len(n)?;
                let mut object_refs = self.read_refs(len)?;
                // Reverse so we can pop off the end of the stack in order
                object_refs.reverse();

                self.stack.push(StackItem {
                    ty: StackType::Array,
                    object_refs: object_refs,
                });

                Some(PlistEvent::StartArray(Some(len)))
            }
            (0xd, n) => {
                // Dict
                let len = self.read_object_len(n)?;
                let key_refs = self.read_refs(len)?;
                let value_refs = self.read_refs(len)?;

                let mut object_refs = self.allocate_vec(len * 2, self.ref_size as usize)?;
                let len = key_refs.len();
                for i in 1..len + 1 {
                    // Reverse so we can pop off the end of the stack in order
                    object_refs.push(value_refs[len - i]);
                    object_refs.push(key_refs[len - i]);
                }

                self.stack.push(StackItem {
                    ty: StackType::Dict,
                    object_refs: object_refs,
                });

                Some(PlistEvent::StartDictionary(Some(len as u64)))
            }
            (_, _) => return Err(Error::InvalidData),
        };

        // Prevent stack overflows when recursively parsing plist.
        if self.stack.len() > self.max_stack_depth {
            return Err(Error::InvalidData);
        }

        Ok(result)
    }
}

impl<R: Read + Seek> Iterator for EventReader<R> {
    type Item = Result<PlistEvent>;

    fn next(&mut self) -> Option<Result<PlistEvent>> {
        if self.finished {
            None
        } else {
            match self.read_next() {
                Ok(Some(event)) => Some(Ok(event)),
                Err(err) => {
                    self.finished = true;
                    Some(Err(err))
                }
                Ok(None) => {
                    self.finished = true;
                    None
                }
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use humantime::parse_rfc3339_weak;
    use std::fs::File;
    use std::path::Path;

    use super::*;
    use PlistEvent;

    #[test]
    fn streaming_parser() {
        use PlistEvent::*;

        let reader = File::open(&Path::new("./tests/data/binary.plist")).unwrap();
        let streaming_parser = EventReader::new(reader);
        let events: Vec<PlistEvent> = streaming_parser.map(|e| e.unwrap()).collect();

        let comparison = &[
            StartDictionary(Some(6)),
            StringValue("Lines".to_owned()),
            StartArray(Some(2)),
            StringValue("It is a tale told by an idiot,".to_owned()),
            StringValue("Full of sound and fury, signifying nothing.".to_owned()),
            EndArray,
            StringValue("Death".to_owned()),
            IntegerValue(1564),
            StringValue("Height".to_owned()),
            RealValue(1.60),
            StringValue("Birthdate".to_owned()),
            DateValue(parse_rfc3339_weak("1981-05-16 11:32:06").unwrap().into()),
            StringValue("Author".to_owned()),
            StringValue("William Shakespeare".to_owned()),
            StringValue("Data".to_owned()),
            DataValue(vec![0, 0, 0, 190, 0, 0, 0, 3, 0, 0, 0, 30, 0, 0, 0]),
            EndDictionary,
        ];

        assert_eq!(events, comparison);
    }

    #[test]
    fn utf16_plist() {
        use PlistEvent::*;

        let reader = File::open(&Path::new("./tests/data/utf16_bplist.plist")).unwrap();
        let streaming_parser = EventReader::new(reader);
        let mut events: Vec<PlistEvent> = streaming_parser.map(|e| e.unwrap()).collect();

        assert_eq!(events[2], StringValue("\u{2605} or better".to_owned()));

        let poem = if let StringValue(ref mut poem) = events[4] {
            poem
        } else {
            panic!("not a string")
        };
        assert_eq!(poem.len(), 643);
        assert_eq!(poem.pop().unwrap(), '\u{2605}');
    }
}
<|endoftext|>


<commit_before>use nom::{Consumer, FileProducer};
use std::io::{Error, ErrorKind, Result};
use std::u32;

use metadata::{
  Block, BlockData,
  StreamInfo, CueSheet, VorbisComment, Picture,
  PictureType,
  MetaDataConsumer,
};

macro_rules! optional_eq (
  ($compare: expr, $option: expr) => (
    $option.map_or(true, |compare| $compare == compare);
  );
);

// With the given filename, return all metadata blocks available.
//
// This function expects a flac file, but will return a proper `Result::Err`
// when things go wrong.
//
// # Failures
//
// * `ErrorKind::NotFound` is returned when the given filename isn't found.
// * `ErrorKind::InvalidData` is returned when the data within the file
//   isn't valid FLAC data.
pub fn get_metadata(filename: &str) -> Result<Vec<Block>> {
  FileProducer::new(filename, 1024).and_then(|mut producer| {
    let mut consumer = MetaDataConsumer::new();

    consumer.run(&mut producer);

    if !consumer.data.is_empty() {
      Ok(consumer.data)
    } else {
      let error_str = "parser: couldn't find any metadata";

      Err(Error::new(ErrorKind::InvalidData, error_str))
    }
  })
}

/// Reads and returns the `StreamInfo` metadata block of the given FLAC
/// file.
///
/// # Failures
///
/// * `ErrorKind::NotFound` is returned when the given filename isn't found
///   or there is no `StreamInfo` within the file.
/// * `ErrorKind::InvalidData` is returned when the data within the file
///   isn't valid FLAC data.
pub fn get_stream_info(filename: &str) -> Result<StreamInfo> {
  get_metadata(filename).and_then(|blocks| {
    let error_str  = "metadata: couldn't find StreamInfo";
    let mut result = Err(Error::new(ErrorKind::NotFound, error_str));

    for block in blocks {
      if let BlockData::StreamInfo(stream_info) = block.data {
        result = Ok(stream_info);
        break;
      }
    }

    result
  })
}

/// Reads and returns the `VorbisComment` metadata block of the given FLAC
/// file.
///
/// # Failures
///
/// * `ErrorKind::NotFound` is returned when the given filename isn't found
///   or there is no `VorbisComment` within the file.
/// * `ErrorKind::InvalidData` is returned when the data within the file
///   isn't valid FLAC data.
pub fn get_vorbis_comment(filename: &str) -> Result<VorbisComment> {
  get_metadata(filename).and_then(|blocks| {
    let error_str  = "metadata: couldn't find VorbisComment";
    let mut result = Err(Error::new(ErrorKind::NotFound, error_str));

    for block in blocks {
      if let BlockData::VorbisComment(vorbis_comment) = block.data {
        result = Ok(vorbis_comment);
        break;
      }
    }

    result
  })
}

/// Reads and returns the `CueSheet` metadata block of the given FLAC file.
///
/// # Failures
///
/// * `ErrorKind::NotFound` is returned when the given filename isn't found
///   or there is no `CueSheet` within the file.
/// * `ErrorKind::InvalidData` is returned when the data within the file
///   isn't valid FLAC data.
pub fn get_cue_sheet(filename: &str) -> Result<CueSheet> {
  get_metadata(filename).and_then(|blocks| {
    let error_str  = "metadata: couldn't find CueSheet";
    let mut result = Err(Error::new(ErrorKind::NotFound, error_str));

    for block in blocks {
      if let BlockData::CueSheet(cue_sheet) = block.data {
        result = Ok(cue_sheet);
        break;
      }
    }

    result
  })
}

/// Reads and returns a `Picture` metadata block of the given FLAC file.
///
/// There can be more than one `Picture` block in a file, this function
/// takes optional, that being `Option<T>`, parameters that act as
/// constraints to search within. The `Picture` with the largest area
/// matching all constraints will be returned.
///
/// # Failures
///
/// * `ErrorKind::NotFound` is returned when the given filename isn't found,
///   there is no `Picture` within the file, or no `Picture` that fits the
///   given constraints.
/// * `ErrorKind::InvalidData` is returned when the data within the file
///   isn't valid FLAC data.
pub fn get_picture(filename: &str,
                   picture_type: Option<PictureType>,
                   mime_type: Option<&str>,
                   description: Option<&str>,
                   max_width: Option<u32>,
                   max_height: Option<u32>,
                   max_depth: Option<u32>,
                   max_colors: Option<u32>)
                   -> Result<Picture> {
  get_metadata(filename).and_then(|blocks| {
    let error_str  = "metadata: couldn't find any Picture";
    let mut result = Err(Error::new(ErrorKind::NotFound, error_str));

    let mut max_area_seen  = 0;
    let mut max_depth_seen = 0;

    let max_value      = u32::max_value();
    let max_width_num  = max_width.unwrap_or(max_value);
    let max_height_num = max_height.unwrap_or(max_value);
    let max_depth_num  = max_depth.unwrap_or(max_value);
    let max_colors_num = max_colors.unwrap_or(max_value);

    for block in blocks {
      if let BlockData::Picture(picture) = block.data {
        let area = (picture.width as u64) * (picture.height as u64);

        if optional_eq!(picture.picture_type, picture_type) &&
           optional_eq!(picture.mime_type, mime_type) &&
           optional_eq!(picture.description, description) &&
           picture.width <= max_width_num &&
           picture.height <= max_height_num &&
           picture.depth <= max_depth_num &&
           picture.colors <= max_colors_num &&
           (area > max_area_seen || (area == max_area_seen &&
                                     picture.depth > max_depth_seen)) {
          max_area_seen  = area;
          max_depth_seen = picture.depth;
          result         = Ok(picture);
        }
      }
    }

    result
  })
}

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

  #[test]
  #[should_panic]
  fn test_panic_optional_eq() {
    assert!(optional_eq!(0, Some(1)));
  }

  #[test]
  fn test_optional_eq() {
    assert!(optional_eq!(0, None), "Should always return true when None");
    assert!(optional_eq!(0, Some(0)), "Should return true (Some(0) == 0)");
  }

  #[test]
  fn test_get_metadata() {
    let not_found    = get_metadata("non-existent/file.txt");
    let invalid_data = get_metadata("README.md");
    let result       = get_metadata("tests/assets/input-SVAUP.flac");

    assert_eq!(not_found.unwrap_err().kind(), ErrorKind::NotFound);
    assert_eq!(invalid_data.unwrap_err().kind(), ErrorKind::InvalidData);
    assert!(result.is_ok());
  }
}
<commit_msg>Add comment about `optional_eq!` macro<commit_after>use nom::{Consumer, FileProducer};
use std::io::{Error, ErrorKind, Result};
use std::u32;

use metadata::{
  Block, BlockData,
  StreamInfo, CueSheet, VorbisComment, Picture,
  PictureType,
  MetaDataConsumer,
};

// Will return true when the unwrapped value of `$option` and `$compare`
// match or `$option` is `Option::None`, otherwise false.
macro_rules! optional_eq (
  ($compare: expr, $option: expr) => (
    $option.map_or(true, |compare| $compare == compare);
  );
);

// With the given filename, return all metadata blocks available.
//
// This function expects a flac file, but will return a proper `Result::Err`
// when things go wrong.
//
// # Failures
//
// * `ErrorKind::NotFound` is returned when the given filename isn't found.
// * `ErrorKind::InvalidData` is returned when the data within the file
//   isn't valid FLAC data.
pub fn get_metadata(filename: &str) -> Result<Vec<Block>> {
  FileProducer::new(filename, 1024).and_then(|mut producer| {
    let mut consumer = MetaDataConsumer::new();

    consumer.run(&mut producer);

    if !consumer.data.is_empty() {
      Ok(consumer.data)
    } else {
      let error_str = "parser: couldn't find any metadata";

      Err(Error::new(ErrorKind::InvalidData, error_str))
    }
  })
}

/// Reads and returns the `StreamInfo` metadata block of the given FLAC
/// file.
///
/// # Failures
///
/// * `ErrorKind::NotFound` is returned when the given filename isn't found
///   or there is no `StreamInfo` within the file.
/// * `ErrorKind::InvalidData` is returned when the data within the file
///   isn't valid FLAC data.
pub fn get_stream_info(filename: &str) -> Result<StreamInfo> {
  get_metadata(filename).and_then(|blocks| {
    let error_str  = "metadata: couldn't find StreamInfo";
    let mut result = Err(Error::new(ErrorKind::NotFound, error_str));

    for block in blocks {
      if let BlockData::StreamInfo(stream_info) = block.data {
        result = Ok(stream_info);
        break;
      }
    }

    result
  })
}

/// Reads and returns the `VorbisComment` metadata block of the given FLAC
/// file.
///
/// # Failures
///
/// * `ErrorKind::NotFound` is returned when the given filename isn't found
///   or there is no `VorbisComment` within the file.
/// * `ErrorKind::InvalidData` is returned when the data within the file
///   isn't valid FLAC data.
pub fn get_vorbis_comment(filename: &str) -> Result<VorbisComment> {
  get_metadata(filename).and_then(|blocks| {
    let error_str  = "metadata: couldn't find VorbisComment";
    let mut result = Err(Error::new(ErrorKind::NotFound, error_str));

    for block in blocks {
      if let BlockData::VorbisComment(vorbis_comment) = block.data {
        result = Ok(vorbis_comment);
        break;
      }
    }

    result
  })
}

/// Reads and returns the `CueSheet` metadata block of the given FLAC file.
///
/// # Failures
///
/// * `ErrorKind::NotFound` is returned when the given filename isn't found
///   or there is no `CueSheet` within the file.
/// * `ErrorKind::InvalidData` is returned when the data within the file
///   isn't valid FLAC data.
pub fn get_cue_sheet(filename: &str) -> Result<CueSheet> {
  get_metadata(filename).and_then(|blocks| {
    let error_str  = "metadata: couldn't find CueSheet";
    let mut result = Err(Error::new(ErrorKind::NotFound, error_str));

    for block in blocks {
      if let BlockData::CueSheet(cue_sheet) = block.data {
        result = Ok(cue_sheet);
        break;
      }
    }

    result
  })
}

/// Reads and returns a `Picture` metadata block of the given FLAC file.
///
/// There can be more than one `Picture` block in a file, this function
/// takes optional, that being `Option<T>`, parameters that act as
/// constraints to search within. The `Picture` with the largest area
/// matching all constraints will be returned.
///
/// # Failures
///
/// * `ErrorKind::NotFound` is returned when the given filename isn't found,
///   there is no `Picture` within the file, or no `Picture` that fits the
///   given constraints.
/// * `ErrorKind::InvalidData` is returned when the data within the file
///   isn't valid FLAC data.
pub fn get_picture(filename: &str,
                   picture_type: Option<PictureType>,
                   mime_type: Option<&str>,
                   description: Option<&str>,
                   max_width: Option<u32>,
                   max_height: Option<u32>,
                   max_depth: Option<u32>,
                   max_colors: Option<u32>)
                   -> Result<Picture> {
  get_metadata(filename).and_then(|blocks| {
    let error_str  = "metadata: couldn't find any Picture";
    let mut result = Err(Error::new(ErrorKind::NotFound, error_str));

    let mut max_area_seen  = 0;
    let mut max_depth_seen = 0;

    let max_value      = u32::max_value();
    let max_width_num  = max_width.unwrap_or(max_value);
    let max_height_num = max_height.unwrap_or(max_value);
    let max_depth_num  = max_depth.unwrap_or(max_value);
    let max_colors_num = max_colors.unwrap_or(max_value);

    for block in blocks {
      if let BlockData::Picture(picture) = block.data {
        let area = (picture.width as u64) * (picture.height as u64);

        if optional_eq!(picture.picture_type, picture_type) &&
           optional_eq!(picture.mime_type, mime_type) &&
           optional_eq!(picture.description, description) &&
           picture.width <= max_width_num &&
           picture.height <= max_height_num &&
           picture.depth <= max_depth_num &&
           picture.colors <= max_colors_num &&
           (area > max_area_seen || (area == max_area_seen &&
                                     picture.depth > max_depth_seen)) {
          max_area_seen  = area;
          max_depth_seen = picture.depth;
          result         = Ok(picture);
        }
      }
    }

    result
  })
}

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

  #[test]
  #[should_panic]
  fn test_panic_optional_eq() {
    assert!(optional_eq!(0, Some(1)));
  }

  #[test]
  fn test_optional_eq() {
    assert!(optional_eq!(0, None), "Should always return true when None");
    assert!(optional_eq!(0, Some(0)), "Should return true (Some(0) == 0)");
  }

  #[test]
  fn test_get_metadata() {
    let not_found    = get_metadata("non-existent/file.txt");
    let invalid_data = get_metadata("README.md");
    let result       = get_metadata("tests/assets/input-SVAUP.flac");

    assert_eq!(not_found.unwrap_err().kind(), ErrorKind::NotFound);
    assert_eq!(invalid_data.unwrap_err().kind(), ErrorKind::InvalidData);
    assert!(result.is_ok());
  }
}
<|endoftext|>


<commit_before>extern crate llvm_tools;
extern crate target_build_utils;
extern crate walkdir;

use std::env;
use std::fs::File;
use std::io::Write;
use std::path::{Path, PathBuf};
#[cfg(feature = "mem")]
use std::process;
use std::process::Command;
use target_build_utils::TargetInfo;
use walkdir::{DirEntry, WalkDir};

fn build_hermit(src_dir: &Path, target_dir_opt: Option<&Path>) {
	assert!(
		src_dir.exists(),
		"rusty_hermit source folder does not exist"
	);
	let target = TargetInfo::new().expect("Could not get target info");
	let profile = env::var("PROFILE").expect("PROFILE was not set");
	let mut cmd = Command::new("cargo");

	if target.target_arch() == "x86_64" {
		cmd.current_dir(src_dir)
			.arg("build")
			.arg("-Z")
			.arg("build-std=core,alloc")
			.arg("--target")
			.arg("x86_64-unknown-none-hermitkernel");
	} else if target.target_arch() == "aarch64" {
		cmd.current_dir(src_dir)
			.arg("build")
			.arg("-Z")
			.arg("build-std=core,alloc")
			.arg("--target")
			.arg("aarch64-unknown-hermit");
	} else {
		panic!("Try to build for an unsupported platform");
	}

	if let Some(target_dir) = target_dir_opt {
		cmd.arg("--target-dir").arg(target_dir);
	}
	let target_dir = match target_dir_opt {
		Some(target_dir) => target_dir.to_path_buf(),
		None => src_dir.join("target"),
	};

	if profile == "release" {
		cmd.arg("--release");
	}

	// disable all default features
	cmd.arg("--no-default-features");

	// do we have to enable PCI support?
	#[cfg(feature = "pci")]
	{
		cmd.arg("--features");
		cmd.arg("pci");
	}

	// do we have to enable acpi support?
	#[cfg(feature = "acpi")]
	{
		cmd.arg("--features");
		cmd.arg("acpi");
	}

	// do we have to enable FSGSBASE support?
	#[cfg(feature = "fsgs_base")]
	{
		cmd.arg("--features");
		cmd.arg("fsgs_base");
	}

	// do we support multi-processor systems?
	#[cfg(feature = "smp")]
	{
		cmd.arg("--features");
		cmd.arg("smp");
	}

	// do we have to enable VGA support
	#[cfg(feature = "vga")]
	{
		cmd.arg("--features");
		cmd.arg("vga");
	}

	#[cfg(feature = "instrument")]
	{
		cmd.env("RUSTFLAGS", "-Z instrument-mcount");
		// if instrument is not set, ensure that instrument is not in environment variables!
		cmd.env(
			"RUSTFLAGS",
			env::var("RUSTFLAGS")
				.unwrap_or_else(|_| "".into())
				.replace("-Z instrument-mcount", ""),
		);
	}

	let output = cmd.output().expect("Unable to build kernel");
	let stdout = std::string::String::from_utf8(output.stdout);
	let stderr = std::string::String::from_utf8(output.stderr);

	println!("Build libhermit-rs output-status: {}", output.status);
	println!("Build libhermit-rs output-stdout: {}", stdout.unwrap());
	println!("Build libhermit-rs output-stderr: {}", stderr.unwrap());
	assert!(output.status.success());

	let lib_location = if target.target_arch() == "x86_64" {
		target_dir
			.join("x86_64-unknown-none-hermitkernel")
			.join(&profile)
			.canonicalize()
			.unwrap() // Must exist after building
	} else if target.target_arch() == "aarch64" {
		target_dir
			.join("aarch64-unknown-hermit")
			.join(&profile)
			.canonicalize()
			.unwrap() // Must exist after building
	} else {
		panic!("Try to build for an unsupported platform");
	};
	println!("Lib location: {}", lib_location.display());

	#[cfg(feature = "mem")]
	{
		// Kernel and user space has its own versions of memcpy, memset, etc,
		// Consequently, we rename the functions in the libos to avoid collisions.
		// In addition, it provides us the offer to create a optimized version of memcpy
		// in user space.

		// get access to llvm tools shipped in the llvm-tools-preview rustup component
		let llvm_tools = match llvm_tools::LlvmTools::new() {
			Ok(tools) => tools,
			Err(llvm_tools::Error::NotFound) => {
				eprintln!("Error: llvm-tools not found");
				eprintln!("Maybe the rustup component `llvm-tools-preview` is missing?");
				eprintln!("  Install it through: `rustup component add llvm-tools-preview`");
				process::exit(1);
			}
			Err(err) => {
				eprintln!("Failed to retrieve llvm-tools component: {:?}", err);
				process::exit(1);
			}
		};

		let lib = lib_location.join("libhermit.a");

		let symbols: [&str; 5] = ["memcpy", "memmove", "memset", "memcmp", "bcmp"];
		for symbol in symbols.iter() {
			// determine llvm_objcopy
			let llvm_objcopy = llvm_tools
				.tool(&llvm_tools::exe("llvm-objcopy"))
				.expect("llvm_objcopy not found in llvm-tools");

			// rename symbols
			let mut cmd = Command::new(llvm_objcopy);
			cmd.arg("--redefine-sym")
				.arg(String::from(*symbol) + &String::from("=kernel") + &String::from(*symbol))
				.arg(lib.display().to_string());

			println!("cmd {:?}", cmd);
			let output = cmd.output().expect("Unable to rename symbols");
			let stdout = std::string::String::from_utf8(output.stdout);
			let stderr = std::string::String::from_utf8(output.stderr);
			println!("Rename symbols output-status: {}", output.status);
			println!("Rename symbols output-stdout: {}", stdout.unwrap());
			println!("Rename symbols output-stderr: {}", stderr.unwrap());
		}
	}

	println!("cargo:rustc-link-search=native={}", lib_location.display());
	println!("cargo:rustc-link-lib=static=hermit");

	//HERMIT_LOG_LEVEL_FILTER sets the log level filter at compile time
	// Doesn't actually rebuild atm - see: https://github.com/rust-lang/cargo/issues/8306
	println!("cargo:rerun-if-env-changed=HERMIT_LOG_LEVEL_FILTER");
}

#[cfg(all(not(feature = "rustc-dep-of-std"), not(feature = "with_submodule")))]
fn build() {
	#[cfg(windows)]
	let out_dir = env::temp_dir();
	#[cfg(not(windows))]
	let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
	let src_dir = out_dir.join("rusty-hermit");

	if !src_dir.as_path().exists() {
		let cargo_download = Command::new("cargo")
			.current_dir(out_dir)
			.arg("download")
			.arg("--output")
			.arg(src_dir.clone().into_os_string())
			.arg("--extract")
			.arg("rusty-hermit")
			.output()
			.expect("could not launch cargo download");
		if !cargo_download.status.success() {
			panic!("Unable to download rusty-hermit. Is `cargo-download` installed?.");
		}
	}

	build_hermit(src_dir.as_ref(), None);
}

#[cfg(all(not(feature = "rustc-dep-of-std"), feature = "with_submodule"))]
fn build() {
	let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
	let target_dir = out_dir.clone().join("target");
	let src_dir = env::current_dir()
		.unwrap()
		.parent()
		.unwrap()
		.join("libhermit-rs");

	build_hermit(src_dir.as_ref(), Some(target_dir.as_ref()));
	configure_cargo_rerun_if_changed(src_dir.as_ref());
}

#[cfg(all(not(feature = "rustc-dep-of-std"), feature = "with_submodule"))]
fn configure_cargo_rerun_if_changed(src_dir: &Path) {
	fn is_not_ignored(entry: &DirEntry) -> bool {
		// Ignore .git .vscode and target directories, but not .cargo or .github
		if entry.depth() == 1
			&& entry.path().is_dir()
			&& (entry.path().ends_with("target")
				|| entry.path().ends_with(".git")
				|| entry.path().ends_with(".vscode"))
		{
			return false;
		}
		true
	}

	WalkDir::new(src_dir)
		.into_iter()
		.filter_entry(|e| is_not_ignored(e))
		.filter_map(|v| v.ok())
		.filter_map(|v| v.path().canonicalize().ok())
		.for_each(|x| println!("cargo:rerun-if-changed={}", x.display()));
}

#[cfg(not(feature = "rustc-dep-of-std"))]
fn create_constants() {
	let out_dir = env::var("OUT_DIR").expect("No out dir");
	let dest_path = Path::new(&out_dir).join("constants.rs");
	let mut f = File::create(&dest_path).expect("Could not create file");

	let ip = option_env!("HERMIT_IP");
	let ip = ip.map_or("10.0.5.3", |v| v);

	let gateway = option_env!("HERMIT_GATEWAY");
	let gateway = gateway.map_or("10.0.5.1", |v| v);

	let mask = option_env!("HERMIT_MASK");
	let mask = mask.map_or("255.255.255.0", |v| v);

	writeln!(&mut f, "const HERMIT_IP: &str = \"{}\";", ip).expect("Could not write file");
	println!("cargo:rerun-if-env-changed=HERMIT_IP");

	writeln!(&mut f, "const HERMIT_GATEWAY: &str = \"{}\";", gateway)
		.expect("Could not write file");
	println!("cargo:rerun-if-env-changed=HERMIT_GATEWAY");

	writeln!(&mut f, "const HERMIT_MASK: &str = \"{}\";", mask).expect("Could not write file");
	println!("cargo:rerun-if-env-changed=HERMIT_MASK");
}

fn main() {
	#[cfg(not(feature = "rustc-dep-of-std"))]
	create_constants();
	#[cfg(not(feature = "rustc-dep-of-std"))]
	build();
}
<commit_msg>build.rs: Rework RUSTFLAGS handling<commit_after>extern crate llvm_tools;
extern crate target_build_utils;
extern crate walkdir;

use std::env;
use std::fs::File;
use std::io::Write;
use std::path::{Path, PathBuf};
#[cfg(feature = "mem")]
use std::process;
use std::process::Command;
use target_build_utils::TargetInfo;
use walkdir::{DirEntry, WalkDir};

fn build_hermit(src_dir: &Path, target_dir_opt: Option<&Path>) {
	assert!(
		src_dir.exists(),
		"rusty_hermit source folder does not exist"
	);
	let target = TargetInfo::new().expect("Could not get target info");
	let profile = env::var("PROFILE").expect("PROFILE was not set");
	let mut cmd = Command::new("cargo");

	if target.target_arch() == "x86_64" {
		cmd.current_dir(src_dir)
			.arg("build")
			.arg("-Z")
			.arg("build-std=core,alloc")
			.arg("--target")
			.arg("x86_64-unknown-none-hermitkernel");
	} else if target.target_arch() == "aarch64" {
		cmd.current_dir(src_dir)
			.arg("build")
			.arg("-Z")
			.arg("build-std=core,alloc")
			.arg("--target")
			.arg("aarch64-unknown-hermit");
	} else {
		panic!("Try to build for an unsupported platform");
	}

	if let Some(target_dir) = target_dir_opt {
		cmd.arg("--target-dir").arg(target_dir);
	}
	let target_dir = match target_dir_opt {
		Some(target_dir) => target_dir.to_path_buf(),
		None => src_dir.join("target"),
	};

	if profile == "release" {
		cmd.arg("--release");
	}

	// disable all default features
	cmd.arg("--no-default-features");

	// do we have to enable PCI support?
	#[cfg(feature = "pci")]
	{
		cmd.arg("--features");
		cmd.arg("pci");
	}

	// do we have to enable acpi support?
	#[cfg(feature = "acpi")]
	{
		cmd.arg("--features");
		cmd.arg("acpi");
	}

	// do we have to enable FSGSBASE support?
	#[cfg(feature = "fsgs_base")]
	{
		cmd.arg("--features");
		cmd.arg("fsgs_base");
	}

	// do we support multi-processor systems?
	#[cfg(feature = "smp")]
	{
		cmd.arg("--features");
		cmd.arg("smp");
	}

	// do we have to enable VGA support
	#[cfg(feature = "vga")]
	{
		cmd.arg("--features");
		cmd.arg("vga");
	}

	let mut rustflags = Vec::new();

	#[cfg(feature = "instrument")]
	{
		rustflags.push("-Zinstrument-mcount".to_string());
		// Add outer `RUSTFLAGS` to command
		if let Ok(var) = env::var("RUSTFLAGS") {
			rustflags.push(var);
		}
	}

	#[cfg(not(feature = "instrument"))]
	{
		// If the `instrument` feature feature is not enabled,
		// filter it from outer `RUSTFLAGS` before adding them to the command.
		if let Ok(var) = env::var("RUSTFLAGS") {
			let flags = var
				.split(',')
				.filter(|&flag| !flag.contains("instrument-mcount"))
				.map(String::from);
			rustflags.extend(flags);
		}
	}

	cmd.env("RUSTFLAGS", rustflags.join(","));

	let output = cmd.output().expect("Unable to build kernel");
	let stdout = std::string::String::from_utf8(output.stdout);
	let stderr = std::string::String::from_utf8(output.stderr);

	println!("Build libhermit-rs output-status: {}", output.status);
	println!("Build libhermit-rs output-stdout: {}", stdout.unwrap());
	println!("Build libhermit-rs output-stderr: {}", stderr.unwrap());
	assert!(output.status.success());

	let lib_location = if target.target_arch() == "x86_64" {
		target_dir
			.join("x86_64-unknown-none-hermitkernel")
			.join(&profile)
			.canonicalize()
			.unwrap() // Must exist after building
	} else if target.target_arch() == "aarch64" {
		target_dir
			.join("aarch64-unknown-hermit")
			.join(&profile)
			.canonicalize()
			.unwrap() // Must exist after building
	} else {
		panic!("Try to build for an unsupported platform");
	};
	println!("Lib location: {}", lib_location.display());

	#[cfg(feature = "mem")]
	{
		// Kernel and user space has its own versions of memcpy, memset, etc,
		// Consequently, we rename the functions in the libos to avoid collisions.
		// In addition, it provides us the offer to create a optimized version of memcpy
		// in user space.

		// get access to llvm tools shipped in the llvm-tools-preview rustup component
		let llvm_tools = match llvm_tools::LlvmTools::new() {
			Ok(tools) => tools,
			Err(llvm_tools::Error::NotFound) => {
				eprintln!("Error: llvm-tools not found");
				eprintln!("Maybe the rustup component `llvm-tools-preview` is missing?");
				eprintln!("  Install it through: `rustup component add llvm-tools-preview`");
				process::exit(1);
			}
			Err(err) => {
				eprintln!("Failed to retrieve llvm-tools component: {:?}", err);
				process::exit(1);
			}
		};

		let lib = lib_location.join("libhermit.a");

		let symbols: [&str; 5] = ["memcpy", "memmove", "memset", "memcmp", "bcmp"];
		for symbol in symbols.iter() {
			// determine llvm_objcopy
			let llvm_objcopy = llvm_tools
				.tool(&llvm_tools::exe("llvm-objcopy"))
				.expect("llvm_objcopy not found in llvm-tools");

			// rename symbols
			let mut cmd = Command::new(llvm_objcopy);
			cmd.arg("--redefine-sym")
				.arg(String::from(*symbol) + &String::from("=kernel") + &String::from(*symbol))
				.arg(lib.display().to_string());

			println!("cmd {:?}", cmd);
			let output = cmd.output().expect("Unable to rename symbols");
			let stdout = std::string::String::from_utf8(output.stdout);
			let stderr = std::string::String::from_utf8(output.stderr);
			println!("Rename symbols output-status: {}", output.status);
			println!("Rename symbols output-stdout: {}", stdout.unwrap());
			println!("Rename symbols output-stderr: {}", stderr.unwrap());
		}
	}

	println!("cargo:rustc-link-search=native={}", lib_location.display());
	println!("cargo:rustc-link-lib=static=hermit");

	//HERMIT_LOG_LEVEL_FILTER sets the log level filter at compile time
	// Doesn't actually rebuild atm - see: https://github.com/rust-lang/cargo/issues/8306
	println!("cargo:rerun-if-env-changed=HERMIT_LOG_LEVEL_FILTER");
}

#[cfg(all(not(feature = "rustc-dep-of-std"), not(feature = "with_submodule")))]
fn build() {
	#[cfg(windows)]
	let out_dir = env::temp_dir();
	#[cfg(not(windows))]
	let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
	let src_dir = out_dir.join("rusty-hermit");

	if !src_dir.as_path().exists() {
		let cargo_download = Command::new("cargo")
			.current_dir(out_dir)
			.arg("download")
			.arg("--output")
			.arg(src_dir.clone().into_os_string())
			.arg("--extract")
			.arg("rusty-hermit")
			.output()
			.expect("could not launch cargo download");
		if !cargo_download.status.success() {
			panic!("Unable to download rusty-hermit. Is `cargo-download` installed?.");
		}
	}

	build_hermit(src_dir.as_ref(), None);
}

#[cfg(all(not(feature = "rustc-dep-of-std"), feature = "with_submodule"))]
fn build() {
	let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
	let target_dir = out_dir.clone().join("target");
	let src_dir = env::current_dir()
		.unwrap()
		.parent()
		.unwrap()
		.join("libhermit-rs");

	build_hermit(src_dir.as_ref(), Some(target_dir.as_ref()));
	configure_cargo_rerun_if_changed(src_dir.as_ref());
}

#[cfg(all(not(feature = "rustc-dep-of-std"), feature = "with_submodule"))]
fn configure_cargo_rerun_if_changed(src_dir: &Path) {
	fn is_not_ignored(entry: &DirEntry) -> bool {
		// Ignore .git .vscode and target directories, but not .cargo or .github
		if entry.depth() == 1
			&& entry.path().is_dir()
			&& (entry.path().ends_with("target")
				|| entry.path().ends_with(".git")
				|| entry.path().ends_with(".vscode"))
		{
			return false;
		}
		true
	}

	WalkDir::new(src_dir)
		.into_iter()
		.filter_entry(|e| is_not_ignored(e))
		.filter_map(|v| v.ok())
		.filter_map(|v| v.path().canonicalize().ok())
		.for_each(|x| println!("cargo:rerun-if-changed={}", x.display()));
}

#[cfg(not(feature = "rustc-dep-of-std"))]
fn create_constants() {
	let out_dir = env::var("OUT_DIR").expect("No out dir");
	let dest_path = Path::new(&out_dir).join("constants.rs");
	let mut f = File::create(&dest_path).expect("Could not create file");

	let ip = option_env!("HERMIT_IP");
	let ip = ip.map_or("10.0.5.3", |v| v);

	let gateway = option_env!("HERMIT_GATEWAY");
	let gateway = gateway.map_or("10.0.5.1", |v| v);

	let mask = option_env!("HERMIT_MASK");
	let mask = mask.map_or("255.255.255.0", |v| v);

	writeln!(&mut f, "const HERMIT_IP: &str = \"{}\";", ip).expect("Could not write file");
	println!("cargo:rerun-if-env-changed=HERMIT_IP");

	writeln!(&mut f, "const HERMIT_GATEWAY: &str = \"{}\";", gateway)
		.expect("Could not write file");
	println!("cargo:rerun-if-env-changed=HERMIT_GATEWAY");

	writeln!(&mut f, "const HERMIT_MASK: &str = \"{}\";", mask).expect("Could not write file");
	println!("cargo:rerun-if-env-changed=HERMIT_MASK");
}

fn main() {
	#[cfg(not(feature = "rustc-dep-of-std"))]
	create_constants();
	#[cfg(not(feature = "rustc-dep-of-std"))]
	build();
}
<|endoftext|>


<commit_before><commit_msg>Slitherlink: inspect the clue which is just added correctly in add_clue<commit_after><|endoftext|>


<commit_before>use std::iter::FromIterator;
use std::slice::Iter;
use std::ascii::AsciiExt;
use std::path::{Path, PathBuf};
use std::sync::Arc;

use ::compiler::{Arg, CommandInfo, CompilationArgs, CompilationTask, InputKind, OutputKind, Scope};

enum ParamValue<T> {
    None,
    Single(T),
    Many(Vec<T>),
}

pub fn create_tasks(command: CommandInfo, args: &[String]) -> Result<Vec<CompilationTask>, String> {
    if args.iter()
        .find(|v| {
            match v as &str {
                "--analyze" => true,
                _ => false,
            }
        })
        .is_some() {
        // Support only compilation steps
        return Ok(Vec::new());
    }
    if args.iter()
        .find(|v| {
            match v as &str {
                "-c" => true,
                _ => false,
            }
        })
        .is_none() {
        // Support only compilation steps
        return Ok(Vec::new());
    }
    let parsed_args = try!(parse_arguments(args));
    // Source file name.
    let input_sources: Vec<PathBuf> = parsed_args.iter()
        .filter_map(|arg| {
            match arg {
                &Arg::Input { ref kind, ref file, .. } if *kind == InputKind::Source => {
                    Some(Path::new(file).to_path_buf())
                }
                _ => None,
            }
        })
        .collect();
    if input_sources.len() == 0 {
        return Err(format!("Can't find source file path."));
    }
    // Precompiled header file name.
    let input_precompiled = match find_param(&parsed_args, |arg: &Arg| -> Option<PathBuf> {
        match arg {
            &Arg::Input { ref kind, ref file, .. } if *kind == InputKind::Precompiled => {
                Some(Path::new(file).to_path_buf())
            }
            _ => None,
        }
    }) {
        ParamValue::None => None,
        ParamValue::Single(v) => Some(v),
        ParamValue::Many(v) => {
            return Err(format!("Found too many precompiled header files: {:?}", v));
        }
    };
    // Precompiled header file name.
    let marker_precompiled = parsed_args.iter()
        .filter_map(|arg| {
            match arg {
                &Arg::Param { ref flag, ref value, .. } if *flag == "include" => Some(value.clone()),
                _ => None,
            }
        })
        .next();
    // Output object file name.
    let output_object = match find_param(&parsed_args, |arg: &Arg| -> Option<PathBuf> {
        match arg {
            &Arg::Output { ref kind, ref file, .. } if *kind == OutputKind::Object => {
                Some(Path::new(file).to_path_buf())
            }
            _ => None,
        }
    }) {
        ParamValue::None => None,
        ParamValue::Single(v) => {
            if input_sources.len() > 1 {
                return Err("Cannot specify -o when generating multiple output files".to_string());
            }
            Some(v)
        }
        ParamValue::Many(v) => {
            return Err(format!("Found too many output object files: {:?}", v));
        }
    };
    // Language
    let language: Option<String> = match find_param(&parsed_args, |arg: &Arg| -> Option<String> {
        match arg {
            &Arg::Param { ref flag, ref value, .. } if *flag == "x" => Some(value.clone()),
            _ => None,
        }
    }) {
        ParamValue::None => None,
        ParamValue::Single(v) => {
            match &v[..] {
                "c" | "c++" => Some(v.to_string()),
                "c-header" | "c++-header" => {
                    // Precompiled headers must build locally
                    return Ok(Vec::new());
                }
                _ => {
                    return Err(format!("Unknown source language type: {}", v));
                }
            }
        }
        ParamValue::Many(v) => {
            return Err(format!("Found too many output object files: {:?}", v));
        }
    };
    let shared = Arc::new(CompilationArgs {
        command: command,
        args: parsed_args,
        output_precompiled: None,
        marker_precompiled: marker_precompiled,
        input_precompiled: input_precompiled,
    });
    input_sources.into_iter()
        .map(|source| {
            Ok(CompilationTask {
                shared: shared.clone(),
                language: try!(language.as_ref()
                    .map_or_else(|| {
                        source.extension()
                            .and_then(|ext| match ext.to_str() {
                                Some(e) if e.eq_ignore_ascii_case("cpp") => Some("c++"),
                                Some(e) if e.eq_ignore_ascii_case("c") => Some("c"),
                                Some(e) if e.eq_ignore_ascii_case("hpp") => Some("c++-header"),
                                Some(e) if e.eq_ignore_ascii_case("h") => Some("c-header"),
                                _ => None,
                            })
                            .map(|ext| ext.to_string())
                    },
                                 |lang| Some(lang.clone()))
                    .ok_or_else(|| {
                        format!("Can't detect file language by extension: {}",
                                source.as_os_str().to_string_lossy())
                    })),
                output_object: output_object.as_ref().map_or_else(|| source.with_extension("o"), |path| path.clone()),
                input_source: source,
            })
        })
        .collect()
}

fn find_param<T, R, F: Fn(&T) -> Option<R>>(args: &Vec<T>, filter: F) -> ParamValue<R> {
    let mut found = Vec::from_iter(args.iter().filter_map(filter));
    match found.len() {
        0 => ParamValue::None,
        1 => ParamValue::Single(found.pop().unwrap()),
        _ => ParamValue::Many(found),
    }
}

fn parse_arguments(args: &[String]) -> Result<Vec<Arg>, String> {
    let mut result: Vec<Arg> = Vec::new();
    let mut errors: Vec<String> = Vec::new();
    let mut iter = args.iter();
    loop {
        match parse_argument(&mut iter) {
            Some(parse_result) => {
                match parse_result {
                    Ok(arg) => {
                        result.push(arg);
                    }
                    Err(e) => {
                        errors.push(e);
                    }
                }
            }
            None => {
                break;
            }
        }
    }
    if errors.len() > 0 {
        return Err(format!("Found unknown command line arguments: {:?}", errors));
    }
    Ok(result)
}

fn parse_argument(iter: &mut Iter<String>) -> Option<Result<Arg, String>> {
    match iter.next() {
        Some(arg) => {
            Some(if arg.starts_with("--") {
                let (key, value) = match arg.find("=") {
                    Some(position) => (&arg[1..position], arg[position + 1..].to_string()),
                    None => {
                        match iter.next() {
                            Some(v) => (&arg[1..], v.clone()),
                            _ => {
                                return Some(Err(arg.to_string()));
                            }
                        }
                    }
                };
                match &key[1..] {
                    "sysroot" => Ok(Arg::flag(Scope::Shared, key.to_string() + "=" + &value)),
                    _ => Err(key.to_string()),
                }
            } else if has_param_prefix(arg) {
                let flag = &arg[1..];
                match is_spaceable_param(flag) {
                    Some((prefix, scope, next_flag)) => {
                        let value = match flag == prefix {
                            true => {
                                match iter.next() {
                                    Some(v) if next_flag == has_param_prefix(v) => v.to_string(),
                                    _ => {
                                        return Some(Err(arg.to_string()));
                                    }
                                }
                            }
                            false => flag[prefix.len()..].to_string(),
                        };
                        match flag {
                            "o" => Ok(Arg::output(OutputKind::Object, prefix, value)),
                            _ => Ok(Arg::param(scope, prefix, value)),
                        }
                    }
                    None => {
                        match flag {
                            "c" => Ok(Arg::flag(Scope::Ignore, flag)),
                            "pipe" => Ok(Arg::flag(Scope::Shared, flag)),
                            s if s.starts_with("f") => Ok(Arg::flag(Scope::Shared, flag)),
                            s if s.starts_with("g") => Ok(Arg::flag(Scope::Shared, flag)),
                            s if s.starts_with("O") => Ok(Arg::flag(Scope::Shared, flag)),
                            s if s.starts_with("W") => Ok(Arg::flag(Scope::Shared, flag)),
                            s if s.starts_with("m") => Ok(Arg::flag(Scope::Shared, flag)),
                            s if s.starts_with("std=") => Ok(Arg::flag(Scope::Shared, flag)),
                            _ => Err(arg.to_string()),
                        }
                    }
                }
            } else {
                Ok(Arg::input(InputKind::Source, String::new(), arg.to_string()))
            })
        }
        None => None,
    }
}

fn is_spaceable_param(flag: &str) -> Option<(&str, Scope, bool)> {
    match flag {
        "include" | "include-pch" => Some((flag, Scope::Preprocessor, false)),
        "target" => Some((flag, Scope::Shared, false)),
        _ => {
            for prefix in ["D", "o"].iter() {
                if flag.starts_with(*prefix) {
                    return Some((*prefix, Scope::Shared, false));
                }
            }
            for prefix in ["x"].iter() {
                if flag.starts_with(*prefix) {
                    return Some((*prefix, Scope::Ignore, false));
                }
            }
            for prefix in ["I"].iter() {
                if flag.starts_with(*prefix) {
                    return Some((*prefix, Scope::Preprocessor, false));
                }
            }
            None
        }
    }
}

fn has_param_prefix(arg: &String) -> bool {
    arg.starts_with("-")
}

#[test]
fn test_parse_argument_precompile() {
    let args = Vec::from_iter("-x c++-header -pipe -Wall -Werror -funwind-tables -Wsequence-point -mmmx -msse -msse2 \
                               -fno-math-errno -fno-rtti -g3 -gdwarf-3 -O2 -D_LINUX64 -IEngine/Source \
                               -IDeveloper/Public -I Runtime/Core/Private -D IS_PROGRAM=1 -D UNICODE \
                               -DIS_MONOLITHIC=1 -std=c++11 -o CorePrivatePCH.h.pch CorePrivatePCH.h"
        .split(" ")
        .map(|x| x.to_string()));
    assert_eq!(parse_arguments(&args).unwrap(),
               [Arg::param(Scope::Ignore, "x", "c++-header"),
                Arg::flag(Scope::Shared, "pipe"),
                Arg::flag(Scope::Shared, "Wall"),
                Arg::flag(Scope::Shared, "Werror"),
                Arg::flag(Scope::Shared, "funwind-tables"),
                Arg::flag(Scope::Shared, "Wsequence-point"),
                Arg::flag(Scope::Shared, "mmmx"),
                Arg::flag(Scope::Shared, "msse"),
                Arg::flag(Scope::Shared, "msse2"),
                Arg::flag(Scope::Shared, "fno-math-errno"),
                Arg::flag(Scope::Shared, "fno-rtti"),
                Arg::flag(Scope::Shared, "g3"),
                Arg::flag(Scope::Shared, "gdwarf-3"),
                Arg::flag(Scope::Shared, "O2"),
                Arg::param(Scope::Shared, "D", "_LINUX64"),
                Arg::param(Scope::Preprocessor, "I", "Engine/Source"),
                Arg::param(Scope::Preprocessor, "I", "Developer/Public"),
                Arg::param(Scope::Preprocessor, "I", "Runtime/Core/Private"),
                Arg::param(Scope::Shared, "D", "IS_PROGRAM=1"),
                Arg::param(Scope::Shared, "D", "UNICODE"),
                Arg::param(Scope::Shared, "D", "IS_MONOLITHIC=1"),
                Arg::flag(Scope::Shared, "std=c++11"),
                Arg::output(OutputKind::Object, "o", "CorePrivatePCH.h.pch"),
                Arg::input(InputKind::Source, "", "CorePrivatePCH.h")])
}

#[test]
fn test_parse_argument_compile() {
    let args = Vec::from_iter("-c -include-pch CorePrivatePCH.h.pch -pipe -Wall -Werror -funwind-tables \
                               -Wsequence-point -mmmx -msse -msse2 -fno-math-errno -fno-rtti -g3 -gdwarf-3 -O2 -D \
                               IS_PROGRAM=1 -D UNICODE -DIS_MONOLITHIC=1 -x c++ -std=c++11 -include CorePrivatePCH.h \
                               -o Module.Core.cpp.o Module.Core.cpp"
        .split(" ")
        .map(|x| x.to_string()));
    assert_eq!(parse_arguments(&args).unwrap(),
               [Arg::flag(Scope::Ignore, "c"),
                Arg::param(Scope::Preprocessor, "include-pch", "CorePrivatePCH.h.pch"),
                Arg::flag(Scope::Shared, "pipe"),
                Arg::flag(Scope::Shared, "Wall"),
                Arg::flag(Scope::Shared, "Werror"),
                Arg::flag(Scope::Shared, "funwind-tables"),
                Arg::flag(Scope::Shared, "Wsequence-point"),
                Arg::flag(Scope::Shared, "mmmx"),
                Arg::flag(Scope::Shared, "msse"),
                Arg::flag(Scope::Shared, "msse2"),
                Arg::flag(Scope::Shared, "nostdinc++"),
                Arg::flag(Scope::Shared, "fno-math-errno"),
                Arg::flag(Scope::Shared, "fno-rtti"),
                Arg::flag(Scope::Shared, "g3"),
                Arg::flag(Scope::Shared, "gdwarf-3"),
                Arg::flag(Scope::Shared, "O2"),
                Arg::param(Scope::Shared, "D", "IS_PROGRAM=1"),
                Arg::param(Scope::Shared, "D", "UNICODE"),
                Arg::param(Scope::Shared, "D", "IS_MONOLITHIC=1"),
                Arg::param(Scope::Ignore, "x", "c++"),
                Arg::flag(Scope::Shared, "std=c++11"),
                Arg::param(Scope::Preprocessor, "include", "CorePrivatePCH.h"),
                Arg::output(OutputKind::Object, "o", "Module.Core.cpp.o"),
                Arg::input(InputKind::Source, "", "Module.Core.cpp")])
}
<commit_msg>Really add -nostdinc++ to whitelist (Fixes #17)<commit_after>use std::iter::FromIterator;
use std::slice::Iter;
use std::ascii::AsciiExt;
use std::path::{Path, PathBuf};
use std::sync::Arc;

use ::compiler::{Arg, CommandInfo, CompilationArgs, CompilationTask, InputKind, OutputKind, Scope};

enum ParamValue<T> {
    None,
    Single(T),
    Many(Vec<T>),
}

pub fn create_tasks(command: CommandInfo, args: &[String]) -> Result<Vec<CompilationTask>, String> {
    if args.iter()
        .find(|v| {
            match v as &str {
                "--analyze" => true,
                _ => false,
            }
        })
        .is_some() {
        // Support only compilation steps
        return Ok(Vec::new());
    }
    if args.iter()
        .find(|v| {
            match v as &str {
                "-c" => true,
                _ => false,
            }
        })
        .is_none() {
        // Support only compilation steps
        return Ok(Vec::new());
    }
    let parsed_args = try!(parse_arguments(args));
    // Source file name.
    let input_sources: Vec<PathBuf> = parsed_args.iter()
        .filter_map(|arg| {
            match arg {
                &Arg::Input { ref kind, ref file, .. } if *kind == InputKind::Source => {
                    Some(Path::new(file).to_path_buf())
                }
                _ => None,
            }
        })
        .collect();
    if input_sources.len() == 0 {
        return Err(format!("Can't find source file path."));
    }
    // Precompiled header file name.
    let input_precompiled = match find_param(&parsed_args, |arg: &Arg| -> Option<PathBuf> {
        match arg {
            &Arg::Input { ref kind, ref file, .. } if *kind == InputKind::Precompiled => {
                Some(Path::new(file).to_path_buf())
            }
            _ => None,
        }
    }) {
        ParamValue::None => None,
        ParamValue::Single(v) => Some(v),
        ParamValue::Many(v) => {
            return Err(format!("Found too many precompiled header files: {:?}", v));
        }
    };
    // Precompiled header file name.
    let marker_precompiled = parsed_args.iter()
        .filter_map(|arg| {
            match arg {
                &Arg::Param { ref flag, ref value, .. } if *flag == "include" => Some(value.clone()),
                _ => None,
            }
        })
        .next();
    // Output object file name.
    let output_object = match find_param(&parsed_args, |arg: &Arg| -> Option<PathBuf> {
        match arg {
            &Arg::Output { ref kind, ref file, .. } if *kind == OutputKind::Object => {
                Some(Path::new(file).to_path_buf())
            }
            _ => None,
        }
    }) {
        ParamValue::None => None,
        ParamValue::Single(v) => {
            if input_sources.len() > 1 {
                return Err("Cannot specify -o when generating multiple output files".to_string());
            }
            Some(v)
        }
        ParamValue::Many(v) => {
            return Err(format!("Found too many output object files: {:?}", v));
        }
    };
    // Language
    let language: Option<String> = match find_param(&parsed_args, |arg: &Arg| -> Option<String> {
        match arg {
            &Arg::Param { ref flag, ref value, .. } if *flag == "x" => Some(value.clone()),
            _ => None,
        }
    }) {
        ParamValue::None => None,
        ParamValue::Single(v) => {
            match &v[..] {
                "c" | "c++" => Some(v.to_string()),
                "c-header" | "c++-header" => {
                    // Precompiled headers must build locally
                    return Ok(Vec::new());
                }
                _ => {
                    return Err(format!("Unknown source language type: {}", v));
                }
            }
        }
        ParamValue::Many(v) => {
            return Err(format!("Found too many output object files: {:?}", v));
        }
    };
    let shared = Arc::new(CompilationArgs {
        command: command,
        args: parsed_args,
        output_precompiled: None,
        marker_precompiled: marker_precompiled,
        input_precompiled: input_precompiled,
    });
    input_sources.into_iter()
        .map(|source| {
            Ok(CompilationTask {
                shared: shared.clone(),
                language: try!(language.as_ref()
                    .map_or_else(|| {
                        source.extension()
                            .and_then(|ext| match ext.to_str() {
                                Some(e) if e.eq_ignore_ascii_case("cpp") => Some("c++"),
                                Some(e) if e.eq_ignore_ascii_case("c") => Some("c"),
                                Some(e) if e.eq_ignore_ascii_case("hpp") => Some("c++-header"),
                                Some(e) if e.eq_ignore_ascii_case("h") => Some("c-header"),
                                _ => None,
                            })
                            .map(|ext| ext.to_string())
                    },
                                 |lang| Some(lang.clone()))
                    .ok_or_else(|| {
                        format!("Can't detect file language by extension: {}",
                                source.as_os_str().to_string_lossy())
                    })),
                output_object: output_object.as_ref().map_or_else(|| source.with_extension("o"), |path| path.clone()),
                input_source: source,
            })
        })
        .collect()
}

fn find_param<T, R, F: Fn(&T) -> Option<R>>(args: &Vec<T>, filter: F) -> ParamValue<R> {
    let mut found = Vec::from_iter(args.iter().filter_map(filter));
    match found.len() {
        0 => ParamValue::None,
        1 => ParamValue::Single(found.pop().unwrap()),
        _ => ParamValue::Many(found),
    }
}

fn parse_arguments(args: &[String]) -> Result<Vec<Arg>, String> {
    let mut result: Vec<Arg> = Vec::new();
    let mut errors: Vec<String> = Vec::new();
    let mut iter = args.iter();
    loop {
        match parse_argument(&mut iter) {
            Some(parse_result) => {
                match parse_result {
                    Ok(arg) => {
                        result.push(arg);
                    }
                    Err(e) => {
                        errors.push(e);
                    }
                }
            }
            None => {
                break;
            }
        }
    }
    if errors.len() > 0 {
        return Err(format!("Found unknown command line arguments: {:?}", errors));
    }
    Ok(result)
}

fn parse_argument(iter: &mut Iter<String>) -> Option<Result<Arg, String>> {
    match iter.next() {
        Some(arg) => {
            Some(if arg.starts_with("--") {
                let (key, value) = match arg.find("=") {
                    Some(position) => (&arg[1..position], arg[position + 1..].to_string()),
                    None => {
                        match iter.next() {
                            Some(v) => (&arg[1..], v.clone()),
                            _ => {
                                return Some(Err(arg.to_string()));
                            }
                        }
                    }
                };
                match &key[1..] {
                    "sysroot" => Ok(Arg::flag(Scope::Shared, key.to_string() + "=" + &value)),
                    _ => Err(key.to_string()),
                }
            } else if has_param_prefix(arg) {
                let flag = &arg[1..];
                match is_spaceable_param(flag) {
                    Some((prefix, scope, next_flag)) => {
                        let value = match flag == prefix {
                            true => {
                                match iter.next() {
                                    Some(v) if next_flag == has_param_prefix(v) => v.to_string(),
                                    _ => {
                                        return Some(Err(arg.to_string()));
                                    }
                                }
                            }
                            false => flag[prefix.len()..].to_string(),
                        };
                        match flag {
                            "o" => Ok(Arg::output(OutputKind::Object, prefix, value)),
                            _ => Ok(Arg::param(scope, prefix, value)),
                        }
                    }
                    None => {
                        match flag {
                            "c" => Ok(Arg::flag(Scope::Ignore, flag)),
                            "pipe" => Ok(Arg::flag(Scope::Shared, flag)),
                            "nostdinc++" => Ok(Arg::flag(Scope::Shared, flag)),
                            s if s.starts_with("f") => Ok(Arg::flag(Scope::Shared, flag)),
                            s if s.starts_with("g") => Ok(Arg::flag(Scope::Shared, flag)),
                            s if s.starts_with("O") => Ok(Arg::flag(Scope::Shared, flag)),
                            s if s.starts_with("W") => Ok(Arg::flag(Scope::Shared, flag)),
                            s if s.starts_with("m") => Ok(Arg::flag(Scope::Shared, flag)),
                            s if s.starts_with("std=") => Ok(Arg::flag(Scope::Shared, flag)),
                            _ => Err(arg.to_string()),
                        }
                    }
                }
            } else {
                Ok(Arg::input(InputKind::Source, String::new(), arg.to_string()))
            })
        }
        None => None,
    }
}

fn is_spaceable_param(flag: &str) -> Option<(&str, Scope, bool)> {
    match flag {
        "include" | "include-pch" => Some((flag, Scope::Preprocessor, false)),
        "target" => Some((flag, Scope::Shared, false)),
        _ => {
            for prefix in ["D", "o"].iter() {
                if flag.starts_with(*prefix) {
                    return Some((*prefix, Scope::Shared, false));
                }
            }
            for prefix in ["x"].iter() {
                if flag.starts_with(*prefix) {
                    return Some((*prefix, Scope::Ignore, false));
                }
            }
            for prefix in ["I"].iter() {
                if flag.starts_with(*prefix) {
                    return Some((*prefix, Scope::Preprocessor, false));
                }
            }
            None
        }
    }
}

fn has_param_prefix(arg: &String) -> bool {
    arg.starts_with("-")
}

#[test]
fn test_parse_argument_precompile() {
    let args = Vec::from_iter("-x c++-header -pipe -Wall -Werror -funwind-tables -Wsequence-point -mmmx -msse -msse2 \
                               -fno-math-errno -fno-rtti -g3 -gdwarf-3 -O2 -D_LINUX64 -IEngine/Source \
                               -IDeveloper/Public -I Runtime/Core/Private -D IS_PROGRAM=1 -D UNICODE \
                               -DIS_MONOLITHIC=1 -std=c++11 -o CorePrivatePCH.h.pch CorePrivatePCH.h"
        .split(" ")
        .map(|x| x.to_string()));
    assert_eq!(parse_arguments(&args).unwrap(),
               [Arg::param(Scope::Ignore, "x", "c++-header"),
                Arg::flag(Scope::Shared, "pipe"),
                Arg::flag(Scope::Shared, "Wall"),
                Arg::flag(Scope::Shared, "Werror"),
                Arg::flag(Scope::Shared, "funwind-tables"),
                Arg::flag(Scope::Shared, "Wsequence-point"),
                Arg::flag(Scope::Shared, "mmmx"),
                Arg::flag(Scope::Shared, "msse"),
                Arg::flag(Scope::Shared, "msse2"),
                Arg::flag(Scope::Shared, "fno-math-errno"),
                Arg::flag(Scope::Shared, "fno-rtti"),
                Arg::flag(Scope::Shared, "g3"),
                Arg::flag(Scope::Shared, "gdwarf-3"),
                Arg::flag(Scope::Shared, "O2"),
                Arg::param(Scope::Shared, "D", "_LINUX64"),
                Arg::param(Scope::Preprocessor, "I", "Engine/Source"),
                Arg::param(Scope::Preprocessor, "I", "Developer/Public"),
                Arg::param(Scope::Preprocessor, "I", "Runtime/Core/Private"),
                Arg::param(Scope::Shared, "D", "IS_PROGRAM=1"),
                Arg::param(Scope::Shared, "D", "UNICODE"),
                Arg::param(Scope::Shared, "D", "IS_MONOLITHIC=1"),
                Arg::flag(Scope::Shared, "std=c++11"),
                Arg::output(OutputKind::Object, "o", "CorePrivatePCH.h.pch"),
                Arg::input(InputKind::Source, "", "CorePrivatePCH.h")])
}

#[test]
fn test_parse_argument_compile() {
    let args = Vec::from_iter("-c -include-pch CorePrivatePCH.h.pch -pipe -Wall -Werror -funwind-tables \
                               -Wsequence-point -mmmx -msse -msse2 -fno-math-errno -fno-rtti -g3 -gdwarf-3 -O2 -D \
                               IS_PROGRAM=1 -D UNICODE -DIS_MONOLITHIC=1 -x c++ -std=c++11 -include CorePrivatePCH.h \
                               -o Module.Core.cpp.o Module.Core.cpp"
        .split(" ")
        .map(|x| x.to_string()));
    assert_eq!(parse_arguments(&args).unwrap(),
               [Arg::flag(Scope::Ignore, "c"),
                Arg::param(Scope::Preprocessor, "include-pch", "CorePrivatePCH.h.pch"),
                Arg::flag(Scope::Shared, "pipe"),
                Arg::flag(Scope::Shared, "Wall"),
                Arg::flag(Scope::Shared, "Werror"),
                Arg::flag(Scope::Shared, "funwind-tables"),
                Arg::flag(Scope::Shared, "Wsequence-point"),
                Arg::flag(Scope::Shared, "mmmx"),
                Arg::flag(Scope::Shared, "msse"),
                Arg::flag(Scope::Shared, "msse2"),
                Arg::flag(Scope::Shared, "fno-math-errno"),
                Arg::flag(Scope::Shared, "fno-rtti"),
                Arg::flag(Scope::Shared, "g3"),
                Arg::flag(Scope::Shared, "gdwarf-3"),
                Arg::flag(Scope::Shared, "O2"),
                Arg::param(Scope::Shared, "D", "IS_PROGRAM=1"),
                Arg::param(Scope::Shared, "D", "UNICODE"),
                Arg::param(Scope::Shared, "D", "IS_MONOLITHIC=1"),
                Arg::param(Scope::Ignore, "x", "c++"),
                Arg::flag(Scope::Shared, "std=c++11"),
                Arg::param(Scope::Preprocessor, "include", "CorePrivatePCH.h"),
                Arg::output(OutputKind::Object, "o", "Module.Core.cpp.o"),
                Arg::input(InputKind::Source, "", "Module.Core.cpp")])
}
<|endoftext|>


<commit_before><commit_msg>Fix rand_jitter error conversion<commit_after><|endoftext|>


<commit_before><commit_msg>get and print method and pathname<commit_after><|endoftext|>


<commit_before><commit_msg>Remove obsolete main function.<commit_after><|endoftext|>