text:
struct RedlichKwong{S,T,A} <: CubicModel
    type::S
    tc::T
    pc::T
    ω::T
    mw::T
    vc::Union{T,Nothing}
    _a::T
    _b::T
    aij::A

    function RedlichKwong(tc,pc,ω,mw,vc=nothing,aij = nothing)
        if length(tc) == 1
            type = SINGLE()

        else
            type = MULTI()
        end
        S = typeof(type)
        tc,pc,ω,mw = promote(tc,pc,ω,mw)
        T = typeof(tc)
        A = typeof(aij)
        _a = 0.42748 .*((RGAS .*tc).^2) ./pc
        _b = 0.08664 .*((RGAS .*tc)) ./pc
        return new{S,T,A}(type,tc,pc,ω,mw,vc,_a,_b,aij)
    end
end

volume_solver_type(model::RedlichKwong) = CubicRoots()
single_sat_Approx(model::RedlichKwong{SINGLE}) =RKSatApprox(model)
mol_density(model::RedlichKwong{SINGLE},::CriticalPoint,unit=u"mol/(m^3)") = convert_unit(u"mol/L",unit,inv(only(model.vc)))
pressure(model::RedlichKwong{SINGLE},::CriticalPoint,unit=u"Pa") = convert_unit(u"Pa",unit,only(model.pc))
temperature(model::RedlichKwong{SINGLE},::CriticalPoint,unit=u"K") = convert_unit(u"K",unit,only(model.tc))
mol_volume(model::RedlichKwong{SINGLE},::CriticalPoint,unit=u"m^3/mol") = convert_unit(u"m^3/mol",unit,only(model.vc))
acentric_factor(model::RedlichKwong{SINGLE}) = only(model.ω)
molecular_weight(model::RedlichKwong{SINGLE}) = only(model.mw)
molecular_weight(model::RedlichKwong{MULTI}) = model.mw

function mol_density(model::RedlichKwong{MULTI},::CriticalPoint,unit=u"mol/(m^3)")
    return convert_unit.(u"mol/L",unit,1 ./ model.vc)
end

function pressure(model::RedlichKwong{MULTI},::CriticalPoint,unit=u"Pa")
    return convert_unit.(u"Pa",unit,model.pc)
end

function temperature(model::RedlichKwong{MULTI},::CriticalPoint,unit=u"K")
    return convert_unit.(u"K",unit,model.tc)
end

function mol_volume(model::RedlichKwong{MULTI},::CriticalPoint,unit=u"m^3/mol")
    return convert_unit.(u"m^3/mol",unit,model.vc)
end

function acentric_factor(model::RedlichKwong{MULTI})
    return model.ω
end

function RedlichKwong(;tc,pc,ω,mw,vc=nothing,aij=nothing)
    return RedlichKwong(tc,pc,ω,mw,vc,aij)
end

function RedlichKwong(model::ThermoModel)
    tc = temperature(model,CriticalPoint())
    pc = pressure(model,CriticalPoint())
    ω = acentric_factor(model)
    mw = molecular_weight(model)
    return RedlichKwong(tc,pc,ω,mw)
end

function cubic_aα(model::RedlichKwong,t,i,j)
    aᵢ =model._a[i]
    tcᵢ = model.tc[i]
    sqrt_t⁻¹ = sqrt(inv(t))
    if i === j
        return  aᵢ* sqrt(tcᵢ)*sqrt_t⁻¹
    else
        aⱼ =model._a[j]
        tcⱼ = model.tc[j]
        return sqrt(aᵢ*aⱼ* sqrt(tcᵢ* tcⱼ))*sqrt_t⁻¹
    end
end

function cubic_ab(mt::SinglePT,model::RedlichKwong{SINGLE},v,t)
    a = cubic_aα(model,t,1,1)
    b = only(model._b)
    return a,b
end

function cubic_ab(mt::MultiPT,model::RedlichKwong{MULTI},p,t,x)
    #two options to introduce alpha:
    #here: it will allocate, but less ops
    bi = model._b
    b = dot(bi,x)
    #here: it will not allocate, but more ops
    sss= (i,j)->cubic_aα(model,t,i,j)
    a = cubic_mixing_rule(sss, x, model.aij)
    return a,b
end

function cubic_abp(mt::SingleVT,model::RedlichKwong{SINGLE},v,t)
    a,b = cubic_ab(QuickStates.pt(),model,v,t) #v is ignored
    p = RGAS*t/(v-b) - a/(v*v)
    return a,b,p
end

function cubic_abp(mt::MultiVT,model::RedlichKwong{MULTI},v,t,x)
    a,b = cubic_ab(QuickStates.ptx(),model,v,t,x) #v is ignored
    p =  RGAS*t/(v-b) - a/((v+b)*v)
    return a,b,p
end

function fugacity_coeff_impl(mt::SingleVT,model::RedlichKwong{SINGLE},v,t)
    a,b,p =  cubic_abp(mt,model,v,t)
    RTinv = 1/(RGAS*t)
    A = a*p*RTinv*RTinv
    B = b*p*RTinv
    z = p*v*RTinv
     _1 = one(z)
     logϕ = z - _1 - log(z-B) - A/z
end

function  αR_impl(mt::MultiVT,model::RedlichKwong{MULTI},rho,t,x)
    R = RGAS
    RTinv = 1/(RGAS*t)
    v = inv(rho)
    a,b,p =  cubic_abp(mt,model,v,t,x)
    -log(1-b*rho) - a*RTinv*log(b*rho+1)/b
end
    #=
    k0 = -AB
    k1 = -B*(B+1) + A
    k2 = -1
    k3 = 1
    =#
function cubic_poly(mt::SinglePT,model::RedlichKwong{SINGLE},p,t)
    a,b = cubic_ab(QuickStates.pt(),model,p,t)
    RTinv = 1/(RGAS*t)
    A = a*p*RTinv*RTinv
    B = b*p*RTinv
    _1 = one(a)
    return (-A*B, -B*(B+_1) + A, -_1, _1)
end

function cubic_poly(mt::MultiPT,model::RedlichKwong{MULTI},p,t,x)
    a,b = cubic_ab(QuickStates.ptx(),model,p,t,x)
    RTinv = 1/(RGAS*t)
    A = a*p*RTinv*RTinv
    B = b*p*RTinv
    _1 = one(a)
    return (-A*B, -B*(B+_1) + A, -_1, _1)
end

struct RKSatApprox{M} <: SaturationModel
    model::M
    function RKSatApprox(model::CubicModel)
        T = typeof(model)
        return new{T}(model)
    end
end

function RKSatApprox(tc,pc,mw=copy(pc),vc=nothing,ω=nothing,aij = nothing)
    model = RedlichKwong(tc,pc,mw,vc,ω,aij)
    return RKSatApprox(model)
end

function RKSatApprox(;tc,pc,mw=copy(pc),vc=nothing,ω=nothing,aij=nothing)
    model =  RedlichKwong(tc,pc,mw,vc,ω,aij)
    return RKSatApprox(model)
end

function pressure_impl(mt::SingleSatT,model::RKSatApprox,t)
    a = 4.869351869381000
    b = 2.783631394064670
    c = 1.106187857326840
    d = -0.256386265669017
    e = -5.975540029435890
    f = 0.024270608586173
    tc = only(model.model.tc)
    pc = only(model.model.pc)
    tr = t/tc
    atr = tc/t
    num = evalpoly(atr,(a,c,e))
    denom = evalpoly(atr,(1.0,b,d,f))
    pr = exp(num/denom)*tr
    p = pr*pc
end

initial_temperature(model::RKSatApprox,p) = critical_sat_interpolation(model,p)


text:
# Linear fractional transformation "LFT"

module LinearFractionalTransformations

import Base.inv, Base.isequal, Base.show, Base.hash
import Base.==, Base.*, Base.getindex
export LFT, isequal, call

const complex_infinity = Inf + Inf * im

struct LFT
    M::Array{Complex{Float64},2}
    function LFT(a, b, c, d)
        if isinf(a) || isinf(b) || isinf(c) || isinf(d)
            error("Arguments must be finite: " * string((a,b,c,d) ))
        end

        if a*d-b*c == 0
            error("Singularity detected: " * string((a,b,c,d)) )
        end

        new( [ a b; c d])
    end
end

function LFT(M::Array)
    return LFT(M[1,1], M[1,2], M[2,1], M[2,2])
end

function LFT()
    return LFT(1,0,0,1)
end

# a --> 0, b-->1, and c--> oo
function LFT(a::Number, b::Number, c::Number)
    if a==b || b==c || a==c
        error("Three arguments must be distinct: "*string((a,b,c)))
    end

    if isinf(a)
        return LFT(0, b-c, 1, -c)
    end

    if isinf(b)
        return LFT(1, -a, 1, -c)
    end

    if isinf(c)
        return LFT(1, -a, 0, b-a)
    end

    # if all args are finite
    aa = (b-c)
    bb = (-a)*(b-c)
    cc = (b-a)
    dd = (-c)*(b-a)
    return LFT(aa,bb,cc,dd)
end

# a-->aa, b-->bb, c-->cc
function LFT(a::Number, aa::Number,
              b::Number, bb::Number,
              c::Number, cc::Number)
    f = LFT(a,b,c)
    g = LFT(aa,bb,cc)
    return inv(g)*f
end

#### Equality checking ####

# These need to be rewritten

function isequal(f::LFT, g::LFT)
    return f[0]==g[0] && f[1]==g[1] && f[Inf]==g[Inf]
    # h = f * inv(g)
    # I = h.M
    # return I[1,1]==I[2,2] && I[1,2]==I[2,1]==0
end

==(f::LFT,g::LFT) = isequal(f,g)

# Inverse transformation
function inv(L::LFT)
    a = L.M[1,1]
    b = L.M[1,2]
    c = L.M[2,1]
    d = L.M[2,2]
    return LFT(d,-b,-c,a)
end

# Composition
*(A::LFT, B::LFT) = LFT(A.M * B.M)

# Function application
function getindex(A::LFT, x::Number)
    if isinf(x)
        a = A.M[1,1]
        b = A.M[2,1]
        if b==0
            return complex_infinity
        end
        return a/b
    end
    w = A.M * [ x+0im ; 1.0+0im ]
    if w[2] == 0
        return complex_infinity
    end
    return w[1]/w[2]
end

# call(A::LFT, x::Number) = A[x]

(A::LFT)(x::Number) = A[x]

function show(io::IO, L::LFT)
    print(io, "LFT( ",
          L.M[1,1], " , ",
          L.M[1,2], " , ",
          L.M[2,1], " , ",
          L.M[2,2], " )"
          )
end

function hash(f::LFT, h::UInt64 = UInt64(0))
    z = 0.0 + 0.0*im # kludge to make -0.0 and -0.0im into +versions
    a = f[0]+z
    b = f[1]+z
    c = f[Inf]+z
    return hash(a,hash(b,hash(c,h)))
end

end # end of module "LFTs"

ext: jl


text:
# ---
# title: 1235. Maximum Profit in Job Scheduling
# id: problem1235
# author: Tian Jun
# date: 2020-10-31
# difficulty: Hard
# categories: Binary Search, Dynamic Programming, Sort
# link: <https://leetcode.com/problems/maximum-profit-in-job-scheduling/description/>
# hidden: true
# ---
#
# We have `n` jobs, where every job is scheduled to be done from `startTime[i]`
# to `endTime[i]`, obtaining a profit of `profit[i]`.
#
# You're given the `startTime` , `endTime` and `profit` arrays, you need to
# output the maximum profit you can take such that there are no 2 jobs in the
# subset with overlapping time range.
#
# If you choose a job that ends at time `X` you will be able to start another
# job that starts at time `X`.
#
#
#
# **Example 1:**
#
# **![](https://assets.leetcode.com/uploads/2019/10/10/sample1_1584.png)**
#
#
#
#     Input: startTime = [1,2,3,3], endTime = [3,4,5,6], profit = [50,10,40,70]
#     Output: 120
#     Explanation: The subset chosen is the first and fourth job.
#     Time range [1-3]+[3-6] , we get profit of 120 = 50 + 70.
#
#
# **Example 2:**
#
# **![](https://assets.leetcode.com/uploads/2019/10/10/sample22_1584.png)**
#
#
#
#     Input: startTime = [1,2,3,4,6], endTime = [3,5,10,6,9], profit = [20,20,100,70,60]
#     Output: 150
#     Explanation: The subset chosen is the first, fourth and fifth job.
#     Profit obtained 150 = 20 + 70 + 60.
#
#
# **Example 3:**
#
# **![](https://assets.leetcode.com/uploads/2019/10/10/sample3_1584.png)**
#
#
#
#     Input: startTime = [1,1,1], endTime = [2,3,4], profit = [5,6,4]
#     Output: 6
#
#
#
#
# **Constraints:**
#
#   * `1 <= startTime.length == endTime.length == profit.length <= 5 * 10^4`
#   * `1 <= startTime[i] < endTime[i] <= 10^9`
#   * `1 <= profit[i] <= 10^4`
#
#
## @lc code=start
using LeetCode

## add your code here:
## @lc code=end


text:
# 1D diffusion problem

# TODO: Add more complex tests.

# Packages and inclusions
using ModelingToolkit,DiffEqOperators,DiffEqBase,LinearAlgebra,Test

# Tests
@testset "Test 00: Dt(u(t,x)) ~ Dxx(u(t,x))" begin
    # Parameters, variables, and derivatives
    @parameters t x
    @variables u(..)
    @derivatives Dt'~t
    @derivatives Dxx''~x

    # 1D PDE and boundary conditions
    eq  = Dt(u(t,x)) ~ Dxx(u(t,x))
    bcs = [u(0,x) ~ -x*(x-1)*sin(x),
           u(t,0) ~ 0.0,
           u(t,1) ~ 0.0]

    # Space and time domains
    domains = [t ∈ IntervalDomain(0.0,1.0),
               x ∈ IntervalDomain(0.0,1.0)]

    # PDE system
    pdesys = PDESystem(eq,bcs,domains,[t,x],[u])

    # Method of lines discretization
    dx = 0.1
    order = 2
    discretization = MOLFiniteDifference(dx,order)

    # Convert the PDE problem into an ODE problem
    prob = discretize(pdesys,discretization)

    # Solve ODE problem
    using OrdinaryDiffEq
    sol = solve(prob,Tsit5(),saveat=0.1)

    #Plot and save results
    # using Plots
    # plot(prob.space[2],Array(prob.extrapolation[1]*sol[:,1,1]))
    # plot!(prob.space[2],Array(prob.extrapolation[1]*sol[:,1,2]))
    # plot!(prob.space[2],Array(prob.extrapolation[1]*sol[:,1,3]))
    # plot!(prob.space[2],Array(prob.extrapolation[1]*sol[:,1,4]))
    # savefig("MOL_1D_Linear_Diffusion_Test00.png")

    # Test
    n = size(sol,1)
    t_f = size(sol,3)

    @test sol[:,1,t_f] ≈ zeros(n) atol = 0.001;
end

@testset "Test 01: Dt(u(t,x)) ~ D*Dxx(u(t,x))" begin
    # Parameters, variables, and derivatives
    @parameters t x D
    @variables u(..)
    @derivatives Dt'~t
    @derivatives Dxx''~x

    D = 1.1

    # 1D PDE and boundary conditions
    eq  = Dt(u(t,x)) ~ D*Dxx(u(t,x))
    bcs = [u(0,x) ~ -x*(x-1)*sin(x),
           u(t,0) ~ 0.0,
           u(t,1) ~ 0.0]

    # Space and time domains
    domains = [t ∈ IntervalDomain(0.0,1.0),
               x ∈ IntervalDomain(0.0,1.0)]

    # PDE system
    pdesys = PDESystem(eq,bcs,domains,[t,x,D],[u])

    # Method of lines discretization
    dx = 0.1
    order = 2
    discretization = MOLFiniteDifference(dx,order)

    # Convert the PDE problem into an ODE problem
    prob = discretize(pdesys,discretization)

    # Solve ODE problem
    using OrdinaryDiffEq
    sol = solve(prob,Tsit5(),saveat=0.1)

    # Plot and save results
    # using Plots
    # plot(prob.space[2],Array(prob.extrapolation[1]*sol[:,1,1]))
    # plot!(prob.space[2],Array(prob.extrapolation[1]*sol[:,1,2]))
    # plot!(prob.space[2],Array(prob.extrapolation[1]*sol[:,1,3]))
    # plot!(prob.space[2],Array(prob.extrapolation[1]*sol[:,1,4]))
    # savefig("MOL_1D_Linear_Diffusion_Test01.png")

    # Test
    n = size(sol,1)
    t_f = size(sol,3)
    @test sol[:,1,t_f] ≈ zeros(n) atol = 0.001;
end

@testset "Test 02: Dt(u(t,x)) ~ Dx(D(t,x))*Dx(u(t,x))+D(t,x)*Dxx(u(t,x))" begin
    # Parameters, variables, and derivatives
    @parameters t x
    @variables u(..) D(..)
    @derivatives Dt'~t
    @derivatives Dx'~x
    @derivatives Dxx''~x

    # 1D PDE and boundary conditions

    eq  = [ Dt(u(t,x)) ~ Dx(D(t,x))*Dx(u(t,x))+D(t,x)*Dxx(u(t,x)),
            D(t,x) ~ 0.999 + 0.001 * t * x  ]

    bcs = [u(0,x) ~ -x*(x-1)*sin(x),
           u(t,0) ~ 0.0,
           u(t,1) ~ 0.0,
           D(0,x) ~ 0.999,
           D(t,0) ~ 0.999,
           D(t,1) ~ 0.999 + 0.001 * t ]

    # Space and time domains
    domains = [t ∈ IntervalDomain(0.0,1.0),
               x ∈ IntervalDomain(0.0,1.0)]

    # PDE system
    pdesys = PDESystem(eq,bcs,domains,[t,x],[u,D])

    # Method of lines discretization
    dx = 0.1
    order = 2
    discretization = MOLFiniteDifference(dx,order)

    # Convert the PDE problem into an ODE problem
    prob = discretize(pdesys,discretization)

    # Solve ODE problem
    using OrdinaryDiffEq
    sol = solve(prob,Tsit5(),saveat=0.1)

    # Plot and save results
    # using Plots
    # plot(prob.space[2],Array(prob.extrapolation[1]*sol[:,1,1]))
    # plot!(prob.space[2],Array(prob.extrapolation[1]*sol[:,1,2]))
    # plot!(prob.space[2],Array(prob.extrapolation[1]*sol[:,1,3]))
    # plot!(prob.space[2],Array(prob.extrapolation[1]*sol[:,1,4]))
    # savefig("MOL_1D_Linear_Diffusion_Test02.png")

    # Test
    n = size(sol,1)
    t_f = size(sol,3)
    @test sol[:,1,t_f] ≈ zeros(n) atol = 0.1;
end


text:
import MPI

function main()
    MPI.init()

    comm = MPI.COMM_WORLD

    MPI.barrier(comm)

    root = 0
    r = MPI.rank(comm)

    sr = MPI.Reduce(r, MPI.SUM, root, comm)

    if(MPI.rank(comm) == root)
        @printf("sum of ranks: %s\n", sr)
    end

    MPI.finalize()
end

main()


text:
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
#
# Desription
# ==============================================================================
#
#   Tests related to DCM miscellaneous functions.
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

# File: ./src/dcm.jl
# ==================

# Functions: summary
# ------------------

@testset "DCM summary" begin
    io = IOBuffer()

    D = DCM(1.0I)
    summary(io, D)
    str = String(take!(io))
    @test str == "DCM{Float64}"

    D = DCM(1.0f0I)
    summary(io, D)
    str = String(take!(io))
    @test str == "DCM{Float32}"
end

# Functions: Tuple
# ----------------

@testset "StaticArrays.jl API" begin
    D = DCM(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0)
    @test Tuple(D) === (1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0)

    D = DCM(1.0f0, 2.0f0, 3.0f0, 4.0f0, 5.0f0, 6.0f0, 7.0f0, 8.0f0, 9.0f0)
    @test Tuple(D) === (1.0f0, 2.0f0, 3.0f0, 4.0f0, 5.0f0, 6.0f0, 7.0f0, 8.0f0, 9.0f0)
end


text:
"""
```
measurement(m::AnSchorfheide{T}, TTT::Matrix{T},
            RRR::Matrix{T}, CCC::Vector{T}) where {T <: AbstractFloat}
```

Assign measurement equation

```
y_t = ZZ*s_t + DD + u_t
```

where

```
Var(ϵ_t) = QQ
Var(u_t) = EE
Cov(ϵ_t, u_t) = 0
```
"""
function measurement(m::AnSchorfheide{T}, TTT::Matrix{T}, # do not edit input arguments
                     RRR::Matrix{T}, CCC::Vector{T};
                     reg::Int = 1, # these keyword arguments are only used to make regime-switching work.
                     TTTs::Vector{<: AbstractMatrix{T}} = Matrix{T}[], # you can delete these keyword arguments if
                     CCCs::Vector{<: AbstractVector{T}} = Vector{T}[], # you don't need regime-switching.
                     information_set::UnitRange = reg:reg,
                     memo::Union{ForwardMultipleExpectationsMemo, Nothing} = nothing) where {T <: AbstractFloat}

    endo     = m.endogenous_states           # make references to model indices
    endo_new = m.endogenous_states_augmented # for convenience. You can rename thse if you want
    exo      = m.exogenous_shocks
    obs      = m.observables

    _n_observables = n_observables(m) # do not edit!
    _n_states = n_states_augmented(m)
    _n_shocks_exogenous = n_shocks_exogenous(m)

    ZZ = zeros(_n_observables, _n_states) # do not edit!
    DD = zeros(_n_observables)
    EE = zeros(_n_observables, _n_observables)
    QQ = zeros(_n_shocks_exogenous, _n_shocks_exogenous)

    # Starting from here, you should probably edit!

    ## Output growth
    ZZ[obs[:obs_gdp], endo[:y_t]]  = 1.0
    ZZ[obs[:obs_gdp], endo[:y_t1]] = -1.0
    ZZ[obs[:obs_gdp], endo[:z_t]]  = 1.0
    DD[obs[:obs_gdp]]              = m[:γ_Q]

    ## Inflation
    ZZ[obs[:obs_cpi], endo[:π_t]] = 4.0
    DD[obs[:obs_cpi]]             = m[:π_star]

    ## Federal Funds Rate
    ZZ[obs[:obs_nominalrate], endo[:R_t]] = 4.0
    DD[obs[:obs_nominalrate]]             = m[:π_star] + m[:rA] + 4.0*m[:γ_Q]

    # Measurement error
    EE[obs[:obs_gdp], obs[:obs_gdp]]                 = m[:e_y]^2
    EE[obs[:obs_cpi], obs[:obs_cpi]]                 = m[:e_π]^2
    EE[obs[:obs_nominalrate], obs[:obs_nominalrate]] = m[:e_R]^2

    # Variance of innovations
    QQ[exo[:z_sh],exo[:z_sh]]   = (m[:σ_z])^2
    QQ[exo[:g_sh],exo[:g_sh]]   = (m[:σ_g])^2
    QQ[exo[:rm_sh],exo[:rm_sh]] = (m[:σ_R])^2

    return Measurement(ZZ, DD, QQ, EE) # do not edit this return
end


text:
#
# Constant primitives.
#

# Singular constant.

ConstQuery(idom, odom, val) =
    Query(
        ConstSig(val),
        Input(convert(Domain, idom)),
        Output(convert(Domain, odom)))

ConstQuery(val) =
    ConstQuery(Any, typeof(val), val)

immutable ConstSig <: AbstractPrimitive
    val
end

describe(io::IO, sig::ConstSig) = print(io, sig.val)

ev(sig::ConstSig, ity::Input, oty::Output, iflow::InputFlow) =
    const_impl(sig.val, ity, oty, iflow)

const_impl(val, ::Input, oty::Output, iflow::InputFlow) =
    let len = length(iflow)
        OutputFlow(oty, Column(OneTo(len+1), fill(val, len)))
    end

# Null constant.

NullQuery(idom) =
    Query(
        NullSig(),
        Input(convert(Domain, idom)),
        Output(None) |> setoptional())

NullQuery() = NullQuery(Any)

immutable NullSig <: AbstractPrimitive
end

describe(io::IO, ::NullSig) = print(io, "null")

ev(sig::NullSig, ::Input, oty::Output, iflow::InputFlow) =
    let len = length(iflow)
        OutputFlow(oty, Column(fill(1, len+1), None[]))
    end

max_stars_repo_name: rbt-lang/rbt-proto

max_issues_repo_name: rbt-lang/rbt-proto

max_forks_repo_name: rbt-lang/rbt-proto


text:
module MyJulia

export mycheatsheet, mycompile, myshowtable, myedit, mygermancsv, myvlscatter

using Reexport

@reexport using CSV, DataFrames, ElectronDisplay, PlotlyJS, VegaLite, Query, BenchmarkTools

using PackageCompiler: create_sysimage
using Pkg: instantiate

function mycompile()
    instantiate()
    image_file = normpath(joinpath(@__DIR__, "..", "MyJuliaImage.dll"))
    packages = [:Revise, :CSV, :DataFrames, :ElectronDisplay, :PlotlyJS, :VegaLite, :TableView, :Blink, :Query]
    if Sys.iswindows()
        packages = [:Revise, :CSV, :DataFrames, :ElectronDisplay, :PlotlyJS, :VegaLite, :TableView, :Query]
    end
    @info "compiling $packages"
    precompile_script = joinpath(@__DIR__, "precompile_script.jl")
    create_sysimage(packages, sysimage_path = image_file, precompile_execution_file = precompile_script)
end

using Blink, TableView

function myshowtable(df)
    w = Window()
    body!(w, showtable(df))
end

using InteractiveUtils: edit

function myedit()
    edit(@__FILE__)
end

using Markdown

function mycheatsheet()
    my_cheatsheet = @md_str """# Cheatsheet
    ## CSV
    CSV.read(filename :: String, delim :: Char, decimal :: Char, skipto :: Int, footerskip)

    Lade CSV File mit delim als Feldseparator, decimal als Decimalseparator. Die ersten (skipto) und die letzten (footerskip) Zeilen werden übersprungen.
    """
    electrondisplay(my_cheatsheet)
end

function mygermancsv(filename, skipto = 0, footerskip = 0)
    CSV.read(filename, decimal = ',', skipto = skipto, footerskip = footerskip)
end

function myvlscatter(df, x, y, title = "")
    electrondisplay(df |> @vlplot(:point, x=x, y=y, title=title))
end

function myvlscatter(arr :: AbstractArray{T, 2}, title="") where T
    if size(arr, 1) == 2
        df = DataFrame(x = arr[1, :], y = arr[2, :])
        myvlscatter(df, :x, :y, title)
    elseif size(arr, 2) == 2
        df = DataFrame(x = arr[:, 1], y = arr[:, 2])
        myvlscatter(df, :x, :y, title)
    else
        error("invalid shape")
    end
end

end # module


text:
@defcomp radiativeforcing begin
    FORC      = Variable(index=[time])   # Increase in radiative forcing (watts per m2 from 1900)

    forcoth   = Parameter(index=[time])  # Exogenous forcing for other greenhouse gases
    MAT       = Parameter(index=[time])  # Carbon concentration increase in atmosphere (GtC from 1750)
    MAT_final = Parameter()              # MAT calculation one timestep further than the model's index
    fco22x    = Parameter()              # Forcings of equilibrium CO2 doubling (Wm-2)

    function run_timestep(p, v, d, t)
        # Define function for FORC
        if ! is_last(t)
            v.FORC[t] = p.fco22x * (log((((p.MAT[t] + p.MAT[t + 1]) / 2) + 0.000001) / 596.4) / log(2)) + p.forcoth[t]
        else # final timestep
            # need to use MAT_final, calculated one step further
            v.FORC[t] = p.fco22x * (log((((p.MAT[t] + p.MAT_final) / 2) + 0.000001) / 596.4) / log(2)) + p.forcoth[t]
        end
    end
end


text:
"""
    render_template(filename[, args])

Renders a Fuji template from a template file. This file can contain valid Julia
expressions and reference variables passed through the `args` parameter. This
can be very useful for creating webpages with dynamic content.

# Examples

hello.txt contents:
Hello, {% name %}!

```julia-repl
julia> render_template("hello.txt", Dict("name" => "Jack"))
"<p>Hello, Jack!</p>"
```
"""
function render_template(filename, args = Dict{String,String}())
    f = open(filename)
    lines = readlines(f)
    close(f)

    content = ""
    expression_regex = r"{%([^%]+)%}"

    for (idx, line) in enumerate(lines)
        if ismatch(expression_regex, line)
            matches = eachmatch(expression_regex, line)

            for match in matches
                expression = match.captures[1]
                result = expression

                try
                    result = eval(parse(expression))
                catch e
                    if isa(e, UndefVarError)
                        result = args[strip(expression)]
                    end
                end

                line = replace(line, string("{%", expression, "%}"), result)
            end
        end

        content = string(content, line, "\n")
    end

    content
end


text:
"""
## deemat!

This subroutine returns the elastic dee matrix for ih=3 (plane strain),
ih=4 (axisymmetry or plane strain elastoplasticity) or ih=6
(three dimensions).

### Method
```julia
deemat!(dee, e, v)
```

### Arguments
```julia
* dee::Matrix{Float64}         : Dee matrix (Updated)
* e::Float64                   : Young's modulus
* v::Float64                   : Poisson's ratio
```
"""
function deemat!(dee::Array{Float64, 2}, e::Float64, v::Float64)
#
# This subroutine returns the elastic dee matrix for ih=3 (plane strain),
# ih=4 (axisymmetry or plane strain elastoplasticity) or ih=6
# (three dimensions).
#
  ih=size(dee,1)
  v1 = 1.0-v
  c = e/((1.0+v)*(1.0-2v))
  if ih == 3
    dee[1,1] = v1*c
    dee[2,2] = v1*c
    dee[1,2] = v*c
    dee[2,1] = v*c
    dee[3,3] = 0.5*c*(1.0-2v)
  elseif ih == 4
    dee[1,1] = v1*c
    dee[2,2] = v1*c
    dee[4,4] = v1*c
    dee[3,3] = 0.5*c*(1.0-2v)
    dee[1,2] = v*c
    dee[2,1] = v*c
    dee[1,4] = v*c
    dee[4,1] = v*c
    dee[2,4] = v*c
    dee[4,2] = v*c
  elseif ih == 6
   v2 = v/(1.0-v)
   vv = (1.0-2v)/(1.0-v)*0.5
   for i in 1:3
    dee[i,i] = 1.0
   end
   for i in 4:6
     dee[i,i] = vv
   end
    dee[1,2] = v2
    dee[2,1] = v2
    dee[1,3] = v2
    dee[3,1] = v2
    dee[2,3] = v2
    dee[3,2] = v2
    dee[:, :] = dee*e/(2.0*(1.0+v)*vv)
  else
   println("wrong size for dee matrix")
  end
end


text:
# Limited Memory Broyden
struct LimitedMemoryBroydenCache{uT,vT,F,X}
    Us::uT
    VTs::vT
    fx_::F
    x::X
end

"""
    LimitedMemoryBroydenSolver(; T=Float32, device, original_dims::Tuple{Int,Int}, batch_size, maxiters::Int=50,
                               ϵ::Real=1e-6, criteria::Symbol=:reltol, abstol::Union{Real,Nothing}=nothing,
                               reltol::Union{Real,Nothing}=nothing

Limited Memory Broyden Solver ([baimultiscale2020](@cite)) for solving Discrete DEQs.

## Arguments

* `T`: The type of the elements of the vectors. (Default: `Float32`)
* `device`: The device to use. Pass `gpu` to use the GPU else pass `cpu`.
* `original_dims`: Dimensions to reshape the arrays into (excluding the batch dimension).
* `batch_size`: The batch size of the problem. Your inputs can have a different batch size, but having
                them match allows us to efficiently cache internal statistics without reallocation.
* `maxiters`: Maximum number of iterations to run.
* `ϵ`: Tolerance for convergence.
* `criteria`: The criteria to use for convergence. Can be `:reltol` or `:abstol`.
* `abstol`: Absolute tolerance.
* `reltol`: Relative tolerance.

See also: [`BroydenSolver`](@ref)
"""
struct LimitedMemoryBroydenSolver{C<:LimitedMemoryBroydenCache,RT<:Union{AbstractFloat,Nothing},
                                  AT<:Union{AbstractFloat,Nothing}}
    cache::C
    original_dims::Tuple{Int,Int}
    maxiters::Int
    batch_size::Int
    criteria::Symbol
    reltol::RT
    abstol::AT
end

function LimitedMemoryBroydenSolver(; T=Float32, device, original_dims::Tuple{Int,Int}, batch_size, maxiters::Int=50,
                                    ϵ::Real=1e-6, criteria::Symbol=:reltol, abstol::Union{Real,Nothing}=nothing,
                                    reltol::Union{Real,Nothing}=nothing)
    @assert criteria ∈ (:abstol, :reltol)

    abstol = abstol !== nothing ? T(abstol) : T(ϵ)
    reltol = reltol !== nothing ? T(reltol) : T(ϵ)

    LBFGS_threshold = min(maxiters, 27)

    x = device(zeros(T, original_dims..., batch_size))
    fx = device(zeros(T, original_dims..., batch_size))

    total_hsize, n_elem, batch_size = size(x)

    # L x 2D x C x N
    Us = fill!(similar(x, (LBFGS_threshold, total_hsize, n_elem, batch_size)), T(0))
    # 2D x C x L x N
    VTs = fill!(similar(x, (total_hsize, n_elem, LBFGS_threshold, batch_size)), T(0))

    cache = LimitedMemoryBroydenCache(Us, VTs, vec(fx), x)

    return LimitedMemoryBroydenSolver(cache, original_dims, maxiters, batch_size, criteria, reltol, abstol)
end

function line_search(update, x₀, f₀, f, nstep::Int=0, on::Bool=false)
    # TODO: Implement a line search algorithm
    x_est = x₀ .+ update
    f₀_new = f(x_est)
    return (x_est, f₀_new, x_est .- x₀, f₀_new .- f₀, 0)
end

function (lbroyden::LimitedMemoryBroydenSolver{C,T})(f!, x_::AbstractVector{T}) where {C,T}
    @unpack cache, original_dims, batch_size, maxiters, criteria, reltol, abstol = lbroyden
    ϵ = getfield(lbroyden, criteria)

    nfeatures = prod(original_dims)
    if nfeatures * batch_size != length(x_)
        # Maybe the last batch is smaller than the others
        cache = LimitedMemoryBroydenSolver(; T=T, device=x_ isa CuArray ? gpu : cpu, original_dims=original_dims,
                                           batch_size=length(x_) ÷ nfeatures, maxiters=maxiters, ϵ=ϵ).cache
    end

    @unpack Us, VTs, fx_, x = cache
    x .= reshape(x_, size(x))
    LBFGS_threshold = size(Us, 1)
    fill!(Us, T(0))
    fill!(VTs, T(0))

    # Counters
    nstep = 1
    tnstep = 1

    # Initialize
    total_hsize, n_elem, batch_size = actual_size = size(x)

    # Modify the functions
    f(x) = (f!(fx_, vec(x)); return reshape(fx_, actual_size))
    fx = f(x)

    update = fx
    new_objective = norm(fx)
    objective_values = [new_objective]

    protect_threshold = (criteria == :abstol ? T(1e6) : T(1e3)) * n_elem
    initial_objective = new_objective
    lowest_objective = new_objective
    lowest_xest = x

    @inbounds while nstep < maxiters
        x, fx, Δx, Δfx, ite = line_search(update, x, fx, f, nstep, false)
        nstep += 1
        tnstep += (ite + 1)

        new_objective = criteria == :abstol ? norm(fx) : (norm(fx) / (norm(fx .+ x) + eps(T)))
        push!(objective_values, new_objective)

        if new_objective < lowest_objective
            lowest_objective = new_objective
            lowest_xest = x
        end
        new_objective < ϵ && break

        new_objective < 3ϵ &&
            nstep >= 30 &&
            maximum(objective_values[(end - nstep + 1):end]) < 1.3 * minimum(objective_values[(end - nstep + 1):end]) &&
            break

        # Prevent Divergence
        (new_objective > initial_objective * protect_threshold) && break

        @views part_Us = Us[1:min(LBFGS_threshold, nstep), :, :, :]
        @views part_VTs = VTs[:, :, 1:min(LBFGS_threshold, nstep), :]

        vT = rmatvec(part_Us, part_VTs, Δx)  # 2D x C x N
        u = (Δx .- matvec(part_Us, part_VTs, Δfx)) ./ sum(vT .* Δfx; dims=(1, 2))  # 2D x C x N
        vT[.!isfinite.(vT)] .= T(0)
        u[.!isfinite.(u)] .= T(0)

        @views VTs[:, :, mod1(nstep, LBFGS_threshold), :] .= vT
        @views Us[mod1(nstep, LBFGS_threshold), :, :, :] .= u

        @views update = -matvec(Us[1:min(LBFGS_threshold, nstep + 1), :, :, :],
                                VTs[:, :, 1:min(LBFGS_threshold, nstep + 1), :], fx)
    end

    return vec(lowest_xest)
end

function matvec(part_Us::AbstractArray{E,4}, part_VTs::AbstractArray{E,4}, x::AbstractArray{E,3}) where {E}
    # part_Us -> (T x D x C x N)
    # part_VTs -> (D x C x T x N)
    # x -> (D x C x N)
    length(part_Us) == 0 && return -x
    T, D, C, N = size(part_Us)
    xTU = sum(reshape(x, (1, D, C, N)) .* part_Us; dims=(2, 3)) # T x 1 x 1 x N
    return -x .+ reshape(sum(permutedims(xTU, (2, 3, 1, 4)) .* part_VTs; dims=3), (D, C, N))
end

function rmatvec(part_Us::AbstractArray{E,4}, part_VTs::AbstractArray{E,4}, x::AbstractArray{E,3}) where {E}
    # part_Us -> (T x D x C x N)
    # part_VTs -> (D x C x T x N)
    # x -> (D x C x N)
    length(part_Us) == 0 && return -x
    T, D, C, N = size(part_Us)
    VTx = sum(part_VTs .* reshape(x, (D, C, 1, N)); dims=(1, 2)) # 1 x 1 x T x N
    return -x .+ reshape(sum(part_Us .* permutedims(VTx, (3, 1, 2, 4)); dims=1), (D, C, N))
end


text:
using GPLikelihoods
using GPLikelihoods: GaussHermiteExpectation, MonteCarloExpectation
using GPLikelihoods.TestInterface: test_interface
using Test
using Random
using Functors
using Distributions
using StatsFuns
using Zygote

@testset "GPLikelihoods.jl" begin
    include("links.jl")
    @testset "likelihoods" begin
        include("likelihoods/bernoulli.jl")
        include("likelihoods/categorical.jl")
        include("likelihoods/gaussian.jl")
        include("likelihoods/poisson.jl")
        include("likelihoods/gamma.jl")
        include("likelihoods/exponential.jl")
        include("likelihoods/negativebinomial.jl")
    end
    include("expectations.jl")
end


text:
module Events

# REFERENCES
#
#    [1] Extensible Markup Language (XML) 1.0 (Fifth Edition),
#        https://www.w3.org/TR/xml/

# ----------------------------------------
# EXPORTED INTERFACE
# ----------------------------------------

export events

# ----------------------------------------
# IMPORTED NAMESPACES
# ----------------------------------------

include("./Lexical.jl")

import .Lexical

# ----------------------------------------
# TYPE DECLARATIONS
# ----------------------------------------

# This is used for element declarations only, but has to be declared earlier because it appears in ElementDeclaration.
#
abstract type AbstractModel
end

struct ExternalIdentifier
    public_identifier ::Union{Nothing, String}
    system_identifier ::Union{Nothing, String}
    location          ::Lexical.Location
end

struct CDATAMarkedSectionStart
    location ::Lexical.Location
end

struct CDATAMarkedSectionEnd
    is_recovery ::Bool
    location    ::Lexical.Location
end

struct CharacterReference
    value    ::String # It's someone else's job to verify that the value is a legitimate character.
    location ::Lexical.Location
end

struct CommentStart
    location ::Lexical.Location
end

struct CommentEnd
    is_recovery ::Bool
    location    ::Lexical.Location
end

struct DTDEnd
    location ::Lexical.Location
end

struct DTDStart
    root                ::String
    external_identifier ::Union{Nothing, ExternalIdentifier}
    location            ::Lexical.Location
end

struct DTDInternalStart
    location ::Lexical.Location
end

struct DataContent
    value    ::String
    is_ws    ::Bool
    location ::Lexical.Location
end

struct ElementDeclaration
    is_recovery   ::Bool
    name          ::String
    content_model ::AbstractModel
    location      ::Lexical.Location
end

struct EntityDeclarationExternalGeneralData
    name                ::String
    external_identifier ::Union{Nothing, ExternalIdentifier}
    ndata_declaration   ::Union{Nothing, String}
    location            ::Lexical.Location
end

struct EntityDeclarationExternalGeneralText
    name                ::String
    external_identifier ::Union{Nothing, ExternalIdentifier}
    location            ::Lexical.Location
end

struct EntityDeclarationExternalParameter
    name                ::String
    external_identifier ::Union{Nothing, ExternalIdentifier}
    location            ::Lexical.Location
end

struct EntityDeclarationInternalGeneral
    name         ::String
    entity_value ::String
    location     ::Lexical.Location
end

struct EntityDeclarationInternalParameter
    name         ::String
    entity_value ::String
    location     ::Lexical.Location
end

struct EntityReferenceGeneral
    name     ::String
    location ::Lexical.Location
end

struct EntityReferenceParameter
    name     ::String
    location ::Lexical.Location
end

struct NotationDeclaration
    name                ::String
    external_identifier ::Union{Nothing, ExternalIdentifier}
    location            ::Lexical.Location
end

struct ProcessingInstruction
    target   ::String
    value    ::String
    location ::Lexical.Location
end

struct MarkupError
    message  ::String # I'll eventually do something more sophisticated here.
    location ::Lexical.Location
end

struct AttributeSpecification
    name     ::String
    value    ::Array{Union{DataContent, CharacterReference, EntityReferenceGeneral, EntityReferenceParameter}, 1}
    location ::Lexical.Location
end

struct ElementEnd
    is_recovery ::Bool
    name        ::String
    location    ::Lexical.Location
end

struct ElementStart
    is_recovery ::Bool
    is_empty    ::Bool
    name        ::String
    attributes  ::Array{AttributeSpecification, 1}
    location    ::Lexical.Location
end

struct ConditionalSectionEnd
    location ::Lexical.Location
end

struct ConditionalSectionStart
    is_recovery ::Bool
    conditional ::Union{String, EntityReferenceParameter}
    location    ::Lexical.Location
end

# These are for element declarations.
#
struct AnyModel <: AbstractModel
end

struct ElementModel <: AbstractModel
    element_name ::String
end

struct EmptyModel <: AbstractModel
end

struct MixedModel <: AbstractModel
   items ::Array{AbstractModel, 1}
end

struct ChoiceGroup <: AbstractModel
   items ::Array{AbstractModel, 1}
end

struct SequenceGroup <: AbstractModel
   items ::Array{AbstractModel, 1}
end

struct OneOrMore <: AbstractModel
   item ::AbstractModel
end

struct Optional <: AbstractModel
   item ::AbstractModel
end

struct ZeroOrMore <: AbstractModel
   item ::AbstractModel
end

# These are for attribute declarations.
#
struct EntityType
end

struct EntitiesType
end

struct EnumeratedNotationType
    names ::Array{String, 1}
end

struct EnumeratedType
    nmtokens ::Array{String, 1}
end

struct IDRefType
end

struct IDRefsType
end

struct IDType
end

struct NameTokenType
end

struct NameTokensType
end

struct StringType
end

struct DefaultValue
    is_fixed ::Bool
    value    ::Array{Union{DataContent, CharacterReference, EntityReferenceGeneral, EntityReferenceParameter}, 1}
end

struct Implied
end

struct Required
end

struct AttributeDeclaration
    is_recovery ::Bool # This basically is a synthesized attribute.
    name        ::String
    type        ::Union{EntityType, EntitiesType, EnumeratedNotationType, EnumeratedType, IDRefType,
                        IDRefsType, IDType, NameTokenType, NameTokensType, StringType}
    default     ::Union{DefaultValue, Implied, Required}
end

struct AttributeDeclarations
    is_recovery  ::Bool # This is a synthesized attribute, synthesized from the contained AttributeDeclaration children.
    name         ::String
    declarations ::Array{AttributeDeclaration, 1}
    location     ::Lexical.Location
end

# ----------------------------------------
# FUNCTIONS
# ----------------------------------------

CommentEnd(location) = CommentEnd(false, location)
CDATAMarkedSectionEnd(location) = CDATAMarkedSectionEnd(false, location)
ConditionalSectionStart(conditional, location) = ConditionalSectionStart(false, conditional, location)
ElementEnd(name, location) = ElementEnd(false, name, location)
ElementStart(name, attributes, location) = ElementStart(false, name, attributes, location)
ElementStart(is_empty, name, attributes, location) = ElementStart(false, is_empty, name, attributes, location)
DataContent(tokens::Array, location) = DataContent(stringify(tokens), location)
DataContent(value, location) = DataContent(value, false, location)

# These are needed for writing tests, because these types contain other structs inside an array.
#
function Base.:(==)(left::ElementStart, right::ElementStart)
    return (left.is_recovery   == right.is_recovery
            && left.name       == right.name
            && left.attributes == right.attributes
            && left.location   == right.location)
end

function Base.:(==)(left::AttributeSpecification, right::AttributeSpecification)
    return (left.name        == right.name
            && left.value    == right.value
            && left.location == right.location)
end

function Base.:(==)(left::ElementDeclaration, right::ElementDeclaration)
    return (left.is_recovery      == right.is_recovery
            && left.name          == right.name
            && left.content_model == right.content_model
            && left.location      == right.location)
end

function Base.:(==)(left::MixedModel, right::MixedModel)
    return left.items == right.items
end

function Base.:(==)(left::OneOrMore, right::OneOrMore)
    return left.item == right.item
end

function Base.:(==)(left::Optional, right::Optional)
    return left.item == right.item
end

function Base.:(==)(left::ZeroOrMore, right::ZeroOrMore)
    return left.item == right.item
end

function Base.:(==)(left::ChoiceGroup, right::ChoiceGroup)
    return left.items == right.items
end

function Base.:(==)(left::SequenceGroup, right::SequenceGroup)
    return left.items == right.items
end

function Base.:(==)(left::AttributeDeclarations, right::AttributeDeclarations)
    return (left.is_recovery     == right.is_recovery
            && left.name         == right.name
            && left.declarations == right.declarations
            && left.location     == right.location)
end

function Base.:(==)(left::AttributeDeclaration, right::AttributeDeclaration)
    return (left.is_recovery == right.is_recovery
            && left.name     == right.name
            && left.type     == right.type
            && left.default  == right.default)
end

function Base.:(==)(left::EnumeratedType, right::EnumeratedType)
    return left.nmtokens == right.nmtokens
end

function Base.:(==)(left::EnumeratedNotationType, right::EnumeratedNotationType)
    return left.names == right.names
end

function Base.:(==)(left::DefaultValue, right::DefaultValue)
    return left.value == right.value
end

function attribute_declarations(mdo, tokens, channel)
    function attribute_declaration(element_name, tokens, channel)
        attribute_name = take!(tokens) # Collect the attribute name token that got us here ...
        consume_white_space!(tokens)   # ... but discard any following white space.

        ( attribute_type_recovery, attribute_type ) = collect_attribute_type(element_name, attribute_name, tokens, channel)
        consume_white_space!(tokens) # Discard any trailing white space.

        ( attribute_default_recovery, attribute_default ) = collect_attribute_default(element_name, attribute_name, tokens, channel)

        return AttributeDeclaration(any([ attribute_type_recovery, attribute_default_recovery ]),
                                    attribute_name.value, attribute_type, attribute_default)
    end

    function collect_attribute_default(element_name, attribute_name, tokens, channel)
        attribute_default = Implied()
        is_recovery       = false

        if is_reserved_name("#IMPLIED", tokens, channel)
            take!(tokens)

        elseif is_reserved_name("#REQUIRED", tokens, channel)
            take!(tokens)
            attribute_default = Required()

        else
            is_fixed = false
            if is_reserved_name("#FIXED", tokens, channel)
                is_fixed = true
                take!(tokens)
                consume_white_space!(tokens)
            end

            ( value_recovery, value ) = collect_attribute_value(attribute_name, tokens, channel)

            attribute_default = DefaultValue(is_fixed, value)
            is_recovery = any([ is_recovery, value_recovery ])
        end

        return ( is_recovery, attribute_default )
    end

    function collect_attribute_type(element_name, attribute_name, tokens, channel)
        attribute_type = StringType()
        is_recovery    = false

        if is_keyword("CDATA", tokens, channel)
            take!(tokens)

        elseif is_keyword("ENTITY", tokens, channel)
            take!(tokens)
            attribute_type = EntityType()

        elseif is_keyword("ENTITIES", tokens, channel)
            take!(tokens)
            attribute_type = EntitiesType()

        elseif is_keyword("IDREF", tokens, channel)
            take!(tokens)
            attribute_type = IDRefType()

        elseif is_keyword("IDREFS", tokens, channel)
            take!(tokens)
            attribute_type = IDRefsType()

        elseif is_keyword("ID", tokens, channel)
            take!(tokens)
            attribute_type = IDType()

        elseif is_keyword("NMTOKEN", tokens, channel)
            take!(tokens)
            attribute_type = NameTokenType()

        elseif is_keyword("NMTOKENS", tokens, channel)
            take!(tokens)
            attribute_type = NameTokensType()

        elseif is_keyword("NOTATION", tokens, channel)
            notation = take!(tokens)
            ws = consume_white_space!(tokens)

            if isnothing(ws)
                # See [1], § 3.3.1 ... the white space is required. This is really bogus, because the only thing that
                # can follow NOTATION in this position is an open parenthesis, so there would be no ambiguity in making
                # the white space optional
                #
                put!(channel, MarkupError("ERROR: White space is required following the 'NOTATION' keyword.",
                                          Lexical.location_of(notation)))
                is_recovery = true
            end

            if !is_token(Lexical.grpo, tokens)
                put!(channel, MarkupError("ERROR: Expecting '(' to open an enumerated notation attribute value.",
                                          Lexical.location_of(notation)))
                is_recovery = true
            end

            # Regardless of whether or not we saw GRPO, try parsing the group as a recovery mechanism.
            #
            ( names_recovery, names ) = collect_group(notation, is_name,
                                                      "ERROR: Expecting a name for an enumerated attribute value.",
                                                      tokens, channel)
            is_recovery = any([ is_recovery, names_recovery ])
            attribute_type = EnumeratedNotationType(names)

        elseif is_token(Lexical.grpo, tokens)
            ( nmtokens_recovery, nmtokens ) = collect_group(attribute_name, is_nmtoken,
                                                            "ERROR: Expecting a NMTOKEN for an enumerated attribute value.",
                                                            tokens, channel)
            is_recovery = any([ is_recovery, nmtokens_recovery ])
            attribute_type = EnumeratedType(nmtokens)

        else
            put!(channel, MarkupError("ERROR: Expecting an attribute type.", Lexical.location_of(attribute_name)))
        end

        return ( is_recovery, attribute_type )
    end

    function collect_group(token, matcher, message, tokens, channel)
        # Careful here ... we're called whether or not the GRPO was present. So only consume it if it's there.
        #
        if is_token(Lexical.grpo, tokens)
            grpo = take!(tokens)
            consume_white_space!(tokens)
        end

        items = Array{String, 1}()
        is_recovery = false

        while true
            if matcher(tokens)
                push!(items, take!(tokens).value)
                consume_white_space!(tokens)

            elseif is_token(Lexical.or, tokens) || is_token(Lexical.grpc, tokens)
                # Leave the token there ... we'll consume it below.
                #
                put!(channel, MarkupError(message, Lexical.location_of(token)))
                is_recovery = true

            else
                put!(channel, MarkupError(message, Lexical.location_of(token)))
                is_recovery = true
                take!(tokens) # Drop the token and try recovering.
                consume_white_space!(tokens)
            end

            if is_token(Lexical.or, tokens)
                take!(tokens)
                consume_white_space!(tokens)

            elseif is_token(Lexical.grpc, tokens)
                take!(tokens)
                consume_white_space!(tokens)
                break

            elseif matcher(tokens)
                put!(channel, MarkupError("ERROR: Expecting '|' between items in an enumerated attribute value.",
                                          Lexical.location_of(token)))
                is_recovery = true

            else
                put!(channel, MarkupError("ERROR: Expecting '|' or ')'.", Lexical.location_of(token)))
                is_recovery = true
                break
            end
        end

        return ( is_recovery, items )
    end

    attlist = take!(tokens) # Consume the ATTLIST keyword that got us here.
    ws      = consume_white_space!(tokens)

    is_recovery = false

    if isnothing(ws)
        # See [1], § 3.3 ... the white space is required.
        #
        put!(channel, MarkupError("ERROR: White space is required following the 'ATTLIST' keyword.", Lexical.location_of(attlist)))
        is_recovery = true
    end

    if is_name(tokens)
        element_name = take!(tokens)

        attribute_declarations = Array{AttributeDeclaration, 1}()

        while true
            if is_token(Lexical.ws, tokens)
                consume_white_space!(tokens)

                if is_name(tokens)
                    push!(attribute_declarations, attribute_declaration(element_name, tokens, channel))

                else
                    break
                end

            else
                break
            end
        end

        if is_token(Lexical.tagc, tokens)
            take!(tokens)

        else
            is_recovery = true
            put!(channel, MarkupError("ERROR: Expecting '>' to end an attribute list declaration.",
                                      Lexical.location_of(element_name)))
        end

        is_recovery = any(vcat(is_recovery, map(declaration -> declaration.is_recovery, attribute_declarations)))

        put!(channel, AttributeDeclarations(is_recovery, element_name.value, attribute_declarations, Lexical.location_of(mdo)))

    else
        put!(channel, MarkupError("ERROR: Expecting an element name.", Lexical.location_of(attlist)))
    end
end

function cdata_marked_section(mdo, dso, tokens, channel)
    text = take!(tokens) # Consume the CDATA keyword that got us here.

    ws = consume_white_space!(tokens)
    if !isnothing(ws)
        # Discard the white space ... there isn't much we can do with it.
        #
        put!(channel, MarkupError("ERROR: White space is not allowed after the 'CDATA' keyword.", Lexical.location_of(text)))
    end

    if is_token(Lexical.dso, tokens)
        take!(tokens)

        put!(channel, CDATAMarkedSectionStart(Lexical.location_of(mdo)))

        consumed = Array{Lexical.Token, 1}()

        while true
            if is_token(Lexical.msc, tokens)
                msc = take!(tokens)

                if is_token(Lexical.tagc, tokens)
                    take!(tokens)
                    put!(channel, DataContent(consumed, locations_of(mdo, consumed)[:head]))
                    put!(channel, CDATAMarkedSectionEnd(Lexical.location_of(msc)))
                    break

                else
                    put!(channel, DataContent(consumed, locations_of(mdo, consumed)[:head]))
                    put!(channel, MarkupError("ERROR: Expecting '>' to end a CDATA marked section.", Lexical.location_of(msc)))
                    put!(channel, CDATAMarkedSectionEnd(true, Lexical.location_of(msc)))
                    break
                end

            elseif is_eoi(tokens)
                put!(channel, DataContent(consumed, locations_of(mdo, consumed)[:head]))
                put!(channel, MarkupError("ERROR: Expecting ']]>' to end a CDATA marked section.",
                                          locations_of(mdo, consumed)[:tail]))
                put!(channel, CDATAMarkedSectionEnd(true, locations_of(mdo, consumed)[:tail]))
                break

            else
                push!(consumed, take!(tokens))
            end
        end

    else
        put!(channel, MarkupError("ERROR: Expecting '[' to open a CDATA marked section.", Lexical.location_of(text)))
    end
end

function comment(mdo, tokens, channel)
    com = take!(tokens) # Consume the COM token that got us here.

    consumed = Array{Lexical.Token, 1}()

    put!(channel, CommentStart(Lexical.location_of(mdo)))

    while true
        if is_token(Lexical.com, tokens)
            tail = take!(tokens)

            # We cannot encounter EOI here ... if we hit "--" at EOI, it gets classified as TEXT, not COM. See
            # consume_until() in src/Lexical.jl
            #
            if is_token(Lexical.tagc, tokens)
                take!(tokens)
                put!(channel, DataContent(consumed, locations_of(com, consumed)[:head]))
                put!(channel, CommentEnd(Lexical.location_of(com)))
                break

            else
                put!(channel, DataContent(consumed, locations_of(com, consumed)[:head]))
                put!(channel, MarkupError("ERROR: '--' is not allowed inside a comment.", locations_of(com, consumed)[:tail]))
                put!(channel, CommentEnd(true, locations_of(com, consumed)[:tail]))
                break
            end

        elseif is_eoi(tokens)
            put!(channel, DataContent(consumed, locations_of(com, consumed)[:head]))
            put!(channel, MarkupError("ERROR: Expecting '-->' to end a comment.", locations_of(com, consumed)[:tail]))
            put!(channel, CommentEnd(true, locations_of(com, consumed)[:tail]))
            break

        else
            push!(consumed, take!(tokens))
        end
    end
end

function conditional_marked_section(mdo, dso, conditional, tokens, channel)
    if is_token(Lexical.dso, tokens)
        is_recovery(string::String) = !(all(map(isuppercase, collect(string))))
        is_recovery(_) = false

        take!(tokens) # Discard the DSO token that follows the conditional ... we don't need it.

        put!(channel, ConditionalSectionStart(is_recovery(conditional), conditional, Lexical.location_of(mdo)))

    else
        # It would be more accurate to use the location of the conditional here, but it isn't necessarily a token ... it
        # could be a properly-parsed parameter entity reference.
        #
        put!(channel, MarkupError("ERROR: Expecting '[' to open a conditional marked section.", Lexical.location_of(mdo)))
        put!(channel, ConditionalSectionStart(true, conditional, Lexical.location_of(mdo)))
    end
end

function document_type_declaration(mdo, tokens, channel)
    doctype = take!(tokens)      # Consume the DOCTYPE keyword that got us here ...
    consume_white_space!(tokens) # ... but discard any following white space.

    if is_name(tokens)
        root = take!(tokens)
        consume_white_space!(tokens)
        external_identifier = collect_external_identifier(mdo, tokens, channel)
        consume_white_space!(tokens)
        if is_token(Lexical.tagc, tokens)
            tagc = take!(tokens)
            put!(channel, DTDStart(root.value, external_identifier, Lexical.location_of(doctype)))
            put!(channel, DTDEnd(Lexical.location_of(doctype)))

        elseif is_token(Lexical.dso, tokens)
            dso = take!(tokens)
            put!(channel, DTDStart(root.value, external_identifier, Lexical.location_of(doctype)))
            put!(channel, DTDInternalStart(Lexical.location_of(doctype)))

        else
            put!(channel, MarkupError("ERROR: Expecting '>' to end a document type declaration.", Lexical.location_of(root)))
        end

    else
        put!(channel, MarkupError("ERROR: Expecting a root element name.", Lexical.location_of(doctype)))
    end
end

function element_declaration(mdo, tokens, channel)
    element = take!(tokens) # Consume the ELEMENT keyword that got us here.
    ws = consume_white_space!(tokens)

    is_recovery = false

    if isnothing(ws)
        # See [1], § 3.2 ... the white space is required.
        #
        put!(channel, MarkupError("ERROR: White space is required following the 'ELEMENT' keyword.", Lexical.location_of(element)))
        is_recovery = true
    end

    if is_name(tokens)
        element_name = take!(tokens)
        ws = consume_white_space!(tokens)

        if isnothing(ws)
            # See [1], § 3.2 ... the white space is required. This is a bit lame ... the white space wouldn't be needed
            # to disambiguate in some cases (e.g., if the content model begins with a parenthesis), but ... well
            # ... whatever.
            #
            put!(channel, MarkupError("ERROR: White space is required following an element name.",
                                      Lexical.location_of(element_name)))
            is_recovery = true
        end

        content_model = AnyModel()

        if is_keyword("ANY", tokens, channel)
            take!(tokens)
            content_model = AnyModel()

        elseif is_keyword("EMPTY", tokens, channel)
            take!(tokens)
            content_model = EmptyModel()

        elseif is_token(Lexical.grpo, tokens)
            grpo = take!(tokens)
            consume_white_space!(tokens)

            if is_reserved_name("#PCDATA", tokens, channel)
                take!(tokens)
                consume_white_space!(tokens)

                ( content_model_recovery, items ) = collect_mixed_content_model(tokens, channel)
                is_recovery = is_recovery || content_model_recovery

                if is_token(Lexical.grpc, tokens)
                    take!(tokens)

                    if is_token(Lexical.rep, tokens)
                        take!(tokens)

                    elseif length(items) > 0
                        # The trailing '*' is only required if element names were encountered while parsing the content
                        # model. Really. See § 3.2.2, the first branch of production [51].
                        #
                        put!(channel, MarkupError("ERROR: Expecting '*' to end a mixed content model.",
                                                  Lexical.location_of(element_name)))
                        is_recovery = true
                    end

                else
                    put!(channel, MarkupError("ERROR: Expecting ')*' to end a mixed content model.",
                                              Lexical.location_of(element_name)))
                    is_recovery = true
                end

                content_model = MixedModel(items)

            else
                ( group_recovery, content_model ) = collect_content_model_group(grpo, tokens, channel)
                is_recovery = is_recovery || group_recovery

                if isnothing(content_model)
                    content_model = AnyModel()

                else
                    if is_token(Lexical.grpc, tokens)
                        take!(tokens)
                        consume_white_space!(tokens)

                    else
                        consume_white_space!(tokens)
                        put!(channel, MarkupError("ERROR: Expecting ')' to end a content model group.", Lexical.location_of(grpo)))
                        is_recovery = true
                    end

                    content_model = collect_occurrence_indicator(content_model, tokens)
                end
            end

        else
            put!(channel, MarkupError("ERROR: Expecting 'ANY', 'EMPTY', or '(' to open a content model.",
                                      Lexical.location_of(element)))
            is_recovery = true
        end

        consume_white_space!(tokens)

        if is_token(Lexical.tagc, tokens)
            take!(tokens)
            put!(channel, ElementDeclaration(is_recovery, element_name.value, content_model, Lexical.location_of(mdo)))

        else
            take!(tokens)
            put!(channel, MarkupError("ERROR: Expecting '>' to end an element declaration.", Lexical.location_of(element_name)))
            put!(channel, ElementDeclaration(true, element_name.value, content_model, Lexical.location_of(element)))
        end

    else
        put!(channel, MarkupError("ERROR: Expecting an element name.", Lexical.location_of(element)))
    end
end

function element_end(tokens, channel)
    etago = take!(tokens) # Consume the ETAGO that got us here.

    if is_name(tokens)
        name = take!(tokens)
        consume_white_space!(tokens)

        if is_token(Lexical.tagc, tokens)
            take!(tokens)
            put!(channel, ElementEnd(name.value, Lexical.location_of(name)))

        else
            put!(channel, MarkupError("ERROR: Expecting '>' to end an element close tag.", Lexical.location_of(name)))
            put!(channel, ElementEnd(true, name.value, Lexical.location_of(name)))
        end

    else
        put!(channel, MarkupError("ERROR: Expecting an element name.", Lexical.location_of(etago)))
    end
end

function element_start(tokens, channel)
    stago = take!(tokens) # Consume the STAGO that got us here.

    if is_name(tokens)
        name = take!(tokens)
        attributes = collect_attributes(tokens, channel) # We should really flip the is_recovery flag on the
                                                         # ElementStart if any of the attributes contain a MarkupError.
        consume_white_space!(tokens)

        if is_token(Lexical.net, tokens)
            take!(tokens)
            if is_token(Lexical.tagc, tokens)
                take!(tokens)
                put!(channel, ElementStart(true, name.value, attributes, Lexical.location_of(name)))
                put!(channel, ElementEnd(name.value, Lexical.location_of(name)))

            else
                put!(channel, ElementStart(true, true, name.value, attributes, Lexical.location_of(name)))
                put!(channel, MarkupError("ERROR: Expecting '>' to end an element open tag.", Lexical.location_of(name)))
                put!(channel, ElementEnd(true, name.value, Lexical.location_of(name)))
            end

        elseif is_token(Lexical.tagc, tokens)
            take!(tokens)
            put!(channel, ElementStart(name.value, attributes, Lexical.location_of(name)))

        else
            put!(channel, ElementStart(true, false, name.value, attributes, Lexical.location_of(name)))
            put!(channel, MarkupError("ERROR: Expecting '>' to end an element open tag.", Lexical.location_of(name)))
        end

    else
        put!(channel, MarkupError("ERROR: Expecting an element name.", Lexical.location_of(stago)))
    end
end

function entity_declaration(mdo, tokens, channel)
    function collect_entity_definition(entity_name, tokens, channel)
        external_identifier = collect_external_identifier(entity_name, tokens, channel)
        if isnothing(external_identifier)
            entity_value = collect_string(Lexical.location_of(entity_name), tokens, channel)

            if isnothing(entity_value)
                # Something is messed up.
                #
                return ( external_identifier = nothing, entity_value = nothing, ndata_name = nothing )

            else
                return ( external_identifier = external_identifier, entity_value = stringify(entity_value), ndata_name = nothing )
            end

        else
            consume_white_space!(tokens)
            if is_keyword("NDATA", tokens, channel)
                ndata = take!(tokens)        # Consume the NDATA keyword ...
                consume_white_space!(tokens) # ... but discard any following white space.

                if is_name(tokens)
                    ndata_name = take!(tokens)

                    return ( external_identifier = external_identifier, entity_value = nothing, ndata_name =  ndata_name )

                else
                    put!(channel, MarkupError("ERROR: Expecting a notation name.", Lexical.location_of(entity_name)))

                    return ( external_identifier = external_identifier, entity_value = nothing, ndata_name = nothing )
                end

            else
                return ( external_identifier = external_identifier, entity_value = nothing, ndata_name = nothing )
            end
        end
    end

    entity = take!(tokens) # Consume the ENTITY keyword that got us here.
    ws = consume_white_space!(tokens)

    if isnothing(ws)
        # See [1], § 4.2 ... the white space is required.
        #
        put!(channel, MarkupError("ERROR: White space is required following the 'ENTITY' keyword.", Lexical.location_of(entity)))
    end

    is_parameter_entity = is_token(Lexical.pero, tokens)
    if is_parameter_entity
        pero = take!(tokens)
        ws = consume_white_space!(tokens)

        if isnothing(ws)
            # See [1], § 4.2 ... the white space is required. This one is pretty lame, but it is what it is.
            #
            put!(channel, MarkupError("ERROR: White space is required following '%'.", Lexical.location_of(pero)))
        end
    end

    if is_name(tokens)
        entity_name = take!(tokens)
        ws = consume_white_space!(tokens)

        if isnothing(ws)
            # See [1], § 4.2, productions [71] and [72] ... the white space is required.
            #
            put!(channel, MarkupError("ERROR: White space is required between the entity name and the entity value.",
                                      Lexical.location_of(entity)))
        end

        ( external_identifier, entity_value, ndata_name ) = collect_entity_definition(entity_name, tokens, channel)
        consume_white_space!(tokens)

        if is_parameter_entity && !isnothing(ndata_name)
            put!(channel, MarkupError("ERROR: A parameter entity cannot have a notation.", Lexical.location_of(entity_name)))
            ndata_name = nothing
        end

        if is_token(Lexical.tagc, tokens)
            tagc = take!(tokens)

        else
            put!(channel, MarkupError("ERROR: Expecting '>' to end an entity declaration.", Lexical.location_of(entity_name)))
            # From now on in this branch, we're basically doing error-recovery: better to pretend the entity declaration
            # was properly parsed, otherwise there could be a cascade of errors down the line.
            #
        end

        constructor = nothing # Make sure we throw if something goes wrong below.

        if isnothing(entity_value)
            if is_parameter_entity
                constructor = (name, location) -> EntityDeclarationExternalParameter(name, external_identifier, location)

            elseif isnothing(ndata_name)
                constructor = (name, location) -> EntityDeclarationExternalGeneralText(name, external_identifier, location)

            else
                constructor = (name, location) -> EntityDeclarationExternalGeneralData(name, external_identifier,
                                                                                ndata_name.value, location)
            end

        else
            if is_parameter_entity
                constructor = (name, location) -> EntityDeclarationInternalParameter(name, entity_value, location)

            else
                constructor = (name, location) -> EntityDeclarationInternalGeneral(name, entity_value, location)
            end
        end

        put!(channel, constructor(entity_name.value, Lexical.location_of(entity)))

    else
        put!(channel, MarkupError("ERROR: Expecting an entity name.", Lexical.location_of(entity)))
    end
end

function events(state::Lexical.State)
    function eventified(channel::Channel)
        tokens = Lexical.tokens(state)

        while true
            if is_token(Lexical.mdo, tokens)
                markup_declaration(tokens, channel)

            elseif is_token(Lexical.cro, tokens)
                put!(channel, collect_character_reference(tokens, channel))

            elseif is_token(Lexical.ero, tokens)
                put!(channel, collect_entity_reference(tokens, channel))

            elseif is_token(Lexical.pero, tokens)
                put!(channel, collect_parameter_entity_reference(tokens, channel))

            elseif is_token(Lexical.pio, tokens)
                processing_instruction(tokens, channel)

            elseif is_token(Lexical.stago, tokens)
                element_start(tokens, channel)

            elseif is_token(Lexical.etago, tokens)
                element_end(tokens, channel)

            elseif is_eoi(tokens)
                break

            elseif is_token(Lexical.msc, tokens)
                msc = take!(tokens) # We're only holding on to this for the sake of its location.

                if is_token(Lexical.tagc, tokens)
                    take!(tokens) # We don't need to hold on to this.
                    push!(channel, ConditionalSectionEnd(Lexical.location_of(msc)))

                else
                    push!(channel, DataContent(msc.value, Lexical.location_of(msc)))
                end

            else
                if is_token(Lexical.ws, tokens)
                    ws = consume_white_space!(tokens)
                    push!(channel, DataContent(ws.value, true, Lexical.location_of(ws)))

                elseif is_token(Lexical.illegal, tokens)
                    token = take!(tokens)
                    put!(channel, MarkupError("ERROR: Encountered the illegal character \'"
                                              * "#x" * string(Int(token.value[1]), base = 16) * "\'."
                                              * " It will be discarded.", Lexical.location_of(token)))

                else
                    token = take!(tokens)
                    push!(channel, DataContent(token.value, Lexical.location_of(token)))
                end
            end
        end
    end

    return Channel(eventified; csize = 1)
end

function marked_section(mdo, tokens, channel)
    dso = take!(tokens) # Consume the DSO that got us here.

    leading = consume_white_space!(tokens) # There might be white space here. It is allowed for conditional sections,
                                           # but disallowed for CDATA sections. For some reason.

    if is_keyword("CDATA", tokens, channel)
        if !isnothing(leading)
            # Discard the white space ... there isn't much we can do with it.
            #
            put!(channel, MarkupError("ERROR: White space is not allowed before the 'CDATA' keyword.", Lexical.location_of(dso)))
        end

        cdata_marked_section(mdo, dso, tokens, channel)

    elseif is_keyword("IGNORE", tokens, channel) || is_keyword("INCLUDE", tokens, channel)
        conditional = take!(tokens)  # Consume the IGNORE/INCLUDE keyword ...
        consume_white_space!(tokens) # ... and discard any trailing white space.

        conditional_marked_section(mdo, dso, conditional.value, tokens, channel)

    elseif is_token(Lexical.pero, tokens)
        conditional = collect_parameter_entity_reference(tokens, channel) # Consume the parameter entity reference ...
        consume_white_space!(tokens)                                      # ... and discard any trailing white space.

        conditional_marked_section(mdo, dso, conditional, tokens, channel)

    else
        put!(channel, MarkupError("ERROR: Expecting the start of a CDATA or conditional marked section.", Lexical.location_of(dso)))
        if !isnothing(leading)
            put!(channel, DataContent(leading.value, true, locations_of(mdo, [ dso ])[:head]))
        end
    end
end

function markup_declaration(tokens, channel)
    mdo = take!(tokens) # Consume the MDO token that got us here.

    if is_token(Lexical.dso, tokens)
        marked_section(mdo, tokens, channel)

    elseif is_token(Lexical.com, tokens)
        comment(mdo, tokens, channel)

    elseif is_keyword("ATTLIST", tokens, channel)
        attribute_declarations(mdo, tokens, channel)

    elseif is_keyword("DOCTYPE", tokens, channel)
        document_type_declaration(mdo, tokens, channel)

    elseif is_keyword("ELEMENT", tokens, channel)
        element_declaration(mdo, tokens, channel)

    elseif is_keyword("ENTITY", tokens, channel)
        entity_declaration(mdo, tokens, channel)

    elseif is_keyword("NOTATION", tokens, channel)
        notation_declaration(mdo, tokens, channel)

    else
        if is_keyword("sgml", tokens, channel)
            put!(channel, MarkupError("ERROR: The keyword 'SGML' is not available in XML.", Lexical.location_of(mdo)))

        elseif is_keyword("shortref", tokens, channel)
            put!(channel, MarkupError("ERROR: The keyword 'SHORTREF' is not available in XML.", Lexical.location_of(mdo)))

        elseif is_keyword("usemap", tokens, channel)
            put!(channel, MarkupError("ERROR: The keyword 'USEMAP' is not available in XML.", Lexical.location_of(mdo)))

        else
            put!(channel, MarkupError("ERROR: Expecting the start of a markup declaration.", Lexical.location_of(mdo)))
        end
    end
end

function notation_declaration(mdo, tokens, channel)
    notation = take!(tokens)      # Consume the NOTATION keyword that got us here ...
    consume_white_space!(tokens)  # ... but discard any following white space.

    if is_name(tokens)
        notation_name = take!(tokens)
        consume_white_space!(tokens)

        external_identifier = collect_external_identifier(mdo, tokens, false, channel)
        consume_white_space!(tokens)

        if isnothing(external_identifier)
            put!(channel, MarkupError("ERROR: A notation declaration must specify an external identifier.",
                                      Lexical.location_of(notation_name)))
        end

        if is_token(Lexical.tagc, tokens)
            tagc = take!(tokens)

        else
            put!(channel, MarkupError("ERROR: Expecting '>' to end a notation declaration.", Lexical.location_of(notation_name)))
        end
        put!(channel, NotationDeclaration(notation_name.value, external_identifier, Lexical.location_of(notation)))

    else
        put!(channel, MarkupError("ERROR: Expecting a notation name.", Lexical.location_of(notation)))
    end
end

function processing_instruction(tokens, channel)
    pio = take!(tokens) # Consume the PIO token that got us here.

    if is_name(tokens)
        if is_keyword("XML", tokens, false, channel)
            put!(channel, MarkupError("ERROR: A PI target cannot be any case variant of 'XML'.", Lexical.location_of(pio)))
        end

        target = take!(tokens) # See [1], § 2.6 ... the PI target is required.

        consume_white_space!(tokens) # Discard any white space. Again, see [1], § 2.6 ... the white space is required,
                                     # so it's syntax and doesn't belong in the PI value.

        consumed = Array{Lexical.Token, 1}()

        while true
            if is_token(Lexical.opt, tokens)
                opt = take!(tokens)

                if is_token(Lexical.tagc, tokens)
                    take!(tokens)
                    put!(channel, ProcessingInstruction(target.value, stringify(consumed), Lexical.location_of(pio)))
                    break

                else
                    push!(consumed, opt)
                end

            elseif is_eoi(tokens)
                put!(channel, MarkupError("ERROR: Expecting '?>' to end a processing instruction.",
                                          locations_of(pio, vcat([ target ], consumed))[:tail]))
                break

            else
                push!(consumed, take!(tokens))
            end
        end

    else
        put!(channel, MarkupError("ERROR: Expecting a PI target.", Lexical.location_of(pio)))
    end
end

# ----------------------------------------
# SMALL PARSERS
#
# Small parsers return the event rather than emitting it to a channel. They can, however, emit markup errors.
# ----------------------------------------

function collect_attribute_value(start, tokens, channel)
    value       = Array{Union{DataContent, CharacterReference, EntityReferenceGeneral, EntityReferenceParameter}, 1}()
    is_recovery = false

    if is_token(Lexical.lit, tokens) || is_token(Lexical.lita, tokens)
        delimiter = take!(tokens)

        while true
            if is_eoi(tokens)
                put!(channel, MarkupError("ERROR: Expecting the remainder of an attribute value.", Lexical.location_of(start)))
                is_recovery = true
                break

            elseif fetch(tokens).token_type == delimiter.token_type
                take!(tokens)
                break

            elseif is_token(Lexical.cro, tokens)
                push!(value, collect_character_reference(tokens, channel))

            elseif is_token(Lexical.ero, tokens)
                push!(value, collect_entity_reference(tokens, channel, true))

            elseif is_token(Lexical.stago, tokens)
                stago = take!(tokens)
                put!(channel, MarkupError("ERROR: A '<' must be escaped inside an attribute value.", Lexical.location_of(stago)))
                is_recovery = true

            elseif is_token(Lexical.ws, tokens)
                ws = consume_white_space!(tokens)
                push!(value, DataContent(ws.value, true, Lexical.location_of(ws)))

            else
                data_content = take!(tokens)
                push!(value, DataContent(data_content.value, Lexical.location_of(data_content)))
            end
        end

    else
        put!(channel, MarkupError("ERROR: Expecting a quoted attribute value.", Lexical.location_of(start)))
        is_recovery = true
    end

    return ( is_recovery, value )
end

function collect_attributes(tokens, channel)
    function collect_attribute(tokens, channel)
        name = take!(tokens)         # Collect the attribute name token that got us here ...
        consume_white_space!(tokens) # ... but discard any following white space.

        value = Array{Union{DataContent, CharacterReference, EntityReferenceGeneral, EntityReferenceParameter}, 1}()

        if is_token(Lexical.vi, tokens)
            vi = take!(tokens)
            consume_white_space!(tokens)
            ( _, value ) = collect_attribute_value(vi, tokens, channel)

        else
            put!(channel, MarkupError("ERROR: Expecting '=' after an attribute name.", Lexical.location_of(name)))
        end

        return AttributeSpecification(name.value, value, Lexical.location_of(name))
    end

    attributes = Array{AttributeSpecification, 1}()

    while true
        if is_token(Lexical.ws, tokens)
            consume_white_space!(tokens)

            if is_name(tokens)
                push!(attributes, collect_attribute(tokens, channel))

            else
                break
            end

        else
            break
        end
    end

    return attributes
end

function collect_character_reference(tokens, channel)
    cro = take!(tokens) # Consume the CRO token that got us here.

    if is_token(Lexical.text, tokens)
        value = take!(tokens)
        if is_token(Lexical.refc, tokens)
            refc = take!(tokens)

            return CharacterReference(value.value, Lexical.location_of(cro))

        else
            put!(channel, MarkupError("ERROR: Expecting ';' to end a character reference.", Lexical.location_of(value)))

            return DataContent([ cro, value ], Lexical.location_of(value))
        end

    else
        put!(channel, MarkupError("ERROR: Expecting a character value.", Lexical.location_of(cro)))

        return DataContent(cro.value, Lexical.location_of(cro))
    end
end

function collect_content_model_group(grpo, tokens, channel)
    function collect_content_model_group_items(grpo, separator, tokens, channel)
        # By the time we're called, we've seen one group item and the separator (which has been consumed). See See [1],
        # § 3.2.1 ... we require another item.
        #
        is_recovery = false
        items       = Array{AbstractModel, 1}()

        while true
            ( item_recovery, item ) = collect_content_model_group_item(grpo, tokens, channel)
            is_recovery = is_recovery || item_recovery

            if isnothing(item)
                break

            else
                push!(items, item)
            end

            if is_token(separator.token_type, tokens)
                take!(tokens)
                consume_white_space!(tokens)

            elseif is_token(Lexical.or, tokens) || is_token(Lexical.seq, tokens)
                s = take!(tokens)
                consume_white_space!(tokens)
                put!(channel, MarkupError("ERROR: '|' and ',' cannot be used in the same content group.", Lexical.location_of(s)))
                is_recovery = true

            elseif is_token(Lexical.grpc, tokens)
                break

            else
                put!(channel, MarkupError("ERROR: Expecting '" * separator.value * "'.", Lexical.location_of(grpo)))
                is_recovery = true
            end
        end

        return ( is_recovery, items )
    end

    ( is_recovery, first_item ) = collect_content_model_group_item(grpo, tokens, channel)

    if isnothing(first_item)
        return ( true, first_item )
    end

    if is_token(Lexical.or, tokens) || is_token(Lexical.seq, tokens)
        separator = take!(tokens)
        consume_white_space!(tokens)

        ( group_recovery, items ) = collect_content_model_group_items(grpo, separator, tokens, channel)
        is_recovery = is_recovery || group_recovery

        pushfirst!(items, first_item)

        if separator.token_type == Lexical.or
            return ( is_recovery, ChoiceGroup(items) )

        else
            return ( is_recovery, SequenceGroup(items) )
        end

    else
        return ( false, first_item )
    end
end

function collect_content_model_group_item(grpo, tokens, channel)
    if is_name(tokens)
        name = take!(tokens)
        consume_white_space!(tokens)

        return ( false, ElementModel(name.value) )

    elseif is_token(Lexical.grpo, tokens)
        grpo = take!(tokens)
        consume_white_space!(tokens)

        ( is_recovery, item ) = collect_content_model_group(grpo, tokens, channel)

        if is_token(Lexical.grpc, tokens)
            take!(tokens)
            consume_white_space!(tokens)

        else
            take!(tokens)
            consume_white_space!(tokens)
            put!(channel, MarkupError("ERROR: Expecting ')' to end a content model group.", Lexical.location_of(grpo)))
            is_recovery = true
        end

        item = collect_occurrence_indicator(item, tokens)

        consume_white_space!(tokens) # In case we saw an occurrence indicator.

        return ( is_recovery, item )

    else
        put!(channel, MarkupError("ERROR: Expecting an element name or '('.", Lexical.location_of(grpo)))

        return ( true, nothing )
    end
end

function collect_entity_reference(tokens, channel, in_attribute = false)
    ero = take!(tokens) # Consume the ERO token that got us here.

    if is_name(tokens)
        name = take!(tokens)
        if is_token(Lexical.refc, tokens)
            refc = take!(tokens)

            return EntityReferenceGeneral(name.value, Lexical.location_of(ero))

        else
            put!(channel, MarkupError("ERROR: Expecting ';' to end an entity reference.", Lexical.location_of(name)))

            # Let's assume we saw the REFC.
            #
            return EntityReferenceGeneral(name.value, Lexical.location_of(ero))
        end

    else
        if in_attribute
            put!(channel, MarkupError("ERROR: A '&' must be escaped inside an attribute value.", Lexical.location_of(ero)))

        else
            put!(channel, MarkupError("ERROR: Expecting an entity name.", Lexical.location_of(ero)))
        end

        return DataContent([ ero ], Lexical.location_of(ero))
    end
end

function collect_external_identifier(mdo, tokens, channel)
    return collect_external_identifier(mdo, tokens, true, channel)
end

function collect_external_identifier(mdo, tokens, is_strict, channel)
    if is_keyword("SYSTEM", tokens, channel)
        system = take!(tokens)
        system_identifier = collect_string(Lexical.location_of(system), tokens, channel)

        if isnothing(system_identifier)
            # There's something funky going on.
            #
            return nothing

        else
            return ExternalIdentifier(nothing, stringify(system_identifier), Lexical.location_of(system))
        end

    elseif is_keyword("PUBLIC", tokens, channel)
        public = take!(tokens)
        public_identifier = collect_string(Lexical.location_of(public), tokens, channel)
        if isnothing(public_identifier)
            # Something is funky. Don't bother trying to parse the system identifier.
            #
            return nothing

        else
            if is_token(Lexical.ws, tokens)
                consume_white_space!(tokens) # Consume any white space. See [1], § 4.2.2 ... the white space is
                                             # required, which seems kind of lame off hand, but so be it.

            elseif is_strict || is_token(Lexical.lit, tokens) || is_token(Lexical.lita, tokens)
                # In strict mode, we always emit a markup if there is no white space between the public and system
                # identifiers. In non-strict mode, we only output a markup error if it appears that we're looking at a
                # system identifier.
                #
                put!(channel, MarkupError("ERROR: Expecting white space following a public identifier.",
                                          locations_of(mdo, public_identifier)[:tail]))
            end

            system_identifier = collect_string(locations_of(mdo, public_identifier)[:tail], tokens, is_strict, channel)

            if isnothing(system_identifier)
                if is_strict
                    put!(channel, MarkupError("ERROR: Expecting a system identifier following a public identifier.",
                                              locations_of(mdo, public_identifier)[:tail]))
                end

                return ExternalIdentifier(stringify(public_identifier), nothing, Lexical.location_of(public))

            else
                return ExternalIdentifier(stringify(public_identifier), stringify(system_identifier), Lexical.location_of(public))
            end
        end

    else
        return nothing
    end
end

function collect_mixed_content_model(tokens, channel)
    is_recovery = false
    items       = Array{AbstractModel, 1}()

    while true
        if is_token(Lexical.or, tokens)
            or = take!(tokens)
            consume_white_space!(tokens)

            if is_name(tokens)
                name = take!(tokens)
                consume_white_space!(tokens)
                push!(items, ElementModel(name.value))

            elseif is_reserved_name("#PCDATA", tokens, channel)
                put!(channel, MarkupError("ERROR: '#PCDATA' can only appear at the start of a mixed content model.",
                                          Lexical.location_of(or)))
                is_recovery = true
                take!(tokens)
                consume_white_space!(tokens)

            elseif is_token(Lexical.or, tokens)
                # Don't break in this case ... maybe we can keep parsing and make sense of this. Also, don't
                # consume it ... we're going to consume it as soon as we wrap around, on the next iteration.
                #
                put!(channel, MarkupError("ERROR: Expecting an element name.", Lexical.location_of(or)))
                is_recovery = true

            else
                put!(channel, MarkupError("ERROR: Expecting an element name.", Lexical.location_of(or)))
                is_recovery = true
                break
            end

        elseif is_name(tokens)
            name = take!(tokens)
            consume_white_space!(tokens)
            put!(channel, MarkupError("ERROR: Items in a mixed content model must be separated by '|'.", Lexical.location_of(name)))
            is_recovery = true
            push!(items, ElementModel(name.value))

        elseif is_reserved_name("#PCDATA", tokens, channel)
            t = take!(tokens)
            put!(channel, MarkupError("ERROR: '#PCDATA' can only appear at the start of a mixed content model.",
                                      Lexical.location_of(t)))
            is_recovery = true
            consume_white_space!(tokens)

        else
            break
        end
    end

    return ( is_recovery, items )
end

function collect_occurrence_indicator(content_model, tokens)
    if is_token(Lexical.opt, tokens)
        take!(tokens)

        return Optional(content_model)

    elseif is_token(Lexical.rep, tokens)
        take!(tokens)

        return ZeroOrMore(content_model)

    elseif is_token(Lexical.plus, tokens)
        take!(tokens)

        return OneOrMore(content_model)

    else
        return content_model
    end
end

function collect_parameter_entity_reference(tokens, channel)
    pero = take!(tokens) # Consume the PERO token that got us here.

    if is_name(tokens)
        name = take!(tokens)
        if is_token(Lexical.refc, tokens)
            refc = take!(tokens)

            return EntityReferenceParameter(name.value, Lexical.location_of(pero))

        else
            put!(channel, MarkupError("ERROR: Expecting ';' to end a parameter entity reference.", Lexical.location_of(name)))

            # Let's assume we saw the REFC.
            #
            return EntityReferenceParameter(name.value, Lexical.location_of(pero))
        end

    else
        put!(channel, MarkupError("ERROR: Expecting a parameter entity name.", Lexical.location_of(pero)))

        return DataContent([ pero ], Lexical.location_of(pero))
    end
end

function collect_string(location, tokens, channel)
    return collect_string(location, tokens, true, channel)
end

function collect_string(location, tokens, is_strict, channel)
    consume_white_space!(tokens)

    if is_token(Lexical.lit, tokens) || is_token(Lexical.lita, tokens)
        delimiter = take!(tokens)

        consumed = Array{Lexical.Token, 1}()

        while true
            if fetch(tokens).token_type == delimiter.token_type
                take!(tokens)
                break

            else
                push!(consumed, take!(tokens))
            end
        end

        return consumed

    else
        if is_strict
            put!(channel, MarkupError("ERROR: Expecting a quoted string.", location))
        end

        return nothing
    end
end

# ----------------------------------------
# UTILITIES
# ----------------------------------------

function consume_white_space!(tokens)
    if is_token(Lexical.ws, tokens)
        return take!(tokens)

    else
        return nothing
    end
end

function is_eoi(tokens)
    return !isopen(tokens) & !isready(tokens)
end

function is_keyword(keyword, tokens, channel)
    is_keyword(keyword, tokens, true, channel)
end

function is_keyword(keyword, tokens, case_sensitive, channel)
    if is_token(Lexical.text, tokens)
        text = fetch(tokens)

        if text.value == keyword
            return true

        elseif lowercase(text.value) == lowercase(keyword)
            if case_sensitive
                # All keywords in XML are uppercased. The only thing that comes close to being an exception is 'XML',
                # where all case variants are reserved.
                #
                put!(channel, MarkupError("ERROR: The keyword '" * text.value * "' must be uppercased.", Lexical.location_of(text)))
            end

            return true

        else
            return false
        end

    else
        return false
    end
end

function is_name(tokens)
    if is_token(Lexical.text, tokens)
        text = fetch(tokens)

        return all(vcat(is_name_start_char(text.value[1]), map(is_name_char, collect(text.value[nextind(text.value, 1):end]))))

    else
        return false
    end
end

function is_name_char(c)
    # See [1], § 2.3, production [4a].
    #
    #       NameStartChar | "-" | "." | [0-9] | #xB7 | [#x0300-#x036F] | [#x203F-#x2040]
    #
    # The ranges are split solely for readability.
    #
    if is_name_start_char(c)
        return true

    elseif c == '-' || c == '.' || c ∈ '0':'9' || Int(c) == 0xb7
        return true

    elseif Int(c) ∈ 0x300:0x36f || Int(c) ∈ 0x203f:0x2040
        return true

    else
        return false
    end
end

function is_name_start_char(c)
    # See [1], § 2.3, production [4].
    #
    #       ":"             | [A-Z]           | "_"             | [a-z]
    #     | [#xC0-#xD6]     | [#xD8-#xF6]     | [#xF8-#x2FF]    | [#x370-#x37D]   | [#x37F-#x1FFF]
    #     | [#x200C-#x200D] | [#x2070-#x218F] | [#x2C00-#x2FEF] | [#x3001-#xD7FF]
    #     | [#xF900-#xFDCF] | [#xFDF0-#xFFFD] | [#x10000-#xEFFFF]
    #
    # The ranges are split solely for readability.
    #
    if c == ':' || c ∈ 'A':'Z' || c ∈ 'a':'z'
        return true

    elseif Int(c) ∈ 0xc0:0xd6 || Int(c) ∈ 0xd8:0xf6 || Int(c) ∈ 0xf8:0x2ff || Int(c) ∈ 0x370:0x37d || Int(c) ∈ 0x37f:0x1fff
        return true

    elseif Int(c) ∈ 0x200c:0x200d || Int(c) ∈ 0x2070:0x218f || Int(c) ∈ 0x2c00:0x2fef || Int(c) ∈ 0x3001:0xd7ff
        return true

    elseif Int(c) ∈ 0xf900:0xfdcf || Int(c) ∈ 0xfdf0:0xfffd || Int(c) ∈ 0x10000:0xeffff
        return true

    else
        return false
    end
end

function is_nmtoken(tokens)
    if is_token(Lexical.text, tokens)
        text = fetch(tokens)

        return all(map(is_name_char, collect(text.value)))

    elseif is_token(Lexical.com, tokens)
        # This makes no sense to me, but it is definitely allowed by the specification: see § 2.3, productions [4a] and
        # [7], which combined definitely allow "--" (i.e., COM) as a NMTOKEN.
        #
        return true

    else
        return false
    end
end

function is_reserved_name(keyword, tokens, channel)
    if length(keyword) > 1 && (keyword[1] == '#') && is_token(Lexical.text, tokens)
        return is_keyword(keyword, tokens, channel)

    else
        return false
    end
end

function is_token(token_type, tokens)
    if isready(tokens) || isopen(tokens)
        token = fetch(tokens)

        return token.token_type == token_type

    else
        return false
    end
end

function locations_of(leader, consumed)
    if length(consumed) > 0
        head = Lexical.location_of(consumed[1])

    else
        head = Lexical.location_of(leader)
    end

    if length(consumed) > 0
        tail = Lexical.location_of(consumed[end])

    else
        tail = Lexical.location_of(leader)
    end

    return ( head = head, tail = tail )
end

function stringify(tokens)
    return join(map(token -> token.value, tokens), "")
end

end

max_stars_repo_name: jlegare/NativeXML

max_issues_repo_name: jlegare/NativeXML

max_forks_repo_name: jlegare/NativeXML


text:
function LButton(scene::Scene; bbox = nothing, kwargs...)

    attrs = merge!(Attributes(kwargs), default_attributes(LButton))

    @extract attrs (padding, textsize, label, font, halign, valign, cornerradius,
        cornersegments, strokewidth, strokecolor, buttoncolor,
        labelcolor, labelcolor_hover, labelcolor_active,
        buttoncolor_active, buttoncolor_hover, clicks)

    decorations = Dict{Symbol, Any}()

    sizeattrs = sizenode!(attrs.width, attrs.height)

    alignment = lift(tuple, halign, valign)

    autosizenode = Node{NTuple{2, Optional{Float32}}}((nothing, nothing))

    computedsize = computedsizenode!(sizeattrs, autosizenode)

    suggestedbbox = create_suggested_bboxnode(bbox)

    finalbbox = alignedbboxnode!(suggestedbbox, computedsize, alignment,
        sizeattrs, autosizenode)

    textpos = Node(Point2f0(0, 0))

    subarea = lift(finalbbox) do bbox
        IRect2D(bbox)
    end
    subscene = Scene(scene, subarea, camera=campixel!)

    lcolor = Node{Any}(labelcolor[])
    labeltext = text!(subscene, label, position = textpos, textsize = textsize, font = font,
        color = lcolor, align = (:center, :center), raw = true)[end]

    buttonwidth = Node{Optional{Float32}}(nothing)
    buttonheight = Node{Optional{Float32}}(nothing)

    onany(label, textsize, font, padding) do label, textsize, font, padding
        textbb = FRect2D(boundingbox(labeltext))
        autowidth = width(textbb) + padding[1] + padding[2]
        autoheight = height(textbb) + padding[3] + padding[4]
        autosizenode[] = (autowidth, autoheight)
    end

    # buttonrect is without the left bottom offset of the bbox
    buttonrect = lift(finalbbox) do bbox
        BBox(0, width(bbox), 0, height(bbox))
    end

    on(buttonrect) do rect
        textpos[] = Point2f0(left(rect) + 0.5f0 * width(rect), bottom(rect) + 0.5f0 * height(rect))
    end

    roundedrectpoints = lift(roundedrectvertices, buttonrect, cornerradius, cornersegments)

    bcolor = Node{Any}(buttoncolor[])
    button = poly!(subscene, roundedrectpoints, strokewidth = strokewidth, strokecolor = strokecolor,
        color = bcolor, raw = true)[end]
    decorations[:button] = button
    # put button in front so the text doesn't block the mouse
    reverse!(subscene.plots)

    mousestate = addmousestate!(scene, button, labeltext)

    onmouseover(mousestate) do state
        bcolor[] = buttoncolor_hover[]
        lcolor[] = labelcolor_hover[]
    end

    onmouseout(mousestate) do state
        bcolor[] = buttoncolor[]
        lcolor[] = labelcolor[]
    end

    onmouseleftdown(mousestate) do state
        bcolor[] = buttoncolor_active[]
        lcolor[] = labelcolor_active[]
    end

    onmouseleftclick(mousestate) do state
        clicks[] = clicks[] + 1
    end

    protrusions = Node(RectSides(0f0, 0f0, 0f0, 0f0))
    layoutnodes = LayoutNodes{LButton, GridLayout}(suggestedbbox, protrusions, computedsize, autosizenode, finalbbox, nothing)

    label[] = label[]
    # trigger bbox
    suggestedbbox[] = suggestedbbox[]

    LButton(scene, layoutnodes, attrs, decorations)
end


text:
#!/usr/bin/julia
# Renames the FASTQ files in a directory by prepending the well number based on
# a MiSeq run workbook

# Activate the proper packages
using Tk
import XLSX
using DataFrames
using Missings

# Prompt for the folder containing the FASTQs
fastq_folder = ChooseDirectory()

# Prompt for the Illumina workbook containing the well info
ont_workbook = GetOpenFile()

# Read in the excel file
xf = XLSX.readdata(ont_workbook, "Run Worksheet!B9:D56")
fastq_ids = DataFrame(xf, :auto)
rename!(fastq_ids, Symbol.(["SampleId", "Taxa", "Barcode"]))
dropmissing!(fastq_ids)
fastq_ids = string.(fastq_ids)

# Truncate the barcode number
fastq_ids.Barcode = last.(fastq_ids.Barcode, 2)

# Get all of the files
fastqs = readdir(fastq_folder)

# Find the FAP number somewhere in the excel workbook
# (The workbooks are not very uniform, so we need to go hunting)
fap_search = XLSX.readdata(ont_workbook, 1, "D1:K20")
fap_search = string.(skipmissing(fap_search))
fap_xl = ""
for fap in fap_search
    global fap_xl
    if first(fap, 3) == "FAP"
        fap_xl = fap
        continue
    end
end

# Check if the FAP numbers from the directory and the Excel workbook match
fap_fl = split(fastqs[1], "_")[1]
if fap_fl != fap_xl
    # Yikes! They don't match! Check if the user is ok with that
    proceed = Messagebox(message=string(fap_fl,
                                        " from the file system doesn't match ",
                                        fap_xl,
                                        " from the workbook. ",
                                        "You might be renaming the wrong files. ",
                                        "Rename anyway?"))

    # The user clicked "cancel"
    if proceed == "cancel"
        exit()
    end

end

# Iterate through each file
for fastq in fastqs
    # Get the full path
    fastq_path = joinpath(fastq_folder, fastq)

    # Pull the barcode number and FAP number based on the file name
    # The file name is divided into 5 chucks separated by underscores,
    # The barcode number is the last two characters of the third chunk
    FAP = split(fastq, "_")[1]
    bc = last(split(fastq, "_")[3], 2)

    # Find this id in the workbook
    id_row = fastq_ids[fastq_ids.Barcode .== bc,:]

    # If there are no matches, keep going
    if size(id_row)[1] < 1
        continue
    end

    # Construct the new filename
    fastq_newname = string(FAP, "_pass_", id_row.SampleId[1], ".fastq.gz")

    # Rename the file
    mv(fastq_path, joinpath(fastq_folder, fastq_newname))
    println(string("Renaming ", fastq_path, " to ", joinpath(fastq_folder, fastq_newname)))

end

max_forks_repo_name: MillironX/wphl-bioinformatics