<commit_before>//! Toplevel handle for a loaded SVG document.
//!
//! This module provides the primitives on which the public APIs are implemented.

use crate::accept_language::UserLanguage;
use crate::bbox::BoundingBox;
use crate::css::{Origin, Stylesheet};
use crate::document::{AcquiredNodes, Document, NodeId};
use crate::dpi::Dpi;
use crate::drawing_ctx::{draw_tree, with_saved_cr, DrawingMode, ViewParams};
use crate::error::{DefsLookupErrorKind, LoadingError, RenderingError};
use crate::length::*;
use crate::node::{CascadedValues, Node, NodeBorrow};
use crate::rect::Rect;
use crate::session::Session;
use crate::structure::IntrinsicDimensions;
use crate::url_resolver::{AllowedUrl, UrlResolver};

/// Loading options for SVG documents.
#[derive(Clone)]
pub struct LoadOptions {
    /// Load url resolver; all references will be resolved with respect to this.
    pub url_resolver: UrlResolver,

    /// Whether to turn off size limits in libxml2.
    pub unlimited_size: bool,

    /// Whether to keep original (undecoded) image data to embed in Cairo PDF surfaces.
    pub keep_image_data: bool,
}

impl LoadOptions {
    /// Creates a `LoadOptions` with defaults, and sets the `url resolver`.
    pub fn new(url_resolver: UrlResolver) -> Self {
        LoadOptions {
            url_resolver,
            unlimited_size: false,
            keep_image_data: false,
        }
    }

    /// Sets whether libxml2's limits on memory usage should be turned off.
    ///
    /// This should only be done for trusted data.
    pub fn with_unlimited_size(mut self, unlimited: bool) -> Self {
        self.unlimited_size = unlimited;
        self
    }

    /// Sets whether to keep the original compressed image data from referenced JPEG/PNG images.
    ///
    /// This is only useful for rendering to Cairo PDF
    /// surfaces, which can embed the original, compressed image data instead of uncompressed
    /// RGB buffers.
    pub fn keep_image_data(mut self, keep: bool) -> Self {
        self.keep_image_data = keep;
        self
    }

    /// Creates a new `LoadOptions` with a different `url resolver`.
    ///
    /// This is used when loading a referenced file that may in turn cause other files
    /// to be loaded, for example `<image xlink:href="subimage.svg"/>`
    pub fn copy_with_base_url(&self, base_url: &AllowedUrl) -> Self {
        let mut url_resolver = self.url_resolver.clone();
        url_resolver.base_url = Some((**base_url).clone());

        LoadOptions {
            url_resolver,
            unlimited_size: self.unlimited_size,
            keep_image_data: self.keep_image_data,
        }
    }
}

/// Main handle to an SVG document.
///
/// This is the main object in librsvg.  It gets created with the [`from_stream`] method
/// and then provides access to all the primitives needed to implement the public APIs.
///
/// [`from_stream`]: #method.from_stream
pub struct Handle {
    session: Session,
    document: Document,
}

impl Handle {
    /// Loads an SVG document into a `Handle`.
    pub fn from_stream(
        session: Session,
        load_options: &LoadOptions,
        stream: &gio::InputStream,
        cancellable: Option<&gio::Cancellable>,
    ) -> Result<Handle, LoadingError> {
        Ok(Handle {
            session: session.clone(),
            document: Document::load_from_stream(
                session,
                load_options,
                stream,
                cancellable,
            )?,
        })
    }

    /// Queries whether a document has a certain element `#foo`.
    ///
    /// The `id` must be an URL fragment identifier, i.e. something
    /// like `#element_id`.
    pub fn has_sub(&self, id: &str) -> Result<bool, RenderingError> {
        match self.lookup_node(id) {
            Ok(_) => Ok(true),

            Err(DefsLookupErrorKind::NotFound) => Ok(false),

            Err(e) => Err(e.into()),
        }
    }

    /// Normalizes the svg's width/height properties with a 0-sized viewport
    ///
    /// This assumes that if one of the properties is in percentage units, then
    /// its corresponding value will not be used.  E.g. if width=100%, the caller
    /// will ignore the resulting width value.
    pub fn width_height_to_user(&self, dpi: Dpi) -> (f64, f64) {
        let dimensions = self.get_intrinsic_dimensions();

        let width = dimensions.width;
        let height = dimensions.height;

        let view_params = ViewParams::new(dpi, 0.0, 0.0);
        let root = self.document.root();
        let cascaded = CascadedValues::new_from_node(&root);
        let values = cascaded.get();

        let params = NormalizeParams::new(values, &view_params);

        (width.to_user(&params), height.to_user(&params))
    }

    fn get_node_or_root(&self, id: Option<&str>) -> Result<Node, RenderingError> {
        if let Some(id) = id {
            Ok(self.lookup_node(id)?)
        } else {
            Ok(self.document.root())
        }
    }

    fn geometry_for_layer(
        &self,
        node: Node,
        viewport: Rect,
        user_language: &UserLanguage,
        dpi: Dpi,
        is_testing: bool,
    ) -> Result<(Rect, Rect), RenderingError> {
        let root = self.document.root();

        let target = cairo::ImageSurface::create(cairo::Format::Rgb24, 1, 1)?;
        let cr = cairo::Context::new(&target)?;

        let bbox = draw_tree(
            self.session.clone(),
            DrawingMode::LimitToStack { node, root },
            &cr,
            viewport,
            user_language,
            dpi,
            true,
            is_testing,
            &mut AcquiredNodes::new(&self.document),
        )?;

        let ink_rect = bbox.ink_rect.unwrap_or_default();
        let logical_rect = bbox.rect.unwrap_or_default();

        Ok((ink_rect, logical_rect))
    }

    pub fn get_geometry_for_layer(
        &self,
        id: Option<&str>,
        viewport: &cairo::Rectangle,
        user_language: &UserLanguage,
        dpi: Dpi,
        is_testing: bool,
    ) -> Result<(cairo::Rectangle, cairo::Rectangle), RenderingError> {
        let viewport = Rect::from(*viewport);
        let node = self.get_node_or_root(id)?;

        let (ink_rect, logical_rect) =
            self.geometry_for_layer(node, viewport, user_language, dpi, is_testing)?;

        Ok((
            cairo::Rectangle::from(ink_rect),
            cairo::Rectangle::from(logical_rect),
        ))
    }

    fn lookup_node(&self, id: &str) -> Result<Node, DefsLookupErrorKind> {
        let node_id = NodeId::parse(id).map_err(|_| DefsLookupErrorKind::InvalidId)?;

        // The public APIs to get geometries of individual elements, or to render
        // them, should only allow referencing elements within the main handle's
        // SVG file; that is, only plain "#foo" fragment IDs are allowed here.
        // Otherwise, a calling program could request "another-file#foo" and cause
        // another-file to be loaded, even if it is not part of the set of
        // resources that the main SVG actually references.  In the future we may
        // relax this requirement to allow lookups within that set, but not to
        // other random files.
        match node_id {
            NodeId::Internal(id) => self
                .document
                .lookup_internal_node(&id)
                .ok_or(DefsLookupErrorKind::NotFound),
            NodeId::External(_, _) => {
                rsvg_log!(
                    self.session,
                    "the public API is not allowed to look up external references: {}",
                    node_id
                );

                Err(DefsLookupErrorKind::CannotLookupExternalReferences)
            }
        }
    }

    pub fn render_document(
        &self,
        cr: &cairo::Context,
        viewport: &cairo::Rectangle,
        user_language: &UserLanguage,
        dpi: Dpi,
        is_testing: bool,
    ) -> Result<(), RenderingError> {
        self.render_layer(cr, None, viewport, user_language, dpi, is_testing)
    }

    pub fn render_layer(
        &self,
        cr: &cairo::Context,
        id: Option<&str>,
        viewport: &cairo::Rectangle,
        user_language: &UserLanguage,
        dpi: Dpi,
        is_testing: bool,
    ) -> Result<(), RenderingError> {
        cr.status()?;

        let node = self.get_node_or_root(id)?;
        let root = self.document.root();

        let viewport = Rect::from(*viewport);

        with_saved_cr(cr, || {
            draw_tree(
                self.session.clone(),
                DrawingMode::LimitToStack { node, root },
                cr,
                viewport,
                user_language,
                dpi,
                false,
                is_testing,
                &mut AcquiredNodes::new(&self.document),
            )
            .map(|_bbox| ())
        })
    }

    fn get_bbox_for_element(
        &self,
        node: &Node,
        user_language: &UserLanguage,
        dpi: Dpi,
        is_testing: bool,
    ) -> Result<BoundingBox, RenderingError> {
        let target = cairo::ImageSurface::create(cairo::Format::Rgb24, 1, 1)?;
        let cr = cairo::Context::new(&target)?;

        let node = node.clone();

        draw_tree(
            self.session.clone(),
            DrawingMode::OnlyNode(node),
            &cr,
            unit_rectangle(),
            user_language,
            dpi,
            true,
            is_testing,
            &mut AcquiredNodes::new(&self.document),
        )
    }

    /// Returns (ink_rect, logical_rect)
    pub fn get_geometry_for_element(
        &self,
        id: Option<&str>,
        user_language: &UserLanguage,
        dpi: Dpi,
        is_testing: bool,
    ) -> Result<(cairo::Rectangle, cairo::Rectangle), RenderingError> {
        let node = self.get_node_or_root(id)?;

        let bbox = self.get_bbox_for_element(&node, user_language, dpi, is_testing)?;

        let ink_rect = bbox.ink_rect.unwrap_or_default();
        let logical_rect = bbox.rect.unwrap_or_default();

        // Translate so ink_rect is always at offset (0, 0)
        let ofs = (-ink_rect.x0, -ink_rect.y0);

        Ok((
            cairo::Rectangle::from(ink_rect.translate(ofs)),
            cairo::Rectangle::from(logical_rect.translate(ofs)),
        ))
    }

    pub fn render_element(
        &self,
        cr: &cairo::Context,
        id: Option<&str>,
        element_viewport: &cairo::Rectangle,
        user_language: &UserLanguage,
        dpi: Dpi,
        is_testing: bool,
    ) -> Result<(), RenderingError> {
        cr.status()?;

        let node = self.get_node_or_root(id)?;

        let bbox = self.get_bbox_for_element(&node, user_language, dpi, is_testing)?;

        if bbox.ink_rect.is_none() || bbox.rect.is_none() {
            // Nothing to draw
            return Ok(());
        }

        let ink_r = bbox.ink_rect.unwrap_or_default();

        if ink_r.is_empty() {
            return Ok(());
        }

        // Render, transforming so element is at the new viewport's origin

        with_saved_cr(cr, || {
            let factor = (element_viewport.width / ink_r.width())
                .min(element_viewport.height / ink_r.height());

            cr.translate(element_viewport.x, element_viewport.y);
            cr.scale(factor, factor);
            cr.translate(-ink_r.x0, -ink_r.y0);

            draw_tree(
                self.session.clone(),
                DrawingMode::OnlyNode(node),
                cr,
                unit_rectangle(),
                user_language,
                dpi,
                false,
                is_testing,
                &mut AcquiredNodes::new(&self.document),
            )
            .map(|_bbox| ())
        })
    }

    pub fn get_intrinsic_dimensions(&self) -> IntrinsicDimensions {
        let root = self.document.root();
        let cascaded = CascadedValues::new_from_node(&root);
        let values = cascaded.get();
        borrow_element_as!(self.document.root(), Svg).get_intrinsic_dimensions(values)
    }

    pub fn set_stylesheet(&mut self, css: &str) -> Result<(), LoadingError> {
        let mut stylesheet = Stylesheet::new(Origin::User);
        stylesheet.parse(css, &UrlResolver::new(None), self.session.clone())?;
        self.document.cascade(&[stylesheet], &self.session);
        Ok(())
    }
}

fn unit_rectangle() -> Rect {
    Rect::from_size(1.0, 1.0)
}
<commit_msg>cargo fmt<commit_after>//! Toplevel handle for a loaded SVG document.
//!
//! This module provides the primitives on which the public APIs are implemented.

use crate::accept_language::UserLanguage;
use crate::bbox::BoundingBox;
use crate::css::{Origin, Stylesheet};
use crate::document::{AcquiredNodes, Document, NodeId};
use crate::dpi::Dpi;
use crate::drawing_ctx::{draw_tree, with_saved_cr, DrawingMode, ViewParams};
use crate::error::{DefsLookupErrorKind, LoadingError, RenderingError};
use crate::length::*;
use crate::node::{CascadedValues, Node, NodeBorrow};
use crate::rect::Rect;
use crate::session::Session;
use crate::structure::IntrinsicDimensions;
use crate::url_resolver::{AllowedUrl, UrlResolver};

/// Loading options for SVG documents.
#[derive(Clone)]
pub struct LoadOptions {
    /// Load url resolver; all references will be resolved with respect to this.
    pub url_resolver: UrlResolver,

    /// Whether to turn off size limits in libxml2.
    pub unlimited_size: bool,

    /// Whether to keep original (undecoded) image data to embed in Cairo PDF surfaces.
    pub keep_image_data: bool,
}

impl LoadOptions {
    /// Creates a `LoadOptions` with defaults, and sets the `url resolver`.
    pub fn new(url_resolver: UrlResolver) -> Self {
        LoadOptions {
            url_resolver,
            unlimited_size: false,
            keep_image_data: false,
        }
    }

    /// Sets whether libxml2's limits on memory usage should be turned off.
    ///
    /// This should only be done for trusted data.
    pub fn with_unlimited_size(mut self, unlimited: bool) -> Self {
        self.unlimited_size = unlimited;
        self
    }

    /// Sets whether to keep the original compressed image data from referenced JPEG/PNG images.
    ///
    /// This is only useful for rendering to Cairo PDF
    /// surfaces, which can embed the original, compressed image data instead of uncompressed
    /// RGB buffers.
    pub fn keep_image_data(mut self, keep: bool) -> Self {
        self.keep_image_data = keep;
        self
    }

    /// Creates a new `LoadOptions` with a different `url resolver`.
    ///
    /// This is used when loading a referenced file that may in turn cause other files
    /// to be loaded, for example `<image xlink:href="subimage.svg"/>`
    pub fn copy_with_base_url(&self, base_url: &AllowedUrl) -> Self {
        let mut url_resolver = self.url_resolver.clone();
        url_resolver.base_url = Some((**base_url).clone());

        LoadOptions {
            url_resolver,
            unlimited_size: self.unlimited_size,
            keep_image_data: self.keep_image_data,
        }
    }
}

/// Main handle to an SVG document.
///
/// This is the main object in librsvg.  It gets created with the [`from_stream`] method
/// and then provides access to all the primitives needed to implement the public APIs.
///
/// [`from_stream`]: #method.from_stream
pub struct Handle {
    session: Session,
    document: Document,
}

impl Handle {
    /// Loads an SVG document into a `Handle`.
    pub fn from_stream(
        session: Session,
        load_options: &LoadOptions,
        stream: &gio::InputStream,
        cancellable: Option<&gio::Cancellable>,
    ) -> Result<Handle, LoadingError> {
        Ok(Handle {
            session: session.clone(),
            document: Document::load_from_stream(session, load_options, stream, cancellable)?,
        })
    }

    /// Queries whether a document has a certain element `#foo`.
    ///
    /// The `id` must be an URL fragment identifier, i.e. something
    /// like `#element_id`.
    pub fn has_sub(&self, id: &str) -> Result<bool, RenderingError> {
        match self.lookup_node(id) {
            Ok(_) => Ok(true),

            Err(DefsLookupErrorKind::NotFound) => Ok(false),

            Err(e) => Err(e.into()),
        }
    }

    /// Normalizes the svg's width/height properties with a 0-sized viewport
    ///
    /// This assumes that if one of the properties is in percentage units, then
    /// its corresponding value will not be used.  E.g. if width=100%, the caller
    /// will ignore the resulting width value.
    pub fn width_height_to_user(&self, dpi: Dpi) -> (f64, f64) {
        let dimensions = self.get_intrinsic_dimensions();

        let width = dimensions.width;
        let height = dimensions.height;

        let view_params = ViewParams::new(dpi, 0.0, 0.0);
        let root = self.document.root();
        let cascaded = CascadedValues::new_from_node(&root);
        let values = cascaded.get();

        let params = NormalizeParams::new(values, &view_params);

        (width.to_user(&params), height.to_user(&params))
    }

    fn get_node_or_root(&self, id: Option<&str>) -> Result<Node, RenderingError> {
        if let Some(id) = id {
            Ok(self.lookup_node(id)?)
        } else {
            Ok(self.document.root())
        }
    }

    fn geometry_for_layer(
        &self,
        node: Node,
        viewport: Rect,
        user_language: &UserLanguage,
        dpi: Dpi,
        is_testing: bool,
    ) -> Result<(Rect, Rect), RenderingError> {
        let root = self.document.root();

        let target = cairo::ImageSurface::create(cairo::Format::Rgb24, 1, 1)?;
        let cr = cairo::Context::new(&target)?;

        let bbox = draw_tree(
            self.session.clone(),
            DrawingMode::LimitToStack { node, root },
            &cr,
            viewport,
            user_language,
            dpi,
            true,
            is_testing,
            &mut AcquiredNodes::new(&self.document),
        )?;

        let ink_rect = bbox.ink_rect.unwrap_or_default();
        let logical_rect = bbox.rect.unwrap_or_default();

        Ok((ink_rect, logical_rect))
    }

    pub fn get_geometry_for_layer(
        &self,
        id: Option<&str>,
        viewport: &cairo::Rectangle,
        user_language: &UserLanguage,
        dpi: Dpi,
        is_testing: bool,
    ) -> Result<(cairo::Rectangle, cairo::Rectangle), RenderingError> {
        let viewport = Rect::from(*viewport);
        let node = self.get_node_or_root(id)?;

        let (ink_rect, logical_rect) =
            self.geometry_for_layer(node, viewport, user_language, dpi, is_testing)?;

        Ok((
            cairo::Rectangle::from(ink_rect),
            cairo::Rectangle::from(logical_rect),
        ))
    }

    fn lookup_node(&self, id: &str) -> Result<Node, DefsLookupErrorKind> {
        let node_id = NodeId::parse(id).map_err(|_| DefsLookupErrorKind::InvalidId)?;

        // The public APIs to get geometries of individual elements, or to render
        // them, should only allow referencing elements within the main handle's
        // SVG file; that is, only plain "#foo" fragment IDs are allowed here.
        // Otherwise, a calling program could request "another-file#foo" and cause
        // another-file to be loaded, even if it is not part of the set of
        // resources that the main SVG actually references.  In the future we may
        // relax this requirement to allow lookups within that set, but not to
        // other random files.
        match node_id {
            NodeId::Internal(id) => self
                .document
                .lookup_internal_node(&id)
                .ok_or(DefsLookupErrorKind::NotFound),
            NodeId::External(_, _) => {
                rsvg_log!(
                    self.session,
                    "the public API is not allowed to look up external references: {}",
                    node_id
                );

                Err(DefsLookupErrorKind::CannotLookupExternalReferences)
            }
        }
    }

    pub fn render_document(
        &self,
        cr: &cairo::Context,
        viewport: &cairo::Rectangle,
        user_language: &UserLanguage,
        dpi: Dpi,
        is_testing: bool,
    ) -> Result<(), RenderingError> {
        self.render_layer(cr, None, viewport, user_language, dpi, is_testing)
    }

    pub fn render_layer(
        &self,
        cr: &cairo::Context,
        id: Option<&str>,
        viewport: &cairo::Rectangle,
        user_language: &UserLanguage,
        dpi: Dpi,
        is_testing: bool,
    ) -> Result<(), RenderingError> {
        cr.status()?;

        let node = self.get_node_or_root(id)?;
        let root = self.document.root();

        let viewport = Rect::from(*viewport);

        with_saved_cr(cr, || {
            draw_tree(
                self.session.clone(),
                DrawingMode::LimitToStack { node, root },
                cr,
                viewport,
                user_language,
                dpi,
                false,
                is_testing,
                &mut AcquiredNodes::new(&self.document),
            )
            .map(|_bbox| ())
        })
    }

    fn get_bbox_for_element(
        &self,
        node: &Node,
        user_language: &UserLanguage,
        dpi: Dpi,
        is_testing: bool,
    ) -> Result<BoundingBox, RenderingError> {
        let target = cairo::ImageSurface::create(cairo::Format::Rgb24, 1, 1)?;
        let cr = cairo::Context::new(&target)?;

        let node = node.clone();

        draw_tree(
            self.session.clone(),
            DrawingMode::OnlyNode(node),
            &cr,
            unit_rectangle(),
            user_language,
            dpi,
            true,
            is_testing,
            &mut AcquiredNodes::new(&self.document),
        )
    }

    /// Returns (ink_rect, logical_rect)
    pub fn get_geometry_for_element(
        &self,
        id: Option<&str>,
        user_language: &UserLanguage,
        dpi: Dpi,
        is_testing: bool,
    ) -> Result<(cairo::Rectangle, cairo::Rectangle), RenderingError> {
        let node = self.get_node_or_root(id)?;

        let bbox = self.get_bbox_for_element(&node, user_language, dpi, is_testing)?;

        let ink_rect = bbox.ink_rect.unwrap_or_default();
        let logical_rect = bbox.rect.unwrap_or_default();

        // Translate so ink_rect is always at offset (0, 0)
        let ofs = (-ink_rect.x0, -ink_rect.y0);

        Ok((
            cairo::Rectangle::from(ink_rect.translate(ofs)),
            cairo::Rectangle::from(logical_rect.translate(ofs)),
        ))
    }

    pub fn render_element(
        &self,
        cr: &cairo::Context,
        id: Option<&str>,
        element_viewport: &cairo::Rectangle,
        user_language: &UserLanguage,
        dpi: Dpi,
        is_testing: bool,
    ) -> Result<(), RenderingError> {
        cr.status()?;

        let node = self.get_node_or_root(id)?;

        let bbox = self.get_bbox_for_element(&node, user_language, dpi, is_testing)?;

        if bbox.ink_rect.is_none() || bbox.rect.is_none() {
            // Nothing to draw
            return Ok(());
        }

        let ink_r = bbox.ink_rect.unwrap_or_default();

        if ink_r.is_empty() {
            return Ok(());
        }

        // Render, transforming so element is at the new viewport's origin

        with_saved_cr(cr, || {
            let factor = (element_viewport.width / ink_r.width())
                .min(element_viewport.height / ink_r.height());

            cr.translate(element_viewport.x, element_viewport.y);
            cr.scale(factor, factor);
            cr.translate(-ink_r.x0, -ink_r.y0);

            draw_tree(
                self.session.clone(),
                DrawingMode::OnlyNode(node),
                cr,
                unit_rectangle(),
                user_language,
                dpi,
                false,
                is_testing,
                &mut AcquiredNodes::new(&self.document),
            )
            .map(|_bbox| ())
        })
    }

    pub fn get_intrinsic_dimensions(&self) -> IntrinsicDimensions {
        let root = self.document.root();
        let cascaded = CascadedValues::new_from_node(&root);
        let values = cascaded.get();
        borrow_element_as!(self.document.root(), Svg).get_intrinsic_dimensions(values)
    }

    pub fn set_stylesheet(&mut self, css: &str) -> Result<(), LoadingError> {
        let mut stylesheet = Stylesheet::new(Origin::User);
        stylesheet.parse(css, &UrlResolver::new(None), self.session.clone())?;
        self.document.cascade(&[stylesheet], &self.session);
        Ok(())
    }
}

fn unit_rectangle() -> Rect {
    Rect::from_size(1.0, 1.0)
}
<|endoftext|>


<commit_before>use std::io::{Read, BufRead, Error as IoError};
use std::str::{from_utf8, Utf8Error};

use num::bigint::{BigInt, ParseBigIntError};
use byteorder::{ReadBytesExt, LittleEndian, Error as ByteorderError};

quick_error! {
    #[derive(Debug)]
    pub enum Error {
        ReadError(err: ByteorderError) {
            from()
        }
        IoError(err: IoError) {
            from()
        }
        UnknownOpcode(opcode: u8) {}
        InvalidInt {
            from(Utf8Error)
            from(ParseBigIntError)
        }
        InvalidString
    }
}

#[derive(Debug)]
pub enum OpCode {
    Int(BigInt),
    BinInt(i32),
    BinInt1(u8),
    BinInt2(u16),
    Long(BigInt),
    Long1(u8),
    Long4(i32),

    String(Vec<u8>),
    BinString(Vec<u8>),
    ShortBinString(Vec<u8>),

    None,

    NewTrue,
    NewFalse,

    Unicode(String),
    BinUnicode(String),

    Float(f64),
    BinFloat(f64),

    EmptyList,
    Append,
    Appends,
    List,

    EmptyTuple,
    Tuple,
    Tuple1,
    Tuple2,
    Tuple3,

    EmptyDict,
    Dict,
    SetItem,
    SetItems,

    Pop,
    Dup,
    Mark,
    PopMark,

    Get(usize),
    BinGet(usize),
    LongBinGet(usize),
    Put(usize),
    BinPut(usize),
    LongBinPut(usize),

    Ext1(u8),
    Ext2(u16),
    Ext4(i32),

    Global(Vec<u8>, Vec<u8>),
    Reduce,
    Build,
    Inst(Vec<u8>, Vec<u8>),
    Obj,
    NewObj,
    Proto(u8),
    Stop,
    PersId(Vec<u8>),
    BinPersId,
}

pub fn read_opcode<R>(rd: &mut R) -> Result<OpCode, Error> where R: Read + BufRead {
    let marker = try!(rd.read_u8());
    return Ok(match marker {
        73 => {
            let mut buf = Vec::new();
            try!(rd.read_until('\n' as u8, &mut buf));

            // Skip last symbol — \n
            let init = match buf.split_last() {
                None => return Err(Error::InvalidString),
                Some((_last, init)) => init,
            };

            OpCode::Int(try!(try!(from_utf8(init)).parse()))
        },
        74 => OpCode::BinInt(try!(rd.read_i32::<LittleEndian>())),
        75 => OpCode::BinInt1(try!(rd.read_u8())),
        77 => OpCode::BinInt2(try!(rd.read_u16::<LittleEndian>())),
        c => return Err(Error::UnknownOpcode(c)),
    })
}

#[cfg(test)]
mod tests {
    use std::io::{Cursor};

    use num::{FromPrimitive};

    use super::{OpCode, Error, read_opcode};

    macro_rules! t {
        ($buffer: expr, $pat:pat, $result:expr) => ({
            match read_opcode(&mut Cursor::new(&$buffer[..])) {
                $pat => $result,
                other => {
                    println!("ERROR {:?}", other);
                    assert!(false)
                },
            }
        })
    }

    #[test]
    fn test_int() {
        t!(b"I", Err(Error::InvalidString), assert!(true));
        t!(b"I\n", Err(Error::InvalidInt), assert!(true));
        t!(b"Iabc\n", Err(Error::InvalidInt), assert!(true));
        t!(b"I123\n", Ok(OpCode::Int(n)), assert_eq!(n, FromPrimitive::from_usize(123).unwrap()));
    }

    #[test]
    fn test_bin_int() {
        t!(b"J\x0a", Err(Error::ReadError(_)), assert!(true));
        t!(b"J\x0a\x00\x00\x00", Ok(OpCode::BinInt(n)), assert_eq!(n, 10));
        t!(b"J\x0a\x00\x00\x01", Ok(OpCode::BinInt(n)), assert_eq!(n, 16777226));
    }

    #[test]
    fn test_bin_int1() {
        t!(b"K", Err(Error::ReadError(_)), assert!(true));
        t!(b"K\x0a", Ok(OpCode::BinInt1(n)), assert_eq!(n, 10));
    }

    #[test]
    fn test_bin_int2() {
        t!(b"M\x0a", Err(Error::ReadError(_)), assert!(true));
        t!(b"M\x0a\x00\x00\x00", Ok(OpCode::BinInt2(n)), assert_eq!(n, 10));
        t!(b"M\x0a\x01\x00\x00", Ok(OpCode::BinInt2(n)), assert_eq!(n, 266));
    }
}
<commit_msg>Add long opcodes<commit_after>use std::io::{Read, BufRead, Error as IoError};
use std::str::{from_utf8, Utf8Error};

use num::bigint::{BigInt, ParseBigIntError};
use byteorder::{ReadBytesExt, LittleEndian, Error as ByteorderError};

quick_error! {
    #[derive(Debug)]
    pub enum Error {
        ReadError(err: ByteorderError) {
            from()
        }
        IoError(err: IoError) {
            from()
        }
        UnknownOpcode(opcode: u8) {}
        InvalidInt {
            from(Utf8Error)
            from(ParseBigIntError)
        }
        InvalidString
        ExpectedTrailingL
    }
}

#[derive(Debug)]
pub enum OpCode {
    Int(BigInt),
    BinInt(i32),
    BinInt1(u8),
    BinInt2(u16),
    Long(BigInt),
    Long1(u8),
    Long4(i32),

    String(Vec<u8>),
    BinString(Vec<u8>),
    ShortBinString(Vec<u8>),

    None,

    NewTrue,
    NewFalse,

    Unicode(String),
    BinUnicode(String),

    Float(f64),
    BinFloat(f64),

    EmptyList,
    Append,
    Appends,
    List,

    EmptyTuple,
    Tuple,
    Tuple1,
    Tuple2,
    Tuple3,

    EmptyDict,
    Dict,
    SetItem,
    SetItems,

    Pop,
    Dup,
    Mark,
    PopMark,

    Get(usize),
    BinGet(usize),
    LongBinGet(usize),
    Put(usize),
    BinPut(usize),
    LongBinPut(usize),

    Ext1(u8),
    Ext2(u16),
    Ext4(i32),

    Global(Vec<u8>, Vec<u8>),
    Reduce,
    Build,
    Inst(Vec<u8>, Vec<u8>),
    Obj,
    NewObj,
    Proto(u8),
    Stop,
    PersId(Vec<u8>),
    BinPersId,
}

pub fn read_opcode<R>(rd: &mut R) -> Result<OpCode, Error> where R: Read + BufRead {
    let marker = try!(rd.read_u8());
    return Ok(match marker {
        73 => {
            let mut buf = Vec::new();
            try!(rd.read_until('\n' as u8, &mut buf));

            // Skip last symbol — \n
            let init = match buf.split_last() {
                None => return Err(Error::InvalidString),
                Some((_last, init)) => init,
            };
            OpCode::Int(try!(try!(from_utf8(init)).parse()))
        },
        74 => OpCode::BinInt(try!(rd.read_i32::<LittleEndian>())),
        75 => OpCode::BinInt1(try!(rd.read_u8())),
        77 => OpCode::BinInt2(try!(rd.read_u16::<LittleEndian>())),
        76 => {

            let mut buf = Vec::new();
            try!(rd.read_until('\n' as u8, &mut buf));

            // Skip last symbol — \n
            let init_with_l = match buf.split_last() {
                None => return Err(Error::InvalidString),
                Some((_last, init)) => init,
            };

            let init = match init_with_l.split_last() {
                None => return Err(Error::InvalidString),
                Some((&76, init)) => init,
                Some(_) => return Err(Error::ExpectedTrailingL),
            };

            OpCode::Long(try!(try!(from_utf8(init)).parse()))
        },
        138 => OpCode::Long1(try!(rd.read_u8())),
        139 => OpCode::Long4(try!(rd.read_i32::<LittleEndian>())),
        c => return Err(Error::UnknownOpcode(c)),
    })
}

#[cfg(test)]
mod tests {
    use std::io::{Cursor};

    use num::{FromPrimitive};

    use super::{OpCode, Error, read_opcode};

    macro_rules! t {
        ($buffer: expr, $pat:pat, $result:expr) => ({
            match read_opcode(&mut Cursor::new(&$buffer[..])) {
                $pat => $result,
                other => {
                    println!("ERROR {:?}", other);
                    assert!(false)
                },
            }
        })
    }

    #[test]
    fn test_int() {
        t!(b"I", Err(Error::InvalidString), assert!(true));
        t!(b"I\n", Err(Error::InvalidInt), assert!(true));
        t!(b"Iabc\n", Err(Error::InvalidInt), assert!(true));
        t!(b"I123\n", Ok(OpCode::Int(n)), assert_eq!(n, FromPrimitive::from_usize(123).unwrap()));
    }

    #[test]
    fn test_bin_int() {
        t!(b"J\x0a", Err(Error::ReadError(_)), assert!(true));
        t!(b"J\x0a\x00\x00\x00", Ok(OpCode::BinInt(n)), assert_eq!(n, 10));
        t!(b"J\x0a\x00\x00\x01", Ok(OpCode::BinInt(n)), assert_eq!(n, 16777226));
    }

    #[test]
    fn test_bin_int1() {
        t!(b"K", Err(Error::ReadError(_)), assert!(true));
        t!(b"K\x0a", Ok(OpCode::BinInt1(n)), assert_eq!(n, 10));
    }

    #[test]
    fn test_bin_int2() {
        t!(b"M\x0a", Err(Error::ReadError(_)), assert!(true));
        t!(b"M\x0a\x00\x00\x00", Ok(OpCode::BinInt2(n)), assert_eq!(n, 10));
        t!(b"M\x0a\x01\x00\x00", Ok(OpCode::BinInt2(n)), assert_eq!(n, 266));
    }

    #[test]
    fn test_long() {
        t!(b"L", Err(Error::InvalidString), assert!(true));
        t!(b"L\n", Err(Error::InvalidString), assert!(true));
        t!(b"Labc\n", Err(Error::ExpectedTrailingL), assert!(true));
        t!(b"LabcL\n", Err(Error::InvalidInt), assert!(true));
        t!(b"L123L\n", Ok(OpCode::Long(n)), assert_eq!(n, FromPrimitive::from_usize(123).unwrap()));
    }

    #[test]
    fn test_long1() {
        t!(b"\x8a", Err(Error::ReadError(_)), assert!(true));
        t!(b"\x8a\x0a", Ok(OpCode::Long1(n)), assert_eq!(n, 10));
    }

    #[test]
    fn test_long4() {
        t!(b"\x8b\x0a", Err(Error::ReadError(_)), assert!(true));
        t!(b"\x8b\x0a\x00\x00\x00", Ok(OpCode::Long4(n)), assert_eq!(n, 10));
        t!(b"\x8b\x0a\x00\x00\x01", Ok(OpCode::Long4(n)), assert_eq!(n, 16777226));
    }
}
<|endoftext|>


<commit_before>use {Euclid, Point};
use point::Euclidean;

/// Clustering via the *k*-means algorithm (aka Lloyd's algorithm).
///
/// > *k*-means clustering aims to partition *n* observations into *k*
/// clusters in which each observation belongs to the cluster with the
/// nearest mean, serving as a prototype of the cluster.wikipedia
///
/// This is a heuristic, iterative approximation to the true optimal
/// assignment. The parameters used to control the approximation can
/// be set via `KmeansBuilder`.
///
/// # Examples
///
/// ```rust
/// use cogset::{Euclid, Kmeans};
///
/// let data = [Euclid([0.0, 0.0]),
///             Euclid([1.0, 0.5]),
///             Euclid([0.2, 0.2]),
///             Euclid([0.3, 0.8]),
///             Euclid([0.0, 1.0])];
/// let k = 3;
///
/// let kmeans = Kmeans::new(&data, k);
///
/// println!("{:?}", kmeans.clusters());
/// ```

pub struct Kmeans<T> {
    assignments: Vec<usize>,
    centres: Vec<Euclid<T>>,
    iterations: usize,
    converged: bool,
}

impl<T> Kmeans<T>
    where Euclid<T>: Point + Euclidean + Clone
{
    /// Run k-means on `data` with the default settings.
    pub fn new(data: &[Euclid<T>], k: usize) -> Kmeans<T> {
        KmeansBuilder::new().kmeans(data, k)
    }

    /// Retrieve the means and the clusters themselves that this
    /// *k*-means instance computed.
    ///
    /// The clusters are represented by vectors of indexes into the
    /// original data.
    pub fn clusters(&self) -> Vec<(Euclid<T>, Vec<usize>)> {
        let mut ret = self.centres.iter().cloned().map(|c| (c, vec![])).collect::<Vec<_>>();

        for (idx, &assign) in self.assignments.iter().enumerate() {
            ret[assign].1.push(idx);
        }

        ret
    }

    /// Return whether the algorithm converged, and how many steps
    /// that took.
    ///
    /// `Ok` is returned if the algorithm did meet the tolerance
    /// criterion, and `Err` if it reached the iteration limit
    /// instead.
    pub fn converged(&self) -> Result<usize, usize> {
        if self.converged {
            Ok(self.iterations)
        } else {
            Err(self.iterations)
        }
    }
}

const DEFAULT_MAX_ITER: usize = 100;
const DEFAULT_TOL: f64 = 1e-6;

/// A builder for *k*-means to provide control over parameters for the
/// algorithm.
///
/// This allows one to tweak settings like the tolerance and the
/// number of iterations.
///
/// # Examples
///
/// ```rust
/// use cogset::{Euclid, KmeansBuilder};
///
/// let data = [Euclid([0.0, 0.0]),
///             Euclid([1.0, 0.5]),
///             Euclid([0.2, 0.2]),
///             Euclid([0.3, 0.8]),
///             Euclid([0.0, 1.0])];
///
/// let k = 3;
///
/// // we want the means extra precise.
/// let tol = 1e-12;
/// let kmeans = KmeansBuilder::new().tolerance(tol).kmeans(&data, k);
///
/// println!("{:?}", kmeans.clusters());
/// ```
pub struct KmeansBuilder {
    tol: f64,
    max_iter: usize,
}

impl KmeansBuilder {
    /// Create a default `KmeansBuilder`
    pub fn new() -> KmeansBuilder {
        KmeansBuilder {
            tol: DEFAULT_TOL,
            max_iter: DEFAULT_MAX_ITER,
        }
    }

    /// Set the tolerance used to decide if the iteration has
    /// converged to `tol`.
    pub fn tolerance(self, tol: f64) -> KmeansBuilder {
        KmeansBuilder { tol: tol, .. self }
    }
    /// Set the maximum number of iterations to run before aborting to
    /// `max_iter`.
    pub fn max_iter(self, max_iter: usize) -> KmeansBuilder {
        KmeansBuilder { max_iter: max_iter, .. self }
    }

    /// Run *k*-means with the given settings.
    ///
    /// This is functionally identical to `Kmeans::new`, other than
    /// the internal parameters differing.
    pub fn kmeans<T>(self, data: &[Euclid<T>], k: usize) -> Kmeans<T>
        where Euclid<T>: Point + Euclidean + Clone
    {
        assert!(2 <= k && k < data.len());

        let n = data.len();
        let mut assignments = vec![!0; n];
        let mut costs = vec![0.0; n];
        let mut centres = data.iter().take(k).cloned().collect::<Vec<_>>();
        let mut counts = vec![0; k];
        update_assignments(data, &mut assignments, &mut counts, &mut costs, &centres);
        let mut objective = costs.iter().fold(0.0, |a, b| a + *b);

        let mut converged = false;
        let mut iter = 0;
        while iter < self.max_iter {
            update_centres(data, &assignments, &counts, &mut centres);
            update_assignments(data, &mut assignments, &mut counts, &mut costs, &centres);

            let new_objective = costs.iter().fold(0.0, |a, b| a + *b);

            if (new_objective - objective).abs() < self.tol {
                converged = true;
                break
            }

            objective = new_objective;
            iter += 1
        }

        Kmeans {
            assignments: assignments,
            centres: centres,
            iterations: iter,
            converged: converged,
        }
    }
}

fn update_assignments<T>(data: &[Euclid<T>],
                         assignments: &mut [usize], counts: &mut [usize], costs: &mut [f64],
                         centres: &[Euclid<T>])
    where Euclid<T>: Point + Euclidean + Clone
{
    use std::f64::INFINITY as INF;

    for place in counts.iter_mut() { *place = 0 }

    for ((point, assign), cost) in data.iter().zip(assignments.iter_mut()).zip(costs.iter_mut()) {
        let mut min_dist = INF;
        let mut index = 0;
        for (i, c) in centres.iter().enumerate() {
            let dist = point.dist2(c);
            if dist < min_dist {
                min_dist = dist;
                index = i;
            }
        }

        *cost = min_dist;
        *assign = index;
        counts[index] += 1;
    }
}

fn update_centres<T>(data: &[Euclid<T>],
                     assignments: &[usize], counts: &[usize],
                     centres: &mut [Euclid<T>])
    where Euclid<T>: Point + Euclidean + Clone
{
    for place in centres.iter_mut() { *place = <Euclid<T>>::zero() }

    for (point, assign) in data.iter().zip(assignments.iter()) {
        centres[*assign].add(point)
    }
    for (place, scale) in centres.iter_mut().zip(counts.iter()) {
        place.scale(1.0 / *scale as f64)
    }
}

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

    #[test]
    fn smoke() {
        let points = [Euclid([0.0, 0.0]),
                      Euclid([1.0, 0.5]),
                      Euclid([0.2, 0.2]),
                      Euclid([0.3, 0.8]),
                      Euclid([0.0, 1.0]),
                      ];

        let res = Kmeans::new(&points, 3);
        let mut clusters = res.clusters();
        for &mut (_, ref mut v) in &mut clusters {
            v.sort()
        }
        clusters.sort_by(|a, b| a.1.cmp(&b.1));
        assert_eq!(clusters,
                   [(Euclid([0.1, 0.1]), vec![0, 2]),
                    (Euclid([1.0, 0.5]), vec![1]),
                    (Euclid([0.15, 0.9]), vec![3, 4])]);

        assert_eq!(res.converged(), Ok(2));
    }
}
<commit_msg>kmeans: fix missed sqrt.<commit_after>use {Euclid, Point};
use point::Euclidean;

/// Clustering via the *k*-means algorithm (aka Lloyd's algorithm).
///
/// > *k*-means clustering aims to partition *n* observations into *k*
/// clusters in which each observation belongs to the cluster with the
/// nearest mean, serving as a prototype of the cluster.wikipedia
///
/// This is a heuristic, iterative approximation to the true optimal
/// assignment. The parameters used to control the approximation can
/// be set via `KmeansBuilder`.
///
/// # Examples
///
/// ```rust
/// use cogset::{Euclid, Kmeans};
///
/// let data = [Euclid([0.0, 0.0]),
///             Euclid([1.0, 0.5]),
///             Euclid([0.2, 0.2]),
///             Euclid([0.3, 0.8]),
///             Euclid([0.0, 1.0])];
/// let k = 3;
///
/// let kmeans = Kmeans::new(&data, k);
///
/// println!("{:?}", kmeans.clusters());
/// ```

pub struct Kmeans<T> {
    assignments: Vec<usize>,
    centres: Vec<Euclid<T>>,
    iterations: usize,
    converged: bool,
}

impl<T> Kmeans<T>
    where Euclid<T>: Point + Euclidean + Clone
{
    /// Run k-means on `data` with the default settings.
    pub fn new(data: &[Euclid<T>], k: usize) -> Kmeans<T> {
        KmeansBuilder::new().kmeans(data, k)
    }

    /// Retrieve the means and the clusters themselves that this
    /// *k*-means instance computed.
    ///
    /// The clusters are represented by vectors of indexes into the
    /// original data.
    pub fn clusters(&self) -> Vec<(Euclid<T>, Vec<usize>)> {
        let mut ret = self.centres.iter().cloned().map(|c| (c, vec![])).collect::<Vec<_>>();

        for (idx, &assign) in self.assignments.iter().enumerate() {
            ret[assign].1.push(idx);
        }

        ret
    }

    /// Return whether the algorithm converged, and how many steps
    /// that took.
    ///
    /// `Ok` is returned if the algorithm did meet the tolerance
    /// criterion, and `Err` if it reached the iteration limit
    /// instead.
    pub fn converged(&self) -> Result<usize, usize> {
        if self.converged {
            Ok(self.iterations)
        } else {
            Err(self.iterations)
        }
    }
}

const DEFAULT_MAX_ITER: usize = 100;
const DEFAULT_TOL: f64 = 1e-6;

/// A builder for *k*-means to provide control over parameters for the
/// algorithm.
///
/// This allows one to tweak settings like the tolerance and the
/// number of iterations.
///
/// # Examples
///
/// ```rust
/// use cogset::{Euclid, KmeansBuilder};
///
/// let data = [Euclid([0.0, 0.0]),
///             Euclid([1.0, 0.5]),
///             Euclid([0.2, 0.2]),
///             Euclid([0.3, 0.8]),
///             Euclid([0.0, 1.0])];
///
/// let k = 3;
///
/// // we want the means extra precise.
/// let tol = 1e-12;
/// let kmeans = KmeansBuilder::new().tolerance(tol).kmeans(&data, k);
///
/// println!("{:?}", kmeans.clusters());
/// ```
pub struct KmeansBuilder {
    tol: f64,
    max_iter: usize,
}

impl KmeansBuilder {
    /// Create a default `KmeansBuilder`
    pub fn new() -> KmeansBuilder {
        KmeansBuilder {
            tol: DEFAULT_TOL,
            max_iter: DEFAULT_MAX_ITER,
        }
    }

    /// Set the tolerance used to decide if the iteration has
    /// converged to `tol`.
    pub fn tolerance(self, tol: f64) -> KmeansBuilder {
        KmeansBuilder { tol: tol, .. self }
    }
    /// Set the maximum number of iterations to run before aborting to
    /// `max_iter`.
    pub fn max_iter(self, max_iter: usize) -> KmeansBuilder {
        KmeansBuilder { max_iter: max_iter, .. self }
    }

    /// Run *k*-means with the given settings.
    ///
    /// This is functionally identical to `Kmeans::new`, other than
    /// the internal parameters differing.
    pub fn kmeans<T>(self, data: &[Euclid<T>], k: usize) -> Kmeans<T>
        where Euclid<T>: Point + Euclidean + Clone
    {
        assert!(2 <= k && k < data.len());

        let n = data.len();
        let mut assignments = vec![!0; n];
        let mut costs = vec![0.0; n];
        let mut centres = data.iter().take(k).cloned().collect::<Vec<_>>();
        let mut counts = vec![0; k];
        update_assignments(data, &mut assignments, &mut counts, &mut costs, &centres);
        let mut objective = costs.iter().fold(0.0, |a, b| a + *b);

        let mut converged = false;
        let mut iter = 0;
        while iter < self.max_iter {
            update_centres(data, &assignments, &counts, &mut centres);
            update_assignments(data, &mut assignments, &mut counts, &mut costs, &centres);

            let new_objective = costs.iter().fold(0.0, |a, b| a + *b);

            if (new_objective - objective).abs() < self.tol {
                converged = true;
                break
            }

            objective = new_objective;
            iter += 1
        }

        Kmeans {
            assignments: assignments,
            centres: centres,
            iterations: iter,
            converged: converged,
        }
    }
}

fn update_assignments<T>(data: &[Euclid<T>],
                         assignments: &mut [usize], counts: &mut [usize], costs: &mut [f64],
                         centres: &[Euclid<T>])
    where Euclid<T>: Point + Euclidean + Clone
{
    use std::f64::INFINITY as INF;

    for place in counts.iter_mut() { *place = 0 }

    for ((point, assign), cost) in data.iter().zip(assignments.iter_mut()).zip(costs.iter_mut()) {
        let mut min_dist = INF;
        let mut index = 0;
        for (i, c) in centres.iter().enumerate() {
            let dist = point.dist(c);
            if dist < min_dist {
                min_dist = dist;
                index = i;
            }
        }

        *cost = min_dist;
        *assign = index;
        counts[index] += 1;
    }
}

fn update_centres<T>(data: &[Euclid<T>],
                     assignments: &[usize], counts: &[usize],
                     centres: &mut [Euclid<T>])
    where Euclid<T>: Point + Euclidean + Clone
{
    for place in centres.iter_mut() { *place = <Euclid<T>>::zero() }

    for (point, assign) in data.iter().zip(assignments.iter()) {
        centres[*assign].add(point)
    }
    for (place, scale) in centres.iter_mut().zip(counts.iter()) {
        place.scale(1.0 / *scale as f64)
    }
}

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

    #[test]
    fn smoke() {
        let points = [Euclid([0.0, 0.0]),
                      Euclid([1.0, 0.5]),
                      Euclid([0.2, 0.2]),
                      Euclid([0.3, 0.8]),
                      Euclid([0.0, 1.0]),
                      ];

        let res = Kmeans::new(&points, 3);
        let mut clusters = res.clusters();
        for &mut (_, ref mut v) in &mut clusters {
            v.sort()
        }
        clusters.sort_by(|a, b| a.1.cmp(&b.1));
        assert_eq!(clusters,
                   [(Euclid([0.1, 0.1]), vec![0, 2]),
                    (Euclid([1.0, 0.5]), vec![1]),
                    (Euclid([0.15, 0.9]), vec![3, 4])]);

        assert_eq!(res.converged(), Ok(2));
    }
}
<|endoftext|>


<commit_before>use oak_runtime::parse_state::*;
use oak_runtime::parse_state::ParseResult::*;

use std::io::prelude::*;
use std::fs::File;
use std::io::BufReader;

use ast::Stmt;

grammar! bailey {
    use ast::Ident;
    use ast::Literal;
    use ast::Expr;
    use ast::Stmt;
    use ast::BinOp;
    use ast::Decl;
    use ast::Block;
    use ast::BinOp::*;
    use std::str::FromStr;

    program
        = newlines (stmt terminator)*

    stmt
        = const_assign_stmt
        / assign_stmt
        / if_stmt
        / while_stmt
        / decl > decl_to_stmt
        / expr > expr_to_stmt

    assign_stmt
        = var bind_op expr > assign_stmt

    const_assign_stmt
        = constant bind_op expr > const_assign_stmt

    if_stmt
        = if_kw expr block (else_kw block)? > if_stmt

    while_stmt
        = while_kw expr block > while_stmt

    decl
        = class_decl
        / method_decl

    class_decl
        = class_kw ident class_body > class_decl

    class_body
        = lbracket (newlines method_decl)* rbracket

    method_decl
        = def_kw ident lparen param_list rparen block > method_decl

    block
        = newlines lbracket stmt_list rbracket spacing > block

    stmt_list
        = empty_stmt_list
        / stmt (terminator stmt)* > stmt_list

    function_call
        = newlines ident lparen arg_list rparen

    method_call
        = callable_expr (dot function_call)+

    expr
        = newlines cond_expr

    cond_expr
        = add_expr (cond_expr_op add_expr)* > fold_left

    add_expr
        = mult_expr (add_expr_op mult_expr)* > fold_left

    mult_expr
        = primary_expr (mult_expr_op primary_expr)* > fold_left

    primary_expr
        = method_call > method_call
        / callable_expr

    callable_expr
        = function_call > function_call
        / literal > lit_to_exp
        / constant
        / var
        / array
        / map
        / lparen expr rparen

    arg_list
        = empty_list
        / expr (comma expr)* > arg_list

    param_list
        = empty_param_list
        / ident (comma ident)* > param_list

    empty_list = &rparen > empty_list
    empty_param_list = &rparen > empty_param_list
    empty_stmt_list = &rbracket > empty_stmt_list

    digit = ["0-9"]
    ident_char = ["A-Za-z0-9_"]

    var = !digit !keyword ident_char+ spacing > var
    ident = !digit !keyword ident_char+ spacing > ident
    constant = !keyword !digit ["A-Z"]+ ident_char+ spacing > constant

    literal
        = string_lit
        / float_lit
        / int_lit

    int_lit = digit+ spacing > int_lit
    float_lit = digit+ dot digit+ spacing > float_lit
    string_lit = (dbl_quot_string_lit / sng_quot_string_lit) spacing > string_lit

    array_elem = expr > array_elem
    array = lsqbracket array_elem (comma array_elem)* rsqbracket > array

    map_entry =  (expr colon expr) > map_entry
    map = lbracket map_entry (comma map_entry)* rbracket > map

    dbl_quot_string_lit = dbl_quot (spacing_char / !dbl_quot .)* dbl_quot
    sng_quot_string_lit = sng_quot (spacing_char / !sng_quot .)* sng_quot

    spacing_char = [" \r\t"]
    spacing = spacing_char* -> ()
    newline = "\n" spacing -> ()
    semicolon = ";" spacing -> ()
    newlines = newline* spacing -> ()
    terminator = (newline / semicolon) newlines -> ()

    class_kw = "class" spacing
    def_kw = "def" spacing
    else_kw = "else" spacing
    false_kw = "false" spacing
    if_kw = "if" spacing
    nil_kw = "nil" spacing
    true_kw = "true" spacing
    while_kw = "while" spacing

    keyword
        = def_kw
        / if_kw
        / else_kw
        / class_kw
        / true_kw
        / false_kw
        / nil_kw

    add_expr_op
        = add_op > add_bin_op
        / sub_op > sub_bin_op

    mult_expr_op
        = mul_op > mul_bin_op
        / div_op > div_bin_op

    cond_expr_op
        = lt_op > lt_bin_op /
        lte_op > lte_bin_op /
        gt_op > gt_bin_op /
        gte_op > gte_bin_op /
        ne_op > ne_bin_op /
        eq_op > eq_bin_op /
        andand_op > and_bin_op /
        oror_op > or_bin_op

    bind_op = "=" spacing
    add_op = "+" spacing
    sub_op = "-" spacing
    mul_op = "*" spacing
    div_op = "/" spacing
    lt_op = "<" spacing
    lte_op = "<=" spacing
    gt_op = ">" spacing
    gte_op = ">=" spacing
    ne_op = "!=" spacing
    eq_op = "==" spacing
    andand_op  = "&&" spacing
    oror_op = "||" spacing

    lparen = "(" newlines -> ()
    rparen = newlines ")" -> ()
    lbracket = "{" newlines -> ()
    rbracket = newlines "}" -> ()
    lsqbracket = "[" newlines
    rsqbracket = newlines "]"
    dbl_quot = "\"" spacing
    sng_quot = "'" spacing
    dot = "." spacing
    comma = "," spacing
    colon = ":" spacing

    fn decl_to_stmt(decl: Decl) -> Stmt {
        Stmt::Decl(Box::new(decl))
    }

    fn expr_to_stmt(expr: Expr) -> Stmt {
        Stmt::Expr(Box::new(expr))
    }

    fn lit_to_exp(literal: Literal) -> Expr {
        Expr::Literal(Box::new(literal))
    }

    fn int_lit(raw_text: Vec<char>) -> Literal {
        Literal::Integer(to_number(raw_text))
    }

    fn float_lit(mut integer: Vec<char>, fractional: Vec<char>) -> Literal {
        integer.push('.');
        Literal::Float(to_float(combine(integer, fractional)))
    }

    fn string_lit(raw_text: Vec<char>) -> Literal {
        Literal::Str(to_string(raw_text))
    }

    fn array(first: Box<Expr>, rest: Vec<Box<Expr>>) -> Expr {
        Expr::Array(combine_one_with_many(first, rest))
    }

    fn array_elem(expr: Expr) -> Box<Expr> {
        Box::new(expr)
    }

    fn map(first: (Box<Expr>, Box<Expr>), rest: Vec<(Box<Expr>, Box<Expr>)>) -> Expr {
        Expr::Map(combine_one_with_many(first, rest))
    }

    fn map_entry(key: Expr, val: Expr) -> (Box<Expr>, Box<Expr>) {
        (Box::new(key), Box::new(val))
    }

    fn constant(first: Vec<char>, rest: Vec<char>) -> Expr {
        Expr::Const(Ident(to_string(combine(first, rest))))
    }

    fn ident(raw_text: Vec<char>) -> Ident {
        Ident(to_string(raw_text))
    }

    fn var(raw_text: Vec<char>) -> Expr {
        Expr::Var(Ident(to_string(raw_text)))
    }

    fn add_bin_op() -> BinOp { Add }
    fn sub_bin_op() -> BinOp { Sub }
    fn mul_bin_op() -> BinOp { Mul }
    fn div_bin_op() -> BinOp { Div }
    fn lt_bin_op() -> BinOp { Lt }
    fn lte_bin_op() -> BinOp { Lte }
    fn gt_bin_op() -> BinOp { Gt }
    fn gte_bin_op() -> BinOp { Gte }
    fn ne_bin_op() -> BinOp { Ne }
    fn eq_bin_op() -> BinOp { Eq }
    fn and_bin_op() -> BinOp { And }
    fn or_bin_op() -> BinOp { Or }

    fn function_call(func: Ident, args: Vec<Expr>) -> Expr {
        Expr::Message(None, func, args)
    }

    fn method_call(recv: Expr, call: Vec<(Ident, Vec<Expr>)>) -> Expr {
        call.into_iter().fold(recv, { |accu, (method, args)|
            Expr::Message(Some(Box::new(accu)), method, args)
        })
    }

    fn assign_stmt(var: Expr, expr: Expr) -> Stmt {
        Stmt::VarAssign(Box::new(var), Box::new(expr))
    }

    fn const_assign_stmt(constant: Expr, expr: Expr) -> Stmt {
        Stmt::ConstAssign(Box::new(constant), Box::new(expr))
    }

    fn class_decl(class: Ident, methods: Vec<Decl>) -> Decl {
        Decl::Class(class, methods)
    }

    fn method_decl(meth: Ident, params: Vec<Ident>, block: Block) -> Decl {
        Decl::Method(meth, params, Box::new(block))
    }

    fn block(stmts: Vec<Stmt>) -> Block {
        Block { stmts: stmts }
    }

    fn program(first: Stmt, mut rest: Vec<Stmt>) -> Vec<Stmt> {
        combine_one_with_many(first, rest)
    }

    fn if_stmt(cond: Expr, true_blk: Block, false_blk: Option<Block>) -> Stmt {
        let false_blk = match false_blk {
            Some(blk) => Some(Box::new(blk)),
            None => None
        };

        Stmt::If(Box::new(cond), Box::new(true_blk), false_blk)
    }

    fn while_stmt(cond: Expr, blk: Block) -> Stmt {
        Stmt::While(Box::new(cond), Box::new(blk))
    }

    fn arg_list(first: Expr, mut rest: Vec<Expr>) -> Vec<Expr> {
        combine_one_with_many(first, rest)
    }

    fn param_list(first: Ident, mut rest: Vec<Ident>) -> Vec<Ident> {
        combine_one_with_many(first, rest)
    }

    fn empty_param_list() -> Vec<Ident> {
        vec![]
    }

    fn stmt_list(first: Stmt, mut rest: Vec<Stmt>) -> Vec<Stmt> {
        combine_one_with_many(first, rest)
    }

    fn empty_list() -> Vec<Expr> {
        vec![]
    }

    fn empty_stmt_list() -> Vec<Stmt> {
        vec![]
    }

    fn to_string(raw_text: Vec<char>) -> String {
        raw_text.into_iter().collect()
    }

    fn to_number(raw_text: Vec<char>) -> i64 {
        i64::from_str(&*to_string(raw_text)).unwrap()
    }

    fn to_float(raw_text: Vec<char>) -> f64 {
        f64::from_str(&*to_string(raw_text)).unwrap()
    }

    fn fold_left(head: Expr, rest: Vec<(BinOp, Expr)>) -> Expr {
        rest.into_iter().fold(head,
          |accu, (op, expr)| Expr::Binary(op, Box::new(accu), Box::new(expr)))
    }

    fn combine<T: Clone>(mut left: Vec<T>, right: Vec<T>) -> Vec<T> {
        left.extend(right.into_iter());
        left
    }

    fn combine_one_with_many<T: Clone>(first: T, rest: Vec<T>) -> Vec<T> {
        combine(vec![first], rest)
    }
}

pub struct Parser {
    pub ast: Option<Vec<Stmt>>,
}

impl Parser {
    pub fn new() -> Parser {
        Parser { ast: None }
    }

    pub fn parse_file(&mut self, file_path: String) -> Result<Vec<Stmt>, String> {
        match File::open(file_path) {
            Ok(file) => {
                let mut reader = BufReader::new(file);
                let mut code = String::new();
                let _ = reader.read_to_string(&mut code);
                self.parse(code)
            }
            Err(error) => Err(format!("Error: {}", error)),
        }
    }

    pub fn parse(&mut self, code: String) -> Result<Vec<Stmt>, String> {
        let state = bailey::parse_program(code.into_state());

        match state.into_result() {
            Success(data) => Ok(data.clone()),
            Partial(data, expectation) => {
                Err(format!("Partial match: {:?} because: {:?}", data, expectation))
            }
            Failure(expectation) => {
                Err(format!("Error: {:?}", expectation))
            }
        }
    }
}

<commit_msg>Reworked Constant and Var expressions.<commit_after>use oak_runtime::parse_state::*;
use oak_runtime::parse_state::ParseResult::*;

use std::io::prelude::*;
use std::fs::File;
use std::io::BufReader;

use ast::Stmt;

grammar! bailey {
    use ast::Ident;
    use ast::Literal;
    use ast::Expr;
    use ast::Stmt;
    use ast::BinOp;
    use ast::Decl;
    use ast::Block;
    use ast::BinOp::*;
    use std::str::FromStr;

    program
        = newlines (stmt terminator)*

    stmt
        = const_assign_stmt
        / assign_stmt
        / if_stmt
        / while_stmt
        / decl > decl_to_stmt
        / expr > expr_to_stmt

    assign_stmt
        = var bind_op expr > assign_stmt

    const_assign_stmt
        = constant bind_op expr > const_assign_stmt

    if_stmt
        = if_kw expr block (else_kw block)? > if_stmt

    while_stmt
        = while_kw expr block > while_stmt

    decl
        = class_decl
        / method_decl

    class_decl
        = class_kw ident class_body > class_decl

    class_body
        = lbracket (newlines method_decl)* rbracket

    method_decl
        = def_kw ident lparen param_list rparen block > method_decl

    block
        = newlines lbracket stmt_list rbracket spacing > block

    stmt_list
        = empty_stmt_list
        / stmt (terminator stmt)* > stmt_list

    function_call
        = newlines ident lparen arg_list rparen

    method_call
        = callable_expr (dot function_call)+

    expr
        = newlines cond_expr

    cond_expr
        = add_expr (cond_expr_op add_expr)* > fold_left

    add_expr
        = mult_expr (add_expr_op mult_expr)* > fold_left

    mult_expr
        = primary_expr (mult_expr_op primary_expr)* > fold_left

    primary_expr
        = method_call > method_call
        / callable_expr

    callable_expr
        = function_call > function_call
        / literal > lit_to_exp
        / constant
        / var
        / array
        / map
        / lparen expr rparen

    arg_list
        = empty_list
        / expr (comma expr)* > arg_list

    param_list
        = empty_param_list
        / ident (comma ident)* > param_list

    empty_list = &rparen > empty_list
    empty_param_list = &rparen > empty_param_list
    empty_stmt_list = &rbracket > empty_stmt_list

    digit = ["0-9"]
    upcase_ltr = ["A-Z"]
    lowcase_ltr = ["a-z_"]
    ident_char = (upcase_ltr / lowcase_ltr / digit)

    var = !digit !keyword &lowcase_ltr ident_char+ spacing > var
    ident = !digit !keyword ident_char+ spacing > ident
    constant = !keyword !digit &upcase_ltr ident_char+ spacing > constant

    literal
        = string_lit
        / float_lit
        / int_lit

    int_lit = digit+ spacing > int_lit
    float_lit = digit+ dot digit+ spacing > float_lit
    string_lit = (dbl_quot_string_lit / sng_quot_string_lit) spacing > string_lit

    array_elem = expr > array_elem
    array = lsqbracket array_elem (comma array_elem)* rsqbracket > array

    map_entry =  (expr colon expr) > map_entry
    map = lbracket map_entry (comma map_entry)* rbracket > map

    dbl_quot_string_lit = dbl_quot (spacing_char / !dbl_quot .)* dbl_quot
    sng_quot_string_lit = sng_quot (spacing_char / !sng_quot .)* sng_quot

    spacing_char = [" \r\t"]
    spacing = spacing_char* -> ()
    newline = "\n" spacing -> ()
    semicolon = ";" spacing -> ()
    newlines = newline* spacing -> ()
    terminator = (newline / semicolon) newlines -> ()

    class_kw = "class" spacing
    def_kw = "def" spacing
    else_kw = "else" spacing
    false_kw = "false" spacing
    if_kw = "if" spacing
    nil_kw = "nil" spacing
    true_kw = "true" spacing
    while_kw = "while" spacing

    keyword
        = def_kw
        / if_kw
        / else_kw
        / class_kw
        / true_kw
        / false_kw
        / nil_kw

    add_expr_op
        = add_op > add_bin_op
        / sub_op > sub_bin_op

    mult_expr_op
        = mul_op > mul_bin_op
        / div_op > div_bin_op

    cond_expr_op
        = lt_op > lt_bin_op /
        lte_op > lte_bin_op /
        gt_op > gt_bin_op /
        gte_op > gte_bin_op /
        ne_op > ne_bin_op /
        eq_op > eq_bin_op /
        andand_op > and_bin_op /
        oror_op > or_bin_op

    bind_op = "=" spacing
    add_op = "+" spacing
    sub_op = "-" spacing
    mul_op = "*" spacing
    div_op = "/" spacing
    lt_op = "<" spacing
    lte_op = "<=" spacing
    gt_op = ">" spacing
    gte_op = ">=" spacing
    ne_op = "!=" spacing
    eq_op = "==" spacing
    andand_op  = "&&" spacing
    oror_op = "||" spacing

    lparen = "(" newlines -> ()
    rparen = newlines ")" -> ()
    lbracket = "{" newlines -> ()
    rbracket = newlines "}" -> ()
    lsqbracket = "[" newlines
    rsqbracket = newlines "]"
    dbl_quot = "\"" spacing
    sng_quot = "'" spacing
    dot = "." spacing
    comma = "," spacing
    colon = ":" spacing

    fn decl_to_stmt(decl: Decl) -> Stmt {
        Stmt::Decl(Box::new(decl))
    }

    fn expr_to_stmt(expr: Expr) -> Stmt {
        Stmt::Expr(Box::new(expr))
    }

    fn lit_to_exp(literal: Literal) -> Expr {
        Expr::Literal(Box::new(literal))
    }

    fn int_lit(raw_text: Vec<char>) -> Literal {
        Literal::Integer(to_number(raw_text))
    }

    fn float_lit(mut integer: Vec<char>, fractional: Vec<char>) -> Literal {
        integer.push('.');
        Literal::Float(to_float(combine(integer, fractional)))
    }

    fn string_lit(raw_text: Vec<char>) -> Literal {
        Literal::Str(to_string(raw_text))
    }

    fn array(first: Box<Expr>, rest: Vec<Box<Expr>>) -> Expr {
        Expr::Array(combine_one_with_many(first, rest))
    }

    fn array_elem(expr: Expr) -> Box<Expr> {
        Box::new(expr)
    }

    fn map(first: (Box<Expr>, Box<Expr>), rest: Vec<(Box<Expr>, Box<Expr>)>) -> Expr {
        Expr::Map(combine_one_with_many(first, rest))
    }

    fn map_entry(key: Expr, val: Expr) -> (Box<Expr>, Box<Expr>) {
        (Box::new(key), Box::new(val))
    }

    fn constant(raw_text: Vec<char>) -> Expr {
        Expr::Const(Ident(to_string(raw_text)))
    }

    fn ident(raw_text: Vec<char>) -> Ident {
        Ident(to_string(raw_text))
    }

    fn var(raw_text: Vec<char>) -> Expr {
        Expr::Var(Ident(to_string(raw_text)))
    }

    fn add_bin_op() -> BinOp { Add }
    fn sub_bin_op() -> BinOp { Sub }
    fn mul_bin_op() -> BinOp { Mul }
    fn div_bin_op() -> BinOp { Div }
    fn lt_bin_op() -> BinOp { Lt }
    fn lte_bin_op() -> BinOp { Lte }
    fn gt_bin_op() -> BinOp { Gt }
    fn gte_bin_op() -> BinOp { Gte }
    fn ne_bin_op() -> BinOp { Ne }
    fn eq_bin_op() -> BinOp { Eq }
    fn and_bin_op() -> BinOp { And }
    fn or_bin_op() -> BinOp { Or }

    fn function_call(func: Ident, args: Vec<Expr>) -> Expr {
        Expr::Message(None, func, args)
    }

    fn method_call(recv: Expr, call: Vec<(Ident, Vec<Expr>)>) -> Expr {
        call.into_iter().fold(recv, { |accu, (method, args)|
            Expr::Message(Some(Box::new(accu)), method, args)
        })
    }

    fn assign_stmt(var: Expr, expr: Expr) -> Stmt {
        Stmt::VarAssign(Box::new(var), Box::new(expr))
    }

    fn const_assign_stmt(constant: Expr, expr: Expr) -> Stmt {
        Stmt::ConstAssign(Box::new(constant), Box::new(expr))
    }

    fn class_decl(class: Ident, methods: Vec<Decl>) -> Decl {
        Decl::Class(class, methods)
    }

    fn method_decl(meth: Ident, params: Vec<Ident>, block: Block) -> Decl {
        Decl::Method(meth, params, Box::new(block))
    }

    fn block(stmts: Vec<Stmt>) -> Block {
        Block { stmts: stmts }
    }

    fn program(first: Stmt, mut rest: Vec<Stmt>) -> Vec<Stmt> {
        combine_one_with_many(first, rest)
    }

    fn if_stmt(cond: Expr, true_blk: Block, false_blk: Option<Block>) -> Stmt {
        let false_blk = match false_blk {
            Some(blk) => Some(Box::new(blk)),
            None => None
        };

        Stmt::If(Box::new(cond), Box::new(true_blk), false_blk)
    }

    fn while_stmt(cond: Expr, blk: Block) -> Stmt {
        Stmt::While(Box::new(cond), Box::new(blk))
    }

    fn arg_list(first: Expr, mut rest: Vec<Expr>) -> Vec<Expr> {
        combine_one_with_many(first, rest)
    }

    fn param_list(first: Ident, mut rest: Vec<Ident>) -> Vec<Ident> {
        combine_one_with_many(first, rest)
    }

    fn empty_param_list() -> Vec<Ident> {
        vec![]
    }

    fn stmt_list(first: Stmt, mut rest: Vec<Stmt>) -> Vec<Stmt> {
        combine_one_with_many(first, rest)
    }

    fn empty_list() -> Vec<Expr> {
        vec![]
    }

    fn empty_stmt_list() -> Vec<Stmt> {
        vec![]
    }

    fn to_string(raw_text: Vec<char>) -> String {
        raw_text.into_iter().collect()
    }

    fn to_number(raw_text: Vec<char>) -> i64 {
        i64::from_str(&*to_string(raw_text)).unwrap()
    }

    fn to_float(raw_text: Vec<char>) -> f64 {
        f64::from_str(&*to_string(raw_text)).unwrap()
    }

    fn fold_left(head: Expr, rest: Vec<(BinOp, Expr)>) -> Expr {
        rest.into_iter().fold(head,
          |accu, (op, expr)| Expr::Binary(op, Box::new(accu), Box::new(expr)))
    }

    fn combine<T: Clone>(mut left: Vec<T>, right: Vec<T>) -> Vec<T> {
        left.extend(right.into_iter());
        left
    }

    fn combine_one_with_many<T: Clone>(first: T, rest: Vec<T>) -> Vec<T> {
        combine(vec![first], rest)
    }
}

pub struct Parser {
    pub ast: Option<Vec<Stmt>>,
}

impl Parser {
    pub fn new() -> Parser {
        Parser { ast: None }
    }

    pub fn parse_file(&mut self, file_path: String) -> Result<Vec<Stmt>, String> {
        match File::open(file_path) {
            Ok(file) => {
                let mut reader = BufReader::new(file);
                let mut code = String::new();
                let _ = reader.read_to_string(&mut code);
                self.parse(code)
            }
            Err(error) => Err(format!("Error: {}", error)),
        }
    }

    pub fn parse(&mut self, code: String) -> Result<Vec<Stmt>, String> {
        let state = bailey::parse_program(code.into_state());

        match state.into_result() {
            Success(data) => Ok(data.clone()),
            Partial(data, expectation) => {
                Err(format!("Partial match: {:?} because: {:?}", data, expectation))
            }
            Failure(expectation) => {
                Err(format!("Error: {:?}", expectation))
            }
        }
    }
}

<|endoftext|>


<commit_before>use chrono::DateTime;
use chrono::prelude::{Local, Utc};
use errors::*;
use nom::{alphanumeric, ErrorKind, IResult};
use output::Output;
use std::str;

#[derive(Debug, PartialEq, Eq)]
struct Program {
    name: MacroOp,
    steps: Vec<Step>,
}

#[derive(Debug, PartialEq, Eq)]
struct Step {
    op: MacroOp,
    args: Vec<String>
}

#[derive(Debug, PartialEq, Eq)]
enum MacroOp {
    Add,
    Divide,
    Multiply,
    Name(String),
    Roll,
    Say,
    Subtract,
    Whisper,
}

#[derive(Debug, PartialEq, Eq)]
enum RollOp {
    Advantage,
    Comment,
    Disadvantage,
    NdD,
}

#[derive(Debug, PartialEq, Eq)]
enum SayOp {
    Message,
    To,
}

named!(arguments <&[u8], String>, alt!(
    string |
    quoted |
    single_quoted
));

/// Matches a command
named!(command <&[u8], MacroOp>, alt!(
    map!(ws!(tag!("!roll")),     |_| MacroOp::Roll)      |
    map!(ws!(tag!("!r")),        |_| MacroOp::Roll)      |
    map!(ws!(tag!("!say")),      |_| MacroOp::Say)       |
    map!(ws!(tag!("!s")),        |_| MacroOp::Say)       |
    map!(ws!(tag!("!whisper")),  |_| MacroOp::Whisper)   |
    map!(ws!(tag!("!w")),        |_| MacroOp::Whisper)
));

/// Matches a macro name
named!(name <&[u8], MacroOp>, ws!(do_parse!(
    tag!("#") >>
    name: map_res!(is_not!(" \t\r\n"), |r: &[u8]| String::from_utf8(r.to_vec())) >>
    (MacroOp::Name(name))
)));

/// Matches any type of operation
named!(op <&[u8], MacroOp>, alt!(
    name |
    command |
    ws!(primitive)
));

/// Matches primitive operations
named!(primitive <&[u8], MacroOp>, alt!(
    map!(tag!("*"), |_| MacroOp::Multiply)  |
    map!(tag!("+"), |_| MacroOp::Add)       |
    map!(tag!("-"), |_| MacroOp::Subtract)  |
    map!(tag!("/"), |_| MacroOp::Divide)
));

/// Parse the complete macro
named!(parse <&[u8], Program>, do_parse!(
    prog_name: name >>
    steps: many0!(parse_step) >>
    (Program {
        name: prog_name,
        steps: steps,
    })
));

/// Parse a step of the program
named!(parse_step <&[u8], Step>, do_parse!(
    op_type: op >>
    args: ws!(many0!(arguments)) >>
    (Step {
        op: op_type,
        args,
    })
));

/// Matches arguments in quotes ("")
named!(quoted <&[u8], String>, do_parse!(
    word: ws!(delimited!(tag!("\""),take_until!("\""), tag!("\""))) >>
    (String::from_utf8(word.to_vec()).unwrap())
));

/// Matches arguments in quotes ('')
named!(single_quoted <&[u8], String>, do_parse!(
    word: ws!(delimited!(tag!("'"),take_until!("'"), tag!("'"))) >>
    (String::from_utf8(word.to_vec()).unwrap())
));

/// Match alphanumeric values to strings
named!(string <&[u8], String>, do_parse!(
    word: ws!(alphanumeric) >>
    (String::from_utf8(word.to_vec()).unwrap())
));

#[test]
fn test_simple_parser() {
    let program = Program {
        name: MacroOp::Name(String::from("macro-name")),
        steps: vec![Step {
            op: MacroOp::Roll,
            args: vec![ "1d20".to_string() ],
        }],
    };
    let (_, result) = parse(b"#macro-name !roll 1d20").unwrap();
    assert_eq!(result, program);

    let program = Program {
        name: MacroOp::Name(String::from("macro-name-2")),
        steps: vec![Step {
            op: MacroOp::Say,
            args: vec![ "Hello, world!".to_string() ],
        }],
    };
    let (_, result) = parse(b"#macro-name-2 !say \"Hello, world!\"").unwrap();
    assert_eq!(result, program);
}

#[test]
fn test_complex_parser() {
    let program = Program {
        name: MacroOp::Name(String::from("macro-name")),
        steps: vec![
            Step {
                op: MacroOp::Roll,
                args: vec![ "1d20".to_string() ],
            },
            Step {
                op: MacroOp::Say,
                args: vec![ "Smite!".to_string() ],
            },
        ],
    };
    let (_, result) = parse(b"#macro-name !roll 1d20 !say \"Smite!\"").unwrap();
    assert_eq!(result, program);

    let program = Program {
        name: MacroOp::Name(String::from("macro-name-2")),
        steps: vec![
            Step {
                op: MacroOp::Roll,
                args: vec![ "3d8".to_string() ],
            },
            Step {
                op: MacroOp::Say,
                args: vec![ "Smite!".to_string() ],
            },
            Step {
                op: MacroOp::Roll,
                args: vec![ "1d20".to_string() ],
            },
        ],
    };
    let (_, result) = parse(b"#macro-name-2 !roll 3d8 !say \"Smite!\" !roll 1d20").unwrap();
    assert_eq!(result, program);
}

#[test]
fn test_name_parser() {
    let (_, result) = name(b"#macro_name").unwrap();
    assert_eq!(result, MacroOp::Name(String::from("macro_name")));

    let (_, result) = name(b"#macro-name").unwrap();
    assert_eq!(result, MacroOp::Name(String::from("macro-name")));

    let (_, result) = name(b"#123macro-name").unwrap();
    assert_eq!(result, MacroOp::Name(String::from("123macro-name")));

    let (_, result) = name(b"#Z123macro-name").unwrap();
    assert_eq!(result, MacroOp::Name(String::from("Z123macro-name")));

    let (_, result) = name(b"#macro-name cannot have spaces").unwrap();
    assert_eq!(result, MacroOp::Name(String::from("macro-name")));

    let bad_result = name(b"macro_name");
    match bad_result {
        IResult::Error(e) => assert_eq!(e, ErrorKind::Tag),
        _ => assert_eq!(1, 0),
    }
}

#[test]
fn test_command_parser_roll() {
    let (_, result) = command(b"!roll 1d20").unwrap();
    assert_eq!(result, MacroOp::Roll);
    let (_, result) = command(b"!r 1d20").unwrap();
    assert_eq!(result, MacroOp::Roll);
    let (_, result) = command(b"!roll advantage").unwrap();
    assert_eq!(result, MacroOp::Roll);
    let (_, result) = command(b"!roll adv").unwrap();
    assert_eq!(result, MacroOp::Roll);
    let (_, result) = command(b"!roll disadvantage").unwrap();
    assert_eq!(result, MacroOp::Roll);
    let (_, result) = command(b"!roll dis").unwrap();
    assert_eq!(result, MacroOp::Roll);
}

#[test]
fn test_op_parser() {
    let (_, result) = op(b"    #test-macro   ").unwrap();
    assert_eq!(result, MacroOp::Name(String::from("test-macro")));
    let (_, result) = command(b"    !roll 1d20 ").unwrap();
    assert_eq!(result, MacroOp::Roll);
    let (_, result) = command(b"   !say \"Hello!\" ").unwrap();
    assert_eq!(result, MacroOp::Say);
    let (_, result) = command(b"   !whisper").unwrap();
    assert_eq!(result, MacroOp::Whisper);
}

#[test]
fn test_arguments_parser() {
    let (_, result) = arguments(b"\"hello\"").unwrap();
    assert_eq!(result, String::from("hello"));
    let (_, result) = arguments(b"   Hello  ").unwrap();
    assert_eq!(result, String::from("Hello"));
    let (_, result) = arguments(b"'   Single String Args'").unwrap();
    assert_eq!(result, String::from("Single String Args"));
}

#[test]
fn test_quoted_parser() {
    let (_, result) = quoted(b"\"hello\"").unwrap();
    assert_eq!(result, String::from("hello"));
    let (_, result) = quoted(b"\"   Hello  \"").unwrap();
    assert_eq!(result, String::from("Hello  "));
}

#[test]
fn test_single_quoted_parser() {
    let (_, result) = single_quoted(b"'test 123'").unwrap();
    assert_eq!(result, String::from("test 123"));
    let (_, result) = single_quoted(b"'   Single String Args'").unwrap();
    assert_eq!(result, String::from("Single String Args"));
}
<commit_msg>parse primitives as arguments<commit_after>use chrono::DateTime;
use chrono::prelude::{Local, Utc};
use errors::*;
use nom::{alphanumeric, ErrorKind, IResult};
use output::Output;
use std::str;

#[derive(Debug, PartialEq, Eq)]
struct Program {
    name: MacroOp,
    steps: Vec<Step>,
}

#[derive(Debug, PartialEq, Eq)]
struct Step {
    op: MacroOp,
    args: Vec<String>
}

#[derive(Debug, PartialEq, Eq)]
enum MacroOp {
    Add,
    Divide,
    Multiply,
    Name(String),
    Roll,
    Say,
    Subtract,
    Whisper,
}

#[derive(Debug, PartialEq, Eq)]
enum RollOp {
    Advantage,
    Comment,
    Disadvantage,
    NdD,
}

#[derive(Debug, PartialEq, Eq)]
enum SayOp {
    Message,
    To,
}

named!(arguments <&[u8], String>, alt!(
    string |
    quoted |
    single_quoted
));

/// Matches a command
named!(command <&[u8], MacroOp>, alt!(
    map!(ws!(tag!("!roll")),     |_| MacroOp::Roll)      |
    map!(ws!(tag!("!r")),        |_| MacroOp::Roll)      |
    map!(ws!(tag!("!say")),      |_| MacroOp::Say)       |
    map!(ws!(tag!("!s")),        |_| MacroOp::Say)       |
    map!(ws!(tag!("!whisper")),  |_| MacroOp::Whisper)   |
    map!(ws!(tag!("!w")),        |_| MacroOp::Whisper)
));

/// Matches a macro name
named!(name <&[u8], MacroOp>, ws!(do_parse!(
    tag!("#") >>
    name: map_res!(is_not!(" \t\r\n"), |r: &[u8]| String::from_utf8(r.to_vec())) >>
    (MacroOp::Name(name))
)));

/// Matches any type of operation
named!(op <&[u8], MacroOp>, alt!(
    name |
    command |
    ws!(primitive)
));

/// Matches primitive operations
named!(primitive <&[u8], MacroOp>, alt!(
    map!(tag!("*"), |_| MacroOp::Multiply)  |
    map!(tag!("+"), |_| MacroOp::Add)       |
    map!(tag!("-"), |_| MacroOp::Subtract)  |
    map!(tag!("/"), |_| MacroOp::Divide)
));

/// Parse the complete macro
named!(parse <&[u8], Program>, do_parse!(
    prog_name: name >>
    steps: many0!(parse_step) >>
    (Program {
        name: prog_name,
        steps: steps,
    })
));

/// Parse a step of the program
named!(parse_step <&[u8], Step>, do_parse!(
    op_type: op >>
    args: ws!(many0!(arguments)) >>
    (Step {
        op: op_type,
        args,
    })
));

/// Matches arguments in quotes ("")
named!(quoted <&[u8], String>, do_parse!(
    word: ws!(delimited!(tag!("\""),take_until!("\""), tag!("\""))) >>
    (String::from_utf8(word.to_vec()).unwrap())
));

/// Matches arguments in quotes ('')
named!(single_quoted <&[u8], String>, do_parse!(
    word: ws!(delimited!(tag!("'"),take_until!("'"), tag!("'"))) >>
    (String::from_utf8(word.to_vec()).unwrap())
));

/// Match alphanumeric values to strings
named!(string <&[u8], String>, do_parse!(
    word: ws!(alphanumeric) >>
    (String::from_utf8(word.to_vec()).unwrap())
));

#[test]
fn test_simple_parser() {
    let program = Program {
        name: MacroOp::Name(String::from("macro-name")),
        steps: vec![Step {
            op: MacroOp::Roll,
            args: vec![ "1d20".to_string() ],
        }],
    };
    let (_, result) = parse(b"#macro-name !roll 1d20").unwrap();
    assert_eq!(result, program);

    let program = Program {
        name: MacroOp::Name(String::from("macro-name-2")),
        steps: vec![Step {
            op: MacroOp::Say,
            args: vec![ "Hello, world!".to_string() ],
        }],
    };
    let (_, result) = parse(b"#macro-name-2 !say \"Hello, world!\"").unwrap();
    assert_eq!(result, program);
}

#[test]
fn test_complex_parser() {
    let program = Program {
        name: MacroOp::Name(String::from("macro-name")),
        steps: vec![
            Step {
                op: MacroOp::Roll,
                args: vec![ "1d20".to_string() ],
            },
            Step {
                op: MacroOp::Say,
                args: vec![ "Smite!".to_string() ],
            },
        ],
    };
    let (_, result) = parse(b"#macro-name !roll 1d20 !say \"Smite!\"").unwrap();
    assert_eq!(result, program);

    let program = Program {
        name: MacroOp::Name(String::from("macro-name-2")),
        steps: vec![
            Step {
                op: MacroOp::Roll,
                args: vec![ "3d8".to_string() ],
            },
            Step {
                op: MacroOp::Add,
                args: vec![ "3".to_string() ],
            },
            Step {
                op: MacroOp::Say,
                args: vec![ "Smite!".to_string() ],
            },
            Step {
                op: MacroOp::Roll,
                args: vec![ "1d20".to_string() ],
            },
        ],
    };
    let (_, result) = parse(b"#macro-name-2 !roll 3d8+3 !say \"Smite!\" !roll 1d20").unwrap();
    assert_eq!(result, program);
}

#[test]
fn test_name_parser() {
    let (_, result) = name(b"#macro_name").unwrap();
    assert_eq!(result, MacroOp::Name(String::from("macro_name")));

    let (_, result) = name(b"#macro-name").unwrap();
    assert_eq!(result, MacroOp::Name(String::from("macro-name")));

    let (_, result) = name(b"#123macro-name").unwrap();
    assert_eq!(result, MacroOp::Name(String::from("123macro-name")));

    let (_, result) = name(b"#Z123macro-name").unwrap();
    assert_eq!(result, MacroOp::Name(String::from("Z123macro-name")));

    let (_, result) = name(b"#macro-name cannot have spaces").unwrap();
    assert_eq!(result, MacroOp::Name(String::from("macro-name")));

    let bad_result = name(b"macro_name");
    match bad_result {
        IResult::Error(e) => assert_eq!(e, ErrorKind::Tag),
        _ => assert_eq!(1, 0),
    }
}

#[test]
fn test_command_parser_roll() {
    let (_, result) = command(b"!roll 1d20").unwrap();
    assert_eq!(result, MacroOp::Roll);
    let (_, result) = command(b"!r 1d20").unwrap();
    assert_eq!(result, MacroOp::Roll);
    let (_, result) = command(b"!roll advantage").unwrap();
    assert_eq!(result, MacroOp::Roll);
    let (_, result) = command(b"!roll adv").unwrap();
    assert_eq!(result, MacroOp::Roll);
    let (_, result) = command(b"!roll disadvantage").unwrap();
    assert_eq!(result, MacroOp::Roll);
    let (_, result) = command(b"!roll dis").unwrap();
    assert_eq!(result, MacroOp::Roll);
}

#[test]
fn test_op_parser() {
    let (_, result) = op(b"    #test-macro   ").unwrap();
    assert_eq!(result, MacroOp::Name(String::from("test-macro")));
    let (_, result) = command(b"    !roll 1d20 ").unwrap();
    assert_eq!(result, MacroOp::Roll);
    let (_, result) = command(b"   !say \"Hello!\" ").unwrap();
    assert_eq!(result, MacroOp::Say);
    let (_, result) = command(b"   !whisper").unwrap();
    assert_eq!(result, MacroOp::Whisper);
}

#[test]
fn test_arguments_parser() {
    let (_, result) = arguments(b"\"hello\"").unwrap();
    assert_eq!(result, String::from("hello"));
    let (_, result) = arguments(b"   Hello  ").unwrap();
    assert_eq!(result, String::from("Hello"));
    let (_, result) = arguments(b"'   Single String Args'").unwrap();
    assert_eq!(result, String::from("Single String Args"));
}

#[test]
fn test_quoted_parser() {
    let (_, result) = quoted(b"\"hello\"").unwrap();
    assert_eq!(result, String::from("hello"));
    let (_, result) = quoted(b"\"   Hello  \"").unwrap();
    assert_eq!(result, String::from("Hello  "));
}

#[test]
fn test_single_quoted_parser() {
    let (_, result) = single_quoted(b"'test 123'").unwrap();
    assert_eq!(result, String::from("test 123"));
    let (_, result) = single_quoted(b"'   Single String Args'").unwrap();
    assert_eq!(result, String::from("Single String Args"));
}
<|endoftext|>


<commit_before>use std::str::from_utf8;
use std::result::Result;
use nom::IResult;

#[derive(Debug,PartialEq,Eq)]
pub struct PosixHeader<'a> {
    pub name:     & 'a str,
    pub mode:     & 'a str,
    pub uid:      u64,
    pub gid:      u64,
    pub size:     u64,
    pub mtime:    u64,
    pub chksum:   & 'a str,
    pub typeflag: TypeFlag,
    pub linkname: & 'a str,
    pub ustar:    Option<UStarHeader<'a>>
}

#[derive(Debug,PartialEq,Eq)]
pub struct UStarHeader<'a> {
    pub magic:    & 'a str,
    pub version:  & 'a str,
    pub uname:    & 'a str,
    pub gname:    & 'a str,
    pub devmajor: u64,
    pub devminor: u64,
    pub prefix:   & 'a str,
}

#[derive(Debug,PartialEq,Eq)]
pub struct TarEntry<'a> {
    pub header:   PosixHeader<'a>,
    pub contents: & 'a str
}

/* TODO: support vendor specific + sparse */
#[derive(Debug,PartialEq,Eq)]
pub enum TypeFlag {
    NormalFile,
    HardLink,
    SymbolicLink,
    CharacterSpecial,
    BlockSpecial,
    Directory,
    FIFO,
    ContiguousFile,
    GlobalExtendedHeaderWithMetadata,
    ExtendedHeaderWithMetadataForNext,
    VendorSpecific,
    Invalid
}

pub fn octal_to_u64(s: &str) -> Result<u64, &'static str> {
    let mut u = 0;

    for c in s.chars() {
        if c < '0' || c > '7' {
            return Err("invalid octal string received");
        }
        u *= 8;
        u += (c as u64) - ('0' as u64);
    }

    Ok(u)
}

fn char_to_type_flag(c: char) -> TypeFlag {
    match c {
        '0' | '\0' => TypeFlag::NormalFile,
        '1' => TypeFlag::HardLink,
        '2' => TypeFlag::SymbolicLink,
        '3' => TypeFlag::CharacterSpecial,
        '4' => TypeFlag::BlockSpecial,
        '5' => TypeFlag::Directory,
        '6' => TypeFlag::FIFO,
        '7' => TypeFlag::ContiguousFile,
        'g' => TypeFlag::GlobalExtendedHeaderWithMetadata,
        'x' => TypeFlag::ExtendedHeaderWithMetadataForNext,
        'A' ... 'Z' => TypeFlag::VendorSpecific,
        _ => TypeFlag::Invalid
    }
}

macro_rules! take_str_eat_garbage (
 ( $i:expr, $size:expr ) => ( chain!( $i, s: map_res!(take_until!("\0"), from_utf8) ~ take!($size - s.len()), ||{ s } ));
);

fn parse_ustar00(i: &[u8]) -> IResult<&[u8], Option<UStarHeader>> {
    chain!(i,
        tag!("00")                                                 ~
        uname:    take_str_eat_garbage!(32)                        ~
        gname:    take_str_eat_garbage!(32)                        ~
        devmajor: map_res!(take_str_eat_garbage!(8), octal_to_u64) ~
        devminor: map_res!(take_str_eat_garbage!(8), octal_to_u64) ~
        prefix:   take_str_eat_garbage!(155)                       ~
        take!(12), /* padding to 512 */
        ||{
            Some(UStarHeader {
                magic:    "ustar\0",
                version:  "00",
                uname:    uname,
                gname:    gname,
                devmajor: devmajor,
                devminor: devminor,
                prefix:   prefix
            })
        }
    )
}

fn parse_ustar(i: &[u8]) -> IResult<&[u8], Option<UStarHeader>> {
    chain!(i,
        tag!("ustar\0") ~
        ustar: parse_ustar00,
        ||{
            ustar
        }
    )
}

fn parse_posix(i: &[u8]) -> IResult<&[u8], Option<UStarHeader>> {
    chain!(i,
        take!(255), /* padding to 512 */
        ||{
            None
        }
    )
}

fn parse_header(i: &[u8]) -> IResult<&[u8], PosixHeader> {
    chain!(i,
        name:     take_str_eat_garbage!(100)                        ~
        mode:     take_str_eat_garbage!(8)                          ~
        uid:      map_res!(take_str_eat_garbage!(8),  octal_to_u64) ~
        gid:      map_res!(take_str_eat_garbage!(8),  octal_to_u64) ~
        size:     map_res!(take_str_eat_garbage!(12), octal_to_u64) ~
        mtime:    map_res!(take_str_eat_garbage!(12), octal_to_u64) ~
        chksum:   take_str_eat_garbage!(8)                          ~
        typeflag: take!(1)                                          ~
        linkname: take_str_eat_garbage!(100)                        ~
        ustar:    alt!(parse_ustar | parse_posix),
        ||{
            PosixHeader {
                name:     name,
                mode:     mode,
                uid:      uid,
                gid:      gid,
                size:     size,
                mtime:    mtime,
                chksum:   chksum,
                typeflag: char_to_type_flag(typeflag[0] as char),
                linkname: linkname,
                ustar:    ustar
            }
        }
    )
}

fn parse_contents(i: &[u8], size: u64) -> IResult<&[u8], &str> {
    let trailing = size % 512;
    let padding = match trailing {
        0 => 0,
        t => 512 - t
    };
    chain!(i,
        contents: take_str!(size as usize) ~
        take!(padding as usize),
        ||{
            contents
        }
    )
}

fn parse_entry(i: &[u8]) -> IResult<&[u8], TarEntry> {
    chain!(i,
        header:   parse_header ~
        contents: apply!(parse_contents, header.size),
        ||{
            TarEntry {
                header: header,
                contents: contents
            }
        }
    )
}

fn filter_entries(entries: Vec<TarEntry>) -> Result<Vec<TarEntry>, &'static str> {
    Ok(entries.into_iter().filter(|e| e.header.name != "").collect::<Vec<TarEntry>>())
}

pub fn parse_tar(i: &[u8]) -> IResult<&[u8], Vec<TarEntry>> {
    map_res!(i, many0!(parse_entry), filter_entries)
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::str::from_utf8;
    use nom::IResult;

    #[test]
    fn octal_to_u64_ok_test() {
        assert_eq!(octal_to_u64("756"), Ok(494));
        assert_eq!(octal_to_u64(""), Ok(0));
    }

    #[test]
    fn octal_to_u64_error_test() {
        assert_eq!(octal_to_u64("1238"), Err("invalid octal string received"));
        assert_eq!(octal_to_u64("a"), Err("invalid octal string received"));
        assert_eq!(octal_to_u64("A"), Err("invalid octal string received"));
    }

    #[test]
    fn take_str_eat_garbage_test() {
        let s = b"foobar\0\0\0\0baz";
        let baz = b"baz";
        assert_eq!(take_str_eat_garbage!(&s[..], 10), IResult::Done(&baz[..], "foobar"));
    }
}
<commit_msg>invalid type flag should be treated as normal file<commit_after>use std::str::from_utf8;
use std::result::Result;
use nom::IResult;

#[derive(Debug,PartialEq,Eq)]
pub struct PosixHeader<'a> {
    pub name:     & 'a str,
    pub mode:     & 'a str,
    pub uid:      u64,
    pub gid:      u64,
    pub size:     u64,
    pub mtime:    u64,
    pub chksum:   & 'a str,
    pub typeflag: TypeFlag,
    pub linkname: & 'a str,
    pub ustar:    Option<UStarHeader<'a>>
}

#[derive(Debug,PartialEq,Eq)]
pub struct UStarHeader<'a> {
    pub magic:    & 'a str,
    pub version:  & 'a str,
    pub uname:    & 'a str,
    pub gname:    & 'a str,
    pub devmajor: u64,
    pub devminor: u64,
    pub prefix:   & 'a str,
}

#[derive(Debug,PartialEq,Eq)]
pub struct TarEntry<'a> {
    pub header:   PosixHeader<'a>,
    pub contents: & 'a str
}

/* TODO: support vendor specific + sparse */
#[derive(Debug,PartialEq,Eq)]
pub enum TypeFlag {
    NormalFile,
    HardLink,
    SymbolicLink,
    CharacterSpecial,
    BlockSpecial,
    Directory,
    FIFO,
    ContiguousFile,
    GlobalExtendedHeaderWithMetadata,
    ExtendedHeaderWithMetadataForNext,
    VendorSpecific
}

pub fn octal_to_u64(s: &str) -> Result<u64, &'static str> {
    let mut u = 0;

    for c in s.chars() {
        if c < '0' || c > '7' {
            return Err("invalid octal string received");
        }
        u *= 8;
        u += (c as u64) - ('0' as u64);
    }

    Ok(u)
}

fn char_to_type_flag(c: char) -> TypeFlag {
    match c {
        '0' | '\0' => TypeFlag::NormalFile,
        '1' => TypeFlag::HardLink,
        '2' => TypeFlag::SymbolicLink,
        '3' => TypeFlag::CharacterSpecial,
        '4' => TypeFlag::BlockSpecial,
        '5' => TypeFlag::Directory,
        '6' => TypeFlag::FIFO,
        '7' => TypeFlag::ContiguousFile,
        'g' => TypeFlag::GlobalExtendedHeaderWithMetadata,
        'x' => TypeFlag::ExtendedHeaderWithMetadataForNext,
        'A' ... 'Z' => TypeFlag::VendorSpecific,
        _ => TypeFlag::NormalFile
    }
}

macro_rules! take_str_eat_garbage (
 ( $i:expr, $size:expr ) => ( chain!( $i, s: map_res!(take_until!("\0"), from_utf8) ~ take!($size - s.len()), ||{ s } ));
);

fn parse_ustar00(i: &[u8]) -> IResult<&[u8], Option<UStarHeader>> {
    chain!(i,
        tag!("00")                                                 ~
        uname:    take_str_eat_garbage!(32)                        ~
        gname:    take_str_eat_garbage!(32)                        ~
        devmajor: map_res!(take_str_eat_garbage!(8), octal_to_u64) ~
        devminor: map_res!(take_str_eat_garbage!(8), octal_to_u64) ~
        prefix:   take_str_eat_garbage!(155)                       ~
        take!(12), /* padding to 512 */
        ||{
            Some(UStarHeader {
                magic:    "ustar\0",
                version:  "00",
                uname:    uname,
                gname:    gname,
                devmajor: devmajor,
                devminor: devminor,
                prefix:   prefix
            })
        }
    )
}

fn parse_ustar(i: &[u8]) -> IResult<&[u8], Option<UStarHeader>> {
    chain!(i,
        tag!("ustar\0") ~
        ustar: parse_ustar00,
        ||{
            ustar
        }
    )
}

fn parse_posix(i: &[u8]) -> IResult<&[u8], Option<UStarHeader>> {
    chain!(i,
        take!(255), /* padding to 512 */
        ||{
            None
        }
    )
}

fn parse_header(i: &[u8]) -> IResult<&[u8], PosixHeader> {
    chain!(i,
        name:     take_str_eat_garbage!(100)                        ~
        mode:     take_str_eat_garbage!(8)                          ~
        uid:      map_res!(take_str_eat_garbage!(8),  octal_to_u64) ~
        gid:      map_res!(take_str_eat_garbage!(8),  octal_to_u64) ~
        size:     map_res!(take_str_eat_garbage!(12), octal_to_u64) ~
        mtime:    map_res!(take_str_eat_garbage!(12), octal_to_u64) ~
        chksum:   take_str_eat_garbage!(8)                          ~
        typeflag: take!(1)                                          ~
        linkname: take_str_eat_garbage!(100)                        ~
        ustar:    alt!(parse_ustar | parse_posix),
        ||{
            PosixHeader {
                name:     name,
                mode:     mode,
                uid:      uid,
                gid:      gid,
                size:     size,
                mtime:    mtime,
                chksum:   chksum,
                typeflag: char_to_type_flag(typeflag[0] as char),
                linkname: linkname,
                ustar:    ustar
            }
        }
    )
}

fn parse_contents(i: &[u8], size: u64) -> IResult<&[u8], &str> {
    let trailing = size % 512;
    let padding = match trailing {
        0 => 0,
        t => 512 - t
    };
    chain!(i,
        contents: take_str!(size as usize) ~
        take!(padding as usize),
        ||{
            contents
        }
    )
}

fn parse_entry(i: &[u8]) -> IResult<&[u8], TarEntry> {
    chain!(i,
        header:   parse_header ~
        contents: apply!(parse_contents, header.size),
        ||{
            TarEntry {
                header: header,
                contents: contents
            }
        }
    )
}

fn filter_entries(entries: Vec<TarEntry>) -> Result<Vec<TarEntry>, &'static str> {
    Ok(entries.into_iter().filter(|e| e.header.name != "").collect::<Vec<TarEntry>>())
}

pub fn parse_tar(i: &[u8]) -> IResult<&[u8], Vec<TarEntry>> {
    map_res!(i, many0!(parse_entry), filter_entries)
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::str::from_utf8;
    use nom::IResult;

    #[test]
    fn octal_to_u64_ok_test() {
        assert_eq!(octal_to_u64("756"), Ok(494));
        assert_eq!(octal_to_u64(""), Ok(0));
    }

    #[test]
    fn octal_to_u64_error_test() {
        assert_eq!(octal_to_u64("1238"), Err("invalid octal string received"));
        assert_eq!(octal_to_u64("a"), Err("invalid octal string received"));
        assert_eq!(octal_to_u64("A"), Err("invalid octal string received"));
    }

    #[test]
    fn take_str_eat_garbage_test() {
        let s = b"foobar\0\0\0\0baz";
        let baz = b"baz";
        assert_eq!(take_str_eat_garbage!(&s[..], 10), IResult::Done(&baz[..], "foobar"));
    }
}
<|endoftext|>