<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//! Servo's experimental layout system builds a tree of `FlowContext` and `RenderBox` objects and
/// solves layout constraints to obtain positions and display attributes of tree nodes. Positions
/// are computed in several tree traversals driven by the fundamental data dependencies required by
/// inline and block layout.
///
/// Flows are interior nodes in the layout tree and correspond closely to *flow contexts* in the
/// CSS specification. Flows are responsible for positioning their child flow contexts and render
/// boxes. Flows have purpose-specific fields, such as auxiliary line box structs, out-of-flow
/// child lists, and so on.
///
/// Currently, the important types of flows are:
///
/// * `BlockFlow`: a flow that establishes a block context. It has several child flows, each of
///   which are positioned according to block formatting context rules (CSS block boxes). Block
///   flows also contain a single `GenericBox` to represent their rendered borders, padding, etc.
///   (In the future, this render box may be folded into `BlockFlow` to save space.)
///
/// * `InlineFlow`: a flow that establishes an inline context. It has a flat list of child
///   boxes/flows that are subject to inline layout and line breaking and structs to represent
///   line breaks and mapping to CSS boxes, for the purpose of handling `getClientRects()` and
///   similar methods.

use dom::node::AbstractNode;
use layout::block::{BlockFlowData, BlockLayout};
use layout::box::RenderBox;
use layout::context::LayoutContext;
use layout::debug::DebugMethods;
use layout::display_list_builder::DisplayListBuilder;
use layout::inline::{InlineFlowData, InlineLayout};
use layout::root::{RootFlowData, RootLayout};

use core::cell::Cell;
use core::ptr;
use geom::point::Point2D;
use geom::rect::Rect;
use gfx::display_list::DisplayList;
use gfx::geometry::Au;

/// The type of the formatting context and data specific to each context, such as line box
/// structures or float lists.
pub enum FlowContext {
    AbsoluteFlow(@mut FlowData),
    BlockFlow(@mut BlockFlowData),
    FloatFlow(@mut FlowData),
    InlineBlockFlow(@mut FlowData),
    InlineFlow(@mut InlineFlowData),
    RootFlow(@mut RootFlowData),
    TableFlow(@mut FlowData),
}

pub enum FlowContextType {
    Flow_Absolute,
    Flow_Block,
    Flow_Float,
    Flow_InlineBlock,
    Flow_Inline,
    Flow_Root,
    Flow_Table
}

/// Data common to all flows.
///
/// FIXME: We need a naming convention for pseudo-inheritance like this. How about
/// `CommonFlowInfo`?
pub struct FlowData {
    node: AbstractNode,

    parent: Option<FlowContext>,
    first_child: Option<FlowContext>,
    last_child: Option<FlowContext>,
    prev_sibling: Option<FlowContext>,
    next_sibling: Option<FlowContext>,

    /* TODO (Issue #87): debug only */
    id: int,

    /* layout computations */
    // TODO: min/pref and position are used during disjoint phases of
    // layout; maybe combine into a single enum to save space.
    min_width: Au,
    pref_width: Au,
    position: Rect<Au>,
}

impl FlowData {
    pub fn new(id: int, node: AbstractNode) -> FlowData {
        FlowData {
            node: node,

            parent: None,
            first_child: None,
            last_child: None,
            prev_sibling: None,
            next_sibling: None,

            id: id,

            min_width: Au(0),
            pref_width: Au(0),
            position: Au::zero_rect(),
        }
    }
}

impl<'self> FlowContext {
    #[inline(always)]
    pub fn with_common_info<R>(&self, block: &fn(&mut FlowData) -> R) -> R {
        match *self {
            AbsoluteFlow(info) => block(info),
            BlockFlow(info) => {
                let info = &mut *info;  // FIXME: Borrow check workaround.
                block(&mut info.common)
            }
            FloatFlow(info) => block(info),
            InlineBlockFlow(info) => block(info),
            InlineFlow(info) => {
                let info = &mut *info;  // FIXME: Borrow check workaround.
                block(&mut info.common)
            }
            RootFlow(info) => {
                let info = &mut *info;  // FIXME: Borrow check workaround.
                block(&mut info.common)
            }
            TableFlow(info) => block(info),
        }
    }

    pub fn position(&self) -> Rect<Au> {
        do self.with_common_info |common_info| {
            common_info.position
        }
    }

    /// Returns the ID of this flow.
    #[inline(always)]
    pub fn id(&self) -> int {
        do self.with_common_info |info| {
            info.id
        }
    }

    /// Iterates over the immediate children of this flow.
    ///
    /// TODO: Fold me into `util::tree`.
    pub fn each_child(&self, f: &fn(FlowContext) -> bool) {
        let mut current_opt = self.with_common_info(|info| info.first_child);
        while !current_opt.is_none() {
            let current = current_opt.get();
            if !f(current) {
                break;
            }
            current_opt = current.with_common_info(|info| info.next_sibling);
        }
    }

    /// Adds the given flow to the end of the list of this flow's children. The new child must be
    /// detached from the tree before calling this method.
    ///
    /// TODO: Fold me into `util::tree`.
    pub fn add_child(&self, child: FlowContext) {
        do self.with_common_info |self_info| {
            do child.with_common_info |child_info| {
                assert!(child_info.parent.is_none());
                assert!(child_info.prev_sibling.is_none());
                assert!(child_info.next_sibling.is_none());

                match self_info.last_child {
                    None => {
                        self_info.first_child = Some(child);
                    }
                    Some(last_child) => {
                        do last_child.with_common_info |last_child_info| {
                            assert!(last_child_info.next_sibling.is_none());
                            last_child_info.next_sibling = Some(child);
                            child_info.prev_sibling = Some(last_child);
                        }
                    }
                }

                self_info.last_child = Some(child);
                child_info.parent = Some(*self);
            }
        }
    }

    /// Removes the given flow from the tree.
    ///
    /// TODO: Fold me into `util::tree`.
    pub fn remove_child(&self, child: FlowContext) {
        do self.with_common_info |self_info| {
            do child.with_common_info |child_info| {
                assert!(child_info.parent.is_some());

                match child_info.prev_sibling {
                    None => self_info.first_child = child_info.next_sibling,
                    Some(prev_sibling) => {
                        do prev_sibling.with_common_info |prev_sibling_info| {
                            prev_sibling_info.next_sibling = child_info.next_sibling;
                            child_info.prev_sibling = None;
                        }
                    }
                }

                match child_info.next_sibling {
                    None => {
                        do child.with_common_info |child_info| {
                            self_info.last_child = child_info.prev_sibling;
                        }
                    }
                    Some(next_sibling) => {
                        do next_sibling.with_common_info |next_sibling_info| {
                            next_sibling_info.prev_sibling = Some(next_sibling);
                            child_info.next_sibling = None;
                        }
                    }
                }

                child_info.parent = None;
            }
        }
    }

    pub fn inline(&self) -> @mut InlineFlowData {
        match *self {
            InlineFlow(info) => info,
            _ => fail!(fmt!("Tried to access inline data of non-inline: f%d", self.id()))
        }
    }

    pub fn block(&self) -> @mut BlockFlowData {
        match *self {
            BlockFlow(info) => info,
            _ => fail!(fmt!("Tried to access block data of non-block: f%d", self.id()))
        }
    }

    pub fn root(&self) -> @mut RootFlowData {
        match *self {
            RootFlow(info) => info,
            _ => fail!(fmt!("Tried to access root data of non-root: f%d", self.id()))
        }
    }

    pub fn bubble_widths(&self, ctx: &mut LayoutContext) {
        match *self {
            BlockFlow(*)     => self.bubble_widths_block(ctx),
            InlineFlow(info) => info.bubble_widths_inline(ctx),
            RootFlow(info)   => info.bubble_widths_root(ctx),
            _ => fail!(fmt!("Tried to bubble_widths of flow: f%d", self.id()))
        }
    }

    pub fn assign_widths(&self, ctx: &mut LayoutContext) {
        match *self {
            BlockFlow(*)     => self.assign_widths_block(ctx),
            InlineFlow(info) => info.assign_widths_inline(ctx),
            RootFlow(info)   => info.assign_widths_root(ctx),
            _ => fail!(fmt!("Tried to assign_widths of flow: f%d", self.id()))
        }
    }

    pub fn assign_height(&self, ctx: &mut LayoutContext) {
        match *self {
            BlockFlow(*)     => self.assign_height_block(ctx),
            InlineFlow(info) => info.assign_height_inline(ctx),
            RootFlow(info)   => info.assign_height_root(ctx),
            _ => fail!(fmt!("Tried to assign_height of flow: f%d", self.id()))
        }
    }

    pub fn build_display_list_recurse(&self,
                                      builder: &DisplayListBuilder,
                                      dirty: &Rect<Au>,
                                      offset: &Point2D<Au>,
                                      list: &Cell<DisplayList>) {
        do self.with_common_info |info| {
            debug!("FlowContext::build_display_list at %?: %s", info.position, self.debug_str());
        }

        match *self {
            RootFlow(info) => info.build_display_list_root(builder, dirty, offset, list),
            BlockFlow(*) => self.build_display_list_block(builder, dirty, offset, list),
            InlineFlow(info) => info.build_display_list_inline(builder, dirty, offset, list),
            _ => fail!(fmt!("Tried to build_display_list_recurse of flow: %?", self))
        }
    }

    // Actual methods that do not require much flow-specific logic
    pub fn foldl_all_boxes<B:Copy>(&self, seed: B, cb: &fn(a: B, b: @mut RenderBox) -> B) -> B {
        match *self {
            RootFlow(root) => {
                let root = &mut *root;
                root.box.map_default(seed, |box| { cb(seed, *box) })
            }
            BlockFlow(block) => {
                let block = &mut *block;
                block.box.map_default(seed, |box| { cb(seed, *box) })
            }
            InlineFlow(inline) => {
                let inline = &mut *inline;
                inline.boxes.foldl(seed, |acc, box| { cb(*acc, *box) })
            }
            _ => fail!(fmt!("Don't know how to iterate node's RenderBoxes for %?", self))
        }
    }

    pub fn foldl_boxes_for_node<B:Copy>(&self,
                                        node: AbstractNode,
                                        seed: B,
                                        callback: &fn(a: B, @mut RenderBox) -> B)
                                        -> B {
        do self.foldl_all_boxes(seed) |acc, box| {
            if box.d().node == node {
                callback(acc, box)
            } else {
                acc
            }
        }
    }

    pub fn iter_all_boxes(&self, cb: &fn(@mut RenderBox) -> bool) {
        match *self {
            RootFlow(root) => {
                let root = &mut *root;
                for root.box.each |box| {
                    if !cb(*box) {
                        break;
                    }
                }
            }
            BlockFlow(block) => {
                let block = &mut *block;
                for block.box.each |box| {
                    if !cb(*box) {
                        break;
                    }
                }
            }
            InlineFlow(inline) => {
                let inline = &mut *inline;
                for inline.boxes.each |box| {
                    if !cb(*box) {
                        break;
                    }
                }
            }
            _ => fail!(fmt!("Don't know how to iterate node's RenderBoxes for %?", self))
        }
    }

    pub fn iter_boxes_for_node(&self, node: AbstractNode, callback: &fn(@mut RenderBox) -> bool) {
        for self.iter_all_boxes |box| {
            if box.d().node == node {
                if !callback(box) {
                    break;
                }
            }
        }
    }
}

impl DebugMethods for FlowContext {
    fn dump(&self) {
        self.dump_indent(0);
    }

    /// Dumps the flow tree, for debugging, with indentation.
    fn dump_indent(&self, indent: uint) {
        let mut s = ~"|";
        for uint::range(0, indent) |_i| {
            s += ~"---- ";
        }

        s += self.debug_str();
        debug!("%s", s);

        // FIXME: this should have a pure/const version?
        for self.each_child |child| {
            child.dump_indent(indent + 1)
        }
    }

    fn debug_str(&self) -> ~str {
        let repr = match *self {
            InlineFlow(inline) => {
                let inline = &mut *inline;
                let mut s = inline.boxes.foldl(~"InlineFlow(children=", |s, box| {
                    fmt!("%s b%d", *s, box.d().id)
                });
                s += ~")";
                s
            },
            BlockFlow(block) => {
                let block = &mut *block;
                match block.box {
                    Some(box) => fmt!("BlockFlow(box=b%d)", box.d().id),
                    None => ~"BlockFlow",
                }
            },
            RootFlow(root) => {
                let root = &mut *root;
                match root.box {
                    Some(box) => fmt!("RootFlo(box=b%d)", box.d().id),
                    None => ~"RootFlow",
                }
            },
            _ => ~"(Unknown flow)"
        };

        do self.with_common_info |info| {
            fmt!("f%? %?", info.id, repr)
        }
    }
}

<commit_msg>layout: Document the root flow.<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//! Servo's experimental layout system builds a tree of `FlowContext` and `RenderBox` objects and
/// solves layout constraints to obtain positions and display attributes of tree nodes. Positions
/// are computed in several tree traversals driven by the fundamental data dependencies required by
/// inline and block layout.
///
/// Flows are interior nodes in the layout tree and correspond closely to *flow contexts* in the
/// CSS specification. Flows are responsible for positioning their child flow contexts and render
/// boxes. Flows have purpose-specific fields, such as auxiliary line box structs, out-of-flow
/// child lists, and so on.
///
/// Currently, the important types of flows are:
///
/// * `BlockFlow`: A flow that establishes a block context. It has several child flows, each of
///   which are positioned according to block formatting context rules (CSS block boxes). Block
///   flows also contain a single `GenericBox` to represent their rendered borders, padding, etc.
///   (In the future, this render box may be folded into `BlockFlow` to save space.)
///
/// * `InlineFlow`: A flow that establishes an inline context. It has a flat list of child
///   boxes/flows that are subject to inline layout and line breaking and structs to represent
///   line breaks and mapping to CSS boxes, for the purpose of handling `getClientRects()` and
///   similar methods.
///
/// * `RootFlow`: The flow at the root of the tree. This flow behaves like a `BlockFlow`, except
///   that stretches to the boundaries of the viewport.

use dom::node::AbstractNode;
use layout::block::{BlockFlowData, BlockLayout};
use layout::box::RenderBox;
use layout::context::LayoutContext;
use layout::debug::DebugMethods;
use layout::display_list_builder::DisplayListBuilder;
use layout::inline::{InlineFlowData};
use layout::root::{RootFlowData};

use core::cell::Cell;
use geom::point::Point2D;
use geom::rect::Rect;
use gfx::display_list::DisplayList;
use gfx::geometry::Au;

/// The type of the formatting context and data specific to each context, such as line box
/// structures or float lists.
pub enum FlowContext {
    AbsoluteFlow(@mut FlowData),
    BlockFlow(@mut BlockFlowData),
    FloatFlow(@mut FlowData),
    InlineBlockFlow(@mut FlowData),
    InlineFlow(@mut InlineFlowData),
    RootFlow(@mut RootFlowData),
    TableFlow(@mut FlowData),
}

pub enum FlowContextType {
    Flow_Absolute,
    Flow_Block,
    Flow_Float,
    Flow_InlineBlock,
    Flow_Inline,
    Flow_Root,
    Flow_Table
}

/// Data common to all flows.
///
/// FIXME: We need a naming convention for pseudo-inheritance like this. How about
/// `CommonFlowInfo`?
pub struct FlowData {
    node: AbstractNode,

    parent: Option<FlowContext>,
    first_child: Option<FlowContext>,
    last_child: Option<FlowContext>,
    prev_sibling: Option<FlowContext>,
    next_sibling: Option<FlowContext>,

    /* TODO (Issue #87): debug only */
    id: int,

    /* layout computations */
    // TODO: min/pref and position are used during disjoint phases of
    // layout; maybe combine into a single enum to save space.
    min_width: Au,
    pref_width: Au,
    position: Rect<Au>,
}

impl FlowData {
    pub fn new(id: int, node: AbstractNode) -> FlowData {
        FlowData {
            node: node,

            parent: None,
            first_child: None,
            last_child: None,
            prev_sibling: None,
            next_sibling: None,

            id: id,

            min_width: Au(0),
            pref_width: Au(0),
            position: Au::zero_rect(),
        }
    }
}

impl<'self> FlowContext {
    #[inline(always)]
    pub fn with_common_info<R>(&self, block: &fn(&mut FlowData) -> R) -> R {
        match *self {
            AbsoluteFlow(info) => block(info),
            BlockFlow(info) => {
                let info = &mut *info;  // FIXME: Borrow check workaround.
                block(&mut info.common)
            }
            FloatFlow(info) => block(info),
            InlineBlockFlow(info) => block(info),
            InlineFlow(info) => {
                let info = &mut *info;  // FIXME: Borrow check workaround.
                block(&mut info.common)
            }
            RootFlow(info) => {
                let info = &mut *info;  // FIXME: Borrow check workaround.
                block(&mut info.common)
            }
            TableFlow(info) => block(info),
        }
    }

    pub fn position(&self) -> Rect<Au> {
        do self.with_common_info |common_info| {
            common_info.position
        }
    }

    /// Returns the ID of this flow.
    #[inline(always)]
    pub fn id(&self) -> int {
        do self.with_common_info |info| {
            info.id
        }
    }

    /// Iterates over the immediate children of this flow.
    ///
    /// TODO: Fold me into `util::tree`.
    pub fn each_child(&self, f: &fn(FlowContext) -> bool) {
        let mut current_opt = self.with_common_info(|info| info.first_child);
        while !current_opt.is_none() {
            let current = current_opt.get();
            if !f(current) {
                break;
            }
            current_opt = current.with_common_info(|info| info.next_sibling);
        }
    }

    /// Adds the given flow to the end of the list of this flow's children. The new child must be
    /// detached from the tree before calling this method.
    ///
    /// TODO: Fold me into `util::tree`.
    pub fn add_child(&self, child: FlowContext) {
        do self.with_common_info |self_info| {
            do child.with_common_info |child_info| {
                assert!(child_info.parent.is_none());
                assert!(child_info.prev_sibling.is_none());
                assert!(child_info.next_sibling.is_none());

                match self_info.last_child {
                    None => {
                        self_info.first_child = Some(child);
                    }
                    Some(last_child) => {
                        do last_child.with_common_info |last_child_info| {
                            assert!(last_child_info.next_sibling.is_none());
                            last_child_info.next_sibling = Some(child);
                            child_info.prev_sibling = Some(last_child);
                        }
                    }
                }

                self_info.last_child = Some(child);
                child_info.parent = Some(*self);
            }
        }
    }

    /// Removes the given flow from the tree.
    ///
    /// TODO: Fold me into `util::tree`.
    pub fn remove_child(&self, child: FlowContext) {
        do self.with_common_info |self_info| {
            do child.with_common_info |child_info| {
                assert!(child_info.parent.is_some());

                match child_info.prev_sibling {
                    None => self_info.first_child = child_info.next_sibling,
                    Some(prev_sibling) => {
                        do prev_sibling.with_common_info |prev_sibling_info| {
                            prev_sibling_info.next_sibling = child_info.next_sibling;
                            child_info.prev_sibling = None;
                        }
                    }
                }

                match child_info.next_sibling {
                    None => {
                        do child.with_common_info |child_info| {
                            self_info.last_child = child_info.prev_sibling;
                        }
                    }
                    Some(next_sibling) => {
                        do next_sibling.with_common_info |next_sibling_info| {
                            next_sibling_info.prev_sibling = Some(next_sibling);
                            child_info.next_sibling = None;
                        }
                    }
                }

                child_info.parent = None;
            }
        }
    }

    pub fn inline(&self) -> @mut InlineFlowData {
        match *self {
            InlineFlow(info) => info,
            _ => fail!(fmt!("Tried to access inline data of non-inline: f%d", self.id()))
        }
    }

    pub fn block(&self) -> @mut BlockFlowData {
        match *self {
            BlockFlow(info) => info,
            _ => fail!(fmt!("Tried to access block data of non-block: f%d", self.id()))
        }
    }

    pub fn root(&self) -> @mut RootFlowData {
        match *self {
            RootFlow(info) => info,
            _ => fail!(fmt!("Tried to access root data of non-root: f%d", self.id()))
        }
    }

    pub fn bubble_widths(&self, ctx: &mut LayoutContext) {
        match *self {
            BlockFlow(*)     => self.bubble_widths_block(ctx),
            InlineFlow(info) => info.bubble_widths_inline(ctx),
            RootFlow(info)   => info.bubble_widths_root(ctx),
            _ => fail!(fmt!("Tried to bubble_widths of flow: f%d", self.id()))
        }
    }

    pub fn assign_widths(&self, ctx: &mut LayoutContext) {
        match *self {
            BlockFlow(*)     => self.assign_widths_block(ctx),
            InlineFlow(info) => info.assign_widths_inline(ctx),
            RootFlow(info)   => info.assign_widths_root(ctx),
            _ => fail!(fmt!("Tried to assign_widths of flow: f%d", self.id()))
        }
    }

    pub fn assign_height(&self, ctx: &mut LayoutContext) {
        match *self {
            BlockFlow(*)     => self.assign_height_block(ctx),
            InlineFlow(info) => info.assign_height_inline(ctx),
            RootFlow(info)   => info.assign_height_root(ctx),
            _ => fail!(fmt!("Tried to assign_height of flow: f%d", self.id()))
        }
    }

    pub fn build_display_list_recurse(&self,
                                      builder: &DisplayListBuilder,
                                      dirty: &Rect<Au>,
                                      offset: &Point2D<Au>,
                                      list: &Cell<DisplayList>) {
        do self.with_common_info |info| {
            debug!("FlowContext::build_display_list at %?: %s", info.position, self.debug_str());
        }

        match *self {
            RootFlow(info) => info.build_display_list_root(builder, dirty, offset, list),
            BlockFlow(*) => self.build_display_list_block(builder, dirty, offset, list),
            InlineFlow(info) => info.build_display_list_inline(builder, dirty, offset, list),
            _ => fail!(fmt!("Tried to build_display_list_recurse of flow: %?", self))
        }
    }

    // Actual methods that do not require much flow-specific logic
    pub fn foldl_all_boxes<B:Copy>(&self, seed: B, cb: &fn(a: B, b: @mut RenderBox) -> B) -> B {
        match *self {
            RootFlow(root) => {
                let root = &mut *root;
                root.box.map_default(seed, |box| { cb(seed, *box) })
            }
            BlockFlow(block) => {
                let block = &mut *block;
                block.box.map_default(seed, |box| { cb(seed, *box) })
            }
            InlineFlow(inline) => {
                let inline = &mut *inline;
                inline.boxes.foldl(seed, |acc, box| { cb(*acc, *box) })
            }
            _ => fail!(fmt!("Don't know how to iterate node's RenderBoxes for %?", self))
        }
    }

    pub fn foldl_boxes_for_node<B:Copy>(&self,
                                        node: AbstractNode,
                                        seed: B,
                                        callback: &fn(a: B, @mut RenderBox) -> B)
                                        -> B {
        do self.foldl_all_boxes(seed) |acc, box| {
            if box.d().node == node {
                callback(acc, box)
            } else {
                acc
            }
        }
    }

    pub fn iter_all_boxes(&self, cb: &fn(@mut RenderBox) -> bool) {
        match *self {
            RootFlow(root) => {
                let root = &mut *root;
                for root.box.each |box| {
                    if !cb(*box) {
                        break;
                    }
                }
            }
            BlockFlow(block) => {
                let block = &mut *block;
                for block.box.each |box| {
                    if !cb(*box) {
                        break;
                    }
                }
            }
            InlineFlow(inline) => {
                let inline = &mut *inline;
                for inline.boxes.each |box| {
                    if !cb(*box) {
                        break;
                    }
                }
            }
            _ => fail!(fmt!("Don't know how to iterate node's RenderBoxes for %?", self))
        }
    }

    pub fn iter_boxes_for_node(&self, node: AbstractNode, callback: &fn(@mut RenderBox) -> bool) {
        for self.iter_all_boxes |box| {
            if box.d().node == node {
                if !callback(box) {
                    break;
                }
            }
        }
    }
}

impl DebugMethods for FlowContext {
    fn dump(&self) {
        self.dump_indent(0);
    }

    /// Dumps the flow tree, for debugging, with indentation.
    fn dump_indent(&self, indent: uint) {
        let mut s = ~"|";
        for uint::range(0, indent) |_i| {
            s += ~"---- ";
        }

        s += self.debug_str();
        debug!("%s", s);

        // FIXME: this should have a pure/const version?
        for self.each_child |child| {
            child.dump_indent(indent + 1)
        }
    }

    fn debug_str(&self) -> ~str {
        let repr = match *self {
            InlineFlow(inline) => {
                let inline = &mut *inline;
                let mut s = inline.boxes.foldl(~"InlineFlow(children=", |s, box| {
                    fmt!("%s b%d", *s, box.d().id)
                });
                s += ~")";
                s
            },
            BlockFlow(block) => {
                let block = &mut *block;
                match block.box {
                    Some(box) => fmt!("BlockFlow(box=b%d)", box.d().id),
                    None => ~"BlockFlow",
                }
            },
            RootFlow(root) => {
                let root = &mut *root;
                match root.box {
                    Some(box) => fmt!("RootFlo(box=b%d)", box.d().id),
                    None => ~"RootFlow",
                }
            },
            _ => ~"(Unknown flow)"
        };

        do self.with_common_info |info| {
            fmt!("f%? %?", info.id, repr)
        }
    }
}

<|endoftext|>


<commit_before>use yaml_rust::{YamlLoader, Yaml, ScanError};
use std::collections::{HashMap, BTreeMap};
use onig::{Regex, Captures, Syntax, self};
use std::rc::Rc;

pub type ScopeElement = String;
pub type CaptureMapping = HashMap<usize, ScopeElement>;

#[derive(Debug)]
pub struct SyntaxDefinition {
    pub name: String,
    pub file_extensions: Vec<String>,
    pub scope: ScopeElement,
    pub first_line_match: Option<Regex>,
    pub hidden: bool,

    pub variables: HashMap<String, String>,
    pub contexts: HashMap<String, Rc<Context>>,
}

#[derive(Debug)]
pub struct Context {
    pub meta_scope: Option<ScopeElement>,
    pub meta_content_scope: Option<ScopeElement>,
    pub meta_include_prototype: bool,

    pub patterns: Vec<Pattern>,
}

#[derive(Debug)]
pub enum Pattern {
    Match(MatchPattern),
    Include(ContextReference),
}

#[derive(Debug)]
pub struct MatchPattern {
    pub regex_str: String,
    // present unless contains backrefs and has to be dynamically compiled
    pub regex: Option<Regex>,
    pub scope: Vec<ScopeElement>,
    pub captures: Option<CaptureMapping>,
    pub operation: MatchOperation,
}

#[derive(Debug)]
pub enum ContextReference {
    Named(String),
    ByScope {
        name: String,
        sub_context: Option<String>,
    },
    File {
        name: String,
        sub_context: Option<String>,
    },
    Inline(Rc<Context>),
}

#[derive(Debug)]
pub enum MatchOperation {
    Push(Vec<ContextReference>),
    Set(Vec<ContextReference>),
    Pop,
    None,
}

#[derive(Debug)]
pub enum ParseError {
    InvalidYaml(ScanError),
    EmptyFile,
    MissingMandatoryKey(&'static str),
    RegexCompileError(onig::Error),
    TypeMismatch,
}

fn get_key<'a, R, F: FnOnce(&'a Yaml) -> Option<R>>(map: &'a BTreeMap<Yaml, Yaml>,
                                                    key: &'static str,
                                                    f: F)
                                                    -> Result<R, ParseError> {
    map.get(&Yaml::String(key.to_owned()))
        .ok_or(ParseError::MissingMandatoryKey(key))
        .and_then(|x| f(x).ok_or(ParseError::TypeMismatch))
}

struct ParserState {
    variables: HashMap<String, String>,
    variable_regex: Regex,
    backref_regex: Regex,
    short_multibyte_regex: Regex,
}

impl SyntaxDefinition {
    pub fn load_from_str(s: &str) -> Result<SyntaxDefinition, ParseError> {
        let docs = match YamlLoader::load_from_str(s) {
            Ok(x) => x,
            Err(e) => return Err(ParseError::InvalidYaml(e)),
        };
        if docs.len() == 0 {
            return Err(ParseError::EmptyFile);
        }
        let doc = &docs[0];
        SyntaxDefinition::parse_top_level(doc)
    }

    fn parse_top_level(doc: &Yaml) -> Result<SyntaxDefinition, ParseError> {
        let h = try!(doc.as_hash().ok_or(ParseError::TypeMismatch));

        let mut variables = HashMap::new();
        if let Ok(map) = get_key(h, "variables", |x| x.as_hash()) {
            for (key, value) in map.iter() {
                if let (Some(key_str), Some(val_str)) = (key.as_str(), value.as_str()) {
                    variables.insert(key_str.to_owned(), val_str.to_owned());
                }
            }
        }
        let state = ParserState {
            variables: variables,
            variable_regex: Regex::new(r"\{\{([A-Za-z0-9_]+)\}\}").unwrap(),
            backref_regex: Regex::new(r"\\\d").unwrap(),
            short_multibyte_regex: Regex::new(r"\\x([a-fA-F][a-fA-F0-9])").unwrap(),
        };

        let contexts_hash = try!(get_key(h, "contexts", |x| x.as_hash()));
        let contexts = try!(SyntaxDefinition::parse_contexts(contexts_hash, &state));

        let defn = SyntaxDefinition {
            name: try!(get_key(h, "name", |x| x.as_str())).to_owned(),
            scope: try!(get_key(h, "scope", |x| x.as_str())).to_owned(),
            file_extensions: {
                get_key(h, "file_extensions", |x| x.as_vec())
                    .map(|v| v.iter().filter_map(|y| y.as_str()).map(|x| x.to_owned()).collect())
                    .unwrap_or_else(|_| Vec::new())
            },
            first_line_match: if let Ok(s) = get_key(h, "first_line_match", |x| x.as_str()) {
                Some(try!(Regex::new(s).map_err(|e| ParseError::RegexCompileError(e))))
            } else {
                None
            },
            hidden: get_key(h, "hidden", |x| x.as_bool()).unwrap_or(false),

            variables: state.variables.clone(),
            contexts: contexts,
        };
        Ok(defn)
    }

    fn parse_contexts(map: &BTreeMap<Yaml, Yaml>,
                      state: &ParserState)
                      -> Result<HashMap<String, Rc<Context>>, ParseError> {
        let mut contexts = HashMap::new();
        for (key, value) in map.iter() {
            if let (Some(name), Some(val_vec)) = (key.as_str(), value.as_vec()) {
                let context = try!(SyntaxDefinition::parse_context(val_vec, state));
                contexts.insert(name.to_owned(), context);
            }
        }
        return Ok(contexts);
    }

    fn parse_context(vec: &Vec<Yaml>,
                     state: &ParserState)
                     -> Result<Rc<Context>, ParseError> {
        let mut context_rc = Rc::new(Context {
            meta_scope: None,
            meta_content_scope: None,
            meta_include_prototype: true,
            patterns: Vec::new(),
        });
        {
            let context = Rc::get_mut(&mut context_rc).unwrap();
            for y in vec.iter() {
                let map = try!(y.as_hash().ok_or(ParseError::TypeMismatch));

                if let Some(x) = get_key(map, "meta_scope", |x| x.as_str()).ok() {
                    context.meta_scope = Some(x.to_owned());
                } else if let Some(x) = get_key(map, "meta_content_scope", |x| x.as_str()).ok() {
                    context.meta_scope = Some(x.to_owned());
                } else if let Some(x) = get_key(map, "meta_include_prototype", |x| x.as_bool()).ok() {
                    context.meta_include_prototype = x;
                } else if let Some(x) = get_key(map, "include", |x| Some(x)).ok() {
                    let reference = try!(SyntaxDefinition::parse_reference(x, state));
                    context.patterns.push(Pattern::Include(reference));
                } else {
                    let pattern = try!(SyntaxDefinition::parse_match_pattern(map, state));
                    context.patterns.push(Pattern::Match(pattern));
                }

            }
        }
        return Ok(context_rc);
    }

    fn parse_reference(y: &Yaml,
                       state: &ParserState)
                       -> Result<ContextReference, ParseError> {
        if let Some(s) = y.as_str() {
            let parts: Vec<&str> = s.split("#").collect();
            let sub_context = if parts.len() > 1 {
                Some(parts[1].to_owned())
            } else {
                None
            };
            if parts[0].starts_with("scope:") {
                Ok(ContextReference::ByScope {
                    name: parts[0][6..].to_owned(),
                    sub_context: sub_context,
                })
            } else if parts[0].ends_with(".sublime-syntax") {
                Ok(ContextReference::File {
                    name: s.to_owned(),
                    sub_context: sub_context,
                })
            } else {
                Ok(ContextReference::Named(parts[0].to_owned()))
            }
        } else if let Some(v) = y.as_vec() {
            let context = try!(SyntaxDefinition::parse_context(v, state));
            Ok(ContextReference::Inline(context))
        } else {
            Err(ParseError::TypeMismatch)
        }
    }

    fn parse_match_pattern(map: &BTreeMap<Yaml, Yaml>,
                           state: &ParserState)
                           -> Result<MatchPattern, ParseError> {
        let raw_regex = try!(get_key(map, "match", |x| x.as_str()));
        let regex_str_1 = state.variable_regex.replace_all(raw_regex, |caps: &Captures| {
            state.variables.get(caps.at(1).unwrap_or("")).map(|x| &**x).unwrap_or("").to_owned()
        });
        // bug triggered by CSS.sublime-syntax, dunno why this is necessary
        let regex_str = state.short_multibyte_regex.replace_all(&regex_str_1, |caps: &Captures| {
            format!("\\x{{000000{}}}", caps.at(1).unwrap_or(""))
        });
        // println!("{:?}", regex_str);

        // if it contains back references we can't resolve it until runtime
        let regex = if state.backref_regex.find(&regex_str).is_some() {
            None
        } else {
            Some(try!(Regex::with_options(&regex_str, onig::REGEX_OPTION_CAPTURE_GROUP, Syntax::default()).map_err(|e| ParseError::RegexCompileError(e))))
        };

        let scope = get_key(map, "scope", |x| x.as_str()).ok().map(|s| s.split_whitespace().map(|scope| scope.to_owned()).collect()).unwrap_or_else(|| vec![]);

        let captures = if let Ok(map) = get_key(map, "captures", |x| x.as_hash()) {
            let mut res_map = HashMap::new();
            for (key, value) in map.iter() {
                if let (Some(key_int), Some(val_str)) = (key.as_i64(), value.as_str()) {
                    res_map.insert(key_int as usize, val_str.to_owned());
                }
            }
            Some(res_map)
        } else {
            None
        };

        let operation = if let Ok(_) = get_key(map, "pop", |x| Some(x)) {
            MatchOperation::Pop
        } else if let Ok(y) = get_key(map, "push", |x| Some(x)) {
            MatchOperation::Push(try!(SyntaxDefinition::parse_pushargs(y, state)))
        } else if let Ok(y) = get_key(map, "set", |x| Some(x)) {
            MatchOperation::Set(try!(SyntaxDefinition::parse_pushargs(y, state)))
        } else {
            MatchOperation::None
        };

        let pattern = MatchPattern {
            regex_str: regex_str,
            regex: regex,
            scope: scope,
            captures: captures,
            operation: operation,
        };
        return Ok(pattern);
    }

    fn parse_pushargs(y: &Yaml,
                      state: &ParserState)
                      -> Result<Vec<ContextReference>, ParseError> {
        // check for a push of multiple items
        if y.as_vec().map(|v| !v.is_empty() && v[0].as_str().is_some()).unwrap_or(false) {
            // this works because Result implements FromIterator to handle the errors
            y.as_vec().unwrap().iter().map(|x| SyntaxDefinition::parse_reference(x, state)).collect()
        } else {
            Ok(vec![try!(SyntaxDefinition::parse_reference(y, state))])
        }
    }
}

#[cfg(test)]
mod tests {
    #[test]
    fn can_parse() {
        use syntax_definition::{SyntaxDefinition, Pattern, CaptureMapping};
        let defn: SyntaxDefinition =
            SyntaxDefinition::load_from_str("name: C\nscope: source.c\ncontexts: {}").unwrap();
        assert_eq!(defn.name, "C");
        assert_eq!(defn.scope, "source.c");
        let exts_empty: Vec<String> = Vec::new();
        assert_eq!(defn.file_extensions, exts_empty);
        assert_eq!(defn.hidden, false);
        assert!(defn.variables.is_empty());
        let defn2: SyntaxDefinition =
            SyntaxDefinition::load_from_str("
        name: C
        scope: source.c
        file_extensions: [c, h]
        hidden: true
        variables:
          ident: '[QY]+'
        contexts:
          main:
            - match: \\b(if|else|for|while|{{ident}})\\b
              scope: keyword.control.c keyword.looping.c
              captures:
                  1: meta.preprocessor.c++
                  2: keyword.control.include.c++
              push: [string, 'scope:source.c#main', 'CSS.sublime-syntax#rule-list-body']
            - match: '\"'
              push: string
          string:
            - meta_scope: string.quoted.double.c
            - match: \\\\.
              scope: constant.character.escape.c
            - match: '\"'
              pop: true
        ")
                .unwrap();
        assert_eq!(defn2.name, "C");
        assert_eq!(defn2.scope, "source.c");
        let exts: Vec<String> = vec![String::from("c"), String::from("h")];
        assert_eq!(defn2.file_extensions, exts);
        assert_eq!(defn2.hidden, true);
        assert_eq!(defn2.variables.get("ident").unwrap(), "[QY]+");

        let n: Option<String> = None;
        println!("{:?}", defn2);
        // assert!(false);
        assert_eq!(defn2.contexts["main"].meta_scope, n);
        assert_eq!(defn2.contexts["main"].meta_include_prototype, true);
        assert_eq!(defn2.contexts["string"].meta_scope,
                   Some(String::from("string.quoted.double.c")));
        let first_pattern: &Pattern = &defn2.contexts["main"].patterns[0];
        match first_pattern {
            &Pattern::Match(ref match_pat) => {
                let m : &CaptureMapping = match_pat.captures.as_ref().expect("test failed");
                let x : &String = &m[&1];
                assert_eq!(x, "meta.preprocessor.c++");
                assert_eq!(format!("{:?}",match_pat.operation),
                    "Push([Named(\"string\"), ByScope { name: \"source.c\", sub_context: Some(\"main\") }, \
                    File { name: \"CSS.sublime-syntax#rule-list-body\", sub_context: Some(\"rule-list-body\") }])");

                assert_eq!(match_pat.scope, vec![String::from("keyword.control.c"), String::from("keyword.looping.c")]);

                let r = match_pat.regex.as_ref().unwrap();
                assert!(r.is_match("else"));
                assert!(!r.is_match("elses"));
                assert!(!r.is_match("elose"));
                assert!(r.is_match("QYYQQQ"));
                assert!(!r.is_match("QYYQZQQ"));
            },
            _ => assert!(false)
        }
    }
}
<commit_msg>Use Rc<RefCell<Context>><commit_after>use yaml_rust::{YamlLoader, Yaml, ScanError};
use std::collections::{HashMap, BTreeMap};
use onig::{Regex, Captures, Syntax, self};
use std::rc::Rc;
use std::cell::{RefCell};

pub type ScopeElement = String;
pub type CaptureMapping = HashMap<usize, ScopeElement>;
pub type ContextPtr = Rc<RefCell<Context>>;

#[derive(Debug)]
pub struct SyntaxDefinition {
    pub name: String,
    pub file_extensions: Vec<String>,
    pub scope: ScopeElement,
    pub first_line_match: Option<Regex>,
    pub hidden: bool,

    pub variables: HashMap<String, String>,
    pub contexts: HashMap<String, ContextPtr>,
}

#[derive(Debug)]
pub struct Context {
    pub meta_scope: Option<ScopeElement>,
    pub meta_content_scope: Option<ScopeElement>,
    pub meta_include_prototype: bool,

    pub patterns: Vec<Pattern>,
}

#[derive(Debug)]
pub enum Pattern {
    Match(MatchPattern),
    Include(ContextReference),
}

#[derive(Debug)]
pub struct MatchPattern {
    pub regex_str: String,
    // present unless contains backrefs and has to be dynamically compiled
    pub regex: Option<Regex>,
    pub scope: Vec<ScopeElement>,
    pub captures: Option<CaptureMapping>,
    pub operation: MatchOperation,
}

#[derive(Debug)]
pub enum ContextReference {
    Named(String),
    ByScope {
        name: String,
        sub_context: Option<String>,
    },
    File {
        name: String,
        sub_context: Option<String>,
    },
    Direct(ContextPtr),
}

#[derive(Debug)]
pub enum MatchOperation {
    Push(Vec<ContextReference>),
    Set(Vec<ContextReference>),
    Pop,
    None,
}

#[derive(Debug)]
pub enum ParseError {
    InvalidYaml(ScanError),
    EmptyFile,
    MissingMandatoryKey(&'static str),
    RegexCompileError(onig::Error),
    TypeMismatch,
}

fn get_key<'a, R, F: FnOnce(&'a Yaml) -> Option<R>>(map: &'a BTreeMap<Yaml, Yaml>,
                                                    key: &'static str,
                                                    f: F)
                                                    -> Result<R, ParseError> {
    map.get(&Yaml::String(key.to_owned()))
        .ok_or(ParseError::MissingMandatoryKey(key))
        .and_then(|x| f(x).ok_or(ParseError::TypeMismatch))
}

struct ParserState {
    variables: HashMap<String, String>,
    variable_regex: Regex,
    backref_regex: Regex,
    short_multibyte_regex: Regex,
}

impl SyntaxDefinition {
    pub fn load_from_str(s: &str) -> Result<SyntaxDefinition, ParseError> {
        let docs = match YamlLoader::load_from_str(s) {
            Ok(x) => x,
            Err(e) => return Err(ParseError::InvalidYaml(e)),
        };
        if docs.len() == 0 {
            return Err(ParseError::EmptyFile);
        }
        let doc = &docs[0];
        SyntaxDefinition::parse_top_level(doc)
    }

    fn parse_top_level(doc: &Yaml) -> Result<SyntaxDefinition, ParseError> {
        let h = try!(doc.as_hash().ok_or(ParseError::TypeMismatch));

        let mut variables = HashMap::new();
        if let Ok(map) = get_key(h, "variables", |x| x.as_hash()) {
            for (key, value) in map.iter() {
                if let (Some(key_str), Some(val_str)) = (key.as_str(), value.as_str()) {
                    variables.insert(key_str.to_owned(), val_str.to_owned());
                }
            }
        }
        let state = ParserState {
            variables: variables,
            variable_regex: Regex::new(r"\{\{([A-Za-z0-9_]+)\}\}").unwrap(),
            backref_regex: Regex::new(r"\\\d").unwrap(),
            short_multibyte_regex: Regex::new(r"\\x([a-fA-F][a-fA-F0-9])").unwrap(),
        };

        let contexts_hash = try!(get_key(h, "contexts", |x| x.as_hash()));
        let contexts = try!(SyntaxDefinition::parse_contexts(contexts_hash, &state));

        let defn = SyntaxDefinition {
            name: try!(get_key(h, "name", |x| x.as_str())).to_owned(),
            scope: try!(get_key(h, "scope", |x| x.as_str())).to_owned(),
            file_extensions: {
                get_key(h, "file_extensions", |x| x.as_vec())
                    .map(|v| v.iter().filter_map(|y| y.as_str()).map(|x| x.to_owned()).collect())
                    .unwrap_or_else(|_| Vec::new())
            },
            first_line_match: if let Ok(s) = get_key(h, "first_line_match", |x| x.as_str()) {
                Some(try!(Regex::new(s).map_err(|e| ParseError::RegexCompileError(e))))
            } else {
                None
            },
            hidden: get_key(h, "hidden", |x| x.as_bool()).unwrap_or(false),

            variables: state.variables.clone(),
            contexts: contexts,
        };
        Ok(defn)
    }

    fn parse_contexts(map: &BTreeMap<Yaml, Yaml>,
                      state: &ParserState)
                      -> Result<HashMap<String, ContextPtr>, ParseError> {
        let mut contexts = HashMap::new();
        for (key, value) in map.iter() {
            if let (Some(name), Some(val_vec)) = (key.as_str(), value.as_vec()) {
                let context = try!(SyntaxDefinition::parse_context(val_vec, state));
                contexts.insert(name.to_owned(), context);
            }
        }
        return Ok(contexts);
    }

    fn parse_context(vec: &Vec<Yaml>,
                     state: &ParserState)
                     -> Result<ContextPtr, ParseError> {
        let mut context = Context {
            meta_scope: None,
            meta_content_scope: None,
            meta_include_prototype: true,
            patterns: Vec::new(),
        };
        for y in vec.iter() {
            let map = try!(y.as_hash().ok_or(ParseError::TypeMismatch));

            if let Some(x) = get_key(map, "meta_scope", |x| x.as_str()).ok() {
                context.meta_scope = Some(x.to_owned());
            } else if let Some(x) = get_key(map, "meta_content_scope", |x| x.as_str()).ok() {
                context.meta_scope = Some(x.to_owned());
            } else if let Some(x) = get_key(map, "meta_include_prototype", |x| x.as_bool()).ok() {
                context.meta_include_prototype = x;
            } else if let Some(x) = get_key(map, "include", |x| Some(x)).ok() {
                let reference = try!(SyntaxDefinition::parse_reference(x, state));
                context.patterns.push(Pattern::Include(reference));
            } else {
                let pattern = try!(SyntaxDefinition::parse_match_pattern(map, state));
                context.patterns.push(Pattern::Match(pattern));
            }

        }
        return Ok(Rc::new(RefCell::new(context)));
    }

    fn parse_reference(y: &Yaml,
                       state: &ParserState)
                       -> Result<ContextReference, ParseError> {
        if let Some(s) = y.as_str() {
            let parts: Vec<&str> = s.split("#").collect();
            let sub_context = if parts.len() > 1 {
                Some(parts[1].to_owned())
            } else {
                None
            };
            if parts[0].starts_with("scope:") {
                Ok(ContextReference::ByScope {
                    name: parts[0][6..].to_owned(),
                    sub_context: sub_context,
                })
            } else if parts[0].ends_with(".sublime-syntax") {
                Ok(ContextReference::File {
                    name: s.to_owned(),
                    sub_context: sub_context,
                })
            } else {
                Ok(ContextReference::Named(parts[0].to_owned()))
            }
        } else if let Some(v) = y.as_vec() {
            let context = try!(SyntaxDefinition::parse_context(v, state));
            Ok(ContextReference::Direct(context))
        } else {
            Err(ParseError::TypeMismatch)
        }
    }

    fn parse_match_pattern(map: &BTreeMap<Yaml, Yaml>,
                           state: &ParserState)
                           -> Result<MatchPattern, ParseError> {
        let raw_regex = try!(get_key(map, "match", |x| x.as_str()));
        let regex_str_1 = state.variable_regex.replace_all(raw_regex, |caps: &Captures| {
            state.variables.get(caps.at(1).unwrap_or("")).map(|x| &**x).unwrap_or("").to_owned()
        });
        // bug triggered by CSS.sublime-syntax, dunno why this is necessary
        let regex_str = state.short_multibyte_regex.replace_all(&regex_str_1, |caps: &Captures| {
            format!("\\x{{000000{}}}", caps.at(1).unwrap_or(""))
        });
        // println!("{:?}", regex_str);

        // if it contains back references we can't resolve it until runtime
        let regex = if state.backref_regex.find(&regex_str).is_some() {
            None
        } else {
            Some(try!(Regex::with_options(&regex_str, onig::REGEX_OPTION_CAPTURE_GROUP, Syntax::default()).map_err(|e| ParseError::RegexCompileError(e))))
        };

        let scope = get_key(map, "scope", |x| x.as_str()).ok().map(|s| s.split_whitespace().map(|scope| scope.to_owned()).collect()).unwrap_or_else(|| vec![]);

        let captures = if let Ok(map) = get_key(map, "captures", |x| x.as_hash()) {
            let mut res_map = HashMap::new();
            for (key, value) in map.iter() {
                if let (Some(key_int), Some(val_str)) = (key.as_i64(), value.as_str()) {
                    res_map.insert(key_int as usize, val_str.to_owned());
                }
            }
            Some(res_map)
        } else {
            None
        };

        let operation = if let Ok(_) = get_key(map, "pop", |x| Some(x)) {
            MatchOperation::Pop
        } else if let Ok(y) = get_key(map, "push", |x| Some(x)) {
            MatchOperation::Push(try!(SyntaxDefinition::parse_pushargs(y, state)))
        } else if let Ok(y) = get_key(map, "set", |x| Some(x)) {
            MatchOperation::Set(try!(SyntaxDefinition::parse_pushargs(y, state)))
        } else {
            MatchOperation::None
        };

        let pattern = MatchPattern {
            regex_str: regex_str,
            regex: regex,
            scope: scope,
            captures: captures,
            operation: operation,
        };
        return Ok(pattern);
    }

    fn parse_pushargs(y: &Yaml,
                      state: &ParserState)
                      -> Result<Vec<ContextReference>, ParseError> {
        // check for a push of multiple items
        if y.as_vec().map(|v| !v.is_empty() && v[0].as_str().is_some()).unwrap_or(false) {
            // this works because Result implements FromIterator to handle the errors
            y.as_vec().unwrap().iter().map(|x| SyntaxDefinition::parse_reference(x, state)).collect()
        } else {
            Ok(vec![try!(SyntaxDefinition::parse_reference(y, state))])
        }
    }
}

#[cfg(test)]
mod tests {
    #[test]
    fn can_parse() {
        use syntax_definition::{SyntaxDefinition, Pattern, CaptureMapping};
        let defn: SyntaxDefinition =
            SyntaxDefinition::load_from_str("name: C\nscope: source.c\ncontexts: {}").unwrap();
        assert_eq!(defn.name, "C");
        assert_eq!(defn.scope, "source.c");
        let exts_empty: Vec<String> = Vec::new();
        assert_eq!(defn.file_extensions, exts_empty);
        assert_eq!(defn.hidden, false);
        assert!(defn.variables.is_empty());
        let defn2: SyntaxDefinition =
            SyntaxDefinition::load_from_str("
        name: C
        scope: source.c
        file_extensions: [c, h]
        hidden: true
        variables:
          ident: '[QY]+'
        contexts:
          main:
            - match: \\b(if|else|for|while|{{ident}})\\b
              scope: keyword.control.c keyword.looping.c
              captures:
                  1: meta.preprocessor.c++
                  2: keyword.control.include.c++
              push: [string, 'scope:source.c#main', 'CSS.sublime-syntax#rule-list-body']
            - match: '\"'
              push: string
          string:
            - meta_scope: string.quoted.double.c
            - match: \\\\.
              scope: constant.character.escape.c
            - match: '\"'
              pop: true
        ")
                .unwrap();
        assert_eq!(defn2.name, "C");
        assert_eq!(defn2.scope, "source.c");
        let exts: Vec<String> = vec![String::from("c"), String::from("h")];
        assert_eq!(defn2.file_extensions, exts);
        assert_eq!(defn2.hidden, true);
        assert_eq!(defn2.variables.get("ident").unwrap(), "[QY]+");

        let n: Option<String> = None;
        println!("{:?}", defn2);
        // assert!(false);
        assert_eq!(defn2.contexts["main"].borrow().meta_scope, n);
        assert_eq!(defn2.contexts["main"].borrow().meta_include_prototype, true);
        assert_eq!(defn2.contexts["string"].borrow().meta_scope,
                   Some(String::from("string.quoted.double.c")));
        let first_pattern: &Pattern = &defn2.contexts["main"].borrow().patterns[0];
        match first_pattern {
            &Pattern::Match(ref match_pat) => {
                let m : &CaptureMapping = match_pat.captures.as_ref().expect("test failed");
                let x : &String = &m[&1];
                assert_eq!(x, "meta.preprocessor.c++");
                assert_eq!(format!("{:?}",match_pat.operation),
                    "Push([Named(\"string\"), ByScope { name: \"source.c\", sub_context: Some(\"main\") }, \
                    File { name: \"CSS.sublime-syntax#rule-list-body\", sub_context: Some(\"rule-list-body\") }])");

                assert_eq!(match_pat.scope, vec![String::from("keyword.control.c"), String::from("keyword.looping.c")]);

                let r = match_pat.regex.as_ref().unwrap();
                assert!(r.is_match("else"));
                assert!(!r.is_match("elses"));
                assert!(!r.is_match("elose"));
                assert!(r.is_match("QYYQQQ"));
                assert!(!r.is_match("QYYQZQQ"));
            },
            _ => assert!(false)
        }
    }
}
<|endoftext|>


<commit_before><commit_msg>Tidy up the string handling in the reflection data reading code<commit_after><|endoftext|>


<commit_before><commit_msg>Get spirv-reflect to list the uniform blocks used by a shader<commit_after><|endoftext|>


<commit_before>// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

//! Compilation of native dependencies like LLVM.
//!
//! Native projects like LLVM unfortunately aren't suited just yet for
//! compilation in build scripts that Cargo has. This is because thie
//! compilation takes a *very* long time but also because we don't want to
//! compile LLVM 3 times as part of a normal bootstrap (we want it cached).
//!
//! LLVM and compiler-rt are essentially just wired up to everything else to
//! ensure that they're always in place if needed.

use std::fs::{self, File};
use std::io::{Read, Write};
use std::path::Path;
use std::process::Command;

use build_helper::output;
use cmake;
use gcc;

use Build;
use util;
use build_helper::up_to_date;

/// Compile LLVM for `target`.
pub fn llvm(build: &Build, target: &str) {
    // If we're using a custom LLVM bail out here, but we can only use a
    // custom LLVM for the build triple.
    if let Some(config) = build.config.target_config.get(target) {
        if let Some(ref s) = config.llvm_config {
            return check_llvm_version(build, s);
        }
    }

    let rebuild_trigger = build.src.join("src/rustllvm/llvm-rebuild-trigger");
    let mut rebuild_trigger_contents = String::new();
    t!(t!(File::open(&rebuild_trigger)).read_to_string(&mut rebuild_trigger_contents));

    let out_dir = build.llvm_out(target);
    let done_stamp = out_dir.join("llvm-finished-building");
    if done_stamp.exists() {
        let mut done_contents = String::new();
        t!(t!(File::open(&done_stamp)).read_to_string(&mut done_contents));

        // If LLVM was already built previously and contents of the rebuild-trigger file
        // didn't change from the previous build, then no action is required.
        if done_contents == rebuild_trigger_contents {
            return
        }
    }
    if build.config.llvm_clean_rebuild {
        drop(fs::remove_dir_all(&out_dir));
    }

    println!("Building LLVM for {}", target);
    let _time = util::timeit();
    t!(fs::create_dir_all(&out_dir));

    // http://llvm.org/docs/CMake.html
    let mut cfg = cmake::Config::new(build.src.join("src/llvm"));
    if build.config.ninja {
        cfg.generator("Ninja");
    }

    let profile = match (build.config.llvm_optimize, build.config.llvm_release_debuginfo) {
        (false, _) => "Debug",
        (true, false) => "Release",
        (true, true) => "RelWithDebInfo",
    };

    // NOTE: remember to also update `config.toml.example` when changing the defaults!
    let llvm_targets = match build.config.llvm_targets {
        Some(ref s) => s,
        None => "X86;ARM;AArch64;Mips;PowerPC;SystemZ;JSBackend;MSP430;Sparc;NVPTX",
    };

    let assertions = if build.config.llvm_assertions {"ON"} else {"OFF"};

    cfg.target(target)
       .host(&build.config.build)
       .out_dir(&out_dir)
       .profile(profile)
       .define("LLVM_ENABLE_ASSERTIONS", assertions)
       .define("LLVM_TARGETS_TO_BUILD", llvm_targets)
       .define("LLVM_INCLUDE_EXAMPLES", "OFF")
       .define("LLVM_INCLUDE_TESTS", "OFF")
       .define("LLVM_INCLUDE_DOCS", "OFF")
       .define("LLVM_ENABLE_ZLIB", "OFF")
       .define("WITH_POLLY", "OFF")
       .define("LLVM_ENABLE_TERMINFO", "OFF")
       .define("LLVM_ENABLE_LIBEDIT", "OFF")
       .define("LLVM_PARALLEL_COMPILE_JOBS", build.jobs().to_string())
       .define("LLVM_TARGET_ARCH", target.split('-').next().unwrap())
       .define("LLVM_DEFAULT_TARGET_TRIPLE", target);

    if target.contains("msvc") {
        cfg.define("LLVM_USE_CRT_DEBUG", "MT");
        cfg.define("LLVM_USE_CRT_RELEASE", "MT");
        cfg.define("LLVM_USE_CRT_RELWITHDEBINFO", "MT");
    }

    if target.starts_with("i686") {
        cfg.define("LLVM_BUILD_32_BITS", "ON");
    }

    if let Some(num_linkers) = build.config.llvm_link_jobs {
        if num_linkers > 0 {
            cfg.define("LLVM_PARALLEL_LINK_JOBS", num_linkers.to_string());
        }
    }

    // http://llvm.org/docs/HowToCrossCompileLLVM.html
    if target != build.config.build {
        // FIXME: if the llvm root for the build triple is overridden then we
        //        should use llvm-tblgen from there, also should verify that it
        //        actually exists most of the time in normal installs of LLVM.
        let host = build.llvm_out(&build.config.build).join("bin/llvm-tblgen");
        cfg.define("CMAKE_CROSSCOMPILING", "True")
           .define("LLVM_TABLEGEN", &host);
    }

    // MSVC handles compiler business itself
    if !target.contains("msvc") {
        if let Some(ref ccache) = build.config.ccache {
           cfg.define("CMAKE_C_COMPILER", ccache)
              .define("CMAKE_C_COMPILER_ARG1", build.cc(target))
              .define("CMAKE_CXX_COMPILER", ccache)
              .define("CMAKE_CXX_COMPILER_ARG1", build.cxx(target));
        } else {
           cfg.define("CMAKE_C_COMPILER", build.cc(target))
              .define("CMAKE_CXX_COMPILER", build.cxx(target));
        }
        cfg.build_arg("-j").build_arg(build.jobs().to_string());

        cfg.define("CMAKE_C_FLAGS", build.cflags(target).join(" "));
        cfg.define("CMAKE_CXX_FLAGS", build.cflags(target).join(" "));
    }

    // FIXME: we don't actually need to build all LLVM tools and all LLVM
    //        libraries here, e.g. we just want a few components and a few
    //        tools. Figure out how to filter them down and only build the right
    //        tools and libs on all platforms.
    cfg.build();

    t!(t!(File::create(&done_stamp)).write_all(rebuild_trigger_contents.as_bytes()));
}

fn check_llvm_version(build: &Build, llvm_config: &Path) {
    if !build.config.llvm_version_check {
        return
    }

    let mut cmd = Command::new(llvm_config);
    let version = output(cmd.arg("--version"));
    if version.starts_with("3.5") || version.starts_with("3.6") ||
       version.starts_with("3.7") {
        return
    }
    panic!("\n\nbad LLVM version: {}, need >=3.5\n\n", version)
}

/// Compiles the `rust_test_helpers.c` library which we used in various
/// `run-pass` test suites for ABI testing.
pub fn test_helpers(build: &Build, target: &str) {
    let dst = build.test_helpers_out(target);
    let src = build.src.join("src/rt/rust_test_helpers.c");
    if up_to_date(&src, &dst.join("librust_test_helpers.a")) {
        return
    }

    println!("Building test helpers");
    t!(fs::create_dir_all(&dst));
    let mut cfg = gcc::Config::new();

    // We may have found various cross-compilers a little differently due to our
    // extra configuration, so inform gcc of these compilers. Note, though, that
    // on MSVC we still need gcc's detection of env vars (ugh).
    if !target.contains("msvc") {
        if let Some(ar) = build.ar(target) {
            cfg.archiver(ar);
        }
        cfg.compiler(build.cc(target));
    }

    cfg.cargo_metadata(false)
       .out_dir(&dst)
       .target(target)
       .host(&build.config.build)
       .opt_level(0)
       .debug(false)
       .file(build.src.join("src/rt/rust_test_helpers.c"))
       .compile("librust_test_helpers.a");
}
const OPENSSL_VERS: &'static str = "1.0.2k";
const OPENSSL_SHA256: &'static str =
    "6b3977c61f2aedf0f96367dcfb5c6e578cf37e7b8d913b4ecb6643c3cb88d8c0";

pub fn openssl(build: &Build, target: &str) {
    let out = match build.openssl_dir(target) {
        Some(dir) => dir,
        None => return,
    };

    let stamp = out.join(".stamp");
    let mut contents = String::new();
    drop(File::open(&stamp).and_then(|mut f| f.read_to_string(&mut contents)));
    if contents == OPENSSL_VERS {
        return
    }
    t!(fs::create_dir_all(&out));

    let name = format!("openssl-{}.tar.gz", OPENSSL_VERS);
    let tarball = out.join(&name);
    if !tarball.exists() {
        let tmp = tarball.with_extension("tmp");
        build.run(Command::new("curl")
                        .arg("-o").arg(&tmp)
                        .arg(format!("https://www.openssl.org/source/{}", name)));
        let mut shasum = if target.contains("apple") {
            let mut cmd = Command::new("shasum");
            cmd.arg("-a").arg("256");
            cmd
        } else {
            Command::new("sha256sum")
        };
        let output = output(&mut shasum.arg(&tmp));
        let found = output.split_whitespace().next().unwrap();
        if found != OPENSSL_SHA256 {
            panic!("downloaded openssl sha256 different\n\
                    expected: {}\n\
                    found:    {}\n", OPENSSL_SHA256, found);
        }
        t!(fs::rename(&tmp, &tarball));
    }
    let obj = out.join(format!("openssl-{}", OPENSSL_VERS));
    let dst = build.openssl_install_dir(target).unwrap();
    drop(fs::remove_dir_all(&obj));
    drop(fs::remove_dir_all(&dst));
    build.run(Command::new("tar").arg("xf").arg(&tarball).current_dir(&out));

    let mut configure = Command::new(obj.join("Configure"));
    configure.arg(format!("--prefix={}", dst.display()));
    configure.arg("no-dso");
    configure.arg("no-ssl2");
    configure.arg("no-ssl3");

    let os = match target {
        "aarch64-unknown-linux-gnu" => "linux-aarch64",
        "arm-unknown-linux-gnueabi" => "linux-armv4",
        "arm-unknown-linux-gnueabihf" => "linux-armv4",
        "armv7-unknown-linux-gnueabihf" => "linux-armv4",
        "i686-apple-darwin" => "darwin-i386-cc",
        "i686-unknown-freebsd" => "BSD-x86-elf",
        "i686-unknown-linux-gnu" => "linux-elf",
        "i686-unknown-linux-musl" => "linux-elf",
        "mips-unknown-linux-gnu" => "linux-mips32",
        "mips64-unknown-linux-gnuabi64" => "linux64-mips64",
        "mips64el-unknown-linux-gnuabi64" => "linux64-mips64",
        "mipsel-unknown-linux-gnu" => "linux-mips32",
        "powerpc-unknown-linux-gnu" => "linux-ppc",
        "powerpc64-unknown-linux-gnu" => "linux-ppc64",
        "powerpc64le-unknown-linux-gnu" => "linux-ppc64le",
        "s390x-unknown-linux-gnu" => "linux64-s390x",
        "x86_64-apple-darwin" => "darwin64-x86_64-cc",
        "x86_64-unknown-freebsd" => "BSD-x86_64",
        "x86_64-unknown-linux-gnu" => "linux-x86_64",
        "x86_64-unknown-linux-musl" => "linux-x86_64",
        "x86_64-unknown-netbsd" => "BSD-x86_64",
        _ => panic!("don't know how to configure OpenSSL for {}", target),
    };
    configure.arg(os);
    configure.env("CC", build.cc(target));
    for flag in build.cflags(target) {
        configure.arg(flag);
    }
    configure.current_dir(&obj);
    println!("Configuring openssl for {}", target);
    build.run_quiet(&mut configure);
    println!("Building openssl for {}", target);
    build.run_quiet(Command::new("make").current_dir(&obj));
    println!("Installing openssl for {}", target);
    build.run_quiet(Command::new("make").arg("install").current_dir(&obj));

    let mut f = t!(File::create(&stamp));
    t!(f.write_all(OPENSSL_VERS.as_bytes()));
}
<commit_msg>Rollup merge of #40545 - alexcrichton:retry-download-openssl, r=nikomatsakis<commit_after>// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

//! Compilation of native dependencies like LLVM.
//!
//! Native projects like LLVM unfortunately aren't suited just yet for
//! compilation in build scripts that Cargo has. This is because thie
//! compilation takes a *very* long time but also because we don't want to
//! compile LLVM 3 times as part of a normal bootstrap (we want it cached).
//!
//! LLVM and compiler-rt are essentially just wired up to everything else to
//! ensure that they're always in place if needed.

use std::fs::{self, File};
use std::io::{Read, Write};
use std::path::Path;
use std::process::Command;

use build_helper::output;
use cmake;
use gcc;

use Build;
use util;
use build_helper::up_to_date;

/// Compile LLVM for `target`.
pub fn llvm(build: &Build, target: &str) {
    // If we're using a custom LLVM bail out here, but we can only use a
    // custom LLVM for the build triple.
    if let Some(config) = build.config.target_config.get(target) {
        if let Some(ref s) = config.llvm_config {
            return check_llvm_version(build, s);
        }
    }

    let rebuild_trigger = build.src.join("src/rustllvm/llvm-rebuild-trigger");
    let mut rebuild_trigger_contents = String::new();
    t!(t!(File::open(&rebuild_trigger)).read_to_string(&mut rebuild_trigger_contents));

    let out_dir = build.llvm_out(target);
    let done_stamp = out_dir.join("llvm-finished-building");
    if done_stamp.exists() {
        let mut done_contents = String::new();
        t!(t!(File::open(&done_stamp)).read_to_string(&mut done_contents));

        // If LLVM was already built previously and contents of the rebuild-trigger file
        // didn't change from the previous build, then no action is required.
        if done_contents == rebuild_trigger_contents {
            return
        }
    }
    if build.config.llvm_clean_rebuild {
        drop(fs::remove_dir_all(&out_dir));
    }

    println!("Building LLVM for {}", target);
    let _time = util::timeit();
    t!(fs::create_dir_all(&out_dir));

    // http://llvm.org/docs/CMake.html
    let mut cfg = cmake::Config::new(build.src.join("src/llvm"));
    if build.config.ninja {
        cfg.generator("Ninja");
    }

    let profile = match (build.config.llvm_optimize, build.config.llvm_release_debuginfo) {
        (false, _) => "Debug",
        (true, false) => "Release",
        (true, true) => "RelWithDebInfo",
    };

    // NOTE: remember to also update `config.toml.example` when changing the defaults!
    let llvm_targets = match build.config.llvm_targets {
        Some(ref s) => s,
        None => "X86;ARM;AArch64;Mips;PowerPC;SystemZ;JSBackend;MSP430;Sparc;NVPTX",
    };

    let assertions = if build.config.llvm_assertions {"ON"} else {"OFF"};

    cfg.target(target)
       .host(&build.config.build)
       .out_dir(&out_dir)
       .profile(profile)
       .define("LLVM_ENABLE_ASSERTIONS", assertions)
       .define("LLVM_TARGETS_TO_BUILD", llvm_targets)
       .define("LLVM_INCLUDE_EXAMPLES", "OFF")
       .define("LLVM_INCLUDE_TESTS", "OFF")
       .define("LLVM_INCLUDE_DOCS", "OFF")
       .define("LLVM_ENABLE_ZLIB", "OFF")
       .define("WITH_POLLY", "OFF")
       .define("LLVM_ENABLE_TERMINFO", "OFF")
       .define("LLVM_ENABLE_LIBEDIT", "OFF")
       .define("LLVM_PARALLEL_COMPILE_JOBS", build.jobs().to_string())
       .define("LLVM_TARGET_ARCH", target.split('-').next().unwrap())
       .define("LLVM_DEFAULT_TARGET_TRIPLE", target);

    if target.contains("msvc") {
        cfg.define("LLVM_USE_CRT_DEBUG", "MT");
        cfg.define("LLVM_USE_CRT_RELEASE", "MT");
        cfg.define("LLVM_USE_CRT_RELWITHDEBINFO", "MT");
    }

    if target.starts_with("i686") {
        cfg.define("LLVM_BUILD_32_BITS", "ON");
    }

    if let Some(num_linkers) = build.config.llvm_link_jobs {
        if num_linkers > 0 {
            cfg.define("LLVM_PARALLEL_LINK_JOBS", num_linkers.to_string());
        }
    }

    // http://llvm.org/docs/HowToCrossCompileLLVM.html
    if target != build.config.build {
        // FIXME: if the llvm root for the build triple is overridden then we
        //        should use llvm-tblgen from there, also should verify that it
        //        actually exists most of the time in normal installs of LLVM.
        let host = build.llvm_out(&build.config.build).join("bin/llvm-tblgen");
        cfg.define("CMAKE_CROSSCOMPILING", "True")
           .define("LLVM_TABLEGEN", &host);
    }

    // MSVC handles compiler business itself
    if !target.contains("msvc") {
        if let Some(ref ccache) = build.config.ccache {
           cfg.define("CMAKE_C_COMPILER", ccache)
              .define("CMAKE_C_COMPILER_ARG1", build.cc(target))
              .define("CMAKE_CXX_COMPILER", ccache)
              .define("CMAKE_CXX_COMPILER_ARG1", build.cxx(target));
        } else {
           cfg.define("CMAKE_C_COMPILER", build.cc(target))
              .define("CMAKE_CXX_COMPILER", build.cxx(target));
        }
        cfg.build_arg("-j").build_arg(build.jobs().to_string());

        cfg.define("CMAKE_C_FLAGS", build.cflags(target).join(" "));
        cfg.define("CMAKE_CXX_FLAGS", build.cflags(target).join(" "));
    }

    // FIXME: we don't actually need to build all LLVM tools and all LLVM
    //        libraries here, e.g. we just want a few components and a few
    //        tools. Figure out how to filter them down and only build the right
    //        tools and libs on all platforms.
    cfg.build();

    t!(t!(File::create(&done_stamp)).write_all(rebuild_trigger_contents.as_bytes()));
}

fn check_llvm_version(build: &Build, llvm_config: &Path) {
    if !build.config.llvm_version_check {
        return
    }

    let mut cmd = Command::new(llvm_config);
    let version = output(cmd.arg("--version"));
    if version.starts_with("3.5") || version.starts_with("3.6") ||
       version.starts_with("3.7") {
        return
    }
    panic!("\n\nbad LLVM version: {}, need >=3.5\n\n", version)
}

/// Compiles the `rust_test_helpers.c` library which we used in various
/// `run-pass` test suites for ABI testing.
pub fn test_helpers(build: &Build, target: &str) {
    let dst = build.test_helpers_out(target);
    let src = build.src.join("src/rt/rust_test_helpers.c");
    if up_to_date(&src, &dst.join("librust_test_helpers.a")) {
        return
    }

    println!("Building test helpers");
    t!(fs::create_dir_all(&dst));
    let mut cfg = gcc::Config::new();

    // We may have found various cross-compilers a little differently due to our
    // extra configuration, so inform gcc of these compilers. Note, though, that
    // on MSVC we still need gcc's detection of env vars (ugh).
    if !target.contains("msvc") {
        if let Some(ar) = build.ar(target) {
            cfg.archiver(ar);
        }
        cfg.compiler(build.cc(target));
    }

    cfg.cargo_metadata(false)
       .out_dir(&dst)
       .target(target)
       .host(&build.config.build)
       .opt_level(0)
       .debug(false)
       .file(build.src.join("src/rt/rust_test_helpers.c"))
       .compile("librust_test_helpers.a");
}
const OPENSSL_VERS: &'static str = "1.0.2k";
const OPENSSL_SHA256: &'static str =
    "6b3977c61f2aedf0f96367dcfb5c6e578cf37e7b8d913b4ecb6643c3cb88d8c0";

pub fn openssl(build: &Build, target: &str) {
    let out = match build.openssl_dir(target) {
        Some(dir) => dir,
        None => return,
    };

    let stamp = out.join(".stamp");
    let mut contents = String::new();
    drop(File::open(&stamp).and_then(|mut f| f.read_to_string(&mut contents)));
    if contents == OPENSSL_VERS {
        return
    }
    t!(fs::create_dir_all(&out));

    let name = format!("openssl-{}.tar.gz", OPENSSL_VERS);
    let tarball = out.join(&name);
    if !tarball.exists() {
        let tmp = tarball.with_extension("tmp");
        // originally from https://www.openssl.org/source/...
        let url = format!("https://s3.amazonaws.com/rust-lang-ci/rust-ci-mirror/{}",
                          name);
        let mut ok = false;
        for _ in 0..3 {
            let status = Command::new("curl")
                            .arg("-o").arg(&tmp)
                            .arg(&url)
                            .status()
                            .expect("failed to spawn curl");
            if status.success() {
                ok = true;
                break
            }
        }
        if !ok {
            panic!("failed to download openssl source")
        }
        let mut shasum = if target.contains("apple") {
            let mut cmd = Command::new("shasum");
            cmd.arg("-a").arg("256");
            cmd
        } else {
            Command::new("sha256sum")
        };
        let output = output(&mut shasum.arg(&tmp));
        let found = output.split_whitespace().next().unwrap();
        if found != OPENSSL_SHA256 {
            panic!("downloaded openssl sha256 different\n\
                    expected: {}\n\
                    found:    {}\n", OPENSSL_SHA256, found);
        }
        t!(fs::rename(&tmp, &tarball));
    }
    let obj = out.join(format!("openssl-{}", OPENSSL_VERS));
    let dst = build.openssl_install_dir(target).unwrap();
    drop(fs::remove_dir_all(&obj));
    drop(fs::remove_dir_all(&dst));
    build.run(Command::new("tar").arg("xf").arg(&tarball).current_dir(&out));

    let mut configure = Command::new(obj.join("Configure"));
    configure.arg(format!("--prefix={}", dst.display()));
    configure.arg("no-dso");
    configure.arg("no-ssl2");
    configure.arg("no-ssl3");

    let os = match target {
        "aarch64-unknown-linux-gnu" => "linux-aarch64",
        "arm-unknown-linux-gnueabi" => "linux-armv4",
        "arm-unknown-linux-gnueabihf" => "linux-armv4",
        "armv7-unknown-linux-gnueabihf" => "linux-armv4",
        "i686-apple-darwin" => "darwin-i386-cc",
        "i686-unknown-freebsd" => "BSD-x86-elf",
        "i686-unknown-linux-gnu" => "linux-elf",
        "i686-unknown-linux-musl" => "linux-elf",
        "mips-unknown-linux-gnu" => "linux-mips32",
        "mips64-unknown-linux-gnuabi64" => "linux64-mips64",
        "mips64el-unknown-linux-gnuabi64" => "linux64-mips64",
        "mipsel-unknown-linux-gnu" => "linux-mips32",
        "powerpc-unknown-linux-gnu" => "linux-ppc",
        "powerpc64-unknown-linux-gnu" => "linux-ppc64",
        "powerpc64le-unknown-linux-gnu" => "linux-ppc64le",
        "s390x-unknown-linux-gnu" => "linux64-s390x",
        "x86_64-apple-darwin" => "darwin64-x86_64-cc",
        "x86_64-unknown-freebsd" => "BSD-x86_64",
        "x86_64-unknown-linux-gnu" => "linux-x86_64",
        "x86_64-unknown-linux-musl" => "linux-x86_64",
        "x86_64-unknown-netbsd" => "BSD-x86_64",
        _ => panic!("don't know how to configure OpenSSL for {}", target),
    };
    configure.arg(os);
    configure.env("CC", build.cc(target));
    for flag in build.cflags(target) {
        configure.arg(flag);
    }
    configure.current_dir(&obj);
    println!("Configuring openssl for {}", target);
    build.run_quiet(&mut configure);
    println!("Building openssl for {}", target);
    build.run_quiet(Command::new("make").current_dir(&obj));
    println!("Installing openssl for {}", target);
    build.run_quiet(Command::new("make").arg("install").current_dir(&obj));

    let mut f = t!(File::create(&stamp));
    t!(f.write_all(OPENSSL_VERS.as_bytes()));
}
<|endoftext|>


<commit_before>// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

//! Sanity checking performed by rustbuild before actually executing anything.
//!
//! This module contains the implementation of ensuring that the build
//! environment looks reasonable before progressing. This will verify that
//! various programs like git and python exist, along with ensuring that all C
//! compilers for cross-compiling are found.
//!
//! In theory if we get past this phase it's a bug if a build fails, but in
//! practice that's likely not true!

use std::collections::HashMap;
use std::env;
use std::ffi::{OsString, OsStr};
use std::fs;
use std::process::Command;
use std::path::PathBuf;

use build_helper::output;

use Build;

struct Finder {
    cache: HashMap<OsString, Option<PathBuf>>,
    path: OsString,
}

impl Finder {
    fn new() -> Self {
        Self {
            cache: HashMap::new(),
            path: env::var_os("PATH").unwrap_or_default()
        }
    }

    fn maybe_have<S: AsRef<OsStr>>(&mut self, cmd: S) -> Option<PathBuf> {
        let cmd: OsString = cmd.as_ref().into();
        let path = self.path.clone();
        self.cache.entry(cmd.clone()).or_insert_with(|| {
            for path in env::split_paths(&path) {
                let target = path.join(&cmd);
                let mut cmd_alt = cmd.clone();
                cmd_alt.push(".exe");
                if target.is_file() || // some/path/git
                target.with_extension("exe").exists() || // some/path/git.exe
                target.join(&cmd_alt).exists() { // some/path/git/git.exe
                    return Some(target);
                }
            }
            None
        }).clone()
    }

    fn must_have<S: AsRef<OsStr>>(&mut self, cmd: S) -> PathBuf {
        self.maybe_have(&cmd).unwrap_or_else(|| {
            panic!("\n\ncouldn't find required command: {:?}\n\n", cmd.as_ref());
        })
    }
}

pub fn check(build: &mut Build) {
    let path = env::var_os("PATH").unwrap_or_default();
    // On Windows, quotes are invalid characters for filename paths, and if
    // one is present as part of the PATH then that can lead to the system
    // being unable to identify the files properly. See
    // https://github.com/rust-lang/rust/issues/34959 for more details.
    if cfg!(windows) && path.to_string_lossy().contains("\"") {
        panic!("PATH contains invalid character '\"'");
    }

    let mut cmd_finder = Finder::new();
    // If we've got a git directory we're gona need git to update
    // submodules and learn about various other aspects.
    if build.rust_info.is_git() {
        cmd_finder.must_have("git");
    }

    // We need cmake, but only if we're actually building LLVM or sanitizers.
    let building_llvm = build.hosts.iter()
        .filter_map(|host| build.config.target_config.get(host))
        .any(|config| config.llvm_config.is_none());
    if building_llvm || build.config.sanitizers {
        cmd_finder.must_have("cmake");
    }

    // Ninja is currently only used for LLVM itself.
    // Some Linux distros rename `ninja` to `ninja-build`.
    // CMake can work with either binary name.
    if building_llvm && build.config.ninja && cmd_finder.maybe_have("ninja-build").is_none() {
        cmd_finder.must_have("ninja");
    }

    build.config.python = build.config.python.take().map(|p| cmd_finder.must_have(p))
        .or_else(|| env::var_os("BOOTSTRAP_PYTHON").map(PathBuf::from)) // set by bootstrap.py
        .or_else(|| cmd_finder.maybe_have("python2.7"))
        .or_else(|| cmd_finder.maybe_have("python2"))
        .or_else(|| Some(cmd_finder.must_have("python")));

    build.config.nodejs = build.config.nodejs.take().map(|p| cmd_finder.must_have(p))
        .or_else(|| cmd_finder.maybe_have("node"))
        .or_else(|| cmd_finder.maybe_have("nodejs"));

    build.config.gdb = build.config.gdb.take().map(|p| cmd_finder.must_have(p))
        .or_else(|| cmd_finder.maybe_have("gdb"));

    // We're gonna build some custom C code here and there, host triples
    // also build some C++ shims for LLVM so we need a C++ compiler.
    for target in &build.targets {
        // On emscripten we don't actually need the C compiler to just
        // build the target artifacts, only for testing. For the sake
        // of easier bot configuration, just skip detection.
        if target.contains("emscripten") {
            continue;
        }

        cmd_finder.must_have(build.cc(*target));
        if let Some(ar) = build.ar(*target) {
            cmd_finder.must_have(ar);
        }
    }

    for host in &build.hosts {
        cmd_finder.must_have(build.cxx(*host).unwrap());

        // The msvc hosts don't use jemalloc, turn it off globally to
        // avoid packaging the dummy liballoc_jemalloc on that platform.
        if host.contains("msvc") {
            build.config.use_jemalloc = false;
        }
    }

    // Externally configured LLVM requires FileCheck to exist
    let filecheck = build.llvm_filecheck(build.build);
    if !filecheck.starts_with(&build.out) && !filecheck.exists() && build.config.codegen_tests {
        panic!("FileCheck executable {:?} does not exist", filecheck);
    }

    for target in &build.targets {
        // Can't compile for iOS unless we're on macOS
        if target.contains("apple-ios") &&
           !build.build.contains("apple-darwin") {
            panic!("the iOS target is only supported on macOS");
        }

        // Make sure musl-root is valid
        if target.contains("musl") && !target.contains("mips") {
            // If this is a native target (host is also musl) and no musl-root is given,
            // fall back to the system toolchain in /usr before giving up
            if build.musl_root(*target).is_none() && build.config.build == *target {
                let target = build.config.target_config.entry(target.clone())
                                 .or_insert(Default::default());
                target.musl_root = Some("/usr".into());
            }
            match build.musl_root(*target) {
                Some(root) => {
                    if fs::metadata(root.join("lib/libc.a")).is_err() {
                        panic!("couldn't find libc.a in musl dir: {}",
                               root.join("lib").display());
                    }
                    if fs::metadata(root.join("lib/libunwind.a")).is_err() {
                        panic!("couldn't find libunwind.a in musl dir: {}",
                               root.join("lib").display());
                    }
                }
                None => {
                    panic!("when targeting MUSL either the rust.musl-root \
                            option or the target.$TARGET.musl-root option must \
                            be specified in config.toml")
                }
            }
        }

        if target.contains("msvc") {
            // There are three builds of cmake on windows: MSVC, MinGW, and
            // Cygwin. The Cygwin build does not have generators for Visual
            // Studio, so detect that here and error.
            let out = output(Command::new("cmake").arg("--help"));
            if !out.contains("Visual Studio") {
                panic!("
cmake does not support Visual Studio generators.

This is likely due to it being an msys/cygwin build of cmake,
rather than the required windows version, built using MinGW
or Visual Studio.

If you are building under msys2 try installing the mingw-w64-x86_64-cmake
package instead of cmake:

$ pacman -R cmake && pacman -S mingw-w64-x86_64-cmake
");
            }
        }
    }

    let run = |cmd: &mut Command| {
        cmd.output().map(|output| {
            String::from_utf8_lossy(&output.stdout)
                   .lines().next().unwrap()
                   .to_string()
        })
    };
    build.lldb_version = run(Command::new("lldb").arg("--version")).ok();
    if build.lldb_version.is_some() {
        build.lldb_python_dir = run(Command::new("lldb").arg("-P")).ok();
    }

    if let Some(ref s) = build.config.ccache {
        cmd_finder.must_have(s);
    }
}
<commit_msg>rustbuild: Automatically enable Ninja on MSVC<commit_after>// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

//! Sanity checking performed by rustbuild before actually executing anything.
//!
//! This module contains the implementation of ensuring that the build
//! environment looks reasonable before progressing. This will verify that
//! various programs like git and python exist, along with ensuring that all C
//! compilers for cross-compiling are found.
//!
//! In theory if we get past this phase it's a bug if a build fails, but in
//! practice that's likely not true!

use std::collections::HashMap;
use std::env;
use std::ffi::{OsString, OsStr};
use std::fs;
use std::process::Command;
use std::path::PathBuf;

use build_helper::output;

use Build;

struct Finder {
    cache: HashMap<OsString, Option<PathBuf>>,
    path: OsString,
}

impl Finder {
    fn new() -> Self {
        Self {
            cache: HashMap::new(),
            path: env::var_os("PATH").unwrap_or_default()
        }
    }

    fn maybe_have<S: AsRef<OsStr>>(&mut self, cmd: S) -> Option<PathBuf> {
        let cmd: OsString = cmd.as_ref().into();
        let path = self.path.clone();
        self.cache.entry(cmd.clone()).or_insert_with(|| {
            for path in env::split_paths(&path) {
                let target = path.join(&cmd);
                let mut cmd_alt = cmd.clone();
                cmd_alt.push(".exe");
                if target.is_file() || // some/path/git
                target.with_extension("exe").exists() || // some/path/git.exe
                target.join(&cmd_alt).exists() { // some/path/git/git.exe
                    return Some(target);
                }
            }
            None
        }).clone()
    }

    fn must_have<S: AsRef<OsStr>>(&mut self, cmd: S) -> PathBuf {
        self.maybe_have(&cmd).unwrap_or_else(|| {
            panic!("\n\ncouldn't find required command: {:?}\n\n", cmd.as_ref());
        })
    }
}

pub fn check(build: &mut Build) {
    let path = env::var_os("PATH").unwrap_or_default();
    // On Windows, quotes are invalid characters for filename paths, and if
    // one is present as part of the PATH then that can lead to the system
    // being unable to identify the files properly. See
    // https://github.com/rust-lang/rust/issues/34959 for more details.
    if cfg!(windows) && path.to_string_lossy().contains("\"") {
        panic!("PATH contains invalid character '\"'");
    }

    let mut cmd_finder = Finder::new();
    // If we've got a git directory we're gona need git to update
    // submodules and learn about various other aspects.
    if build.rust_info.is_git() {
        cmd_finder.must_have("git");
    }

    // We need cmake, but only if we're actually building LLVM or sanitizers.
    let building_llvm = build.hosts.iter()
        .filter_map(|host| build.config.target_config.get(host))
        .any(|config| config.llvm_config.is_none());
    if building_llvm || build.config.sanitizers {
        cmd_finder.must_have("cmake");
    }

    // Ninja is currently only used for LLVM itself.
    if building_llvm {
        if build.config.ninja {
            // Some Linux distros rename `ninja` to `ninja-build`.
            // CMake can work with either binary name.
            if cmd_finder.maybe_have("ninja-build").is_none() {
                cmd_finder.must_have("ninja");
            }
        }

        // If ninja isn't enabled but we're building for MSVC then we try
        // doubly hard to enable it. It was realized in #43767 that the msbuild
        // CMake generator for MSVC doesn't respect configuration options like
        // disabling LLVM assertions, which can often be quite important!
        //
        // In these cases we automatically enable Ninja if we find it in the
        // environment.
        if !build.config.ninja && build.config.build.contains("msvc") {
            if cmd_finder.maybe_have("ninja").is_some() {
                build.config.ninja = true;
            }
        }
    }

    build.config.python = build.config.python.take().map(|p| cmd_finder.must_have(p))
        .or_else(|| env::var_os("BOOTSTRAP_PYTHON").map(PathBuf::from)) // set by bootstrap.py
        .or_else(|| cmd_finder.maybe_have("python2.7"))
        .or_else(|| cmd_finder.maybe_have("python2"))
        .or_else(|| Some(cmd_finder.must_have("python")));

    build.config.nodejs = build.config.nodejs.take().map(|p| cmd_finder.must_have(p))
        .or_else(|| cmd_finder.maybe_have("node"))
        .or_else(|| cmd_finder.maybe_have("nodejs"));

    build.config.gdb = build.config.gdb.take().map(|p| cmd_finder.must_have(p))
        .or_else(|| cmd_finder.maybe_have("gdb"));

    // We're gonna build some custom C code here and there, host triples
    // also build some C++ shims for LLVM so we need a C++ compiler.
    for target in &build.targets {
        // On emscripten we don't actually need the C compiler to just
        // build the target artifacts, only for testing. For the sake
        // of easier bot configuration, just skip detection.
        if target.contains("emscripten") {
            continue;
        }

        cmd_finder.must_have(build.cc(*target));
        if let Some(ar) = build.ar(*target) {
            cmd_finder.must_have(ar);
        }
    }

    for host in &build.hosts {
        cmd_finder.must_have(build.cxx(*host).unwrap());

        // The msvc hosts don't use jemalloc, turn it off globally to
        // avoid packaging the dummy liballoc_jemalloc on that platform.
        if host.contains("msvc") {
            build.config.use_jemalloc = false;
        }
    }

    // Externally configured LLVM requires FileCheck to exist
    let filecheck = build.llvm_filecheck(build.build);
    if !filecheck.starts_with(&build.out) && !filecheck.exists() && build.config.codegen_tests {
        panic!("FileCheck executable {:?} does not exist", filecheck);
    }

    for target in &build.targets {
        // Can't compile for iOS unless we're on macOS
        if target.contains("apple-ios") &&
           !build.build.contains("apple-darwin") {
            panic!("the iOS target is only supported on macOS");
        }

        // Make sure musl-root is valid
        if target.contains("musl") && !target.contains("mips") {
            // If this is a native target (host is also musl) and no musl-root is given,
            // fall back to the system toolchain in /usr before giving up
            if build.musl_root(*target).is_none() && build.config.build == *target {
                let target = build.config.target_config.entry(target.clone())
                                 .or_insert(Default::default());
                target.musl_root = Some("/usr".into());
            }
            match build.musl_root(*target) {
                Some(root) => {
                    if fs::metadata(root.join("lib/libc.a")).is_err() {
                        panic!("couldn't find libc.a in musl dir: {}",
                               root.join("lib").display());
                    }
                    if fs::metadata(root.join("lib/libunwind.a")).is_err() {
                        panic!("couldn't find libunwind.a in musl dir: {}",
                               root.join("lib").display());
                    }
                }
                None => {
                    panic!("when targeting MUSL either the rust.musl-root \
                            option or the target.$TARGET.musl-root option must \
                            be specified in config.toml")
                }
            }
        }

        if target.contains("msvc") {
            // There are three builds of cmake on windows: MSVC, MinGW, and
            // Cygwin. The Cygwin build does not have generators for Visual
            // Studio, so detect that here and error.
            let out = output(Command::new("cmake").arg("--help"));
            if !out.contains("Visual Studio") {
                panic!("
cmake does not support Visual Studio generators.

This is likely due to it being an msys/cygwin build of cmake,
rather than the required windows version, built using MinGW
or Visual Studio.

If you are building under msys2 try installing the mingw-w64-x86_64-cmake
package instead of cmake:

$ pacman -R cmake && pacman -S mingw-w64-x86_64-cmake
");
            }
        }
    }

    let run = |cmd: &mut Command| {
        cmd.output().map(|output| {
            String::from_utf8_lossy(&output.stdout)
                   .lines().next().unwrap()
                   .to_string()
        })
    };
    build.lldb_version = run(Command::new("lldb").arg("--version")).ok();
    if build.lldb_version.is_some() {
        build.lldb_python_dir = run(Command::new("lldb").arg("-P")).ok();
    }

    if let Some(ref s) = build.config.ccache {
        cmd_finder.must_have(s);
    }
}
<|endoftext|>


<commit_before><commit_msg>core: Add missing from_str.rs<commit_after>trait FromStr {
    static fn from_str(s: &str) -> Option<self>;
}
<|endoftext|>