<commit_before>//! Linux DMABUF protocol
//!
//! This module provides helper to handle the linux-dmabuf protocol, which allows clients to submit their
//! contents as dmabuf file descriptors. These handlers automate the aggregation of the metadata associated
//! with a dma buffer, and do some basic checking of the sanity of what the client sends.
//!
//! ## How to use
//!
//! To setup the dmabuf global, you will need to provide 2 things:
//!
//! - a list of the dmabuf formats you wish to support
//! - a closure to test if a dmabuf buffer can be imported by your renderer
//!
//! The list of supported format is just a `Vec<Format>`, where you will enter all the (code, modifier)
//! couples you support. You can typically receive a list of supported formats for one renderer by calling
//! [`crate::backend::renderer::ImportDma::dmabuf_formats`].
//!
//! ```
//! # extern crate wayland_server;
//! # extern crate smithay;
//! use smithay::{
//!     backend::allocator::dmabuf::Dmabuf,
//!     reexports::{wayland_server::protocol::wl_buffer::WlBuffer},
//!     wayland::dmabuf::init_dmabuf_global,
//! };
//!
//! # let mut display = wayland_server::Display::new();
//! // define your supported formats
//! let formats = vec![
//!     /* ... */
//! ];
//! let dmabuf_global = init_dmabuf_global(
//!     &mut display,
//!     formats,
//!     |buffer, dispatch_data| {
//!         /* validate the dmabuf and import it into your renderer state */
//!         true
//!     },
//!     None // we don't provide a logger in this example
//! );
//! ```

use std::{
    cell::RefCell,
    convert::TryFrom,
    os::unix::io::{IntoRawFd, RawFd},
    rc::Rc,
};

pub use wayland_protocols::unstable::linux_dmabuf::v1::server::zwp_linux_buffer_params_v1::Flags;
use wayland_protocols::unstable::linux_dmabuf::v1::server::{
    zwp_linux_buffer_params_v1::{
        Error as ParamError, Request as ParamsRequest, ZwpLinuxBufferParamsV1 as BufferParams,
    },
    zwp_linux_dmabuf_v1,
};
use wayland_server::{protocol::wl_buffer, DispatchData, Display, Filter, Global, Main};

use slog::{o, trace};

use crate::backend::allocator::{
    dmabuf::{Dmabuf, DmabufFlags, Plane},
    Format, Fourcc, Modifier,
};

/// Handler trait for dmabuf validation
///
/// You need to provide an implementation of this trait

/// Initialize a dmabuf global.
///
/// You need to provide a vector of the supported formats, as well as a closure,
/// that will validate the parameters provided by the client and tests the import as a dmabuf.
pub fn init_dmabuf_global<F, L>(
    display: &mut Display,
    formats: Vec<Format>,
    handler: F,
    logger: L,
) -> Global<zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1>
where
    L: Into<Option<::slog::Logger>>,
    F: for<'a> FnMut(&Dmabuf, DispatchData<'a>) -> bool + 'static,
{
    let log = crate::slog_or_fallback(logger).new(o!("smithay_module" => "dmabuf_handler"));

    let formats = Rc::<[Format]>::from(formats);
    let handler = Rc::new(RefCell::new(handler));

    trace!(
        log,
        "Initializing DMABUF handler with {} supported formats",
        formats.len()
    );

    display.create_global(
        3,
        Filter::new(
            move |(dmabuf, version): (Main<zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1>, u32), _, _| {
                let dma_formats = formats.clone();
                let dma_handler = handler.clone();
                let dma_log = log.clone();
                dmabuf.quick_assign(move |_, req, _| {
                    if let zwp_linux_dmabuf_v1::Request::CreateParams { params_id } = req {
                        let mut handler = ParamsHandler {
                            pending_planes: Vec::new(),
                            max_planes: 4,
                            used: false,
                            formats: dma_formats.clone(),
                            handler: dma_handler.clone(),
                            log: dma_log.clone(),
                        };
                        params_id.quick_assign(move |params, req, ddata| match req {
                            ParamsRequest::Add {
                                fd,
                                plane_idx,
                                offset,
                                stride,
                                modifier_hi,
                                modifier_lo,
                            } => handler.add(
                                &*params,
                                fd,
                                plane_idx,
                                offset,
                                stride,
                                ((modifier_hi as u64) << 32) + (modifier_lo as u64),
                            ),
                            ParamsRequest::Create {
                                width,
                                height,
                                format,
                                flags,
                            } => handler.create(&*params, width, height, format, flags, ddata),
                            ParamsRequest::CreateImmed {
                                buffer_id,
                                width,
                                height,
                                format,
                                flags,
                            } => {
                                handler.create_immed(&*params, buffer_id, width, height, format, flags, ddata)
                            }
                            _ => {}
                        });
                    }
                });

                // send the supported formats
                for f in &*formats {
                    dmabuf.format(f.code as u32);
                    if version >= 3 {
                        dmabuf.modifier(
                            f.code as u32,
                            (Into::<u64>::into(f.modifier) >> 32) as u32,
                            Into::<u64>::into(f.modifier) as u32,
                        );
                    }
                }
            },
        ),
    )
}

struct ParamsHandler<H: for<'a> FnMut(&Dmabuf, DispatchData<'a>) -> bool + 'static> {
    pending_planes: Vec<Plane>,
    max_planes: u32,
    used: bool,
    formats: Rc<[Format]>,
    handler: Rc<RefCell<H>>,
    log: ::slog::Logger,
}

impl<H> ParamsHandler<H>
where
    H: for<'a> FnMut(&Dmabuf, DispatchData<'a>) -> bool + 'static,
{
    fn add(
        &mut self,
        params: &BufferParams,
        fd: RawFd,
        plane_idx: u32,
        offset: u32,
        stride: u32,
        modifier: u64,
    ) {
        // protocol checks:
        // Cannot reuse a params:
        if self.used {
            params.as_ref().post_error(
                ParamError::AlreadyUsed as u32,
                "This buffer_params has already been used to create a buffer.".into(),
            );
            return;
        }
        // plane_idx is not too large
        if plane_idx >= self.max_planes {
            // plane_idx starts at 0
            params.as_ref().post_error(
                ParamError::PlaneIdx as u32,
                format!("Plane index {} is out of bounds.", plane_idx),
            );
            return;
        }
        // plane_idx has already been set
        if self.pending_planes.iter().any(|d| d.plane_idx == plane_idx) {
            params.as_ref().post_error(
                ParamError::PlaneSet as u32,
                format!("Plane index {} is already set.", plane_idx),
            );
            return;
        }
        // all checks passed, store the plane
        self.pending_planes.push(Plane {
            fd: Some(fd),
            plane_idx,
            offset,
            stride,
            modifier: Modifier::from(modifier),
        });
    }

    fn create<'a>(
        &mut self,
        params: &BufferParams,
        width: i32,
        height: i32,
        format: u32,
        flags: u32,
        ddata: DispatchData<'a>,
    ) {
        // Cannot reuse a params:
        if self.used {
            params.as_ref().post_error(
                ParamError::AlreadyUsed as u32,
                "This buffer_params has already been used to create a buffer.".into(),
            );
            return;
        }
        self.used = true;

        let format = match Fourcc::try_from(format) {
            Ok(format) => format,
            Err(_) => {
                params.as_ref().post_error(
                    ParamError::InvalidFormat as u32,
                    format!("Format {:x} is not supported", format),
                );
                return;
            }
        };

        if !buffer_basic_checks(
            &self.formats,
            &self.pending_planes,
            &params,
            format,
            width,
            height,
        ) {
            trace!(self.log, "Killing client providing bogus dmabuf buffer params.");
            return;
        }

        let mut buf = Dmabuf::builder(
            width as u32,
            height as u32,
            format,
            DmabufFlags::from_bits_truncate(flags),
        );
        let planes = std::mem::take(&mut self.pending_planes);
        for (i, plane) in planes.into_iter().enumerate() {
            let offset = plane.offset;
            let stride = plane.stride;
            let modi = plane.modifier;
            buf.add_plane(plane.into_raw_fd(), i as u32, offset, stride, modi);
        }
        let dmabuf = match buf.build() {
            Some(buf) => buf,
            None => {
                params.as_ref().post_error(
                    ParamError::Incomplete as u32,
                    "Provided buffer is incomplete, it has zero planes".to_string(),
                );
                return;
            }
        };

        let mut handler = self.handler.borrow_mut();
        if handler(&dmabuf, ddata) {
            if let Some(buffer) = params
                .as_ref()
                .client()
                .and_then(|c| c.create_resource::<wl_buffer::WlBuffer>(1))
            {
                buffer.as_ref().user_data().set_threadsafe(|| dmabuf);
                buffer.quick_assign(|_, _, _| {});

                trace!(self.log, "Created a new validated dma wl_buffer.");
                params.created(&buffer);
            } else {
                trace!(self.log, "Failed to create a wl_buffer");
                params.failed();
            }
        } else {
            trace!(self.log, "Refusing creation of an invalid dma wl_buffer.");
            params.failed();
        }
    }

    #[allow(clippy::too_many_arguments)]
    fn create_immed<'a>(
        &mut self,
        params: &BufferParams,
        buffer: Main<wl_buffer::WlBuffer>,
        width: i32,
        height: i32,
        format: u32,
        flags: u32,
        ddata: DispatchData<'a>,
    ) {
        // Cannot reuse a params:
        if self.used {
            params.as_ref().post_error(
                ParamError::AlreadyUsed as u32,
                "This buffer_params has already been used to create a buffer.".into(),
            );
            return;
        }
        self.used = true;

        let format = match Fourcc::try_from(format) {
            Ok(format) => format,
            Err(_) => {
                params.as_ref().post_error(
                    ParamError::InvalidFormat as u32,
                    format!("Format {:x} is not supported", format),
                );
                return;
            }
        };

        if !buffer_basic_checks(
            &self.formats,
            &self.pending_planes,
            &params,
            format,
            width,
            height,
        ) {
            trace!(self.log, "Killing client providing bogus dmabuf buffer params.");
            return;
        }

        let mut buf = Dmabuf::builder(
            width as u32,
            height as u32,
            format,
            DmabufFlags::from_bits_truncate(flags),
        );
        let planes = ::std::mem::take(&mut self.pending_planes);
        for (i, plane) in planes.into_iter().enumerate() {
            let offset = plane.offset;
            let stride = plane.stride;
            let modi = plane.modifier;
            buf.add_plane(plane.into_raw_fd(), i as u32, offset, stride, modi);
        }
        let dmabuf = match buf.build() {
            Some(buf) => buf,
            None => {
                params.as_ref().post_error(
                    ParamError::Incomplete as u32,
                    "Provided buffer is incomplete, it has zero planes".to_string(),
                );
                return;
            }
        };

        let mut handler = self.handler.borrow_mut();
        if handler(&dmabuf, ddata) {
            buffer.as_ref().user_data().set_threadsafe(|| dmabuf);
            buffer.quick_assign(|_, _, _| {});
            trace!(self.log, "Created a new validated dma wl_buffer.");
        } else {
            trace!(
                self.log,
                "Refusing creation of an invalid immediate dma wl_buffer, killing client."
            );
            params.as_ref().post_error(
                ParamError::InvalidWlBuffer as u32,
                "create_immed resulted in an invalid buffer.".into(),
            );
        }
    }
}

fn buffer_basic_checks(
    formats: &[Format],
    pending_planes: &[Plane],
    params: &BufferParams,
    format: Fourcc,
    width: i32,
    height: i32,
) -> bool {
    // protocol_checks:
    // This must be a known format
    let _format = match formats.iter().find(|f| f.code == format) {
        Some(f) => f,
        None => {
            params.as_ref().post_error(
                ParamError::InvalidFormat as u32,
                format!("Format {:?}/{:x} is not supported.", format, format as u32),
            );
            return false;
        }
    };
    // Width and height must be positivie
    if width < 1 || height < 1 {
        params.as_ref().post_error(
            ParamError::InvalidDimensions as u32,
            format!("Dimensions ({},{}) are not valid.", width, height),
        );
        return false;
    }
    // check the size of each plane buffer
    for plane in pending_planes {
        // check size for overflow
        let end = match plane
            .stride
            .checked_mul(height as u32)
            .and_then(|o| o.checked_add(plane.offset))
        {
            None => {
                params.as_ref().post_error(
                    ParamError::OutOfBounds as u32,
                    format!("Size overflow for plane {}.", plane.plane_idx),
                );
                return false;
            }
            Some(e) => e,
        };
        if let Ok(size) = ::nix::unistd::lseek(plane.fd.unwrap(), 0, ::nix::unistd::Whence::SeekEnd) {
            // reset the seek point
            let _ = ::nix::unistd::lseek(plane.fd.unwrap(), 0, ::nix::unistd::Whence::SeekSet);
            if plane.offset as libc::off_t > size {
                params.as_ref().post_error(
                    ParamError::OutOfBounds as u32,
                    format!("Invalid offset {} for plane {}.", plane.offset, plane.plane_idx),
                );
                return false;
            }
            if (plane.offset + plane.stride) as libc::off_t > size {
                params.as_ref().post_error(
                    ParamError::OutOfBounds as u32,
                    format!("Invalid stride {} for plane {}.", plane.stride, plane.plane_idx),
                );
                return false;
            }
            // Planes > 0 can be subsampled, in which case 'size' will be smaller
            // than expected.
            if plane.plane_idx == 0 && end as libc::off_t > size {
                params.as_ref().post_error(
                    ParamError::OutOfBounds as u32,
                    format!(
                        "Invalid stride ({}) or height ({}) for plane {}.",
                        plane.stride, height, plane.plane_idx
                    ),
                );
                return false;
            }
        }
    }
    true
}
<commit_msg>wayland.dmabuf: review docs & API<commit_after>//! Linux DMABUF protocol
//!
//! This module provides helper to handle the linux-dmabuf protocol, which allows clients to submit their
//! contents as dmabuf file descriptors. These handlers automate the aggregation of the metadata associated
//! with a dma buffer, and do some basic checking of the sanity of what the client sends.
//!
//! ## How to use
//!
//! To setup the dmabuf global, you will need to provide 2 things:
//!
//! - a list of the dmabuf formats you wish to support
//! - a closure to test if a dmabuf buffer can be imported by your renderer
//!
//! The list of supported format is just a `Vec<Format>`, where you will enter all the (code, modifier)
//! couples you support. You can typically receive a list of supported formats for one renderer by calling
//! [`ImportDma::dmabuf_formats`](crate::backend::renderer::ImportDma::dmabuf_formats).
//!
//! ```
//! # extern crate wayland_server;
//! # extern crate smithay;
//! use smithay::{
//!     backend::allocator::dmabuf::Dmabuf,
//!     reexports::{wayland_server::protocol::wl_buffer::WlBuffer},
//!     wayland::dmabuf::init_dmabuf_global,
//! };
//!
//! # let mut display = wayland_server::Display::new();
//! // define your supported formats
//! let formats = vec![
//!     /* ... */
//! ];
//! let dmabuf_global = init_dmabuf_global(
//!     &mut display,
//!     formats,
//!     |buffer, dispatch_data| {
//!         /* validate the dmabuf and import it into your renderer state */
//!         true
//!     },
//!     None // we don't provide a logger in this example
//! );
//! ```

use std::{
    cell::RefCell,
    convert::TryFrom,
    os::unix::io::{IntoRawFd, RawFd},
    rc::Rc,
};

use wayland_protocols::unstable::linux_dmabuf::v1::server::{
    zwp_linux_buffer_params_v1::{
        Error as ParamError, Request as ParamsRequest, ZwpLinuxBufferParamsV1 as BufferParams
    },
    zwp_linux_dmabuf_v1,
};
use wayland_server::{protocol::wl_buffer, DispatchData, Display, Filter, Global, Main};

use slog::{o, trace};

use crate::backend::allocator::{
    dmabuf::{Dmabuf, DmabufFlags, Plane},
    Format, Fourcc, Modifier,
};

/// Handler trait for dmabuf validation
///
/// You need to provide an implementation of this trait

/// Initialize a dmabuf global.
///
/// You need to provide a vector of the supported formats, as well as a closure,
/// that will validate the parameters provided by the client and tests the import as a dmabuf.
pub fn init_dmabuf_global<F, L>(
    display: &mut Display,
    formats: Vec<Format>,
    handler: F,
    logger: L,
) -> Global<zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1>
where
    L: Into<Option<::slog::Logger>>,
    F: for<'a> FnMut(&Dmabuf, DispatchData<'a>) -> bool + 'static,
{
    let log = crate::slog_or_fallback(logger).new(o!("smithay_module" => "dmabuf_handler"));

    let formats = Rc::<[Format]>::from(formats);
    let handler = Rc::new(RefCell::new(handler));

    trace!(
        log,
        "Initializing DMABUF handler with {} supported formats",
        formats.len()
    );

    display.create_global(
        3,
        Filter::new(
            move |(dmabuf, version): (Main<zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1>, u32), _, _| {
                let dma_formats = formats.clone();
                let dma_handler = handler.clone();
                let dma_log = log.clone();
                dmabuf.quick_assign(move |_, req, _| {
                    if let zwp_linux_dmabuf_v1::Request::CreateParams { params_id } = req {
                        let mut handler = ParamsHandler {
                            pending_planes: Vec::new(),
                            max_planes: 4,
                            used: false,
                            formats: dma_formats.clone(),
                            handler: dma_handler.clone(),
                            log: dma_log.clone(),
                        };
                        params_id.quick_assign(move |params, req, ddata| match req {
                            ParamsRequest::Add {
                                fd,
                                plane_idx,
                                offset,
                                stride,
                                modifier_hi,
                                modifier_lo,
                            } => handler.add(
                                &*params,
                                fd,
                                plane_idx,
                                offset,
                                stride,
                                ((modifier_hi as u64) << 32) + (modifier_lo as u64),
                            ),
                            ParamsRequest::Create {
                                width,
                                height,
                                format,
                                flags,
                            } => handler.create(&*params, width, height, format, flags, ddata),
                            ParamsRequest::CreateImmed {
                                buffer_id,
                                width,
                                height,
                                format,
                                flags,
                            } => {
                                handler.create_immed(&*params, buffer_id, width, height, format, flags, ddata)
                            }
                            _ => {}
                        });
                    }
                });

                // send the supported formats
                for f in &*formats {
                    dmabuf.format(f.code as u32);
                    if version >= 3 {
                        dmabuf.modifier(
                            f.code as u32,
                            (Into::<u64>::into(f.modifier) >> 32) as u32,
                            Into::<u64>::into(f.modifier) as u32,
                        );
                    }
                }
            },
        ),
    )
}

struct ParamsHandler<H: for<'a> FnMut(&Dmabuf, DispatchData<'a>) -> bool + 'static> {
    pending_planes: Vec<Plane>,
    max_planes: u32,
    used: bool,
    formats: Rc<[Format]>,
    handler: Rc<RefCell<H>>,
    log: ::slog::Logger,
}

impl<H> ParamsHandler<H>
where
    H: for<'a> FnMut(&Dmabuf, DispatchData<'a>) -> bool + 'static,
{
    fn add(
        &mut self,
        params: &BufferParams,
        fd: RawFd,
        plane_idx: u32,
        offset: u32,
        stride: u32,
        modifier: u64,
    ) {
        // protocol checks:
        // Cannot reuse a params:
        if self.used {
            params.as_ref().post_error(
                ParamError::AlreadyUsed as u32,
                "This buffer_params has already been used to create a buffer.".into(),
            );
            return;
        }
        // plane_idx is not too large
        if plane_idx >= self.max_planes {
            // plane_idx starts at 0
            params.as_ref().post_error(
                ParamError::PlaneIdx as u32,
                format!("Plane index {} is out of bounds.", plane_idx),
            );
            return;
        }
        // plane_idx has already been set
        if self.pending_planes.iter().any(|d| d.plane_idx == plane_idx) {
            params.as_ref().post_error(
                ParamError::PlaneSet as u32,
                format!("Plane index {} is already set.", plane_idx),
            );
            return;
        }
        // all checks passed, store the plane
        self.pending_planes.push(Plane {
            fd: Some(fd),
            plane_idx,
            offset,
            stride,
            modifier: Modifier::from(modifier),
        });
    }

    fn create<'a>(
        &mut self,
        params: &BufferParams,
        width: i32,
        height: i32,
        format: u32,
        flags: u32,
        ddata: DispatchData<'a>,
    ) {
        // Cannot reuse a params:
        if self.used {
            params.as_ref().post_error(
                ParamError::AlreadyUsed as u32,
                "This buffer_params has already been used to create a buffer.".into(),
            );
            return;
        }
        self.used = true;

        let format = match Fourcc::try_from(format) {
            Ok(format) => format,
            Err(_) => {
                params.as_ref().post_error(
                    ParamError::InvalidFormat as u32,
                    format!("Format {:x} is not supported", format),
                );
                return;
            }
        };

        if !buffer_basic_checks(
            &self.formats,
            &self.pending_planes,
            &params,
            format,
            width,
            height,
        ) {
            trace!(self.log, "Killing client providing bogus dmabuf buffer params.");
            return;
        }

        let mut buf = Dmabuf::builder(
            width as u32,
            height as u32,
            format,
            DmabufFlags::from_bits_truncate(flags),
        );
        let planes = std::mem::take(&mut self.pending_planes);
        for (i, plane) in planes.into_iter().enumerate() {
            let offset = plane.offset;
            let stride = plane.stride;
            let modi = plane.modifier;
            buf.add_plane(plane.into_raw_fd(), i as u32, offset, stride, modi);
        }
        let dmabuf = match buf.build() {
            Some(buf) => buf,
            None => {
                params.as_ref().post_error(
                    ParamError::Incomplete as u32,
                    "Provided buffer is incomplete, it has zero planes".to_string(),
                );
                return;
            }
        };

        let mut handler = self.handler.borrow_mut();
        if handler(&dmabuf, ddata) {
            if let Some(buffer) = params
                .as_ref()
                .client()
                .and_then(|c| c.create_resource::<wl_buffer::WlBuffer>(1))
            {
                buffer.as_ref().user_data().set_threadsafe(|| dmabuf);
                buffer.quick_assign(|_, _, _| {});

                trace!(self.log, "Created a new validated dma wl_buffer.");
                params.created(&buffer);
            } else {
                trace!(self.log, "Failed to create a wl_buffer");
                params.failed();
            }
        } else {
            trace!(self.log, "Refusing creation of an invalid dma wl_buffer.");
            params.failed();
        }
    }

    #[allow(clippy::too_many_arguments)]
    fn create_immed<'a>(
        &mut self,
        params: &BufferParams,
        buffer: Main<wl_buffer::WlBuffer>,
        width: i32,
        height: i32,
        format: u32,
        flags: u32,
        ddata: DispatchData<'a>,
    ) {
        // Cannot reuse a params:
        if self.used {
            params.as_ref().post_error(
                ParamError::AlreadyUsed as u32,
                "This buffer_params has already been used to create a buffer.".into(),
            );
            return;
        }
        self.used = true;

        let format = match Fourcc::try_from(format) {
            Ok(format) => format,
            Err(_) => {
                params.as_ref().post_error(
                    ParamError::InvalidFormat as u32,
                    format!("Format {:x} is not supported", format),
                );
                return;
            }
        };

        if !buffer_basic_checks(
            &self.formats,
            &self.pending_planes,
            &params,
            format,
            width,
            height,
        ) {
            trace!(self.log, "Killing client providing bogus dmabuf buffer params.");
            return;
        }

        let mut buf = Dmabuf::builder(
            width as u32,
            height as u32,
            format,
            DmabufFlags::from_bits_truncate(flags),
        );
        let planes = ::std::mem::take(&mut self.pending_planes);
        for (i, plane) in planes.into_iter().enumerate() {
            let offset = plane.offset;
            let stride = plane.stride;
            let modi = plane.modifier;
            buf.add_plane(plane.into_raw_fd(), i as u32, offset, stride, modi);
        }
        let dmabuf = match buf.build() {
            Some(buf) => buf,
            None => {
                params.as_ref().post_error(
                    ParamError::Incomplete as u32,
                    "Provided buffer is incomplete, it has zero planes".to_string(),
                );
                return;
            }
        };

        let mut handler = self.handler.borrow_mut();
        if handler(&dmabuf, ddata) {
            buffer.as_ref().user_data().set_threadsafe(|| dmabuf);
            buffer.quick_assign(|_, _, _| {});
            trace!(self.log, "Created a new validated dma wl_buffer.");
        } else {
            trace!(
                self.log,
                "Refusing creation of an invalid immediate dma wl_buffer, killing client."
            );
            params.as_ref().post_error(
                ParamError::InvalidWlBuffer as u32,
                "create_immed resulted in an invalid buffer.".into(),
            );
        }
    }
}

fn buffer_basic_checks(
    formats: &[Format],
    pending_planes: &[Plane],
    params: &BufferParams,
    format: Fourcc,
    width: i32,
    height: i32,
) -> bool {
    // protocol_checks:
    // This must be a known format
    let _format = match formats.iter().find(|f| f.code == format) {
        Some(f) => f,
        None => {
            params.as_ref().post_error(
                ParamError::InvalidFormat as u32,
                format!("Format {:?}/{:x} is not supported.", format, format as u32),
            );
            return false;
        }
    };
    // Width and height must be positivie
    if width < 1 || height < 1 {
        params.as_ref().post_error(
            ParamError::InvalidDimensions as u32,
            format!("Dimensions ({},{}) are not valid.", width, height),
        );
        return false;
    }
    // check the size of each plane buffer
    for plane in pending_planes {
        // check size for overflow
        let end = match plane
            .stride
            .checked_mul(height as u32)
            .and_then(|o| o.checked_add(plane.offset))
        {
            None => {
                params.as_ref().post_error(
                    ParamError::OutOfBounds as u32,
                    format!("Size overflow for plane {}.", plane.plane_idx),
                );
                return false;
            }
            Some(e) => e,
        };
        if let Ok(size) = ::nix::unistd::lseek(plane.fd.unwrap(), 0, ::nix::unistd::Whence::SeekEnd) {
            // reset the seek point
            let _ = ::nix::unistd::lseek(plane.fd.unwrap(), 0, ::nix::unistd::Whence::SeekSet);
            if plane.offset as libc::off_t > size {
                params.as_ref().post_error(
                    ParamError::OutOfBounds as u32,
                    format!("Invalid offset {} for plane {}.", plane.offset, plane.plane_idx),
                );
                return false;
            }
            if (plane.offset + plane.stride) as libc::off_t > size {
                params.as_ref().post_error(
                    ParamError::OutOfBounds as u32,
                    format!("Invalid stride {} for plane {}.", plane.stride, plane.plane_idx),
                );
                return false;
            }
            // Planes > 0 can be subsampled, in which case 'size' will be smaller
            // than expected.
            if plane.plane_idx == 0 && end as libc::off_t > size {
                params.as_ref().post_error(
                    ParamError::OutOfBounds as u32,
                    format!(
                        "Invalid stride ({}) or height ({}) for plane {}.",
                        plane.stride, height, plane.plane_idx
                    ),
                );
                return false;
            }
        }
    }
    true
}
<|endoftext|>


<commit_before>use std::num::{Zero, One};
use structs::vec::{Vec2, Vec3, Vec2MulRhs, Vec3MulRhs};
use structs::mat::{Mat1, Mat2, Mat3, Mat3MulRhs, Mat2MulRhs};
use traits::operations::{Inv, Det};
use traits::structure::{Row, Col};

// some specializations:
impl<N: Num + Clone> Inv for Mat1<N> {
    #[inline]
    fn inv_cpy(m: &Mat1<N>) -> Option<Mat1<N>> {
        let mut res = m.clone();

        if res.inv() {
            Some(res)
        }
        else {
            None
        }
    }

    #[inline]
    fn inv(&mut self) -> bool {
        if self.m11.is_zero() {
            false
        }
        else {
            let _1: N = One::one();

            self.m11 = _1 / Det::det(self);
            true
        }
    }
}

impl<N: Num + Clone> Inv for Mat2<N> {
    #[inline]
    fn inv_cpy(m: &Mat2<N>) -> Option<Mat2<N>> {
        let mut res = m.clone();

        if res.inv() {
            Some(res)
        }
        else {
            None
        }
    }

    #[inline]
    fn inv(&mut self) -> bool {
        let det = Det::det(self);

        if det.is_zero() {
            false
        }
        else {
            *self = Mat2::new(
                self.m22 / det , -self.m12 / det,
                -self.m21 / det, self.m11 / det);

            true
        }
    }
}

impl<N: Num + Clone> Inv for Mat3<N> {
    #[inline]
    fn inv_cpy(m: &Mat3<N>) -> Option<Mat3<N>> {
        let mut res = m.clone();

        if res.inv() {
            Some(res)
        }
        else {
            None
        }
    }

    #[inline]
    fn inv(&mut self) -> bool {
        let minor_m12_m23 = self.m22 * self.m33 - self.m32 * self.m23;
        let minor_m11_m23 = self.m21 * self.m33 - self.m31 * self.m23;
        let minor_m11_m22 = self.m21 * self.m32 - self.m31 * self.m22;

        let det = self.m11 * minor_m12_m23 - self.m12 * minor_m11_m23 + self.m13 * minor_m11_m22;

        if det.is_zero() {
            false
        }
        else {
            *self = Mat3::new(
                (minor_m12_m23 / det),
                ((self.m13 * self.m32 - self.m33 * self.m12) / det),
                ((self.m12 * self.m23 - self.m22 * self.m13) / det),

                (-minor_m11_m23 / det),
                ((self.m11 * self.m33 - self.m31 * self.m13) / det),
                ((self.m13 * self.m21 - self.m23 * self.m11) / det),

                (minor_m11_m22  / det),
                ((self.m12 * self.m31 - self.m32 * self.m11) / det),
                ((self.m11 * self.m22 - self.m21 * self.m12) / det)
                );

            true
        }
    }
}

impl<N: Num + Clone> Det<N> for Mat1<N> {
    #[inline]
    fn det(m: &Mat1<N>) -> N {
        m.m11.clone()
    }
}

impl<N: Num> Det<N> for Mat2<N> {
    #[inline]
    fn det(m: &Mat2<N>) -> N {
        m.m11 * m.m22 - m.m21 * m.m12
    }
}

impl<N: Num> Det<N> for Mat3<N> {
    #[inline]
    fn det(m: &Mat3<N>) -> N {
        let minor_m12_m23 = m.m22 * m.m33 - m.m32 * m.m23;
        let minor_m11_m23 = m.m21 * m.m33 - m.m31 * m.m23;
        let minor_m11_m22 = m.m21 * m.m32 - m.m31 * m.m22;

        m.m11 * minor_m12_m23 - m.m12 * minor_m11_m23 + m.m13 * minor_m11_m22
    }
}

impl<N: Clone> Row<Vec3<N>> for Mat3<N> {
    #[inline]
    fn nrows(&self) -> uint {
        3
    }

    #[inline]
    fn row(&self, i: uint) -> Vec3<N> {
        match i {
            0 => Vec3::new(self.m11.clone(), self.m12.clone(), self.m13.clone()),
            1 => Vec3::new(self.m21.clone(), self.m22.clone(), self.m23.clone()),
            2 => Vec3::new(self.m31.clone(), self.m32.clone(), self.m33.clone()),
            _ => fail!(format!("Index out of range: 3d matrices do not have {} rows.",  i))
        }
    }

    #[inline]
    fn set_row(&mut self, i: uint, r: Vec3<N>) {
        match i {
            0 => {
                self.m11 = r.x.clone();
                self.m12 = r.y.clone();
                self.m13 = r.z;
            },
            1 => {
                self.m21 = r.x.clone();
                self.m22 = r.y.clone();
                self.m23 = r.z;
            },
            2 => {
                self.m31 = r.x.clone();
                self.m32 = r.y.clone();
                self.m33 = r.z;
            },
            _ => fail!(format!("Index out of range: 3d matrices do not have {} rows.",  i))

        }
    }
}

impl<N: Clone> Col<Vec3<N>> for Mat3<N> {
    #[inline]
    fn ncols(&self) -> uint {
        3
    }

    #[inline]
    fn col(&self, i: uint) -> Vec3<N> {
        match i {
            0 => Vec3::new(self.m11.clone(), self.m21.clone(), self.m31.clone()),
            1 => Vec3::new(self.m12.clone(), self.m22.clone(), self.m32.clone()),
            2 => Vec3::new(self.m13.clone(), self.m23.clone(), self.m33.clone()),
            _ => fail!(format!("Index out of range: 3d matrices do not have {} cols.", i))
        }
    }

    #[inline]
    fn set_col(&mut self, i: uint, r: Vec3<N>) {
        match i {
            0 => {
                self.m11 = r.x.clone();
                self.m21 = r.y.clone();
                self.m31 = r.z;
            },
            1 => {
                self.m12 = r.x.clone();
                self.m22 = r.y.clone();
                self.m32 = r.z;
            },
            2 => {
                self.m13 = r.x.clone();
                self.m23 = r.y.clone();
                self.m33 = r.z;
            },
            _ => fail!(format!("Index out of range: 3d matrices do not have {} cols.", i))

        }
    }
}

impl<N: Mul<N, N> + Add<N, N>> Mat3MulRhs<N, Mat3<N>> for Mat3<N> {
    #[inline]
    fn binop(left: &Mat3<N>, right: &Mat3<N>) -> Mat3<N> {
        Mat3::new(
            left.m11 * right.m11 + left.m12 * right.m21 + left.m13 * right.m31,
            left.m11 * right.m12 + left.m12 * right.m22 + left.m13 * right.m32,
            left.m11 * right.m13 + left.m12 * right.m23 + left.m13 * right.m33,

            left.m21 * right.m11 + left.m22 * right.m21 + left.m23 * right.m31,
            left.m21 * right.m12 + left.m22 * right.m22 + left.m23 * right.m32,
            left.m21 * right.m13 + left.m22 * right.m23 + left.m23 * right.m33,

            left.m31 * right.m11 + left.m32 * right.m21 + left.m33 * right.m31,
            left.m31 * right.m12 + left.m32 * right.m22 + left.m33 * right.m32,
            left.m31 * right.m13 + left.m32 * right.m23 + left.m33 * right.m33
        )
    }
}

impl<N: Mul<N, N> + Add<N, N>> Mat2MulRhs<N, Mat2<N>> for Mat2<N> {
    #[inline(always)]
    fn binop(left: &Mat2<N>, right: &Mat2<N>) -> Mat2<N> {
        Mat2::new(
            left.m11 * right.m11 + left.m12 * right.m21,
            left.m11 * right.m12 + left.m12 * right.m22,

            left.m21 * right.m11 + left.m22 * right.m21,
            left.m21 * right.m12 + left.m22 * right.m22
        )
    }
}

impl<N: Mul<N, N> + Add<N, N>> Mat3MulRhs<N, Vec3<N>> for Vec3<N> {
    #[inline(always)]
    fn binop(left: &Mat3<N>, right: &Vec3<N>) -> Vec3<N> {
        Vec3::new(
            left.m11 * right.x + left.m12 * right.y + left.m13 * right.z,
            left.m21 * right.x + left.m22 * right.y + left.m23 * right.z,
            left.m31 * right.x + left.m32 * right.y + left.m33 * right.z
        )
    }
}

impl<N: Mul<N, N> + Add<N, N>> Vec3MulRhs<N, Vec3<N>> for Mat3<N> {
    #[inline(always)]
    fn binop(left: &Vec3<N>, right: &Mat3<N>) -> Vec3<N> {
        Vec3::new(
            left.x * right.m11 + left.y * right.m21 + left.z * right.m31,
            left.x * right.m12 + left.y * right.m22 + left.z * right.m32,
            left.x * right.m13 + left.y * right.m23 + left.z * right.m33
        )
    }
}

impl<N: Mul<N, N> + Add<N, N>> Vec2MulRhs<N, Vec2<N>> for Mat2<N> {
    #[inline(always)]
    fn binop(left: &Vec2<N>, right: &Mat2<N>) -> Vec2<N> {
        Vec2::new(
            left.x * right.m11 + left.y * right.m21,
            left.x * right.m12 + left.y * right.m22
        )
    }
}

impl<N: Mul<N, N> + Add<N, N>> Mat2MulRhs<N, Vec2<N>> for Vec2<N> {
    #[inline(always)]
    fn binop(left: &Mat2<N>, right: &Vec2<N>) -> Vec2<N> {
        Vec2::new(
            left.m11 * right.x + left.m12 * right.y,
            left.m21 * right.x + left.m22 * right.y
        )
    }
}
<commit_msg>Do not compute the matrix inverse if the determinant is near-zero.<commit_after>use std::num::{Zero, One};
use structs::vec::{Vec2, Vec3, Vec2MulRhs, Vec3MulRhs};
use structs::mat::{Mat1, Mat2, Mat3, Mat3MulRhs, Mat2MulRhs};
use traits::operations::{Inv, Det, ApproxEq};
use traits::structure::{Row, Col};

// some specializations:
impl<N: Num + ApproxEq<N> + Clone> Inv for Mat1<N> {
    #[inline]
    fn inv_cpy(m: &Mat1<N>) -> Option<Mat1<N>> {
        let mut res = m.clone();

        if res.inv() {
            Some(res)
        }
        else {
            None
        }
    }

    #[inline]
    fn inv(&mut self) -> bool {
        if ApproxEq::approx_eq(&self.m11, &Zero::zero()) {
            false
        }
        else {
            let _1: N = One::one();

            self.m11 = _1 / Det::det(self);
            true
        }
    }
}

impl<N: Num + ApproxEq<N> + Clone> Inv for Mat2<N> {
    #[inline]
    fn inv_cpy(m: &Mat2<N>) -> Option<Mat2<N>> {
        let mut res = m.clone();

        if res.inv() {
            Some(res)
        }
        else {
            None
        }
    }

    #[inline]
    fn inv(&mut self) -> bool {
        let det = Det::det(self);

        if ApproxEq::approx_eq(&det, &Zero::zero()) {
            false
        }
        else {
            *self = Mat2::new(
                self.m22 / det , -self.m12 / det,
                -self.m21 / det, self.m11 / det);

            true
        }
    }
}

impl<N: Num + ApproxEq<N> + Clone> Inv for Mat3<N> {
    #[inline]
    fn inv_cpy(m: &Mat3<N>) -> Option<Mat3<N>> {
        let mut res = m.clone();

        if res.inv() {
            Some(res)
        }
        else {
            None
        }
    }

    #[inline]
    fn inv(&mut self) -> bool {
        let minor_m12_m23 = self.m22 * self.m33 - self.m32 * self.m23;
        let minor_m11_m23 = self.m21 * self.m33 - self.m31 * self.m23;
        let minor_m11_m22 = self.m21 * self.m32 - self.m31 * self.m22;

        let det = self.m11 * minor_m12_m23 - self.m12 * minor_m11_m23 + self.m13 * minor_m11_m22;

        if ApproxEq::approx_eq(&det, &Zero::zero()) {
            false
        }
        else {
            *self = Mat3::new(
                (minor_m12_m23 / det),
                ((self.m13 * self.m32 - self.m33 * self.m12) / det),
                ((self.m12 * self.m23 - self.m22 * self.m13) / det),

                (-minor_m11_m23 / det),
                ((self.m11 * self.m33 - self.m31 * self.m13) / det),
                ((self.m13 * self.m21 - self.m23 * self.m11) / det),

                (minor_m11_m22  / det),
                ((self.m12 * self.m31 - self.m32 * self.m11) / det),
                ((self.m11 * self.m22 - self.m21 * self.m12) / det)
                );

            true
        }
    }
}

impl<N: Num + Clone> Det<N> for Mat1<N> {
    #[inline]
    fn det(m: &Mat1<N>) -> N {
        m.m11.clone()
    }
}

impl<N: Num> Det<N> for Mat2<N> {
    #[inline]
    fn det(m: &Mat2<N>) -> N {
        m.m11 * m.m22 - m.m21 * m.m12
    }
}

impl<N: Num> Det<N> for Mat3<N> {
    #[inline]
    fn det(m: &Mat3<N>) -> N {
        let minor_m12_m23 = m.m22 * m.m33 - m.m32 * m.m23;
        let minor_m11_m23 = m.m21 * m.m33 - m.m31 * m.m23;
        let minor_m11_m22 = m.m21 * m.m32 - m.m31 * m.m22;

        m.m11 * minor_m12_m23 - m.m12 * minor_m11_m23 + m.m13 * minor_m11_m22
    }
}

impl<N: Clone> Row<Vec3<N>> for Mat3<N> {
    #[inline]
    fn nrows(&self) -> uint {
        3
    }

    #[inline]
    fn row(&self, i: uint) -> Vec3<N> {
        match i {
            0 => Vec3::new(self.m11.clone(), self.m12.clone(), self.m13.clone()),
            1 => Vec3::new(self.m21.clone(), self.m22.clone(), self.m23.clone()),
            2 => Vec3::new(self.m31.clone(), self.m32.clone(), self.m33.clone()),
            _ => fail!(format!("Index out of range: 3d matrices do not have {} rows.",  i))
        }
    }

    #[inline]
    fn set_row(&mut self, i: uint, r: Vec3<N>) {
        match i {
            0 => {
                self.m11 = r.x.clone();
                self.m12 = r.y.clone();
                self.m13 = r.z;
            },
            1 => {
                self.m21 = r.x.clone();
                self.m22 = r.y.clone();
                self.m23 = r.z;
            },
            2 => {
                self.m31 = r.x.clone();
                self.m32 = r.y.clone();
                self.m33 = r.z;
            },
            _ => fail!(format!("Index out of range: 3d matrices do not have {} rows.",  i))

        }
    }
}

impl<N: Clone> Col<Vec3<N>> for Mat3<N> {
    #[inline]
    fn ncols(&self) -> uint {
        3
    }

    #[inline]
    fn col(&self, i: uint) -> Vec3<N> {
        match i {
            0 => Vec3::new(self.m11.clone(), self.m21.clone(), self.m31.clone()),
            1 => Vec3::new(self.m12.clone(), self.m22.clone(), self.m32.clone()),
            2 => Vec3::new(self.m13.clone(), self.m23.clone(), self.m33.clone()),
            _ => fail!(format!("Index out of range: 3d matrices do not have {} cols.", i))
        }
    }

    #[inline]
    fn set_col(&mut self, i: uint, r: Vec3<N>) {
        match i {
            0 => {
                self.m11 = r.x.clone();
                self.m21 = r.y.clone();
                self.m31 = r.z;
            },
            1 => {
                self.m12 = r.x.clone();
                self.m22 = r.y.clone();
                self.m32 = r.z;
            },
            2 => {
                self.m13 = r.x.clone();
                self.m23 = r.y.clone();
                self.m33 = r.z;
            },
            _ => fail!(format!("Index out of range: 3d matrices do not have {} cols.", i))

        }
    }
}

impl<N: Mul<N, N> + Add<N, N>> Mat3MulRhs<N, Mat3<N>> for Mat3<N> {
    #[inline]
    fn binop(left: &Mat3<N>, right: &Mat3<N>) -> Mat3<N> {
        Mat3::new(
            left.m11 * right.m11 + left.m12 * right.m21 + left.m13 * right.m31,
            left.m11 * right.m12 + left.m12 * right.m22 + left.m13 * right.m32,
            left.m11 * right.m13 + left.m12 * right.m23 + left.m13 * right.m33,

            left.m21 * right.m11 + left.m22 * right.m21 + left.m23 * right.m31,
            left.m21 * right.m12 + left.m22 * right.m22 + left.m23 * right.m32,
            left.m21 * right.m13 + left.m22 * right.m23 + left.m23 * right.m33,

            left.m31 * right.m11 + left.m32 * right.m21 + left.m33 * right.m31,
            left.m31 * right.m12 + left.m32 * right.m22 + left.m33 * right.m32,
            left.m31 * right.m13 + left.m32 * right.m23 + left.m33 * right.m33
        )
    }
}

impl<N: Mul<N, N> + Add<N, N>> Mat2MulRhs<N, Mat2<N>> for Mat2<N> {
    #[inline(always)]
    fn binop(left: &Mat2<N>, right: &Mat2<N>) -> Mat2<N> {
        Mat2::new(
            left.m11 * right.m11 + left.m12 * right.m21,
            left.m11 * right.m12 + left.m12 * right.m22,

            left.m21 * right.m11 + left.m22 * right.m21,
            left.m21 * right.m12 + left.m22 * right.m22
        )
    }
}

impl<N: Mul<N, N> + Add<N, N>> Mat3MulRhs<N, Vec3<N>> for Vec3<N> {
    #[inline(always)]
    fn binop(left: &Mat3<N>, right: &Vec3<N>) -> Vec3<N> {
        Vec3::new(
            left.m11 * right.x + left.m12 * right.y + left.m13 * right.z,
            left.m21 * right.x + left.m22 * right.y + left.m23 * right.z,
            left.m31 * right.x + left.m32 * right.y + left.m33 * right.z
        )
    }
}

impl<N: Mul<N, N> + Add<N, N>> Vec3MulRhs<N, Vec3<N>> for Mat3<N> {
    #[inline(always)]
    fn binop(left: &Vec3<N>, right: &Mat3<N>) -> Vec3<N> {
        Vec3::new(
            left.x * right.m11 + left.y * right.m21 + left.z * right.m31,
            left.x * right.m12 + left.y * right.m22 + left.z * right.m32,
            left.x * right.m13 + left.y * right.m23 + left.z * right.m33
        )
    }
}

impl<N: Mul<N, N> + Add<N, N>> Vec2MulRhs<N, Vec2<N>> for Mat2<N> {
    #[inline(always)]
    fn binop(left: &Vec2<N>, right: &Mat2<N>) -> Vec2<N> {
        Vec2::new(
            left.x * right.m11 + left.y * right.m21,
            left.x * right.m12 + left.y * right.m22
        )
    }
}

impl<N: Mul<N, N> + Add<N, N>> Mat2MulRhs<N, Vec2<N>> for Vec2<N> {
    #[inline(always)]
    fn binop(left: &Mat2<N>, right: &Vec2<N>) -> Vec2<N> {
        Vec2::new(
            left.m11 * right.x + left.m12 * right.y,
            left.m21 * right.x + left.m22 * right.y
        )
    }
}
<|endoftext|>


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

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

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

impl Coordinate {
    fn move_in(&self, dir: Direction, delta: usize) -> Coordinate {
        match dir {
            Direction::Up => Coordinate{row: self.row - delta, col: self.col},
            Direction::Down => Coordinate{row: self.row + delta, col: self.col},
            Direction::Left => Coordinate{row: self.row, col: self.col - delta},
            Direction::Right => Coordinate{row: self.row, col: self.col + delta},
        }
    }
}

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

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

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

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

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

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

impl Board {
    fn make_move(&mut self, m: Move) {
        self.set_squares(m, true)
    }

    fn undo_move(&mut self, m: Move) {
        self.set_squares(m, false)
    }

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

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

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

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

    // Unfortunately I kind of have to hard-code this.
    fn makes_board_unsolvable(&self, c: Coordinate) -> bool {
        (self.occupied(Coordinate{row: 3, col: 0}) && c == Coordinate{row: 2, col: 1}) ||
        (self.occupied(Coordinate{row: 3, col: 0}) && c == Coordinate{row: 1, col: 1}) ||
        (self.occupied(Coordinate{row: 2, col: 0}) && c == Coordinate{row: 1, col: 1}) ||
        (self.occupied(Coordinate{row: 2, col: 1}) && c == Coordinate{row: 3, col: 0}) ||
        (self.occupied(Coordinate{row: 1, col: 1}) && c == Coordinate{row: 2, col: 0}) ||
        (self.occupied(Coordinate{row: 1, col: 1}) && c == Coordinate{row: 3, col: 0})
    }

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

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

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

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

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

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

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

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

    (best, best_move)
}

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

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

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

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

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

    if interactive {
        let (result, best_move) = minimax_alpha_beta(starting_player, &mut starting_board, GameResult::PlaceholderJunSeok, GameResult::PlaceholderYeonSeung);
        println!("{:?}", result);
        println!("{:?}", best_move);
        match best_move {
            Some(x) => {
                starting_board.make_move(x);
                starting_board.print();
            },
            None => (),
        }
        for legal_move in starting_board.legal_moves().iter() {
            println!("{:?}", legal_move);
        }
    }
}
<commit_msg>Implement board type<commit_after>#[derive(Copy, Clone, Debug)]
enum Player {
    YeonSeung,
    JunSeok,
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    (best, best_move)
}

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

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

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

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

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

    if interactive {
        let (result, best_move) = minimax_alpha_beta(starting_player, &mut starting_board, GameResult::PlaceholderJunSeok, GameResult::PlaceholderYeonSeung);
        println!("{:?}", result);
        println!("{:?}", best_move);
        match best_move {
            Some(x) => {
                starting_board.make_move(x);
                starting_board.print();
            },
            None => (),
        }
        for legal_move in starting_board.legal_moves().iter() {
            println!("{:?}", legal_move);
        }
    }
}
<|endoftext|>


<commit_before>use nom::{
  IResult,
  bytes::complete::{
      tag,
      take_while1,
      take,
  },
  multi::{
      many0,
  },
  combinator::{
      map,
      peek,
  },
  branch::alt,
  error::{
      Error,
      ErrorKind,
  },
};
use std::fmt;

// This starts karma tokenizer stream
// TODO: make fancy but for now basic objective
// TODO: we probs want to stress test the round-trip feature (use a fuzzer to help this)
// 1. Convert inbound &str to a token stream and work on that (for structural concerns)
// 2. support 2 types of braces '"' and '['/']' to support single type and open/close
// 3. suppport 3 types of karma, up, down, side
// 4. support single character or multiple character karma token, for now '++', '--', '+-' '±'
#[derive(Debug, PartialEq, Clone)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub enum KarmaToken{
    // Alphanumberic
    Text(String),
    // Whitespace (with a copy in so we can reproduce the string exactly) (only because we care if
    // there is whitespace before the karma or not)
    Space(String),
    // Only the " mark
    // TODO: quotation escaping?
    Quote,
    // Only the [ mark
    OpenBrace,
    // Only the ] mark
    CloseBrace,
    // Karma (only complete chunk makes it in) ('++', '--', '+-', '±' for now)
    // TODO: this is eager, will need to figure out if we want to deal with "+++" at all
    Karma(String)
}

impl <'a> fmt::Display for KarmaToken {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            KarmaToken::Text(t)    => write!(f, "{}", t),
            KarmaToken::Space(t)   => write!(f, "{}", t),
            KarmaToken::Karma(t)   => write!(f, "{}", t),
            KarmaToken::Quote      => write!(f, "\""),
            KarmaToken::OpenBrace  => write!(f, "["),
            KarmaToken::CloseBrace => write!(f, "]"),
        }
    }
}

pub fn all_token(input: &str) -> IResult<&str, Vec<KarmaToken>> {
    many0(token)(input)
}

// TODO: maybe instead of parsing in only whole karma chunk could be greedy
// and start only if its a whole chunk (for some) but if there's more,
// continue accumulating. "a+++" -> Text("a"), Karma("+++") for eg. Then parse
// the rightmost for valid karma value to use
//
// Look into just capturing all value in a karma, then doing some sort of iterative
// parsing to try to fit things together, and if it comes up with something workable
// use it otherwise reject it.
//
// Rightmost parse with space/eof after, will become a token, the rejects get shuffled
// back into a Text
fn token(input: &str) -> IResult<&str, KarmaToken> {
    alt((
        symbols,
        space,
        // TODO: should be able to make this work better if we break up the
        // rightmost_karma into 2 pieces, a karma parse, and a 'remaining text' parse
        // and then have one return karma, one return text, and then make text_or_karma
        // do a karma/symbol/rightmost parse and bail if it hits it. this will result in
        // a bit more text(), text() ... sequence but that should be overall harmless?
        text_or_karma,
    ))(input)
}

fn symbols(input: &str) -> IResult<&str, KarmaToken> {
    alt((
        //map(karma,       |k| KarmaToken::Karma(k.to_string())),
        map(tag("\""),   |_| KarmaToken::Quote),
        map(tag("["),    |_| KarmaToken::OpenBrace),
        map(tag("]"),    |_| KarmaToken::CloseBrace),
    ))(input)
}

fn karma(input: &str) -> IResult<&str, &str> {
    alt((tag("++"), tag("--"), tag("+-"), tag("±")))(input)
}

fn rightmost_karma(input: &str) -> IResult<&str, (&str, &str)> {
    let (_, candidate) = take_while1(|c:char| is_karma_symbol(c))(input)?;

    // Let's try a list of karma
    let klist = ["++", "--", "+-", "±"];
    let mut longest = "";

    for k in klist.iter() {
        // Try a rmatch approach first
        let v: Vec<_> = candidate.rmatch_indices(k).collect();
        println!("candidate: {:?}, k: {:?}", candidate, k);
        println!("rmatch: {:?}", v);

        // Check if there is any possible match at all
        if !v.is_empty() {
            // There is, let's verify if the index of the match is eol
            let (idx, _) = v.get(0).unwrap();
            let offset = candidate.len() - k.len();

            if idx == &offset {
                if k.len() >= longest.len() {
                    longest = k;
                }
            }
        }
    }

    // If longest is empty, nothing matched
    if longest.is_empty() {
        Err(nom::Err::Error(Error::new(input, ErrorKind::Tag)))
    } else {
        // Returns the input with the text portion sliced off, also return the karma matched to
        // Memory layout is
        // [input_slice, longest, tmp_input] and we want to return
        // [input_slice, [longest + tmp_input] via slicing the original input
        let input_slice = &candidate[
            0..(candidate.len() - longest.len())
        ];
        let ret_slice = &input[
            input_slice.len()..
        ];

        Ok((&ret_slice, (&input_slice, longest)))
    }
}

fn space(input: &str) -> IResult<&str, KarmaToken> {
    map(take_while1(|c:char| c.is_whitespace()), |s:&str| KarmaToken::Space(s.to_string()))(input)
}

fn is_karma_symbol(s: char) -> bool {
    let sym = vec!['+', '-', '±'];
    sym.contains(&s)
}

fn is_symbol(s: char) -> bool {
    let sym = vec!['"', '[', ']'];
    sym.contains(&s)
}

// Steps needed:
// 1. Take any non [whitespace, part of karma, or braces]
// 2. When take_while stops, do a peek parse of [whitespace/karma/braces]
// 3. [if fail] add the character to the string and resume take_while
// 4. [if succ] exit parser with a Text(ret)
fn text_or_karma(input: &str) -> IResult<&str, KarmaToken> {
    let mut ret = "".to_string();
    let mut cur_input = input;

    // Bail out if empty input
    if input.is_empty() {
        return Err(nom::Err::Error(Error::new(input, ErrorKind::Eof)));
    }

    loop {
        println!("========");
        println!("1i: {:?}", cur_input);
        println!("1r: {:?}", ret);

        // Force forward progress
        let par: IResult<&str, &str> = take_while1(
            |c:char| !c.is_whitespace() & !is_symbol(c) & !is_karma_symbol(c)
        )(cur_input);
        println!("1p: {:?}", par);
        if let Ok((input, tok)) = par {
            cur_input = input;
            ret.push_str(tok);
        }

        println!("");
        println!("2i: {:?}", cur_input);
        println!("2r: {:?}", ret);

        // Check if it is a whitespace or symbol parse
        let par = peek(alt((space, symbols)))(cur_input);
        println!("2p: {:?}", par);
        match par {
            Ok(_)  => break,
            Err(_) => {
                // Check if it hit eof, if so exit
                if cur_input.is_empty() {
                    break;
                }

                // Check if its a rightmost karma parse
                let kpar = rightmost_karma(cur_input);
                println!("2k: {:?}", kpar);
                match kpar {
                    Ok((input, (txt, kar))) => {
                        // Check if the returned txt is empty, if so then its a valid karma
                        // and we should bail here to allow the karma tag to parse it?
                        //
                        // TODO: identify if we should return early instead of exit early with a
                        // karma parse
                        println!("********");
                        println!("cur_input: {:?}", cur_input);
                        println!("ret      : {:?}", ret);
                        println!("input    : {:?}", input);
                        println!("karma    : {:?}", kar);
                        println!("kar-text : {:?}", txt);
                        println!("********");

                        // If ret and txt is empty, return a karma parse
                        if txt.is_empty() & ret.is_empty() {
                            // Do a karma parse
                            return map(karma, |k| KarmaToken::Karma(k.to_string()))(cur_input);

                        } else if txt.is_empty() {
                            break;

                        } else {
                            cur_input = input;
                            ret.push_str(txt);
                        }
                    },
                    Err(_) => {
                        // It did not parse, grab the next char and append it and continue
                        let (input, tok) = take(1usize)(cur_input)?;
                        cur_input = input;
                        ret.push_str(tok);
                    },
                }

                println!("");
                println!("3i: {:?}", cur_input);
                println!("3r: {:?}", ret);
            },
        }
    }

    let stuff = Ok((cur_input, KarmaToken::Text(ret)));
    println!("========");
    println!("ret: {:?}", stuff);
    stuff
}

#[cfg(test)]
macro_rules! space {
    ($data:expr) => {
        KarmaToken::Space($data.to_string())
    }
}

#[cfg(test)]
macro_rules! text {
    ($data:expr) => {
        KarmaToken::Text($data.to_string())
    }
}

#[cfg(test)]
macro_rules! karma {
    ($data:expr) => {
        KarmaToken::Karma($data.to_string())
    }
}

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

    #[test]
    fn test_rightmost_valid() {
        assert_eq!(
            rightmost_karma("--++"),
            Ok(("++", ("--", "++")))
        );
    }

    #[test]
    fn test_rightmost_only_karma() {
        assert_eq!(
            rightmost_karma("++"),
            Ok(("++", ("", "++")))
        );
    }

    #[test]
    fn test_rightmost_text_before() {
        assert_eq!(
            rightmost_karma("text++"),
            Err(nom::Err::Error(Error::new("text++", ErrorKind::TakeWhile1)))
        );
    }

    #[test]
    fn test_rightmost_text_after() {
        assert_eq!(
            rightmost_karma("++text"),
            Ok(("++text", ("", "++")))
        );
    }

    #[test]
    fn test_karma_run() {
        assert_eq!(
            all_token("++--++"),
            Ok(("", vec![text!("++--"), karma!("++")]))
        );
    }

    #[test]
    fn test_karma_incomplete_run() {
        assert_eq!(
            all_token("+++"),
            Ok(("", vec![text!("+"), karma!("++")]))
        );
    }

    // TODO: this test depends on us supporting "+-" but not "-+" find alternatives later
    #[test]
    fn test_karma_incomplete_not_valid_run() {
        assert_eq!(
            all_token("+-+"),
            Ok(("", vec![text!("+-+")]))
        );
    }

    #[test]
    fn test_quote() {
        assert_eq!(
            all_token("\"\"\""),
            Ok(("", vec![KarmaToken::Quote; 3]))
        );
    }

    #[test]
    fn test_brace() {
        assert_eq!(
            all_token("][]]"),
            Ok(("", vec![
                KarmaToken::CloseBrace,
                KarmaToken::OpenBrace,
                KarmaToken::CloseBrace,
                KarmaToken::CloseBrace
            ]))
        );
    }

    #[test]
    fn test_whitespace() {
        assert_eq!(
            all_token(" \t"),
            Ok(("", vec![space!(" \t")]))
        );
    }

    #[test]
    fn test_whitespace_block() {
        assert_eq!(
            all_token(" ]\t[  "),
            Ok(("", vec![
                space!(" "),
                KarmaToken::CloseBrace,
                space!("\t"),
                KarmaToken::OpenBrace,
                space!("  ")
            ]))
        );
    }

    #[test]
    fn test_text() {
        assert_eq!(
            all_token("this藏test-d"),
            Ok(("", vec![text!("this藏test-d")]))
        );
    }

    #[test]
    fn test_text_starting_ending_hypen() {
        assert_eq!(
            all_token("-test-"),
            Ok(("", vec![text!("-test-")]))
        );
    }

    // TODO: this test depends on us supporting "+-" but not "-+" find alternatives later
    #[test]
    fn test_text_unsupported_karma() {
        assert_eq!(
            all_token("text-+"),
            Ok(("", vec![text!("text-+")]))
        );
    }

    #[test]
    fn test_text_block() {
        assert_eq!(
            all_token("this\t[\"藏  ++]"),
            Ok(("", vec![
                text!("this"),
                space!("\t"),
                KarmaToken::OpenBrace,
                KarmaToken::Quote,
                text!("藏"),
                space!("  "),
                karma!("++"),
                KarmaToken::CloseBrace,
            ]))
        );
    }

    #[test]
    fn test_roundtrip() {
        let text = "this\t[\"藏  ++]";
        let parse = all_token(text).unwrap().1;

        assert_eq!(parse.iter().map(|t| t.to_string()).collect::<Vec<String>>().join(""), text);
    }

    #[test]
    fn test_fuzz_one() {
        assert_eq!(
            all_token("±+\""),
            Ok(("", vec![text!("±+"), KarmaToken::Quote]))
        );
    }
}
<commit_msg>Simplified the text parser by breaking the karma portion out and modifying the karma_run one a bit<commit_after>use nom::{
  IResult,
  bytes::complete::{
      tag,
      take_while1,
      take,
  },
  multi::{
      many0,
  },
  combinator::{
      map,
      peek,
  },
  branch::alt,
  error::{
      Error,
      ErrorKind,
  },
};
use std::fmt;

// This starts karma tokenizer stream
// TODO: make fancy but for now basic objective
// TODO: we probs want to stress test the round-trip feature (use a fuzzer to help this)
// 1. Convert inbound &str to a token stream and work on that (for structural concerns)
// 2. support 2 types of braces '"' and '['/']' to support single type and open/close
// 3. suppport 3 types of karma, up, down, side
// 4. support single character or multiple character karma token, for now '++', '--', '+-' '±'
#[derive(Debug, PartialEq, Clone)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub enum KarmaToken{
    // Alphanumberic
    Text(String),
    // Whitespace (with a copy in so we can reproduce the string exactly) (only because we care if
    // there is whitespace before the karma or not)
    Space(String),
    // Only the " mark
    // TODO: quotation escaping?
    Quote,
    // Only the [ mark
    OpenBrace,
    // Only the ] mark
    CloseBrace,
    // Karma (only complete chunk makes it in) ('++', '--', '+-', '±' for now)
    // TODO: this is eager, will need to figure out if we want to deal with "+++" at all
    Karma(String),
    // TODO: Consider KText(String) for karma runs since it'll have slightly more restrictions than
    // Text
}

impl <'a> fmt::Display for KarmaToken {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            KarmaToken::Text(t)    => write!(f, "{}", t),
            KarmaToken::Space(t)   => write!(f, "{}", t),
            KarmaToken::Karma(t)   => write!(f, "{}", t),
            KarmaToken::Quote      => write!(f, "\""),
            KarmaToken::OpenBrace  => write!(f, "["),
            KarmaToken::CloseBrace => write!(f, "]"),
        }
    }
}

pub fn all_token(input: &str) -> IResult<&str, Vec<KarmaToken>> {
    many0(token)(input)
}

// TODO: maybe instead of parsing in only whole karma chunk could be greedy
// and start only if its a whole chunk (for some) but if there's more,
// continue accumulating. "a+++" -> Text("a"), Karma("+++") for eg. Then parse
// the rightmost for valid karma value to use
//
// Look into just capturing all value in a karma, then doing some sort of iterative
// parsing to try to fit things together, and if it comes up with something workable
// use it otherwise reject it.
//
// Rightmost parse with space/eof after, will become a token, the rejects get shuffled
// back into a Text
fn token(input: &str) -> IResult<&str, KarmaToken> {
    alt((
        symbols,
        space,
        karma_run,
        karma,
        text,
    ))(input)
}

fn symbols(input: &str) -> IResult<&str, KarmaToken> {
    alt((
        map(tag("\""),   |_| KarmaToken::Quote),
        map(tag("["),    |_| KarmaToken::OpenBrace),
        map(tag("]"),    |_| KarmaToken::CloseBrace),
    ))(input)
}

fn space(input: &str) -> IResult<&str, KarmaToken> {
    map(take_while1(|c:char| c.is_whitespace()), |s:&str| KarmaToken::Space(s.to_string()))(input)
}

fn karma(input: &str) -> IResult<&str, KarmaToken> {
    map(
        alt((tag("++"), tag("--"), tag("+-"), tag("±"))),
        |k:&str| KarmaToken::Karma(k.to_string())
    )(input)
}

fn karma_run(input: &str) -> IResult<&str, KarmaToken> {
    let (_, candidate) = take_while1(|c:char| is_karma_symbol(c))(input)?;

    // Let's try a list of karma
    let klist = ["++", "--", "+-", "±"];
    let mut longest = "";

    for k in klist.iter() {
        // Short circuit this check if
        // 1. karma is shorter than longest match, it won't win
        // 2. candidate is shorter than the karma to be checked
        if (longest.len() > k.len()) | (k.len() > candidate.len()) {
            continue;
        }

        // Try a rmatch approach first
        let v: Vec<_> = candidate.rmatch_indices(k).collect();
        println!("candidate: {:?}, k: {:?}", candidate, k);
        println!("rmatch: {:?}", v);
        println!("c-len: {:?}, k-len: {:?}, c-k-len: {:?}", candidate.len(), k.len(), candidate.len() - k.len());

        // Check if there is any possible match at all
        if !v.is_empty() {
            // There is, let's verify if the index of the match is eol
            let (idx, _) = v.get(0).unwrap();
            let offset = candidate.len() - k.len();

            if idx == &offset {
                if k.len() >= longest.len() {
                    longest = k;
                }
            }
        }
    }

    // If longest is empty, nothing matched
    if longest.is_empty() {
        Err(nom::Err::Error(Error::new(input, ErrorKind::Tag)))
    } else {
        // Returns the input with the text portion sliced off, also return the karma matched to
        // Memory layout is
        // [input_slice, longest, tmp_input] and we want to return
        // [input_slice, [longest + tmp_input] via slicing the original input
        let input_slice = &candidate[
            0..(candidate.len() - longest.len())
        ];
        let ret_slice = &input[
            input_slice.len()..
        ];

        // If text to be returned is empty, we only got the karma/other parse now
        if input_slice.is_empty() {
            Err(nom::Err::Error(Error::new(input, ErrorKind::Tag)))

        } else {
            println!("===");
            println!("Winning: {:?}, text: {:?}, input: {:?}", longest, &input_slice, &ret_slice);
            println!("===");

            Ok((&ret_slice, KarmaToken::Text(input_slice.to_string())))
        }
    }
}

fn is_karma_symbol(s: char) -> bool {
    let sym = vec!['+', '-', '±'];
    sym.contains(&s)
}

fn is_symbol(s: char) -> bool {
    let sym = vec!['"', '[', ']'];
    sym.contains(&s)
}

// Steps needed:
// 1. Take any non [whitespace, part of karma, or braces]
// 2. When take_while stops, do a peek parse of [whitespace/karma/braces]
// 3. [if fail] add the character to the string and resume take_while
// 4. [if succ] exit parser with a Text(ret)
fn text(input: &str) -> IResult<&str, KarmaToken> {
    let mut ret = "".to_string();
    let mut cur_input = input;

    // Bail out if empty input
    if input.is_empty() {
        return Err(nom::Err::Error(Error::new(input, ErrorKind::Eof)));
    }

    loop {
        println!("========");
        println!("1i: {:?}", cur_input);
        println!("1r: {:?}", ret);

        // Force forward progress
        let par: IResult<&str, &str> = take_while1(
            |c:char| !c.is_whitespace() & !is_symbol(c) & !is_karma_symbol(c)
        )(cur_input);
        println!("1p: {:?}", par);
        if let Ok((input, tok)) = par {
            cur_input = input;
            ret.push_str(tok);
        }

        println!("");
        println!("2i: {:?}", cur_input);
        println!("2r: {:?}", ret);

        // Check if it is a whitespace or symbol parse
        let par = peek(alt((
            space,
            symbols,
            karma_run,
            karma,
        )))(cur_input);
        println!("2p: {:?}", par);
        match par {
            Ok(_)  => break,
            Err(_) => {
                // Check if it hit eof, if so exit
                if cur_input.is_empty() {
                    break;
                }

                let (input, tok) = take(1usize)(cur_input)?;
                cur_input = input;
                ret.push_str(tok);

                println!("");
                println!("3i: {:?}", cur_input);
                println!("3r: {:?}", ret);
            },
        }
    }

    let stuff = Ok((cur_input, KarmaToken::Text(ret)));
    println!("========");
    println!("ret: {:?}", stuff);
    stuff
}

#[cfg(test)]
macro_rules! space {
    ($data:expr) => {
        KarmaToken::Space($data.to_string())
    }
}

#[cfg(test)]
macro_rules! text {
    ($data:expr) => {
        KarmaToken::Text($data.to_string())
    }
}

#[cfg(test)]
macro_rules! karma {
    ($data:expr) => {
        KarmaToken::Karma($data.to_string())
    }
}

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

    #[test]
    fn test_rightmost_valid() {
        assert_eq!(
            karma_run("--++"),
            Ok(("++", text!("--")))
        );
    }

    #[test]
    fn test_rightmost_only_karma() {
        assert_eq!(
            karma_run("++"),
            Err(nom::Err::Error(Error::new("++", ErrorKind::Tag)))
        );
    }

    #[test]
    fn test_rightmost_text_before() {
        assert_eq!(
            karma_run("text++"),
            Err(nom::Err::Error(Error::new("text++", ErrorKind::TakeWhile1)))
        );
    }

    #[test]
    fn test_rightmost_text_after() {
        assert_eq!(
            karma_run("++text"),
            Err(nom::Err::Error(Error::new("++text", ErrorKind::Tag)))
        );
    }

    #[test]
    fn test_karma_run() {
        assert_eq!(
            all_token("++--++"),
            Ok(("", vec![text!("++--"), karma!("++")]))
        );
    }

    #[test]
    fn test_karma_incomplete_run() {
        assert_eq!(
            all_token("+++"),
            Ok(("", vec![text!("+"), karma!("++")]))
        );
    }

    // TODO: this test depends on us supporting "+-" but not "-+" find alternatives later
    #[test]
    fn test_karma_incomplete_not_valid_run() {
        assert_eq!(
            all_token("+-+"),
            Ok(("", vec![karma!("+-"), text!("+")]))
        );
    }

    #[test]
    fn test_quote() {
        assert_eq!(
            all_token("\"\"\""),
            Ok(("", vec![KarmaToken::Quote; 3]))
        );
    }

    #[test]
    fn test_brace() {
        assert_eq!(
            all_token("][]]"),
            Ok(("", vec![
                KarmaToken::CloseBrace,
                KarmaToken::OpenBrace,
                KarmaToken::CloseBrace,
                KarmaToken::CloseBrace
            ]))
        );
    }

    #[test]
    fn test_whitespace() {
        assert_eq!(
            all_token(" \t"),
            Ok(("", vec![space!(" \t")]))
        );
    }

    #[test]
    fn test_whitespace_block() {
        assert_eq!(
            all_token(" ]\t[  "),
            Ok(("", vec![
                space!(" "),
                KarmaToken::CloseBrace,
                space!("\t"),
                KarmaToken::OpenBrace,
                space!("  ")
            ]))
        );
    }

    #[test]
    fn test_text_plain() {
        assert_eq!(
            all_token("this藏test-d"),
            Ok(("", vec![text!("this藏test-d")]))
        );
    }

    #[test]
    fn test_text_starting_ending_hypen() {
        assert_eq!(
            all_token("-test-"),
            Ok(("", vec![text!("-test-")]))
        );
    }

    // TODO: this test depends on us supporting "+-" but not "-+" find alternatives later
    #[test]
    fn test_text_unsupported_karma() {
        assert_eq!(
            all_token("text-+"),
            Ok(("", vec![text!("text-+")]))
        );
    }

    #[test]
    fn test_text_block() {
        assert_eq!(
            all_token("this\t[\"藏  ++]"),
            Ok(("", vec![
                text!("this"),
                space!("\t"),
                KarmaToken::OpenBrace,
                KarmaToken::Quote,
                text!("藏"),
                space!("  "),
                karma!("++"),
                KarmaToken::CloseBrace,
            ]))
        );
    }

    #[test]
    fn test_roundtrip() {
        let text = "this\t[\"藏  ++]";
        let parse = all_token(text).unwrap().1;

        assert_eq!(parse.iter().map(|t| t.to_string()).collect::<Vec<String>>().join(""), text);
    }

    #[test]
    fn test_fuzz_one() {
        assert_eq!(
            all_token("±+\""),
            Ok(("", vec![karma!("±"), text!("+"), KarmaToken::Quote]))
        );
    }
}
<|endoftext|>