<commit_before>use std::iter::IntoIterator;

use syntax::ast;
use syntax::codemap::{DUMMY_SP, Span};
use syntax::ptr::P;

use invoke::{Invoke, Identity};

use ident::ToIdent;
use name::ToName;
use ty::TyBuilder;

use lifetime::IntoLifetime;

//////////////////////////////////////////////////////////////////////////////

pub trait IntoPath {
    fn into_path(self) -> ast::Path;
}

impl IntoPath for ast::Path {
    fn into_path(self) -> ast::Path {
        self
    }
}

impl IntoPath for ast::Ident {
    fn into_path(self) -> ast::Path {
        PathBuilder::new().id(self).build()
    }
}

impl<'a> IntoPath for &'a str {
    fn into_path(self) -> ast::Path {
        PathBuilder::new().id(self).build()
    }
}

impl IntoPath for String {
    fn into_path(self) -> ast::Path {
        (&*self).into_path()
    }
}

impl<'a, T> IntoPath for &'a [T] where T: ToIdent {
    fn into_path(self) -> ast::Path {
        PathBuilder::new().ids(self).build()
    }
}

//////////////////////////////////////////////////////////////////////////////

pub struct PathBuilder<F=Identity> {
    callback: F,
    span: Span,
    global: bool,
}

impl PathBuilder {
    pub fn new() -> Self {
        PathBuilder::with_callback(Identity)
    }
}

impl<F> PathBuilder<F>
    where F: Invoke<ast::Path>,
{
    pub fn with_callback(callback: F) -> Self {
        PathBuilder {
            callback: callback,
            span: DUMMY_SP,
            global: false,
        }
    }

    pub fn build(self, path: ast::Path) -> F::Result {
        self.callback.invoke(path)
    }

    /// Update the span to start from this location.
    pub fn span(mut self, span: Span) -> Self {
        self.span = span;
        self
    }

    pub fn global(mut self) -> Self {
        self.global = true;
        self
    }

    pub fn ids<I, T>(self, ids: I) -> PathSegmentsBuilder<F>
        where I: IntoIterator<Item=T>,
              T: ToIdent,
    {
        let mut ids = ids.into_iter();
        let id = ids.next().expect("passed path with no id");

        self.id(id).ids(ids)
    }

    pub fn id<I>(self, id: I) -> PathSegmentsBuilder<F>
        where I: ToIdent,
    {
        self.segment(id).build()
    }

    pub fn segment<I>(self, id: I)
        -> PathSegmentBuilder<PathSegmentsBuilder<F>>
        where I: ToIdent,
    {
        PathSegmentBuilder::with_callback(id, PathSegmentsBuilder {
            callback: self.callback,
            span: self.span,
            global: self.global,
            segments: Vec::new(),
        })
    }
}

//////////////////////////////////////////////////////////////////////////////

pub struct PathSegmentsBuilder<F=Identity> {
    callback: F,
    span: Span,
    global: bool,
    segments: Vec<ast::PathSegment>,
}

impl<F> PathSegmentsBuilder<F>
    where F: Invoke<ast::Path>,
{
    pub fn ids<I, T>(mut self, ids: I) -> PathSegmentsBuilder<F>
        where I: IntoIterator<Item=T>,
              T: ToIdent,
    {
        for id in ids {
            self = self.id(id);
        }

        self
    }

    pub fn id<T>(self, id: T) -> PathSegmentsBuilder<F>
        where T: ToIdent,
    {
        self.segment(id).build()
    }

    pub fn segment<T>(self, id: T) -> PathSegmentBuilder<Self>
        where T: ToIdent,
    {
        PathSegmentBuilder::with_callback(id, self)
    }

    pub fn build(self) -> F::Result {
        self.callback.invoke(ast::Path {
            span: self.span,
            global: self.global,
            segments: self.segments,
        })
    }
}

impl<F> Invoke<ast::PathSegment> for PathSegmentsBuilder<F> {
    type Result = Self;

    fn invoke(mut self, segment: ast::PathSegment) -> Self {
        self.segments.push(segment);
        self
    }
}

//////////////////////////////////////////////////////////////////////////////

pub struct PathSegmentBuilder<F=Identity> {
    callback: F,
    span: Span,
    id: ast::Ident,
    lifetimes: Vec<ast::Lifetime>,
    tys: Vec<P<ast::Ty>>,
    bindings: Vec<P<ast::TypeBinding>>,
}

impl<F> PathSegmentBuilder<F>
    where F: Invoke<ast::PathSegment>,
{
    pub fn with_callback<I>(id: I, callback: F) -> Self
        where I: ToIdent,
    {
        PathSegmentBuilder {
            callback: callback,
            span: DUMMY_SP,
            id: id.to_ident(),
            lifetimes: Vec::new(),
            tys: Vec::new(),
            bindings: Vec::new(),
        }
    }

    pub fn span(mut self, span: Span) -> Self {
        self.span = span;
        self
    }

    pub fn with_generics(self, generics: ast::Generics) -> Self {
        // Strip off the bounds.
        let lifetimes = generics.lifetimes.iter()
            .map(|lifetime_def| lifetime_def.lifetime);

        let tys = generics.ty_params.iter()
            .map(|ty_param| TyBuilder::new().id(ty_param.ident));

        self.with_lifetimes(lifetimes)
            .with_tys(tys)
    }

    pub fn with_lifetimes<I, L>(mut self, iter: I) -> Self
        where I: IntoIterator<Item=L>,
              L: IntoLifetime,
    {
        let iter = iter.into_iter().map(|lifetime| lifetime.into_lifetime());
        self.lifetimes.extend(iter);
        self
    }

    pub fn with_lifetime<L>(mut self, lifetime: L) -> Self
        where L: IntoLifetime,
    {
        self.lifetimes.push(lifetime.into_lifetime());
        self
    }

    pub fn lifetime<N>(self, name: N) -> Self
        where N: ToName,
    {
        let lifetime = ast::Lifetime {
            id: ast::DUMMY_NODE_ID,
            span: self.span,
            name: name.to_name(),
        };
        self.with_lifetime(lifetime)
    }

    pub fn with_tys<I>(mut self, iter: I) -> Self
        where I: IntoIterator<Item=P<ast::Ty>>,
    {
        self.tys.extend(iter);
        self
    }

    pub fn with_ty(mut self, ty: P<ast::Ty>) -> Self {
        self.tys.push(ty);
        self
    }

    pub fn ty(self) -> TyBuilder<Self> {
        TyBuilder::with_callback(self)
    }

    pub fn build(self) -> F::Result {
        let data = ast::AngleBracketedParameterData {
            lifetimes: self.lifetimes,
            types: P::from_vec(self.tys),
            bindings: P::from_vec(self.bindings),
        };

        let parameters = ast::PathParameters::AngleBracketed(data);

        self.callback.invoke(ast::PathSegment {
            identifier: self.id,
            parameters: parameters,
        })
    }
}

impl<F> Invoke<P<ast::Ty>> for PathSegmentBuilder<F>
    where F: Invoke<ast::PathSegment>
{
    type Result = Self;

    fn invoke(self, ty: P<ast::Ty>) -> Self {
        self.with_ty(ty)
    }
}
<commit_msg>feat(path): Add support for path type bindings<commit_after>use std::iter::IntoIterator;

use syntax::ast;
use syntax::codemap::{DUMMY_SP, Span};
use syntax::ptr::P;

use invoke::{Invoke, Identity};

use ident::ToIdent;
use name::ToName;
use ty::TyBuilder;

use lifetime::IntoLifetime;

//////////////////////////////////////////////////////////////////////////////

pub trait IntoPath {
    fn into_path(self) -> ast::Path;
}

impl IntoPath for ast::Path {
    fn into_path(self) -> ast::Path {
        self
    }
}

impl IntoPath for ast::Ident {
    fn into_path(self) -> ast::Path {
        PathBuilder::new().id(self).build()
    }
}

impl<'a> IntoPath for &'a str {
    fn into_path(self) -> ast::Path {
        PathBuilder::new().id(self).build()
    }
}

impl IntoPath for String {
    fn into_path(self) -> ast::Path {
        (&*self).into_path()
    }
}

impl<'a, T> IntoPath for &'a [T] where T: ToIdent {
    fn into_path(self) -> ast::Path {
        PathBuilder::new().ids(self).build()
    }
}

//////////////////////////////////////////////////////////////////////////////

pub struct PathBuilder<F=Identity> {
    callback: F,
    span: Span,
    global: bool,
}

impl PathBuilder {
    pub fn new() -> Self {
        PathBuilder::with_callback(Identity)
    }
}

impl<F> PathBuilder<F>
    where F: Invoke<ast::Path>,
{
    pub fn with_callback(callback: F) -> Self {
        PathBuilder {
            callback: callback,
            span: DUMMY_SP,
            global: false,
        }
    }

    pub fn build(self, path: ast::Path) -> F::Result {
        self.callback.invoke(path)
    }

    /// Update the span to start from this location.
    pub fn span(mut self, span: Span) -> Self {
        self.span = span;
        self
    }

    pub fn global(mut self) -> Self {
        self.global = true;
        self
    }

    pub fn ids<I, T>(self, ids: I) -> PathSegmentsBuilder<F>
        where I: IntoIterator<Item=T>,
              T: ToIdent,
    {
        let mut ids = ids.into_iter();
        let id = ids.next().expect("passed path with no id");

        self.id(id).ids(ids)
    }

    pub fn id<I>(self, id: I) -> PathSegmentsBuilder<F>
        where I: ToIdent,
    {
        self.segment(id).build()
    }

    pub fn segment<I>(self, id: I)
        -> PathSegmentBuilder<PathSegmentsBuilder<F>>
        where I: ToIdent,
    {
        PathSegmentBuilder::with_callback(id, PathSegmentsBuilder {
            callback: self.callback,
            span: self.span,
            global: self.global,
            segments: Vec::new(),
        })
    }
}

//////////////////////////////////////////////////////////////////////////////

pub struct PathSegmentsBuilder<F=Identity> {
    callback: F,
    span: Span,
    global: bool,
    segments: Vec<ast::PathSegment>,
}

impl<F> PathSegmentsBuilder<F>
    where F: Invoke<ast::Path>,
{
    pub fn ids<I, T>(mut self, ids: I) -> PathSegmentsBuilder<F>
        where I: IntoIterator<Item=T>,
              T: ToIdent,
    {
        for id in ids {
            self = self.id(id);
        }

        self
    }

    pub fn id<T>(self, id: T) -> PathSegmentsBuilder<F>
        where T: ToIdent,
    {
        self.segment(id).build()
    }

    pub fn segment<T>(self, id: T) -> PathSegmentBuilder<Self>
        where T: ToIdent,
    {
        PathSegmentBuilder::with_callback(id, self)
    }

    pub fn build(self) -> F::Result {
        self.callback.invoke(ast::Path {
            span: self.span,
            global: self.global,
            segments: self.segments,
        })
    }
}

impl<F> Invoke<ast::PathSegment> for PathSegmentsBuilder<F> {
    type Result = Self;

    fn invoke(mut self, segment: ast::PathSegment) -> Self {
        self.segments.push(segment);
        self
    }
}

//////////////////////////////////////////////////////////////////////////////

pub struct PathSegmentBuilder<F=Identity> {
    callback: F,
    span: Span,
    id: ast::Ident,
    lifetimes: Vec<ast::Lifetime>,
    tys: Vec<P<ast::Ty>>,
    bindings: Vec<P<ast::TypeBinding>>,
}

impl<F> PathSegmentBuilder<F>
    where F: Invoke<ast::PathSegment>,
{
    pub fn with_callback<I>(id: I, callback: F) -> Self
        where I: ToIdent,
    {
        PathSegmentBuilder {
            callback: callback,
            span: DUMMY_SP,
            id: id.to_ident(),
            lifetimes: Vec::new(),
            tys: Vec::new(),
            bindings: Vec::new(),
        }
    }

    pub fn span(mut self, span: Span) -> Self {
        self.span = span;
        self
    }

    pub fn with_generics(self, generics: ast::Generics) -> Self {
        // Strip off the bounds.
        let lifetimes = generics.lifetimes.iter()
            .map(|lifetime_def| lifetime_def.lifetime);

        let tys = generics.ty_params.iter()
            .map(|ty_param| TyBuilder::new().id(ty_param.ident));

        self.with_lifetimes(lifetimes)
            .with_tys(tys)
    }

    pub fn with_lifetimes<I, L>(mut self, iter: I) -> Self
        where I: IntoIterator<Item=L>,
              L: IntoLifetime,
    {
        let iter = iter.into_iter().map(|lifetime| lifetime.into_lifetime());
        self.lifetimes.extend(iter);
        self
    }

    pub fn with_lifetime<L>(mut self, lifetime: L) -> Self
        where L: IntoLifetime,
    {
        self.lifetimes.push(lifetime.into_lifetime());
        self
    }

    pub fn lifetime<N>(self, name: N) -> Self
        where N: ToName,
    {
        let lifetime = ast::Lifetime {
            id: ast::DUMMY_NODE_ID,
            span: self.span,
            name: name.to_name(),
        };
        self.with_lifetime(lifetime)
    }

    pub fn with_tys<I>(mut self, iter: I) -> Self
        where I: IntoIterator<Item=P<ast::Ty>>,
    {
        self.tys.extend(iter);
        self
    }

    pub fn with_ty(mut self, ty: P<ast::Ty>) -> Self {
        self.tys.push(ty);
        self
    }

    pub fn ty(self) -> TyBuilder<Self> {
        TyBuilder::with_callback(self)
    }

    pub fn with_binding(mut self, binding: P<ast::TypeBinding>) -> Self {
        self.bindings.push(binding);
        self
    }

    pub fn binding<T>(self, id: T) -> TyBuilder<TypeBindingBuilder<F>>
        where T: ToIdent,
    {
        TyBuilder::with_callback(TypeBindingBuilder {
            id: id.to_ident(),
            builder: self,
        })
    }

    pub fn build(self) -> F::Result {
        let data = ast::AngleBracketedParameterData {
            lifetimes: self.lifetimes,
            types: P::from_vec(self.tys),
            bindings: P::from_vec(self.bindings),
        };

        let parameters = ast::PathParameters::AngleBracketed(data);

        self.callback.invoke(ast::PathSegment {
            identifier: self.id,
            parameters: parameters,
        })
    }
}

impl<F> Invoke<P<ast::Ty>> for PathSegmentBuilder<F>
    where F: Invoke<ast::PathSegment>
{
    type Result = Self;

    fn invoke(self, ty: P<ast::Ty>) -> Self {
        self.with_ty(ty)
    }
}

//////////////////////////////////////////////////////////////////////////////

pub struct TypeBindingBuilder<F> {
    id: ast::Ident,
    builder: PathSegmentBuilder<F>,
}

impl<F> Invoke<P<ast::Ty>> for TypeBindingBuilder<F>
    where F: Invoke<ast::PathSegment>
{
    type Result = PathSegmentBuilder<F>;

    fn invoke(self, ty: P<ast::Ty>) -> Self::Result {
        let id = self.id;
        let span = self.builder.span;

        self.builder.with_binding(P(ast::TypeBinding {
            id: ast::DUMMY_NODE_ID,
            ident: id,
            ty: ty,
            span: span,
        }))
    }
}
<|endoftext|>


<commit_before>use std::cell::Cell;

use cairo::{self, MatrixTrait};

use attributes::Attribute;
use coord_units::CoordUnits;
use drawing_ctx::{self, RsvgDrawingCtx};
use handle::RsvgHandle;
use node::{NodeResult, NodeTrait, RsvgNode};
use parsers::parse;
use property_bag::PropertyBag;

coord_units!(ClipPathUnits, CoordUnits::UserSpaceOnUse);

pub struct NodeClipPath {
    units: Cell<ClipPathUnits>,
}

impl NodeClipPath {
    pub fn new() -> NodeClipPath {
        NodeClipPath {
            units: Cell::new(ClipPathUnits::default()),
        }
    }

    pub fn get_units(&self) -> ClipPathUnits {
        self.units.get()
    }

    pub fn to_cairo_context(
        &self,
        node: &RsvgNode,
        affine_before_clip: &cairo::Matrix,
        draw_ctx: *mut RsvgDrawingCtx,
    ) {
        let cascaded = node.get_cascaded_values();

        let clip_units = self.units.get();

        let orig_bbox = drawing_ctx::get_bbox(draw_ctx).clone();

        let child_matrix = if clip_units == ClipPathUnits(CoordUnits::ObjectBoundingBox) {
            let rect = orig_bbox.rect.unwrap();

            let mut bbtransform =
                cairo::Matrix::new(rect.width, 0.0, 0.0, rect.height, rect.x, rect.y);
            cairo::Matrix::multiply(&bbtransform, affine_before_clip)
        } else {
            *affine_before_clip
        };

        let cr = drawing_ctx::get_cairo_context(draw_ctx);
        let save_affine = cr.get_matrix();
        cr.set_matrix(child_matrix);

        node.draw_children(node, &cascaded, draw_ctx, false, true);

        cr.set_matrix(save_affine);

        // FIXME: this is an EPIC HACK to keep the clipping context from
        // accumulating bounding boxes.  We'll remove this later, when we
        // are able to extract bounding boxes from outside the
        // general drawing loop.
        drawing_ctx::set_bbox(draw_ctx, &orig_bbox);

        let cr = drawing_ctx::get_cairo_context(draw_ctx);
        cr.clip();
    }
}

impl NodeTrait for NodeClipPath {
    fn set_atts(&self, _: &RsvgNode, _: *const RsvgHandle, pbag: &PropertyBag) -> NodeResult {
        for (_key, attr, value) in pbag.iter() {
            match attr {
                Attribute::ClipPathUnits => self.units.set(parse("clipPathUnits", value, ())?),

                _ => (),
            }
        }

        Ok(())
    }
}
<commit_msg>clip_path.rs: Let our call do draw_children push a layer - it will be a no-op because we are clipping<commit_after>use std::cell::Cell;

use cairo::{self, MatrixTrait};

use attributes::Attribute;
use coord_units::CoordUnits;
use drawing_ctx::{self, RsvgDrawingCtx};
use handle::RsvgHandle;
use node::{NodeResult, NodeTrait, RsvgNode};
use parsers::parse;
use property_bag::PropertyBag;

coord_units!(ClipPathUnits, CoordUnits::UserSpaceOnUse);

pub struct NodeClipPath {
    units: Cell<ClipPathUnits>,
}

impl NodeClipPath {
    pub fn new() -> NodeClipPath {
        NodeClipPath {
            units: Cell::new(ClipPathUnits::default()),
        }
    }

    pub fn get_units(&self) -> ClipPathUnits {
        self.units.get()
    }

    pub fn to_cairo_context(
        &self,
        node: &RsvgNode,
        affine_before_clip: &cairo::Matrix,
        draw_ctx: *mut RsvgDrawingCtx,
    ) {
        let cascaded = node.get_cascaded_values();

        let clip_units = self.units.get();

        let orig_bbox = drawing_ctx::get_bbox(draw_ctx).clone();

        let child_matrix = if clip_units == ClipPathUnits(CoordUnits::ObjectBoundingBox) {
            let rect = orig_bbox.rect.unwrap();

            let mut bbtransform =
                cairo::Matrix::new(rect.width, 0.0, 0.0, rect.height, rect.x, rect.y);
            cairo::Matrix::multiply(&bbtransform, affine_before_clip)
        } else {
            *affine_before_clip
        };

        let cr = drawing_ctx::get_cairo_context(draw_ctx);
        let save_affine = cr.get_matrix();
        cr.set_matrix(child_matrix);

        node.draw_children(node, &cascaded, draw_ctx, true, true);

        cr.set_matrix(save_affine);

        // FIXME: this is an EPIC HACK to keep the clipping context from
        // accumulating bounding boxes.  We'll remove this later, when we
        // are able to extract bounding boxes from outside the
        // general drawing loop.
        drawing_ctx::set_bbox(draw_ctx, &orig_bbox);

        let cr = drawing_ctx::get_cairo_context(draw_ctx);
        cr.clip();
    }
}

impl NodeTrait for NodeClipPath {
    fn set_atts(&self, _: &RsvgNode, _: *const RsvgHandle, pbag: &PropertyBag) -> NodeResult {
        for (_key, attr, value) in pbag.iter() {
            match attr {
                Attribute::ClipPathUnits => self.units.set(parse("clipPathUnits", value, ())?),

                _ => (),
            }
        }

        Ok(())
    }
}
<|endoftext|>


<commit_before>extern crate libc;
extern crate libnewsboat;

use libc::c_char;
use std::ffi::{CStr, CString};

use libnewsboat::utils;

#[no_mangle]
pub extern "C" fn rs_replace_all(
    input: *const c_char,
    from: *const c_char,
    to: *const c_char)
    -> *mut c_char
{
    let rs_input = unsafe { CStr::from_ptr(input) };
    let rs_input = rs_input.to_string_lossy().into_owned();

    let rs_from = unsafe { CStr::from_ptr(from) };
    let rs_from = rs_from.to_str()
        .expect("rs_from contained invalid UTF-8");

    let rs_to = unsafe { CStr::from_ptr(to) };
    let rs_to = rs_to.to_str()
        .expect("rs_to contained invalid UTF-8");

    let result = utils::replace_all(rs_input, rs_from, rs_to);
    // Panic here can't happen because:
    // 1. panic can only happen if `result` contains null bytes;
    // 2. `result` contains what `input` contained, plus maybe what `to` contains (if there were
    //    replacements);
    // 3. neither `input` nor `to` could contain null bytes because they're null-terminated
    //    strings we got from C.
    let result = CString::new(result).unwrap();
    result.into_raw()
}

#[no_mangle]
pub extern "C" fn rs_consolidate_whitespace( input: *const c_char) -> *mut c_char {
    let rs_input = unsafe { CStr::from_ptr(input) };
    let rs_input = rs_input.to_string_lossy().into_owned();

    let result = utils::consolidate_whitespace(rs_input);
    // Panic here can't happen because:
    // 1. panic can only happen if `result` contains null bytes;
    // 2. `result` contains what `input` contained, and input is a
    // null-terminated string from C.
    let result = CString::new(result).unwrap();
    result.into_raw()
}

#[no_mangle]
pub extern "C" fn rs_to_u(in_str: *const c_char, default_value: u32) -> u32
{
    let rs_str = unsafe { CStr::from_ptr(in_str) };
    let rs_str = rs_str.to_string_lossy().into_owned();

    utils::to_u( rs_str, default_value)
}

#[no_mangle]
pub extern "C" fn rs_trim_end(input: *const c_char) -> *mut c_char {
    let rs_input = unsafe { CStr::from_ptr(input) };
    let rs_input = rs_input.to_string_lossy().into_owned();

    let result = utils::trim_end(rs_input);

    let result = CString::new(result).unwrap();
    result.into_raw()
}

#[no_mangle]
pub extern "C" fn rs_trim(input: *const c_char) -> *mut c_char {
    let rs_input = unsafe { CStr::from_ptr(input) };
    let rs_input = rs_input.to_string_lossy().into_owned();

    let result = utils::trim(rs_input);

    let result = CString::new(result).unwrap();
    result.into_raw()
}

#[no_mangle]
pub extern "C" fn rs_get_random_value(rs_max: u32 ) -> u32 {
    utils::get_random_value(rs_max)
}

#[no_mangle]
pub extern "C" fn rs_cstring_free(string: *mut c_char) {
    unsafe {
        if string.is_null() { return }
        CString::from_raw(string);
    };
}

#[no_mangle]
pub extern "C" fn rs_get_default_browser() -> *mut c_char {
    let browser = utils::get_default_browser();
    // Panic here can't happen because:
    //1. panic can only happen if `result` contains null bytes;
    //2. std::env::var returns an error if the variable contains invalid Unicode,
    // In that case get_default_browser will return "lynx", which obviously doesn't contain null bytes.
    let result = CString::new(browser).unwrap();
    result.into_raw()
}

#[no_mangle]
pub extern "C" fn rs_is_valid_color(input: *const c_char) -> bool {
    let rs_input = unsafe { CStr::from_ptr(input) };
    let rs_input = rs_input.to_string_lossy().into_owned();
    utils::is_valid_color(&rs_input)
}
<commit_msg>This `into_owned` isn't necessary<commit_after>extern crate libc;
extern crate libnewsboat;

use libc::c_char;
use std::ffi::{CStr, CString};

use libnewsboat::utils;

#[no_mangle]
pub extern "C" fn rs_replace_all(
    input: *const c_char,
    from: *const c_char,
    to: *const c_char)
    -> *mut c_char
{
    let rs_input = unsafe { CStr::from_ptr(input) };
    let rs_input = rs_input.to_string_lossy().into_owned();

    let rs_from = unsafe { CStr::from_ptr(from) };
    let rs_from = rs_from.to_str()
        .expect("rs_from contained invalid UTF-8");

    let rs_to = unsafe { CStr::from_ptr(to) };
    let rs_to = rs_to.to_str()
        .expect("rs_to contained invalid UTF-8");

    let result = utils::replace_all(rs_input, rs_from, rs_to);
    // Panic here can't happen because:
    // 1. panic can only happen if `result` contains null bytes;
    // 2. `result` contains what `input` contained, plus maybe what `to` contains (if there were
    //    replacements);
    // 3. neither `input` nor `to` could contain null bytes because they're null-terminated
    //    strings we got from C.
    let result = CString::new(result).unwrap();
    result.into_raw()
}

#[no_mangle]
pub extern "C" fn rs_consolidate_whitespace( input: *const c_char) -> *mut c_char {
    let rs_input = unsafe { CStr::from_ptr(input) };
    let rs_input = rs_input.to_string_lossy().into_owned();

    let result = utils::consolidate_whitespace(rs_input);
    // Panic here can't happen because:
    // 1. panic can only happen if `result` contains null bytes;
    // 2. `result` contains what `input` contained, and input is a
    // null-terminated string from C.
    let result = CString::new(result).unwrap();
    result.into_raw()
}

#[no_mangle]
pub extern "C" fn rs_to_u(in_str: *const c_char, default_value: u32) -> u32
{
    let rs_str = unsafe { CStr::from_ptr(in_str) };
    let rs_str = rs_str.to_string_lossy().into_owned();

    utils::to_u( rs_str, default_value)
}

#[no_mangle]
pub extern "C" fn rs_trim_end(input: *const c_char) -> *mut c_char {
    let rs_input = unsafe { CStr::from_ptr(input) };
    let rs_input = rs_input.to_string_lossy().into_owned();

    let result = utils::trim_end(rs_input);

    let result = CString::new(result).unwrap();
    result.into_raw()
}

#[no_mangle]
pub extern "C" fn rs_trim(input: *const c_char) -> *mut c_char {
    let rs_input = unsafe { CStr::from_ptr(input) };
    let rs_input = rs_input.to_string_lossy().into_owned();

    let result = utils::trim(rs_input);

    let result = CString::new(result).unwrap();
    result.into_raw()
}

#[no_mangle]
pub extern "C" fn rs_get_random_value(rs_max: u32 ) -> u32 {
    utils::get_random_value(rs_max)
}

#[no_mangle]
pub extern "C" fn rs_cstring_free(string: *mut c_char) {
    unsafe {
        if string.is_null() { return }
        CString::from_raw(string);
    };
}

#[no_mangle]
pub extern "C" fn rs_get_default_browser() -> *mut c_char {
    let browser = utils::get_default_browser();
    // Panic here can't happen because:
    //1. panic can only happen if `result` contains null bytes;
    //2. std::env::var returns an error if the variable contains invalid Unicode,
    // In that case get_default_browser will return "lynx", which obviously doesn't contain null bytes.
    let result = CString::new(browser).unwrap();
    result.into_raw()
}

#[no_mangle]
pub extern "C" fn rs_is_valid_color(input: *const c_char) -> bool {
    let rs_input = unsafe { CStr::from_ptr(input) };
    let rs_input = rs_input.to_string_lossy();
    utils::is_valid_color(&rs_input)
}
<|endoftext|>


<commit_before>// Copyright 2016 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! A syntax highlighting plugin based on syntect.

#[macro_use]
extern crate serde_json;

extern crate syntect;
extern crate xi_plugin_lib;
extern crate xi_core_lib;
extern crate xi_rope;

mod stackmap;

use std::sync::MutexGuard;
use std::borrow::Cow;

use serde_json::Value;

use xi_plugin_lib::state_cache::{self, PluginCtx};
use xi_core_lib::plugin_rpc::ScopeSpan;
use xi_rope::rope::RopeDelta;
use xi_rope::interval::Interval;
use xi_rope::delta::Builder as EditBuilder;

use syntect::parsing::{ParseState, ScopeStack, SyntaxSet, SCOPE_REPO, ScopeRepository};
use stackmap::{StackMap, LookupResult};

/// The state for syntax highlighting of one file.
struct PluginState<'a> {
    syntax_set: &'a SyntaxSet,
    stack_idents: StackMap,
    offset: usize,
    initial_state: Option<(ParseState, ScopeStack)>,
    spans_start: usize,
    // unflushed spans
    spans: Vec<ScopeSpan>,
    new_scopes: Vec<Vec<String>>,
    syntax_name: String,
}

const LINES_PER_RPC: usize = 10;
const INDENTATION_PRIORITY: usize = 100;

type LockedRepo = MutexGuard<'static, ScopeRepository>;

/// The syntax highlighting state corresponding to the beginning of a line
/// (as stored in the state cache).
// Note: this needs to be option because the caching layer relies on Default.
// We can't implement that because the actual initial state depends on the
// syntax. There are other ways to handle this, but this will do for now.
type State = Option<(ParseState, ScopeStack)>;

impl<'a> PluginState<'a> {
    pub fn new(syntax_set: &'a SyntaxSet) -> Self {
        PluginState {
            syntax_set: syntax_set,
            stack_idents: StackMap::default(),
            offset: 0,
            initial_state: None,
            spans_start: 0,
            spans: Vec::new(),
            new_scopes: Vec::new(),
            syntax_name: String::from("None"),
        }
    }

    // compute syntax for one line, also accumulating the style spans
    fn compute_syntax(&mut self, line: &str, state: State) -> State {
        let (mut parse_state, mut scope_state) = state.or_else(|| self.initial_state.clone()).unwrap();
        let ops = parse_state.parse_line(&line);

        let mut prev_cursor = 0;
        let repo = SCOPE_REPO.lock().unwrap();
        for (cursor, batch) in ops {
            if scope_state.len() > 0 {
                let scope_id = self.identifier_for_stack(&scope_state, &repo);
                let start = self.offset - self.spans_start + prev_cursor;
                let end = start + (cursor - prev_cursor);
                if start != end {
                    let span = ScopeSpan { start, end, scope_id };
                    self.spans.push(span);
                }
            }
            prev_cursor = cursor;
            scope_state.apply(&batch);
        }
        // add span for final state
        let start = self.offset - self.spans_start + prev_cursor;
        let end = start + (line.len() - prev_cursor);
        let scope_id = self.identifier_for_stack(&scope_state, &repo);
        let span = ScopeSpan { start, end, scope_id };
        self.spans.push(span);
        Some((parse_state, scope_state))
    }

    /// Returns the unique identifier for this `ScopeStack`. We use identifiers
    /// so we aren't constantly sending long stack names to the peer.
    fn identifier_for_stack(&mut self, stack: &ScopeStack, repo: &LockedRepo) -> u32 {
        let identifier = self.stack_idents.get_value(stack.as_slice());
        match identifier {
            LookupResult::Existing(id) => id,
            LookupResult::New(id) => {
                let stack_strings = stack.as_slice().iter()
                    .map(|slice| repo.to_string(*slice))
                    .collect::<Vec<_>>();
                self.new_scopes.push(stack_strings);
                id
            }
        }
    }

    #[allow(unused)]
    // Return true if there's any more work to be done.
    fn highlight_one_line(&mut self, ctx: &mut PluginCtx<State>) -> bool {
        if let Some(line_num) = ctx.get_frontier() {
            let (line_num, offset, state) = ctx.get_prev(line_num);
            if offset != self.offset {
                self.flush_spans(ctx);
                self.offset = offset;
                self.spans_start = offset;
            }
            let new_frontier = match ctx.get_line(line_num) {
                Ok("") => None,
                Ok(s) => {
                    let new_state = self.compute_syntax(s, state);
                    self.offset += s.len();
                    if s.as_bytes().last() == Some(&b'\n') {
                        Some((new_state, line_num + 1))
                    } else {
                        None
                    }
                }
                Err(_) => None,
            };
            let mut converged = false;
            if let Some((ref new_state, new_line_num)) = new_frontier {
                if let Some(old_state) = ctx.get(new_line_num) {
                    converged = old_state.as_ref().unwrap().0 == new_state.as_ref().unwrap().0;
                }
            }
            if !converged {
                if let Some((new_state, new_line_num)) = new_frontier {
                    ctx.set(new_line_num, new_state);
                    ctx.update_frontier(new_line_num);
                    return true;
                }
            }
            ctx.close_frontier();
        }
        false
    }

    fn flush_spans(&mut self, ctx: &mut PluginCtx<State>) {
        if !self.new_scopes.is_empty() {
            ctx.add_scopes(&self.new_scopes);
            self.new_scopes.clear();
        }
        if self.spans_start != self.offset {
            ctx.update_spans(self.spans_start, self.offset - self.spans_start,
                             &self.spans);
            self.spans.clear();
        }
        self.spans_start = self.offset;
    }

    fn do_highlighting(&mut self, mut ctx: PluginCtx<State>) {
        let syntax = match ctx.get_path() {
            Some(ref path) => self.syntax_set.find_syntax_for_file(path).unwrap()
                .unwrap_or_else(|| self.syntax_set.find_syntax_plain_text()),
            None => self.syntax_set.find_syntax_plain_text(),
        };

        if syntax.name != self.syntax_name {
            self.syntax_name = syntax.name.clone();
            eprintln!("syntect using {}", syntax.name);
        }

        self.initial_state = Some((ParseState::new(syntax), ScopeStack::new()));
        self.spans = Vec::new();
        self.new_scopes = Vec::new();
        self.offset = 0;
        self.spans_start = 0;
        ctx.reset();
        ctx.schedule_idle(0);
    }

    /// Checks if a newline has been inserted, and if so inserts whitespace
    /// as necessary.
    fn do_indentation(&mut self, ctx: &mut PluginCtx<State>, start: usize,
                      end: usize, rev: usize, text: &str) -> Option<Value> {
        // don't touch indentation if this is not a simple edit
        if end != start { return None }

        let is_newline = &ctx.get_config().line_ending == text;
        if is_newline {
            let line_num = ctx.find_offset(start).err();

            let use_spaces = ctx.get_config().translate_tabs_to_spaces;
            let tab_size = ctx.get_config().tab_size;
            let buf_size = ctx.get_buf_size();
            if let Some(line) = line_num.and_then(|idx| ctx.get_line(idx).ok()) {
                let indent = self.indent_for_next_line(
                    line, use_spaces, tab_size);
                let ix = start + text.len();
                let interval = Interval::new_open_closed(ix, ix);
                let mut builder = EditBuilder::new(buf_size);
                builder.replace(interval, indent.into());
                let delta = builder.build();
                let edit = json!({
                    "rev": rev,
                    "delta": delta,
                    "priority": INDENTATION_PRIORITY,
                    "after_cursor": false,
                    "author": "syntect",
                });
                return Some(edit)
            }
        }
        None
    }

    /// Returns the string which should be inserted after the newline
    /// to achieve the desired indentation level.
    fn indent_for_next_line<'b>(&self, prev_line: &'b str, use_spaces: bool,
                                tab_size: usize) -> Cow<'b, str> {
        let leading_ws = prev_line.char_indices()
            .find(|&(_, c)| !c.is_whitespace())
            .or(prev_line.char_indices().last())
            .map(|(idx, _)| unsafe { prev_line.slice_unchecked(0, idx) } )
            .unwrap_or("");

        if self.increase_indentation(prev_line) {
            let indent_text = if use_spaces {
                &"                                    "[..tab_size]
            } else {
                "\t"
            };
            format!("{}{}", leading_ws, indent_text).into()
        } else {
            leading_ws.into()
        }
    }

    /// Checks if the indent level should be increased.
    fn increase_indentation(&self, prev_line: &str) -> bool {
        let trailing_char = prev_line.trim_right().chars()
            .rev().next().unwrap_or(' ');
        // very naive heuristic for modifying indentation level.
        match trailing_char {
            '{' | ':' => true,
            _ => false,
        }
    }
}

impl<'a> state_cache::Plugin for PluginState<'a> {
    type State = State;

    fn initialize(&mut self, ctx: PluginCtx<State>, _buf_size: usize) {
        self.do_highlighting(ctx);
    }

    fn update(&mut self, mut ctx: PluginCtx<State>, rev: usize,
              delta: Option<RopeDelta>) -> Option<Value> {
        ctx.schedule_idle(0);
        let should_auto_indent = ctx.get_config().auto_indent;
        if should_auto_indent {
            if let Some(delta) = delta {
                let (iv, _) = delta.summary();
                if let Some(s) = delta.as_simple_insert() {
                    let s: String = s.into();
                    return self.do_indentation(&mut ctx, iv.start(), iv.end(), rev, &s)
                }
            }
        }
        None
    }

    fn did_save(&mut self, ctx: PluginCtx<State>) {
        // TODO: use smarter logic to figure out whether we need to re-highlight the whole file
        self.do_highlighting(ctx);
    }

    fn idle(&mut self, mut ctx: PluginCtx<State>, _token: usize) {
        //eprintln!("idle task at offset {}", self.offset);
        for _ in 0..LINES_PER_RPC {
            if !self.highlight_one_line(&mut ctx) {
                self.flush_spans(&mut ctx);
                return;
            }
            if ctx.request_is_pending() {
                eprintln!("request pending at offset {}", self.offset);
                break;
            }
        }
        self.flush_spans(&mut ctx);
        ctx.schedule_idle(0);
    }
}

fn main() {
    let syntax_set = SyntaxSet::load_defaults_newlines();
    let mut state = PluginState::new(&syntax_set);

    let _ = state_cache::mainloop(&mut state);
}
<commit_msg>prevent syntect plugin autoindent update on empty string<commit_after>// Copyright 2016 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! A syntax highlighting plugin based on syntect.

#[macro_use]
extern crate serde_json;

extern crate syntect;
extern crate xi_plugin_lib;
extern crate xi_core_lib;
extern crate xi_rope;

mod stackmap;

use std::sync::MutexGuard;
use std::borrow::Cow;

use serde_json::Value;

use xi_plugin_lib::state_cache::{self, PluginCtx};
use xi_core_lib::plugin_rpc::ScopeSpan;
use xi_rope::rope::RopeDelta;
use xi_rope::interval::Interval;
use xi_rope::delta::Builder as EditBuilder;

use syntect::parsing::{ParseState, ScopeStack, SyntaxSet, SCOPE_REPO, ScopeRepository};
use stackmap::{StackMap, LookupResult};

/// The state for syntax highlighting of one file.
struct PluginState<'a> {
    syntax_set: &'a SyntaxSet,
    stack_idents: StackMap,
    offset: usize,
    initial_state: Option<(ParseState, ScopeStack)>,
    spans_start: usize,
    // unflushed spans
    spans: Vec<ScopeSpan>,
    new_scopes: Vec<Vec<String>>,
    syntax_name: String,
}

const LINES_PER_RPC: usize = 10;
const INDENTATION_PRIORITY: usize = 100;

type LockedRepo = MutexGuard<'static, ScopeRepository>;

/// The syntax highlighting state corresponding to the beginning of a line
/// (as stored in the state cache).
// Note: this needs to be option because the caching layer relies on Default.
// We can't implement that because the actual initial state depends on the
// syntax. There are other ways to handle this, but this will do for now.
type State = Option<(ParseState, ScopeStack)>;

impl<'a> PluginState<'a> {
    pub fn new(syntax_set: &'a SyntaxSet) -> Self {
        PluginState {
            syntax_set: syntax_set,
            stack_idents: StackMap::default(),
            offset: 0,
            initial_state: None,
            spans_start: 0,
            spans: Vec::new(),
            new_scopes: Vec::new(),
            syntax_name: String::from("None"),
        }
    }

    // compute syntax for one line, also accumulating the style spans
    fn compute_syntax(&mut self, line: &str, state: State) -> State {
        let (mut parse_state, mut scope_state) = state.or_else(|| self.initial_state.clone()).unwrap();
        let ops = parse_state.parse_line(&line);

        let mut prev_cursor = 0;
        let repo = SCOPE_REPO.lock().unwrap();
        for (cursor, batch) in ops {
            if scope_state.len() > 0 {
                let scope_id = self.identifier_for_stack(&scope_state, &repo);
                let start = self.offset - self.spans_start + prev_cursor;
                let end = start + (cursor - prev_cursor);
                if start != end {
                    let span = ScopeSpan { start, end, scope_id };
                    self.spans.push(span);
                }
            }
            prev_cursor = cursor;
            scope_state.apply(&batch);
        }
        // add span for final state
        let start = self.offset - self.spans_start + prev_cursor;
        let end = start + (line.len() - prev_cursor);
        let scope_id = self.identifier_for_stack(&scope_state, &repo);
        let span = ScopeSpan { start, end, scope_id };
        self.spans.push(span);
        Some((parse_state, scope_state))
    }

    /// Returns the unique identifier for this `ScopeStack`. We use identifiers
    /// so we aren't constantly sending long stack names to the peer.
    fn identifier_for_stack(&mut self, stack: &ScopeStack, repo: &LockedRepo) -> u32 {
        let identifier = self.stack_idents.get_value(stack.as_slice());
        match identifier {
            LookupResult::Existing(id) => id,
            LookupResult::New(id) => {
                let stack_strings = stack.as_slice().iter()
                    .map(|slice| repo.to_string(*slice))
                    .collect::<Vec<_>>();
                self.new_scopes.push(stack_strings);
                id
            }
        }
    }

    #[allow(unused)]
    // Return true if there's any more work to be done.
    fn highlight_one_line(&mut self, ctx: &mut PluginCtx<State>) -> bool {
        if let Some(line_num) = ctx.get_frontier() {
            let (line_num, offset, state) = ctx.get_prev(line_num);
            if offset != self.offset {
                self.flush_spans(ctx);
                self.offset = offset;
                self.spans_start = offset;
            }
            let new_frontier = match ctx.get_line(line_num) {
                Ok("") => None,
                Ok(s) => {
                    let new_state = self.compute_syntax(s, state);
                    self.offset += s.len();
                    if s.as_bytes().last() == Some(&b'\n') {
                        Some((new_state, line_num + 1))
                    } else {
                        None
                    }
                }
                Err(_) => None,
            };
            let mut converged = false;
            if let Some((ref new_state, new_line_num)) = new_frontier {
                if let Some(old_state) = ctx.get(new_line_num) {
                    converged = old_state.as_ref().unwrap().0 == new_state.as_ref().unwrap().0;
                }
            }
            if !converged {
                if let Some((new_state, new_line_num)) = new_frontier {
                    ctx.set(new_line_num, new_state);
                    ctx.update_frontier(new_line_num);
                    return true;
                }
            }
            ctx.close_frontier();
        }
        false
    }

    fn flush_spans(&mut self, ctx: &mut PluginCtx<State>) {
        if !self.new_scopes.is_empty() {
            ctx.add_scopes(&self.new_scopes);
            self.new_scopes.clear();
        }
        if self.spans_start != self.offset {
            ctx.update_spans(self.spans_start, self.offset - self.spans_start,
                             &self.spans);
            self.spans.clear();
        }
        self.spans_start = self.offset;
    }

    fn do_highlighting(&mut self, mut ctx: PluginCtx<State>) {
        let syntax = match ctx.get_path() {
            Some(ref path) => self.syntax_set.find_syntax_for_file(path).unwrap()
                .unwrap_or_else(|| self.syntax_set.find_syntax_plain_text()),
            None => self.syntax_set.find_syntax_plain_text(),
        };

        if syntax.name != self.syntax_name {
            self.syntax_name = syntax.name.clone();
            eprintln!("syntect using {}", syntax.name);
        }

        self.initial_state = Some((ParseState::new(syntax), ScopeStack::new()));
        self.spans = Vec::new();
        self.new_scopes = Vec::new();
        self.offset = 0;
        self.spans_start = 0;
        ctx.reset();
        ctx.schedule_idle(0);
    }

    /// Checks if a newline has been inserted, and if so inserts whitespace
    /// as necessary.
    fn do_indentation(&mut self, ctx: &mut PluginCtx<State>, start: usize,
                      end: usize, rev: usize, text: &str) -> Option<Value> {
        // don't touch indentation if this is not a simple edit
        if end != start { return None }

        let line_ending = ctx.get_config().line_ending.clone();
        let is_newline = line_ending == text;

        if is_newline {
            let line_num = ctx.find_offset(start).err();

            let use_spaces = ctx.get_config().translate_tabs_to_spaces;
            let tab_size = ctx.get_config().tab_size;
            let buf_size = ctx.get_buf_size();
            if let Some(line) = line_num.and_then(|idx| ctx.get_line(idx).ok()) {
                // do not send update if last line is empty string (contains only line ending)
                if line == line_ending { return None }

                let indent = self.indent_for_next_line(
                    line, use_spaces, tab_size);
                let ix = start + text.len();
                let interval = Interval::new_open_closed(ix, ix);
                let mut builder = EditBuilder::new(buf_size);
                builder.replace(interval, indent.into());
                let delta = builder.build();
                let edit = json!({
                    "rev": rev,
                    "delta": delta,
                    "priority": INDENTATION_PRIORITY,
                    "after_cursor": false,
                    "author": "syntect",
                });
                return Some(edit)
            }
        }
        None
    }

    /// Returns the string which should be inserted after the newline
    /// to achieve the desired indentation level.
    fn indent_for_next_line<'b>(&self, prev_line: &'b str, use_spaces: bool,
                                tab_size: usize) -> Cow<'b, str> {
        let leading_ws = prev_line.char_indices()
            .find(|&(_, c)| !c.is_whitespace())
            .or(prev_line.char_indices().last())
            .map(|(idx, _)| unsafe { prev_line.slice_unchecked(0, idx) } )
            .unwrap_or("");

        if self.increase_indentation(prev_line) {
            let indent_text = if use_spaces {
                &"                                    "[..tab_size]
            } else {
                "\t"
            };
            format!("{}{}", leading_ws, indent_text).into()
        } else {
            leading_ws.into()
        }
    }

    /// Checks if the indent level should be increased.
    fn increase_indentation(&self, prev_line: &str) -> bool {
        let trailing_char = prev_line.trim_right().chars()
            .rev().next().unwrap_or(' ');
        // very naive heuristic for modifying indentation level.
        match trailing_char {
            '{' | ':' => true,
            _ => false,
        }
    }
}

impl<'a> state_cache::Plugin for PluginState<'a> {
    type State = State;

    fn initialize(&mut self, ctx: PluginCtx<State>, _buf_size: usize) {
        self.do_highlighting(ctx);
    }

    fn update(&mut self, mut ctx: PluginCtx<State>, rev: usize,
              delta: Option<RopeDelta>) -> Option<Value> {
        ctx.schedule_idle(0);
        let should_auto_indent = ctx.get_config().auto_indent;
        if should_auto_indent {
            if let Some(delta) = delta {
                let (iv, _) = delta.summary();
                if let Some(s) = delta.as_simple_insert() {
                    let s: String = s.into();
                    return self.do_indentation(&mut ctx, iv.start(), iv.end(), rev, &s)
                }
            }
        }
        None
    }

    fn did_save(&mut self, ctx: PluginCtx<State>) {
        // TODO: use smarter logic to figure out whether we need to re-highlight the whole file
        self.do_highlighting(ctx);
    }

    fn idle(&mut self, mut ctx: PluginCtx<State>, _token: usize) {
        //eprintln!("idle task at offset {}", self.offset);
        for _ in 0..LINES_PER_RPC {
            if !self.highlight_one_line(&mut ctx) {
                self.flush_spans(&mut ctx);
                return;
            }
            if ctx.request_is_pending() {
                eprintln!("request pending at offset {}", self.offset);
                break;
            }
        }
        self.flush_spans(&mut ctx);
        ctx.schedule_idle(0);
    }
}

fn main() {
    let syntax_set = SyntaxSet::load_defaults_newlines();
    let mut state = PluginState::new(&syntax_set);

    let _ = state_cache::mainloop(&mut state);
}
<|endoftext|>


<commit_before>#![feature(plugin, custom_derive)]
#![plugin(serde_macros)]

#[macro_use]
extern crate lazy_static;

extern crate hyper;
extern crate regex;
extern crate rust_rosetta;
extern crate serde;
extern crate url;
extern crate walkdir;

use std::collections::{HashMap, HashSet, VecDeque};
use std::fs::File;
use std::io::BufReader;
use std::io::prelude::*;
use std::iter::FromIterator;
use std::path::PathBuf;

use hyper::Client;
use hyper::header::Connection;
use regex::Regex;
use url::Url;
use walkdir::WalkDir;

use rust_rosetta::rosetta_code::find_unimplemented_tasks;

lazy_static!{
    static ref TASK_COMMENT_RE: Regex = Regex::new("// http://rosettacode.org/wiki/(.+)").unwrap();
    static ref LIB_OR_MOD: Regex = Regex::new("^(lib|mod)$").unwrap();

    /// Extracts code from the first Rust section from Rosetta Code wiki markup.
    static ref RUST_WIKI_SECTION_RE: Regex =
        Regex::new(r"==\{\{header\|Rust\}\}==(?s:.*?)<lang rust>((?s:.*?))</lang>").unwrap();

}

#[derive(Debug, Serialize, Deserialize)]
pub struct Task {
    /// The title of the task.
    pub title: String,

    /// The code of the task implemented locally. If the tasks is not implemented in the
    /// repository, then it will be `None`.
    pub local_code: Option<String>,

    /// The code of the task implemented on the Rosetta Code wiki. If the task is not yet
    /// implemented online, it will be `None`.
    pub remote_code: Option<String>,

    /// The URL of the task on the RosettaCode wiki.
    pub url: String, // FIXME: Should be a url::Url
}

impl Task {
    /// True if and only if the task is only implemented on the Rosetta Code wiki.
    pub fn is_remote_only(&self) -> bool {
        self.local_code.is_none() && self.remote_code.is_some()
    }

    /// True if and only if the task is is only implemented in the repository.
    pub fn is_local_only(&self) -> bool {
        self.local_code.is_some() && self.remote_code.is_none()
    }

    /// True if and only if the task is neither implemented on the Rosetta code wiki or on the
    /// repository.
    pub fn is_unimplemented(&self) -> bool {
        self.local_code.is_none() && self.local_code.is_none()
    }
}

/// Transforms a task title to a URL task title.
fn normalize(title: &str) -> String {
    title.replace(" ", "_")
}

/// Returns the titles of every task on Rosetta Code.
fn all_task_titles() -> Vec<String> {
    find_unimplemented_tasks::all_tasks().iter().map(|task| task.title.to_owned()).collect()
}

/// Iterator over task information. One `Task` will be returned each iteration.
pub struct TaskIterator {
    task_titles: VecDeque<String>,
    local_tasks: HashMap<String, PathBuf>,
    client: Client,
}

impl TaskIterator {
    pub fn new(titles: &[String]) -> Self {
        // Determine which tasks are implemented locally by walking the src folder and reading the
        // comment at the top of the file.
        let mut local_tasks = HashMap::new();
        for entry in WalkDir::new("../src") {
            let entry = entry.unwrap();
            let file_stem = entry.path().file_stem().unwrap().to_str().unwrap();

            // If we find a non-Rust file (or a lib or mod) skip it.
            match entry.path().extension().and_then(|s| s.to_str()) {
                Some("rs") if !LIB_OR_MOD.is_match(file_stem) => (),
                _ => continue,
            }

            let file = File::open(entry.path()).unwrap();
            let first_line = BufReader::new(file).lines().next().unwrap().unwrap();
            let task_name = TASK_COMMENT_RE.captures(&first_line)
                                           .and_then(|c| c.at(1))
                                           .expect(&format!("could not parse task name for {:?}",
                                                            entry.path()));

            local_tasks.insert(task_name.to_owned(), entry.path().to_owned());
        }

        TaskIterator {
            client: Client::new(),
            task_titles: titles.iter().cloned().collect(),
            local_tasks: local_tasks,
        }
    }
}

impl Iterator for TaskIterator {
    type Item = Task;

    fn next(&mut self) -> Option<Self::Item> {
        self.task_titles.pop_front().map(|title| {
            let normalized_title = normalize(&title);

            let mut task_url = Url::parse(&format!("http://rosettacode.org/wiki/{}",
                                                   normalized_title))
                                   .unwrap();
            task_url.set_query_from_pairs(&[("action", "raw")]);

            let local_code = self.local_tasks
                                 .get(&normalized_title)
                                 .and_then(|path| File::open(path).ok())
                                 .map(|mut file| {
                                     let mut local_code = String::new();
                                     file.read_to_string(&mut local_code).unwrap();
                                     local_code
                                 });

            let mut res = self.client
                              .get(&task_url.serialize())
                              .header(Connection::close())
                              .send()
                              .unwrap();

            let mut body = String::new();
            res.read_to_string(&mut body).unwrap();
            let remote_code = RUST_WIKI_SECTION_RE.captures(&body)
                                                  .map(|captures| captures.at(1).unwrap())
                                                  .map(|code| code.to_owned());

            let mut wiki_url = task_url.clone();
            wiki_url.query = None;

            Task {
                title: title.to_owned(),
                local_code: local_code,
                remote_code: remote_code,
                url: wiki_url.serialize(),
            }
        })
    }
}

/// Retrieves data for every task on Rosetta Code.
pub fn fetch_all_tasks() -> TaskIterator {
    fetch_tasks(&all_task_titles())
}

/// Parses both local (implemented in this repository) and remote (implemented on the wiki) tasks,
/// and returns the code of each.
pub fn fetch_tasks(tasks: &[String]) -> TaskIterator {
    let all_task_titles: HashSet<String> = HashSet::from_iter(all_task_titles());
    let requested_task_titles = HashSet::from_iter(tasks.iter().cloned());
    let mut task_titles: Vec<String> = all_task_titles.intersection(&requested_task_titles)
                                                      .cloned()
                                                      .collect();
    task_titles.sort();

    TaskIterator::new(&task_titles)
}
<commit_msg>use "RUST_ROSETTA_SRC" variable if present<commit_after>#![feature(plugin, custom_derive)]
#![plugin(serde_macros)]

#[macro_use]
extern crate lazy_static;

extern crate hyper;
extern crate regex;
extern crate rust_rosetta;
extern crate serde;
extern crate url;
extern crate walkdir;

use std::collections::{HashMap, HashSet, VecDeque};
use std::fs::File;
use std::io::BufReader;
use std::io::prelude::*;
use std::iter::FromIterator;
use std::path::PathBuf;

use hyper::Client;
use hyper::header::Connection;
use regex::Regex;
use url::Url;
use walkdir::WalkDir;

use rust_rosetta::rosetta_code::find_unimplemented_tasks;

lazy_static!{
    static ref TASK_COMMENT_RE: Regex = Regex::new("// http://rosettacode.org/wiki/(.+)").unwrap();
    static ref LIB_OR_MOD: Regex = Regex::new("^(lib|mod)$").unwrap();

    /// Extracts code from the first Rust section from Rosetta Code wiki markup.
    static ref RUST_WIKI_SECTION_RE: Regex =
        Regex::new(r"==\{\{header\|Rust\}\}==(?s:.*?)<lang rust>((?s:.*?))</lang>").unwrap();

    /// The location of implemented tasks.
    ///
    /// Uses the `RUST_ROSETTA_SRC` environment variable if specified, otherwise uses `src` in the
    /// parent directory.
    static ref RUST_ROSETTA_SRC: PathBuf =
        PathBuf::from(option_env!("RUST_ROSETTA_SRC").unwrap_or("../src"));
}

#[derive(Debug, Serialize, Deserialize)]
pub struct Task {
    /// The title of the task.
    pub title: String,

    /// The code of the task implemented locally. If the tasks is not implemented in the
    /// repository, then it will be `None`.
    pub local_code: Option<String>,

    /// The code of the task implemented on the Rosetta Code wiki. If the task is not yet
    /// implemented online, it will be `None`.
    pub remote_code: Option<String>,

    /// The URL of the task on the RosettaCode wiki.
    pub url: String, // FIXME: Should be a url::Url
}

impl Task {
    /// True if and only if the task is only implemented on the Rosetta Code wiki.
    pub fn is_remote_only(&self) -> bool {
        self.local_code.is_none() && self.remote_code.is_some()
    }

    /// True if and only if the task is is only implemented in the repository.
    pub fn is_local_only(&self) -> bool {
        self.local_code.is_some() && self.remote_code.is_none()
    }

    /// True if and only if the task is neither implemented on the Rosetta code wiki or on the
    /// repository.
    pub fn is_unimplemented(&self) -> bool {
        self.local_code.is_none() && self.local_code.is_none()
    }
}

/// Transforms a task title to a URL task title.
fn normalize(title: &str) -> String {
    title.replace(" ", "_")
}

/// Returns the titles of every task on Rosetta Code.
fn all_task_titles() -> Vec<String> {
    find_unimplemented_tasks::all_tasks().iter().map(|task| task.title.to_owned()).collect()
}

/// Iterator over task information. One `Task` will be returned each iteration.
pub struct TaskIterator {
    task_titles: VecDeque<String>,
    local_tasks: HashMap<String, PathBuf>,
    client: Client,
}

impl TaskIterator {
    pub fn new(titles: &[String]) -> Self {
        // Determine which tasks are implemented locally by walking the src folder and reading the
        // comment at the top of the file.
        let mut local_tasks = HashMap::new();
        for entry in WalkDir::new(RUST_ROSETTA_SRC.as_path()) {
            let entry = entry.unwrap();
            let file_stem = entry.path().file_stem().unwrap().to_str().unwrap();

            // If we find a non-Rust file (or a lib or mod) skip it.
            match entry.path().extension().and_then(|s| s.to_str()) {
                Some("rs") if !LIB_OR_MOD.is_match(file_stem) => (),
                _ => continue,
            }

            let file = File::open(entry.path()).unwrap();
            let first_line = BufReader::new(file).lines().next().unwrap().unwrap();
            let task_name = TASK_COMMENT_RE.captures(&first_line)
                                           .and_then(|c| c.at(1))
                                           .expect(&format!("could not parse task name for {:?}",
                                                            entry.path()));

            local_tasks.insert(task_name.to_owned(), entry.path().to_owned());
        }

        TaskIterator {
            client: Client::new(),
            task_titles: titles.iter().cloned().collect(),
            local_tasks: local_tasks,
        }
    }
}

impl Iterator for TaskIterator {
    type Item = Task;

    fn next(&mut self) -> Option<Self::Item> {
        self.task_titles.pop_front().map(|title| {
            let normalized_title = normalize(&title);

            let mut task_url = Url::parse(&format!("http://rosettacode.org/wiki/{}",
                                                   normalized_title))
                                   .unwrap();
            task_url.set_query_from_pairs(&[("action", "raw")]);

            let local_code = self.local_tasks
                                 .get(&normalized_title)
                                 .and_then(|path| File::open(path).ok())
                                 .map(|mut file| {
                                     let mut local_code = String::new();
                                     file.read_to_string(&mut local_code).unwrap();
                                     local_code
                                 });

            let mut res = self.client
                              .get(&task_url.serialize())
                              .header(Connection::close())
                              .send()
                              .unwrap();

            let mut body = String::new();
            res.read_to_string(&mut body).unwrap();
            let remote_code = RUST_WIKI_SECTION_RE.captures(&body)
                                                  .map(|captures| captures.at(1).unwrap())
                                                  .map(|code| code.to_owned());

            let mut wiki_url = task_url.clone();
            wiki_url.query = None;

            Task {
                title: title.to_owned(),
                local_code: local_code,
                remote_code: remote_code,
                url: wiki_url.serialize(),
            }
        })
    }
}

/// Retrieves data for every task on Rosetta Code.
pub fn fetch_all_tasks() -> TaskIterator {
    fetch_tasks(&all_task_titles())
}

/// Parses both local (implemented in this repository) and remote (implemented on the wiki) tasks,
/// and returns the code of each.
pub fn fetch_tasks(tasks: &[String]) -> TaskIterator {
    let all_task_titles: HashSet<String> = HashSet::from_iter(all_task_titles());
    let requested_task_titles = HashSet::from_iter(tasks.iter().cloned());
    let mut task_titles: Vec<String> = all_task_titles.intersection(&requested_task_titles)
                                                      .cloned()
                                                      .collect();
    task_titles.sort();

    TaskIterator::new(&task_titles)
}
<|endoftext|>


<commit_before>//! Symbol- and name-storage facilities.
//!
//! This is based on [Servo's string-cache
//! library](https://github.com/servo/string-cache); at the moment it's very
//! lifetime-unsafe for `Pooled` instances.
use std;
use std::fmt;
use std::slice::bytes;
use std::hash::{hash, Hash, Hasher, SipHasher};
use std::collections::BTreeMap;
use std::borrow::ToOwned;

use super::util::intrusive;

#[macro_use]
pub mod nameable;
pub use self::nameable::Nameable;

/// Methods required of `unpacked` types.
pub trait PackFormat {
    /// Pack an unpacked symbol in the implementing format into a generic
    /// Symbol object.
    fn pack(&self) -> Symbol;

    /// Unpack a generic Symbol into the implementing format.  This function is
    /// marked as `unsafe` because the caller must verify that the symbol is
    /// indeed packed by the receiver's implementation.
    unsafe fn unpack(sym: &Symbol) -> Self;

    /// Fetch a `&str` slice from a symbol packed in the implementing format.
    /// This function is marked as `unsafe` because the caller must verify that
    /// the symbol is indeed packed by the receiver's implementation.
    unsafe fn as_slice_from<'t>(sym: &'t Symbol) -> &'t str;
}

/* ****************************************************************
 * Pool
 */
/// A collection of symbols.
#[derive(Debug)]
pub struct Pool {
    refcount: usize,
    map:  BTreeMap<u64,String>
}

impl Pool {
    /// Create a new symbol pool.
    pub fn new() -> Pool {
        Pool{refcount: 1, map: BTreeMap::new()}
    }
    /// Fetch a symbol corresponding to the given string.  If if
    pub fn symbol(&mut self, name: &str) -> Symbol {
        if name.len() <= INLINE_SYMBOL_MAX_LEN
        { Inline::new(name).pack() }
        else { let hsh = hash::<_, SipHasher>(&name) << 1;
               if ! self.map.contains_key(&hsh) {
                   self.map.insert(hsh, name.to_owned());
               }

               Pooled::new(hsh, self).pack()
        }
    }
}

default_refcounted_impl!(Pool, refcount);

/* ****************************************************************
 * Symbol
 */
/// An atomic `Copy`able string.  Symbol either encodes a short string
/// directly, or stores it in an external Pool.
#[derive(Debug)]
pub struct Symbol {
    value: [u64; 2]
}
impl Copy for Symbol {}

impl Symbol {
    /// Convert the symbol to its unpacked representation.
    #[inline(always)]
    pub fn unpack(&self) -> Unpacked
    { unsafe { <Unpacked as PackFormat>::unpack(self) } }

    /// Get symbol's pack format.
    #[inline(always)]
    pub fn type_of(&self) -> Type {
        match (self.value[0] & 0x01) as u8 {
            INLINE => Type::INLINE,
            POOLED => Type::POOLED,
            _ => unreachable!()
        }
    }

}

impl std::str::Str for Symbol {
    fn as_slice<'t>(&'t self) -> &'t str { unsafe { <Unpacked as PackFormat>::as_slice_from(self) } }
}

impl Nameable for Symbol {
    fn name(&self) -> Symbol { *self }
}

impl Hash for Symbol {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.as_slice().hash(state);
    }
}

impl fmt::Display for Symbol {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.as_slice())
    }
}

impl Eq for Symbol {}
impl PartialEq<Symbol> for Symbol
{
    fn eq(&self, other: &Symbol) -> bool
    {
        self.value == other.value
    }
}

/* ****************************************************************
 * Inline
 */
const INLINE_SYMBOL_MAX_LEN: usize = 15;//std::mem::size_of::<Symbol>() - 1;

/// Data format for symbols packed entirely into a `Symbol` instance.
#[derive(Debug)]
pub struct Inline{len: u8, data: [u8; INLINE_SYMBOL_MAX_LEN]}

impl Inline {
    /// Create a new Inline-format symbol using the given string.
    pub fn new(name: &str) -> Self {
        panic_unless!(name.len() <= INLINE_SYMBOL_MAX_LEN,
                      "Argument to Inline::new (length {}) exceeds maximum length ({}) for an inlined symbol",
                      name.len(), INLINE_SYMBOL_MAX_LEN);
        let mut buf: [u8; INLINE_SYMBOL_MAX_LEN] = [0; INLINE_SYMBOL_MAX_LEN];

        bytes::copy_memory(buf.as_mut_slice(), name.as_bytes());
        Inline{len: name.len() as u8, data: buf}
    }
}

impl std::str::Str for Inline {
    fn as_slice<'t>(&'t self) -> &'t str {
        let src: &[u8] = self.data.as_slice();
        std::str::from_utf8(&src[..(self.len as usize)]).unwrap()
    }
}

impl PackFormat for Inline {
    // Convert the Inline symbol into a generic packed representation.
    fn pack(&self) -> Symbol {
        unsafe {
            let mut out = Symbol{ value: [0; 2] };
            let mut dest: &mut [u8; 16] = std::mem::transmute(&mut out.value);
            dest[0] = (self.len << 1) | INLINE;
            bytes::copy_memory(&mut dest[1..], &self.data);
            out
        }
    }
    fn unpack(sym: &Symbol) -> Self {
        let mut out = Inline{len: 0, data: [0; INLINE_SYMBOL_MAX_LEN]};
        let src: &[u8;16] =
            unsafe { std::mem::transmute(&sym.value) };

        panic_unless!(src[0] & TAG_MASK == INLINE, "Invalid tag bit for inlined symbol!");
        out.len = src[0] >> 1;
        panic_unless!(out.len <= INLINE_SYMBOL_MAX_LEN as u8,
                      "Symbol length ({}) exceeds maximum ({}) for inlined symbol",
                      out.len, INLINE_SYMBOL_MAX_LEN);
        bytes::copy_memory(out.data.as_mut_slice(), &src[1..(out.len as usize + 1)]);

        out
    }

    fn as_slice_from<'t>(sym: &'t Symbol) -> &'t str {
        let src: &[u8; 16] = unsafe { std::mem::transmute(&sym.value) };
        panic_unless!(src[0] & TAG_MASK == INLINE, "Invalid tag bit for inlined symbol!");
        let len: usize = (src[0] >> 1) as usize;
        panic_unless!(len <= INLINE_SYMBOL_MAX_LEN,
                      "Symbol length ({}) exceeds maximum ({}) for inlined symbol",
                      len, INLINE_SYMBOL_MAX_LEN);

        std::str::from_utf8(&src[1..(len + 1)]).unwrap()
    }
}

impl std::cmp::Eq for Inline {}
impl std::cmp::PartialEq for Inline {
    fn eq(&self, other: &Self) -> bool {
        self.len == other.len && self.data == other.data
    }
}

/* ****************************************************************
 * Pooled
 */
/// Data format for symbols stored as references to a symbol pool.
#[derive(Debug)]
pub struct Pooled{key: u64, pool: *const Pool}

impl Pooled {
    #[inline(always)]
    pub fn new(key: u64, pool: *const Pool) -> Self {
        Pooled{key: key, pool: pool}
    }

}

impl std::str::Str for Pooled {
    fn as_slice<'u>(&'u self) -> &'u str {
        unsafe { (*self.pool).map[self.key].as_slice() }
    }
}

impl PackFormat for Pooled {
    fn pack(&self) -> Symbol {
        let mut val: [u64; 2] = [0; 2];
        val[0] = self.key  | POOLED as u64;
        val[1] = self.pool as u64;
        Symbol{value: val}
    }
    fn unpack(sym: &Symbol) -> Self {
        Pooled{key: sym.value[0],
               pool: unsafe { std::mem::transmute(sym.value[1] as *const Pool) }}
    }

    fn as_slice_from<'t>(sym: &'t Symbol) -> &'t str {
        panic_unless!(sym.value[0] & TAG_MASK as u64 == POOLED as u64,
                      "Invalid flag bit for pooled symbol");
        unsafe { std::mem::transmute::<_,&'t Pool>(sym.value[1] as *const Pool) }.
            map[sym.value[0]].as_slice()
    }
}

impl std::cmp::Eq for Pooled {}
impl std::cmp::PartialEq for Pooled {
    fn eq(&self, other: &Self) -> bool {
        self.key == other.key && self.pool as u64 == other.pool as u64
    }
}

/* ****************************************************************
 * Types
 */
const TAG_MASK: u8 = 0x01;
const POOLED: u8 = 0;
const INLINE: u8 = 1;

pub enum Type { INLINE = 0, POOLED = 1 }

impl std::cmp::Eq for Type {}
impl std::cmp::PartialEq for Type {
    fn eq(&self, other: &Self) -> bool {
        match *self { Type::INLINE => match *other { Type::INLINE => true, _ => false },
                      Type::POOLED => match *other { Type::POOLED => true, _ => false } }
    }
}

impl std::fmt::Debug for Type {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self { Type::INLINE => write!(f, "inline"),
                      Type::POOLED => write!(f, "pooled") }
    }
}

/* ****************************************************************
 * Unpacked
 */

/// A differentiated `Symbol`.
#[derive(Debug)]
pub enum Unpacked {
    /// Symbol stored inline in `Symbol`'s data fields.
    Inline(Inline),

    /// Symbol stored in a Pool
    Pooled(Pooled)
}

impl PackFormat for Unpacked {
    fn pack(&self) -> Symbol {
        match *self {
            Unpacked::Inline(ref x) => x.pack(),
            Unpacked::Pooled(ref x) => x.pack()
        }
    }

    fn unpack(sym: &Symbol) -> Unpacked {
        match sym.type_of() {
            Type::POOLED => Unpacked::Pooled(unsafe { <Pooled as PackFormat>::unpack(sym) }),
            Type::INLINE => Unpacked::Inline(unsafe { <Inline as PackFormat>::unpack(sym) }),
        }
    }

    fn as_slice_from<'t>(sym: &'t Symbol) -> &'t str {
        match sym.type_of() {
            Type::INLINE => unsafe { <Inline as PackFormat>::as_slice_from(sym) },
            Type::POOLED => unsafe { <Pooled as PackFormat>::as_slice_from(sym) }
        }
    }
}

impl std::str::Str for Unpacked {
    fn as_slice<'t>(&'t self) -> &'t str {
        match *self {
            Unpacked::Inline(ref x) => x.as_slice(),
            Unpacked::Pooled(ref x) => x.as_slice()
        }
    }
}

impl std::cmp::Eq for Unpacked {}
impl std::cmp::PartialEq for Unpacked {
    fn eq(&self, other: &Self) -> bool {
        match *self {
            Unpacked::Inline(ref si) =>
                match *other { Unpacked::Inline(ref oi) => si == oi, _ => false },
            Unpacked::Pooled(ref sp) =>
                match *other { Unpacked::Pooled(ref op) => sp == op, _ => false }
        }
    }
}

impl Unpacked {
}
<commit_msg>symbol::Pool: remove unnecessary intrusive refcount.<commit_after>//! Symbol- and name-storage facilities.
//!
//! This is based on [Servo's string-cache
//! library](https://github.com/servo/string-cache); at the moment it's very
//! lifetime-unsafe for `Pooled` instances.
use std;
use std::fmt;
use std::slice::bytes;
use std::hash::{hash, Hash, Hasher, SipHasher};
use std::collections::BTreeMap;
use std::borrow::ToOwned;

#[macro_use]
pub mod nameable;
pub use self::nameable::Nameable;

/// Methods required of `unpacked` types.
pub trait PackFormat {
    /// Pack an unpacked symbol in the implementing format into a generic
    /// Symbol object.
    fn pack(&self) -> Symbol;

    /// Unpack a generic Symbol into the implementing format.  This function is
    /// marked as `unsafe` because the caller must verify that the symbol is
    /// indeed packed by the receiver's implementation.
    unsafe fn unpack(sym: &Symbol) -> Self;

    /// Fetch a `&str` slice from a symbol packed in the implementing format.
    /// This function is marked as `unsafe` because the caller must verify that
    /// the symbol is indeed packed by the receiver's implementation.
    unsafe fn as_slice_from<'t>(sym: &'t Symbol) -> &'t str;
}

/* ****************************************************************
 * Pool
 */
/// A collection of symbols.
#[derive(Debug)]
pub struct Pool {
    map:  BTreeMap<u64,String>
}

impl Pool {
    /// Create a new symbol pool.
    pub fn new() -> Pool {
        Pool{refcount: 1, map: BTreeMap::new()}
    }
    /// Fetch a symbol corresponding to the given string.  If if
    pub fn symbol(&mut self, name: &str) -> Symbol {
        if name.len() <= INLINE_SYMBOL_MAX_LEN
        { Inline::new(name).pack() }
        else { let hsh = hash::<_, SipHasher>(&name) << 1;
               if ! self.map.contains_key(&hsh) {
                   self.map.insert(hsh, name.to_owned());
               }

               Pooled::new(hsh, self).pack()
        }
    }
}

/* ****************************************************************
 * Symbol
 */
/// An atomic `Copy`able string.  Symbol either encodes a short string
/// directly, or stores it in an external Pool.
#[derive(Debug)]
pub struct Symbol {
    value: [u64; 2]
}
impl Copy for Symbol {}

impl Symbol {
    /// Convert the symbol to its unpacked representation.
    #[inline(always)]
    pub fn unpack(&self) -> Unpacked
    { unsafe { <Unpacked as PackFormat>::unpack(self) } }

    /// Get symbol's pack format.
    #[inline(always)]
    pub fn type_of(&self) -> Type {
        match (self.value[0] & 0x01) as u8 {
            INLINE => Type::INLINE,
            POOLED => Type::POOLED,
            _ => unreachable!()
        }
    }

}

impl std::str::Str for Symbol {
    fn as_slice<'t>(&'t self) -> &'t str { unsafe { <Unpacked as PackFormat>::as_slice_from(self) } }
}

impl Nameable for Symbol {
    fn name(&self) -> Symbol { *self }
}

impl Hash for Symbol {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.as_slice().hash(state);
    }
}

impl fmt::Display for Symbol {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.as_slice())
    }
}

impl Eq for Symbol {}
impl PartialEq<Symbol> for Symbol
{
    fn eq(&self, other: &Symbol) -> bool
    {
        self.value == other.value
    }
}

/* ****************************************************************
 * Inline
 */
const INLINE_SYMBOL_MAX_LEN: usize = 15;//std::mem::size_of::<Symbol>() - 1;

/// Data format for symbols packed entirely into a `Symbol` instance.
#[derive(Debug)]
pub struct Inline{len: u8, data: [u8; INLINE_SYMBOL_MAX_LEN]}

impl Inline {
    /// Create a new Inline-format symbol using the given string.
    pub fn new(name: &str) -> Self {
        panic_unless!(name.len() <= INLINE_SYMBOL_MAX_LEN,
                      "Argument to Inline::new (length {}) exceeds maximum length ({}) for an inlined symbol",
                      name.len(), INLINE_SYMBOL_MAX_LEN);
        let mut buf: [u8; INLINE_SYMBOL_MAX_LEN] = [0; INLINE_SYMBOL_MAX_LEN];

        bytes::copy_memory(buf.as_mut_slice(), name.as_bytes());
        Inline{len: name.len() as u8, data: buf}
    }
}

impl std::str::Str for Inline {
    fn as_slice<'t>(&'t self) -> &'t str {
        let src: &[u8] = self.data.as_slice();
        std::str::from_utf8(&src[..(self.len as usize)]).unwrap()
    }
}

impl PackFormat for Inline {
    // Convert the Inline symbol into a generic packed representation.
    fn pack(&self) -> Symbol {
        unsafe {
            let mut out = Symbol{ value: [0; 2] };
            let mut dest: &mut [u8; 16] = std::mem::transmute(&mut out.value);
            dest[0] = (self.len << 1) | INLINE;
            bytes::copy_memory(&mut dest[1..], &self.data);
            out
        }
    }
    fn unpack(sym: &Symbol) -> Self {
        let mut out = Inline{len: 0, data: [0; INLINE_SYMBOL_MAX_LEN]};
        let src: &[u8;16] =
            unsafe { std::mem::transmute(&sym.value) };

        panic_unless!(src[0] & TAG_MASK == INLINE, "Invalid tag bit for inlined symbol!");
        out.len = src[0] >> 1;
        panic_unless!(out.len <= INLINE_SYMBOL_MAX_LEN as u8,
                      "Symbol length ({}) exceeds maximum ({}) for inlined symbol",
                      out.len, INLINE_SYMBOL_MAX_LEN);
        bytes::copy_memory(out.data.as_mut_slice(), &src[1..(out.len as usize + 1)]);

        out
    }

    fn as_slice_from<'t>(sym: &'t Symbol) -> &'t str {
        let src: &[u8; 16] = unsafe { std::mem::transmute(&sym.value) };
        panic_unless!(src[0] & TAG_MASK == INLINE, "Invalid tag bit for inlined symbol!");
        let len: usize = (src[0] >> 1) as usize;
        panic_unless!(len <= INLINE_SYMBOL_MAX_LEN,
                      "Symbol length ({}) exceeds maximum ({}) for inlined symbol",
                      len, INLINE_SYMBOL_MAX_LEN);

        std::str::from_utf8(&src[1..(len + 1)]).unwrap()
    }
}

impl std::cmp::Eq for Inline {}
impl std::cmp::PartialEq for Inline {
    fn eq(&self, other: &Self) -> bool {
        self.len == other.len && self.data == other.data
    }
}

/* ****************************************************************
 * Pooled
 */
/// Data format for symbols stored as references to a symbol pool.
#[derive(Debug)]
pub struct Pooled{key: u64, pool: *const Pool}

impl Pooled {
    #[inline(always)]
    pub fn new(key: u64, pool: *const Pool) -> Self {
        Pooled{key: key, pool: pool}
    }

}

impl std::str::Str for Pooled {
    fn as_slice<'u>(&'u self) -> &'u str {
        unsafe { (*self.pool).map[self.key].as_slice() }
    }
}

impl PackFormat for Pooled {
    fn pack(&self) -> Symbol {
        let mut val: [u64; 2] = [0; 2];
        val[0] = self.key  | POOLED as u64;
        val[1] = self.pool as u64;
        Symbol{value: val}
    }
    fn unpack(sym: &Symbol) -> Self {
        Pooled{key: sym.value[0],
               pool: unsafe { std::mem::transmute(sym.value[1] as *const Pool) }}
    }

    fn as_slice_from<'t>(sym: &'t Symbol) -> &'t str {
        panic_unless!(sym.value[0] & TAG_MASK as u64 == POOLED as u64,
                      "Invalid flag bit for pooled symbol");
        unsafe { std::mem::transmute::<_,&'t Pool>(sym.value[1] as *const Pool) }.
            map[sym.value[0]].as_slice()
    }
}

impl std::cmp::Eq for Pooled {}
impl std::cmp::PartialEq for Pooled {
    fn eq(&self, other: &Self) -> bool {
        self.key == other.key && self.pool as u64 == other.pool as u64
    }
}

/* ****************************************************************
 * Types
 */
const TAG_MASK: u8 = 0x01;
const POOLED: u8 = 0;
const INLINE: u8 = 1;

pub enum Type { INLINE = 0, POOLED = 1 }

impl std::cmp::Eq for Type {}
impl std::cmp::PartialEq for Type {
    fn eq(&self, other: &Self) -> bool {
        match *self { Type::INLINE => match *other { Type::INLINE => true, _ => false },
                      Type::POOLED => match *other { Type::POOLED => true, _ => false } }
    }
}

impl std::fmt::Debug for Type {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self { Type::INLINE => write!(f, "inline"),
                      Type::POOLED => write!(f, "pooled") }
    }
}

/* ****************************************************************
 * Unpacked
 */

/// A differentiated `Symbol`.
#[derive(Debug)]
pub enum Unpacked {
    /// Symbol stored inline in `Symbol`'s data fields.
    Inline(Inline),

    /// Symbol stored in a Pool
    Pooled(Pooled)
}

impl PackFormat for Unpacked {
    fn pack(&self) -> Symbol {
        match *self {
            Unpacked::Inline(ref x) => x.pack(),
            Unpacked::Pooled(ref x) => x.pack()
        }
    }

    fn unpack(sym: &Symbol) -> Unpacked {
        match sym.type_of() {
            Type::POOLED => Unpacked::Pooled(unsafe { <Pooled as PackFormat>::unpack(sym) }),
            Type::INLINE => Unpacked::Inline(unsafe { <Inline as PackFormat>::unpack(sym) }),
        }
    }

    fn as_slice_from<'t>(sym: &'t Symbol) -> &'t str {
        match sym.type_of() {
            Type::INLINE => unsafe { <Inline as PackFormat>::as_slice_from(sym) },
            Type::POOLED => unsafe { <Pooled as PackFormat>::as_slice_from(sym) }
        }
    }
}

impl std::str::Str for Unpacked {
    fn as_slice<'t>(&'t self) -> &'t str {
        match *self {
            Unpacked::Inline(ref x) => x.as_slice(),
            Unpacked::Pooled(ref x) => x.as_slice()
        }
    }
}

impl std::cmp::Eq for Unpacked {}
impl std::cmp::PartialEq for Unpacked {
    fn eq(&self, other: &Self) -> bool {
        match *self {
            Unpacked::Inline(ref si) =>
                match *other { Unpacked::Inline(ref oi) => si == oi, _ => false },
            Unpacked::Pooled(ref sp) =>
                match *other { Unpacked::Pooled(ref op) => sp == op, _ => false }
        }
    }
}

impl Unpacked {
}
<|endoftext|>