<commit_before>//! The Gilbert–Johnson–Keerthi distance algorithm.

use num::Bounded;

use alga::general::Real;
use na::{self, Unit};

use query::algorithms::{CSOPoint, VoronoiSimplex};
use shape::{ConstantOrigin, SupportMap};
// use query::Proximity;
use math::{Isometry, Point, Vector, DIM};
use query::{ray_internal, Ray};

/// Results of the GJK algorithm.
#[derive(Clone, Debug, PartialEq)]
pub enum GJKResult<N: Real> {
    /// Result of the GJK algorithm when the origin is inside of the polytope.
    Intersection,
    /// Result of the GJK algorithm when a projection of the origin on the polytope is found.
    ClosestPoints(Point<N>, Point<N>, Unit<Vector<N>>),
    /// Result of the GJK algorithm when the origin is too close to the polytope but not inside of it.
    Proximity(Unit<Vector<N>>),
    /// Result of the GJK algorithm when the origin is too far away from the polytope.
    NoIntersection(Unit<Vector<N>>),
}

/// The absolute tolerence used by the GJK algorithm.
pub fn eps_tol<N: Real>() -> N {
    let _eps = N::default_epsilon();
    _eps * na::convert(100.0f64)
}

/// Projects the origin on the boundary of the given shape.
///
/// The origin is assumed to be outside of the shape. If it is inside,
/// use the EPA algorithm instead.
/// Return `None` if the origin is not inside of the shape or if
/// the EPA algorithm failed to compute the projection.
pub fn project_origin<N, G: ?Sized>(
    m: &Isometry<N>,
    g: &G,
    simplex: &mut VoronoiSimplex<N>,
) -> Option<Point<N>>
where
    N: Real,
    G: SupportMap<N>,
{
    match closest_points(
        m,
        g,
        &Isometry::identity(),
        &ConstantOrigin,
        N::max_value(),
        true,
        simplex,
    ) {
        GJKResult::Intersection => None,
        GJKResult::ClosestPoints(p, _, _) => Some(p),
        _ => unreachable!(),
    }
}

/*
 * Separating Axis GJK
 */
/// Projects the origin on a shape using the Separating Axis GJK algorithm.
/// The algorithm will stop as soon as the polytope can be proven to be at least `max_dist` away
/// from the origin.
///
/// # Arguments:
/// * simplex - the simplex to be used by the GJK algorithm. It must be already initialized
///             with at least one point on the shape boundary.
/// * exact_dist - if `false`, the gjk will stop as soon as it can prove that the origin is at
/// a distance smaller than `max_dist` but not inside of `shape`. In that case, it returns a
/// `GJKResult::Proximity(sep_axis)` where `sep_axis` is a separating axis. If `false` the gjk will
/// compute the exact distance and return `GJKResult::Projection(point)` if the origin is closer
/// than `max_dist` but not inside `shape`.
pub fn closest_points<N, G1: ?Sized, G2: ?Sized>(
    m1: &Isometry<N>,
    g1: &G1,
    m2: &Isometry<N>,
    g2: &G2,
    max_dist: N,
    exact_dist: bool,
    simplex: &mut VoronoiSimplex<N>,
) -> GJKResult<N>
where
    N: Real,
    G1: SupportMap<N>,
    G2: SupportMap<N>,
{
    let _eps = N::default_epsilon();
    let _eps_tol: N = eps_tol();
    let _eps_rel: N = _eps_tol.sqrt();

    fn result<N: Real>(simplex: &VoronoiSimplex<N>, prev: bool) -> (Point<N>, Point<N>) {
        let mut res = (Point::origin(), Point::origin());
        if prev {
            for i in 0..simplex.prev_dimension() + 1 {
                let coord = simplex.prev_proj_coord(i);
                let point = simplex.prev_point(i);
                res.0 += point.orig1.coords * coord;
                res.1 += point.orig2.coords * coord;
            }

            res
        } else {
            for i in 0..simplex.dimension() + 1 {
                let coord = simplex.proj_coord(i);
                let point = simplex.point(i);
                res.0 += point.orig1.coords * coord;
                res.1 += point.orig2.coords * coord;
            }

            res
        }
    }

    // FIXME: reset the simplex if it is empty?
    let mut proj = simplex.project_origin_and_reduce();
    let mut old_dir = -Unit::new_normalize(proj.coords);
    let mut max_bound = N::max_value();
    let mut dir;
    let mut niter = 0;

    loop {
        let old_max_bound = max_bound;

        if let Some((new_dir, dist)) = Unit::try_new_and_get(-proj.coords, _eps_tol) {
            dir = new_dir;
            max_bound = dist;
        } else {
            // The origin is on the simplex.
            return GJKResult::Intersection;
        }

        if max_bound >= old_max_bound {
            if exact_dist {
                let (p1, p2) = result(simplex, true);
                return GJKResult::ClosestPoints(p1, p2, old_dir); // upper bounds inconsistencies
            } else {
                return GJKResult::Proximity(old_dir);
            }
        }

        let cso_point = CSOPoint::from_shapes(m1, g1, m2, g2, &dir);
        let min_bound = -na::dot(dir.as_ref(), &cso_point.point.coords);

        assert!(min_bound == min_bound);

        if min_bound > max_dist {
            return GJKResult::NoIntersection(dir);
        } else if !exact_dist && min_bound > na::zero() {
            return GJKResult::Proximity(old_dir);
        } else if max_bound - min_bound <= _eps_rel * max_bound {
            if exact_dist {
                let (p1, p2) = result(simplex, false);
                return GJKResult::ClosestPoints(p1, p2, dir); // the distance found has a good enough precision
            } else {
                return GJKResult::Proximity(dir);
            }
        }

        if !simplex.add_point(cso_point) {
            if exact_dist {
                let (p1, p2) = result(simplex, false);
                return GJKResult::ClosestPoints(p1, p2, dir);
            } else {
                return GJKResult::Proximity(dir);
            }
        }

        old_dir = dir;
        proj = simplex.project_origin_and_reduce();

        if simplex.dimension() == DIM {
            if min_bound >= _eps_tol {
                if exact_dist {
                    let (p1, p2) = result(simplex, true);
                    return GJKResult::ClosestPoints(p1, p2, old_dir);
                } else {
                    // NOTE: previous implementation used old_proj here.
                    return GJKResult::Proximity(old_dir);
                }
            } else {
                return GJKResult::Intersection; // Point inside of the cso.
            }
        }
        niter += 1;
        if niter == 10000 {
            println!("Error: GJK did not converge.");
            return GJKResult::NoIntersection(Vector::x_axis());
        }
    }
}

/// Casts a ray on a support map using the GJK algorithm.
pub fn cast_ray<N, G: ?Sized>(
    m: &Isometry<N>,
    shape: &G,
    simplex: &mut VoronoiSimplex<N>,
    ray: &Ray<N>,
) -> Option<(N, Vector<N>)>
where
    N: Real,
    G: SupportMap<N>,
{
    let m2 = Isometry::identity();
    let g2 = ConstantOrigin;
    minkowski_ray_cast(m, shape, &m2, &g2, ray, simplex)
}

/// Compute the distance that can travel `g1` along the direction `dir` so that
/// `g1` and `g2` just touch.
pub fn directional_distance<N, G1: ?Sized, G2: ?Sized>(
    m1: &Isometry<N>,
    g1: &G1,
    m2: &Isometry<N>,
    g2: &G2,
    dir: &Vector<N>,
    simplex: &mut VoronoiSimplex<N>,
) -> Option<N>
where
    N: Real,
    G1: SupportMap<N>,
    G2: SupportMap<N>,
{
    let ray = Ray::new(Point::origin(), *dir);
    minkowski_ray_cast(m1, g1, m2, g2, &ray, simplex).map(|res| res.0)
}

// Ray-cast on the Minkowski Difference `m1 * g1 - m2 * g2`.
fn minkowski_ray_cast<N, G1: ?Sized, G2: ?Sized>(
    m1: &Isometry<N>,
    g1: &G1,
    m2: &Isometry<N>,
    g2: &G2,
    ray: &Ray<N>,
    simplex: &mut VoronoiSimplex<N>,
) -> Option<(N, Vector<N>)>
where
    N: Real,
    G1: SupportMap<N>,
    G2: SupportMap<N>,
{
    let mut ltoi: N = na::zero();
    let _eps_tol = eps_tol();

    if relative_eq!(ray.dir.norm_squared(), N::zero()) {
        return None;
    }

    let mut curr_ray = *ray;
    let mut dir = -ray.dir;
    let mut old_max_bound: N = Bounded::max_value();
    let mut ldir = dir;
    let mut simplex_init = false;

    loop {
        if dir.normalize_mut().is_zero() {
            return Some((ltoi, ldir));
        }

        let support_point = CSOPoint::from_shapes(m1, g1, m2, g2, &dir);

        // Clip the ray on the support plane (None <=> t < 0)
        // The configurations are:
        //   dir.dot(ray.dir)  |   t   |               Action
        // −−−−−−−−−−−−−−−−−−−−+−−−−−−−+−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
        //          < 0        |  < 0  | Continue.
        //          < 0        |  > 0  | New lower bound, move the origin.
        //          > 0        |  < 0  | Miss. No intersection.
        //          > 0        |  > 0  | New higher bound.
        match ray_internal::plane_toi_with_ray(&support_point.point, &dir, &curr_ray) {
            Some(t) => {
                if na::dot(&dir, &ray.dir) < na::zero() && t > _eps_tol {
                    // new lower bound
                    ldir = dir;
                    ltoi = ltoi + t;
                    curr_ray.origin = ray.origin + ray.dir * ltoi;
                    dir = curr_ray.origin - support_point.point;
                    // FIXME: could we simply translate the simplex by old_origin - new_origin ?
                    simplex.reset(support_point.translate1(&-curr_ray.origin.coords));
                    old_max_bound = Bounded::max_value();
                    continue;
                }
            }
            None => {
                if na::dot(&dir, &ray.dir) > -N::zero() {
                    // misss
                    return None;
                }
            }
        }

        if !simplex_init {
            simplex.reset(support_point.translate1(&-curr_ray.origin.coords));
            simplex_init = true;
        } else if !simplex.add_point(support_point.translate1(&-curr_ray.origin.coords)) {
            return None;
        }

        let proj = simplex.project_origin_and_reduce().coords;
        let max_bound = na::norm_squared(&proj);

        if simplex.dimension() == DIM {
            return Some((ltoi, ldir));
        } else if max_bound <= _eps_tol * simplex.max_sq_len() {
            // Return ldir: the last projection plane is tangeant to the intersected surface.
            return Some((ltoi, ldir));
        } else if max_bound >= old_max_bound {
            // Use dir instead of proj since this situations means that the new projection is less
            // accurate than the last one (which is stored on dir).
            return Some((ltoi, dir));
        }

        old_max_bound = max_bound;
        dir = -proj;
    }
}
<commit_msg>GJK ray-cast: fix convergence criteria.<commit_after>//! The Gilbert–Johnson–Keerthi distance algorithm.

use num::Bounded;

use alga::general::Real;
use na::{self, Unit};

use query::algorithms::{CSOPoint, VoronoiSimplex};
use shape::{ConstantOrigin, SupportMap};
// use query::Proximity;
use math::{Isometry, Point, Vector, DIM};
use query::{ray_internal, Ray};

/// Results of the GJK algorithm.
#[derive(Clone, Debug, PartialEq)]
pub enum GJKResult<N: Real> {
    /// Result of the GJK algorithm when the origin is inside of the polytope.
    Intersection,
    /// Result of the GJK algorithm when a projection of the origin on the polytope is found.
    ClosestPoints(Point<N>, Point<N>, Unit<Vector<N>>),
    /// Result of the GJK algorithm when the origin is too close to the polytope but not inside of it.
    Proximity(Unit<Vector<N>>),
    /// Result of the GJK algorithm when the origin is too far away from the polytope.
    NoIntersection(Unit<Vector<N>>),
}

/// The absolute tolerence used by the GJK algorithm.
pub fn eps_tol<N: Real>() -> N {
    let _eps = N::default_epsilon();
    _eps * na::convert(100.0f64)
}

/// Projects the origin on the boundary of the given shape.
///
/// The origin is assumed to be outside of the shape. If it is inside,
/// use the EPA algorithm instead.
/// Return `None` if the origin is not inside of the shape or if
/// the EPA algorithm failed to compute the projection.
pub fn project_origin<N, G: ?Sized>(
    m: &Isometry<N>,
    g: &G,
    simplex: &mut VoronoiSimplex<N>,
) -> Option<Point<N>>
where
    N: Real,
    G: SupportMap<N>,
{
    match closest_points(
        m,
        g,
        &Isometry::identity(),
        &ConstantOrigin,
        N::max_value(),
        true,
        simplex,
    ) {
        GJKResult::Intersection => None,
        GJKResult::ClosestPoints(p, _, _) => Some(p),
        _ => unreachable!(),
    }
}

/*
 * Separating Axis GJK
 */
/// Projects the origin on a shape using the Separating Axis GJK algorithm.
/// The algorithm will stop as soon as the polytope can be proven to be at least `max_dist` away
/// from the origin.
///
/// # Arguments:
/// * simplex - the simplex to be used by the GJK algorithm. It must be already initialized
///             with at least one point on the shape boundary.
/// * exact_dist - if `false`, the gjk will stop as soon as it can prove that the origin is at
/// a distance smaller than `max_dist` but not inside of `shape`. In that case, it returns a
/// `GJKResult::Proximity(sep_axis)` where `sep_axis` is a separating axis. If `false` the gjk will
/// compute the exact distance and return `GJKResult::Projection(point)` if the origin is closer
/// than `max_dist` but not inside `shape`.
pub fn closest_points<N, G1: ?Sized, G2: ?Sized>(
    m1: &Isometry<N>,
    g1: &G1,
    m2: &Isometry<N>,
    g2: &G2,
    max_dist: N,
    exact_dist: bool,
    simplex: &mut VoronoiSimplex<N>,
) -> GJKResult<N>
where
    N: Real,
    G1: SupportMap<N>,
    G2: SupportMap<N>,
{
    let _eps = N::default_epsilon();
    let _eps_tol: N = eps_tol();
    let _eps_rel: N = _eps_tol.sqrt();

    fn result<N: Real>(simplex: &VoronoiSimplex<N>, prev: bool) -> (Point<N>, Point<N>) {
        let mut res = (Point::origin(), Point::origin());
        if prev {
            for i in 0..simplex.prev_dimension() + 1 {
                let coord = simplex.prev_proj_coord(i);
                let point = simplex.prev_point(i);
                res.0 += point.orig1.coords * coord;
                res.1 += point.orig2.coords * coord;
            }

            res
        } else {
            for i in 0..simplex.dimension() + 1 {
                let coord = simplex.proj_coord(i);
                let point = simplex.point(i);
                res.0 += point.orig1.coords * coord;
                res.1 += point.orig2.coords * coord;
            }

            res
        }
    }

    // FIXME: reset the simplex if it is empty?
    let mut proj = simplex.project_origin_and_reduce();
    let mut old_dir = -Unit::new_normalize(proj.coords);
    let mut max_bound = N::max_value();
    let mut dir;
    let mut niter = 0;

    loop {
        let old_max_bound = max_bound;

        if let Some((new_dir, dist)) = Unit::try_new_and_get(-proj.coords, _eps_tol) {
            dir = new_dir;
            max_bound = dist;
        } else {
            // The origin is on the simplex.
            return GJKResult::Intersection;
        }

        if max_bound >= old_max_bound {
            if exact_dist {
                let (p1, p2) = result(simplex, true);
                return GJKResult::ClosestPoints(p1, p2, old_dir); // upper bounds inconsistencies
            } else {
                return GJKResult::Proximity(old_dir);
            }
        }

        let cso_point = CSOPoint::from_shapes(m1, g1, m2, g2, &dir);
        let min_bound = -na::dot(dir.as_ref(), &cso_point.point.coords);

        assert!(min_bound == min_bound);

        if min_bound > max_dist {
            return GJKResult::NoIntersection(dir);
        } else if !exact_dist && min_bound > na::zero() {
            return GJKResult::Proximity(old_dir);
        } else if max_bound - min_bound <= _eps_rel * max_bound {
            if exact_dist {
                let (p1, p2) = result(simplex, false);
                return GJKResult::ClosestPoints(p1, p2, dir); // the distance found has a good enough precision
            } else {
                return GJKResult::Proximity(dir);
            }
        }

        if !simplex.add_point(cso_point) {
            if exact_dist {
                let (p1, p2) = result(simplex, false);
                return GJKResult::ClosestPoints(p1, p2, dir);
            } else {
                return GJKResult::Proximity(dir);
            }
        }

        old_dir = dir;
        proj = simplex.project_origin_and_reduce();

        if simplex.dimension() == DIM {
            if min_bound >= _eps_tol {
                if exact_dist {
                    let (p1, p2) = result(simplex, true);
                    return GJKResult::ClosestPoints(p1, p2, old_dir);
                } else {
                    // NOTE: previous implementation used old_proj here.
                    return GJKResult::Proximity(old_dir);
                }
            } else {
                return GJKResult::Intersection; // Point inside of the cso.
            }
        }
        niter += 1;
        if niter == 10000 {
            println!("Error: GJK did not converge.");
            return GJKResult::NoIntersection(Vector::x_axis());
        }
    }
}

/// Casts a ray on a support map using the GJK algorithm.
pub fn cast_ray<N, G: ?Sized>(
    m: &Isometry<N>,
    shape: &G,
    simplex: &mut VoronoiSimplex<N>,
    ray: &Ray<N>,
) -> Option<(N, Vector<N>)>
where
    N: Real,
    G: SupportMap<N>,
{
    let m2 = Isometry::identity();
    let g2 = ConstantOrigin;
    minkowski_ray_cast(m, shape, &m2, &g2, ray, simplex)
}

/// Compute the distance that can travel `g1` along the direction `dir` so that
/// `g1` and `g2` just touch.
pub fn directional_distance<N, G1: ?Sized, G2: ?Sized>(
    m1: &Isometry<N>,
    g1: &G1,
    m2: &Isometry<N>,
    g2: &G2,
    dir: &Vector<N>,
    simplex: &mut VoronoiSimplex<N>,
) -> Option<N>
where
    N: Real,
    G1: SupportMap<N>,
    G2: SupportMap<N>,
{
    let ray = Ray::new(Point::origin(), *dir);
    minkowski_ray_cast(m1, g1, m2, g2, &ray, simplex).map(|res| res.0)
}

// Ray-cast on the Minkowski Difference `m1 * g1 - m2 * g2`.
fn minkowski_ray_cast<N, G1: ?Sized, G2: ?Sized>(
    m1: &Isometry<N>,
    g1: &G1,
    m2: &Isometry<N>,
    g2: &G2,
    ray: &Ray<N>,
    simplex: &mut VoronoiSimplex<N>,
) -> Option<(N, Vector<N>)>
where
    N: Real,
    G1: SupportMap<N>,
    G2: SupportMap<N>,
{
    let mut ltoi: N = na::zero();
    let _eps_tol = eps_tol();

    if relative_eq!(ray.dir.norm_squared(), N::zero()) {
        return None;
    }

    let mut curr_ray = *ray;
    let mut dir = -ray.dir;
    let mut old_max_bound: N = Bounded::max_value();
    let mut ldir = dir;
    let mut simplex_init = false;
    let mut niter = 0;

    loop {
        niter += 1;
        if dir.normalize_mut().is_zero() {
            return Some((ltoi, ldir));
        }

        let support_point = CSOPoint::from_shapes(m1, g1, m2, g2, &dir);

        // Clip the ray on the support plane (None <=> t < 0)
        // The configurations are:
        //   dir.dot(ray.dir)  |   t   |               Action
        // −−−−−−−−−−−−−−−−−−−−+−−−−−−−+−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
        //          < 0        |  < 0  | Continue.
        //          < 0        |  > 0  | New lower bound, move the origin.
        //          > 0        |  < 0  | Miss. No intersection.
        //          > 0        |  > 0  | New higher bound.
        match ray_internal::plane_toi_with_ray(&support_point.point, &dir, &curr_ray) {
            Some(t) => {
                if na::dot(&dir, &ray.dir) < na::zero() && t > _eps_tol {
                    // new lower bound
                    ldir = dir;
                    ltoi = ltoi + t;
                    curr_ray.origin = ray.origin + ray.dir * ltoi;
                    dir = curr_ray.origin - support_point.point;
                    // FIXME: could we simply translate the simplex by old_origin - new_origin ?
                    simplex.reset(support_point.translate1(&-curr_ray.origin.coords));
                    old_max_bound = Bounded::max_value();
                    continue;
                }
            }
            None => {
                if na::dot(&dir, &ray.dir) > N::zero() {
                    // miss
                    return None;
                }
            }
        }

        if !simplex_init {
            simplex.reset(support_point.translate1(&-curr_ray.origin.coords));
            simplex_init = true;
        } else if !simplex.add_point(support_point.translate1(&-curr_ray.origin.coords)) {
            return None;
        }

        let proj = simplex.project_origin_and_reduce().coords;
        let max_bound = na::norm_squared(&proj);

        if simplex.dimension() == DIM {
            return Some((ltoi, ldir));
        } else if max_bound <= _eps_tol || max_bound <= _eps_tol * simplex.max_sq_len() {
            // FIXME: we use the same tolerence for absolute and relative epsilons. This could be improved.
            // Return ldir: the last projection plane is tangeant to the intersected surface.
            return Some((ltoi, ldir));
        } else if max_bound >= old_max_bound {
            // Use dir instead of proj since this situations means that the new projection is less
            // accurate than the last one (which is stored on dir).
            return Some((ltoi, dir));
        }

        old_max_bound = max_bound;
        dir = -proj;
    }
}
<|endoftext|>


<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/. */

// http://dev.w3.org/csswg/css-syntax/#parsing

/// The input to these functions needs to implement Iterator<(ComponentValue, SourceLocation)>.
/// The input is consumed to avoid doing a lot of copying.
/// A conforming input can be obtained:
///
/// * From a string in CSS syntax, with tokenize()
/// * From a ~[(ComponentValue, SourceLocation)] vector
///   (as found in "nested" component values such as CurlyBracketBlock),
///   with v.consume_iter()

use std::iterator::Iterator;
use std::ascii::eq_ignore_ascii_case;

use ast::*;

/// Parse top-level of a CSS stylesheet.
/// Return a Iterator<Result<Rule, SyntaxError>>
#[inline]
pub fn parse_stylesheet_rules<T: Iterator<Node>>(iter: T) -> StylesheetParser<T> {
    StylesheetParser(iter)
}

/// Parse a non-top level list of rules eg. the content of an @media rule.
/// Return a Iterator<Result<Rule, SyntaxError>>
#[inline]
pub fn parse_rule_list<T: Iterator<Node>>(iter: T) -> RuleListParser<T> {
    RuleListParser(iter)
}

/// Parse a list of declarations and at-rules,
/// like @page in CSS 2.1, all declaration lists in level 3
/// Return a Iterator<Result<DeclarationListItem, SyntaxError>>
#[inline]
pub fn parse_declaration_list<T: Iterator<Node>>(iter: T) -> DeclarationListParser<T> {
    DeclarationListParser(iter)
}

/// Parse a single rule.
/// Used eg. for CSSRuleList.insertRule()
pub fn parse_one_rule<T: Iterator<Node>>(iter: T) -> Result<Rule, SyntaxError> {
    let mut parser = RuleListParser(iter);
    match parser.next() {
        None => error(START_LOCATION, ErrEmptyInput),
        Some(result) => {
            if result.is_err() { result }
            else { match next_non_whitespace(&mut *parser) {
                None => result,
                Some((_component_value, location)) => error(location, ErrExtraInput),
            }}
        }
    }
}

/// Parse a single declaration (not an at-rule)
/// Used eg. in @supports
pub fn parse_one_declaration<T: Iterator<Node>>(mut iter: T) -> Result<Declaration, SyntaxError> {
    match next_non_whitespace(&mut iter) {
        None => error(START_LOCATION, ErrEmptyInput),
        Some((component_value, location)) => {
            let result = parse_declaration(&mut iter, component_value, location);
            if result.is_err() { result }
            else { match next_non_whitespace(&mut iter) {
                None => result,
                Some((_component_value, location)) => error(location, ErrExtraInput),
            }}
        }
    }
}

/// Parse a single component value.
/// Used eg. in attr(foo, color)
pub fn parse_one_component_value<T: Iterator<Node>>(mut iter: T)
                                -> Result<ComponentValue, SyntaxError> {
    match next_non_whitespace(&mut iter) {
        None => error(START_LOCATION, ErrEmptyInput),
        Some((component_value, _location)) => {
            match next_non_whitespace(&mut iter) {
                None => Ok(component_value),
                Some((_component_value, location)) => error(location, ErrExtraInput),
            }
        }
    }
}

//  ***********  End of public API  ***********

struct StylesheetParser<T>(T);
struct RuleListParser<T>(T);
struct DeclarationListParser<T>(T);

// Work around "error: cannot borrow `*iter` as mutable more than once at a time"
// when using a normal for loop.
macro_rules! for_iter(
    ($iter: ident, $pattern: pat, $loop_body: expr) => (
        loop {
            match $iter.next() { None => break, Some($pattern) => $loop_body }
        }
    );
)

impl<T: Iterator<Node>> Iterator<Result<Rule, SyntaxError>> for StylesheetParser<T> {
    fn next(&mut self) -> Option<Result<Rule, SyntaxError>> {
        let iter = &mut **self;
        for_iter!(iter, (component_value, location), {
            match component_value {
                WhiteSpace | CDO | CDC => (),
                AtKeyword(name) => return Some(Ok(AtRule(parse_at_rule(iter, name, location)))),
                _ => return Some(match parse_qualified_rule(iter, component_value, location) {
                    Ok(rule) => Ok(QualifiedRule(rule)),
                    Err(e) => Err(e),
                }),
            }
        })
        None
    }
}

impl<T: Iterator<Node>> Iterator<Result<Rule, SyntaxError>> for RuleListParser<T> {
    fn next(&mut self) -> Option<Result<Rule, SyntaxError>> {
        let iter = &mut **self;
        for_iter!(iter, (component_value, location), {
            match component_value {
                WhiteSpace => (),
                AtKeyword(name) => return Some(Ok(AtRule(parse_at_rule(iter, name, location)))),
                _ => return Some(match parse_qualified_rule(iter, component_value, location) {
                    Ok(rule) => Ok(QualifiedRule(rule)),
                    Err(e) => Err(e),
                }),
            }
        })
        None
    }
}

impl<T: Iterator<Node>> Iterator<Result<DeclarationListItem, SyntaxError>>
for DeclarationListParser<T> {
    fn next(&mut self) -> Option<Result<DeclarationListItem, SyntaxError>> {
        let iter = &mut **self;
        for_iter!(iter, (component_value, location), {
            match component_value {
                WhiteSpace | Semicolon => (),
                AtKeyword(name)
                => return Some(Ok(Decl_AtRule(parse_at_rule(iter, name, location)))),
                _ => return Some(match parse_declaration(iter, component_value, location) {
                    Ok(declaration) => Ok(Declaration(declaration)),
                    Err(e) => {
                        // Find the end of the declaration
                        for (v, _) in *iter { if v == Semicolon { break } }
                        Err(e)
                    }
                }),
            }
        })
        None
    }
}

fn parse_at_rule<T: Iterator<Node>>(iter: &mut T, name: ~str, location: SourceLocation)
                 -> AtRule {
    let mut prelude = ~[];
    let mut block = None;
    for_iter!(iter, (component_value, _location), {
        match component_value {
            CurlyBracketBlock(content) => { block = Some(content); break },
            Semicolon => break,
            component_value => prelude.push(component_value),
        }
    })
    AtRule {location: location, name: name, prelude: prelude, block: block}
}

fn parse_qualified_rule<T: Iterator<Node>>(iter: &mut T, first: ComponentValue,
                                           location: SourceLocation)
                                           -> Result<QualifiedRule, SyntaxError> {
    match first {
        CurlyBracketBlock(content)
        => return Ok(QualifiedRule { location: location, prelude: ~[], block: content }),
        _ => (),
    }
    let mut prelude = ~[first];
    for_iter!(iter, (component_value, _location), {
        match component_value {
            CurlyBracketBlock(content)
            => return Ok(QualifiedRule {location: location, prelude: prelude, block: content}),
            component_value => prelude.push(component_value),
        }
    })
    error(location, ErrMissingQualifiedRuleBlock)
}

fn parse_declaration<T: Iterator<Node>>(iter: &mut T, first: ComponentValue,
                                        location: SourceLocation)
                                        -> Result<Declaration, SyntaxError> {
    let name = match first {
        Ident(name) => name,
        _ => return error(location, ErrInvalidDeclarationSyntax)
    };
    match next_non_whitespace(iter) {
        Some((Colon, _)) => (),
        _ => return error(location, ErrInvalidDeclarationSyntax),
    }
    let mut value = ~[];
    let mut important = false;
    for_iter!(iter, (component_value, _location), {
        match component_value {
            Semicolon => break,
            Delim('!') => if parse_declaration_important(iter) {
                important = true;
                break
            } else {
                return error(location, ErrInvalidBangImportantSyntax)
            },
            component_value => value.push(component_value),
        }
    })
    Ok(Declaration{location: location, name: name, value: value, important: important})
}

#[inline]
fn parse_declaration_important<T: Iterator<Node>>(iter: &mut T) -> bool {
    let ident_value = match next_non_whitespace(iter) {
        Some((Ident(value), _)) => value,
        _ => return false,
    };
    if !eq_ignore_ascii_case(ident_value, "important") { return false }
    match next_non_whitespace(iter) {
        Some((Semicolon, _)) => true,
        None => true,
        _ => false
    }
}

#[inline]
fn next_non_whitespace<T: Iterator<Node>>(iter: &mut T) -> Option<Node> {
    for (component_value, location) in *iter {
        if component_value != WhiteSpace { return Some((component_value, location)) }
    }
    None
}

#[inline]
fn error<T>(location: SourceLocation, reason: ErrorReason) -> Result<T, SyntaxError> {
    Err(SyntaxError{location: location, reason: reason})
}

// When parsing one thing on an empty input
static START_LOCATION: SourceLocation = SourceLocation{ line: 1, column: 1 };
<commit_msg>Avoid tuple-like structs. See mozilla/rust#7899<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/. */

// http://dev.w3.org/csswg/css-syntax/#parsing

/// The input to these functions needs to implement Iterator<(ComponentValue, SourceLocation)>.
/// The input is consumed to avoid doing a lot of copying.
/// A conforming input can be obtained:
///
/// * From a string in CSS syntax, with tokenize()
/// * From a ~[(ComponentValue, SourceLocation)] vector
///   (as found in "nested" component values such as CurlyBracketBlock),
///   with v.consume_iter()

use std::iterator::Iterator;
use std::ascii::eq_ignore_ascii_case;

use ast::*;

/// Parse top-level of a CSS stylesheet.
/// Return a Iterator<Result<Rule, SyntaxError>>
#[inline]
pub fn parse_stylesheet_rules<T: Iterator<Node>>(iter: T) -> StylesheetParser<T> {
    StylesheetParser{ iter: iter }
}

/// Parse a non-top level list of rules eg. the content of an @media rule.
/// Return a Iterator<Result<Rule, SyntaxError>>
#[inline]
pub fn parse_rule_list<T: Iterator<Node>>(iter: T) -> RuleListParser<T> {
    RuleListParser{ iter: iter }
}

/// Parse a list of declarations and at-rules,
/// like @page in CSS 2.1, all declaration lists in level 3
/// Return a Iterator<Result<DeclarationListItem, SyntaxError>>
#[inline]
pub fn parse_declaration_list<T: Iterator<Node>>(iter: T) -> DeclarationListParser<T> {
    DeclarationListParser{ iter: iter }
}

/// Parse a single rule.
/// Used eg. for CSSRuleList.insertRule()
pub fn parse_one_rule<T: Iterator<Node>>(iter: T) -> Result<Rule, SyntaxError> {
    let mut parser = RuleListParser{ iter: iter };
    match parser.next() {
        None => error(START_LOCATION, ErrEmptyInput),
        Some(result) => {
            if result.is_err() { result }
            else { match next_non_whitespace(&mut parser.iter) {
                None => result,
                Some((_component_value, location)) => error(location, ErrExtraInput),
            }}
        }
    }
}

/// Parse a single declaration (not an at-rule)
/// Used eg. in @supports
pub fn parse_one_declaration<T: Iterator<Node>>(mut iter: T) -> Result<Declaration, SyntaxError> {
    match next_non_whitespace(&mut iter) {
        None => error(START_LOCATION, ErrEmptyInput),
        Some((component_value, location)) => {
            let result = parse_declaration(&mut iter, component_value, location);
            if result.is_err() { result }
            else { match next_non_whitespace(&mut iter) {
                None => result,
                Some((_component_value, location)) => error(location, ErrExtraInput),
            }}
        }
    }
}

/// Parse a single component value.
/// Used eg. in attr(foo, color)
pub fn parse_one_component_value<T: Iterator<Node>>(mut iter: T)
                                -> Result<ComponentValue, SyntaxError> {
    match next_non_whitespace(&mut iter) {
        None => error(START_LOCATION, ErrEmptyInput),
        Some((component_value, _location)) => {
            match next_non_whitespace(&mut iter) {
                None => Ok(component_value),
                Some((_component_value, location)) => error(location, ErrExtraInput),
            }
        }
    }
}

//  ***********  End of public API  ***********

struct StylesheetParser<T>{ iter: T }
struct RuleListParser<T>{ iter: T }
struct DeclarationListParser<T>{ iter: T }

// Work around "error: cannot borrow `*iter` as mutable more than once at a time"
// when using a normal for loop.
macro_rules! for_iter(
    ($iter: ident, $pattern: pat, $loop_body: expr) => (
        loop {
            match $iter.next() { None => break, Some($pattern) => $loop_body }
        }
    );
)

impl<T: Iterator<Node>> Iterator<Result<Rule, SyntaxError>> for StylesheetParser<T> {
    fn next(&mut self) -> Option<Result<Rule, SyntaxError>> {
        let iter = &mut self.iter;
        for_iter!(iter, (component_value, location), {
            match component_value {
                WhiteSpace | CDO | CDC => (),
                AtKeyword(name) => return Some(Ok(AtRule(parse_at_rule(iter, name, location)))),
                _ => return Some(match parse_qualified_rule(iter, component_value, location) {
                    Ok(rule) => Ok(QualifiedRule(rule)),
                    Err(e) => Err(e),
                }),
            }
        })
        None
    }
}

impl<T: Iterator<Node>> Iterator<Result<Rule, SyntaxError>> for RuleListParser<T> {
    fn next(&mut self) -> Option<Result<Rule, SyntaxError>> {
        let iter = &mut self.iter;
        for_iter!(iter, (component_value, location), {
            match component_value {
                WhiteSpace => (),
                AtKeyword(name) => return Some(Ok(AtRule(parse_at_rule(iter, name, location)))),
                _ => return Some(match parse_qualified_rule(iter, component_value, location) {
                    Ok(rule) => Ok(QualifiedRule(rule)),
                    Err(e) => Err(e),
                }),
            }
        })
        None
    }
}

impl<T: Iterator<Node>> Iterator<Result<DeclarationListItem, SyntaxError>>
for DeclarationListParser<T> {
    fn next(&mut self) -> Option<Result<DeclarationListItem, SyntaxError>> {
        let iter = &mut self.iter;
        for_iter!(iter, (component_value, location), {
            match component_value {
                WhiteSpace | Semicolon => (),
                AtKeyword(name)
                => return Some(Ok(Decl_AtRule(parse_at_rule(iter, name, location)))),
                _ => return Some(match parse_declaration(iter, component_value, location) {
                    Ok(declaration) => Ok(Declaration(declaration)),
                    Err(e) => {
                        // Find the end of the declaration
                        for (v, _) in *iter { if v == Semicolon { break } }
                        Err(e)
                    }
                }),
            }
        })
        None
    }
}

fn parse_at_rule<T: Iterator<Node>>(iter: &mut T, name: ~str, location: SourceLocation)
                 -> AtRule {
    let mut prelude = ~[];
    let mut block = None;
    for_iter!(iter, (component_value, _location), {
        match component_value {
            CurlyBracketBlock(content) => { block = Some(content); break },
            Semicolon => break,
            component_value => prelude.push(component_value),
        }
    })
    AtRule {location: location, name: name, prelude: prelude, block: block}
}

fn parse_qualified_rule<T: Iterator<Node>>(iter: &mut T, first: ComponentValue,
                                           location: SourceLocation)
                                           -> Result<QualifiedRule, SyntaxError> {
    match first {
        CurlyBracketBlock(content)
        => return Ok(QualifiedRule { location: location, prelude: ~[], block: content }),
        _ => (),
    }
    let mut prelude = ~[first];
    for_iter!(iter, (component_value, _location), {
        match component_value {
            CurlyBracketBlock(content)
            => return Ok(QualifiedRule {location: location, prelude: prelude, block: content}),
            component_value => prelude.push(component_value),
        }
    })
    error(location, ErrMissingQualifiedRuleBlock)
}

fn parse_declaration<T: Iterator<Node>>(iter: &mut T, first: ComponentValue,
                                        location: SourceLocation)
                                        -> Result<Declaration, SyntaxError> {
    let name = match first {
        Ident(name) => name,
        _ => return error(location, ErrInvalidDeclarationSyntax)
    };
    match next_non_whitespace(iter) {
        Some((Colon, _)) => (),
        _ => return error(location, ErrInvalidDeclarationSyntax),
    }
    let mut value = ~[];
    let mut important = false;
    for_iter!(iter, (component_value, _location), {
        match component_value {
            Semicolon => break,
            Delim('!') => if parse_declaration_important(iter) {
                important = true;
                break
            } else {
                return error(location, ErrInvalidBangImportantSyntax)
            },
            component_value => value.push(component_value),
        }
    })
    Ok(Declaration{location: location, name: name, value: value, important: important})
}

#[inline]
fn parse_declaration_important<T: Iterator<Node>>(iter: &mut T) -> bool {
    let ident_value = match next_non_whitespace(iter) {
        Some((Ident(value), _)) => value,
        _ => return false,
    };
    if !eq_ignore_ascii_case(ident_value, "important") { return false }
    match next_non_whitespace(iter) {
        Some((Semicolon, _)) => true,
        None => true,
        _ => false
    }
}

#[inline]
fn next_non_whitespace<T: Iterator<Node>>(iter: &mut T) -> Option<Node> {
    for (component_value, location) in *iter {
        if component_value != WhiteSpace { return Some((component_value, location)) }
    }
    None
}

#[inline]
fn error<T>(location: SourceLocation, reason: ErrorReason) -> Result<T, SyntaxError> {
    Err(SyntaxError{location: location, reason: reason})
}

// When parsing one thing on an empty input
static START_LOCATION: SourceLocation = SourceLocation{ line: 1, column: 1 };
<|endoftext|>


<commit_before>generate_error_module!(
    generate_error_types!(ViewError, ViewErrorKind,
        Unknown => "Unknown view error"
    );
);

pub use self::error::ViewError;
pub use self::error::ViewErrorKind;

<commit_msg>Add error kinds for glob()ing errors<commit_after>generate_error_module!(
    generate_error_types!(ViewError, ViewErrorKind,
        Unknown              => "Unknown view error",
        GlobError            => "Error while glob()ing",
        PatternError         => "Error in glob() pattern",
        PatternBuildingError => "Could not build glob() pattern"
    );
);

pub use self::error::ViewError;
pub use self::error::ViewErrorKind;

<|endoftext|>


<commit_before>#![feature(test)] extern crate test;
extern crate bk_tree;
extern crate rand;

use bk_tree::metrics::levenshtein;
use test::Bencher;
use rand::{thread_rng, Rng};

fn make_words<R: Rng>(rng: &mut R, n: i32) -> Vec<String> {
    let mut words: Vec<String> = Vec::new();
    for _ in 1..n {
        let l = rng.gen_range(4, 12);
        let s: String = rng.gen_ascii_chars().take(l).collect();
        words.push(s);
    }
    words
}

#[bench]
fn bench_find_exact(b: &mut Bencher) {
    let mut tree: BKTree<String> = BKTree::new(levenshtein);
    let words = make_words(&mut thread_rng(), 1000);
    tree.extend(words.clone());
    let word = words.last().unwrap();

    b.iter(move || {
        tree.find_exact(word.clone())
    });
}

#[bench]
fn bench_find_tol_one(b: &mut Bencher) {
    let mut tree: BKTree<String> = BKTree::new(levenshtein);
    let words = make_words(&mut thread_rng(), 1000);
    tree.extend(words.clone());
    let word = words.last().unwrap();

    b.iter(move || {
        tree.find(word.clone(), 1)
    });
}

#[bench]
fn bench_find_tol_two(b: &mut Bencher) {
    let mut tree: BKTree<String> = BKTree::new(levenshtein);
    let words = make_words(&mut thread_rng(), 1000);
    tree.extend(words.clone());
    let word = words.last().unwrap();

    b.iter(move || {
        tree.find(word.clone(), 2)
    });
}

#[bench]
fn bench_add(b: &mut Bencher) {
    let words = make_words(&mut thread_rng(), 1000);

    b.iter(move || {
        let mut tree: BKTree<String> = BKTree::new(levenshtein);
        for word in words.clone() {
            tree.add(word);
        }
    });
}

#[bench]
fn bench_extend(b: &mut Bencher) {
    let words = make_words(&mut thread_rng(), 1000);

    b.iter(move || {
        let mut tree: BKTree<String> = BKTree::new(levenshtein);
        tree.extend(words.clone());
    });
}
<commit_msg>Fix benches<commit_after>#![feature(test)] extern crate test;
extern crate bk_tree;
extern crate rand;

use bk_tree::BKTree;
use bk_tree::metrics::levenshtein;
use test::Bencher;
use rand::{thread_rng, Rng};

fn make_words<R: Rng>(rng: &mut R, n: i32) -> Vec<String> {
    let mut words: Vec<String> = Vec::new();
    for _ in 1..n {
        let l = rng.gen_range(4, 12);
        let s: String = rng.gen_ascii_chars().take(l).collect();
        words.push(s);
    }
    words
}

#[bench]
fn bench_find_exact(b: &mut Bencher) {
    let mut tree: BKTree<String> = BKTree::new(levenshtein);
    let words = make_words(&mut thread_rng(), 1000);
    tree.extend(words.clone());
    let word = words.last().unwrap();

    b.iter(move || {
        tree.find_exact(word.clone())
    });
}

#[bench]
fn bench_find_tol_one(b: &mut Bencher) {
    let mut tree: BKTree<String> = BKTree::new(levenshtein);
    let words = make_words(&mut thread_rng(), 1000);
    tree.extend(words.clone());
    let word = words.last().unwrap();

    b.iter(move || {
        tree.find(word.clone(), 1)
    });
}

#[bench]
fn bench_find_tol_two(b: &mut Bencher) {
    let mut tree: BKTree<String> = BKTree::new(levenshtein);
    let words = make_words(&mut thread_rng(), 1000);
    tree.extend(words.clone());
    let word = words.last().unwrap();

    b.iter(move || {
        tree.find(word.clone(), 2)
    });
}

#[bench]
fn bench_add(b: &mut Bencher) {
    let words = make_words(&mut thread_rng(), 1000);

    b.iter(move || {
        let mut tree: BKTree<String> = BKTree::new(levenshtein);
        for word in words.clone() {
            tree.add(word);
        }
    });
}

#[bench]
fn bench_extend(b: &mut Bencher) {
    let words = make_words(&mut thread_rng(), 1000);

    b.iter(move || {
        let mut tree: BKTree<String> = BKTree::new(levenshtein);
        tree.extend(words.clone());
    });
}
<|endoftext|>


<commit_before>//! Crate to `#include` C++ headers in your Rust code, and generate
//! idiomatic bindings using `cxx`. See [include_cpp] for details.

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

#[cfg(test)]
mod reduce_test;

use std::{
    fs::File,
    io::Write,
    os::unix::prelude::PermissionsExt,
    path::{Path, PathBuf},
    process::Command,
};

use autocxx_engine::preprocess;
use clap::{crate_authors, crate_version, App, Arg, ArgMatches};
use indoc::indoc;
use itertools::Itertools;
use tempfile::TempDir;

static LONG_HELP: &str = indoc! {"
Command line utility to minimize autocxx bug cases.

This is a wrapper for creduce.

Example command-line:
autocxx-reduce -I my-inc-dir -h my-header -d 'generate!(\"MyClass\")' -k -- --n 64
"};

fn main() {
    let matches = App::new("autocxx-reduce")
        .version(crate_version!())
        .author(crate_authors!())
        .about("Reduce a C++ test case")
        .long_about(LONG_HELP)
        .arg(
            Arg::with_name("inc")
                .short("I")
                .long("inc")
                .multiple(true)
                .number_of_values(1)
                .value_name("INCLUDE DIRS")
                .help("include path")
                .takes_value(true),
        )
        .arg(
            Arg::with_name("define")
                .short("D")
                .long("define")
                .multiple(true)
                .number_of_values(1)
                .value_name("DEFINE")
                .help("macro definition")
                .takes_value(true),
        )
        .arg(
            Arg::with_name("header")
                .short("h")
                .long("header")
                .multiple(true)
                .number_of_values(1)
                .required(true)
                .value_name("HEADER")
                .help("header file name")
                .takes_value(true),
        )
        .arg(
            Arg::with_name("directive")
                .short("d")
                .long("directive")
                .multiple(true)
                .number_of_values(1)
                .value_name("DIRECTIVE")
                .help("directives to put within include_cpp!")
                .takes_value(true),
        )
        .arg(
            Arg::with_name("problem")
                .short("p")
                .long("problem")
                .required(true)
                .value_name("PROBLEM")
                .help("problem string we're looking for... may be in logs, or in generated C++, or generated .rs")
                .takes_value(true),
        )
        .arg(
            Arg::with_name("creduce")
                .long("creduce")
                .required(true)
                .value_name("PATH")
                .help("creduce binary location")
                .default_value("creduce")
                .takes_value(true),
        )
        .arg(
            Arg::with_name("output")
                .short("o")
                .long("output")
                .value_name("OUTPUT")
                .help("where to write minimized output")
                .takes_value(true),
        )
        .arg(
            Arg::with_name("gen-cmd")
                .short("g")
                .long("gen-cmd")
                .value_name("GEN-CMD")
                .help("where to find autocxx-gen")
                .takes_value(true),
        )
        .arg(
            Arg::with_name("keep")
                .short("k")
                .long("keep-dir")
                .help("keep the temporary directory for debugging purposes"),
        )
        .arg(
            Arg::with_name("clang-args")
                .short("c")
                .long("clang-arg")
                .multiple(true)
                .value_name("CLANG_ARG")
                .help("Extra arguments to pass to Clang"),
        )
        .arg(Arg::with_name("creduce-args").last(true).multiple(true))
        .get_matches();
    run(matches).unwrap();
}

fn run(matches: ArgMatches) -> Result<(), std::io::Error> {
    let keep_tmp = matches.is_present("keep");
    let tmp_dir = TempDir::new()?;
    let r = do_run(matches, &tmp_dir);
    if keep_tmp {
        println!(
            "Keeping temp dir created at: {}",
            tmp_dir.into_path().to_str().unwrap()
        );
    }
    r
}

fn do_run(matches: ArgMatches, tmp_dir: &TempDir) -> Result<(), std::io::Error> {
    let incs: Vec<_> = matches
        .values_of("inc")
        .unwrap_or_default()
        .map(PathBuf::from)
        .collect();
    let defs: Vec<_> = matches.values_of("define").unwrap_or_default().collect();
    let headers: Vec<_> = matches.values_of("header").unwrap_or_default().collect();
    let listing_path = tmp_dir.path().join("listing.h");
    create_concatenated_header(&headers, &listing_path)?;
    let concat_path = tmp_dir.path().join("concat.h");
    announce_progress(&format!(
        "Preprocessing {:?} to {:?}",
        listing_path, concat_path
    ));
    preprocess(&listing_path, &concat_path, &incs, &defs)?;
    let rs_path = tmp_dir.path().join("input.rs");
    let directives: Vec<_> = std::iter::once("#include \"concat.h\"\n".to_string())
        .chain(
            matches
                .values_of("directive")
                .unwrap_or_default()
                .map(|s| format!("{}\n", s)),
        )
        .collect();
    create_rs_file(&rs_path, &directives)?;
    let extra_clang_args: Vec<_> = matches
        .values_of("clang-args")
        .unwrap_or_default()
        .collect();
    let default_gen_cmd = std::env::current_exe()?
        .parent()
        .unwrap()
        .join("autocxx-gen")
        .to_str()
        .unwrap()
        .to_string();
    let gen_cmd = matches.value_of("gen-cmd").unwrap_or(&default_gen_cmd);
    run_sample_gen_cmd(gen_cmd, &rs_path, &tmp_dir.path(), &extra_clang_args)?;
    let interestingness_test = tmp_dir.path().join("test.sh");
    create_interestingness_test(
        gen_cmd,
        &interestingness_test,
        matches.value_of("problem").unwrap(),
        &rs_path,
        &extra_clang_args,
    )?;
    run_interestingness_test(&interestingness_test);
    run_creduce(
        matches.value_of("creduce").unwrap(),
        &interestingness_test,
        &concat_path,
        matches.values_of("creduce-args").unwrap_or_default(),
    );
    announce_progress("creduce completed");
    let output_path = matches.value_of("output");
    match output_path {
        None => print_minimized_case(&concat_path)?,
        Some(output_path) => {
            std::fs::copy(&concat_path, &PathBuf::from(output_path))?;
        }
    };
    Ok(())
}

fn announce_progress(msg: &str) {
    println!("=== {} ===", msg);
}

fn print_minimized_case(concat_path: &Path) -> Result<(), std::io::Error> {
    announce_progress("Completed. Minimized test case:");
    let contents = std::fs::read_to_string(concat_path)?;
    println!("{}", contents);
    Ok(())
}

/// Arguments we always pass to creduce. This pass always seems to cause a crash
/// as far as I can tell, so always exclude it. It may be environment-dependent,
/// of course, but as I'm the primary user of this tool I am ruthlessly removing it.
const CREDUCE_STANDARD_ARGS: &[&str] = &["--remove-pass", "pass_line_markers", "*"];

fn run_creduce<'a>(
    creduce_cmd: &str,
    interestingness_test: &'a Path,
    concat_path: &'a Path,
    creduce_args: impl Iterator<Item = &'a str>,
) {
    announce_progress("creduce");
    let args = std::iter::once(interestingness_test.to_str().unwrap())
        .chain(std::iter::once(concat_path.to_str().unwrap()))
        .chain(CREDUCE_STANDARD_ARGS.iter().copied())
        .chain(creduce_args)
        .collect::<Vec<_>>();
    println!("Command: {} {}", creduce_cmd, args.join(" "));
    Command::new(creduce_cmd)
        .args(args)
        .status()
        .expect("failed to creduce");
}

fn run_sample_gen_cmd(
    gen_cmd: &str,
    rs_file: &Path,
    tmp_dir: &Path,
    extra_clang_args: &[&str],
) -> Result<(), std::io::Error> {
    let args = format_gen_cmd(rs_file, tmp_dir.to_str().unwrap(), extra_clang_args);
    let args = args.collect::<Vec<_>>();
    let args_str = args.join(" ");
    announce_progress(&format!("Running sample gen cmd: {} {}", gen_cmd, args_str));
    Command::new(gen_cmd).args(args).status()?;
    Ok(())
}

fn format_gen_cmd<'a>(
    rs_file: &Path,
    dir: &str,
    extra_clang_args: &'a [&str],
) -> impl Iterator<Item = String> + 'a {
    let args = [
        "-o".to_string(),
        dir.to_string(),
        "-I".to_string(),
        dir.to_string(),
        rs_file.to_str().unwrap().to_string(),
        "--gen-rs-complete".to_string(),
        "--gen-cpp".to_string(),
        "--".to_string(),
    ]
    .to_vec();
    args.into_iter()
        .chain(extra_clang_args.iter().map(|s| s.to_string()))
}

fn create_interestingness_test(
    gen_cmd: &str,
    test_path: &Path,
    problem: &str,
    rs_file: &Path,
    extra_clang_args: &[&str],
) -> Result<(), std::io::Error> {
    announce_progress("Creating interestingness test");
    // Ensure we refer to the input header by relative path
    // because creduce will invoke us in some other directory with
    // a copy thereof.
    let mut args = format_gen_cmd(rs_file, "$(pwd)", extra_clang_args);
    let args = args.join(" ");
    let content = format!(
        indoc! {"
        #!/bin/sh
        ({} {} 2>&1 && cat gen.complete.rs && cat autocxxgen*.h) | grep \"{}\"  >/dev/null 2>&1
    "},
        gen_cmd, args, problem
    );
    println!("Interestingness test:\n{}", content);
    {
        let mut file = File::create(test_path)?;
        file.write_all(content.as_bytes())?;
    }

    let mut perms = std::fs::metadata(&test_path)?.permissions();
    perms.set_mode(0o700);
    std::fs::set_permissions(&test_path, perms)?;
    Ok(())
}

fn run_interestingness_test(test_path: &Path) {
    announce_progress("Running interestingness test");
    let status = Command::new(test_path).status().unwrap();
    announce_progress(&format!(
        "Have run interestingness test - result is {:?}",
        status.code()
    ));
}

fn create_rs_file(rs_path: &Path, directives: &[String]) -> Result<(), std::io::Error> {
    announce_progress("Creating Rust input file");
    let mut file = File::create(rs_path)?;
    file.write_all("use autocxx::include_cpp;\ninclude_cpp! (\n".as_bytes())?;
    for directive in directives {
        file.write_all(directive.as_bytes())?;
    }
    file.write_all(");\n".as_bytes())?;
    Ok(())
}

fn create_concatenated_header(headers: &[&str], listing_path: &Path) -> Result<(), std::io::Error> {
    announce_progress("Creating preprocessed header");
    let mut file = File::create(listing_path)?;
    for header in headers {
        file.write_all(format!("#include \"{}\"\n", header).as_bytes())?;
    }
    Ok(())
}
<commit_msg>Skip --remove-pass if unsupported<commit_after>//! Crate to `#include` C++ headers in your Rust code, and generate
//! idiomatic bindings using `cxx`. See [include_cpp] for details.

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

#[cfg(test)]
mod reduce_test;

use std::{
    fs::File,
    io::Write,
    os::unix::prelude::PermissionsExt,
    path::{Path, PathBuf},
    process::Command,
};

use autocxx_engine::preprocess;
use clap::{crate_authors, crate_version, App, Arg, ArgMatches};
use indoc::indoc;
use itertools::Itertools;
use tempfile::TempDir;

static LONG_HELP: &str = indoc! {"
Command line utility to minimize autocxx bug cases.

This is a wrapper for creduce.

Example command-line:
autocxx-reduce -I my-inc-dir -h my-header -d 'generate!(\"MyClass\")' -k -- --n 64
"};

fn main() {
    let matches = App::new("autocxx-reduce")
        .version(crate_version!())
        .author(crate_authors!())
        .about("Reduce a C++ test case")
        .long_about(LONG_HELP)
        .arg(
            Arg::with_name("inc")
                .short("I")
                .long("inc")
                .multiple(true)
                .number_of_values(1)
                .value_name("INCLUDE DIRS")
                .help("include path")
                .takes_value(true),
        )
        .arg(
            Arg::with_name("define")
                .short("D")
                .long("define")
                .multiple(true)
                .number_of_values(1)
                .value_name("DEFINE")
                .help("macro definition")
                .takes_value(true),
        )
        .arg(
            Arg::with_name("header")
                .short("h")
                .long("header")
                .multiple(true)
                .number_of_values(1)
                .required(true)
                .value_name("HEADER")
                .help("header file name")
                .takes_value(true),
        )
        .arg(
            Arg::with_name("directive")
                .short("d")
                .long("directive")
                .multiple(true)
                .number_of_values(1)
                .value_name("DIRECTIVE")
                .help("directives to put within include_cpp!")
                .takes_value(true),
        )
        .arg(
            Arg::with_name("problem")
                .short("p")
                .long("problem")
                .required(true)
                .value_name("PROBLEM")
                .help("problem string we're looking for... may be in logs, or in generated C++, or generated .rs")
                .takes_value(true),
        )
        .arg(
            Arg::with_name("creduce")
                .long("creduce")
                .required(true)
                .value_name("PATH")
                .help("creduce binary location")
                .default_value("creduce")
                .takes_value(true),
        )
        .arg(
            Arg::with_name("output")
                .short("o")
                .long("output")
                .value_name("OUTPUT")
                .help("where to write minimized output")
                .takes_value(true),
        )
        .arg(
            Arg::with_name("gen-cmd")
                .short("g")
                .long("gen-cmd")
                .value_name("GEN-CMD")
                .help("where to find autocxx-gen")
                .takes_value(true),
        )
        .arg(
            Arg::with_name("keep")
                .short("k")
                .long("keep-dir")
                .help("keep the temporary directory for debugging purposes"),
        )
        .arg(
            Arg::with_name("clang-args")
                .short("c")
                .long("clang-arg")
                .multiple(true)
                .value_name("CLANG_ARG")
                .help("Extra arguments to pass to Clang"),
        )
        .arg(Arg::with_name("creduce-args").last(true).multiple(true))
        .get_matches();
    run(matches).unwrap();
}

fn run(matches: ArgMatches) -> Result<(), std::io::Error> {
    let keep_tmp = matches.is_present("keep");
    let tmp_dir = TempDir::new()?;
    let r = do_run(matches, &tmp_dir);
    if keep_tmp {
        println!(
            "Keeping temp dir created at: {}",
            tmp_dir.into_path().to_str().unwrap()
        );
    }
    r
}

fn do_run(matches: ArgMatches, tmp_dir: &TempDir) -> Result<(), std::io::Error> {
    let incs: Vec<_> = matches
        .values_of("inc")
        .unwrap_or_default()
        .map(PathBuf::from)
        .collect();
    let defs: Vec<_> = matches.values_of("define").unwrap_or_default().collect();
    let headers: Vec<_> = matches.values_of("header").unwrap_or_default().collect();
    let listing_path = tmp_dir.path().join("listing.h");
    create_concatenated_header(&headers, &listing_path)?;
    let concat_path = tmp_dir.path().join("concat.h");
    announce_progress(&format!(
        "Preprocessing {:?} to {:?}",
        listing_path, concat_path
    ));
    preprocess(&listing_path, &concat_path, &incs, &defs)?;
    let rs_path = tmp_dir.path().join("input.rs");
    let directives: Vec<_> = std::iter::once("#include \"concat.h\"\n".to_string())
        .chain(
            matches
                .values_of("directive")
                .unwrap_or_default()
                .map(|s| format!("{}\n", s)),
        )
        .collect();
    create_rs_file(&rs_path, &directives)?;
    let extra_clang_args: Vec<_> = matches
        .values_of("clang-args")
        .unwrap_or_default()
        .collect();
    let default_gen_cmd = std::env::current_exe()?
        .parent()
        .unwrap()
        .join("autocxx-gen")
        .to_str()
        .unwrap()
        .to_string();
    let gen_cmd = matches.value_of("gen-cmd").unwrap_or(&default_gen_cmd);
    run_sample_gen_cmd(gen_cmd, &rs_path, &tmp_dir.path(), &extra_clang_args)?;
    let interestingness_test = tmp_dir.path().join("test.sh");
    create_interestingness_test(
        gen_cmd,
        &interestingness_test,
        matches.value_of("problem").unwrap(),
        &rs_path,
        &extra_clang_args,
    )?;
    run_interestingness_test(&interestingness_test);
    run_creduce(
        matches.value_of("creduce").unwrap(),
        &interestingness_test,
        &concat_path,
        matches.values_of("creduce-args").unwrap_or_default(),
    );
    announce_progress("creduce completed");
    let output_path = matches.value_of("output");
    match output_path {
        None => print_minimized_case(&concat_path)?,
        Some(output_path) => {
            std::fs::copy(&concat_path, &PathBuf::from(output_path))?;
        }
    };
    Ok(())
}

fn announce_progress(msg: &str) {
    println!("=== {} ===", msg);
}

fn print_minimized_case(concat_path: &Path) -> Result<(), std::io::Error> {
    announce_progress("Completed. Minimized test case:");
    let contents = std::fs::read_to_string(concat_path)?;
    println!("{}", contents);
    Ok(())
}

/// Arguments we pass to creduce if supported. This pass always seems to cause a crash
/// as far as I can tell, so always exclude it. It may be environment-dependent,
/// of course, but as I'm the primary user of this tool I am ruthlessly removing it.
const REMOVE_PASS_LINE_MARKERS: &[&str] = &["--remove-pass", "pass_line_markers", "*"];
const SKIP_INITIAL_PASSES: &[&str] = &["--skip-initial-passes"];

fn creduce_supports_remove_pass(creduce_cmd: &str) -> bool {
    let msg = Command::new(creduce_cmd)
        .arg("--help")
        .output()
        .unwrap()
        .stdout;
    let msg = std::str::from_utf8(&msg).unwrap();
    msg.contains("--remove-pass")
}

fn run_creduce<'a>(
    creduce_cmd: &str,
    interestingness_test: &'a Path,
    concat_path: &'a Path,
    creduce_args: impl Iterator<Item = &'a str>,
) {
    announce_progress("creduce");
    let args = std::iter::once(interestingness_test.to_str().unwrap())
        .chain(std::iter::once(concat_path.to_str().unwrap()))
        .chain(creduce_args)
        .chain(
            if creduce_supports_remove_pass(creduce_cmd) {
                REMOVE_PASS_LINE_MARKERS
            } else {
                SKIP_INITIAL_PASSES
            }
            .iter()
            .copied(),
        )
        .collect::<Vec<_>>();
    println!("Command: {} {}", creduce_cmd, args.join(" "));
    Command::new(creduce_cmd)
        .args(args)
        .status()
        .expect("failed to creduce");
}

fn run_sample_gen_cmd(
    gen_cmd: &str,
    rs_file: &Path,
    tmp_dir: &Path,
    extra_clang_args: &[&str],
) -> Result<(), std::io::Error> {
    let args = format_gen_cmd(rs_file, tmp_dir.to_str().unwrap(), extra_clang_args);
    let args = args.collect::<Vec<_>>();
    let args_str = args.join(" ");
    announce_progress(&format!("Running sample gen cmd: {} {}", gen_cmd, args_str));
    Command::new(gen_cmd).args(args).status()?;
    Ok(())
}

fn format_gen_cmd<'a>(
    rs_file: &Path,
    dir: &str,
    extra_clang_args: &'a [&str],
) -> impl Iterator<Item = String> + 'a {
    let args = [
        "-o".to_string(),
        dir.to_string(),
        "-I".to_string(),
        dir.to_string(),
        rs_file.to_str().unwrap().to_string(),
        "--gen-rs-complete".to_string(),
        "--gen-cpp".to_string(),
        "--".to_string(),
    ]
    .to_vec();
    args.into_iter()
        .chain(extra_clang_args.iter().map(|s| s.to_string()))
}

fn create_interestingness_test(
    gen_cmd: &str,
    test_path: &Path,
    problem: &str,
    rs_file: &Path,
    extra_clang_args: &[&str],
) -> Result<(), std::io::Error> {
    announce_progress("Creating interestingness test");
    // Ensure we refer to the input header by relative path
    // because creduce will invoke us in some other directory with
    // a copy thereof.
    let mut args = format_gen_cmd(rs_file, "$(pwd)", extra_clang_args);
    let args = args.join(" ");
    let content = format!(
        indoc! {"
        #!/bin/sh
        ({} {} 2>&1 && cat gen.complete.rs && cat autocxxgen*.h) | grep \"{}\"  >/dev/null 2>&1
    "},
        gen_cmd, args, problem
    );
    println!("Interestingness test:\n{}", content);
    {
        let mut file = File::create(test_path)?;
        file.write_all(content.as_bytes())?;
    }

    let mut perms = std::fs::metadata(&test_path)?.permissions();
    perms.set_mode(0o700);
    std::fs::set_permissions(&test_path, perms)?;
    Ok(())
}

fn run_interestingness_test(test_path: &Path) {
    announce_progress("Running interestingness test");
    let status = Command::new(test_path).status().unwrap();
    announce_progress(&format!(
        "Have run interestingness test - result is {:?}",
        status.code()
    ));
}

fn create_rs_file(rs_path: &Path, directives: &[String]) -> Result<(), std::io::Error> {
    announce_progress("Creating Rust input file");
    let mut file = File::create(rs_path)?;
    file.write_all("use autocxx::include_cpp;\ninclude_cpp! (\n".as_bytes())?;
    for directive in directives {
        file.write_all(directive.as_bytes())?;
    }
    file.write_all(");\n".as_bytes())?;
    Ok(())
}

fn create_concatenated_header(headers: &[&str], listing_path: &Path) -> Result<(), std::io::Error> {
    announce_progress("Creating preprocessed header");
    let mut file = File::create(listing_path)?;
    for header in headers {
        file.write_all(format!("#include \"{}\"\n", header).as_bytes())?;
    }
    Ok(())
}
<|endoftext|>


<commit_before>/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use common::Diagnostic;
use common::FeatureFlags;
use common::NoopPerfLogger;
use common::SourceLocationKey;
use common::SourceLocationKey::Generated;
use common::TextSource;

use graphql_ir::Program;

use graphql_text_printer;
use graphql_text_printer::PrinterOptions;
use intern::string_key::Intern;
use relay_codegen::print_fragment;
use relay_codegen::print_operation;
use relay_config::ProjectConfig;
use relay_schema::build_schema_with_extensions;
use relay_transforms::apply_transforms;
use relay_transforms::Programs;
use relay_typegen::generate_fragment_type_exports_section;
use relay_typegen::generate_operation_type_exports_section;
use relay_typegen::TypegenConfig;
use schema::SDLSchema;
use serde::Serialize;

use std::sync::Arc;

use wasm_bindgen::prelude::*;

// When the `wee_alloc` feature is enabled, use `wee_alloc` as the global
// allocator.
#[cfg(feature = "wee_alloc")]
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;

#[derive(Serialize, Debug)]
pub struct WasmDiagnostic {
    message: String,
    line_start: u32,
    line_end: u32,
    column_start: u32,
    column_end: u32,
}

enum InputType<'a> {
    Schema(&'a str),
    Document(&'a str),
}

impl<'a> InputType<'a> {
    fn source(&self) -> &'a str {
        match self {
            Self::Schema(source) => source,
            Self::Document(source) => source,
        }
    }
}

#[derive(Serialize, Debug)]
pub enum PlaygroundError {
    DocumentDiagnostics(Vec<WasmDiagnostic>),
    SchemaDiagnostics(Vec<WasmDiagnostic>),
    ConfigError(String),
    TypegenConfigError(String),
}

pub type PlaygroundResult = Result<String, PlaygroundError>;

#[wasm_bindgen]
pub fn parse_to_ast(document_text: &str) -> String {
    serialize_result(parse_to_ast_impl(document_text))
}

pub fn parse_to_ast_impl(document_text: &str) -> PlaygroundResult {
    let serialized_document = graphql_syntax::parse_executable(document_text, Generated)
        .map(|document| format!("{:?}", document))
        .map_err(|diagnostics| map_diagnostics(diagnostics, &InputType::Document(document_text)))?;

    Ok(serialized_document)
}

#[wasm_bindgen]
pub fn parse_to_ir(schema_text: &str, document_text: &str) -> String {
    serialize_result(parse_to_ir_impl(schema_text, document_text))
}

pub fn parse_to_ir_impl(schema_text: &str, document_text: &str) -> PlaygroundResult {
    let document = graphql_syntax::parse_executable(document_text, Generated)
        .map_err(|diagnostics| map_diagnostics(diagnostics, &InputType::Document(document_text)))?;

    let schema = Arc::new(
        build_schema_with_extensions(
            &[(schema_text, SourceLocationKey::generated())],
            &Vec::<(&str, SourceLocationKey)>::new(),
        )
        .map_err(|diagnostics| map_diagnostics(diagnostics, &InputType::Schema(schema_text)))?,
    );

    let ir = graphql_ir::build(&schema, &document.definitions)
        .map_err(|diagnostics| map_diagnostics(diagnostics, &InputType::Document(document_text)))?;

    Ok(ir
        .iter()
        .map(|definition| format!("{:?}", definition))
        .collect::<Vec<String>>()
        .join("\n"))
}

#[wasm_bindgen]
pub fn parse_to_reader_ast(
    feature_flags_json: &str,
    schema_text: &str,
    document_text: &str,
) -> String {
    serialize_result(parse_to_reader_ast_impl(
        feature_flags_json,
        schema_text,
        document_text,
    ))
}

pub fn parse_to_reader_ast_impl(
    feature_flags_json: &str,
    schema_text: &str,
    document_text: &str,
) -> PlaygroundResult {
    let schema = Arc::new(
        build_schema_with_extensions(
            &[(schema_text, SourceLocationKey::Generated)],
            &Vec::<(&str, SourceLocationKey)>::new(),
        )
        .map_err(|diagnostics| map_diagnostics(diagnostics, &InputType::Schema(schema_text)))?,
    );
    let project_config = get_project_config(feature_flags_json, None)?;
    let programs = get_programs(&schema, &project_config, document_text)?;

    let reader_ast_string = programs
        .reader
        .fragments()
        .map(|def| {
            let mut import_statements = Default::default();
            let fragment = print_fragment(&schema, def, &project_config, &mut import_statements);
            format!("{}{}", import_statements, fragment)
        })
        .chain(programs.reader.operations().map(|def| {
            let mut import_statements = Default::default();
            let operation = print_operation(&schema, def, &project_config, &mut import_statements);
            format!("{}{}", import_statements, operation)
        }))
        .collect::<Vec<_>>()
        .join("\n\n");

    Ok(reader_ast_string)
}

#[wasm_bindgen]
pub fn parse_to_normalization_ast(
    feature_flags_json: &str,
    schema_text: &str,
    document_text: &str,
) -> String {
    serialize_result(parse_to_normalization_ast_impl(
        feature_flags_json,
        schema_text,
        document_text,
    ))
}

pub fn parse_to_normalization_ast_impl(
    feature_flags_json: &str,
    schema_text: &str,
    document_text: &str,
) -> PlaygroundResult {
    let schema = Arc::new(
        build_schema_with_extensions(
            &[(schema_text, SourceLocationKey::Generated)],
            &Vec::<(&str, SourceLocationKey)>::new(),
        )
        .map_err(|diagnostics| map_diagnostics(diagnostics, &InputType::Schema(schema_text)))?,
    );
    let project_config = get_project_config(feature_flags_json, None)?;
    let programs = get_programs(&schema, &project_config, document_text)?;

    let normalization_ast_string = programs
        .normalization
        .operations()
        .map(|def| {
            let mut import_statements = Default::default();
            let operation = print_operation(&schema, def, &project_config, &mut import_statements);

            format!("{}{}", import_statements, operation)
        })
        .collect::<Vec<_>>()
        .join("\n\n");

    Ok(normalization_ast_string)
}

#[wasm_bindgen]
pub fn parse_to_types(
    feature_flags_json: &str,
    typegen_config_json: &str,
    schema_text: &str,
    document_text: &str,
) -> String {
    serialize_result(parse_to_types_impl(
        feature_flags_json,
        typegen_config_json,
        schema_text,
        document_text,
    ))
}

pub fn parse_to_types_impl(
    feature_flags_json: &str,
    typegen_config_json: &str,
    schema_text: &str,
    document_text: &str,
) -> PlaygroundResult {
    let schema = Arc::new(
        build_schema_with_extensions(
            &[(schema_text, SourceLocationKey::Generated)],
            &Vec::<(&str, SourceLocationKey)>::new(),
        )
        .map_err(|diagnostics| map_diagnostics(diagnostics, &InputType::Schema(schema_text)))?,
    );
    let project_config = get_project_config(feature_flags_json, Some(typegen_config_json))?;
    let programs = get_programs(&schema, &project_config, document_text)?;

    let types_string = programs
        .typegen
        .fragments()
        .map(|def| generate_fragment_type_exports_section(def, &schema, &project_config))
        .chain(programs.typegen.operations().map(|typegen_operation| {
            let normalization_operation = programs
                .normalization
                .operation(typegen_operation.name.item)
                .unwrap();

            generate_operation_type_exports_section(
                typegen_operation,
                normalization_operation,
                &schema,
                &project_config,
            )
        }))
        .collect::<Vec<_>>()
        .join("\n\n");

    Ok(types_string)
}

#[wasm_bindgen]
pub fn transform(feature_flags_json: &str, schema_text: &str, document_text: &str) -> String {
    serialize_result(transform_impl(
        feature_flags_json,
        schema_text,
        document_text,
    ))
}

fn transform_impl(
    feature_flags_json: &str,
    schema_text: &str,
    document_text: &str,
) -> PlaygroundResult {
    let schema = Arc::new(
        build_schema_with_extensions(
            &[(schema_text, SourceLocationKey::Generated)],
            &Vec::<(&str, SourceLocationKey)>::new(),
        )
        .map_err(|diagnostics| map_diagnostics(diagnostics, &InputType::Schema(schema_text)))?,
    );
    let project_config = get_project_config(feature_flags_json, None)?;
    let programs = get_programs(&schema, &project_config, document_text)?;

    let transformed_operations = programs
        .operation_text
        .operations()
        .map(|operation| {
            graphql_text_printer::print_operation(&schema, operation, PrinterOptions::default())
        })
        .collect::<Vec<_>>()
        .join("\n");

    let transformed_fragments = programs
        .operation_text
        .fragments()
        .map(|operation| {
            graphql_text_printer::print_fragment(&schema, operation, PrinterOptions::default())
        })
        .collect::<Vec<_>>()
        .join("\n");

    let output = transformed_operations + "\n\n" + &transformed_fragments;
    Ok(output)
}

fn get_project_config(
    feature_flags_json: &str,
    typegen_config_json: Option<&str>,
) -> Result<ProjectConfig, PlaygroundError> {
    let feature_flags: FeatureFlags = serde_json::from_str(feature_flags_json)
        .map_err(|err| PlaygroundError::ConfigError(format!("{}", err)))?;
    let project_name = "test_project".intern();
    let typegen_config: TypegenConfig = typegen_config_json
        .map(|str| {
            serde_json::from_str(str)
                .map_err(|err| PlaygroundError::TypegenConfigError(format!("{}", err)))
        })
        .unwrap_or(Ok(TypegenConfig::default()))?;

    Ok(ProjectConfig {
        name: project_name,
        feature_flags: Arc::new(feature_flags),
        typegen_config,
        ..Default::default()
    })
}

fn get_programs(
    schema: &Arc<SDLSchema>,
    project_config: &ProjectConfig,
    document_text: &str,
) -> Result<Programs, PlaygroundError> {
    let document = graphql_syntax::parse_executable(document_text, Generated)
        .map_err(|diagnostics| map_diagnostics(diagnostics, &InputType::Document(document_text)))?;

    let ir = graphql_ir::build(schema, &document.definitions)
        .map_err(|diagnostics| map_diagnostics(diagnostics, &InputType::Document(document_text)))?;

    let program = Program::from_definitions(schema.clone(), ir);
    let base_fragment_names = Arc::new(Default::default());

    apply_transforms(
        &project_config,
        Arc::new(program),
        base_fragment_names,
        Arc::new(NoopPerfLogger),
        None,
        None,
    )
    .map_err(|diagnostics: Vec<Diagnostic>| {
        map_diagnostics(diagnostics, &InputType::Document(document_text))
    })
}

fn map_diagnostics(diagnostics: Vec<Diagnostic>, source: &InputType<'_>) -> PlaygroundError {
    let wasm_diagnostics = diagnostics
        .iter()
        .map(|diagnostic| {
            let range = TextSource::from_whole_document(source.source())
                .to_span_range(diagnostic.location().span());
            WasmDiagnostic {
                message: diagnostic.print_without_source(),
                line_start: range.start.line,
                line_end: range.end.line,
                column_start: range.start.character,
                column_end: range.end.character,
            }
        })
        .collect();
    match source {
        InputType::Document(_) => PlaygroundError::DocumentDiagnostics(wasm_diagnostics),
        InputType::Schema(_) => PlaygroundError::SchemaDiagnostics(wasm_diagnostics),
    }
}

fn serialize_result(result: PlaygroundResult) -> String {
    serde_json::to_string(&result).unwrap()
}

#[wasm_bindgen(start)]
pub fn start() {
    console_error_panic_hook::set_once();
}
<commit_msg>Codemod away ::{self};<commit_after>/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use common::Diagnostic;
use common::FeatureFlags;
use common::NoopPerfLogger;
use common::SourceLocationKey;
use common::SourceLocationKey::Generated;
use common::TextSource;

use graphql_ir::Program;

use graphql_text_printer::PrinterOptions;
use intern::string_key::Intern;
use relay_codegen::print_fragment;
use relay_codegen::print_operation;
use relay_config::ProjectConfig;
use relay_schema::build_schema_with_extensions;
use relay_transforms::apply_transforms;
use relay_transforms::Programs;
use relay_typegen::generate_fragment_type_exports_section;
use relay_typegen::generate_operation_type_exports_section;
use relay_typegen::TypegenConfig;
use schema::SDLSchema;
use serde::Serialize;

use std::sync::Arc;

use wasm_bindgen::prelude::*;

// When the `wee_alloc` feature is enabled, use `wee_alloc` as the global
// allocator.
#[cfg(feature = "wee_alloc")]
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;

#[derive(Serialize, Debug)]
pub struct WasmDiagnostic {
    message: String,
    line_start: u32,
    line_end: u32,
    column_start: u32,
    column_end: u32,
}

enum InputType<'a> {
    Schema(&'a str),
    Document(&'a str),
}

impl<'a> InputType<'a> {
    fn source(&self) -> &'a str {
        match self {
            Self::Schema(source) => source,
            Self::Document(source) => source,
        }
    }
}

#[derive(Serialize, Debug)]
pub enum PlaygroundError {
    DocumentDiagnostics(Vec<WasmDiagnostic>),
    SchemaDiagnostics(Vec<WasmDiagnostic>),
    ConfigError(String),
    TypegenConfigError(String),
}

pub type PlaygroundResult = Result<String, PlaygroundError>;

#[wasm_bindgen]
pub fn parse_to_ast(document_text: &str) -> String {
    serialize_result(parse_to_ast_impl(document_text))
}

pub fn parse_to_ast_impl(document_text: &str) -> PlaygroundResult {
    let serialized_document = graphql_syntax::parse_executable(document_text, Generated)
        .map(|document| format!("{:?}", document))
        .map_err(|diagnostics| map_diagnostics(diagnostics, &InputType::Document(document_text)))?;

    Ok(serialized_document)
}

#[wasm_bindgen]
pub fn parse_to_ir(schema_text: &str, document_text: &str) -> String {
    serialize_result(parse_to_ir_impl(schema_text, document_text))
}

pub fn parse_to_ir_impl(schema_text: &str, document_text: &str) -> PlaygroundResult {
    let document = graphql_syntax::parse_executable(document_text, Generated)
        .map_err(|diagnostics| map_diagnostics(diagnostics, &InputType::Document(document_text)))?;

    let schema = Arc::new(
        build_schema_with_extensions(
            &[(schema_text, SourceLocationKey::generated())],
            &Vec::<(&str, SourceLocationKey)>::new(),
        )
        .map_err(|diagnostics| map_diagnostics(diagnostics, &InputType::Schema(schema_text)))?,
    );

    let ir = graphql_ir::build(&schema, &document.definitions)
        .map_err(|diagnostics| map_diagnostics(diagnostics, &InputType::Document(document_text)))?;

    Ok(ir
        .iter()
        .map(|definition| format!("{:?}", definition))
        .collect::<Vec<String>>()
        .join("\n"))
}

#[wasm_bindgen]
pub fn parse_to_reader_ast(
    feature_flags_json: &str,
    schema_text: &str,
    document_text: &str,
) -> String {
    serialize_result(parse_to_reader_ast_impl(
        feature_flags_json,
        schema_text,
        document_text,
    ))
}

pub fn parse_to_reader_ast_impl(
    feature_flags_json: &str,
    schema_text: &str,
    document_text: &str,
) -> PlaygroundResult {
    let schema = Arc::new(
        build_schema_with_extensions(
            &[(schema_text, SourceLocationKey::Generated)],
            &Vec::<(&str, SourceLocationKey)>::new(),
        )
        .map_err(|diagnostics| map_diagnostics(diagnostics, &InputType::Schema(schema_text)))?,
    );
    let project_config = get_project_config(feature_flags_json, None)?;
    let programs = get_programs(&schema, &project_config, document_text)?;

    let reader_ast_string = programs
        .reader
        .fragments()
        .map(|def| {
            let mut import_statements = Default::default();
            let fragment = print_fragment(&schema, def, &project_config, &mut import_statements);
            format!("{}{}", import_statements, fragment)
        })
        .chain(programs.reader.operations().map(|def| {
            let mut import_statements = Default::default();
            let operation = print_operation(&schema, def, &project_config, &mut import_statements);
            format!("{}{}", import_statements, operation)
        }))
        .collect::<Vec<_>>()
        .join("\n\n");

    Ok(reader_ast_string)
}

#[wasm_bindgen]
pub fn parse_to_normalization_ast(
    feature_flags_json: &str,
    schema_text: &str,
    document_text: &str,
) -> String {
    serialize_result(parse_to_normalization_ast_impl(
        feature_flags_json,
        schema_text,
        document_text,
    ))
}

pub fn parse_to_normalization_ast_impl(
    feature_flags_json: &str,
    schema_text: &str,
    document_text: &str,
) -> PlaygroundResult {
    let schema = Arc::new(
        build_schema_with_extensions(
            &[(schema_text, SourceLocationKey::Generated)],
            &Vec::<(&str, SourceLocationKey)>::new(),
        )
        .map_err(|diagnostics| map_diagnostics(diagnostics, &InputType::Schema(schema_text)))?,
    );
    let project_config = get_project_config(feature_flags_json, None)?;
    let programs = get_programs(&schema, &project_config, document_text)?;

    let normalization_ast_string = programs
        .normalization
        .operations()
        .map(|def| {
            let mut import_statements = Default::default();
            let operation = print_operation(&schema, def, &project_config, &mut import_statements);

            format!("{}{}", import_statements, operation)
        })
        .collect::<Vec<_>>()
        .join("\n\n");

    Ok(normalization_ast_string)
}

#[wasm_bindgen]
pub fn parse_to_types(
    feature_flags_json: &str,
    typegen_config_json: &str,
    schema_text: &str,
    document_text: &str,
) -> String {
    serialize_result(parse_to_types_impl(
        feature_flags_json,
        typegen_config_json,
        schema_text,
        document_text,
    ))
}

pub fn parse_to_types_impl(
    feature_flags_json: &str,
    typegen_config_json: &str,
    schema_text: &str,
    document_text: &str,
) -> PlaygroundResult {
    let schema = Arc::new(
        build_schema_with_extensions(
            &[(schema_text, SourceLocationKey::Generated)],
            &Vec::<(&str, SourceLocationKey)>::new(),
        )
        .map_err(|diagnostics| map_diagnostics(diagnostics, &InputType::Schema(schema_text)))?,
    );
    let project_config = get_project_config(feature_flags_json, Some(typegen_config_json))?;
    let programs = get_programs(&schema, &project_config, document_text)?;

    let types_string = programs
        .typegen
        .fragments()
        .map(|def| generate_fragment_type_exports_section(def, &schema, &project_config))
        .chain(programs.typegen.operations().map(|typegen_operation| {
            let normalization_operation = programs
                .normalization
                .operation(typegen_operation.name.item)
                .unwrap();

            generate_operation_type_exports_section(
                typegen_operation,
                normalization_operation,
                &schema,
                &project_config,
            )
        }))
        .collect::<Vec<_>>()
        .join("\n\n");

    Ok(types_string)
}

#[wasm_bindgen]
pub fn transform(feature_flags_json: &str, schema_text: &str, document_text: &str) -> String {
    serialize_result(transform_impl(
        feature_flags_json,
        schema_text,
        document_text,
    ))
}

fn transform_impl(
    feature_flags_json: &str,
    schema_text: &str,
    document_text: &str,
) -> PlaygroundResult {
    let schema = Arc::new(
        build_schema_with_extensions(
            &[(schema_text, SourceLocationKey::Generated)],
            &Vec::<(&str, SourceLocationKey)>::new(),
        )
        .map_err(|diagnostics| map_diagnostics(diagnostics, &InputType::Schema(schema_text)))?,
    );
    let project_config = get_project_config(feature_flags_json, None)?;
    let programs = get_programs(&schema, &project_config, document_text)?;

    let transformed_operations = programs
        .operation_text
        .operations()
        .map(|operation| {
            graphql_text_printer::print_operation(&schema, operation, PrinterOptions::default())
        })
        .collect::<Vec<_>>()
        .join("\n");

    let transformed_fragments = programs
        .operation_text
        .fragments()
        .map(|operation| {
            graphql_text_printer::print_fragment(&schema, operation, PrinterOptions::default())
        })
        .collect::<Vec<_>>()
        .join("\n");

    let output = transformed_operations + "\n\n" + &transformed_fragments;
    Ok(output)
}

fn get_project_config(
    feature_flags_json: &str,
    typegen_config_json: Option<&str>,
) -> Result<ProjectConfig, PlaygroundError> {
    let feature_flags: FeatureFlags = serde_json::from_str(feature_flags_json)
        .map_err(|err| PlaygroundError::ConfigError(format!("{}", err)))?;
    let project_name = "test_project".intern();
    let typegen_config: TypegenConfig = typegen_config_json
        .map(|str| {
            serde_json::from_str(str)
                .map_err(|err| PlaygroundError::TypegenConfigError(format!("{}", err)))
        })
        .unwrap_or(Ok(TypegenConfig::default()))?;

    Ok(ProjectConfig {
        name: project_name,
        feature_flags: Arc::new(feature_flags),
        typegen_config,
        ..Default::default()
    })
}

fn get_programs(
    schema: &Arc<SDLSchema>,
    project_config: &ProjectConfig,
    document_text: &str,
) -> Result<Programs, PlaygroundError> {
    let document = graphql_syntax::parse_executable(document_text, Generated)
        .map_err(|diagnostics| map_diagnostics(diagnostics, &InputType::Document(document_text)))?;

    let ir = graphql_ir::build(schema, &document.definitions)
        .map_err(|diagnostics| map_diagnostics(diagnostics, &InputType::Document(document_text)))?;

    let program = Program::from_definitions(schema.clone(), ir);
    let base_fragment_names = Arc::new(Default::default());

    apply_transforms(
        &project_config,
        Arc::new(program),
        base_fragment_names,
        Arc::new(NoopPerfLogger),
        None,
        None,
    )
    .map_err(|diagnostics: Vec<Diagnostic>| {
        map_diagnostics(diagnostics, &InputType::Document(document_text))
    })
}

fn map_diagnostics(diagnostics: Vec<Diagnostic>, source: &InputType<'_>) -> PlaygroundError {
    let wasm_diagnostics = diagnostics
        .iter()
        .map(|diagnostic| {
            let range = TextSource::from_whole_document(source.source())
                .to_span_range(diagnostic.location().span());
            WasmDiagnostic {
                message: diagnostic.print_without_source(),
                line_start: range.start.line,
                line_end: range.end.line,
                column_start: range.start.character,
                column_end: range.end.character,
            }
        })
        .collect();
    match source {
        InputType::Document(_) => PlaygroundError::DocumentDiagnostics(wasm_diagnostics),
        InputType::Schema(_) => PlaygroundError::SchemaDiagnostics(wasm_diagnostics),
    }
}

fn serialize_result(result: PlaygroundResult) -> String {
    serde_json::to_string(&result).unwrap()
}

#[wasm_bindgen(start)]
pub fn start() {
    console_error_panic_hook::set_once();
}
<|endoftext|>