text:
# Core traits and accessor functions

"""
`alpha(p)` extracts the alpha component of a color. For a color
without an alpha channel, it will always return 1.
"""
alpha(c::TransparentColor) = c.alpha
alpha(c::Color)   = oneunit(eltype(c))
alpha(c::RGB24)   = N0f8(1)
alpha(c::ARGB32)  = reinterpret(N0f8, (c.color >> 0x18) % UInt8)
alpha(c::AGray32) = reinterpret(N0f8, (c.color >> 0x18) % UInt8)
alpha(x::Number)  = convert(eltype(ccolor(Gray, typeof(x))), oneunit(x))  # ensures it's a type supported by Gray (which has widest eltype support)

"`red(c)` returns the red component of an `AbstractRGB` opaque or transparent color."
red(c::AbstractRGB   ) = c.r
red(c::TransparentRGB) = c.r
red(c::RGB24)  = reinterpret(N0f8, (c.color >> 0x10) % UInt8)
red(c::ARGB32) = reinterpret(N0f8, (c.color >> 0x10) % UInt8)

"`green(c)` returns the green component of an `AbstractRGB` opaque or transparent color."
green(c::AbstractRGB   ) = c.g
green(c::TransparentRGB) = c.g
green(c::RGB24)  = reinterpret(N0f8, (c.color >> 0x08) % UInt8)
green(c::ARGB32) = reinterpret(N0f8, (c.color >> 0x08) % UInt8)

"`blue(c)` returns the blue component of an `AbstractRGB` opaque or transparent color."
blue(c::AbstractRGB   ) = c.b
blue(c::TransparentRGB) = c.b
blue(c::RGB24)  = reinterpret(N0f8, c.color % UInt8)
blue(c::ARGB32) = reinterpret(N0f8, c.color % UInt8)

"`gray(c)` returns the gray component of a grayscale opaque or transparent color."
gray(c::Gray)    = c.val
gray(c::TransparentGray) = c.val
gray(c::Gray24)  = reinterpret(N0f8, c.color % UInt8)
gray(c::AGray32) = reinterpret(N0f8, c.color % UInt8)
gray(x::Number)  = convert(eltype(ccolor(Gray, typeof(x))), x)   # ensures it's a type supported by Gray

"""
`chroma(c)` returns the chroma of a `Lab`, `Luv` or their variants color.
!!! note
    The other color types (e.g. `RGB`, `HSV` or `YCbCr`) are not supported
    because their definitions of *chroma* are not clear. Colorfulness, chroma
    and saturation are defined as distinct aspects by the CIE.
"""
chroma(c::Union{C,AlphaColor{C},ColorAlpha{C}}) where {C<:Union{Lab,Oklab,DIN99,DIN99o,DIN99d}} = sqrt(c.a^2 + c.b^2)
chroma(c::Union{C,AlphaColor{C},ColorAlpha{C}}) where {C<:Luv} = sqrt(c.u^2 + c.v^2)
chroma(c::Union{C,AlphaColor{C},ColorAlpha{C}}) where {C<:Union{LCHab,LCHuv, LCHOklab}} = c.c

"""
`hue(c)` returns the hue in degrees. This function does not guarantee that the
return value is in [0, 360].
"""
hue(c::Union{C,AlphaColor{C},ColorAlpha{C}}) where {C<:Union{HSV,HSL,HSI,LCHab,LCHuv, LCHOklab}} = c.h
hue(c::Union{C,AlphaColor{C},ColorAlpha{C}}) where {C<:Union{Lab,Oklab,DIN99,DIN99o,DIN99d}} =
    (h = atand(c.b, c.a); h < 0 ? h + 360 : h)
hue(c::Union{C,AlphaColor{C},ColorAlpha{C}}) where {C<:Luv} =
    (h = atand(c.v, c.u); h < 0 ? h + 360 : h)

# fallbacks for compN
_comp(::Val{N}, c::Colorant) where N = getfield(c, N)
_comp(::Val{N}, c::AlphaColor) where N = getfield(c, N + 1)
_comp(::Val{N}, c::AlphaColorN{N}) where N = alpha(c)
_comp(::Val{N}, c::ColorAlphaN{N}) where N = alpha(c)

@noinline function _comp_error(c::ColorantN{N}, n::Int) where N
    io = IOBuffer()
    print(io, "attempt to access ", N, "-component color ")
    print(IOContext(io, :compact=>true), typeof(c), " with `comp", n, "`")
    throw(ArgumentError(String(take!(io))))
end

"""
`comp1(c)` extracts the first component you'd pass to the constructor
of the corresponding object.  For most color types without an alpha
channel, this is just the first field, but for types like `BGR` that
reverse the internal storage order this provides the value that you'd
use to reconstruct the color.

Specifically, for any `Color{T,3}`,

    c == typeof(c)(comp1(c), comp2(c), comp3(c))

returns true.
"""
comp1(c::Colorant) = _comp(Val(1), c)
comp1(c::Union{AbstractRGB, TransparentRGB}) = red(c)
comp1(c::Union{AbstractGray, TransparentGray}) = gray(c)

"`comp2(c)` extracts the second constructor argument (see `comp1`)."
comp2(c::Colorant) = _comp(Val(2), c)
comp2(c::ColorantN{1}) = _comp_error(c, 2)
comp2(c::Union{AbstractRGB, TransparentRGB}) = green(c)

"`comp3(c)` extracts the third constructor argument (see `comp1`)."
comp3(c::Colorant) = _comp(Val(3), c)
comp3(c::Union{ColorantN{1}, ColorantN{2}}) = _comp_error(c, 3)
comp3(c::Union{AbstractRGB, TransparentRGB}) = blue(c)

"`comp4(c)` extracts the fourth constructor argument (see `comp1`)."
comp4(c::Colorant) = _comp(Val(4), c)
comp4(c::Union{ColorantN{1}, ColorantN{2}, ColorantN{3}}) = _comp_error(c, 4)

"`comp5(c)` extracts the fifth constructor argument (see `comp1`)."
comp5(c::Colorant) = _comp(Val(5), c)
comp5(c::Union{ColorantN{1}, ColorantN{2}, ColorantN{3}, ColorantN{4}}) = _comp_error(c, 5)

"`color(c)` extracts the opaque color component from a Colorant (e.g., omits the alpha channel, if present)."
color(c::Color) = c
color(c::TransparentColorN{2,C}) where {C} = C(comp1(c))
color(c::TransparentColorN{3,C}) where {C} = C(comp1(c), comp2(c))
color(c::TransparentColorN{4,C}) where {C} = C(comp1(c), comp2(c), comp3(c))
color(c::TransparentColorN{5,C}) where {C} = C(comp1(c), comp2(c), comp3(c), comp4(c))

# Some of these traits exploit a nice trick: for subtypes, walk up the
# type hierarchy until we get to a stage where we can define the
# function in general

# recurse up the type hierarchy until you get to Colorant{T,N} for
# specific T,N.
to_top(::Type{Colorant{T,N}}) where {T,N} = Colorant{T,N}
@pure to_top(::Type{C}) where {C<:Colorant} = to_top(supertype(C))

to_top(c::Colorant) = to_top(typeof(c))

# Return the number of components in the color
# Note this is different from div(sizeof(c), sizeof(eltype(c))) (e.g., XRGB)
length(::Type{<:ColorantN{N}}) where N = N

length(c::Colorant) = length(typeof(c))

# eltype(RGB{Float32}) -> Float32
eltype(::Type{C}) where {C<:Colorant{T}} where {T} = T

eltype(c::Colorant) = eltype(typeof(c))

@pure function _parameter_upper_bound(t::UnionAll, idx)
    Base.rewrap_unionall((Base.unwrap_unionall(t)::DataType).parameters[idx], t)
end

# eltypes_supported(Colorant{T<:X}) -> X
function eltypes_supported(::Type{C}) where {C<:Colorant}
    Cb = base_colorant_type(C)
    isconcretetype(C) && C === Cb && return eltype(C)
    _eltypes_supported(Cb, supertype(Cb))
end
@pure _eltypes_supported(::Type{<:Colorant}, ::Type{C}) where {C<:Colorant} = _eltypes_supported(C, supertype(C))
_eltypes_supported(::Type{C}, ::Type) where {C<:Colorant} = _parameter_upper_bound(C, 1)

eltypes_supported(c::Colorant) = eltypes_supported(typeof(c))

"""
    issupported(C::Type, T::Type)::Bool

Returns `true` if `T` is a valid numeric eltype for `C<:Colorant`.
"""
issupported(::Type{C}, ::Type{T}) where {C<:Colorant,T} = T <: eltypes_supported(C)

"""
    CT = color_type(C::Type)
    CT = color_type(c::Colorant)

Return the type of the Color object, discarding any alpha channel.
For example,

    color_type(RGB)          === RGB
    color_type(RGB{Float32}) === RGB{Float32}
    color_type(ARGB{N0f8})   === RGB{N0f8}
    color_type(RGB(1,0,0))   === RGB{N0f8}

`color_type`, and related functions like [`base_color_type`](@ref),
[`base_colorant_type`](@ref), and [`ccolor`](@ref) are useful for manipulating types
when writing generic code.
"""
color_type(::Type{C}) where {C<:Color} = C
color_type(::Type{C}) where {C<:AlphaColor} = color_type(supertype(C)) # may help caching
color_type(::Type{C}) where {C<:ColorAlpha} = color_type(supertype(C)) # may help caching
function color_type(::Type{TC}) where {TC <: TransparentColor}
    _color_type(::Type{TC}) where {C, TC <: TransparentColor{C}} = C
    if TC isa UnionAll
        supertype(TC) <: TransparentColor && return color_type(supertype(TC))
        C1 = _parameter_upper_bound(TC, 1)
        color_type(C1) # simplify
    else
        _color_type(TC)
    end
end
color_type(::Type{Colorant{T,N}}) where {T,N} = Color{T,N}
color_type(::Type{ColorantN{N}}) where {N} = ColorN{N}
color_type(::Type{Colorant{T}}) where {T} = Color{T}
color_type(::Type{Colorant}) = Color
color_type(::Type{X}) where {X<:Number} = typeof(Gray(zero(X)))

color_type(c::Union{Colorant, Number}) = color_type(typeof(c))

"""
    Cbase = base_color_type(C::Type)
    Cbase = base_color_type(c::Colorant)

Return the Color type without alpha channel or a specified numeric element type.
`base_color_type` is similar to [`color_type`](@ref), except it "strips off" the
element type. It always returns a parametric type, which makes it convenient
for switching the numeric element type.

For example, compare

    base_color_type(RGB{N0f8})  === RGB
    base_color_type(RGBA{N0f8}) === RGB

vs

    color_type(RGB{N0f8})       === RGB{N0f8}

Switching element types can be done as follows:

    c64 = base_color_type(c){Float64}(color(c))

`base_color_type` discards any alpha channel. See [`base_colorant_type`](@ref)
if you want to preserve the alpha channel.
"""
base_color_type(::Type{C}) where {C<:Colorant} = base_colorant_type(color_type(C))
base_color_type(::Type{<:Number}) = Gray

base_color_type(x::Union{Colorant,Number}) = base_color_type(typeof(x))

## base_colorant_type implementation

base_colorant_type(::Type{C}) where {C<:Colorant} = isabstracttype(C) ? abstract_basetype(C) : basetype(C)
base_colorant_type(::Type{<:Number}) = Gray

@pure basetype(@nospecialize(C)) = Base.typename(C).wrapper

abstract_basetype(::Type{<:AbstractRGB}) = AbstractRGB
abstract_basetype(::Type{<:AbstractGray}) = AbstractGray
abstract_basetype(::Type{<:ColorN{N}}) where N = ColorN{N}
abstract_basetype(::Type{<:Color}) = Color
function abstract_basetype(::Type{TC}) where {TC<:TransparentColor}
    P = TC <: AlphaColor ? AlphaColor : TC <: ColorAlpha ? ColorAlpha : TransparentColor
    _abstract_transparent_basetype(TC, P, base_color_type(TC))
end
function _abstract_transparent_basetype(::Type{TC}, P, Cb) where {N, TC <: TransparentColorN{N}}
    Cb === Color && return P{C,T,N} where {T,C<:ColorN{N-1,T}}
    isabstracttype(Cb) || return P{Cb{T},T,N} where T
    return P{C,T,N} where {T,C<:Cb{T}}
end
function _abstract_transparent_basetype(::Type{TC}, P, Cb) where {TC <: TransparentColor}
    TC === P && return P
    isabstracttype(Cb) || return P{Cb{T},T} where T
    return P{C,T} where {T,C<:Cb{T}}
end
# This fallback dispatches to a separate function to control method ordering.
# Otherwise the generic ColorantN methods supersede the AlphaColor/ColorAlpha/Transparent methods
abstract_basetype(::Type{C}) where C = abstract_colorant_basetype(C)

abstract_colorant_basetype(::Type{<:ColorantN{N}}) where N = ColorantN{N}
abstract_colorant_basetype(::Type{<:Colorant})             = Colorant

"""
    Cbase = base_colorant_type(C::Type)
    Cbase = base_colorant_type(c::Colorant)

Return the Colorant type without specified numeric element type.

For example, compare

    base_colorant_type(ARGB{N0f8}) === ARGB

vs

    base_color_type(ARGB{N0f8})    === RGB
    color_type(ARGB{N0f8})         === RGB{N0f8}

When possible, `base_colorant_type` returns a parametric `UnionAll`, so
that `Cbase{T}` can be used to specify a colorant with element type `T`.
However, `base_colorant_type` is not guaranteed to return a parametric result,
for example

```jldoctest; setup = :(using ColorTypes)
julia> base_colorant_type(RGB24)
RGB24
```

In generic code, you can check whether `Cbase` is a `DataType` or `UnionAll`;
`Cbase{T}` will be valid only if it's a `UnionAll`.

# Usage example

A safe and easy way to switch the element type of a colorant value `c` to be `Float64` is

    Cbase = base_colorant_type(parametric_colorant(typeof(c)))
    c64 = Cbase{Float64}(c)

"""
base_colorant_type(c::Union{Colorant, Number}) = base_colorant_type(typeof(c))

"""
    Cp = parametric_colorant(C::Type)

Return a parametric colorant type.

# Examples

```jldoctest; setup = :(using ColorTypes, ColorTypes.FixedPointNumbers)
julia> parametric_colorant(RGB)
RGB

julia> parametric_colorant(RGB{Float32})
RGB{Float32}

julia> parametric_colorant(BGR)
BGR

julia> parametric_colorant(RGB24) == RGB{N0f8}
true
```
"""
parametric_colorant(::Type{C}) where C<:Colorant = C
parametric_colorant(::Type{RGB24}) = RGB{N0f8}
parametric_colorant(::Type{Gray24}) = Gray{N0f8}
parametric_colorant(::Type{ARGB32}) = ARGB{N0f8}
parametric_colorant(::Type{AGray32}) = AGray{N0f8}

"""
    CF = floattype(C::Type)

Promote storage data type of colorant type `C` to `AbstractFloat` while keeping the
`base_colorant_type` the same.

!!! info

    Non-parametric colorants will be promoted to corresponding parametric
    colorants. For example, `floattype(RGB24) == RGB{Float32}`.
"""
floattype(::Type{T}) where T <: Colorant =
    base_colorant_type(T){floattype(eltype(T))} # 1 parameter
# 0 parameter
floattype(::Type{RGB24})   = RGB{floattype(N0f8)}
floattype(::Type{Gray24})  = Gray{floattype(N0f8)}
floattype(::Type{ARGB32})  = ARGB{floattype(N0f8)}
floattype(::Type{AGray32}) = AGray{floattype(N0f8)}

@pure pureintersect(::Type{C1}, ::Type{C2}) where {C1,C2} = typeintersect(C1, C2)

"""
    Calpha, Cbase, T = colorsplit(C)

Split a color type `C` into three components: `T` is the numeric type,
`Cbase` is the `base_color_type(C)`, and `Calpha` is one of `Nothing`
(when `C` has no transparency), `AlphaColor`, or `ColorAlpha`.

If `C` is an abstract type, `Calpha` may be `TransparentColor` or `Any`.
"""
function colorsplit(::Type{C}) where C<:Colorant
    Calpha = C <: AlphaColor ? AlphaColor :
             C <: ColorAlpha ? ColorAlpha :
             C <: Color ? Nothing :
             C <: TransparentColor ? TransparentColor : Any
    Cbase = C <: Union{Color,TransparentColor} ? base_color_type(C) : Color
    return Calpha, Cbase, eltype(C)
end

"""
    C = ccolor(Cdest, Csrc)

`ccolor` ("concrete color") supports independent selection of colorspace and
numeric element type. `ccolor` chooses the numeric element type from
`Cdest` if available, but if not specified gets it from `Csrc`.

```jldoctest; setup = :(using ColorTypes, ColorTypes.FixedPointNumbers)
julia> ccolor(RGB, Gray{Float32})
RGB{Float32}

julia> ccolor(RGB, Gray{N0f8}) == RGB{N0f8}
true

julia> ccolor(RGB{Float32}, Gray{N0f8})
RGB{Float32}
```

Some colorspaces don't support `FixedPoint` numeric element types;
in such cases the `eltype_default` for `Cdest` is chosen:

```jldoctest; setup = :(using ColorTypes, ColorTypes.FixedPointNumbers)
julia> ccolor(HSV, RGB{N0f8})
HSV{Float32}
```

`Cdest` can be an abstract type, in which case `Csrc` must be in that
abstract color space.

```jldoctest; setup = :(using ColorTypes, ColorTypes.FixedPointNumbers)
julia> ccolor(Color{Float32}, RGB{N0f8})
RGB{Float32}

julia> ccolor(AbstractRGB{Float32}, BGR{N0f8})
BGR{Float32}

julia> ccolor(AbstractRGB{Float32}, HSV{Float32})
ERROR: in ccolor, empty intersection between AbstractRGB and HSV
[...]
```
`ccolor` will throw an error when no unambiguous concrete type can be determined.
In the previous case, `AbstractRGB{Float32}` encompases `RGB{Float32}` and `BGR{Float32}`.

`ccolor` is most useful in defining other methods.
For example, to allow users to write `convert(RGB, c)` without having to specify the
numeric element type of `RGB`, define

```
convert(::Type{C}, p::Colorant) where C<:Colorant = cnvt(ccolor(C,typeof(p)), p)
```

where `cnvt` is the function that performs explicit conversion.
"""
function ccolor(::Type{Cdest}, ::Type{Csrc}) where {Cdest<:Colorant, Csrc<:Union{Number,Colorant}}
    Cdestalpha, Cdestbase, Tdest = colorsplit(Cdest)
    if Csrc <: Number
        Cdestbase <: Union{AbstractGray, AbstractRGB} || throw(ColorTypeResolutionError(:ccolor, "no automatic conversion from", Csrc, Cdestbase))
        Csrcalpha, Csrcbase, Tsrc = Any, Color, Csrc
    else
        Csrcalpha, Csrcbase, Tsrc = colorsplit(Csrc)
    end
    # Step 1: pick the base color type
    if isabstracttype(Cdestbase)
        C = pureintersect(Cdestbase, Csrcbase)
        C === Union{} && throw(ColorTypeResolutionError(:ccolor, "empty intersection between", Cdestbase, Csrcbase))
        isabstracttype(C) && throw(ColorTypeResolutionError(:ccolor, "abstract intersection between", Cdestbase, Csrcbase))
    else
        C = Cdestbase
    end
    # Step 2: combine with the alpha representation
    if Cdestalpha === Nothing
    else
        Calpha = (Cdestalpha === TransparentColor || Cdestalpha === Any) ? Csrcalpha : Cdestalpha
        Cdestalpha === TransparentColor && Calpha === Nothing && throw(ColorTypeResolutionError(:ccolor, "ambiguous alpha storage between", Cdest, Csrc))
        if Calpha !== Nothing
            Calpha === TransparentColor && throw(ColorTypeResolutionError(:ccolor, "ambiguous alpha storage between", Cdest, Csrc))
            C = Calpha <: AlphaColor ? alphacolor(C) :
                Calpha <: ColorAlpha ? coloralpha(C) : error("unexpected Calpha ", Calpha)
        end
    end
    # Step 3: assign the eltype
    if !isa(C, UnionAll)
        eltype(C) <: Tdest && return C   # RGB24 etc.
        error("nonparametric type ", C, " has ambiguous destination ", Cdest)
    end
    isabstracttype(Tdest) || !issupported(C, Tdest) || return C{Tdest}
    T = pureintersect(Tdest, Tsrc)
    T === Union{} && throw(ColorTypeResolutionError(:ccolor, "empty intersection between", Tdest, Tsrc))
    T === Any && return C
    issupported(C, T) && return C{T}
    Tdef = eltype_default(C)
    T<:Integer && return C{Tdef}
    Tf = floattype(Tsrc)
    issupported(C, Tf) && return C{Tf}
    return C{Tdef}
end

isfinite(c::Colorant) = mapreducec(isfinite, &, true, c)
isinf(c::Colorant) = mapreducec(isinf, |, false, c)
isnan(c::Colorant) = mapreducec(isnan, |, false, c)

"""
    ColorTypes.nan(C)

Return a color instance of the specified type in which all components are NaN.

# Examples
```jldoctest; setup = :(using ColorTypes)
julia> ColorTypes.nan(RGB{Float32})
RGB{Float32}(NaN, NaN, NaN)

julia> ColorTypes.nan(AHSV{Float64})
AHSV{Float64}(NaN, NaN, NaN, NaN)
```
"""
nan(::Type{T}) where {T<:AbstractFloat} = convert(T, NaN)
nan(::Type{C}) where {T<:AbstractFloat, N, C<:Colorant{T, N}} = C(ntuple(_ -> nan(T), Val(N))...)

zero(::Type{C}) where {N, C<:ColorantN{N}} = C(ntuple(_ -> 0, Val(N))...)
zero(c::Colorant) = zero(typeof(c))

oneunit(::Type{C}) where {C<:Colorant} = throw_oneunit_error(C)
@noinline function throw_oneunit_error(@nospecialize(C))
    throw(ArgumentError("`oneunit` for $C is not defined. Perhaps the meaning is not clear."))
end
oneunit(::Type{C}) where {C<:AbstractGray} = C(1)
# It's not clear what `oneunit` means for most Color3s,
# but for AbstractRGB, XYZ, and LMS, it's OK
oneunit(::Type{C}) where {C<:AbstractRGB}      = C(1, 1, 1)
oneunit(::Type{C}) where {C<:Union{XYZ, LMS}}  = C(1, 1, 1)
oneunit(::Type{C}) where {C<:TransparentColor} = C(oneunit(color_type(C)))
oneunit(::Type{C}) where {C<:Union{AGray, GrayA}} = C(1, 1) # workaround for inconsistent `color_type`
oneunit(c::Colorant) = oneunit(typeof(c))

one(::Type{C}) where {C<:Colorant} = one(eltype_default(C))
one(::Type{C}) where {T, C<:Colorant{T}} = one(T)
one(c::Colorant) = one(typeof(c))

Base.broadcastable(x::Colorant) = Ref(x)


text:
## Data frame test
## not run by default. Too time consuming and relies on external pacakgs
using Mustache, DataFrames
using Test
using Printf

#  simple usage
tpl = mt"""
{{:TITLE}}
{{#:D}}
{{:english}} <--> {{:spanish}}
{{/:D}}
"""

d = DataFrame(english=["hello", "good bye"], spanish=["hola", "adios"])

@test render(tpl, TITLE="translate",  D=d) == "translate\nhello <--> hola\ngood bye <--> adios\n"

## Issue with data frames as keyword arguments
tpl = """
{{#:fred}}{{:a}}--{{:b}}{{/:fred}}
{{:barney}}
"""
d = DataFrame(a=[1,2,3], b=[3,2,1])
@test render(tpl, fred=d, barney="123") == "1--32--23--1\n123\n"

ext: jl


text:
nx(x)=(nothing,[]) # ignore this
Derivative[nx]=void
Inplace[nx]=void
export nx
ADnothing(n)=ADnode(nx,n)
export ADnothing

max_stars_repo_name: davidbarber/AutoDiff

max_issues_repo_name: davidbarber/AutoDiff

max_forks_repo_name: davidbarber/AutoDiff


text:
module BuildTools

const COMONICON_URL = "https://github.com/Roger-luo/Comonicon.jl"
export install, build

using Logging
using PackageCompiler
using Pkg.TOML
using Pkg.PlatformEngines
using ..Comonicon
using ..Comonicon.Configurations
using ..Comonicon.Parse
using ..Comonicon.CodeGen
using ..Comonicon.PATH
using ..Comonicon.Types
using ..Comonicon.Tools

function install(m::Module; kwargs...)
    configs = read_configs(m; kwargs...)
    return install(m, configs)
end

function install(m::Module, configs::Configurations.Comonicon)
    if isempty(ARGS)
        if configs.install.quiet
            logger = NullLogger()
        else
            logger = ConsoleLogger()
        end

        with_logger(logger) do
            install_script(m, configs)
        end
        return
    elseif length(ARGS) == 1
        if "sysimg" in ARGS && !isnothing(configs.sysimg)
            return build_sysimg(m, configs)
        elseif "app" in ARGS && !isnothing(configs.application)
            return build_application(m, configs)
        elseif "tarball" in ARGS && (!isnothing(configs.sysimg) || !isnothing(configs.application))
            return build_tarball(m, configs)
        end
        printstyled("target $(ARGS[1]) not found.")
    end

    print("""
    Comonicon - Installation CLI.

    USAGE

        julia --project deps/build.jl <target>

    ARGS

        <target> [sysimg|app|tarball] specify this argument to build.

    """)
end

"install a script as the CLI"
function install_script(m::Module, configs::Configurations.Comonicon)
    bin = expanduser(joinpath(configs.install.path, "bin"))
    shadow = joinpath(bin, configs.name * ".jl")

    if isnothing(configs.sysimg)
        sysimg = nothing
    else
        download_sysimg(m, configs)
        sysimg = PATH.project(m, configs.sysimg.path, "lib", PATH.sysimg(configs.name))
    end

    shell_script = cmd_script(m, shadow;
        sysimg = sysimg,
        compile = configs.install.compile,
        optimize = configs.install.optimize,
    )
    file = joinpath(bin, configs.name)

    # start writing
    if !ispath(bin)
        @info "cannot find Julia bin folder creating $bin"
        mkpath(bin)
    end

    # generate contents
    @info "generating $shadow"
    open(shadow, "w+") do f
        println(f, "#= generated by Comonicon for $(configs.name) =# using $m; exit($m.command_main())")
    end

    @info "generating $file"
    open(file, "w+") do f
        println(f, shell_script)
    end

    if configs.install.completion
        install_completion(m, configs)
    end

    chmod(file, 0o777)
    return
end

function install_completion(m::Module, configs::Configurations.Comonicon)
    completions_dir = expanduser(joinpath(configs.install.path, "completions"))
    sh = detect_shell()
    sh === nothing && return

    @info "generating auto-completion script for $sh"
    script = completion_script(sh, m)
    script === nothing && return

    if !ispath(completions_dir)
        mkpath(completions_dir)
    end

    completion_file = joinpath(completions_dir, "_" * configs.name)
    @info "writing to $completion_file"
    write(completion_file, script)
    return
end

function build_sysimg(m::Module, configs::Configurations.Comonicon;
        # allow override these two options
        incremental = configs.sysimg.incremental,
        filter_stdlibs = configs.sysimg.filter_stdlibs,
        cpu_target = configs.sysimg.cpu_target,
    )
    lib = PATH.project(m, configs.sysimg.path, "lib")
    if !ispath(lib)
        @info "creating library path: $lib"
        mkpath(lib)
    end

    @info "compile under project: $(PATH.project(m))"
    @info configs.sysimg

    if incremental != configs.sysimg.incremental
        @info "incremental override to $incremental"
    end

    if filter_stdlibs != configs.sysimg.filter_stdlibs
        @info "filter_stdlibs override to $filter_stdlibs"
    end

    if cpu_target != configs.sysimg.cpu_target
        @info "cpu_target override to $cpu_target"
    end

    exec_file = map(x->PATH.project(m, "deps", x), configs.sysimg.precompile.execution_file)
    stmt_file = map(x->PATH.project(m, "deps", x), configs.sysimg.precompile.statements_file)

    create_sysimage(
        nameof(m);
        sysimage_path = joinpath(lib, PATH.sysimg(configs.name)),
        incremental = incremental,
        filter_stdlibs = filter_stdlibs,
        project = PATH.project(m),
        precompile_execution_file = exec_file,
        precompile_statements_file = stmt_file,
        cpu_target = cpu_target,
    )

    return
end

function build_application(m::Module, configs::Configurations.Comonicon)
    build_dir = PATH.project(m, configs.application.path, configs.name)
    if !ispath(build_dir)
        @info "creating build path: $build_dir"
        mkpath(build_dir)
    end

    @info configs.application

    exec_file = map(x->PATH.project(m, "deps", x), configs.application.precompile.execution_file)
    stmt_file = map(x->PATH.project(m, "deps", x), configs.application.precompile.statements_file)
    create_app(PATH.project(m), build_dir;
        app_name = configs.name,
        precompile_execution_file = exec_file,
        precompile_statements_file = stmt_file,
        incremental = configs.application.incremental,
        filter_stdlibs = configs.application.filter_stdlibs,
        force=true,
        cpu_target = configs.application.cpu_target,
    )

    if configs.install.completion
        @info "generating completion scripts"
        build_completion(m, configs)
    end
    return
end

function build_tarball(m::Module, configs::Configurations.Comonicon)
    @info configs
    if configs.sysimg !== nothing
        build_sysimg(m, configs)
        # pack tarball
        tarball = tarball_name(configs.name)
        @info "creating system image tarball $tarball"
        cd(PATH.project(m, configs.sysimg.path)) do
            run(`tar -czvf $tarball lib`)
        end
    end

    if configs.application !== nothing
        build_application(m, configs)
        # pack tarball
        tarball = "application-" * tarball_name(configs.name)
        @info "creating application tarball $tarball"
        cd(PATH.project(m, configs.application.path)) do
            run(`tar -czvf $tarball $(configs.name)`)
        end
    end
    return
end

function download_sysimg(m::Module, configs::Configurations.Comonicon)
    url = sysimg_url(m, configs)
    PlatformEngines.probe_platform_engines!()

    try
        tarball = download(url)
        path = PATH.project(m, configs.sysimg.path)
        unpack(tarball, path)
        # NOTE: sysimg won't be shared, so we can just remove it
        isfile(tarball) && rm(tarball)
    catch e
        @warn "fail to download $url, building the system image locally"
        # force incremental build
        build_sysimg(m, configs; incremental=true, filter_stdlibs=false, cpu_target="native")
    end
    return
end

function build_completion(m::Module, configs::Configurations.Comonicon)
    completion_dir = PATH.project(m, configs.application.path, configs.name, "completions")
    if !ispath(completion_dir)
        @info "creating path: $completion_dir"
        mkpath(completion_dir)
    end

    for sh in ["zsh"]
        script = completion_script(sh, m)
        script === nothing && continue
        write(joinpath(completion_dir, "$sh.completion"), script)
    end
    return
end

function sysimg_url(mod::Module, configs::Configurations.Comonicon)
    name = configs.name
    host = configs.download.host

    if host == "github.com"
        url =
            "https://github.com/" * configs.download.user *
            "/" *
            configs.download.repo *
            "/releases/download/"
    else
        error("host $host is not supported, please open an issue at $COMONICON_URL")
    end

    tarball = tarball_name(name)
    url *= "v$(Comonicon.get_version(mod))/$tarball"
    return url
end

function tarball_name(name::String)
    return "$name-$VERSION-$(osname())-$(Sys.ARCH).tar.gz"
end

"""
    osname()

Return the name of OS, will be used in building tarball.
"""
function osname()
    return Sys.isapple() ? "darwin" :
           Sys.islinux() ? "linux" :
           error("unsupported OS, please open an issue to request support at $COMONICON_URL")
end

"""
    cmd_script(mod, shadow; kwargs...)

Generates a shell script that can be use as the entry of
`mod.command_main`.

# Arguments

- `mod`: a module that contains the commands and the entry.
- `shadow`: location of a Julia script that calls the actual `mod.command_main`.

# Keywords

- `exename`: The julia executable name, default is [`PATH.default_exename`](@ref).
- `sysimg`: System image to use, default is `nothing`.
- `project`: the project path of the CLI.
- `compile`: julia compile level, can be [:yes, :no, :all, :min]
- `optimize`: julia optimization level, default is 2.
"""
function cmd_script(
    mod::Module,
    shadow::String;
    project::String = PATH.project(mod),
    exename::String = PATH.default_exename(),
    sysimg = nothing,
    compile = nothing,
    optimize = 2,
)

    head = "#!/bin/sh\n"
    if (project !== nothing) && ispath(project)
        head *= "JULIA_PROJECT=$project "
    end
    head *= exename
    script = String[head]

    if sysimg !== nothing
        push!(script, "-J$sysimg")
    end

    if compile in [:yes, :no, :all, :min]
        push!(script, "--compile=$compile")
    end

    push!(script, "-O$optimize")
    push!(script, "--startup-file=no")
    push!(script, "-- $shadow \$@")

    return join(script, " \\\n    ")
end

function completion_script(sh::String, m::Module)
    isdefined(m, :CASTED_COMMANDS) || error("cannot find Comonicon CLI entry")
    haskey(m.CASTED_COMMANDS, "main") || error("cannot find Comonicon CLI entry")
    main = m.CASTED_COMMANDS["main"]

    if sh == "zsh"
        return CodeGen.codegen(ZSHCompletionCtx(), main)
    else
        @warn(
            "$sh autocompletion is not supported, " *
            "please open an issue at $COMONICON_URL for feature request."
        )
    end
    return
end

Base.write(x::EntryCommand) = write(cachefile(), x)

"""
    write([io], cmd::EntryCommand)

Write the generated CLI script into a Julia script file. Default is the [`cachefile`](@ref).
"""
function Base.write(io::IO, x::EntryCommand)
    println(io, "#= generated by Comonicon =#")
    println(io, prettify(codegen(x)))
    println(io, "command_main()")
end

"""
    detect_shell()

Detect shell type via `SHELL` environment variable.
"""
function detect_shell()
    haskey(ENV, "SHELL") || error("cannot find available shell command")
    return basename(ENV["SHELL"])
end

function contain_comonicon_path(rcfile, env = ENV)
    if !haskey(env, "PATH")
        _contain_path(rcfile) && return true
        return false
    end

    for each in split(env["PATH"], ":")
        each == PATH.default_julia_bin() && return true
    end
    return false
end

function contain_comonicon_fpath(rcfile, env = ENV)
    if !haskey(env, "FPATH")
        _contain_fpath(rcfile) && return true
        return false
    end

    for each in split(env["FPATH"], ":")
        each == PATH.default_julia_fpath() && return true
    end
    return false
end

function _contain_path(rcfile)
    for line in readlines(rcfile)
        if strip(line) == "export PATH=\"\$HOME/.julia/bin:\$PATH\"" ||
           strip(line) == "export PATH=\"$(PATH.default_julia_bin()):\$PATH\""
            return true
        end
    end
    return false
end

function _contain_fpath(rcfile)
    for line in readlines(rcfile)
        if strip(line) == "export FPATH=\$HOME/.julia/completions:\$FPATH" ||
           strip(line) == "export FPATH=\"$(PATH.default_julia_fpath()):\$FPATH\""
            return true
        end
    end
    return false
end

function install_env_path(; yes::Bool = false)
    shell = detect_shell()

    config_file = ""
    if shell == "zsh"
        config_file = joinpath((haskey(ENV, "ZDOTDIR") ? ENV["ZDOTDIR"] : homedir()), ".zshrc")
    elseif shell == "bash"
        config_file = joinpath(homedir(), ".bashrc")
    else
        @warn "auto installation for $shell is not supported, please open an issue under Comonicon.jl"
    end

    write_path(joinpath(homedir(), config_file), yes)
end

"""
    write_path(rcfile[, yes=false])

Write `PATH` and `FPATH` to current shell's rc files (.zshrc, .bashrc)
if they do not exists.
"""
function write_path(rcfile, yes::Bool = false, env = ENV)
    isempty(rcfile) && return

    script = []
    msg = "cannot detect $(PATH.default_julia_bin()) in PATH, do you want to add it in PATH?"

    if !contain_comonicon_path(rcfile, env) && Tools.prompt(msg, yes)
        push!(
            script,
            """
            # generated by Comonicon
            # Julia bin PATH
            export PATH="$(PATH.default_julia_bin()):\$PATH"
            """,
        )
        @info "adding PATH to $rcfile"
    end

    msg = "cannot detect $(PATH.default_julia_fpath()) in FPATH, do you want to add it in FPATH?"
    if !contain_comonicon_fpath(rcfile, env) && Tools.prompt(msg, yes)
        push!(
            script,
            """
            # generated by Comonicon
            # Julia autocompletion PATH
            export FPATH="$(PATH.default_julia_fpath()):\$FPATH"
            autoload -Uz compinit && compinit
            """,
        )
        @info "adding FPATH to $rcfile"
    end

    # exit if nothing to add
    isempty(script) && return
    # NOTE: we don't create the file if not exists
    open(rcfile, "a") do io
        write(io, "\n" * join(script, "\n"))
    end
    @info "open a new terminal, or source $rcfile to enable the new PATH."
    return
end

end # BuildTools


text:
# math built-ins
println(round(30.123))
println(round(30.124, RoundUp))
println(abs(-12))

# 'is' functions
println(isdigit('1'))
println(isdigit('a'))

max_stars_repo_name: rpillar/JuliaExamples

max_issues_repo_name: rpillar/JuliaExamples

max_forks_repo_name: rpillar/JuliaExamples


text:
### A Pluto.jl notebook ###
# v0.17.4

using Markdown
using InteractiveUtils

# ╔═╡ 6a407bd2-802c-11ec-0c58-b3f5f4ac5b8f
md"""
# Functions

Topics:
1. How to declare a function
2. Duck-typing in Julia
3. Mutating vs. non-mutating functions
4. Some higher order functions

"""

# ╔═╡ e75e9339-1d60-4d04-9063-a5885d0113f8
md"""
## How to declare a function
Julia gives us a few different ways to write a function. The first requires the `function` and `end` keywords
"""

# ╔═╡ a637b4f0-bc1a-4071-be73-d00c257d4266
md"### 1. Default variant:"

# ╔═╡ 07ed7ab9-a5a4-418c-b71f-5c2bc681eeb3
function sayhi(name)

    return "Hi $name, it's great to see you!"
end

# ╔═╡ fd562b69-53ba-4554-9897-6c5709e237fe
md"
We can call either of these functions like this:"

# ╔═╡ 809baaa8-339b-4b70-bb21-8c24a9cab24f
sayhi("C-3PO")

# ╔═╡ 200a1cbb-9e71-4996-8d4f-8204a8701344
md"### 2. Shortcut variant:"

# ╔═╡ 53a47fcd-52f7-49bf-a274-97496258ace6
md"Alternatively, we could have declared either of these functions in a single line"

# ╔═╡ 2413ce2b-ee1b-4d3f-ad8d-ca4064b19d7c
sayhi2(name) = "Hi $name, it's great to see you!"

# ╔═╡ 40666547-6209-42fd-a14f-4c944a5e30ab
sayhi2("R2D2")

# ╔═╡ 9e4f43a7-58e7-4b86-b874-e5cb44298e23
md"### 3. Anonymous functions:"

# ╔═╡ 162585b0-936d-4f9f-8667-3e8c5a1b7212
md"""Finally, we could have declared these as "anonymous" functions"""

# ╔═╡ 191be85a-9cce-4af2-bbe8-6c45d7ab30df
name -> "Hi $name, it's great to see you!"

# ╔═╡ 3a74682e-72ff-4f91-9884-52e26c64c427
sayhi3("Chewbacca")

# ╔═╡ bcd284f3-cb2a-482e-ba27-9a5f7a86c046
md"""
## Duck-typing in Julia
> If it quacks like a duck, it's a duck.

Julia functions will just work on whatever inputs make sense.

For example, sayhi works on the name of this minor tv character, written as an integer...

"""

# ╔═╡ bd4182c4-18ed-41f4-a3f1-50aa553b4924
sayhi(55595472)

# ╔═╡ 47d3b1f8-cc8d-4b0e-89b6-f1d7d06337f8
md"
And `f` will work on a matrix."

# ╔═╡ c4b6e75d-0a51-4159-b7f7-292f5e25d1be
A = rand(3, 3)

# ╔═╡ ec0b4d04-44ce-40ae-b807-8ba38a6a8b59
md"""`f` will also work on a string like "hi" because * is defined for string inputs as string concatenation."""

# ╔═╡ f95f8ea9-1478-4ac2-8e22-085fc6516e56
md"""On the other hand, `f` will not work on a vector. Unlike `A^2`, which is well-defined, the meaning of `v^2` for a vector, `v`, is not a well-defined algebraic operation. """

# ╔═╡ 5620baa2-2aed-49eb-84a1-9fcfdee568c9
v = rand(3)

# ╔═╡ 23b408a4-a91a-4828-a7d8-038325e31d64
Base.:^(x::Vector, i::Int64) = x .^ i

# ╔═╡ 6a407bd2-802c-11ec-3ed6-a7acf451b25a
begin
	bigbreak = HTML("
"^4);
	using PlutoUI
	using Test
end

# ╔═╡ ed5addf3-f534-4ab7-944d-ca948c177ce4
bigbreak

# ╔═╡ e9cf1e4e-e5fa-46de-ab86-f7a35e5b1302
bigbreak

# ╔═╡ dbd09e73-afa6-4065-baee-342951e10031
bigbreak

# ╔═╡ aa60f167-fa1f-4a7e-9d87-eeba0146ed0f
bigbreak

# ╔═╡ f8ce588a-cf7f-4b61-ac9f-afb520003571
function f(x)
    return x^2
end

# ╔═╡ 928f37b1-9059-4b6e-b733-89997203c138
f2(x) = x^2

# ╔═╡ b7a066ac-7d19-4449-bb1e-bf7fcab4eadc
f2(42)

# ╔═╡ 51955b6a-9508-49ca-b435-ba54fb8deb1a
f4(x) = x^2

# ╔═╡ 6b2b763f-6223-4c50-93a0-54680c4313ff
f3 = x -> x^2

# ╔═╡ 353c23a2-4e6d-401f-8a0c-8038f4ef0998
f3(42)

# ╔═╡ 652b18db-731f-4283-b5dc-26dc95bf25a5
bigbreak

# ╔═╡ 5bea284c-9eed-467a-b916-1ba6bc99495f
md"""
## Mutating vs. non-mutating functions

By convention, functions followed by `!` alter their contents and functions lacking `!` do not.

For example, let's look at the difference between `sort` and `sort!`.
"""

# ╔═╡ ee1468af-b2d0-4505-ad59-4b04437d0751
let
	w = [3, 1, 2]
	w = sort(w)
	w
end

# ╔═╡ 1589bbe3-15c4-495b-aae2-eac2f421b2f8
md"""
`sort(w)` returns a sorted array that contains the same elements as `w`, but `w` is left unchanged.

On the other hand, when we run `sort!(w)`, the contents of `w` are sorted within the array `w`.
"""

# ╔═╡ efb84537-41b6-41af-9205-9df193d49dcc
let
	w = [3, 1, 2]
	sort!(w)
	w
end

# ╔═╡ d52cb505-1492-4e64-869a-d598fd99d361
bigbreak

# ╔═╡ 2bc704f5-82e8-4a43-b5ae-1cdbfc18747b
md"""
## Optional arguments and keyword arguments

There are three kind of arguments:

- **Required arguments**, first in the list
- **Optional arguments**, with `= value` to indicate default choice (order matters!)
- **Keword arguments**, after a semicolon `;`. (order does not matter, only keywords)
"""

# ╔═╡ d451cbb3-fd29-46c8-a4ff-a36db76c97ca
md"""
```julia
function fnc( req1, req2,
		      op1 = dv1, opt2 = dv2;
   			  kw1 = kval1, kw2 = kval2)

end
```

"""

# ╔═╡ 3eb97bc4-0ea7-466c-a237-f40fc8b43a71
function g(x, y = 1.0; a = 2, b = 3)
	return (;x,y,a,b)
end

# ╔═╡ 3b57b7eb-d4ea-4668-a702-48d1950e8d63
md"You can call this function in this way:"

# ╔═╡ ce4831c8-e098-4837-901f-383a445a2998
g("🐱"; b = "🐶")

# ╔═╡ 216540a3-0c24-4854-b17e-cd1623e93c43
g("🐱", "🐶")

# ╔═╡ 2de2c2bb-1b17-4085-a0d7-de322fb21370
g("🐱"; a = "🐶")

# ╔═╡ 8eee9f7d-7645-4ff1-b4ef-04dbb4da386e
g("🐱")

# ╔═╡ 3c315931-12fb-4b86-b745-9053cb8ccf68
md"_It is recommended (but optional) to separate optional arguments and keyword arguments with ';'_"

# ╔═╡ e8a86b02-ecc9-479b-97b9-68e3f6ba410a
bigbreak

# ╔═╡ 20f9c597-b1a9-4f32-bca4-ad174f3b78ed
md"""
## Some higher order functions

### map

`map` is a "higher-order" function in Julia that *takes a function* as one of its input arguments.
`map` then applies that function to every element of the data structure you pass it. For example, executing

```julia
map(f, [1, 2, 3])
```
will give you an output array where the function `f` has been applied to all elements of `[1, 2, 3]`
```julia
[f(1), f(2), f(3)]
```
"""

# ╔═╡ 2b69252d-cf04-40b6-95d9-eacf369c4dc5
md"""
Here we've squared all the elements of the vector `[1, 2, 3]`, rather than squaring the vector `[1, 2, 3]`.

To do this, we could have passed to `map` an anonymous function rather than a named function, such as
"""

# ╔═╡ 88dbda57-a845-404e-b680-31c2a473794e
x -> x^3

# ╔═╡ b53c7e60-559c-4d92-99bb-f970f44e48cb
md"via"

# ╔═╡ d0c94953-b7a5-4749-aa4b-b5286233767c
map(x -> x^3, [1, 2, 3])

# ╔═╡ ebb4b925-9c00-471e-b248-d7f5c7794ca0
md"and now we've cubed all the elements of `[1, 2, 3]`!"

# ╔═╡ 76d3ebff-1e69-4f7a-bb55-a10f5f83c806
with_terminal() do
	println("test")
end

# ╔═╡ 783648ba-d6b5-4cdf-91ac-13323255428a
bigbreak

# ╔═╡ e452fd6d-fb31-43b6-b399-0863784115fc
md"""
## broadcast

`broadcast` is another higher-order function like `map`. `broadcast` is a generalization of `map`, so it can do every thing `map` can do and more. The syntax for calling `broadcast` is the same as for calling `map`
"""

# ╔═╡ 69e8fbb4-38e2-402b-89a0-018b4083d7a1
md"""
and again, we've applied `f` (squared) to all the elements of `[1, 2, 3]` - this time by "broadcasting" `f`!

Some syntactic sugar for calling `broadcast` is to place a `.` between the name of the function you want to `broadcast` and its input arguments. For example,

```julia
broadcast(f, [1, 2, 3])
```
is the same as
```julia
f.([1, 2, 3])
```
"""

# ╔═╡ a20f8214-0126-43f4-8a56-bcfed741463f
md"""
Notice again how different this is from calling
```julia
f([1, 2, 3])
```
We can square every element of a vector, but we can't square a vector!
"""

# ╔═╡ 0bdca8db-d6f6-4cc3-8661-98c5cb72fdae
md"""
To drive home the point, let's look at the difference between

```julia
f(A)
```
and
```julia
f.(A)
```
for a matrix `A`:
"""

# ╔═╡ 963169eb-9244-4bbd-8337-65fec67c725c
A

# ╔═╡ 7b411fc5-7ab1-4052-a199-3195a931860a
B = [i + 3*j for j in 0:2, i in 1:3]

# ╔═╡ 99edddcb-73ac-4848-9540-d8ac22f80e5f

# ╔═╡ 3dafca9a-56ee-4a83-9853-917efaf6a93d
md"""
As before we see that for a matrix, `A`,
```
f(A) = A^2 = A * A
```

On the other hand,
"""

# ╔═╡ e56b633e-8b74-4629-b564-f9a4a656626f
f(x,y) = 10*x + y

# ╔═╡ 87ceb82b-94a7-4e5a-84da-f56e24279755
f(42)

# ╔═╡ 9b8a0c73-27f4-48af-b13b-0af1711440b3
f(A)

# ╔═╡ 04a0dbd8-d8f2-434b-8968-63ee989b3131
f("hi")

# ╔═╡ 770cafe7-11e8-4b1c-86c9-b51e4c20ec42
# This won't work:
f(v)

# ╔═╡ 01590c72-ec3e-4db5-957a-02308a6a5e6c
map(f, [1, 2, 3])

# ╔═╡ 928bf981-ba2d-4eb7-9b31-8dd88be33976
broadcast(f, [1, 2, 3])

# ╔═╡ 413e16a6-c8af-43b5-85ab-e6bd16690006
f.( [1, 2, 3] )

# ╔═╡ 33b1ce0b-371d-4af8-af52-7328e4e5b70b
f(B) == B*B == B^2

# ╔═╡ f3efe46a-f1bc-4792-884d-b29ef98595df
f.(B) == B .^ 2

# ╔═╡ 1537e7c8-4e74-4eb5-8a26-7dbb3af600ab
f.( [1,2], 2)

# ╔═╡ a18cdba8-8969-4715-992e-2f3ea339c6e1
md"""
contains the squares of all the entries of `A`.

This dot syntax for broadcasting allows us to write relatively complex compound elementwise expressions in a way that looks natural/closer to mathematical notation. For example, we can write
"""

# ╔═╡ 59fea39e-dea8-4412-8cd3-64b4cede04db
A .+ 2 .* f.(A) ./ A

# ╔═╡ e29c6b03-1128-4928-9878-289886dbef38
A .+ 2 .* f.(A) ./ A

# ╔═╡ 4477326e-bef1-4b74-a24b-c421fcb60c18
md"or"

# ╔═╡ f0dead06-58f6-4c97-9f14-5000b79be8cb
@. A + 2 * f(A) / A

# ╔═╡ 429ec6d9-f80c-4ca7-85b8-db8096f826ea
md"instead of"

# ╔═╡ c6f86e8a-05e8-4ad3-8e7c-3598b22a4779
broadcast(x -> x + 2 * f(x) / x, A)

# ╔═╡ 60eea80b-a510-455b-a2a4-d16366303b87
md"and the two will do exactly the same!"

# ╔═╡ 63032f05-4635-438b-9c56-5571620a180d
bigbreak

# ╔═╡ 62d01357-fdc5-4bd2-9b32-d7515c0c44f6
md"""### Fusing the dot operator

Composition of `broadcast` is suboptimal, as in each step an intermediate resulat is stored, i.e.
```julia
A .+ B .+ C == broadcast( (r+c) -> r + c, broadcast( (a,b) -> a+b, A, B), C)
```

But with a hand _macro_ we can fuse those broadcast calls, i.e.
```julia
@. A + B + C  ==   broadcast( (a,b,c) -> a+b+c, A, B, C)
```
"""

# ╔═╡ e4f1a223-ff88-4588-94b1-1bd6aeb0e856
A .* A .* A

# ╔═╡ 94070b96-502c-4685-bdb9-f6ee6fe55a06
@. A * A * A

# ╔═╡ 281ac3be-aaea-4078-9fb0-391997924306

# ╔═╡ 96e07c5b-6a14-4aa0-8d96-99aaf19a235f
@allocated @. A * A * A

# ╔═╡ 7617e358-c1bf-42b8-b0e3-6e50f993ec2e
@allocated A .* A .* A

# ╔═╡ 6b3f422f-e8cc-480c-aede-296808d063fc
bigbreak

# ╔═╡ fc405e7f-d062-41ed-bcc3-5599e80e3467
md"""
## Exercises

6.1

Write a function `add_one` that adds 1 to its input.
"""

# ╔═╡ 1e1b4cca-0753-440b-9eb2-6f8382964e71

# ╔═╡ 68c25eb9-c682-4f7d-8c82-1e786557de01

# ╔═╡ 86edb75d-a484-4d7f-bf9a-bc6b97cce3ec
( @isdefined(add_one) &&
hasmethod(add_one, Tuple{Union{Any,Int,Float64}}) &&
add_one(11) == 12)

# ╔═╡ 185ffedc-8462-4ef3-add4-9ba371e236ee
bigbreak

# ╔═╡ 56ead0a8-4c9b-4e07-9a8c-0c56b4b30c21
md"""
6.2

Use map or broadcast to increment every element of matrix `B` by 1 and assign it to a variable `A1`.
"""

# ╔═╡ 8e725749-3c7a-413d-8838-49e471c8b47e
B

# ╔═╡ bd5e73ca-a683-476b-8239-ddf57cba89ba
begin
	A1 = B
end

# ╔═╡ e11ca969-a16a-451d-9f6f-5f826d18a55f

# ╔═╡ b9d3ae00-42a2-4007-a0f9-3327d656a425
A1 == [2 3 4; 5 6 7; 8 9 10]

# ╔═╡ e83429b7-77e9-4efe-a365-827b7234c360
bigbreak

# ╔═╡ 93609093-4353-44a3-be9f-5ac41ec69c81
md"""
6.3

Use the broadcast dot syntax to increment every element of matrix `A1` by `1` and store it in variable `A2`
"""

# ╔═╡ 43917a91-235f-49ab-9a60-dc0dfd9bcb4d
begin
	A2 = A1
end

# ╔═╡ da127939-3adc-40c9-9031-db9dd3de847b

# ╔═╡ 15459bb0-bd18-4d48-b39a-193f7a01b1f8
A2 == [3 4 5; 6 7 8; 9 10 11]

# ╔═╡ 00000000-0000-0000-0000-000000000001
PLUTO_PROJECT_TOML_CONTENTS = """
[deps]
PlutoUI = "7f904dfe-b85e-4ff6-b463-dae2292396a8"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[compat]
PlutoUI = "~0.7.32"
"""

# ╔═╡ 00000000-0000-0000-0000-000000000002
PLUTO_MANIFEST_TOML_CONTENTS = """
# This file is machine-generated - editing it directly is not advised

julia_version = "1.7.1"
manifest_format = "2.0"

[[deps.AbstractPlutoDingetjes]]
deps = ["Pkg"]
git-tree-sha1 = "8eaf9f1b4921132a4cff3f36a1d9ba923b14a481"
uuid = "6e696c72-6542-2067-7265-42206c756150"
version = "1.1.4"

[[deps.ArgTools]]
uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f"

[[deps.Artifacts]]
uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33"

[[deps.Base64]]
uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"

[[deps.ColorTypes]]
deps = ["FixedPointNumbers", "Random"]
git-tree-sha1 = "024fe24d83e4a5bf5fc80501a314ce0d1aa35597"
uuid = "3da002f7-5984-5a60-b8a6-cbb66c0b333f"
version = "0.11.0"

[[deps.CompilerSupportLibraries_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae"

[[deps.Dates]]
deps = ["Printf"]
uuid = "ade2ca70-3891-5945-98fb-dc099432e06a"

[[deps.Downloads]]
deps = ["ArgTools", "LibCURL", "NetworkOptions"]
uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6"

[[deps.FixedPointNumbers]]
deps = ["Statistics"]
git-tree-sha1 = "335bfdceacc84c5cdf16aadc768aa5ddfc5383cc"
uuid = "53c48c17-4a7d-5ca2-90c5-79b7896eea93"
version = "0.8.4"

[[deps.Hyperscript]]
deps = ["Test"]
git-tree-sha1 = "8d511d5b81240fc8e6802386302675bdf47737b9"
uuid = "47d2ed2b-36de-50cf-bf87-49c2cf4b8b91"
version = "0.0.4"

[[deps.HypertextLiteral]]
git-tree-sha1 = "2b078b5a615c6c0396c77810d92ee8c6f470d238"
uuid = "ac1192a8-f4b3-4bfe-ba22-af5b92cd3ab2"
version = "0.9.3"

[[deps.IOCapture]]
deps = ["Logging", "Random"]
git-tree-sha1 = "f7be53659ab06ddc986428d3a9dcc95f6fa6705a"
uuid = "b5f81e59-6552-4d32-b1f0-c071b021bf89"
version = "0.2.2"

[[deps.InteractiveUtils]]
deps = ["Markdown"]
uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240"

[[deps.JSON]]
deps = ["Dates", "Mmap", "Parsers", "Unicode"]
git-tree-sha1 = "8076680b162ada2a031f707ac7b4953e30667a37"
uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
version = "0.21.2"

[[deps.LibCURL]]
deps = ["LibCURL_jll", "MozillaCACerts_jll"]
uuid = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21"

[[deps.LibCURL_jll]]
deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll", "Zlib_jll", "nghttp2_jll"]
uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0"

[[deps.LibGit2]]
deps = ["Base64", "NetworkOptions", "Printf", "SHA"]
uuid = "76f85450-5226-5b5a-8eaa-529ad045b433"

[[deps.LibSSH2_jll]]
deps = ["Artifacts", "Libdl", "MbedTLS_jll"]
uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8"

[[deps.Libdl]]
uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb"

[[deps.LinearAlgebra]]
deps = ["Libdl", "libblastrampoline_jll"]
uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"

[[deps.Logging]]
uuid = "56ddb016-857b-54e1-b83d-db4d58db5568"

[[deps.Markdown]]
deps = ["Base64"]
uuid = "d6f4376e-aef5-505a-96c1-9c027394607a"

[[deps.MbedTLS_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1"

[[deps.Mmap]]
uuid = "a63ad114-7e13-5084-954f-fe012c677804"

[[deps.MozillaCACerts_jll]]
uuid = "14a3606d-f60d-562e-9121-12d972cd8159"

[[deps.NetworkOptions]]
uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908"

[[deps.OpenBLAS_jll]]
deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"]
uuid = "4536629a-c528-5b80-bd46-f80d51c5b363"

[[deps.Parsers]]
deps = ["Dates"]
git-tree-sha1 = "0b5cfbb704034b5b4c1869e36634438a047df065"
uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0"
version = "2.2.1"

[[deps.Pkg]]
deps = ["Artifacts", "Dates", "Downloads", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "REPL", "Random", "SHA", "Serialization", "TOML", "Tar", "UUIDs", "p7zip_jll"]
uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"

[[deps.PlutoUI]]
deps = ["AbstractPlutoDingetjes", "Base64", "ColorTypes", "Dates", "Hyperscript", "HypertextLiteral", "IOCapture", "InteractiveUtils", "JSON", "Logging", "Markdown", "Random", "Reexport", "UUIDs"]
git-tree-sha1 = "ae6145ca68947569058866e443df69587acc1806"
uuid = "7f904dfe-b85e-4ff6-b463-dae2292396a8"
version = "0.7.32"

[[deps.Printf]]
deps = ["Unicode"]
uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7"

[[deps.REPL]]
deps = ["InteractiveUtils", "Markdown", "Sockets", "Unicode"]
uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"

[[deps.Random]]
deps = ["SHA", "Serialization"]
uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"

[[deps.Reexport]]
git-tree-sha1 = "45e428421666073eab6f2da5c9d310d99bb12f9b"
uuid = "189a3867-3050-52da-a836-e630ba90ab69"
version = "1.2.2"

[[deps.SHA]]
uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce"

[[deps.Serialization]]
uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b"

[[deps.Sockets]]
uuid = "6462fe0b-24de-5631-8697-dd941f90decc"

[[deps.SparseArrays]]
deps = ["LinearAlgebra", "Random"]
uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"

[[deps.Statistics]]
deps = ["LinearAlgebra", "SparseArrays"]
uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"

[[deps.TOML]]
deps = ["Dates"]
uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76"

[[deps.Tar]]
deps = ["ArgTools", "SHA"]
uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e"

[[deps.Test]]
deps = ["InteractiveUtils", "Logging", "Random", "Serialization"]
uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[[deps.UUIDs]]
deps = ["Random", "SHA"]
uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"

[[deps.Unicode]]
uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5"

[[deps.Zlib_jll]]
deps = ["Libdl"]
uuid = "83775a58-1f1d-513f-b197-d71354ab007a"

[[deps.libblastrampoline_jll]]
deps = ["Artifacts", "Libdl", "OpenBLAS_jll"]
uuid = "8e850b90-86db-534c-a0d3-1478176c7d93"

[[deps.nghttp2_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d"

[[deps.p7zip_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0"
"""

# ╔═╡ Cell order:
# ╟─6a407bd2-802c-11ec-3ed6-a7acf451b25a
# ╟─6a407bd2-802c-11ec-0c58-b3f5f4ac5b8f
# ╟─e75e9339-1d60-4d04-9063-a5885d0113f8
# ╟─a637b4f0-bc1a-4071-be73-d00c257d4266
# ╠═07ed7ab9-a5a4-418c-b71f-5c2bc681eeb3
# ╠═f8ce588a-cf7f-4b61-ac9f-afb520003571
# ╟─fd562b69-53ba-4554-9897-6c5709e237fe
# ╠═809baaa8-339b-4b70-bb21-8c24a9cab24f
# ╠═87ceb82b-94a7-4e5a-84da-f56e24279755
# ╟─ed5addf3-f534-4ab7-944d-ca948c177ce4
# ╟─200a1cbb-9e71-4996-8d4f-8204a8701344
# ╟─53a47fcd-52f7-49bf-a274-97496258ace6
# ╠═2413ce2b-ee1b-4d3f-ad8d-ca4064b19d7c
# ╠═928f37b1-9059-4b6e-b733-89997203c138
# ╠═51955b6a-9508-49ca-b435-ba54fb8deb1a
# ╠═40666547-6209-42fd-a14f-4c944a5e30ab
# ╠═b7a066ac-7d19-4449-bb1e-bf7fcab4eadc
# ╟─e9cf1e4e-e5fa-46de-ab86-f7a35e5b1302
# ╟─9e4f43a7-58e7-4b86-b874-e5cb44298e23
# ╟─162585b0-936d-4f9f-8667-3e8c5a1b7212
# ╠═191be85a-9cce-4af2-bbe8-6c45d7ab30df
# ╠═6b2b763f-6223-4c50-93a0-54680c4313ff
# ╠═3a74682e-72ff-4f91-9884-52e26c64c427
# ╠═353c23a2-4e6d-401f-8a0c-8038f4ef0998
# ╟─dbd09e73-afa6-4065-baee-342951e10031
# ╟─bcd284f3-cb2a-482e-ba27-9a5f7a86c046
# ╠═bd4182c4-18ed-41f4-a3f1-50aa553b4924
# ╟─47d3b1f8-cc8d-4b0e-89b6-f1d7d06337f8
# ╠═c4b6e75d-0a51-4159-b7f7-292f5e25d1be
# ╠═9b8a0c73-27f4-48af-b13b-0af1711440b3
# ╟─ec0b4d04-44ce-40ae-b807-8ba38a6a8b59
# ╠═04a0dbd8-d8f2-434b-8968-63ee989b3131
# ╟─f95f8ea9-1478-4ac2-8e22-085fc6516e56
# ╠═5620baa2-2aed-49eb-84a1-9fcfdee568c9
# ╟─aa60f167-fa1f-4a7e-9d87-eeba0146ed0f
# ╠═770cafe7-11e8-4b1c-86c9-b51e4c20ec42
# ╠═23b408a4-a91a-4828-a7d8-038325e31d64
# ╟─652b18db-731f-4283-b5dc-26dc95bf25a5
# ╟─5bea284c-9eed-467a-b916-1ba6bc99495f
# ╠═ee1468af-b2d0-4505-ad59-4b04437d0751
# ╟─1589bbe3-15c4-495b-aae2-eac2f421b2f8
# ╠═efb84537-41b6-41af-9205-9df193d49dcc
# ╟─d52cb505-1492-4e64-869a-d598fd99d361
# ╟─2bc704f5-82e8-4a43-b5ae-1cdbfc18747b
# ╟─d451cbb3-fd29-46c8-a4ff-a36db76c97ca
# ╠═3eb97bc4-0ea7-466c-a237-f40fc8b43a71
# ╟─3b57b7eb-d4ea-4668-a702-48d1950e8d63
# ╠═ce4831c8-e098-4837-901f-383a445a2998
# ╠═216540a3-0c24-4854-b17e-cd1623e93c43
# ╠═2de2c2bb-1b17-4085-a0d7-de322fb21370
# ╠═8eee9f7d-7645-4ff1-b4ef-04dbb4da386e
# ╟─3c315931-12fb-4b86-b745-9053cb8ccf68
# ╟─e8a86b02-ecc9-479b-97b9-68e3f6ba410a
# ╟─20f9c597-b1a9-4f32-bca4-ad174f3b78ed
# ╠═01590c72-ec3e-4db5-957a-02308a6a5e6c
# ╟─2b69252d-cf04-40b6-95d9-eacf369c4dc5
# ╠═88dbda57-a845-404e-b680-31c2a473794e
# ╟─b53c7e60-559c-4d92-99bb-f970f44e48cb
# ╠═d0c94953-b7a5-4749-aa4b-b5286233767c
# ╟─ebb4b925-9c00-471e-b248-d7f5c7794ca0
# ╠═76d3ebff-1e69-4f7a-bb55-a10f5f83c806
# ╟─783648ba-d6b5-4cdf-91ac-13323255428a
# ╟─e452fd6d-fb31-43b6-b399-0863784115fc
# ╠═928bf981-ba2d-4eb7-9b31-8dd88be33976
# ╟─69e8fbb4-38e2-402b-89a0-018b4083d7a1
# ╠═413e16a6-c8af-43b5-85ab-e6bd16690006
# ╟─a20f8214-0126-43f4-8a56-bcfed741463f
# ╟─0bdca8db-d6f6-4cc3-8661-98c5cb72fdae
# ╠═963169eb-9244-4bbd-8337-65fec67c725c
# ╠═7b411fc5-7ab1-4052-a199-3195a931860a
# ╠═99edddcb-73ac-4848-9540-d8ac22f80e5f
# ╠═33b1ce0b-371d-4af8-af52-7328e4e5b70b
# ╟─3dafca9a-56ee-4a83-9853-917efaf6a93d
# ╠═f3efe46a-f1bc-4792-884d-b29ef98595df
# ╠═e56b633e-8b74-4629-b564-f9a4a656626f
# ╠═1537e7c8-4e74-4eb5-8a26-7dbb3af600ab
# ╟─a18cdba8-8969-4715-992e-2f3ea339c6e1
# ╠═59fea39e-dea8-4412-8cd3-64b4cede04db
# ╠═e29c6b03-1128-4928-9878-289886dbef38
# ╟─4477326e-bef1-4b74-a24b-c421fcb60c18
# ╠═f0dead06-58f6-4c97-9f14-5000b79be8cb
# ╟─429ec6d9-f80c-4ca7-85b8-db8096f826ea
# ╠═c6f86e8a-05e8-4ad3-8e7c-3598b22a4779
# ╟─60eea80b-a510-455b-a2a4-d16366303b87
# ╟─63032f05-4635-438b-9c56-5571620a180d
# ╠═62d01357-fdc5-4bd2-9b32-d7515c0c44f6
# ╠═e4f1a223-ff88-4588-94b1-1bd6aeb0e856
# ╠═94070b96-502c-4685-bdb9-f6ee6fe55a06
# ╠═281ac3be-aaea-4078-9fb0-391997924306
# ╠═96e07c5b-6a14-4aa0-8d96-99aaf19a235f
# ╠═7617e358-c1bf-42b8-b0e3-6e50f993ec2e
# ╟─6b3f422f-e8cc-480c-aede-296808d063fc
# ╠═fc405e7f-d062-41ed-bcc3-5599e80e3467
# ╠═1e1b4cca-0753-440b-9eb2-6f8382964e71
# ╟─68c25eb9-c682-4f7d-8c82-1e786557de01
# ╟─86edb75d-a484-4d7f-bf9a-bc6b97cce3ec
# ╟─185ffedc-8462-4ef3-add4-9ba371e236ee
# ╟─56ead0a8-4c9b-4e07-9a8c-0c56b4b30c21
# ╠═8e725749-3c7a-413d-8838-49e471c8b47e
# ╠═bd5e73ca-a683-476b-8239-ddf57cba89ba
# ╟─e11ca969-a16a-451d-9f6f-5f826d18a55f
# ╟─b9d3ae00-42a2-4007-a0f9-3327d656a425
# ╟─e83429b7-77e9-4efe-a365-827b7234c360
# ╠═93609093-4353-44a3-be9f-5ac41ec69c81
# ╠═43917a91-235f-49ab-9a60-dc0dfd9bcb4d
# ╟─da127939-3adc-40c9-9031-db9dd3de847b
# ╟─15459bb0-bd18-4d48-b39a-193f7a01b1f8
# ╟─00000000-0000-0000-0000-000000000001
# ╟─00000000-0000-0000-0000-000000000002

max_stars_repo_path: slides/5 - Functions.jl

max_stars_repo_name: SteffenPL/Julia-for-mathematicians

max_issues_repo_path: slides/5 - Functions.jl

max_issues_repo_name: SteffenPL/Julia-for-mathematicians

max_forks_repo_path: slides/5 - Functions.jl

max_forks_repo_name: SteffenPL/Julia-for-mathematicians


text:
"""
    islazy(an::Type{<:AbstractAnalysis})

Should return `true` if the EGraph Analysis `an` is lazy
and false otherwise. A *lazy* EGraph Analysis is computed
only when [analyze!](@ref) is called. *Non-lazy*
analyses are instead computed on-the-fly every time ENodes are added to the EGraph or
EClasses are merged.
"""
islazy(an::Type{<:AbstractAnalysis})::Bool = false

"""
    modify!(an::Type{<:AbstractAnalysis}, g, id)

The `modify!` function for EGraph Analysis can optionally modify the eclass
`g[id]` after it has been analyzed, typically by adding an ENode.
It should be **idempotent** if no other changes occur to the EClass.
(See the [egg paper](https://dl.acm.org/doi/pdf/10.1145/3434304)).
"""
modify!(analysis::Type{<:AbstractAnalysis}, g, id) = nothing

"""
    join(an::Type{<:AbstractAnalysis}, a, b)

Joins two analyses values into a single one, used by [analyze!](@ref)
when two eclasses are being merged or the analysis is being constructed.
"""
join(analysis::Type{<:AbstractAnalysis}, a, b) =
    error("Analysis does not implement join")

"""
    make(an::Type{<:AbstractAnalysis}, g, n)

Given an ENode `n`, `make` should return the corresponding analysis value.
"""
make(analysis::Type{<:AbstractAnalysis}, g, n) =
    error("Analysis does not implement make")

# TODO default analysis for metadata here
abstract type MetadataAnalysis <: AbstractAnalysis end

analyze!(g::EGraph, an::Type{<:AbstractAnalysis}, id::EClassId) = analyze!(g, an, reachable(g, id))
analyze!(g::EGraph, an::Type{<:AbstractAnalysis}) = analyze!(g, an, collect(keys(g.classes)))

"""
    analyze!(egraph, analysis, [ECLASS_IDS])

Given an [EGraph](@ref) and an `analysis` of type `<:AbstractAnalysis`,
do an automated bottom up trasversal of the EGraph, associating a value from the
domain of `analysis` to each ENode in the egraph by the [make](@ref) function.
Then, for each [EClass](@ref), compute the [join](@ref) of the children ENodes analyses values.
After `analyze!` is called, an analysis value will be associated to each EClass in the EGraph.
One can inspect and retrieve analysis values by using [hasdata](@ref) and [getdata](@ref).
Note that an [EGraph](@ref) can only contain one analysis of type `an`.
"""
function analyze!(g::EGraph, an::Type{<:AbstractAnalysis}, ids::Vector{EClassId})
    push!(g.analyses, an)
    ids = sort(ids)
    # @assert isempty(g.dirty)

    did_something = true
    while did_something
        did_something = false

        for id ∈ ids
            eclass = g[id]
            id = eclass.id
            pass = mapreduce(x -> make(an, g, x), (x, y) -> join(an, x, y), eclass)
            # pass = make_pass(G, analysis, find(G,id))

            # if pass !== missing
            if !isequal(pass, getdata(eclass, an, missing))
                setdata!(eclass, an, pass)
                did_something = true
                push!(g.dirty, id)
            end
        end
    end

    for id ∈ ids
        eclass = g[id]
        id = eclass.id
        if !hasdata(eclass, an)
            error("failed to compute analysis for eclass ", id)
        end
    end

    return true
end

"""
A basic cost function, where the computed cost is the size
(number of children) of the current expression.
"""
function astsize(n::ENodeTerm, g::EGraph, an::Type{<:AbstractAnalysis})
    cost = 1 + arity(n)
    for id ∈ arguments(n)
        eclass = g[id]
        !hasdata(eclass, an) && (cost += Inf; break)
        cost += last(getdata(eclass, an))
    end
    return cost
end

astsize(n::ENodeLiteral, g::EGraph, an::Type{<:AbstractAnalysis}) = 1

"""
A basic cost function, where the computed cost is the size
(number of children) of the current expression, times -1.
Strives to get the largest expression
"""
function astsize_inv(n::ENodeTerm, g::EGraph, an::Type{<:AbstractAnalysis})
    cost = -(1 + arity(n)) # minus sign here is the only difference vs astsize
    for id ∈ arguments(n)
        eclass = g[id]
        !hasdata(eclass, an) && (cost += Inf; break)
        cost += last(getdata(eclass, an))
    end
    return cost
end

astsize_inv(n::ENodeLiteral, g::EGraph, an::Type{<:AbstractAnalysis}) = -1

"""
An [`AbstractAnalysis`](@ref) that computes the cost of expression nodes
and chooses the node with the smallest cost for each E-Class.
This abstract type is parametrised by a function F.
This is useful for the analysis storage in [`EClass`](@ref)
"""
abstract type ExtractionAnalysis{F} <: AbstractAnalysis end

make(a::Type{ExtractionAnalysis{F}}, g::EGraph, n::AbstractENode) where F = (n, F(n, g, a))

join(a::Type{<:ExtractionAnalysis}, from, to) = last(from) <= last(to) ? from : to

islazy(a::Type{<:ExtractionAnalysis}) = true

function rec_extract(g::EGraph, an, id::EClassId; simterm=similarterm, cse_env=nothing)
    eclass = g[id]
    if !isnothing(cse_env) && haskey(cse_env, id)
        (sym, _) = cse_env[id]
        return sym
    end
    anval = getdata(eclass, an, (nothing, Inf))
    (n, ck) = anval
    ck == Inf && error("Infinite cost when extracting enode")

    if n isa ENodeLiteral
        return n.value
    elseif n isa ENodeTerm
        children = map(child -> rec_extract(g, an, child; simterm=simterm, cse_env=cse_env), arguments(n))
        meta = getdata(eclass, MetadataAnalysis, nothing)
        T = termtype(n)
        simterm(T, operation(n), children; metadata=meta, exprhead=exprhead(n));
    else
        error("Unknown ENode Type $(typeof(cn))")
    end
end

"""
Given a cost function, extract the expression
with the smallest computed cost from an [`EGraph`](@ref)
"""
function extract!(g::EGraph, costfun::Function; root=-1, simterm=similarterm, cse=false)
    a = ExtractionAnalysis{costfun}
    if root == -1
        root = g.root
    end
    analyze!(g, a, root)
    if cse
        # TODO make sure there is no assignments/stateful code!!
        cse_env = OrderedDict{EClassId, Tuple{Symbol, Any}}() #
        collect_cse!(g, a, root, cse_env, Set{EClassId}(); simterm=simterm)
        # @show root
        # @show cse_env

        body = rec_extract(g, a, root; simterm=simterm, cse_env=cse_env)

        assignments = [Expr(:(=), name, val) for (id, (name, val)) in cse_env]
        # return body
        Expr(:let, Expr(:block, assignments...), body)
    else
        return rec_extract(g, a, root; simterm=simterm)
    end
end

# Builds a dict e-class id => (symbol, extracted term) of common subexpressions in an e-graph
function collect_cse!(g::EGraph, an, id, cse_env, seen; simterm=similarterm)
    eclass = g[id]
    anval = getdata(eclass, an, (nothing, Inf))
    (cn, ck) = anval
    ck == Inf && error("Error when computing CSE")
    if cn isa ENodeTerm
        if id in seen
            cse_env[id] = (gensym(), rec_extract(g, an, id; simterm=simterm))#, cse_env=cse_env)) # todo generalize symbol?
            return
        end
        for child_id in arguments(cn)
            collect_cse!(g, an, child_id, cse_env, seen; simterm=simterm)
        end
        push!(seen, id)
    end
end

getcost!(g::EGraph, costfun::Function; root=-1) = getcost!(g, ExtractionAnalysis{costfun}; root=root)

function getcost!(g::EGraph, analysis::Type{ExtractionAnalysis{F}}; root=-1) where {F}
    if root == -1
        root = g.root
    end
    analyze!(g, analysis, root)
    bestnode, cost = getdata(g[root], analysis)
    return cost
end


text:
# http://adventofcode.com/2016/day/8

WIDTH = 50
HEIGHT = 6

screen = falses(HEIGHT, WIDTH)

function rect(width, height)
    screen[1:height, 1:width] = true
end

function rotate_col(col, by)
    screen[:, col + 1] = ror!(screen[:, col + 1], by)
end

function rotate_row(row, by)
    screen[row + 1, :] = ror!(screen[row + 1, :], by)
end

function main()
    open("inputs/Day_8_input.txt") do file
        for ln in eachline(file)

            tok = split(ln)

            if tok[1] == "rect"
                args = split(tok[2], "x")
                rect(map(x -> parse(Int, x), args)...)
            elseif tok[2] == "row"
                args = [ tok[3][3:end], tok[5] ]
                rotate_row(map(x -> parse(Int, x), args)...)
            elseif tok[2] == "column"
                args = [ tok[3][3:end], tok[5] ]
                rotate_col(map(x -> parse(Int, x), args)...)
            end

        end
    end

    println("Part 1: ", sum(screen)) # 123

    println("Part 2:\n")
    for i in 1:size(screen, 1)
        for j in screen[i, :]
            if j == true
                print('#')
            else
                print(' ')
            end
        end
        println(' ')
    end

     ##  #### ###  #  # ###  #### ###    ## ###   ###
    #  # #    #  # #  # #  #    # #  #    # #  # #
    #  # ###  ###  #  # #  #   #  ###     # #  # #
    #### #    #  # #  # ###   #   #  #    # ###   ##
    #  # #    #  # #  # #    #    #  # #  # #       #
    #  # #    ###   ##  #    #### ###   ##  #    ###

end

main()


text:
# Permute systems in a state rho_ABCD....
#
# This is a port of QETLAB's permutesystems to Julia.

export permutesystems

""" `rhoOut = permutesystems(rho, perm)` or `rhoOut = permutesystems(rho, perm, dim)` or `rhoOut = permutesystems(rho,perm, dim, row_only)` or `rhoOut = permutesystems(rho,perm, dim, row_only, inv_perm)`

Permutes the systems in the matrix rho.

Inputs:
- *rho* input matrix
- *perm* a permutation vector on all the systems
- *dim* (optional keyword argument) vector with dimensions of all the systems. By default all systems are of equal dimension.
- *row_only* (optional keyword argument) if set to true, only the rows of *rho* are permuted.
- *inv_perm* (optional keyword argument) if set to true, the subsystems are permuted according to the inverse of *perm* rather than *perm* itself.

Outputs:
- *rhoOut* matrix *rho* with permuted systems
"""

function permutesystems(X, perm, dim = false, row_only = false, inv_perm = false)
	dX = collect(size(X));
  	is_vec = (minimum(dX) == 1) || (length(dX) == 1);
  	num_sys = length(perm);
  	if is_vec
    		if length(dX) == 1
      			vec_orien = 2;
    		else
      			vec_orien = 3 - findfirst(x -> x == 1, dX); # 1 if column vector, 2 if row vector;
    		end
  	end

  	# set optional argument defaults: dim=round(lX^(1/num_sys)), row_only=0, inv_perm=0
  	if dim == false
    		dim = [round(dX[1]^(1/num_sys))*ones(1, num_sys); round(dX[2]^(1/num_sys))*ones(1, num_sys)]
  	end

  	# allow the user to enter a vector for dim if X is square
  	if minimum(size(dim)) == 1 || length(size(dim)) == 1
    		dim_tmp = dim[:]' # force dim to be a row vector
    		if is_vec
      			dim = ones(2, length(dim))
      			dim[vec_orien, :] = dim_tmp
    		else
      			dim = [dim_tmp; dim_tmp]
    		end
  	end

  	prod_dimR = prod(dim[1, :])
  	prod_dimC = prod(dim[2, :])

  	# Do some basic input checking.
  	@assert length(perm) == num_sys "length(PERM) must equal length(DIM)."
  	@assert sort(perm) == collect(1:num_sys) "PERM must be a permutation vector."
  	# Permuting systems for pure states is easy enough, so just make the vector
  	# full and then perform the permutation (new-ish versions of MATLAB don't
  	# like sparse multidimensional arrays).
  	if is_vec
    		dim = round(Int, dim)
    		if inv_perm
      			PX = reshape(ipermutedims(reshape(full(X), tuple(dim[vec_orien, end:-1:1]...)), num_sys + 1 - perm[end:-1:1]), dX[1])
    		else
      			PX = reshape(permutedims(reshape(full(X), tuple(dim[vec_orien,end:-1:1]...)), num_sys + 1 - perm[end:-1:1]), dX[1])
    		end
    		#
    		# Preserve the sparsity of X.
    		if issparse(X)
        		PX = sparse(PX)
    		end
    		return PX
  	end

  	# If X is not a pure state, it's slightly trickier... do *not* just use the
  	# same pure state trick with repeated indices though, since that has an
  	# intermediate step of making the matrix a multidimensional array, which
  	# you can't do with sparse matrices in new-ish version of MATLAB. The trick
  	# used here reduces the problem to the pure state version of the problem in
  	# another way that plays nicely with both full and sparse matrices
  	dim = round(Int, dim)
  	row_perm = permutesystems(collect(1:dX[1]), perm, dim[1, :], false, inv_perm)
  	PX = X[row_perm, :]
  	if ! row_only
    		col_perm = permutesystems(collect(1:dX[2]), perm, dim[2, :], false, inv_perm)
    		PX = PX[:, col_perm]
  	end
  	return PX;
end


text:
using ExploringOceanData
@analysisplate

local_directory = "/mnt/podaac_drive/Version4/Release4/interp_monthly/"
# Salinity Stuff
salinity_extrema = []
# Variables of intersest
voi = ["SALT"]
variable = voi[1]
# Find available years
years = readdir(local_directory*variable)
year = years[1]
# Find available months
for year in years
    println("Looking at year ", year)
    months = readdir(joinpath(local_directory*variable,year))
    for month in months
        tic = time()
        println("Looking at month ", month)
        full_path = joinpath(local_directory, variable, year, month)
        # Load Data Set
        ds = Dataset(full_path,"r")

        # Augment list
        salinity = ds[variable]
        push!(salinity_extrema, extrema(salinity[:,:,1,1])) # surface salinity
        println("The salinity extrema are, ", salinity_extrema[end])
        toc = time()
        println("This took ", toc - tic, " seconds to calculate")
        println("----------------------")
    end
end


text:
module QuantumSpin

    export σ⁰, σˣ, σʸ, σᶻ
    include("pauli_matrices.jl")

    export ⊗
    include("kronecker_product.jl")

    export pbc
    include("pbc.jl")

    export lattice
    export issquare
    include("lattice.jl")

    export set_spins
    include("set_spins.jl")

    export on_site_term
    export spin_spin_interaction_term
    export hamiltonian
    include("operators.jl")
end


text:
## Simple PageRank
# This treats everything as a dense matrix :(
A = [0 1 1 1 1 0 1 0 0 0
     1 0 0 0 0 0 0 0 0 0
     0 0 0 0 1 1 1 0 0 0
     1 1 0 0 1 1 0 1 0 0
     1 1 1 1 0 0 0 0 1 0
     0 0 1 0 0 0 1 0 1 1
     0 0 0 0 0 1 0 0 0 1
     0 0 0 0 0 0 0 0 1 0
     0 0 0 0 0 0 0 1 0 0
     0 0 0 0 0 0 0 0 0 0]
d = sum(A,dims=2)
##
using Printf
for i=1:10
  for j=1:10
    if i==j
      @printf("1 & ")
    elseif A[j,i] != 0
      @printf("-\\alpha/%i & ", d[j])
    else
      @printf("0 & ")
    end
  end
  println("\\\\")
end
## Build the I - alpha D^+ A'
alpha = 0.85
M = zeros(size(A)...)
for i=1:10
  for j=1:10
    if i==j
      M[i,j] = 1
    elseif A[j,i] != 0
      M[i,j] = -alpha*A[j,i]/d[j]
    else
      M[i,j] = 0
    end
  end
end
M[:,end] .= -alpha/10
M[end,end] += 1
M[:,1]
##
b = ones(size(A,1))/size(A,1)*(1-alpha)
x = M\b


text:
using StructOpt
using DataStructures, LinearAlgebra, PGFPlotsX, Random, Distributions, Colors

function get_problems()
    problems = Dict()

    nseeds = 1

    ## Lasso nuclear norm
    lnuclear_pbs = []

    # m, n, sparsity = 8^2, 10^2, 6
    m, n, sparsity = 16^2, 20^2, 3
    Random.seed!(1234)
    xstart = rand(Normal(), n) .* 10
    for seed in 1:nseeds
        pb = get_randomlasso(n, m, sparsity, reg=regularizer_lnuclear, seed = seed)
        xorig = pb.x0

        @show get_support(regularizer_lnuclear, xorig)
        @show get_support(regularizer_lnuclear, xstart)

        println("Computing objopt")
        xstar, hist = solve_proxgrad(pb, xstart, extra_FISTA; itmax=1e5, tol=1e-13, saveiter=false)

        objopt = minimum(values(hist[:iter_f]))
        @show objopt
        @show F(pb, xstar)

        push!(lnuclear_pbs, (name = "pblasso_lnuclear", pb = pb, xstart = xstart, objopt = objopt))
    end

    problems["lnuclear_randinit"] = lnuclear_pbs

    ## Lasso nuclear norm - zero init
    lnuclear_zeroinit_pbs = []
    for pb in lnuclear_pbs
        push!(lnuclear_zeroinit_pbs, (name = "pblasso_lnuclear", pb = pb.pb, xstart = zeros(n), objopt = pb.objopt))
    end

    problems["lnuclear_zeroinit"] = lnuclear_zeroinit_pbs

    return problems
end

function get_algorithms()
    algorithms = []

    # αuser = 0.001
    itmax = 3e4
    printstep = 1e3
    push!(algorithms, (
        name="FISTA",
        updatefunc=extra_FISTA,
        params=Dict(
            # :αuser => αuser,
            :printstep => printstep,
            :itmax => itmax,
            :saveiter => false,
        ),
    ))

    push!(algorithms, (
        name="ISTA",
        updatefunc=extra_ISTA,
        params=Dict(
            # :αuser => αuser,
            :printstep => printstep,
            :itmax => itmax,
            :saveiter => false,
        ),
    ))

    push!(algorithms, (
        name="T1",
        updatefunc=extra_CondInertia,
        params=Dict(
            :id_testkind=>:default,
            # :αuser => αuser,
            :printstep => printstep,
            :itmax => itmax,
            :saveiter => false,
        ),
    ))

    push!(algorithms, (
        name="T2",
        updatefunc=extra_CondPredInertia,
        params=Dict(
            # :αuser => αuser,
            :printstep => printstep,
            :itmax => itmax,
            :saveiter => false,
        ),
    ))

    return algorithms
end

function main()

    problems = get_problems()
    algorithms = get_algorithms()
    FIGS_FOLDER="./temp"

    ## Start from interesting point and optimize with ISTA, FISTA.
    problemclass_to_algstats = OrderedDict()
    for (problems_class_name, problem_class) in problems
        printstyled("\n---- Class $(problems_class_name)\n", color=:light_blue)

        algo_to_stats = OrderedDict()
        for (algo_id, algo) in enumerate(algorithms)
            printstyled("\n---- Algorithm $(algo.name)\n", color=:light_blue)

            algo_to_stats[algo] = Dict()
            for (pb_id, pb_data) in enumerate(problem_class)
                printstyled("\n----- Solving problem $pb_id, $(pb_data.name)\n", color=:light_blue)

                xopt, hist = solve_proxgrad(pb_data.pb, pb_data.xstart, algo.updatefunc; algo.params...)
                algo_to_stats[algo][pb_data] = (hist=hist, xopt=xopt)
            end
        end

        problemclass_to_algstats[problems_class_name] = algo_to_stats
    end

    ####################################################
    ## Display suboptimality, iterates position.
    FIGS_FOLDER = "./figs"
    basename(pwd()) == "src" && (FIGS_FOLDER = joinpath("..", FIGS_FOLDER))
    !ispath(FIGS_FOLDER) && mkpath(FIGS_FOLDER)

    alg_to_fig = Dict()

    for (pbclass_name, algo_to_stats) in problemclass_to_algstats
        println("Generating graphs for class ", pbclass_name)

        colors_vec = distinguishable_colors(8) # transform=deuteranopic

        # algo_id = 1
        for (algo, stats) in algo_to_stats
            ps_nidentif, ps_subopt = [], []
            @show algo.name

            ############################################
            ## N identified manifolds - 1st problem
            (pb, pbinstance_stats) = first(stats); delete!(stats, pb)

            reg = typeof(pb.pb).parameters[1]
            opt_supp = get_support(reg, pb.pb.x0)

            coords_nbman_y_identified = simplify_coords([ (k, length(intersect(v, opt_supp))) for (k, v) in pbinstance_stats.hist[:iter_support_y]])
            coords_nbman_y_identified = normalize_identified_coords(coords_nbman_y_identified, opt_supp)

            push!(ps_nidentif, PlotInc(
                PGFPlotsX.Options(
                    "no marks" => nothing,
                    # "line width" => "0.3pt",
                    # "color" => colors_vec[algo_id],
                    get_traj_params(algo.name)...
                ),
                Coordinates(coords_nbman_y_identified)
            ))

            ############################################
            ## subopt - 1st problem
            minval = pb.objopt

            inds = collect(keys(pbinstance_stats.hist[:iter_f]))
            vals = collect(abs.(values(pbinstance_stats.hist[:iter_f]) .- minval))
            coords = [ (inds[i], vals[i]) for i in 1:length(vals) if mod(i, 10) == 0]

            push!(ps_subopt, PlotInc(
                PGFPlotsX.Options(
                    "mark" => "none",
                    "color" => "black",
                ),
                Coordinates(coords)
            ))
            add_identif_point!(ps_subopt, pb, pbinstance_stats, inds, vals, color=get_alg_color(algo.name))

            push!(ps_nidentif, LegendEntry(get_legend(algo.name)))

            fig = @pgf TikzPicture(
                Axis(
                    {
                        "scale only axis",
                        axis_y_line => "left",
                        ymin=0,
                        ymax=100,
                        xmajorgrids,
                        ymajorgrids,
                        yminorgrids,
                        # ylabel_style = "align=center",
                        # ylabel="Plot 1",
                        legend_pos = "north east",
                        legend_cell_align = "left",
                        legend_style = "font=\\footnotesize",
                    },
                    ps_nidentif...
                ),
                Axis(
                    {
                        ymin=1e-9,
                        ymax=1e1,
                        ymode  = "log",
                        # xmode  = "log",
                        "scale only axis",
                        axis_y_line => "right",
                        axis_x_line = "none",
                        # ylabel_style = "align=center",
                        # ylabel = "Plot 2",
                    },
                    ps_subopt...
                ),
            )

            pgfsave(joinpath(FIGS_FOLDER, "$(pbclass_name)_$(algo.name).pdf"), fig)
            pgfsave(joinpath(FIGS_FOLDER, "$(pbclass_name)_$(algo.name).tikz"), fig)
        end
    end

    return
end

main()


text:
"""
    BehavioralData
"""
abstract type BehavioralData <: BIDSDataType end

datatypefolder(::BehavioralData) = "beh"


text:
mutable struct SumTo <: DiffableFunction
    grad_field::GradField
    shape
    x_shape
    SumTo(shape, x_shape) = new(GradField(), shape, x_shape)
end

function _sum_to(x, shape::Tuple{})
    return sum(x)
end

function _sum_to(x, shape)
    x_shape = size(x)
    (shape == x_shape) && (return x)
    if length(x_shape) < length(shape)
        throw(DimensionMismatch("Unexpected shape. length(x_shape) must be bigger than length(shape). But length(x_shape) = $(length(x_shape)), length(shape) = $(length(shape))"))
    elseif length(x_shape) > length(shape)
        n_repeat = length(x_shape) - length(shape)
        repeated_axis = vcat([shape...], ones(Int, n_repeat))
        shape = (repeated_axis...,)
        sum_axis = findall(x -> x != 0, shape .- x_shape)
        result = _sum(x, dims=sum_axis)
        drop_dim = ((length(x_shape) - n_repeat + 1:length(x_shape))...,)
        result = dropdims(result, dims=drop_dim)
    else
        sum_axis = findall(x -> x != 0, shape .- x_shape)
        result = _sum(x, dims=sum_axis)
    end
    return result
end

function forward(f::SumTo, x)
    f.x_shape = size(x)
    y = _sum_to(x, f.shape)
    return y
end

function backward(f::SumTo, gy)
    return broadcast_to(gy, f.x_shape)
end

"""
    sum_to(x, shape)
Take the sum of each axis to form a `shape`.

# Examples
```julia-repl
julia> x = Variable(rand(2, 3))
name: nothing
values: [0.2397911359535343 0.34270903251201146 0.699060178623987; 0.2345132451371843 0.21845435948625758 0.2924942369518322]
creator: User-Defined(nothing)

julia> JITrench.sum_to(x, (2, 1))
name: nothing
values: [1.2815603470895327; 0.7454618415752741]
creator: JITrench.SumTo
```
"""
sum_to(x, shape) = SumTo(shape, size(x.values))(x)


text:
"""
    partition_sources!(queue_list, sources)

Partition `sources` using [`LightGraphs.unweighted_contiguous_partition`](@ref) and place
the i^{th} partition  into `queue_list[i]` and set to empty_list[i] to true if the
i^{th} partition is empty.
"""
function partition_sources!(
    queue_list::Vector{Vector{T}},
    sources::Vector{<:Integer},
    empty_list::Vector{Bool}
    ) where T<:Integer

    partitions = LightGraphs.unweighted_contiguous_partition(length(sources), length(queue_list))
    for (i, p) in enumerate(partitions)
        append!(queue_list[i], sources[p])
        empty_list[i] = isempty(p)
    end
end

"""
    gdistances!(g, sources, vert_level; queue_segment_size=20)
    gdistances!(g, source, vert_level; queue_segment_size=20)

Parallel implementation of [`LightGraphs.gdistances!`](@ref) with dynamic load balancing.

### Optional Arguments
- `queue_segment_size = 20`: It is the number of vertices a thread can claim from a queue
at a time. For graphs with uniform degree, a larger value of `queue_segment_size` could
improve performance.

### References
- [Avoiding Locks and Atomic Instructions in Shared-Memory Parallel BFS Using Optimistic
Parallelization](https://www.computer.org/csdl/proceedings/ipdpsw/2013/4979/00/4979b628-abs.html).
"""
function gdistances!(
    g::AbstractGraph{T},
    sources::Vector{<:Integer},
    vert_level::Vector{T};
    queue_segment_size::Integer=20
    ) where T <:Integer

    nvg = nv(g)
    n_t = nthreads()
    segment_size = convert(T, queue_segment_size) # Type stability
    fill!(vert_level, typemax(T))
    visited = zeros(Bool, nvg)

    #bitVector not thread safe
    next_level_t = [sizehint!(Vector{T}(), cld(nvg, n_t)) for _ in Base.OneTo(n_t)]
    cur_level_t = [sizehint!(Vector{T}(), cld(nvg, n_t)) for _ in Base.OneTo(n_t)]
    cur_front_t = ones(T, n_t)
    queue_explored_t = zeros(Bool, n_t)

    for s in sources
        visited[s] = true
        vert_level[s] = zero(T)
    end
    partition_sources!(cur_level_t, sources, queue_explored_t)
    is_cur_level_t_empty = isempty(sources)
    n_level = zero(T)

    while !is_cur_level_t_empty
        n_level += one(T)

        let n_level=n_level # let block used due to bug #15276
        @threads for thread_id in Base.OneTo(n_t)
            #Explore current level in parallel
            @inbounds next_level = next_level_t[thread_id]

            @inbounds for t_range in (thread_id:n_t, 1:(thread_id-1)), t in t_range
                queue_explored_t[t] && continue
                cur_level = cur_level_t[t]
                cur_len = length(cur_level)

                # Explore cur_level_t[t] one segment at a time.
                while true
                    local_front = cur_front_t[t]  # Data race, but first read always succeeds
                    cur_front_t[t] += segment_size # Failure of increment is acceptable

                    (local_front > cur_len || local_front <= zero(T)) && break
                    while local_front <= cur_len && cur_level[local_front] != zero(T)
                        v = cur_level[local_front]
                        cur_level[local_front] = zero(T)
                        local_front += one(T)

                        # Check if v was successfully read.
                        (visited[v] && vert_level[v] == n_level-one(T)) || continue
                        for i in outneighbors(g, v)
                            # Data race, but first read on visited[i] always succeeds
                            if !visited[i]
                                vert_level[i] = n_level
                                #Concurrent visited[i] = true always succeeds
                                visited[i] = true
                                push!(next_level, i)
                            end
                        end
                    end
                end
                queue_explored_t[t] = true
            end
        end
        end

        is_cur_level_t_empty = true
        @inbounds for t in Base.OneTo(n_t)
            cur_level_t[t], next_level_t[t] = next_level_t[t], cur_level_t[t]
            cur_front_t[t] = one(T)
            empty!(next_level_t[t])
            queue_explored_t[t] = isempty(cur_level_t[t])
            is_cur_level_t_empty = is_cur_level_t_empty && queue_explored_t[t]
        end
    end

    return vert_level
end

gdistances!(g::AbstractGraph{T}, source::Integer, vert_level::Vector{T}; queue_segment_size::Integer=20) where T<:Integer =
gdistances!(g, [source,], vert_level; queue_segment_size=20)

"""
    gdistances(g, sources; queue_segment_size=20)
    gdistances(g, source; queue_segment_size=20)

Parallel implementation of [`LightGraphs.gdistances!`](@ref) with dynamic load balancing.

### Optional Arguments
- `queue_segment_size = 20`: It is the number of vertices a thread can claim from a queue at a time.
For denser graphs, a smaller value of `queue_segment_size` could improve performance.

### References
- [Avoiding Locks and Atomic Instructions in Shared-Memory Parallel BFS Using Optimistic
Parallelization](https://www.computer.org/csdl/proceedings/ipdpsw/2013/4979/00/4979b628-abs.html).
"""
gdistances(g::AbstractGraph{T}, sources::Vector{<:Integer}; queue_segment_size::Integer=20) where T<:Integer =
gdistances!(g, sources, Vector{T}(undef, nv(g)); queue_segment_size=20)

gdistances(g::AbstractGraph{T}, source::Integer; queue_segment_size::Integer=20) where T<:Integer =
gdistances!(g, [source,], Vector{T}(undef, nv(g)); queue_segment_size=20)


text:
@safetestset "Content negotiation hooks" begin
  using Genie, HTTP

  custom_message = "Got you!"
  original_message = "Hello!"

  function hook(req, res, params)
    if haskey(params, :ROUTE)
      params[:ROUTE].action = () -> custom_message
    end

    req, res, params
  end

  route("/") do
    original_message
  end

  port = nothing
  port = rand(8500:8900)

  server = up(port)

  response = HTTP.request("GET", "http://localhost:$port")
  @test response.status == 200
  @test String(response.body) == original_message

  push!(Genie.Router.content_negotiation_hooks, hook)

  response = HTTP.request("GET", "http://localhost:$port")
  @test response.status == 200
  @test String(response.body) == custom_message

  pop!(Genie.Router.content_negotiation_hooks)
  Genie.AppServer.down!()

  sleep(1)
  server = nothing
  port = nothing
end


text:
#   This file is part of UnitSystems.jl
#   It is licensed under the MIT license
#   UnitSystems Copyright (C) 2020 Michael Reed
#       _           _                         _
#      | |         | |                       | |
#   ___| |__   __ _| | ___ __ __ ___   ____ _| | __ _
#  / __| '_ \ / _` | |/ / '__/ _` \ \ / / _` | |/ _` |
# | (__| | | | (_| |   <| | | (_| |\ V / (_| | | (_| |
#  \___|_| |_|\__,_|_|\_\_|  \__,_| \_/ \__,_|_|\__,_|
#
#   https://github.com/chakravala
#   https://crucialflow.com

@pure avogadro(U::UnitSystem,C::Coupling=universe(U)) = molarmass(U,C)*electronunit(C)/electronmass(U,C)
@pure atomicmass(U::UnitSystem,C::Coupling=universe(U)) = electronmass(U,C)/electronunit(C)
@pure protonmass(U::UnitSystem,C::Coupling=universe(U)) = protonelectron(C)*electronmass(U,C)
@pure gaussgravitation(U::UnitSystem,C::Coupling=universe(U)) = sqrt(normal(gravitation(IAU)))*radian(U)/day(U)
@pure einstein(U::UnitSystem,C::Coupling=universe(U)) = two(U)*spat(U)*gravitation(U,C)/lightspeed(U,C)^4
#@pure einstein2(U::UnitSystem,C::Coupling=universe(U)) = two(U)*spat(U)*gravitation(U,C)/lightspeed(U,C)^2
@pure molargas(U::UnitSystem,C::Coupling=universe(U)) = boltzmann(U,C)*avogadro(U,C)
@pure stefan(U::UnitSystem,C::Coupling=universe(U)) = normal(turn(U))^4/two(U)^5*spat(U)*boltzmann(U,C)^4/(three(U)*five(U)*planck(U,C)^3*lightspeed(U,C)^2)
@pure radiationdensity(U::UnitSystem,C::Coupling=universe(U)) = two(U)^2*stefan(U,C)/lightspeed(U,C)
@pure vacuumpermittivity(U::UnitSystem,C::Coupling=universe(U)) = inv(vacuumpermeability(U,C)*(lightspeed(U,C)*lorentz(U))^2)
@pure electrostatic(U::UnitSystem,C::Coupling=universe(U)) = rationalization(U)/spat(U)/vacuumpermittivity(U,C)
@pure biotsavart(U::UnitSystem,C::Coupling=universe(U)) = vacuumpermeability(U,C)*lorentz(U)*(rationalization(U)/spat(U))
@pure magnetostatic(U::UnitSystem) = lorentz(U)*biotsavart(U)
@pure vacuumimpedance(U::UnitSystem,C::Coupling=universe(U)) = vacuumpermeability(U,C)*lightspeed(U,C)*rationalization(U)*lorentz(U)^2
@pure faraday(U::UnitSystem,C::Coupling=universe(U)) = elementarycharge(U,C)*avogadro(U,C)
@pure josephson(U::UnitSystem,C::Coupling=universe(U)) = two(U)*elementarycharge(U,C)*lorentz(U)/planck(U,C)
@pure magneticfluxquantum(U::UnitSystem,C::Coupling=universe(U)) = inv(josephson(U,C))
@pure klitzing(U::UnitSystem,C::Coupling=universe(U)) = planck(U,C)/elementarycharge(U,C)^2
@pure conductancequantum(U::UnitSystem,C::Coupling=universe(U)) = two(U)*elementarycharge(U,C)^2/planck(U,C)
@pure hartree(U::UnitSystem,C::Coupling=universe(U)) = electronmass(U,C)/gravity(U)*(lightspeed(U,C)*finestructure(C))^2
@pure rydberg(U::UnitSystem,C::Coupling=universe(U)) = hartree(U,C)/(two(U)*planck(U,C))/lightspeed(U,C)
@pure bohr(U::UnitSystem,C::Coupling=universe(U)) = planckreduced(U,C)*gravity(U)/electronmass(U,C)/lightspeed(U,C)/finestructure(C)
#@pure bohrreduced(U::UnitSystem,C::Coupling=universe(U)) = bohr(U,C)*(one(U)+inv(protonelectron(C)))
@pure electronradius(U::UnitSystem,C::Coupling=universe(U)) = finestructure(C)*planckreduced(U,C)*gravity(U)/electronmass(U,C)/lightspeed(U,C)
@pure magneton(U::UnitSystem,C::Coupling=universe(U)) = elementarycharge(U,C)*planckreduced(U,C)*lorentz(U)/(two(U)*electronmass(U,C))

@pure hyperfine(U::UnitSystem) = frequency(ΔνCs,U,Metric)
@pure hubble(U::UnitSystem) = time(one(U),Hubble,U)
@pure cosmological(U::UnitSystem,C::Coupling=universe(U)) = three(U)*darkenergydensity(C)*(hubble(U)/lightspeed(U,C))^2
@pure loschmidt(U::UnitSystem,P=atmosphere(U),T=T₀*temperature(SI2019,U)) = P/T/boltzmann(U)
@pure amagat(U::UnitSystem) = loschmidt(U)/avogadro(U)
@pure wienwavelength(U::UnitSystem) = planck(U)*lightspeed(U)/boltzmann(U)/Constant(4.965114231744276303)
@pure wienfrequency(U::UnitSystem) = Constant(2.821439372122078893)*boltzmann(U)/planck(U)
@pure solarmass(U::UnitSystem) = mass(GM☉/G,U,Metric)
@pure earthmass(U::UnitSystem) = mass(GME/G,U,Metric)
@pure jupitermass(U::UnitSystem) = mass(GMJ/G,U,Metric)
@pure lunarmass(U::UnitSystem) = earthmass(U)/μE☾
@pure mechanicalheat(U::UnitSystem) = molargas(U)*normal(calorie(Metric)/molargas(Metric))
@pure gaussianyear(U::UnitSystem) = turn(U)/gaussgravitation(U)
@pure siderealyear(U::UnitSystem) = gaussianyear(U)/normal(sqrt(solarmass(IAU)+earthmass(IAU)+lunarmass(IAU)))
@pure gaussianmonth(U::UnitSystem) = normal(turn(Metric)*sqrt(normal(lunardistance(Metric))^3/GME))*time(Metric,U)
@pure siderealmonth(U::UnitSystem) = gaussianmonth(U)/normal(sqrt(earthmass(IAUE)+lunarmass(IAUE)))
@pure synodicmonth(U::UnitSystem) = inv(inv(siderealmonth(U))-inv(siderealyear(U)))
@pure jovianyear(U::UnitSystem) = day(U)*sqrt(normal(jupiterdistance(U)^3/solarmass(U)/gravitation(U)))*turn(U)/radian(U)/normal(sqrt(solarmass(IAU)+jupitermass(IAU)))

include("derived.jl")

include("physicsdocs.jl")


text:
using Base.Broadcast

import Base.Broadcast: BroadcastStyle, Broadcasted, ArrayStyle

# we define a generic `BroadcastStyle` here that should be sufficient for most cases.
# dependent packages like `CuArrays` can define their own `BroadcastStyle` allowing
# them to further change or optimize broadcasting.
#
# TODO: investigate if we should define out own `GPUArrayStyle{N} <: AbstractArrayStyle{N}`
#
# NOTE: this uses the specific `T` that was used e.g. `JLArray` or `CLArray` for ArrayStyle,
#       instead of using `ArrayStyle{GPUArray}`, due to the fact how `similar` works.
BroadcastStyle(::Type{T}) where {T<:GPUArray} = ArrayStyle{T}()

# These wrapper types otherwise forget that they are GPU compatible
#
# NOTE: Don't directly use ArrayStyle{GPUArray} here since that would mean that `CuArrays`
#       customization no longer take effect.
BroadcastStyle(::Type{<:LinearAlgebra.Transpose{<:Any,T}}) where {T<:GPUArray} = BroadcastStyle(T)
BroadcastStyle(::Type{<:LinearAlgebra.Adjoint{<:Any,T}}) where {T<:GPUArray} = BroadcastStyle(T)
BroadcastStyle(::Type{<:SubArray{<:Any,<:Any,T}}) where {T<:GPUArray} = BroadcastStyle(T)

backend(::Type{<:LinearAlgebra.Transpose{<:Any,T}}) where {T<:GPUArray} = backend(T)
backend(::Type{<:LinearAlgebra.Adjoint{<:Any,T}}) where {T<:GPUArray} = backend(T)
backend(::Type{<:SubArray{<:Any,<:Any,T}}) where {T<:GPUArray} = backend(T)

# This Union is a hack. Ideally Base would have a Transpose <: WrappedArray <: AbstractArray
# and we could define our methods in terms of Union{GPUArray, WrappedArray{<:Any, <:GPUArray}}
const GPUDestArray = Union{GPUArray,
                           LinearAlgebra.Transpose{<:Any,<:GPUArray},
                           LinearAlgebra.Adjoint{<:Any,<:GPUArray},
                           SubArray{<:Any,<:Any,<:GPUArray}}

# This method is responsible for selection the output type of broadcast
function Base.similar(bc::Broadcasted{<:ArrayStyle{GPU}}, ::Type{ElType}) where
                     {GPU <: GPUArray, ElType}
    similar(GPU, ElType, axes(bc))
end

# We purposefully only specialize `copyto!`, dependent packages need to make sure that they
# can handle:
# - `bc::Broadcast.Broadcasted{Style}`
# - `ex::Broadcast.Extruded`
# - `LinearAlgebra.Transpose{,<:GPUArray}` and `LinearAlgebra.Adjoint{,<:GPUArray}`, etc
#    as arguments to a kernel and that they do the right conversion.
#
# This Broadcast can be further customize by:
# - `Broadcast.preprocess(dest::GPUArray, bc::Broadcasted{Nothing})` which allows for a
#   complete transformation based on the output type just at the end of the pipeline.
# - `Broadcast.broadcasted(::Style, f)` selection of an implementation of `f` compatible
#   with `Style`
#
# For more information see the Base documentation.
@inline function Base.copyto!(dest::GPUDestArray, bc::Broadcasted{Nothing})
    axes(dest) == axes(bc) || Broadcast.throwdm(axes(dest), axes(bc))
    bc′ = Broadcast.preprocess(dest, bc)
    gpu_call(dest, (dest, bc′)) do state, dest, bc′
        let I = CartesianIndex(@cartesianidx(dest))
            @inbounds dest[I] = bc′[I]
        end
        return
    end

    return dest
end

# Base defines this method as a performance optimization, but we don't know how to do
# `fill!` in general for all `GPUDestArray` so we just go straight to the fallback
@inline Base.copyto!(dest::GPUDestArray, bc::Broadcasted{<:Broadcast.AbstractArrayStyle{0}}) =
    copyto!(dest, convert(Broadcasted{Nothing}, bc))


text:
#############################################################################
# lieb_ando.jl
# Returns tr(K' * A^{1-t} * K * B^t) where A and B are positive semidefinite
# matrices and K is an arbitrary matrix (possibly rectangular).
#
# Disciplined convex programming information:
#    lieb_ando(A,B,K,t) is concave in (A,B) for t in [0,1], and convex
#    in (A,B) for t in [-1,0] or [1,2]. K is a fixed matrix.
#
# Seems numerically unstable when t is on the endpoints of these ranges.
#
# All expressions and atoms are subtypes of AbstractExpr.
# Please read expressions.jl first.
#
#REFERENCE
#   Ported from CVXQUAD which is based on the paper: "Lieb's concavity
#   theorem, matrix geometric means and semidefinite optimization" by Hamza
#   Fawzi and James Saunderson (arXiv:1512.03401)
#############################################################################

const MatrixOrConstant = Union{AbstractMatrix, Constant}

function lieb_ando(A::MatrixOrConstant, B::MatrixOrConstant, K::MatrixOrConstant, t::Rational)
    if t < -1 || t > 2
        throw(DomainError(t, "t must be between -1 and 2"))
    end
    return real(tr(K' * A^(1-t) * K * B^t))
end

function lieb_ando(A::MatrixOrConstant, B::AbstractExpr, K::MatrixOrConstant, t::Rational)
    if t < -1 || t > 2
        throw(DomainError(t, "t must be between -1 and 2"))
    end
    KAK = K' * A^(1-t) * K
    KAK = (KAK+KAK')/2
    return trace_mpower(B, t, KAK)
end

function lieb_ando(A::AbstractExpr, B::MatrixOrConstant, K::MatrixOrConstant, t::Rational)
    if t < -1 || t > 2
        throw(DomainError(t, "t must be between -1 and 2"))
    end
    KBK = K * B^t * K'
    KBK = (KBK+KBK')/2
    return trace_mpower(A, 1-t, KBK)
end

function lieb_ando(A::AbstractExpr, B::AbstractExpr, K::MatrixOrConstant, t::Rational)
    n = size(A,1)
    m = size(B,1)
    Kvec = reshape(K',n*m,1)
    KvKv = Kvec * Kvec'
    KvKv = (KvKv+KvKv')/2
    Im = Matrix(1.0*I, m, m)
    In = Matrix(1.0*I, n, n)

    is_complex = sign(A) == ComplexSign() || sign(B) == ComplexSign() || sign(Constant(K)) == ComplexSign()
    if is_complex
        T = HermitianSemidefinite(n*m)
    else
        T = Semidefinite(n*m)
    end

    if t >= 0 && t <= 1
        # Concave function
        add_constraint!(T, T in GeomMeanHypoCone(kron(A,Im), kron(In,conj(B)), t, false))
        return real(tr(KvKv * T))
    elseif (t >= -1 && t <= 0) || (t >= 1 && t <= 2)
        # Convex function
        add_constraint!(T, T in GeomMeanEpiCone(kron(A,Im), kron(In,conj(B)), t, false))
        return real(tr(KvKv * T))
    else
        throw(DomainError(t, "t must be between -1 and 2"))
    end
end