<commit_before>/*! # nalgebra-glm − nalgebra in _easy mode_
    **nalgebra-glm** is a GLM-like interface for the **nalgebra** general-purpose linear algebra library.
    [GLM](https://glm.g-truc.net) itself is a popular C++ linear algebra library essentially targeting computer graphics. Therefore
    **nalgebra-glm** draws inspiration from GLM to define a nice and easy-to-use API for simple graphics application.

    All the types of **nalgebra-glm** are aliases of types from **nalgebra**. Therefore there is a complete and
    seamless inter-operability between both.

    ## Getting started
    First of all, you should start by taking a look at the official [GLM API documentation](http://glm.g-truc.net/0.9.9/api/index.html)
    since **nalgebra-glm** implements a large subset of it. To use **nalgebra-glm** to your project, you
    should add it as a dependency to your `Crates.toml`:

    ```toml
    [dependencies]
    nalgebra-glm = "0.1"
    ```

    Then, you should add an `extern crate` statement to your `lib.rs` or `main.rs` file. It is **strongly
    recommended** to add a crate alias to `glm` as well so that you will be able to call functions of
    **nalgebra-glm** using the module prefix `glm::`. For example you will write `glm::rotate(...)` instead
    of the more verbose `nalgebra_glm::rotate(...)`:

    ```rust
    extern crate nalgebra_glm as glm;
    ```

    ## Features overview
    **nalgebra-glm** supports most linear-algebra related features of the C++ GLM library. Mathematically
    speaking, it supports all the common transformations like rotations, translations, scaling, shearing,
    and projections but operating in homogeneous coordinates. This means all the 2D transformations are
    expressed as 3x3 matrices, and all the 3D transformations as 4x4 matrices. This is less computationally-efficient
    and memory-efficient than nalgebra's [transformation types](https://www.nalgebra.org/points_and_transformations/#transformations),
    but this has the benefit of being simpler to use.
    ### Main differences compared to GLM
    While **nalgebra-glm** follows the feature line of the C++ GLM library, quite a few differences
    remain and they are mostly syntactic. The main ones are:
    * All function names use `snake_case`, which is the Rust convention.
    * All type names use `CamelCase`, which is the Rust convention.
    * All function arguments, except for scalars, are all passed by-reference.
    * Some feature are not yet implemented and should be added in the future. In particular, no packing
    functions are available.
    * A few features are not implemented and will never be. This includes functions related to color
    spaces, and closest points computations. Other crates should be used for those. For example, closest
    points computation can be handled by the [ncollide](https://ncollide.org) project.

    In addition, because Rust does not allows function overloading, all functions must be given a unique name.
    Here are a few rules chosen arbitrarily for **nalgebra-glm**:
    * Functions operating in 2d will usually end with the `2d` suffix, e.g., `glm::rotade2d` is for 2D while `glm::rotate` is for 3D.
    * Functions operating on vector will often end with the `_vec` suffix, possibly followed by the dimension of vector, e.g., `glm::rotate_vec2`.
    * Every function related to quaternions start with the `quat_` prefix, e.g., `glm::quat_dot(q1, q2)`.
    * All the conversion functions have unique names as described [bellow](#conversions).
    ### Vector and matrix construction
    Vectors, matrices, and quaternions can be constructed using several approaches:
    * Using functions with the same name as their type in lower-case. For example `glm::vec3(x, y, z)` will create a 3D vector.
    * Using the `::new` constructor. For example `Vec3::new(x, y, z)` will create a 3D vector.
    * Using the functions prefixed by `make_` to build a vector a matrix from a slice. For example `glm::make_vec3(&[x, y, z])` will create a 3D vector.
    Keep in mind that constructing a matrix using this type of funcitons require its components to be arrange in column-major order on the slice.
    * Using a geometric construction function. For example `glm::rotation(angle, axis)` will build a 4x4 homogeneous rotation matrix from an angle (in radians) and an axis.
    * Using swizzling and conversions as described in the next sections.
    ### Swizzling
    Vector swizzling is a native feature of **nalgebra** itself. Therefore, you can use it with all
    the vectors of **nalgebra-glm** as well. Swizzling is supported as methods and works only up to
    dimension 3, i.e., you can only refer to the components `x`, `y` and `z` and can only create a
    2D or 3D vector using this technique. Here is some examples, assuming `v` is a vector with float
    components here:
    * `v.xx()` is equivalent to `glm::vec2(v.x, v.x)` and to `Vec2::new(v.x, v.x)`.
    * `v.zx()` is equivalent to `glm::vec2(v.z, v.x)` and to `Vec2::new(v.z, v.x)`.
    * `v.yxz()` is equivalent to `glm::vec3(v.y, v.x, v.z)` and to `Vec3::new(v.y, v.x, v.z)`.
    * `v.zzy()` is equivalent to `glm::vec3(v.z, v.z, v.y)` and to `Vec3::new(v.z, v.z, v.y)`.

    Any combination of two or three components picked among `x`, `y`, and `z` will work.
    ### Conversions
    It is often useful to convert one algebraic type to another. There are two main approaches for converting
    between types in `nalgebra-glm`:
    * Using function with the form `type1_to_type2` in order to convert an instance of `type1` into an instance of `type2`.
    For example `glm::mat3_to_mat4(m)` will convert the 3x3 matrix `m` to a 4x4 matrix by appending one column on the right
    and one row on the left. Those now row and columns are filled with 0 except for the diagonal element which is set to 1.
    * Using one of the `convert`, `try_convert`, or `convert_unchecked` functions.
    These functions are directly re-exported from nalgebra and are extremely versatile:
        1. The `convert` function can convert any type (especially geometric types from nalgebra like `Isometry3`) into another algebraic type which equivalent but more general. For example,
    `let sim: Similarity3<_> = na::convert(isometry)` will convert an `Isometry3` into a `Similarity3`.
    In addition, `let mat: Mat4 = glm::convert(isometry)` will convert an `Isometry3` to a 4x4 matrix. This will also convert the scalar types,
    therefore: `let mat: DMat4 = glm::convert(m)` where `m: Mat4` will work. However, conversion will not work the other way round: you
    can't convert a `Matrix4` to an `Isometry3` using `glm::convert` because that could cause unexpected results if the matrix does
    not complies to the requirements of the isometry.
        2. If you need this kind of conversions anyway, you can use `try_convert` which will test if the object being converted complies with the algebraic requirements of the target type.
        This will return `None` if the requirements are not satisfied.
        3. The `convert_unchecked` will ignore those tests and always perform the conversion, even if that breaks the invariants of the target type.
        This must be used with care! This is actually the recommended method to convert between homogeneous transformations generated by `nalgebra-glm` and
        specific transformation types from **nalgebra** like `Isometry3`. Just be careful you know your conversions make sense.

    ### Should I use nalgebra or nalgebra-glm?
    Well that depends on your tastes and your background. **nalgebra** is more powerful overall since it allows stronger typing,
    and goes much further than simple computer graphics math. However, has a bit of a learning curve for
    those not used to the abstract mathematical concepts for transformations. **nalgebra-glm** however
    have more straightforward functions and benefit from the various tutorials existing on the internet
    for the original C++ GLM library.

    Overall, if you are already used to the C++ GLM library, or to working with homogeneous coordinates (like 4D
    matrices for 3D transformations), then you will have more success with **nalgebra-glm**. If on the other
    hand you prefer more rigorous treatments of transformations, with type-level restrictions, then go for **nalgebra**.
    If you need dynamically-sized matrices, you should go for **nalgebra** as well.

    Keep in mind that **nalgebra-glm** is just a different API for **nalgebra**. So you can very well use both
    and benefit from both their advantages: use **nalgebra-glm** when mathematical rigor is not that important,
    and **nalgebra** itself when you need more expressive types, and more powerful linear algebra operations like
    matrix factorizations and slicing. Just remember that all the **nalgebra-glm** types are just aliases to **nalgebra** types,
    and keep in mind it is possible to convert, e.g., an `Isometry3` to a `Mat4` and vice-versa (see the [conversions section](#conversions)).
 */

extern crate num_traits as num;
#[macro_use]
extern crate approx;
extern crate alga;
extern crate nalgebra as na;

pub use aliases::*;
pub use constructors::*;
pub use common::{abs, ceil, clamp, clamp_scalar, clamp_vec, float_bits_to_int, float_bits_to_int_vec, float_bits_to_uint, float_bits_to_uint_vec, floor, fract, int_bits_to_float, int_bits_to_float_vec, mix, modf, modf_vec, round, sign, smoothstep, step, step_scalar, step_vec, trunc, uint_bits_to_float, uint_bits_to_float_scalar};
pub use geometric::{reflect_vec, cross, distance, dot, faceforward, length, magnitude, normalize, refract_vec};
pub use matrix::{transpose, determinant, inverse, matrix_comp_mult, outer_product};
pub use traits::{Dimension, Number, Alloc};
pub use trigonometric::{acos, acosh, asin, asinh, atan, atan2, atanh, cos, cosh, degrees, radians, sin, sinh, tan, tanh};
pub use vector_relational::{all, any, equal, greater_than, greater_than_equal, less_than, less_than_equal, not, not_equal};
pub use exponential::{exp, exp2, inversesqrt, log, log2, pow, sqrt};

pub use gtx::{
    comp_add, comp_max, comp_min, comp_mul,
    cross2d,
    left_handed, right_handed,
    matrix_cross, matrix_cross3,
    diagonal2x2, diagonal2x3, diagonal2x4, diagonal3x2, diagonal3x3, diagonal3x4, diagonal4x2, diagonal4x3, diagonal4x4,
    distance2, l1_distance, l1_norm, l2_distance, l2_norm, length2, magnitude2,
    triangle_normal,
    fast_normalize_dot, normalize_dot,
    quat_rotate_normalized_axis, rotate_normalized_axis,
    orientation, rotate_vec2, rotate_vec3, rotate_vec4, rotate_x_vec4, rotate_x_vec3, rotate_y_vec4, rotate_y_vec3, rotate_z_vec4, rotate_z_vec3, slerp,
    rotation, scaling, translation, rotation2d, scaling2d, translation2d,
    proj, proj2d, reflect, reflect2d, scale_bias, scale_bias_matrix, shear2d_x, shear_x, shear_y, shear2d_y, shear_z,
    rotate2d, scale2d, translate2d,
    angle,
    are_collinear, are_collinear2d, are_orthogonal, is_comp_null, is_normalized, is_null,
    quat_to_mat3, quat_rotate_vec, quat_cross_vec, mat3_to_quat, quat_extract_real_component, quat_fast_mix, quat_inv_cross_vec, quat_length2, quat_magnitude2, quat_identity, quat_rotate_vec3, quat_rotation, quat_short_mix, quat_to_mat4, to_quat
};
pub use gtc::{
    e, two_pi, euler, four_over_pi, golden_ratio, half_pi, ln_ln_two, ln_ten, ln_two, one, one_over_pi, one_over_root_two, one_over_two_pi, quarter_pi, root_five, root_half_pi, root_ln_four, root_pi, root_three, root_two, root_two_pi, third, three_over_two_pi, two_over_pi, two_over_root_pi, two_thirds, zero,
    column, row, set_column, set_row,
    affine_inverse, inverse_transpose,
    make_mat2, make_mat2x2, make_mat2x3, make_mat2x4, make_mat3, make_mat3x2, make_mat3x3, make_mat3x4, make_mat4, make_mat4x2, make_mat4x3, make_mat4x4, make_quat, make_vec1, make_vec2, make_vec3, make_vec4, value_ptr, value_ptr_mut, vec1_to_vec2, vec1_to_vec3, vec1_to_vec4, vec2_to_vec1, vec2_to_vec2, vec2_to_vec3, vec2_to_vec4, vec3_to_vec1, vec3_to_vec2, vec3_to_vec3, vec3_to_vec4, vec4_to_vec1, vec4_to_vec2, vec4_to_vec3, vec4_to_vec4, mat2_to_mat3, mat2_to_mat4, mat3_to_mat2, mat3_to_mat4, mat4_to_mat2, mat4_to_mat3,
    quat_cast, quat_euler_angles, quat_greater_than, quat_greater_than_equal, quat_less_than, quat_less_than_equal, quat_look_at, quat_look_at_lh, quat_look_at_rh, quat_pitch, quat_roll, quat_yaw
};
pub use ext::{
    ortho, perspective,
    pick_matrix, project, project_no, project_zo, unproject, unproject_no, unproject_zo,
    equal_columns, equal_columns_eps, equal_columns_eps_vec, not_equal_columns, not_equal_columns_eps, not_equal_columns_eps_vec,
    identity, look_at, look_at_lh, rotate, scale, look_at_rh, translate, rotate_x, rotate_y, rotate_z,
    max3_scalar, max4_scalar, min3_scalar, min4_scalar,
    epsilon, pi,
    max, max2, max3, max4, min, min2, min3, min4,
    equal_eps, equal_eps_vec, not_equal_eps, not_equal_eps_vec,
    quat_conjugate, quat_inverse, quat_lerp, quat_slerp,
    quat_cross, quat_dot, quat_length, quat_magnitude, quat_normalize,
    quat_equal, quat_equal_eps, quat_not_equal, quat_not_equal_eps,
    quat_exp, quat_log, quat_pow, quat_rotate,
    quat_angle, quat_angle_axis, quat_axis
};

pub use na::{convert, convert_ref, convert_unchecked, convert_ref_unchecked, try_convert, try_convert_ref};

mod aliases;
mod constructors;
mod common;
mod matrix;
mod geometric;
mod traits;
mod trigonometric;
mod vector_relational;
mod exponential;
//mod integer;
//mod packing;

mod ext;
mod gtc;
mod gtx;<commit_msg>Reexport more nalgebra types and traits.<commit_after>/*! # nalgebra-glm − nalgebra in _easy mode_
    **nalgebra-glm** is a GLM-like interface for the **nalgebra** general-purpose linear algebra library.
    [GLM](https://glm.g-truc.net) itself is a popular C++ linear algebra library essentially targeting computer graphics. Therefore
    **nalgebra-glm** draws inspiration from GLM to define a nice and easy-to-use API for simple graphics application.

    All the types of **nalgebra-glm** are aliases of types from **nalgebra**. Therefore there is a complete and
    seamless inter-operability between both.

    ## Getting started
    First of all, you should start by taking a look at the official [GLM API documentation](http://glm.g-truc.net/0.9.9/api/index.html)
    since **nalgebra-glm** implements a large subset of it. To use **nalgebra-glm** to your project, you
    should add it as a dependency to your `Crates.toml`:

    ```toml
    [dependencies]
    nalgebra-glm = "0.1"
    ```

    Then, you should add an `extern crate` statement to your `lib.rs` or `main.rs` file. It is **strongly
    recommended** to add a crate alias to `glm` as well so that you will be able to call functions of
    **nalgebra-glm** using the module prefix `glm::`. For example you will write `glm::rotate(...)` instead
    of the more verbose `nalgebra_glm::rotate(...)`:

    ```rust
    extern crate nalgebra_glm as glm;
    ```

    ## Features overview
    **nalgebra-glm** supports most linear-algebra related features of the C++ GLM library. Mathematically
    speaking, it supports all the common transformations like rotations, translations, scaling, shearing,
    and projections but operating in homogeneous coordinates. This means all the 2D transformations are
    expressed as 3x3 matrices, and all the 3D transformations as 4x4 matrices. This is less computationally-efficient
    and memory-efficient than nalgebra's [transformation types](https://www.nalgebra.org/points_and_transformations/#transformations),
    but this has the benefit of being simpler to use.
    ### Main differences compared to GLM
    While **nalgebra-glm** follows the feature line of the C++ GLM library, quite a few differences
    remain and they are mostly syntactic. The main ones are:
    * All function names use `snake_case`, which is the Rust convention.
    * All type names use `CamelCase`, which is the Rust convention.
    * All function arguments, except for scalars, are all passed by-reference.
    * Some feature are not yet implemented and should be added in the future. In particular, no packing
    functions are available.
    * A few features are not implemented and will never be. This includes functions related to color
    spaces, and closest points computations. Other crates should be used for those. For example, closest
    points computation can be handled by the [ncollide](https://ncollide.org) project.

    In addition, because Rust does not allows function overloading, all functions must be given a unique name.
    Here are a few rules chosen arbitrarily for **nalgebra-glm**:
    * Functions operating in 2d will usually end with the `2d` suffix, e.g., `glm::rotade2d` is for 2D while `glm::rotate` is for 3D.
    * Functions operating on vector will often end with the `_vec` suffix, possibly followed by the dimension of vector, e.g., `glm::rotate_vec2`.
    * Every function related to quaternions start with the `quat_` prefix, e.g., `glm::quat_dot(q1, q2)`.
    * All the conversion functions have unique names as described [bellow](#conversions).
    ### Vector and matrix construction
    Vectors, matrices, and quaternions can be constructed using several approaches:
    * Using functions with the same name as their type in lower-case. For example `glm::vec3(x, y, z)` will create a 3D vector.
    * Using the `::new` constructor. For example `Vec3::new(x, y, z)` will create a 3D vector.
    * Using the functions prefixed by `make_` to build a vector a matrix from a slice. For example `glm::make_vec3(&[x, y, z])` will create a 3D vector.
    Keep in mind that constructing a matrix using this type of funcitons require its components to be arrange in column-major order on the slice.
    * Using a geometric construction function. For example `glm::rotation(angle, axis)` will build a 4x4 homogeneous rotation matrix from an angle (in radians) and an axis.
    * Using swizzling and conversions as described in the next sections.
    ### Swizzling
    Vector swizzling is a native feature of **nalgebra** itself. Therefore, you can use it with all
    the vectors of **nalgebra-glm** as well. Swizzling is supported as methods and works only up to
    dimension 3, i.e., you can only refer to the components `x`, `y` and `z` and can only create a
    2D or 3D vector using this technique. Here is some examples, assuming `v` is a vector with float
    components here:
    * `v.xx()` is equivalent to `glm::vec2(v.x, v.x)` and to `Vec2::new(v.x, v.x)`.
    * `v.zx()` is equivalent to `glm::vec2(v.z, v.x)` and to `Vec2::new(v.z, v.x)`.
    * `v.yxz()` is equivalent to `glm::vec3(v.y, v.x, v.z)` and to `Vec3::new(v.y, v.x, v.z)`.
    * `v.zzy()` is equivalent to `glm::vec3(v.z, v.z, v.y)` and to `Vec3::new(v.z, v.z, v.y)`.

    Any combination of two or three components picked among `x`, `y`, and `z` will work.
    ### Conversions
    It is often useful to convert one algebraic type to another. There are two main approaches for converting
    between types in `nalgebra-glm`:
    * Using function with the form `type1_to_type2` in order to convert an instance of `type1` into an instance of `type2`.
    For example `glm::mat3_to_mat4(m)` will convert the 3x3 matrix `m` to a 4x4 matrix by appending one column on the right
    and one row on the left. Those now row and columns are filled with 0 except for the diagonal element which is set to 1.
    * Using one of the `convert`, `try_convert`, or `convert_unchecked` functions.
    These functions are directly re-exported from nalgebra and are extremely versatile:
        1. The `convert` function can convert any type (especially geometric types from nalgebra like `Isometry3`) into another algebraic type which equivalent but more general. For example,
    `let sim: Similarity3<_> = na::convert(isometry)` will convert an `Isometry3` into a `Similarity3`.
    In addition, `let mat: Mat4 = glm::convert(isometry)` will convert an `Isometry3` to a 4x4 matrix. This will also convert the scalar types,
    therefore: `let mat: DMat4 = glm::convert(m)` where `m: Mat4` will work. However, conversion will not work the other way round: you
    can't convert a `Matrix4` to an `Isometry3` using `glm::convert` because that could cause unexpected results if the matrix does
    not complies to the requirements of the isometry.
        2. If you need this kind of conversions anyway, you can use `try_convert` which will test if the object being converted complies with the algebraic requirements of the target type.
        This will return `None` if the requirements are not satisfied.
        3. The `convert_unchecked` will ignore those tests and always perform the conversion, even if that breaks the invariants of the target type.
        This must be used with care! This is actually the recommended method to convert between homogeneous transformations generated by `nalgebra-glm` and
        specific transformation types from **nalgebra** like `Isometry3`. Just be careful you know your conversions make sense.

    ### Should I use nalgebra or nalgebra-glm?
    Well that depends on your tastes and your background. **nalgebra** is more powerful overall since it allows stronger typing,
    and goes much further than simple computer graphics math. However, has a bit of a learning curve for
    those not used to the abstract mathematical concepts for transformations. **nalgebra-glm** however
    have more straightforward functions and benefit from the various tutorials existing on the internet
    for the original C++ GLM library.

    Overall, if you are already used to the C++ GLM library, or to working with homogeneous coordinates (like 4D
    matrices for 3D transformations), then you will have more success with **nalgebra-glm**. If on the other
    hand you prefer more rigorous treatments of transformations, with type-level restrictions, then go for **nalgebra**.
    If you need dynamically-sized matrices, you should go for **nalgebra** as well.

    Keep in mind that **nalgebra-glm** is just a different API for **nalgebra**. So you can very well use both
    and benefit from both their advantages: use **nalgebra-glm** when mathematical rigor is not that important,
    and **nalgebra** itself when you need more expressive types, and more powerful linear algebra operations like
    matrix factorizations and slicing. Just remember that all the **nalgebra-glm** types are just aliases to **nalgebra** types,
    and keep in mind it is possible to convert, e.g., an `Isometry3` to a `Mat4` and vice-versa (see the [conversions section](#conversions)).
 */

extern crate num_traits as num;
#[macro_use]
extern crate approx;
extern crate alga;
extern crate nalgebra as na;

pub use aliases::*;
pub use constructors::*;
pub use common::{abs, ceil, clamp, clamp_scalar, clamp_vec, float_bits_to_int, float_bits_to_int_vec, float_bits_to_uint, float_bits_to_uint_vec, floor, fract, int_bits_to_float, int_bits_to_float_vec, mix, modf, modf_vec, round, sign, smoothstep, step, step_scalar, step_vec, trunc, uint_bits_to_float, uint_bits_to_float_scalar};
pub use geometric::{reflect_vec, cross, distance, dot, faceforward, length, magnitude, normalize, refract_vec};
pub use matrix::{transpose, determinant, inverse, matrix_comp_mult, outer_product};
pub use traits::{Dimension, Number, Alloc};
pub use trigonometric::{acos, acosh, asin, asinh, atan, atan2, atanh, cos, cosh, degrees, radians, sin, sinh, tan, tanh};
pub use vector_relational::{all, any, equal, greater_than, greater_than_equal, less_than, less_than_equal, not, not_equal};
pub use exponential::{exp, exp2, inversesqrt, log, log2, pow, sqrt};

pub use gtx::{
    comp_add, comp_max, comp_min, comp_mul,
    cross2d,
    left_handed, right_handed,
    matrix_cross, matrix_cross3,
    diagonal2x2, diagonal2x3, diagonal2x4, diagonal3x2, diagonal3x3, diagonal3x4, diagonal4x2, diagonal4x3, diagonal4x4,
    distance2, l1_distance, l1_norm, l2_distance, l2_norm, length2, magnitude2,
    triangle_normal,
    fast_normalize_dot, normalize_dot,
    quat_rotate_normalized_axis, rotate_normalized_axis,
    orientation, rotate_vec2, rotate_vec3, rotate_vec4, rotate_x_vec4, rotate_x_vec3, rotate_y_vec4, rotate_y_vec3, rotate_z_vec4, rotate_z_vec3, slerp,
    rotation, scaling, translation, rotation2d, scaling2d, translation2d,
    proj, proj2d, reflect, reflect2d, scale_bias, scale_bias_matrix, shear2d_x, shear_x, shear_y, shear2d_y, shear_z,
    rotate2d, scale2d, translate2d,
    angle,
    are_collinear, are_collinear2d, are_orthogonal, is_comp_null, is_normalized, is_null,
    quat_to_mat3, quat_rotate_vec, quat_cross_vec, mat3_to_quat, quat_extract_real_component, quat_fast_mix, quat_inv_cross_vec, quat_length2, quat_magnitude2, quat_identity, quat_rotate_vec3, quat_rotation, quat_short_mix, quat_to_mat4, to_quat
};
pub use gtc::{
    e, two_pi, euler, four_over_pi, golden_ratio, half_pi, ln_ln_two, ln_ten, ln_two, one, one_over_pi, one_over_root_two, one_over_two_pi, quarter_pi, root_five, root_half_pi, root_ln_four, root_pi, root_three, root_two, root_two_pi, third, three_over_two_pi, two_over_pi, two_over_root_pi, two_thirds, zero,
    column, row, set_column, set_row,
    affine_inverse, inverse_transpose,
    make_mat2, make_mat2x2, make_mat2x3, make_mat2x4, make_mat3, make_mat3x2, make_mat3x3, make_mat3x4, make_mat4, make_mat4x2, make_mat4x3, make_mat4x4, make_quat, make_vec1, make_vec2, make_vec3, make_vec4, value_ptr, value_ptr_mut, vec1_to_vec2, vec1_to_vec3, vec1_to_vec4, vec2_to_vec1, vec2_to_vec2, vec2_to_vec3, vec2_to_vec4, vec3_to_vec1, vec3_to_vec2, vec3_to_vec3, vec3_to_vec4, vec4_to_vec1, vec4_to_vec2, vec4_to_vec3, vec4_to_vec4, mat2_to_mat3, mat2_to_mat4, mat3_to_mat2, mat3_to_mat4, mat4_to_mat2, mat4_to_mat3,
    quat_cast, quat_euler_angles, quat_greater_than, quat_greater_than_equal, quat_less_than, quat_less_than_equal, quat_look_at, quat_look_at_lh, quat_look_at_rh, quat_pitch, quat_roll, quat_yaw
};
pub use ext::{
    ortho, perspective,
    pick_matrix, project, project_no, project_zo, unproject, unproject_no, unproject_zo,
    equal_columns, equal_columns_eps, equal_columns_eps_vec, not_equal_columns, not_equal_columns_eps, not_equal_columns_eps_vec,
    identity, look_at, look_at_lh, rotate, scale, look_at_rh, translate, rotate_x, rotate_y, rotate_z,
    max3_scalar, max4_scalar, min3_scalar, min4_scalar,
    epsilon, pi,
    max, max2, max3, max4, min, min2, min3, min4,
    equal_eps, equal_eps_vec, not_equal_eps, not_equal_eps_vec,
    quat_conjugate, quat_inverse, quat_lerp, quat_slerp,
    quat_cross, quat_dot, quat_length, quat_magnitude, quat_normalize,
    quat_equal, quat_equal_eps, quat_not_equal, quat_not_equal_eps,
    quat_exp, quat_log, quat_pow, quat_rotate,
    quat_angle, quat_angle_axis, quat_axis
};

pub use na::{convert, convert_ref, convert_unchecked, convert_ref_unchecked, try_convert, try_convert_ref};
pub use na::{Scalar, Real, DefaultAllocator, U1, U2, U3, U4};

mod aliases;
mod constructors;
mod common;
mod matrix;
mod geometric;
mod traits;
mod trigonometric;
mod vector_relational;
mod exponential;
//mod integer;
//mod packing;

mod ext;
mod gtc;
mod gtx;<|endoftext|>


<commit_before>use super::error::{Error, Result};
use super::generate::parse_grammar::GrammarJSON;
use std::ffi::{OsStr, OsString};
use std::fs;
use std::path::Path;
use std::process::Command;

pub fn compile_language_to_wasm(language_dir: &Path) -> Result<()> {
    let src_dir = language_dir.join("src");

    // Parse the grammar.json to find out the language name.
    let grammar_json_path = src_dir.join("grammar.json");
    let grammar_json = fs::read_to_string(&grammar_json_path).map_err(|e| {
        format!(
            "Failed to read grammar file {:?} - {}",
            grammar_json_path, e
        )
    })?;
    let grammar: GrammarJSON = serde_json::from_str(&grammar_json).map_err(|e| {
        format!(
            "Failed to parse grammar file {:?} - {}",
            grammar_json_path, e
        )
    })?;
    let output_filename = format!("tree-sitter-{}.wasm", grammar.name);

    // Get the current user id so that files created in the docker container will have
    // the same owner.
    let user_id_output = Command::new("id")
        .arg("-u")
        .output()
        .map_err(|e| format!("Failed to get get current user id {}", e))?;
    let user_id = String::from_utf8_lossy(&user_id_output.stdout);
    let user_id = user_id.trim();

    // Use `emscripten-slim` docker image with the parser directory mounted as a volume.
    let mut command = Command::new("docker");
    let mut volume_string = OsString::from(language_dir);
    volume_string.push(":/src");
    command.args(&["run", "--rm"]);
    command.args(&[OsStr::new("--volume"), &volume_string]);
    command.args(&["--user", user_id, "trzeci/emscripten-slim"]);

    // Run emscripten in the container
    command.args(&[
        "emcc",
        "-o",
        &output_filename,
        "-Os",
        "-s",
        "WASM=1",
        "-s",
        "SIDE_MODULE=1",
        "-s",
        &format!("EXPORTED_FUNCTIONS=[\"_tree_sitter_{}\"]", grammar.name),
        "-I",
        "src",
    ]);

    // Find source files to pass to emscripten
    let src_entries = fs::read_dir(&src_dir)
        .map_err(|e| format!("Failed to read source directory {:?} - {}", src_dir, e))?;

    for entry in src_entries {
        let entry = entry?;
        let file_name = entry.file_name();

        // Do not compile the node.js binding file.
        if file_name
            .to_str()
            .map_or(false, |s| s.starts_with("binding"))
        {
            continue;
        }

        // Compile any .c, .cc, or .cpp files
        if let Some(extension) = Path::new(&file_name).extension().and_then(|s| s.to_str()) {
            if extension == "c" || extension == "cc" || extension == "cpp" {
                command.arg(Path::new("src").join(entry.file_name()));
            }
        }
    }

    let output = command
        .output()
        .map_err(|e| format!("Failed to run docker emcc command - {}", e))?;
    if !output.status.success() {
        return Err(Error::from(format!(
            "emcc command failed - {}",
            String::from_utf8_lossy(&output.stderr)
        )));
    }

    // Move the created `.wasm` file into the current working directory.
    fs::rename(&language_dir.join(&output_filename), &output_filename)
        .map_err(|e| format!("Couldn't find output file {:?} - {}", output_filename, e))?;

    Ok(())
}
<commit_msg>Increase memory limit for language wasm files<commit_after>use super::error::{Error, Result};
use super::generate::parse_grammar::GrammarJSON;
use std::ffi::{OsStr, OsString};
use std::fs;
use std::path::Path;
use std::process::Command;

pub fn compile_language_to_wasm(language_dir: &Path) -> Result<()> {
    let src_dir = language_dir.join("src");

    // Parse the grammar.json to find out the language name.
    let grammar_json_path = src_dir.join("grammar.json");
    let grammar_json = fs::read_to_string(&grammar_json_path).map_err(|e| {
        format!(
            "Failed to read grammar file {:?} - {}",
            grammar_json_path, e
        )
    })?;
    let grammar: GrammarJSON = serde_json::from_str(&grammar_json).map_err(|e| {
        format!(
            "Failed to parse grammar file {:?} - {}",
            grammar_json_path, e
        )
    })?;
    let output_filename = format!("tree-sitter-{}.wasm", grammar.name);

    // Get the current user id so that files created in the docker container will have
    // the same owner.
    let user_id_output = Command::new("id")
        .arg("-u")
        .output()
        .map_err(|e| format!("Failed to get get current user id {}", e))?;
    let user_id = String::from_utf8_lossy(&user_id_output.stdout);
    let user_id = user_id.trim();

    // Use `emscripten-slim` docker image with the parser directory mounted as a volume.
    let mut command = Command::new("docker");
    let mut volume_string = OsString::from(language_dir);
    volume_string.push(":/src");
    command.args(&["run", "--rm"]);
    command.args(&[OsStr::new("--volume"), &volume_string]);
    command.args(&["--user", user_id, "trzeci/emscripten-slim"]);

    // Run emscripten in the container
    command.args(&[
        "emcc",
        "-o",
        &output_filename,
        "-Os",
        "-s",
        "WASM=1",
        "-s",
        "SIDE_MODULE=1",
        "-s",
        "TOTAL_MEMORY=33554432",
        "-s",
        &format!("EXPORTED_FUNCTIONS=[\"_tree_sitter_{}\"]", grammar.name),
        "-I",
        "src",
    ]);

    // Find source files to pass to emscripten
    let src_entries = fs::read_dir(&src_dir)
        .map_err(|e| format!("Failed to read source directory {:?} - {}", src_dir, e))?;

    for entry in src_entries {
        let entry = entry?;
        let file_name = entry.file_name();

        // Do not compile the node.js binding file.
        if file_name
            .to_str()
            .map_or(false, |s| s.starts_with("binding"))
        {
            continue;
        }

        // Compile any .c, .cc, or .cpp files
        if let Some(extension) = Path::new(&file_name).extension().and_then(|s| s.to_str()) {
            if extension == "c" || extension == "cc" || extension == "cpp" {
                command.arg(Path::new("src").join(entry.file_name()));
            }
        }
    }

    let output = command
        .output()
        .map_err(|e| format!("Failed to run docker emcc command - {}", e))?;
    if !output.status.success() {
        return Err(Error::from(format!(
            "emcc command failed - {}",
            String::from_utf8_lossy(&output.stderr)
        )));
    }

    // Move the created `.wasm` file into the current working directory.
    fs::rename(&language_dir.join(&output_filename), &output_filename)
        .map_err(|e| format!("Couldn't find output file {:?} - {}", output_filename, e))?;

    Ok(())
}
<|endoftext|>


<commit_before>use geom::point::Point2D;
use geom::size::Size2D;
use gfx::geometry::{au, px_to_au};
use libc::{c_void};
use font_library::FontLibrary;
use font::Font;
use glyph::Glyph;
use shaper::shape_text;

#[doc="A single, unbroken line of text."]
struct TextRun {
    priv glyphs: ~[Glyph],
    priv size_: Size2D<au>,
}

impl TextRun {
    fn size() -> Size2D<au> { self.size_ }
    fn preferred_width() -> au { self.size_.width }
}

fn TextRun(font: Font, text: ~str) -> TextRun {
    let glyphs = shape_text(&font, text);
    let size = glyph_run_size(glyphs);

    TextRun {
        size_: size,
        glyphs: shape_text(&font, text)
    }
}

fn glyph_run_size(glyphs: &[Glyph]) -> Size2D<au> {
    let height = px_to_au(20);
    let pen_start_x = px_to_au(0);
    let pen_start_y = height;
    let pen_start = Point2D(pen_start_x, pen_start_y);
    let pen_end = glyphs.foldl(pen_start, |cur, glyph| {
        Point2D(cur.x.add(glyph.pos.offset.x).add(glyph.pos.advance.x),
                cur.y.add(glyph.pos.offset.y).add(glyph.pos.advance.y))
    });
    return Size2D(pen_end.x, pen_end.y);
}

fn should_calculate_the_total_size() {
    #[test];
    #[ignore(cfg(target_os = "macos"))];

    let flib = FontLibrary();
    let font = flib.get_test_font();
    let run = TextRun(*font, ~"firecracker");
    let expected = Size2D(px_to_au(84), px_to_au(20));
    assert run.size() == expected;
}
<commit_msg>Add TextRun.min_width<commit_after>use geom::point::Point2D;
use geom::size::Size2D;
use gfx::geometry::{au, px_to_au};
use libc::{c_void};
use font_library::FontLibrary;
use font::Font;
use glyph::Glyph;
use shaper::shape_text;

#[doc="A single, unbroken line of text."]
struct TextRun {
    priv glyphs: ~[Glyph],
    priv size_: Size2D<au>,
    priv min_width_: au,
}

impl TextRun {
    fn size() -> Size2D<au> { self.size_ }
    fn preferred_width() -> au { self.size_.width }
    fn min_width() -> au { self.min_width_ }
}

fn TextRun(font: Font, text: ~str) -> TextRun {
    let glyphs = shape_text(&font, text);
    let size = glyph_run_size(glyphs);

    let min_width = match calc_min_width(&font, text) {
      Some(w) => w,
      None => size.width
    };

    TextRun {
        glyphs: shape_text(&font, text),
        size_: size,
        min_width_: min_width
    }
}

fn glyph_run_size(glyphs: &[Glyph]) -> Size2D<au> {
    let height = px_to_au(20);
    let pen_start_x = px_to_au(0);
    let pen_start_y = height;
    let pen_start = Point2D(pen_start_x, pen_start_y);
    let pen_end = glyphs.foldl(pen_start, |cur, glyph| {
        Point2D(cur.x.add(glyph.pos.offset.x).add(glyph.pos.advance.x),
                cur.y.add(glyph.pos.offset.y).add(glyph.pos.advance.y))
    });
    return Size2D(pen_end.x, pen_end.y);
}

/// If there are breaking opportunities inside a string, then
/// returns the width of the text up to the first break. Otherwise None.
fn calc_min_width(font: &Font, text: &str) -> Option<au> {
    match str::find(text, |c| char::is_whitespace(c)) {
      Some(offset) => {
        let short_text = str::view(text, 0, offset);
        let glyphs = shape_text(font, short_text);
        let size = glyph_run_size(glyphs);
        Some(size.width)
      }
      None => None
    }
}

#[test]
fn test_calc_min_width_with_breaking() {
    let flib = FontLibrary();
    let font = flib.get_test_font();
    let actual = calc_min_width(font, ~"firecracker yumyum");
    let expected = Some(px_to_au(84));
    assert expected == actual;
}

#[test]
fn test_calc_min_width_without_breaking() {
    let flib = FontLibrary();
    let font = flib.get_test_font();
    let actual = calc_min_width(font, ~"firecracker_yumyum");
    let expected = None;
    assert expected == actual;
}

fn should_calculate_the_total_size() {
    #[test];
    #[ignore(cfg(target_os = "macos"))];

    let flib = FontLibrary();
    let font = flib.get_test_font();
    let run = TextRun(*font, ~"firecracker");
    let expected = Size2D(px_to_au(84), px_to_au(20));
    assert run.size() == expected;
}

<|endoftext|>


<commit_before>/// Provide syntax highlighting via [Syntect].
///
/// [Syntect]: https://docs.rs/syntect/1.0.0/syntect/

// Standard library
use std::default::Default;
use std::str;

// Third party
use quick_xml::{XmlReader, XmlWriter, Element, Event};
use syntect::html::highlighted_snippet_for_string;
use syntect::highlighting::ThemeSet;
use syntect::parsing::SyntaxSet;

/// A `Language` is a `String` representing a code highlighting language.
type Language = String;

/// Define XML parsing states of interest for highlighting.
///
/// We use these in conjunction with `ParseEvent`s to drive a state machine
/// which tracks whether a stream of `quick_xml::Event`s indicates the presence
/// of a code block to be highlighted.
#[derive(Clone, Debug, PartialEq)]
enum ParseState {
    /// Not in a code block at all.
    NotInBlock,
    /// Possibly the start of a highlight block, with the language as payload.
    MaybeStartBlock(Language),
    /// The start of the code block has been confirmed; highlight next text node.
    WillStartCodeBlock(Language),
    /// In a code block; use the content for highlighting.
    InCodeBlock(Language),
}

/// Define XML parsing events of interest for highlighting.
///
/// We don't care at all about any other items for this specific scenario; we
/// use these in conjunction with the `ParseState` to drive the state  machine
/// described there.
#[derive(Debug)]
enum ParseEvent {
    // Starting a `<pre>`; optionally specifying a language.
    StartPre(Option<Language>),
    // Starting a `<code>`.
    StartCode,
    // Ending a `<code>`.
    EndCode,
    Text,
    Whitespace,
    Other,
}

impl ParseState {
    /// Get the next `ParseState` given current `ParseState` and a `ParseEvent`.
    fn next(self, event: ParseEvent) -> ParseState {
        use self::ParseState::*;
        use self::ParseEvent::*;

        match (self, event) {
            (NotInBlock, StartPre(Some(language))) => MaybeStartBlock(language),
            (MaybeStartBlock(language), Whitespace) => MaybeStartBlock(language),
            (MaybeStartBlock(language), StartCode) => WillStartCodeBlock(language),
            (WillStartCodeBlock(language), Text) |
            (WillStartCodeBlock(language), Whitespace) => InCodeBlock(language),
            (InCodeBlock(_), EndCode) => NotInBlock,
            (_, _) => NotInBlock,
        }
    }
}

impl Default for ParseState {
    fn default() -> ParseState {
        ParseState::NotInBlock
    }
}

impl<'e> From<&'e Event> for ParseEvent {
    fn from(event: &'e Event) -> ParseEvent {
        // TODO @1.15: remove `'static`.
        const PRE: &'static [u8] = b"pre";
        const CODE: &'static [u8] = b"code";
        const CLASS: &'static [u8] = b"class";
        const WHITE_SPACE: &'static [u8] = b"";

        match event {
            &Event::Start(ref element) => {
                match element.name() {
                    PRE => {
                        let maybe_class_attr = element.attributes()
                            .map(|attr| attr.unwrap())
                            .filter(|attr| attr.0 == CLASS)
                            .next();

                        match maybe_class_attr {
                            Some(class_attr) => {
                                match str::from_utf8(class_attr.1) {
                                    Ok(lang) => ParseEvent::StartPre(Some(lang.to_string())),
                                    Err(_) => ParseEvent::StartPre(None),
                                }
                            }
                            None => ParseEvent::StartPre(None),
                        }
                    }
                    CODE => ParseEvent::StartCode,
                    _ => ParseEvent::Other,
                }
            }
            &Event::End(ref element) => {
                match element.name() {
                    CODE => ParseEvent::EndCode,
                    _ => ParseEvent::Other,
                }
            }
            &Event::Text(ref element) => {
                match element.name() {
                    WHITE_SPACE => ParseEvent::Whitespace,
                    _ => ParseEvent::Text,
                }
            }
            _ => ParseEvent::Other,
        }
    }
}

struct Accumulator {
    writer: XmlWriter<Vec<u8>>,
    state: ParseState,
}

/// Highlight all code blocks in a block of HTML.
///
/// Assumes that the blocks to be highlighted are in the following basic format:
///
/// ```html
/// <pre class="rust">
///   <code>
///     pub fn syntax_highlight(html_string: String) -> String {
///         // implementation details
///     }
///   </code>
/// </pre>
/// ```
///
/// The `class` attribute value from the `pre` tag defines the language used to
/// highlight the code.
///
/// Note that any `html_string` will do; if it cannot be parsed as XML, it will
/// simply be returned unchanged; and if there are no code blocks to highlight,
/// it will also be returned unchanged.
pub fn syntax_highlight(html_string: String) -> String {
    let original_string = html_string.clone();
    let reader = XmlReader::from(html_string.as_str());

    let accumulator = Accumulator {
        writer: XmlWriter::new(Vec::<u8>::new()),
        state: ParseState::default(),
    };

    let final_state = reader.fold(accumulator, |mut acc, event| {
        let event = match event {
            Ok(event) => event,
            Err(_) => {
                return acc;
            }
        };

        let parse_event = ParseEvent::from(&event);
        acc.state = ParseState::next(acc.state, parse_event);

        let language = match acc.state.clone() {
            ParseState::InCodeBlock(language) => language,
            _ => {
                assert!(acc.writer.write(event.clone()).is_ok());
                return acc;
            }
        };

        let unescaped_content = match event.element().unescaped_content() {
            Ok(content) => content.into_owned(),
            Err(_) => {
                assert!(acc.writer.write(event.clone()).is_ok());
                return acc;
            }
        };

        let content_to_highlight = match str::from_utf8(&unescaped_content) {
            Ok(utf8_str) => utf8_str,
            Err(_) => {
                assert!(acc.writer.write(event.clone()).is_ok());
                return acc;
            },
        };

        let ss = SyntaxSet::load_defaults_nonewlines();
        let syntax = ss.find_syntax_by_token(&language);
        let valid_syntax = match syntax {
            Some(valid_syntax) => valid_syntax,
            None => {
                assert!(acc.writer.write(event.clone()).is_ok());
                return acc;
            }
        };

        let highlighted = highlighted_snippet_for_string(
            content_to_highlight,
            valid_syntax,
            &ThemeSet::load_defaults().themes["base16-eighties.dark"]
        );

        let text = Element::new(highlighted);
        assert!(acc.writer.write(Event::Text(text)).is_ok());

        acc
    });

    String::from_utf8(final_state.writer.into_inner()).unwrap_or(original_string)
}

#[cfg(test)]
mod tests {

    #[test]
    fn test_parse_state() {
        use super::ParseState;
        use super::ParseEvent;

        let lang = "rust";

        assert_eq!(ParseState::NotInBlock.next(ParseEvent::StartPre(Some(lang.to_string()))),
                   ParseState::MaybeStartBlock(lang.to_string()));

        assert_eq!(ParseState::NotInBlock.next(ParseEvent::EndCode),
                   ParseState::NotInBlock);

        assert_eq!(ParseState::NotInBlock.next(ParseEvent::Other),
                   ParseState::NotInBlock);

        assert_eq!(ParseState::NotInBlock.next(ParseEvent::StartCode),
                   ParseState::NotInBlock);

        assert_eq!(ParseState::MaybeStartBlock(lang.to_string()).next(ParseEvent::StartCode),
                   ParseState::WillStartCodeBlock(lang.to_string()));

        assert_eq!(ParseState::MaybeStartBlock(lang.to_string()).next(ParseEvent::Text),
                   ParseState::NotInBlock);

        assert_eq!(ParseState::MaybeStartBlock(lang.to_string()).next(ParseEvent::EndCode),
                   ParseState::NotInBlock);

        assert_eq!(ParseState::MaybeStartBlock(lang.to_string())
                       .next(ParseEvent::StartPre(Some(lang.to_string()))),
                   ParseState::NotInBlock);

        assert_eq!(ParseState::MaybeStartBlock(lang.to_string()).next(ParseEvent::Other),
                   ParseState::NotInBlock);

        assert_eq!(ParseState::WillStartCodeBlock(lang.to_string()).next(ParseEvent::Text),
                   ParseState::InCodeBlock(lang.to_string()));
    }
}
<commit_msg>Improve syntax highlighting event stream parsing.<commit_after>/// Provide syntax highlighting via [Syntect].
///
/// [Syntect]: https://docs.rs/syntect/1.0.0/syntect/

// Standard library
use std::default::Default;
use std::str;

// Third party
use quick_xml::{XmlReader, XmlWriter, Element, Event};
use syntect::html::highlighted_snippet_for_string;
use syntect::highlighting::ThemeSet;
use syntect::parsing::SyntaxSet;

/// A `Language` is a `String` representing a code highlighting language.
type Language = String;

/// Define XML parsing states of interest for highlighting.
///
/// We use these in conjunction with `ParseEvent`s to drive a state machine
/// which tracks whether a stream of `quick_xml::Event`s indicates the presence
/// of a code block to be highlighted.
#[derive(Clone, Debug, PartialEq)]
enum ParseState {
    /// Not in a code block at all.
    NotInBlock,
    /// Possibly the start of a highlight block, with the language as payload.
    MaybeStartBlock(Language),
    /// The start of the code block has been confirmed; highlight next text node.
    WillStartCodeBlock(Language),
    /// In a code block; use the content for highlighting.
    InCodeBlock(Language),
}

/// Define XML parsing events of interest for highlighting.
///
/// We don't care at all about any other items for this specific scenario; we
/// use these in conjunction with the `ParseState` to drive the state  machine
/// described there.
#[derive(Debug)]
enum ParseEvent {
    // Starting a `<pre>`; optionally specifying a language.
    StartPre(Option<Language>),
    // Starting a `<code>`.
    StartCode,
    // Ending a `<code>`.
    EndCode,
    Text,
    Whitespace,
    Other,
}

impl ParseState {
    /// Get the next `ParseState` given current `ParseState` and a `ParseEvent`.
    fn next(self, event: ParseEvent) -> ParseState {
        use self::ParseState::*;
        use self::ParseEvent::*;

        match (self, event) {
            (NotInBlock, StartPre(Some(language))) => MaybeStartBlock(language),
            (MaybeStartBlock(language), Whitespace) => MaybeStartBlock(language),
            (MaybeStartBlock(language), StartCode) => WillStartCodeBlock(language),
            (WillStartCodeBlock(language), Text) |
            (WillStartCodeBlock(language), Whitespace) => InCodeBlock(language),
            (InCodeBlock(_), EndCode) => NotInBlock,
            (_, _) => NotInBlock,
        }
    }
}

impl Default for ParseState {
    fn default() -> ParseState {
        ParseState::NotInBlock
    }
}

impl<'e> From<&'e Event> for ParseEvent {
    fn from(event: &'e Event) -> ParseEvent {
        // TODO @1.15: remove `'static`.
        const PRE: &'static [u8] = b"pre";
        const CODE: &'static [u8] = b"code";
        const CLASS: &'static [u8] = b"class";
        const WHITE_SPACE: &'static [u8] = b"";

        match *event {
            Event::Start(ref element) => {
                match element.name() {
                    PRE => {
                        let maybe_class_attr = element.attributes()
                                                      .map(|attr| attr.unwrap())
                                                      .filter(|&(attr, _value)| attr == CLASS)
                                                      .next();

                        if let Some((_attr, value)) = maybe_class_attr {
                            match str::from_utf8(value) {
                                Ok(lang) => ParseEvent::StartPre(Some(lang.to_string())),
                                Err(_) => ParseEvent::StartPre(None),
                            }
                        } else {
                            ParseEvent::StartPre(None)
                        }
                    }
                    CODE => ParseEvent::StartCode,
                    _ => ParseEvent::Other,
                }
            }
            Event::End(ref element) => {
                match element.name() {
                    CODE => ParseEvent::EndCode,
                    _ => ParseEvent::Other,
                }
            }
            Event::Text(ref element) => {
                match element.name() {
                    WHITE_SPACE => ParseEvent::Whitespace,
                    _ => ParseEvent::Text,
                }
            }
            _ => ParseEvent::Other,
        }
    }
}

struct Accumulator {
    writer: XmlWriter<Vec<u8>>,
    state: ParseState,
}

/// Highlight all code blocks in a block of HTML.
///
/// Assumes that the blocks to be highlighted are in the following basic format:
///
/// ```html
/// <pre class="rust">
///   <code>
///     pub fn syntax_highlight(html_string: String) -> String {
///         // implementation details
///     }
///   </code>
/// </pre>
/// ```
///
/// The `class` attribute value from the `pre` tag defines the language used to
/// highlight the code.
///
/// Note that any `html_string` will do; if it cannot be parsed as XML, it will
/// simply be returned unchanged; and if there are no code blocks to highlight,
/// it will also be returned unchanged.
pub fn syntax_highlight(html_string: String) -> String {
    let original_string = html_string.clone();
    let reader = XmlReader::from(html_string.as_str());

    let accumulator = Accumulator {
        writer: XmlWriter::new(Vec::<u8>::new()),
        state: ParseState::default(),
    };

    let final_state = reader.fold(accumulator, |mut acc, event| {
        let event = match event {
            Ok(event) => event,
            Err(_) => {
                return acc;
            }
        };

        let parse_event = ParseEvent::from(&event);
        acc.state = ParseState::next(acc.state, parse_event);

        let language = match acc.state.clone() {
            ParseState::InCodeBlock(language) => language,
            _ => {
                assert!(acc.writer.write(event.clone()).is_ok());
                return acc;
            }
        };

        let unescaped_content = match event.element().unescaped_content() {
            Ok(content) => content.into_owned(),
            Err(_) => {
                assert!(acc.writer.write(event.clone()).is_ok());
                return acc;
            }
        };

        let content_to_highlight = match str::from_utf8(&unescaped_content) {
            Ok(utf8_str) => utf8_str,
            Err(_) => {
                assert!(acc.writer.write(event.clone()).is_ok());
                return acc;
            },
        };

        let ss = SyntaxSet::load_defaults_nonewlines();
        let syntax = ss.find_syntax_by_token(&language);
        let valid_syntax = match syntax {
            Some(valid_syntax) => valid_syntax,
            None => {
                assert!(acc.writer.write(event.clone()).is_ok());
                return acc;
            }
        };

        let highlighted = highlighted_snippet_for_string(
            content_to_highlight,
            valid_syntax,
            &ThemeSet::load_defaults().themes["base16-eighties.dark"]
        );

        let text = Element::new(highlighted);
        assert!(acc.writer.write(Event::Text(text)).is_ok());

        acc
    });

    String::from_utf8(final_state.writer.into_inner()).unwrap_or(original_string)
}

#[cfg(test)]
mod tests {

    #[test]
    fn test_parse_state() {
        use super::ParseState;
        use super::ParseEvent;

        let lang = "rust";

        assert_eq!(ParseState::NotInBlock.next(ParseEvent::StartPre(Some(lang.to_string()))),
                   ParseState::MaybeStartBlock(lang.to_string()));

        assert_eq!(ParseState::NotInBlock.next(ParseEvent::EndCode),
                   ParseState::NotInBlock);

        assert_eq!(ParseState::NotInBlock.next(ParseEvent::Other),
                   ParseState::NotInBlock);

        assert_eq!(ParseState::NotInBlock.next(ParseEvent::StartCode),
                   ParseState::NotInBlock);

        assert_eq!(ParseState::MaybeStartBlock(lang.to_string()).next(ParseEvent::StartCode),
                   ParseState::WillStartCodeBlock(lang.to_string()));

        assert_eq!(ParseState::MaybeStartBlock(lang.to_string()).next(ParseEvent::Text),
                   ParseState::NotInBlock);

        assert_eq!(ParseState::MaybeStartBlock(lang.to_string()).next(ParseEvent::EndCode),
                   ParseState::NotInBlock);

        assert_eq!(ParseState::MaybeStartBlock(lang.to_string())
                       .next(ParseEvent::StartPre(Some(lang.to_string()))),
                   ParseState::NotInBlock);

        assert_eq!(ParseState::MaybeStartBlock(lang.to_string()).next(ParseEvent::Other),
                   ParseState::NotInBlock);

        assert_eq!(ParseState::WillStartCodeBlock(lang.to_string()).next(ParseEvent::Text),
                   ParseState::InCodeBlock(lang.to_string()));
    }
}
<|endoftext|>


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

use crate::ast::{Ast, AstBuilder, AstKey, ObjectEntry, Primitive, RequestParameters};
use crate::build_ast::{
    build_fragment, build_operation, build_request, build_request_params,
    build_request_params_ast_key,
};
use crate::constants::CODEGEN_CONSTANTS;
use crate::indentation::print_indentation;
use crate::utils::escape;

use graphql_ir::{FragmentDefinition, OperationDefinition};
use schema::Schema;

use fnv::{FnvBuildHasher, FnvHashSet};
use indexmap::IndexMap;
use interner::StringKey;
use std::fmt::{Result as FmtResult, Write};

pub struct Printer {
    builder: AstBuilder,
}

type VariableDefinitions = IndexMap<AstKey, String, FnvBuildHasher>;
struct DedupedJSONPrinter<'b> {
    variable_definitions: VariableDefinitions,
    duplicates: FnvHashSet<AstKey>,
    builder: &'b AstBuilder,
    root_key: AstKey,
}

impl<'b> DedupedJSONPrinter<'b> {
    fn new(builder: &'b AstBuilder, root_key: AstKey) -> Self {
        let mut visited = Default::default();
        let mut duplicates = Default::default();
        Self::collect_value_duplicates(&mut visited, &mut duplicates, builder, root_key);
        Self {
            variable_definitions: Default::default(),
            duplicates,
            builder,
            root_key,
        }
    }

    fn new_without_dedupe(builder: &'b AstBuilder, root_key: AstKey) -> Self {
        Self {
            variable_definitions: Default::default(),
            duplicates: Default::default(),
            builder,
            root_key,
        }
    }

    /// We don't dedupe in an already deduped AST unless the duplicate
    /// also appears on a subtree that's not a duplicate
    /// Input:
    /// [
    ///     [{a: 1}, {b:2}],
    ///     [{a: 1}, {b:2}],
    ///     {b: 2}
    /// ]
    /// Output:
    /// v0 = {b: 2};
    /// v1 = [{a: 1}, v0];
    fn collect_value_duplicates(
        visited: &mut FnvHashSet<AstKey>,
        duplicates: &mut FnvHashSet<AstKey>,
        builder: &AstBuilder,
        key: AstKey,
    ) {
        match builder.lookup(key) {
            Ast::Array(array) => {
                if array.is_empty() {
                    return;
                }
                if !visited.insert(key) {
                    duplicates.insert(key);
                    return;
                }
                for val in array {
                    if let Primitive::Key(key) = val {
                        Self::collect_value_duplicates(visited, duplicates, builder, *key);
                    }
                }
            }
            Ast::Object(object) => {
                if object.is_empty() {
                    return;
                }
                if !visited.insert(key) {
                    duplicates.insert(key);
                    return;
                }
                for entry in object {
                    if let Primitive::Key(key) = entry.value {
                        Self::collect_value_duplicates(visited, duplicates, builder, key);
                    }
                }
            }
        }
    }

    fn print(mut self) -> String {
        let mut result = String::new();
        self.print_ast(&mut result, self.root_key, 0, false);
        if self.variable_definitions.is_empty() {
            result
        } else {
            let mut with_variables = String::new();
            with_variables.push_str("(function(){\nvar ");
            let last = self.variable_definitions.len() - 1;
            for (i, (_, value)) in self.variable_definitions.drain(..).enumerate() {
                writeln!(
                    &mut with_variables,
                    "v{} = {}{}",
                    i,
                    value,
                    if i == last { ";" } else { "," }
                )
                .unwrap();
            }
            write!(&mut with_variables, "return {};\n}})()", result).unwrap();
            with_variables
        }
    }

    fn print_ast(&mut self, f: &mut String, key: AstKey, indent: usize, is_dedupe_var: bool) {
        // Only use variable references at depth beyond the top level.
        if indent > 0 && self.duplicates.contains(&key) {
            let v = if self.variable_definitions.contains_key(&key) {
                self.variable_definitions.get_full(&key).unwrap().0
            } else {
                let mut variable = String::new();
                self.print_ast(&mut variable, key, 0, true);
                let v = self.variable_definitions.len();
                self.variable_definitions.insert(key, variable);
                v
            };
            return write!(f, "(v{}/*: any*/)", v).unwrap();
        }

        let ast = self.builder.lookup(key);
        match ast {
            Ast::Object(object) => {
                if object.is_empty() {
                    f.push_str("{}");
                } else {
                    let next_indent = indent + 1;
                    f.push('{');
                    for ObjectEntry { key, value } in object {
                        f.push('\n');
                        print_indentation(f, next_indent);
                        write!(f, "\"{}\": ", key.lookup()).unwrap();
                        self.print_primitive(f, value, next_indent, is_dedupe_var)
                            .unwrap();
                        f.push(',');
                    }
                    f.pop();
                    f.push('\n');
                    print_indentation(f, indent);
                    f.push('}');
                }
            }
            Ast::Array(array) => {
                if array.is_empty() {
                    if is_dedupe_var {
                        // Empty arrays can only have one inferred flow type and then conflict if
                        // used in different places, this is unsound if we would write to them but
                        // this whole module is based on the idea of a read only JSON tree.
                        f.push_str("([]/*: any*/)");
                    } else {
                        f.push_str("[]");
                    }
                } else {
                    f.push('[');
                    let next_indent = indent + 1;
                    for value in array {
                        f.push('\n');
                        print_indentation(f, next_indent);
                        self.print_primitive(f, value, next_indent, is_dedupe_var)
                            .unwrap();
                        f.push(',');
                    }
                    f.pop();
                    f.push('\n');
                    print_indentation(f, indent);
                    f.push(']');
                }
            }
        }
    }

    fn print_primitive(
        &mut self,
        f: &mut String,
        primitive: &Primitive,
        indent: usize,
        is_dedupe_var: bool,
    ) -> FmtResult {
        match primitive {
            Primitive::Null => write!(f, "null"),
            Primitive::Bool(b) => write!(f, "{}", if *b { "true" } else { "false" }),
            Primitive::RawString(str) => {
                f.push('\"');
                escape(str, f);
                f.push('\"');
                Ok(())
            }
            Primitive::String(key) => write!(f, "\"{}\"", key),
            Primitive::Float(value) => write!(f, "{}", value.as_float()),
            Primitive::Int(value) => write!(f, "{}", value),
            Primitive::Key(key) => {
                self.print_ast(f, *key, indent, is_dedupe_var);
                Ok(())
            }
            Primitive::StorageKey(field_name, key) => {
                print_static_storage_key(f, &self.builder, *field_name, *key)
            }
            Primitive::ModuleDependency(key) => write!(f, "require('{}.graphql')", key),
        }
    }
}

impl Default for Printer {
    fn default() -> Self {
        Self {
            builder: AstBuilder::default(),
        }
    }
}

pub fn print_operation(schema: &Schema, operation: &OperationDefinition) -> String {
    let mut builder = AstBuilder::default();
    let key = build_operation(schema, &mut builder, operation);
    let printer = DedupedJSONPrinter::new_without_dedupe(&builder, key);
    printer.print()
}

pub fn print_fragment(schema: &Schema, fragment: &FragmentDefinition) -> String {
    let mut builder = AstBuilder::default();
    let key = build_fragment(schema, &mut builder, fragment);
    let printer = DedupedJSONPrinter::new_without_dedupe(&builder, key);
    printer.print()
}

pub fn print_request(
    schema: &Schema,
    operation: &OperationDefinition,
    fragment: &FragmentDefinition,
    request_parameters: RequestParameters,
) -> String {
    let mut builder = AstBuilder::default();
    let request_parameters =
        build_request_params_ast_key(schema, request_parameters, &mut builder, operation);
    let key = build_request(
        schema,
        &mut builder,
        operation,
        fragment,
        request_parameters,
    );
    let printer = DedupedJSONPrinter::new_without_dedupe(&builder, key);
    printer.print()
}

pub fn print_request_params(
    schema: &Schema,
    operation: &OperationDefinition,
    query_id: Option<String>,
) -> String {
    let mut request_parameters = build_request_params(operation);
    request_parameters.id = query_id;

    let mut builder = AstBuilder::default();
    let request_parameters_ast_key =
        build_request_params_ast_key(schema, request_parameters, &mut builder, operation);
    let printer = DedupedJSONPrinter::new_without_dedupe(&builder, request_parameters_ast_key);

    printer.print()
}

impl Printer {
    pub fn print_request_deduped(
        &mut self,
        schema: &Schema,
        operation: &OperationDefinition,
        fragment: &FragmentDefinition,
        request_parameters: RequestParameters,
    ) -> String {
        let request_parameters =
            build_request_params_ast_key(schema, request_parameters, &mut self.builder, operation);
        let key = build_request(
            schema,
            &mut self.builder,
            operation,
            fragment,
            request_parameters,
        );
        let deduped_printer = DedupedJSONPrinter::new(&self.builder, key);
        deduped_printer.print()
    }

    pub fn print_operation_deduped(
        &mut self,
        schema: &Schema,
        operation: &OperationDefinition,
    ) -> String {
        let key = build_operation(schema, &mut self.builder, operation);
        let deduped_printer = DedupedJSONPrinter::new(&self.builder, key);
        deduped_printer.print()
    }

    pub fn print_fragment_deduped(
        &mut self,
        schema: &Schema,
        fragment: &FragmentDefinition,
    ) -> String {
        let key = build_fragment(schema, &mut self.builder, fragment);
        let deduped_printer = DedupedJSONPrinter::new(&self.builder, key);
        deduped_printer.print()
    }
}

/// Pre-computes storage key if possible and advantageous. Storage keys are
/// generated for fields with supplied arguments that are all statically known
/// (ie. literals, no variables) at build time.
fn print_static_storage_key(
    f: &mut String,
    builder: &AstBuilder,
    field_name: StringKey,
    args_key: AstKey,
) -> FmtResult {
    // TODO (T64585375): JS compiler has an option to force a storageKey.
    let args = builder.lookup(args_key).assert_array();
    write_static_storage_key(f, builder, field_name, args)
}

fn write_static_storage_key(
    f: &mut String,
    builder: &AstBuilder,
    field_name: StringKey,
    args: &[Primitive],
) -> FmtResult {
    write!(f, "\"{}(", field_name)?;
    for arg_key in args {
        let arg = builder.lookup(arg_key.assert_key()).assert_object();
        let name = &arg
            .iter()
            .find(|ObjectEntry { key, value: _ }| *key == CODEGEN_CONSTANTS.name)
            .expect("Expected `name` to exist")
            .value;
        let name = name.assert_string();
        write!(f, "{}:", name)?;
        write_argument_value(f, builder, arg)?;
        f.push(',');
    }
    f.pop(); // args won't be empty
    f.push_str(")\"");
    Ok(())
}

fn write_argument_value(f: &mut String, builder: &AstBuilder, arg: &[ObjectEntry]) -> FmtResult {
    let key = &arg
        .iter()
        .find(|entry| entry.key == CODEGEN_CONSTANTS.kind)
        .expect("Expected `kind` to exist")
        .value;
    let key = key.assert_string();
    // match doesn't allow `CODEGEN_CONSTANTS.<>` on the match arm, falling back to if statements
    if key == CODEGEN_CONSTANTS.literal {
        let literal = &arg
            .iter()
            .find(|entry| entry.key == CODEGEN_CONSTANTS.value)
            .expect("Expected `name` to exist")
            .value;
        write_constant_value(f, builder, literal)?;
    } else if key == CODEGEN_CONSTANTS.list_value {
        let items = &arg
            .iter()
            .find(|entry| entry.key == CODEGEN_CONSTANTS.items)
            .expect("Expected `items` to exist")
            .value;
        let array = builder.lookup(items.assert_key()).assert_array();

        f.push('[');
        let mut after_first = false;
        for key_or_null in array {
            match key_or_null {
                Primitive::Null => {}
                Primitive::Key(key) => {
                    if after_first {
                        f.push(',');
                    } else {
                        after_first = true;
                    }
                    let object = builder.lookup(*key).assert_object();
                    write_argument_value(f, builder, object)?;
                }
                _ => panic!("Expected an object key or null"),
            }
        }
        f.push(']');
    } else {
        // We filtered out Variables, here it should only be ObjectValue
        let fields = &arg
            .iter()
            .find(|entry| entry.key == CODEGEN_CONSTANTS.fields)
            .expect("Expected `fields` to exist")
            .value;
        let fields = builder.lookup(fields.assert_key()).assert_array();

        f.push('{');
        for field in fields {
            let field = builder.lookup(field.assert_key()).assert_object();
            let name = &field
                .iter()
                .find(|entry| entry.key == CODEGEN_CONSTANTS.name)
                .expect("Expected `name` to exist")
                .value;
            let name = name.assert_string();
            write!(f, "\\\"{}\\\":", name)?;
            write_argument_value(f, builder, field)?;
            f.push(',');
        }
        if !fields.is_empty() {
            f.pop();
        }
        f.push('}');
    }
    Ok(())
}

fn write_constant_value(f: &mut String, builder: &AstBuilder, value: &Primitive) -> FmtResult {
    match value {
        Primitive::Bool(b) => write!(f, "{}", if *b { "true" } else { "false" }),
        Primitive::String(key) => write!(f, "\\\"{}\\\"", key),
        Primitive::Float(value) => write!(f, "{}", value.as_float()),
        Primitive::Int(value) => write!(f, "{}", value),
        Primitive::Key(key) => {
            let ast = builder.lookup(*key);
            match ast {
                Ast::Array(arr) => {
                    f.push('[');
                    for value in arr {
                        write_constant_value(f, builder, value)?;
                        f.push(',');
                    }
                    if !arr.is_empty() {
                        f.pop();
                    }
                    f.push(']');
                    Ok(())
                }
                Ast::Object(obj) => {
                    f.push('{');
                    for ObjectEntry { key: name, value } in obj {
                        write!(f, "\\\"{}\\\":", name)?;
                        write_constant_value(f, builder, value)?;
                        f.push(',');
                    }
                    if !obj.is_empty() {
                        f.pop();
                    }
                    f.push('}');
                    Ok(())
                }
            }
        }
        Primitive::Null => {
            f.push_str("null");
            Ok(())
        }
        Primitive::StorageKey(_, _) => panic!("Unexpected StorageKey"),
        Primitive::RawString(_) => panic!("Unexpected RawString"),
        Primitive::ModuleDependency(_) => panic!("Unexpected ModuleDependency"),
    }
}
<commit_msg>inline print_static_storage_key<commit_after>/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use crate::ast::{Ast, AstBuilder, AstKey, ObjectEntry, Primitive, RequestParameters};
use crate::build_ast::{
    build_fragment, build_operation, build_request, build_request_params,
    build_request_params_ast_key,
};
use crate::constants::CODEGEN_CONSTANTS;
use crate::indentation::print_indentation;
use crate::utils::escape;

use graphql_ir::{FragmentDefinition, OperationDefinition};
use schema::Schema;

use fnv::{FnvBuildHasher, FnvHashSet};
use indexmap::IndexMap;
use interner::StringKey;
use std::fmt::{Result as FmtResult, Write};

pub struct Printer {
    builder: AstBuilder,
}

type VariableDefinitions = IndexMap<AstKey, String, FnvBuildHasher>;
struct DedupedJSONPrinter<'b> {
    variable_definitions: VariableDefinitions,
    duplicates: FnvHashSet<AstKey>,
    builder: &'b AstBuilder,
    root_key: AstKey,
}

impl<'b> DedupedJSONPrinter<'b> {
    fn new(builder: &'b AstBuilder, root_key: AstKey) -> Self {
        let mut visited = Default::default();
        let mut duplicates = Default::default();
        Self::collect_value_duplicates(&mut visited, &mut duplicates, builder, root_key);
        Self {
            variable_definitions: Default::default(),
            duplicates,
            builder,
            root_key,
        }
    }

    fn new_without_dedupe(builder: &'b AstBuilder, root_key: AstKey) -> Self {
        Self {
            variable_definitions: Default::default(),
            duplicates: Default::default(),
            builder,
            root_key,
        }
    }

    /// We don't dedupe in an already deduped AST unless the duplicate
    /// also appears on a subtree that's not a duplicate
    /// Input:
    /// [
    ///     [{a: 1}, {b:2}],
    ///     [{a: 1}, {b:2}],
    ///     {b: 2}
    /// ]
    /// Output:
    /// v0 = {b: 2};
    /// v1 = [{a: 1}, v0];
    fn collect_value_duplicates(
        visited: &mut FnvHashSet<AstKey>,
        duplicates: &mut FnvHashSet<AstKey>,
        builder: &AstBuilder,
        key: AstKey,
    ) {
        match builder.lookup(key) {
            Ast::Array(array) => {
                if array.is_empty() {
                    return;
                }
                if !visited.insert(key) {
                    duplicates.insert(key);
                    return;
                }
                for val in array {
                    if let Primitive::Key(key) = val {
                        Self::collect_value_duplicates(visited, duplicates, builder, *key);
                    }
                }
            }
            Ast::Object(object) => {
                if object.is_empty() {
                    return;
                }
                if !visited.insert(key) {
                    duplicates.insert(key);
                    return;
                }
                for entry in object {
                    if let Primitive::Key(key) = entry.value {
                        Self::collect_value_duplicates(visited, duplicates, builder, key);
                    }
                }
            }
        }
    }

    fn print(mut self) -> String {
        let mut result = String::new();
        self.print_ast(&mut result, self.root_key, 0, false);
        if self.variable_definitions.is_empty() {
            result
        } else {
            let mut with_variables = String::new();
            with_variables.push_str("(function(){\nvar ");
            let last = self.variable_definitions.len() - 1;
            for (i, (_, value)) in self.variable_definitions.drain(..).enumerate() {
                writeln!(
                    &mut with_variables,
                    "v{} = {}{}",
                    i,
                    value,
                    if i == last { ";" } else { "," }
                )
                .unwrap();
            }
            write!(&mut with_variables, "return {};\n}})()", result).unwrap();
            with_variables
        }
    }

    fn print_ast(&mut self, f: &mut String, key: AstKey, indent: usize, is_dedupe_var: bool) {
        // Only use variable references at depth beyond the top level.
        if indent > 0 && self.duplicates.contains(&key) {
            let v = if self.variable_definitions.contains_key(&key) {
                self.variable_definitions.get_full(&key).unwrap().0
            } else {
                let mut variable = String::new();
                self.print_ast(&mut variable, key, 0, true);
                let v = self.variable_definitions.len();
                self.variable_definitions.insert(key, variable);
                v
            };
            return write!(f, "(v{}/*: any*/)", v).unwrap();
        }

        let ast = self.builder.lookup(key);
        match ast {
            Ast::Object(object) => {
                if object.is_empty() {
                    f.push_str("{}");
                } else {
                    let next_indent = indent + 1;
                    f.push('{');
                    for ObjectEntry { key, value } in object {
                        f.push('\n');
                        print_indentation(f, next_indent);
                        write!(f, "\"{}\": ", key.lookup()).unwrap();
                        self.print_primitive(f, value, next_indent, is_dedupe_var)
                            .unwrap();
                        f.push(',');
                    }
                    f.pop();
                    f.push('\n');
                    print_indentation(f, indent);
                    f.push('}');
                }
            }
            Ast::Array(array) => {
                if array.is_empty() {
                    if is_dedupe_var {
                        // Empty arrays can only have one inferred flow type and then conflict if
                        // used in different places, this is unsound if we would write to them but
                        // this whole module is based on the idea of a read only JSON tree.
                        f.push_str("([]/*: any*/)");
                    } else {
                        f.push_str("[]");
                    }
                } else {
                    f.push('[');
                    let next_indent = indent + 1;
                    for value in array {
                        f.push('\n');
                        print_indentation(f, next_indent);
                        self.print_primitive(f, value, next_indent, is_dedupe_var)
                            .unwrap();
                        f.push(',');
                    }
                    f.pop();
                    f.push('\n');
                    print_indentation(f, indent);
                    f.push(']');
                }
            }
        }
    }

    fn print_primitive(
        &mut self,
        f: &mut String,
        primitive: &Primitive,
        indent: usize,
        is_dedupe_var: bool,
    ) -> FmtResult {
        match primitive {
            Primitive::Null => write!(f, "null"),
            Primitive::Bool(b) => write!(f, "{}", if *b { "true" } else { "false" }),
            Primitive::RawString(str) => {
                f.push('\"');
                escape(str, f);
                f.push('\"');
                Ok(())
            }
            Primitive::String(key) => write!(f, "\"{}\"", key),
            Primitive::Float(value) => write!(f, "{}", value.as_float()),
            Primitive::Int(value) => write!(f, "{}", value),
            Primitive::Key(key) => {
                self.print_ast(f, *key, indent, is_dedupe_var);
                Ok(())
            }
            Primitive::StorageKey(field_name, key) => {
                write_static_storage_key(f, &self.builder, *field_name, *key)
            }
            Primitive::ModuleDependency(key) => write!(f, "require('{}.graphql')", key),
        }
    }
}

impl Default for Printer {
    fn default() -> Self {
        Self {
            builder: AstBuilder::default(),
        }
    }
}

pub fn print_operation(schema: &Schema, operation: &OperationDefinition) -> String {
    let mut builder = AstBuilder::default();
    let key = build_operation(schema, &mut builder, operation);
    let printer = DedupedJSONPrinter::new_without_dedupe(&builder, key);
    printer.print()
}

pub fn print_fragment(schema: &Schema, fragment: &FragmentDefinition) -> String {
    let mut builder = AstBuilder::default();
    let key = build_fragment(schema, &mut builder, fragment);
    let printer = DedupedJSONPrinter::new_without_dedupe(&builder, key);
    printer.print()
}

pub fn print_request(
    schema: &Schema,
    operation: &OperationDefinition,
    fragment: &FragmentDefinition,
    request_parameters: RequestParameters,
) -> String {
    let mut builder = AstBuilder::default();
    let request_parameters =
        build_request_params_ast_key(schema, request_parameters, &mut builder, operation);
    let key = build_request(
        schema,
        &mut builder,
        operation,
        fragment,
        request_parameters,
    );
    let printer = DedupedJSONPrinter::new_without_dedupe(&builder, key);
    printer.print()
}

pub fn print_request_params(
    schema: &Schema,
    operation: &OperationDefinition,
    query_id: Option<String>,
) -> String {
    let mut request_parameters = build_request_params(operation);
    request_parameters.id = query_id;

    let mut builder = AstBuilder::default();
    let request_parameters_ast_key =
        build_request_params_ast_key(schema, request_parameters, &mut builder, operation);
    let printer = DedupedJSONPrinter::new_without_dedupe(&builder, request_parameters_ast_key);

    printer.print()
}

impl Printer {
    pub fn print_request_deduped(
        &mut self,
        schema: &Schema,
        operation: &OperationDefinition,
        fragment: &FragmentDefinition,
        request_parameters: RequestParameters,
    ) -> String {
        let request_parameters =
            build_request_params_ast_key(schema, request_parameters, &mut self.builder, operation);
        let key = build_request(
            schema,
            &mut self.builder,
            operation,
            fragment,
            request_parameters,
        );
        let deduped_printer = DedupedJSONPrinter::new(&self.builder, key);
        deduped_printer.print()
    }

    pub fn print_operation_deduped(
        &mut self,
        schema: &Schema,
        operation: &OperationDefinition,
    ) -> String {
        let key = build_operation(schema, &mut self.builder, operation);
        let deduped_printer = DedupedJSONPrinter::new(&self.builder, key);
        deduped_printer.print()
    }

    pub fn print_fragment_deduped(
        &mut self,
        schema: &Schema,
        fragment: &FragmentDefinition,
    ) -> String {
        let key = build_fragment(schema, &mut self.builder, fragment);
        let deduped_printer = DedupedJSONPrinter::new(&self.builder, key);
        deduped_printer.print()
    }
}

fn write_static_storage_key(
    f: &mut String,
    builder: &AstBuilder,
    field_name: StringKey,
    args_key: AstKey,
) -> FmtResult {
    write!(f, "\"{}(", field_name)?;
    let args = builder.lookup(args_key).assert_array();
    for arg_key in args {
        let arg = builder.lookup(arg_key.assert_key()).assert_object();
        let name = &arg
            .iter()
            .find(|ObjectEntry { key, value: _ }| *key == CODEGEN_CONSTANTS.name)
            .expect("Expected `name` to exist")
            .value;
        let name = name.assert_string();
        write!(f, "{}:", name)?;
        write_argument_value(f, builder, arg)?;
        f.push(',');
    }
    f.pop(); // args won't be empty
    f.push_str(")\"");
    Ok(())
}

fn write_argument_value(f: &mut String, builder: &AstBuilder, arg: &[ObjectEntry]) -> FmtResult {
    let key = &arg
        .iter()
        .find(|entry| entry.key == CODEGEN_CONSTANTS.kind)
        .expect("Expected `kind` to exist")
        .value;
    let key = key.assert_string();
    // match doesn't allow `CODEGEN_CONSTANTS.<>` on the match arm, falling back to if statements
    if key == CODEGEN_CONSTANTS.literal {
        let literal = &arg
            .iter()
            .find(|entry| entry.key == CODEGEN_CONSTANTS.value)
            .expect("Expected `name` to exist")
            .value;
        write_constant_value(f, builder, literal)?;
    } else if key == CODEGEN_CONSTANTS.list_value {
        let items = &arg
            .iter()
            .find(|entry| entry.key == CODEGEN_CONSTANTS.items)
            .expect("Expected `items` to exist")
            .value;
        let array = builder.lookup(items.assert_key()).assert_array();

        f.push('[');
        let mut after_first = false;
        for key_or_null in array {
            match key_or_null {
                Primitive::Null => {}
                Primitive::Key(key) => {
                    if after_first {
                        f.push(',');
                    } else {
                        after_first = true;
                    }
                    let object = builder.lookup(*key).assert_object();
                    write_argument_value(f, builder, object)?;
                }
                _ => panic!("Expected an object key or null"),
            }
        }
        f.push(']');
    } else {
        // We filtered out Variables, here it should only be ObjectValue
        let fields = &arg
            .iter()
            .find(|entry| entry.key == CODEGEN_CONSTANTS.fields)
            .expect("Expected `fields` to exist")
            .value;
        let fields = builder.lookup(fields.assert_key()).assert_array();

        f.push('{');
        for field in fields {
            let field = builder.lookup(field.assert_key()).assert_object();
            let name = &field
                .iter()
                .find(|entry| entry.key == CODEGEN_CONSTANTS.name)
                .expect("Expected `name` to exist")
                .value;
            let name = name.assert_string();
            write!(f, "\\\"{}\\\":", name)?;
            write_argument_value(f, builder, field)?;
            f.push(',');
        }
        if !fields.is_empty() {
            f.pop();
        }
        f.push('}');
    }
    Ok(())
}

fn write_constant_value(f: &mut String, builder: &AstBuilder, value: &Primitive) -> FmtResult {
    match value {
        Primitive::Bool(b) => write!(f, "{}", if *b { "true" } else { "false" }),
        Primitive::String(key) => write!(f, "\\\"{}\\\"", key),
        Primitive::Float(value) => write!(f, "{}", value.as_float()),
        Primitive::Int(value) => write!(f, "{}", value),
        Primitive::Key(key) => {
            let ast = builder.lookup(*key);
            match ast {
                Ast::Array(arr) => {
                    f.push('[');
                    for value in arr {
                        write_constant_value(f, builder, value)?;
                        f.push(',');
                    }
                    if !arr.is_empty() {
                        f.pop();
                    }
                    f.push(']');
                    Ok(())
                }
                Ast::Object(obj) => {
                    f.push('{');
                    for ObjectEntry { key: name, value } in obj {
                        write!(f, "\\\"{}\\\":", name)?;
                        write_constant_value(f, builder, value)?;
                        f.push(',');
                    }
                    if !obj.is_empty() {
                        f.pop();
                    }
                    f.push('}');
                    Ok(())
                }
            }
        }
        Primitive::Null => {
            f.push_str("null");
            Ok(())
        }
        Primitive::StorageKey(_, _) => panic!("Unexpected StorageKey"),
        Primitive::RawString(_) => panic!("Unexpected RawString"),
        Primitive::ModuleDependency(_) => panic!("Unexpected ModuleDependency"),
    }
}
<|endoftext|>