text:
#=
The code in here has been extracted from the JuliaParser.jl package
with license:

The JuliaParser.jl package is licensed under the MIT "Expat" License:

> Copyright (c) 2014: Jake Bolewski.
>
> Permission is hereby granted, free of charge, to any person obtaining
> a copy of this software and associated documentation files (the
> "Software"), to deal in the Software without restriction, including
> without limitation the rights to use, copy, modify, merge, publish,
> distribute, sublicense, and/or sell copies of the Software, and to
> permit persons to whom the Software is furnished to do so, subject to
> the following conditions:
>
> The above copyright notice and this permission notice shall be
> included in all copies or substantial portions of the Software.
>
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
> CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
=#

import Base.Unicode

@inline function utf8_trailing(i)
    if i < 193
        return 0
    elseif i < 225
        return 1
    elseif i < 241
        return 2
    elseif i < 249
        return 3
    elseif i < 253
        return 4
    else
        return 5
    end
end

const utf8_offset = [0x00000000
                    0x00003080
                    0x000e2080
                    0x03c82080
                    0xfa082080
                    0x82082080]
# const EOF_CHAR = convert(Char,typemax(UInt32))
const EOF_CHAR = typemax(Char)

function is_cat_id_start(ch::Char, cat::Integer)
    c = UInt32(ch)
    return (cat == Unicode.UTF8PROC_CATEGORY_LU || cat == Unicode.UTF8PROC_CATEGORY_LL ||
            cat == Unicode.UTF8PROC_CATEGORY_LT || cat == Unicode.UTF8PROC_CATEGORY_LM ||
            cat == Unicode.UTF8PROC_CATEGORY_LO || cat == Unicode.UTF8PROC_CATEGORY_NL ||
            cat == Unicode.UTF8PROC_CATEGORY_SC ||  # allow currency symbols
            cat == Unicode.UTF8PROC_CATEGORY_SO ||  # other symbols

            # math symbol (category Sm) whitelist
            (c >= 0x2140 && c <= 0x2a1c &&
             ((c >= 0x2140 && c <= 0x2144) || # ⅀, ⅁, ⅂, ⅃, ⅄
              c == 0x223f || c == 0x22be || c == 0x22bf || # ∿, ⊾, ⊿
              c == 0x22a4 || c == 0x22a5 || # ⊤ ⊥

              (c >= 0x2202 && c <= 0x2233 &&
               (c == 0x2202 || c == 0x2205 || c == 0x2206 || # ∂, ∅, ∆
                c == 0x2207 || c == 0x220e || c == 0x220f || # ∇, ∎, ∏
                c == 0x2210 || c == 0x2211 || # ∐, ∑
                c == 0x221e || c == 0x221f || # ∞, ∟
                c >= 0x222b)) || # ∫, ∬, ∭, ∮, ∯, ∰, ∱, ∲, ∳

              (c >= 0x22c0 && c <= 0x22c3) ||  # N-ary big ops: ⋀, ⋁, ⋂, ⋃
              (c >= 0x25F8 && c <= 0x25ff) ||  # ◸, ◹, ◺, ◻, ◼, ◽, ◾, ◿

              (c >= 0x266f &&
               (c == 0x266f || c == 0x27d8 || c == 0x27d9 || # ♯, ⟘, ⟙
                (c >= 0x27c0 && c <= 0x27c1) ||  # ⟀, ⟁
                (c >= 0x29b0 && c <= 0x29b4) ||  # ⦰, ⦱, ⦲, ⦳, ⦴
                (c >= 0x2a00 && c <= 0x2a06) ||  # ⨀, ⨁, ⨂, ⨃, ⨄, ⨅, ⨆
                (c >= 0x2a09 && c <= 0x2a16) ||  # ⨉, ⨊, ⨋, ⨌, ⨍, ⨎, ⨏, ⨐, ⨑, ⨒,
                                                 # ⨓, ⨔, ⨕, ⨖
                c == 0x2a1b || c == 0x2a1c)))) || # ⨛, ⨜

            (c >= 0x1d6c1 && # variants of \nabla and \partial
             (c == 0x1d6c1 || c == 0x1d6db ||
              c == 0x1d6fb || c == 0x1d715 ||
              c == 0x1d735 || c == 0x1d74f ||
              c == 0x1d76f || c == 0x1d789 ||
              c == 0x1d7a9 || c == 0x1d7c3)) ||

            # super- and subscript +-=()
            (c >= 0x207a && c <= 0x207e) ||
            (c >= 0x208a && c <= 0x208e) ||

            # angle symbols
            (c >= 0x2220 && c <= 0x2222) || # ∠, ∡, ∢
            (c >= 0x299b && c <= 0x29af) || # ⦛, ⦜, ⦝, ⦞, ⦟, ⦠, ⦡, ⦢, ⦣, ⦤, ⦥,
                                            # ⦦, ⦧, ⦨, ⦩, ⦪, ⦫, ⦬, ⦭, ⦮, ⦯
            # Other_ID_Start
            c == 0x2118 || c == 0x212E || # ℘, ℮
            (c >= 0x309B && c <= 0x309C)) # katakana-hiragana sound marks
end

function is_identifier_char(c::Char)
    c == EOF_CHAR && return false
    return Base.is_id_char(c)
end

function is_identifier_start_char(c::Char)
    c == EOF_CHAR && return false
    return Base.is_id_start_char(c)
end

# Chars that we will never allow to be part of a valid non-operator identifier
function is_never_id_char(ch::Char)
    cat = Unicode.category_code(ch)
    c = UInt32(ch)
    return (
        # spaces and control characters:
        (cat >= Unicode.UTF8PROC_CATEGORY_ZS && cat <= Unicode.UTF8PROC_CATEGORY_CS) ||

        # ASCII and Latin1 non-connector punctuation
        (c < 0xff &&
         cat >= Unicode.UTF8PROC_CATEGORY_PD && cat <= Unicode.UTF8PROC_CATEGORY_PO) ||

        c == UInt32('`') ||

        # mathematical brackets
        (c >= 0x27e6 && c <= 0x27ef) ||
        # angle, corner, and lenticular brackets
        (c >= 0x3008 && c <= 0x3011) ||
        # tortoise shell, square, and more lenticular brackets
        (c >= 0x3014 && c <= 0x301b) ||
        # fullwidth parens
        (c == 0xff08 || c == 0xff09) ||
        # fullwidth square brackets
        (c == 0xff3b || c == 0xff3d)
    )
end

function peekchar(io::Base.GenericIOBuffer)
    if !io.readable || io.ptr > io.size
        return EOF_CHAR
    end
    ch, _ = readutf(io)
    return ch
end

function readutf(io, offset = 0)
    ch = convert(UInt8, io.data[io.ptr + offset])
    if ch < 0x80
        return convert(Char, ch), 0
    end
    trailing = utf8_trailing(ch + 1)
    c::UInt32 = 0
    for j = 1:trailing
        c += ch
        c <<= 6
        ch = convert(UInt8, io.data[io.ptr + j + offset])
    end
    c += ch
    c -= utf8_offset[trailing + 1]
    return convert(Char, c), trailing
end

function dpeekchar(io::IOBuffer)
    if !io.readable || io.ptr > io.size
        return EOF_CHAR, EOF_CHAR
    end
    ch1, trailing = readutf(io)
    offset = trailing + 1

    if io.ptr + offset > io.size
        return ch1, EOF_CHAR
    end
    ch2, _ = readutf(io, offset)

    return ch1, ch2
end

# this implementation is copied from Base
peekchar(s::IOStream) = begin
    _CHTMP = Ref{Char}()
    if ccall(:ios_peekutf8, Int32, (Ptr{Nothing}, Ptr{Char}), s, _CHTMP) < 0
        return EOF_CHAR
    end
    return _CHTMP[]
end

eof(io::IO) = Base.eof(io)
eof(c::Char) = c === EOF_CHAR

readchar(io::IO) = eof(io) ? EOF_CHAR : read(io, Char)

# Checks whether a Char is an operator, which can not be juxtaposed with another
# Char to be an operator (i.e <=), and can be prefixed by a dot (.)
# magic number list created by filtering ops by those that successfully parse
# `a .(op) b` or `.(op)a` and where `length(string(op)) == 1`
@inline function dotop1(c1::Char)
    c1 == EOF_CHAR && return false
    c = UInt32(c1)
    c == 0x00000021 ||
    c == 0x000000a6 ||
    c == 0x0000002e ||
    c == 0x0000007e ||
    c == 0x000000ac ||
    c == 0x000000b1 ||
    c == 0x000000b7 ||
    c == 0x000000d7 ||
    c == 0x00000387 ||
    c == 0x00002026 ||
    c == 0x0000205d ||
    c == 0x0000214b ||
    0x00002190 <= c <= 0x00002194 ||
    0x0000219a <= c <= 0x0000219e ||
    c == 0x000021a0 ||
    0x000021a2 <= c <= 0x000021a4 ||
    0x000021aa <= c <= 0x000021ac ||
    c == 0x000021a6 ||
    c == 0x000021a9 ||
    c == 0x000021ae ||
    c == 0x000021c0 ||
    c == 0x000021c1 ||
    c == 0x000021c4 ||
    c == 0x000021c6 ||
    c == 0x000021c7 ||
    c == 0x000021c9 ||
    0x000021cb <= c <= 0x000021cf ||
    c == 0x000021d2 ||
    c == 0x000021d4 ||
    c == 0x000021b6 ||
    c == 0x000021b7 ||
    0x000021ba <= c <= 0x000021bd ||
    c == 0x000021d0 ||
    0x000021da <= c <= 0x000021dd ||
    c == 0x000021e0 ||
    c == 0x000021e2 ||
    0x000021f4 <= c <= 0x000021ff ||
    0x00002208 <= c <= 0x0000220d ||
    0x00002213 <= c <= 0x00002214 ||
    0x00002217 <= c <= 0x00002219 ||
    0x0000221a <= c <= 0x0000221d ||
    0x00002224 <= c <= 0x0000222a ||
    0x00002237 <= c <= 0x00002238 ||
    0x0000223a <= c <= 0x0000223b ||
    0x0000223d <= c <= 0x0000223e ||
    0x00002240 <= c <= 0x0000228b ||
    0x0000228d <= c <= 0x0000229c ||
    0x0000229e <= c <= 0x000022a3 ||
    c == 0x000022a9 ||
    c == 0x000022ac ||
    c == 0x000022ae ||
    0x000022b0 <= c <= 0x000022b7 ||
    0x000022bc <= c <= 0x000022bd ||
    0x000022c4 <= c <= 0x000022c7 ||
    0x000022c9 <= c <= 0x000022d3 ||
    0x000022d5 <= c <= 0x000022ff ||
    c == 0x0000233f ||
    c == 0x000025b7 ||
    c == 0x000027c2 ||
    0x000027c8 <= c <= 0x000027c9 ||
    0x000027d1 <= c <= 0x000027d2 ||
    0x000027d5 <= c <= 0x000027d7 ||
    0x000027f0 <= c <= 0x000027f1 ||
    0x000027f5 <= c <= 0x000027f7 ||
    0x000027f9 <= c <= 0x000027ff ||
    0x00002900 <= c <= 0x00002918 ||
    0x0000291d <= c <= 0x00002920 ||
    0x00002944 <= c <= 0x00002970 ||
    0x000029b7 <= c <= 0x000029b8 ||
    c == 0x000029bc ||
    0x000029be <= c <= 0x000029c1 ||
    c == 0x000029e1 ||
    0x000029e3 <= c <= 0x000029e5 ||
    c == 0x000029f4 ||
    0x000029f6 <= c <= 0x000029f7 ||
    0x000029fa <= c <= 0x000029fb ||
    0x00002a07 <= c <= 0x00002a08 ||
    c == 0x00002a1d ||
    c == 0x00002a1f ||
    0x00002a22 <= c <= 0x00002a2e ||
    0x00002a30 <= c <= 0x00002a3d ||
    0x00002a40 <= c <= 0x00002a45 ||
    0x00002a4a <= c <= 0x00002a58 ||
    0x00002a5a <= c <= 0x00002a63 ||
    0x00002a66 <= c <= 0x00002a67 ||
    0x00002a6a <= c <= 0x00002ad9 ||
    c == 0x00002adb ||
    c == 0x00002aea ||
    c == 0x00002aeb ||
    0x00002af7 <= c <= 0x00002afa ||
    0x00002b30 <= c <= 0x00002b44 ||
    0x00002b47 <= c <= 0x00002b4c ||
    0x0000ffe9 <= c <= 0x0000ffec
end

function dotop2(pc, dpc)
    dotop1(pc) ||
    pc =='+' ||
    pc =='-' ||
    pc =='*' ||
    pc =='/' ||
    pc =='\\' ||
    pc =='^' ||
    pc =='<' ||
    pc =='>' ||
    pc =='&' && dpc === '=' ||
    pc =='&' ||
    pc =='%' ||
    pc == '=' && dpc != '>' ||
    pc == '|' && dpc != '|' ||
    pc == '!' && dpc == '=' ||
    pc == '⊻' ||
    pc == '÷' ||
    pc == '=' && dpc == '>'
end

# suffix operators
# https://github.com/JuliaLang/julia/blob/d7d2b0c692eb6ad409d7193ba8d9d42972cbf182/src/flisp/julia_extensions.c#L156-L174
#
# ₀₁₂₃₄₅₆₇₈₉₊₋₌₍₎²³¹ʰʲʳʷʸˡˢˣᴬᴮᴰᴱᴳᴴᴵᴶᴷᴸᴹᴺᴼᴾᴿᵀᵁᵂᵃᵇᵈᵉᵍᵏᵐᵒᵖᵗᵘᵛᵝᵞᵟᵠᵡᵢᵣᵤᵥᵦᵧᵨᵩᵪᶜᶠᶥᶦᶫᶰᶸᶻᶿ ⁰ⁱ⁴⁵⁶⁷⁸⁹⁺⁻⁼⁽⁾ⁿₐₑₒₓₕₖₗₘₙₚₛₜⱼⱽ′″‴‵‶‷⁗

@inline function isopsuffix(c1::Char)
    c1 == EOF_CHAR && return false
    c = UInt32(c1)
    if (c < 0xa1 || c > 0x10ffff)
        return false
    end
    cat = Base.Unicode.category_code(c)
    if (cat == Base.Unicode.UTF8PROC_CATEGORY_MN ||
        cat == Base.Unicode.UTF8PROC_CATEGORY_MC ||
        cat == Base.Unicode.UTF8PROC_CATEGORY_ME)
        return true
    end
    return 0x000000b2 <= c <= 0x000000b3 ||
    c == 0x000000b9 ||
    c == 0x000002b0 ||
    0x000002b2 <= c <= 0x000002b3 ||
    0x000002b7 <= c <= 0x000002b8 ||
    0x000002e1 <= c <= 0x000002e3 ||
    c == 0x00000302 ||
    c == 0x00001d2c ||
    c == 0x00001d2e ||
    0x00001d30 <= c <= 0x00001d31 ||
    0x00001d33 <= c <= 0x00001d3a ||
    c == 0x00001d3c ||
    0x00001d3e <= c <= 0x00001d43 ||
    0x00001d47 <= c <= 0x00001d49 ||
    c == 0x00001d4d ||
    0x00001d4f <= c <= 0x00001d50 ||
    c == 0x00001d52 ||
    0x00001d56 <= c <= 0x00001d58 ||
    c == 0x00001d5b ||
    0x00001d5d <= c <= 0x00001d6a ||
    c == 0x00001d9c ||
    c == 0x00001da0 ||
    0x00001da5 <= c <= 0x00001da6 ||
    c == 0x00001dab ||
    c == 0x00001db0 ||
    c == 0x00001db8 ||
    c == 0x00001dbb ||
    c == 0x00001dbf ||
    c == 0x00002009 ||
    0x00002032 <= c <= 0x00002037 ||
    c == 0x00002057 ||
    0x00002070 <= c <= 0x00002071 ||
    0x00002074 <= c <= 0x0000208e ||
    0x00002090 <= c <= 0x00002093 ||
    0x00002095 <= c <= 0x0000209c ||
    0x00002c7c <= c <= 0x00002c7d ||
    0x0000a71b <= c <= 0x0000a71d
end

function optakessuffix(k)
    (Tokens.begin_ops < k < Tokens.end_ops) &&
    !(
        k == Tokens.DDDOT ||
        Tokens.begin_assignments <= k <= Tokens.end_assignments ||
        k == Tokens.CONDITIONAL ||
        k == Tokens.ISSUBTYPE ||
        k == Tokens.ISSUPERTYPE ||
        k == Tokens.IN ||
        k == Tokens.ISA ||
        k == Tokens.COLON_EQUALS ||
        k == Tokens.DOUBLE_COLON_EQUAL ||
        k == Tokens.COLON ||
        k == Tokens.DDOT ||
        k == Tokens.EX_OR ||
        k == Tokens.DECLARATION ||
        k == Tokens.WHERE ||
        k == Tokens.DOT ||
        k == Tokens.NOT ||
        k == Tokens.TRANSPOSE ||
        k == Tokens.ANON_FUNC ||
        Tokens.NOT_SIGN <= k <= Tokens.QUAD_ROOT
    )
end

function is_operator_start_char(c::Char)
    eof(c) && return false
    is_operator_start_char(UInt32(c))
end
is_operator_start_char(u::UInt32) = u == 0x00000021 || (u == 0x00000024 || (u == 0x00000025 || (u == 0x00000026 || (u == 0x00000027 || (u == 0x0000002a || (u == 0x0000002b || (u == 0x0000002d || (u == 0x0000002e || (u == 0x0000002f || (u == 0x0000003a || (u == 0x0000003c || (u == 0x0000003d || (u == 0x0000003e || (u == 0x0000003f || (u == 0x0000005c || (u == 0x0000005e || (u == 0x00000069 || (u == 0x00000077 || (u == 0x0000007c || (u == 0x0000007e || (u == 0x000000ac || (u == 0x000000b1 || (u == 0x000000d7 || (u == 0x000000f7 || (u == 0x00002026 || (u == 0x0000205d || (u == 0x0000214b || (u == 0x00002190 || (u == 0x00002191 || (u == 0x00002192 || (u == 0x00002193 || (u == 0x00002194 || (u == 0x0000219a || (u == 0x0000219b || (u == 0x000021a0 || (u == 0x000021a3 || (u == 0x000021a6 || (u == 0x000021ae || (u == 0x000021ce || (u == 0x000021cf || (u == 0x000021d2 || (u == 0x000021d4 || (u == 0x000021f4 || (u == 0x000021f5 || (u == 0x000021f6 || (u == 0x000021f7 || (u == 0x000021f8 || (u == 0x000021f9 || (u == 0x000021fa || (u == 0x000021fb || (u == 0x000021fc || (u == 0x000021fd || (u == 0x000021fe || (u == 0x000021ff || (u == 0x00002208 || (u == 0x00002209 || (u == 0x0000220a || (u == 0x0000220b || (u == 0x0000220c || (u == 0x0000220d || (u == 0x00002213 || (u == 0x00002214 || (u == 0x00002217 || (u == 0x00002218 || (u == 0x00002219 || (u == 0x0000221a || (u == 0x0000221b || (u == 0x0000221c || (u == 0x0000221d || (u == 0x00002224 || (u == 0x00002225 || (u == 0x00002226 || (u == 0x00002227 || (u == 0x00002228 || (u == 0x00002229 || (u == 0x0000222a || (u == 0x00002237 || (u == 0x00002238 || (u == 0x0000223a || (u == 0x0000223b || (u == 0x0000223d || (u == 0x0000223e || (u == 0x00002240 || (u == 0x00002241 || (u == 0x00002242 || (u == 0x00002243 || (u == 0x00002244 || (u == 0x00002245 || (u == 0x00002246 || (u == 0x00002247 || (u == 0x00002248 || (u == 0x00002249 || (u == 0x0000224a || (u == 0x0000224b || (u == 0x0000224c || (u == 0x0000224d || (u == 0x0000224e || (u == 0x0000224f || (u == 0x00002250 || (u == 0x00002251 || (u == 0x00002252 || (u == 0x00002253 || (u == 0x00002254 || (u == 0x00002255 || (u == 0x00002256 || (u == 0x00002257 || (u == 0x00002258 || (u == 0x00002259 || (u == 0x0000225a || (u == 0x0000225b || (u == 0x0000225c || (u == 0x0000225d || (u == 0x0000225e || (u == 0x0000225f || (u == 0x00002260 || (u == 0x00002261 || (u == 0x00002262 || (u == 0x00002263 || (u == 0x00002264 || (u == 0x00002265 || (u == 0x00002266 || (u == 0x00002267 || (u == 0x00002268 || (u == 0x00002269 || (u == 0x0000226a || (u == 0x0000226b || (u == 0x0000226c || (u == 0x0000226d || (u == 0x0000226e || (u == 0x0000226f || (u == 0x00002270 || (u == 0x00002271 || (u == 0x00002272 || (u == 0x00002273 || (u == 0x00002274 || (u == 0x00002275 || (u == 0x00002276 || (u == 0x00002277 || (u == 0x00002278 || (u == 0x00002279 || (u == 0x0000227a || (u == 0x0000227b || (u == 0x0000227c || (u == 0x0000227d || (u == 0x0000227e || (u == 0x0000227f || (u == 0x00002280 || (u == 0x00002281 || (u == 0x00002282 || (u == 0x00002283 || (u == 0x00002284 || (u == 0x00002285 || (u == 0x00002286 || (u == 0x00002287 || (u == 0x00002288 || (u == 0x00002289 || (u == 0x0000228a || (u == 0x0000228b || (u == 0x0000228d || (u == 0x0000228e || (u == 0x0000228f || (u == 0x00002290 || (u == 0x00002291 || (u == 0x00002292 || (u == 0x00002293 || (u == 0x00002294 || (u == 0x00002295 || (u == 0x00002296 || (u == 0x00002297 || (u == 0x00002298 || (u == 0x00002299 || (u == 0x0000229a || (u == 0x0000229b || (u == 0x0000229c || (u == 0x0000229e || (u == 0x0000229f || (u == 0x000022a0 || (u == 0x000022a1 || (u == 0x000022a2 || (u == 0x000022a3 || (u == 0x000022a9 || (u == 0x000022ac || (u == 0x000022ae || (u == 0x000022b0 || (u == 0x000022b1 || (u == 0x000022b2 || (u == 0x000022b3 || (u == 0x000022b4 || (u == 0x000022b5 || (u == 0x000022b6 || (u == 0x000022b7 || (u == 0x000022bb || (u == 0x000022bc || (u == 0x000022bd || (u == 0x000022c4 || (u == 0x000022c5 || (u == 0x000022c6 || (u == 0x000022c7 || (u == 0x000022c9 || (u == 0x000022ca || (u == 0x000022cb || (u == 0x000022cc || (u == 0x000022cd || (u == 0x000022ce || (u == 0x000022cf || (u == 0x000022d0 || (u == 0x000022d1 || (u == 0x000022d2 || (u == 0x000022d3 || (u == 0x000022d5 || (u == 0x000022d6 || (u == 0x000022d7 || (u == 0x000022d8 || (u == 0x000022d9 || (u == 0x000022da || (u == 0x000022db || (u == 0x000022dc || (u == 0x000022dd || (u == 0x000022de || (u == 0x000022df || (u == 0x000022e0 || (u == 0x000022e1 || (u == 0x000022e2 || (u == 0x000022e3 || (u == 0x000022e4 || (u == 0x000022e5 || (u == 0x000022e6 || (u == 0x000022e7 || (u == 0x000022e8 || (u == 0x000022e9 || (u == 0x000022ea || (u == 0x000022eb || (u == 0x000022ec || (u == 0x000022ed || (u == 0x000022ee || (u == 0x000022ef || (u == 0x000022f0 || (u == 0x000022f1 || (u == 0x000022f2 || (u == 0x000022f3 || (u == 0x000022f4 || (u == 0x000022f5 || (u == 0x000022f6 || (u == 0x000022f7 || (u == 0x000022f8 || (u == 0x000022f9 || (u == 0x000022fa || (u == 0x000022fb || (u == 0x000022fc || (u == 0x000022fd || (u == 0x000022fe || (u == 0x000022ff || (u == 0x000025b7 || (u == 0x000027c2 || (u == 0x000027c8 || (u == 0x000027c9 || (u == 0x000027d1 || (u == 0x000027d2 || (u == 0x000027d5 || (u == 0x000027d6 || (u == 0x000027d7 || (u == 0x000027f0 || (u == 0x000027f1 || (u == 0x000027f5 || (u == 0x000027f6 || (u == 0x000027f7 || (u == 0x000027f9 || (u == 0x000027fa || (u == 0x000027fb || (u == 0x000027fc || (u == 0x000027fd || (u == 0x000027fe || (u == 0x000027ff || (u == 0x00002900 || (u == 0x00002901 || (u == 0x00002902 || (u == 0x00002903 || (u == 0x00002904 || (u == 0x00002905 || (u == 0x00002906 || (u == 0x00002907 || (u == 0x00002908 || (u == 0x00002909 || (u == 0x0000290a || (u == 0x0000290b || (u == 0x0000290c || (u == 0x0000290d || (u == 0x0000290e || (u == 0x0000290f || (u == 0x00002910 || (u == 0x00002911 || (u == 0x00002912 || (u == 0x00002913 || (u == 0x00002914 || (u == 0x00002915 || (u == 0x00002916 || (u == 0x00002917 || (u == 0x00002918 || (u == 0x0000291d || (u == 0x0000291e || (u == 0x0000291f || (u == 0x00002920 || (u == 0x00002944 || (u == 0x00002945 || (u == 0x00002946 || (u == 0x00002947 || (u == 0x00002948 || (u == 0x00002949 || (u == 0x0000294a || (u == 0x0000294b || (u == 0x0000294c || (u == 0x0000294d || (u == 0x0000294e || (u == 0x0000294f || (u == 0x00002950 || (u == 0x00002951 || (u == 0x00002952 || (u == 0x00002953 || (u == 0x00002954 || (u == 0x00002955 || (u == 0x00002956 || (u == 0x00002957 || (u == 0x00002958 || (u == 0x00002959 || (u == 0x0000295a || (u == 0x0000295b || (u == 0x0000295c || (u == 0x0000295d || (u == 0x0000295e || (u == 0x0000295f || (u == 0x00002960 || (u == 0x00002961 || (u == 0x00002962 || (u == 0x00002963 || (u == 0x00002964 || (u == 0x00002965 || (u == 0x00002966 || (u == 0x00002967 || (u == 0x00002968 || (u == 0x00002969 || (u == 0x0000296a || (u == 0x0000296b || (u == 0x0000296c || (u == 0x0000296d || (u == 0x0000296e || (u == 0x0000296f || (u == 0x00002970 || (u == 0x000029b7 || (u == 0x000029b8 || (u == 0x000029bc || (u == 0x000029be || (u == 0x000029bf || (u == 0x000029c0 || (u == 0x000029c1 || (u == 0x000029e1 || (u == 0x000029e3 || (u == 0x000029e4 || (u == 0x000029e5 || (u == 0x000029f4 || (u == 0x000029f6 || (u == 0x000029f7 || (u == 0x000029fa || (u == 0x000029fb || (u == 0x00002a07 || (u == 0x00002a08 || (u == 0x00002a1d || (u == 0x00002a22 || (u == 0x00002a23 || (u == 0x00002a24 || (u == 0x00002a25 || (u == 0x00002a26 || (u == 0x00002a27 || (u == 0x00002a28 || (u == 0x00002a29 || (u == 0x00002a2a || (u == 0x00002a2b || (u == 0x00002a2c || (u == 0x00002a2d || (u == 0x00002a2e || (u == 0x00002a30 || (u == 0x00002a31 || (u == 0x00002a32 || (u == 0x00002a33 || (u == 0x00002a34 || (u == 0x00002a35 || (u == 0x00002a36 || (u == 0x00002a37 || (u == 0x00002a38 || (u == 0x00002a39 || (u == 0x00002a3a || (u == 0x00002a3b || (u == 0x00002a3c || (u == 0x00002a3d || (u == 0x00002a40 || (u == 0x00002a41 || (u == 0x00002a42 || (u == 0x00002a43 || (u == 0x00002a44 || (u == 0x00002a45 || (u == 0x00002a4a || (u == 0x00002a4b || (u == 0x00002a4c || (u == 0x00002a4d || (u == 0x00002a4e || (u == 0x00002a4f || (u == 0x00002a50 || (u == 0x00002a51 || (u == 0x00002a52 || (u == 0x00002a53 || (u == 0x00002a54 || (u == 0x00002a55 || (u == 0x00002a56 || (u == 0x00002a57 || (u == 0x00002a58 || (u == 0x00002a5a || (u == 0x00002a5b || (u == 0x00002a5c || (u == 0x00002a5d || (u == 0x00002a5e || (u == 0x00002a5f || (u == 0x00002a60 || (u == 0x00002a61 || (u == 0x00002a62 || (u == 0x00002a63 || (u == 0x00002a66 || (u == 0x00002a67 || (u == 0x00002a6a || (u == 0x00002a6b || (u == 0x00002a6c || (u == 0x00002a6d || (u == 0x00002a6e || (u == 0x00002a6f || (u == 0x00002a70 || (u == 0x00002a71 || (u == 0x00002a72 || (u == 0x00002a73 || (u == 0x00002a74 || (u == 0x00002a75 || (u == 0x00002a76 || (u == 0x00002a77 || (u == 0x00002a78 || (u == 0x00002a79 || (u == 0x00002a7a || (u == 0x00002a7b || (u == 0x00002a7c || (u == 0x00002a7d || (u == 0x00002a7e || (u == 0x00002a7f || (u == 0x00002a80 || (u == 0x00002a81 || (u == 0x00002a82 || (u == 0x00002a83 || (u == 0x00002a84 || (u == 0x00002a85 || (u == 0x00002a86 || (u == 0x00002a87 || (u == 0x00002a88 || (u == 0x00002a89 || (u == 0x00002a8a || (u == 0x00002a8b || (u == 0x00002a8c || (u == 0x00002a8d || (u == 0x00002a8e || (u == 0x00002a8f || (u == 0x00002a90 || (u == 0x00002a91 || (u == 0x00002a92 || (u == 0x00002a93 || (u == 0x00002a94 || (u == 0x00002a95 || (u == 0x00002a96 || (u == 0x00002a97 || (u == 0x00002a98 || (u == 0x00002a99 || (u == 0x00002a9a || (u == 0x00002a9b || (u == 0x00002a9c || (u == 0x00002a9d || (u == 0x00002a9e || (u == 0x00002a9f || (u == 0x00002aa0 || (u == 0x00002aa1 || (u == 0x00002aa2 || (u == 0x00002aa3 || (u == 0x00002aa4 || (u == 0x00002aa5 || (u == 0x00002aa6 || (u == 0x00002aa7 || (u == 0x00002aa8 || (u == 0x00002aa9 || (u == 0x00002aaa || (u == 0x00002aab || (u == 0x00002aac || (u == 0x00002aad || (u == 0x00002aae || (u == 0x00002aaf || (u == 0x00002ab0 || (u == 0x00002ab1 || (u == 0x00002ab2 || (u == 0x00002ab3 || (u == 0x00002ab4 || (u == 0x00002ab5 || (u == 0x00002ab6 || (u == 0x00002ab7 || (u == 0x00002ab8 || (u == 0x00002ab9 || (u == 0x00002aba || (u == 0x00002abb || (u == 0x00002abc || (u == 0x00002abd || (u == 0x00002abe || (u == 0x00002abf || (u == 0x00002ac0 || (u == 0x00002ac1 || (u == 0x00002ac2 || (u == 0x00002ac3 || (u == 0x00002ac4 || (u == 0x00002ac5 || (u == 0x00002ac6 || (u == 0x00002ac7 || (u == 0x00002ac8 || (u == 0x00002ac9 || (u == 0x00002aca || (u == 0x00002acb || (u == 0x00002acc || (u == 0x00002acd || (u == 0x00002ace || (u == 0x00002acf || (u == 0x00002ad0 || (u == 0x00002ad1 || (u == 0x00002ad2 || (u == 0x00002ad3 || (u == 0x00002ad4 || (u == 0x00002ad5 || (u == 0x00002ad6 || (u == 0x00002ad7 || (u == 0x00002ad8 || (u == 0x00002ad9 || (u == 0x00002adb || (u == 0x00002af7 || (u == 0x00002af8 || (u == 0x00002af9 || (u == 0x00002afa || (u == 0x00002b30 || (u == 0x00002b31 || (u == 0x00002b32 || (u == 0x00002b33 || (u == 0x00002b34 || (u == 0x00002b35 || (u == 0x00002b36 || (u == 0x00002b37 || (u == 0x00002b38 || (u == 0x00002b39 || (u == 0x00002b3a || (u == 0x00002b3b || (u == 0x00002b3c || (u == 0x00002b3d || (u == 0x00002b3e || (u == 0x00002b3f || (u == 0x00002b40 || (u == 0x00002b41 || (u == 0x00002b42 || (u == 0x00002b43 || (u == 0x00002b44 || (u == 0x00002b47 || (u == 0x00002b48 || (u == 0x00002b49 || (u == 0x00002b4a || (u == 0x00002b4b || (u == 0x00002b4c || (u == 0x0000ffe9 || (u == 0x0000ffea || (u == 0x0000ffeb || u == 0x0000ffec)))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))


text:
using MatchingMarkets
using Test

include("test_deferred_acceptance.jl")
include("test_matching_tools.jl")
include("test_util.jl")

ext: jl


text:
###############################################################################
#
# Beta functions to make soil moisture correction
#
###############################################################################
"""
    β_factor(hs::LeafHydraulics{FT},
             svc::AbstractSoilVC{FT},
             bt::AbstractBetaFunction{FT},
             p_leaf::FT,
             p_soil::FT,
             swc::FT
    ) where {FT<:AbstractFloat}

Calculate the β correction factor, given
- `hs` `LeafHydraulics` structure
- `svc` Soil vulnerability curve
- `bt` [`AbstractBetaFunction`](@ref) type struct
- `p_leaf` Leaf water potential `[MPa]`
- `p_soil` Soil water potential `[MPa]`
- `swc` Soil water content
"""
function β_factor(
            hs::LeafHydraulics{FT},
            svc::AbstractSoilVC{FT},
            bt::Union{BetaGLinearKleaf{FT}, BetaVLinearKleaf{FT}},
            p_leaf::FT,
            p_soil::FT,
            swc::FT
) where {FT<:AbstractFloat}
    @unpack f_st, f_vis, vc = hs;

    return xylem_k_ratio(vc, p_leaf/f_st, f_vis)
end

function β_factor(
            hs::LeafHydraulics{FT},
            svc::AbstractSoilVC{FT},
            bt::Union{BetaGLinearKsoil{FT}, BetaVLinearKsoil{FT}},
            p_leaf::FT,
            p_soil::FT,
            swc::FT
) where {FT<:AbstractFloat}
    return soil_k_ratio_p25(svc, p_soil)
end

function β_factor(
            hs::LeafHydraulics{FT},
            svc::AbstractSoilVC{FT},
            bt::Union{BetaGLinearPleaf{FT}, BetaVLinearPleaf{FT}},
            p_leaf::FT,
            p_soil::FT,
            swc::FT
) where {FT<:AbstractFloat}
    @unpack p_max, p_min = bt;

    if p_leaf >= p_max
        return FT(1)
    elseif p_leaf <= p_min
        return FT(0)
    else
        return (p_leaf - p_min) / (p_max - p_min)
    end
end

function β_factor(
            hs::LeafHydraulics{FT},
            svc::AbstractSoilVC{FT},
            bt::Union{BetaGLinearPsoil{FT}, BetaVLinearPsoil{FT}},
            p_leaf::FT,
            p_soil::FT,
            swc::FT
) where {FT<:AbstractFloat}
    @unpack p_max, p_min = bt;

    if p_soil >= p_max
        return FT(1)
    elseif p_soil <= p_min
        return FT(0)
    else
        return (p_soil - p_min) / (p_max - p_min)
    end
end

function β_factor(
            hs::LeafHydraulics{FT},
            svc::AbstractSoilVC{FT},
            bt::Union{BetaGLinearSWC{FT}, BetaVLinearSWC{FT}},
            p_leaf::FT,
            p_soil::FT,
            swc::FT
) where {FT<:AbstractFloat}
    @unpack swc_max, swc_min = bt;

    if swc >= swc_max
        return FT(1)
    elseif swc <= swc_min
        return FT(0)
    else
        return (swc - swc_min) / (swc_max - swc_min)
    end
end


text:
"""
    WatchedFile

Struct for a file being watched containing the path to the file as well as the time of last
modification.
"""
mutable struct WatchedFile{T<:AbstractString}
    path::T
    mtime::Float64
end

"""
    WatchedFile(f_path)

Construct a new `WatchedFile` object around a file `f_path`.
"""
WatchedFile(f_path::AbstractString) = WatchedFile(f_path, mtime(f_path))

"""
    has_changed(wf::WatchedFile)

Check if a `WatchedFile` has changed. Returns -1 if the file does not exist, 0 if it does exist but
has not changed, and 1 if it has changed.
"""
function has_changed(wf::WatchedFile)
    isfile(wf.path) || return -1
    return Int(mtime(wf.path) > wf.mtime)
end

"""
    set_unchanged!(wf::WatchedFile)

Set the current state of a `WatchedFile` as unchanged"
"""
set_unchanged!(wf::WatchedFile) = (wf.mtime = mtime(wf.path))

"""
    FileWatcher

Abstract Type for file watching objects such as [`SimpleWatcher`](@ref).
"""
abstract type FileWatcher end

"""
    SimpleWatcher([callback]; sleeptime::Float64=0.1) <: FileWatcher

A simple file watcher. You can specify a callback function, receiving the path of each file that
has changed as an `AbstractString`, at construction or later by the API function [`set_callback!`](@ref).
The `sleeptime` is the time waited between two runs of the loop looking for changed files, it is
constrained to be at least 0.05s.
"""
mutable struct SimpleWatcher <: FileWatcher
    callback::Union{Nothing,Function} # callback function triggered upon file change
    task::Union{Nothing,Task}         # asynchronous file-watching task
    sleeptime::Float64                # sleep-time before checking for file changes
    watchedfiles::Vector{WatchedFile} # list of files being watched
    status::Symbol                    # set to :interrupted as appropriate (caught by server)
end

SimpleWatcher(callback::Union{Nothing,Function}=nothing; sleeptime::Float64=0.1) =
    SimpleWatcher(callback, nothing, max(0.05, sleeptime), Vector{WatchedFile}(), :runnable)

"""
    file_watcher_task!(w::FileWatcher)

Helper function that's spawned as an asynchronous task and checks for file changes. This task
is normally terminated upon an `InterruptException` and shows a warning in the presence of
any other exception.
"""
function file_watcher_task!(fw::FileWatcher)
    try
        while true
            sleep(fw.sleeptime)

            # only check files if there's a callback to call upon changes
            fw.callback === nothing && continue

            # keep track of any file that may have been deleted
            deleted_files = Vector{Int}()
            for (i, wf) ∈ enumerate(fw.watchedfiles)
                state = has_changed(wf)
                if state == 0
                    continue
                elseif state == 1
                    # the file has changed, set it unchanged and trigger callback
                    set_unchanged!(wf)
                    fw.callback(wf.path)
                elseif state == -1
                    # the file does not exist, eventually delete it from list of watched files
                    push!(deleted_files, i)
                    if VERBOSE[]
                        @info "[FileWatcher]: file '$(wf.path)' does not " *
                              "exist (anymore); removing it from list of " *
                              " watched files."
                    end
                end
            end
            # remove deleted files from list of watched files
            deleteat!(fw.watchedfiles, deleted_files)
        end
    catch err
        fw.status = :interrupted
        # an InterruptException is the normal way for this task to end
        if !isa(err, InterruptException)
            @error "An error happened whilst watching files; shutting down. Error was: $err"
        end
        return nothing
    end
end

"""
    set_callback!(fw::FileWatcher, callback::Function)

Set or change the callback function being executed upon a file change.
Can be "hot-swapped", i.e. while the file watcher is running.
"""
function set_callback!(fw::FileWatcher, callback::Function)
    prev_running = stop(fw)   # returns true if was running
    fw.callback  = callback
    prev_running && start(fw) # restart if it was running before
    fw.status = :runnable
    return nothing
end

"""
    is_running(fw::FileWatcher)

Checks whether the file watcher is running.
"""
is_running(fw::FileWatcher) = (fw.task !== nothing) && !istaskdone(fw.task)

"""
    start(fw::FileWatcher)

Start the file watcher and wait to make sure the task has started.
"""
function start(fw::FileWatcher)
    is_running(fw) || (fw.task = @async file_watcher_task!(fw))
    # wait until task runs to ensure reliable start (e.g. if `stop` called right afterwards)
    while fw.task.state != :runnable
        sleep(0.01)
    end
end

"""
    stop(fw::FileWatcher)

Stop the file watcher. The list of files being watched is preserved and new files can still be
added to the file watcher using `watch_file!`. It can be restarted with `start`.
Returns a `Bool` indicating whether the watcher was running before `stop` was called.
"""
function stop(fw::FileWatcher)
    was_running = is_running(fw)
    if was_running
        # this may fail as the task may get interrupted in between which would lead to
        # an error "schedule Task not runnable"
        try
            schedule(fw.task, InterruptException(), error=true)
        catch
        end
        # wait until sure the task is done
        while !istaskdone(fw.task)
            sleep(0.1)
        end
    end
    return was_running
end

"""
    is_watched(fw::FileWatcher, f_path::AbstractString)

Checks whether the file specified by `f_path` is being watched.
"""
is_watched(fw::FileWatcher, f_path::AbstractString) = any(wf -> wf.path == f_path, fw.watchedfiles)

"""
    watch_file!(fw::FileWatcher, f_path::AbstractString)

Add a file to be watched for changes.
"""
function watch_file!(fw::FileWatcher, f_path::AbstractString)
    if isfile(f_path) && !is_watched(fw, f_path)
        push!(fw.watchedfiles, WatchedFile(f_path))
        VERBOSE[] && @info("[FileWatcher]: now watching '$f_path'")
    end
end


text:
module Utils

using StaticArrays
using ProgressMeter

function toMatrix(mat::Matrix{SVector{3,Float64}})
    n,m = size(mat)
    smat = fill(0.0, 3*n, m )
    for i=1:n
        for j =1:m
            smat[(3*(i-1)+1):(3*i), j] = mat[i,j]
        end
    end
    return smat
end

function toMatrix(mat::Matrix{SMatrix{3, 3, Float64, 9}})
    n,m = size(mat)
    smat = fill(0.0, 3*n, 3*m )
    for i=1:n
        for j =1:m
            smat[(3*(i-1)+1):(3*i), (3*(j-1)+1):(3*j)] = mat[i,j]
        end
    end
    return smat
end

function dot2(A::Matrix{Float64}, B::Matrix{SMatrix{3, 3, Float64, 9}})
    return A
end

function stack_Γ(Γ_list::Vector{Matrix{Float64}})
    Γ_tensor = zeros(size(Γ_list[1])...,length(Γ_list))
    for (i,Γ) in enumerate(Γ_list)
        Γ_tensor[:,:,i] = Γ
    end
    return Γ_tensor
end

function stack_B(B_list::Vector{Vector{Matrix{Float64}}} )

    B_tensor = zeros(size(B_list[1][1])...,length(B_list[1]),length(B_list))
    for (i,B) in enumerate(B_list)
        B_tensor[:,:,:,i] = stack_Γ(B)
    end
    return B_tensor
end

using LinearAlgebra
#n = 4
#a = [i+j for i in 1:n, j =1:n]
#B = [@SVector [i+j,2*i+j,3*i+j] for i in 1:n, j =1:n]
#b = [2*i+j for i in 1:n, j =1:n]
#a
#B
#C = a*B
#sum(a.*b)
#dot(a,b)
end

max_stars_repo_name: ACEsuit/ACEds

max_issues_repo_name: ACEsuit/ACEds

max_forks_repo_name: ACEsuit/ACEds


text:
include("struct.jl")

"""
Create a latex table from the results of an autoexperiment.

The lines of the latex table corresponds to the different possible values of the variables contained in rowVariables (obtained from argument tableStructure).
The columns of the latex table corresponds to variable columnVariables (idem).
"""
function createTexTables(parameters::ExpeParameters; compileTexFile::Bool=true)

    outputFile = parameters.latexOutputFile
    outputstream = open(outputFile, "w")
    println(outputstream, documentHeader())

    tableConstructed = false

    for tableStructure in parameters.latexFormatPath
        println(Dates.format(now(), "yyyy/mm/dd - HHhMM:SS"), "\t\t\t Creating table from ", tableStructure)
        isValid = createTexTable(parameters, tableStructure, outputstream)
        if isValid
            tableConstructed = true
        end
    end

    println(outputstream, documentFooter())
    close(outputstream)

    if compileTexFile && tableConstructed

        outputFolder = dirname(outputFile)
        if length(outputFolder) == 0
            read(`pdflatex $outputFile`)
        else
            read(`pdflatex --output-directory $outputFolder $outputFile`)
        end
    end

    return nothing
end

"""
Create a latex table from the results of an autoexperiment.

The lines of the latex table corresponds to the different possible values of the variables contained in rowVariables (obtained from argument tableStructure).
The columns of the latex table corresponds to variable columnVariables (idem).
"""
function createTexTable(parameters::ExpeParameters,  tableStructureFilePath::String, outputstream::IOStream)

    # Get the table format (i.e., structure and features of the table)
    tableParam, rowVariables, columns = readLatexFormat(tableStructureFilePath)

    # Get all the results
    # results[i] is one dictionary representing all information obtained from one resolution
    results = Vector{Dict{String, Any}}(getResults(parameters))

    # tableCombinations[i]::CombinationResults: all results used to compute values in row(s) of combination n°i
    # (i.e., results which correspond to the combination of values of the row variables used in row n°i of the table)
    tableCombinations = splitResultsByCombination(parameters, tableParam, results, columns, rowVariables)

    # If none of the row variables has a value in the result files, do not create the table
    if tableCombinations == nothing
        return false
    end

    # Compute the values displayed in the table
    computeTableValues(parameters, tableParam, tableCombinations, columns)

    containColumnGroups = false

    # Check if there is a ColumnGroup
    for column in columns
        if typeof(column) == ColumnGroup
            containColumnGroups = true
        end
    end

    # Create the table header to the latex file
    tableHeader = createTableHeader(tableParam, rowVariables, columns, containColumnGroups)

    # Count the number of rows to split the table if it reaches tableParam.maxRowsInATable
    rowCount = 1

    if containColumnGroups
        rowCount = 2
    end

    mustReprintHeader = true
    tableHasMissingValues = false

    # For each CombinationResults (i.e., each combination of row parameters)
    for combinationId in 1:length(tableCombinations)

        combinationResults = tableCombinations[combinationId]

        # Value of the row variables which are different from the previous row
        latexModifiedRowVariables = ""

        # Value of all the row variables
        latexAllRowVariables = ""

        # For each row variable
        for i in 1:length(rowVariables)

            # Get its value in the current combination
            displayedValue = combinationResults.combination[i]

            # If it is a numerical value
            numValue = numericalValue(displayedValue)

            if numValue != nothing
                numValue *= rowVariables[i].multiplier # Multiply it
                if rowVariables[i].digits != -1 # Round it if required
                    if rowVariables[i].digits == 0
                        numValue = round(Int, numValue)
                    else
                        numValue = round(numValue, digits = rowVariables[i].digits)
                    end
                end
                displayedValue = numValue
            end

            latexAllRowVariables *= string(displayedValue) * " & "

            if combinationId > 1 && combinationResults.combination[i] != tableCombinations[combinationId-1].combination[i]
                latexModifiedRowVariables *= string(displayedValue)
            end

            latexModifiedRowVariables *=  " & "
        end

        latexRow = ""

        # If the table represents the mean value over all instances
        if !tableParam.expandInstances

            if mustReprintHeader
                latexRow = latexAllRowVariables
            else
                latexRow = latexModifiedRowVariables
            end

            rowCount, mustReprintHeader, tableHasMissingValues = addResultsToRow(combinationResults.displayedValues, mustReprintHeader, outputstream, tableHeader, latexRow, rowCount, tableHasMissingValues, tableParam, containColumnGroups)

        else # If the result of each instance is on a different line

            instancesName = collect(keys(combinationResults.instancesResults))

            # For each instance
            for instanceId in 1:length(instancesName)

                latexRow = ""
                if mustReprintHeader
                    latexRow *= latexAllRowVariables
                elseif instanceId == 1
                    latexRow *= latexModifiedRowVariables
                else
                    latexRow *= "&"^length(rowVariables)
                end

                # TODO: remove extension according to tableParam.hideInstancesExtension
                instanceName = basename(instancesName[instanceId])
                instanceName = replace(instanceName, "_" => "\\_")
                latexRow *= instanceName * " & "

                instanceResults = combinationResults.instancesResults[instancesName[instanceId]]

                rowCount, mustReprintHeader, tableHasMissingValues = addResultsToRow(instanceResults.displayedValues, mustReprintHeader, outputstream, tableHeader, latexRow, rowCount, tableHasMissingValues, tableParam, containColumnGroups)
            end
        end

        if length(combinationResults.clineColumns) == 2 && !mustReprintHeader
            println(outputstream, "\\cline{" * string(combinationResults.clineColumns[1]) * "-" * string(combinationResults.clineColumns[2]) * "}")
        end
    end

    # If the last table is not closed
    if !mustReprintHeader
        println(outputstream,  getTableFooter(tableParam.caption, hasMissingValues=tableHasMissingValues))
    end
    return true
end

function getTableFooter(caption::String; hasMissingValues::Bool=false)
    tableFooter = raw"""\hline\end{array}$
    \end{center}
    \caption{""" * caption

    if hasMissingValues
        tableFooter *= raw""" The symbol $\dag$ indicates an average value   computed from an incomplete set of results compared to other values on the same line."""
    end

    tableFooter *= raw"""}
    \end{table}
    \newpage

    """

    return tableFooter
end

"""
Unique name of a column used to identify a column without any ambguity.
Required since several columns may have the same displayed name in the latex table.
"""
function uniqueColumnName(col::Column)
    result = col.columnName * "-"

    if col.computeValue != emptyFunction
        result *= String(Symbol(col.computeValue)) * "("
    end

    for val in col.valInfos
        result *= val.key

        if length(val.indexes) > 0
            result *= "["
            for indexId in 1:length(val.indexes)
                result *=  string(val.indexes[indexId])

                if indexId != length(val.indexes)
                    result *= ","
                end
            end
            result *= "]"
        end

        result *= " from " * val.resolutionMethod
    end

    if col.computeValue != emptyFunction
        result *= ")"
    end

    return result
end

"""
For each row variable, get all its values which appear in the results

Intput
- variables: list of RowVariables
- results: dictionary in which each entry corresponds to a result

Output
- array which contains one vector for each variable. The array associated to a variable contains all its possible values
"""
function getVariablesValues(rowVariables::Vector{RowVariable}, results::Vector{Dict{String, Any}}, expeParameters::ExpeParameters)

    # Different values of all the row variables
    variablesValues = Vector{Vector{Any}}([])

    # Add an empty array for each row variable
    for var in 1: length(rowVariables)
        push!(variablesValues, [])
    end

    # For each result
    for result in results

        # For each RowVariable considered
        for rvId in 1:length(rowVariables)
            rvKey = rowVariables[rvId].valInfo.key

            # If the result contains this variable and if its value in this result is new, add it
            if haskey(result, rvKey) && !(result[rvKey] in variablesValues[rvId])

                # If the variable is not part of the parameters of the experiment or if its value in the result is included in the values considered in the experiment
                # (if the user specify values for a parameter in the experiment, we only want to include in the table results for these values)
                if !haskey(expeParameters.parametersToCombine, rvKey) || result[rvKey] in expeParameters.parametersToCombine[rvKey]
                    push!(variablesValues[rvId], result[rvKey])
                end
            end
        end
    end

    # Sort each array
    for rvId in 1:length(rowVariables)
        variablesValues[rvId] = sort(variablesValues[rvId])

        # If the row variable only contains integer values, set its number of displayed digits to 0
        isIntRowVariable = true
        valueId = 1

        while isIntRowVariable && valueId < length(variablesValues[rvId])
            if typeof(numericalValue(variablesValues[rvId][valueId])) != Int
                isIntRowVariable = false
            end
            valueId += 1
        end

        if isIntRowVariable
            rowVariables[rvId].digits = 0
        end
    end

    return variablesValues

end

"""
Compute the mean value of the numerical values contained in an array
Returns nothing if there are no numerical value in the array.
"""
function mean(t::Vector{Any})

    numT = numericalVector(t)

    if length(numT) > 0
        return sum(numT)/length(numT)
    else
        println("Warning: unable to compute the mean value of a vector without any numerical values (vector: ", t, ")")
        return nothing
    end
end

"""
Compute the gap of the two numerical values included in an array (|t[1] - t[2]|/t[1]).
Returns nothing if:
- the size of the array is not 2;
- both values are not numerical.

Note:
- to avoid dividing by 0, 1E-6 is added to the denominator;
- to avoid very high gaps, the denominator is set to 0.1 if it is <0.1 and if the gap exceeds 1000.
"""
function gap(t::Vector{Any})

    numT = numericalVector(t)

    if length(t) == 2
        numerator = abs(t[1] - t[2])
        denominator = abs(t[1])

        result = 100 * numerator / (denominator + 1E-6)

        if result > 1000 && denominator < 0.1
            println("Warning: gap over 1000 found with a denominator < 0.1. Setting the denominator to 0.1 to avoid huge gap values (numerator: ", numerator, ", initial denominator: ", denominator, ")")
            result = 100 * numerator / 0.1
        end

        return result
    else
        println("Warning: a gap must be computed between two numerical values but you entered ", length(t), " values instead: gap(", t, ")")
    end

    return nothing
end

"""
Convert a vector into numerical values. Unexpected value are ignored (e.g., non numerical values, arrays, strings, ...)
"""
function numericalVector(v::Vector{Any}; displayWarning::Bool=true)
    result = Vector{Any}()

    for element in v
        numElement = numericalValue(element)

        if numElement != nothing
            push!(result, numElement)
        else
            println("Warning: unable to convert \"", element, "\" into a numerical value. Value skipped.")
        end
    end

    return result
end

"""
Parse a value from a result file into a numerical value:
- convert the strings which represent integers and floats; and
- returns nothing for unexpected values.
"""
function numericalValue(v)

    if isa(v, Array)
        return nothing
    else
        intValue = nothing
        floatValue = nothing
        try
            intValue = tryparse(Int, v)
        catch e
            if typeof(v) == Int
                intValue = v
            end
        end

        try
            floatValue = tryparse(Float64, v)
        catch e
            if typeof(v) == Float64
                floatValue = v
            end
        end

        if intValue == nothing || (floatValue != intValue && floatValue != nothing)
            return floatValue
        else
            return intValue
        end
    end
end

"""
Header of the latex document
"""
function documentHeader()
    return raw"""
\documentclass{article}

\usepackage[french]{babel}
\usepackage [utf8] {inputenc} % utf-8 / latin1
\usepackage{amsmath}
\usepackage{multicol}
\usepackage{multirow}
\usepackage[landscape,a3paper]{geometry}

\setlength{\hoffset}{-18pt}
\setlength{\oddsidemargin}{0pt} % Marge gauche sur pages impaires
\setlength{\evensidemargin}{9pt} % Marge gauche sur pages paires
\setlength{\marginparwidth}{54pt} % Largeur de note dans la marge
\setlength{\textwidth}{520pt} % Largeur de la zone de texte (17cm)
\setlength{\voffset}{-18pt} % Bon pour DOS
\setlength{\marginparsep}{7pt} % Séparation de la marge
\setlength{\topmargin}{0pt} % Pas de marge en haut
\setlength{\headheight}{5pt} % Haut de page
\setlength{\headsep}{5pt} % Entre le haut de page et le texte
\setlength{\footskip}{27pt} % Bas de page + séparation
\setlength{\textheight}{550pt} % Hauteur de la zone de texte (25cm)

\begin{document}
\renewcommand{\arraystretch}{1.4}
"""
end

"""
Footer of the latex document
"""
function documentFooter()
    return raw"""
\end{document}
%%% Local Variables:
%%% mode: latex
%%% TeX-master: t
%%% End:
"""
end

"""
Read the format of a latex table from a json file
"""
function readLatexFormat(jsonFilePath::String)

    tableParam = TableParameters()
    rows = Vector{RowVariable}()

    # Contains either Column or ColumnGroup
    columns = Vector{Any}()

    stringdata=join(readlines(jsonFilePath))
    formatArray = JSON.parse(stringdata)

    # For each entry (row, column or table parameters) in the format
    for entry in formatArray

        # If the entry corresponds to the table parameters
        if haskey(entry, "caption")

            tableParam.caption = entry["caption"]

            if haskey(entry, "maxRowsInATable")
                numValue = numericalValue(entry["maxRowsInATable"])
                if numValue != nothing
                    tableParam.maxRowsInATable = numValue
                else
                    println("Warning: The maximum number of rows in a table (\"maxRowsInATable\") must be a numerical value. The value is ignored.\n\tCurrent value: ", entry["maxRowsInATable"])
                end
            end

            if haskey(entry, "expandInstances")
                tableParam.expandInstances = entry["expandInstances"]
            end

            if haskey(entry, "hideInstancesExtension")
                tableParam.hideInstancesExtension = entry["hideInstancesExtension"]
            end

            if haskey(entry, "leftVline")
                tableParam.leftVline = entry["leftVline"]
            end

            if haskey(entry, "rightVline")
                tableParam.rightVline = entry["rightVline"]
            end

            if haskey(entry, "vlineAfterRowParams")
                tableParam.vlineAfterRowParams = entry["vlineAfterRowParams"]
            end

            if haskey(entry, "maxNonScientificValue")
                numValue = numericalValue(entry["maxNonScientificValue"])
                if numValue != nothing
                    tableParam.maxNonScientificValue = numValue
                else
                    println("Warning: The maximum number of rows in a table (\"maxNonScientificValue\") must be a numerical value. The value is ignored.\n\tCurrent value: ", entry["maxNonScientificValue"])
                end
            end

        # If the entry corresponds to a row
        elseif haskey(entry, "rowParameterName")

            parameterName = entry["rowParameterName"]

            # If the parameter is obtained from an array, get its indice(s)
            indexes = nothing
            invalidIndexes = Vector{Any}()
            if haskey(entry, "indexes")

                if !(entry["indexes"] <: Vector)
                    entry["indexes"] = [entry["indexes"]]
                end

                indexes = Vector{Int}()

                for index in entry["indexes"]

                    numValue = numericalValue(index)

                    if numValue == nothing
                        push!(invalidIndexes, index)
                    else
                        push!(indexes, numValue)
                    end
                end
            end

            # If the row parameter does not have any invalid index
            if length(invalidIndexes) == 0

                if indexes == nothing
                    vi = ValueInformation(parameterName)
                else
                    vi = ValueInformation(parameterName, indexes=indexes)
                end

                displayedName = vi.key

                # If the name displayed is different from the parameter name
                if haskey(entry, "displayedName")
                    displayedName = entry["displayedName"]
                end

                hline = haskey(entry, "hline") && entry["hline"]
                rowVar = RowVariable(vi, hlineBetweenValues=hline, displayedName=displayedName)

                if haskey(entry, "multiplier") && numericalValue(entry["multiplier"]) != nothing
                    rowVar.multiplier = numericalValue(entry["multiplier"])
                end

                if haskey(entry, "digits") && numericalValue(entry["digits"]) != nothing
                    rowVar.digits = numericalValue(entry["digits"])
                end

                push!(rows, rowVar)

            else # If the parameter has invalid indexes
                println("Warning: The indexes of row parameters must be numerical values. The row parameter is ignored.\n\trow parameter name: ", parameterName, "\n\tInvalid indexes: ", invalidEntries)
            end

        # If the entry corresponds to a column
        elseif haskey(entry, "columnParameters")

            column = readColumn(entry)

            if column != nothing
                push!(columns, column)
            end

        elseif haskey(entry, "columnGroupName") && haskey(entry, "columns")

            colGroup = ColumnGroup(entry["columnGroupName"], Vector{Column}())

            for columnEntry in entry["columns"]

                column = readColumn(columnEntry)

                if column != nothing
                    push!(colGroup.columns, column)
                end
            end

            if haskey(entry, "isBestValueMinimal")
                colGroup.isBestValueMinimal = entry["isBestValueMinimal"]
            end

            if haskey(entry, "vlineAfter")
                colGroup.vlineAfter = entry["vlineAfter"]
            end

            if haskey(entry, "highlightBestValue")
                colGroup.highlightBestValue = entry["highlightBestValue"]
            end

            push!(columns, colGroup)

        else
            println("Warning: the json array contains an element which is neither a table parameter, nor a column, nor a row, nor a column group:")
            println("\t- Each entry must be a json object which contains either the key(s) \"caption\", \"rowParameterName\", (\"columnParameters\" and \"method\") or  \"columnGroupName\";")
            println("\t- The value of key \"rowParameterName\", \"method\" and \"columnGroupName\" must be strings;")
            println("\t- The value of key\"columnParameters\" must be an array of objects. Each object must contain at least a key \"name\".")
            println("Element in the array: ", entry)
        end

    end

    return tableParam, rows, columns
end

"""
Read the json entry which corresponds to a column and return the corresponding Column.
"""
function readColumn(dictColumn::Dict{String, Any})

    if !haskey(dictColumn, "columnParameters")
        println("Warning: the following input element does not correspond to a column (input: \"", displayStringDict(dictColumn), "\"). It must contain a key \"columnParameters\". The column is thus ignored.")
        return nothing
    end

    defaultResolutionMethod = ""

    if haskey(dictColumn, "method")
        defaultResolutionMethod = dictColumn["method"]
    end

    parameters = dictColumn["columnParameters"]

    # If there is only one parameter
    if !(typeof(parameters) <: Vector)
        parameters = [parameters]
    end

    viParameters = Vector{ValueInformation}()

    for parameter in parameters
        vi = nothing

        # If the column parameter is a dictionary
        if typeof(parameter) <: Dict
            if haskey(parameter, "name")
                vi = ValueInformation(parameter["name"])

                if !haskey(parameter, "method")
                    vi.resolutionMethod = defaultResolutionMethod
                else
                    vi.resolutionMethod = parameter["method"]
                end

                if haskey(parameter, "indexes")
                    vi.indexes = Vector{Int}()

                    if isa(parameter["indexes"], Array)
                        for id in 1:length(parameter["indexes"])
                            value = parameter["indexes"][id]
                            if typeof(value) == Int
                                push!(vi.indexes, value)
                            else
                                numValue = numericalValue(value)
                                if numValue != nothing
                                    push!(vi.indexes,  round(Int, numValue))
                                end
                            end
                        end
                    else
                        if parameter["indexes"] != nothing
                            push!(vi.indexes, round(Int, parameter["indexes"]))
                        end
                    end
                end
            else
                println("Warning: parameter without any key \"name\" or any key \"method\"")
                println("Parameter: ", displayStringDict(parameter))
                println("Column column: ", displayStringDict(dictColumn))
            end
        elseif typeof(parameter) == String # If the parameter is a string
            vi = ValueInformation(parameter)
            vi.resolutionMethod = defaultResolutionMethod
        end

        if vi != nothing
            if vi.resolutionMethod != ""
                push!(viParameters, vi)
            else
                println("Warning: unspecified resolution method for a column parameter (the parameter: ", parameter, "). You can add a field  \"method\"  either:\n- in the column parameter; or\n- in the column entry (it will be used by default for all columnParameters of this column with an unspecified resolution method).")
            end
        else
            println("Warning: invalid format for a column parameter. The format must either be String or Dict.\n\tCurrent parameter: ", parameter, "\n\tCurrent type: ", typeof(parameter))
        end
    end

    vline = haskey(dictColumn, "vline") && dictColumn["vline"]

    fct = emptyFunction

    if haskey(dictColumn, "function")
        if dictColumn["function"] == "gap"
            fct = gap
        elseif dictColumn["function"] == "mean"
            fct = mean
        else
            println("Warning: unknown input function \"", functionString, "\".")
        end
    end

    column = Column(viParameters, vlineAfter=vline, computeValue=fct)

    if haskey(dictColumn, "displayedName")
        column.columnName = dictColumn["displayedName"]
    end

    if haskey(dictColumn, "multiplier") && numericalValue(dictColumn["multiplier"]) != nothing
        column.multiplier = numericalValue(dictColumn["multiplier"])
    end

    if haskey(dictColumn, "digits") && numericalValue(dictColumn["digits"]) != nothing
        column.digits = numericalValue(dictColumn["digits"])
    end

    if haskey(dictColumn, "prefix")
        column.prefix = dictColumn["prefix"]
    end

    if haskey(dictColumn, "suffix")
        column.suffix = dictColumn["suffix"]
    end

    if haskey(dictColumn, "ignoreBold")
        column.ignoreBold = dictColumn["ignoreBold"]
    end

    return column

end

"""
Get all the results computed in the experiment
"""
function getResults(parameters::ExpeParameters)

    # Variable which contains all the results of all instances
    # Each result is a Dict which contains:
    # - one entry with the key "method"
    # - one entry for each parameter
    # - one entry for each result
    results = Vector{Dict{String, Any}}()

    for instancePath in parameters.instancesPath

        instanceName = splitext(basename(instancePath))[1]
        outputFile = parameters.outputPath * "/" * instanceName * ".json"

        # Get the results previously computed for this instance if any
        if isfile(outputFile)
            stringdata=join(readlines(outputFile))

            try
                instanceResults = Vector{Dict{String, Any}}(JSON.parse(stringdata))

                for result in instanceResults
                    result["auto_expe_instance_path"] = instancePath
                end

                append!(results, instanceResults)
            catch e
                println("Error: Unable to read result file: ", outputFile)
                print(e)
            end
        end
    end
    return results
end

"""
Split all the results according to their combination of parameters

Input
- parameters: parameters of the experiment;
- results: all the results saved during the experiment;
- columns: the columns of the table;
- rowVariables: the row variables of the table.

Output
- an array of CombinationResults ordered according to the row of the table (i.e. the ith index of this array corresponds to all the results used to compute the ith line)
"""
function splitResultsByCombination(parameters::ExpeParameters, tableParam::TableParameters, results::Vector{Dict{String, Any}}, columns::Vector{Any}, rowVariables::Vector{RowVariable})

    # For each row variable, get all its possible values
    # rowValues[i] is an array including all the values of the ith row variable in rowVariables
    rowValues = getVariablesValues(rowVariables, results, parameters)

    for rowVariableId in length(rowVariables):-1:1
        if length(rowValues[rowVariableId]) == 0
            println("Warning: No value found for variable ", rowVariables[rowVariableId].valInfo.key, ". This variable is ignored.")
            deleteat!(rowVariables, rowVariableId)
            deleteat!(rowValues, rowVariableId)
        end
    end

    ## Get the number of result columns
    resultColumnsCount = 0

    # For each column
    for column in columns

        if typeof(column) == ColumnGroup
            for col in column.columns
                resultColumnsCount += 1
            end
        else
            resultColumnsCount += 1
        end
    end

    # Index in rowValues of the value of all row parameters in the current combination
    # ex: if combinationIds = [3, 4] the current line corresponds to the value rowValues[1][3] and rowValues[2][4]
    combinationIds = Vector{Int}(ones(length(rowVariables)))
    previousCombinationIds = Vector{Int}(-ones(length(rowVariables)))
    isOver = false

    tableCombinations = Vector{CombinationResults}()

    # While all the combinations have not been considered
    while !isOver

        combinationResults = Vector{Dict{String, Any}}()

        ## Get all the result for this combination

        # For each result
        for result in results

            isResultInThisCombination = true
            checkId = 1

            # While all the row variables have not all been checked
            while isResultInThisCombination && checkId <= length(rowVariables)

                # If the result does not have a value for row variable checkId or if its value is not equal to its value in the current line
                if !haskey(result, rowVariables[checkId].valInfo.key) || result[rowVariables[checkId].valInfo.key] != rowValues[checkId][combinationIds[checkId]]
                    isResultInThisCombination = false
                end

                checkId += 1
            end

            # If the result is in the current combination
            if isResultInThisCombination
                push!(combinationResults, result)
            end
        end

        parametersValues = Vector{Any}()

        for i in 1:length(combinationIds)
            push!(parametersValues, rowValues[i][combinationIds[i]])
        end

       push!(tableCombinations, CombinationResults(parameters, parametersValues, combinationResults))

        # Get the next combination of row parameters
        varId = length(rowVariables)
        nextCombinationFound = false

        for i in 1:length(combinationIds)
            previousCombinationIds[i] = combinationIds[i]
        end

        if length(rowVariables) == 0
            isOver = true
        end

        while !nextCombinationFound && !isOver

            # If the id of variable varId can be incremented
            if combinationIds[varId] < length(rowValues[varId])

                # Increment it
                combinationIds[varId] += 1
                nextCombinationFound = true

                if rowVariables[varId].hlineBetweenValues
                    lineWidth = resultColumnsCount + length(rowVariables)

                    if tableParam.expandInstances
                        lineWidth += 1
                    end

                    tableCombinations[end].clineColumns = Vector{Int}([varId, lineWidth])
                end

            # If variable varId cannot be incremented
            else
                # If we reached the last combination
                if varId == 1
                    isOver = true
                else
                    # Try to increment the id of the previous variable
                    combinationIds[varId] = 1
                    varId -= 1
                end
            end
        end
    end

    return tableCombinations
end

"""
Compute the value for each result column for each CombinationResults
"""
function computeTableValues(parameters::ExpeParameters, tableParameters::TableParameters, combinationResults::Vector{CombinationResults}, columns::Vector{Any})

    # For each combination
    for combination in combinationResults

        # For each column
        columnId = 1 # Id of the column in InstanceResults.computedResults (i.e., 1 id per columns in the table)
        columnsId = 1 # Id of the column or the group in "columns" (i.e., 1 id per groups and ungrouped columns)
        groupId = 1 # If columns[columnsId] is a ColumnGroup, id of the current column in the group (ignored otherwise)

        column = columns[columnsId]
        if typeof(column) == ColumnGroup
            column = column.columns[1]
        end

        allColumnsConsidered = false

        while !allColumnsConsidered

            # For each instance
            for (instanceName, instanceResults) in combination.instancesResults

                instanceResults.instancePath = instanceName

                # Compute the column values for this instance and add them in instanceResults.computedResults
                computeInstanceValues(parameters, instanceResults, column)

                # If the table displays one row for each instance
                if tableParameters.expandInstances

                    if length(instanceResults.computedResults) == 0
                        emptyValue = TableValue(nothing, false, false, 1, column)
                        push!(instanceResults.displayedValues,  emptyValue)
                    else
                        meanValue, isNumerical = computeTableValue(parameters, instanceResults.computedResults[end], column)
                        missingResultsForValue = hasMissingResults(column, instanceResults)
                        push!(instanceResults.displayedValues, TableValue(meanValue, isNumerical, missingResultsForValue, tableParameters.maxNonScientificValue, column))
                    end
                end
            end

            # If the table display the mean value over all the instances
            if !tableParameters.expandInstances

                # Get all the values over all the instances in one vector
                allInstancesResults = Vector{Any}()
                missingResultsForValue = false

                for (instanceName, instanceResults) in combination.instancesResults
                    append!(allInstancesResults, instanceResults.computedResults[end])
                    if hasMissingResults(column, instanceResults)
                        missingResultsForValue = true
                    end
                end

                meanValue, isNumerical = computeTableValue(parameters, allInstancesResults, column)
                push!(combination.displayedValues, TableValue(meanValue, isNumerical, missingResultsForValue, tableParameters.maxNonScientificValue, column))
            end

            ## Get the next column to process

            # Do we go to the next id in array columns?
            setNextColumn = false

            if typeof(columns[columnsId]) == ColumnGroup
                if groupId == length(columns[columnsId].columns)
                    if columnsId == length(columns)
                        allColumnsConsidered = true
                    else
                        setNextColumn = true
                    end
                else
                    groupId += 1
                    column = columns[columnsId].columns[groupId]
                end
            else
                if columnsId == length(columns)
                    allColumnsConsidered = true
                else
                    setNextColumn = true
                end
            end

            # If this is the end of a group
            if typeof(columns[columnsId]) == ColumnGroup && (setNextColumn || allColumnsConsidered)
                group = columns[columnsId]

                # If the best values of the group must be highlighted
                if group.highlightBestValue
                    if !tableParameters.expandInstances
                        setBoldValuesInGroup(group, combination.displayedValues[columnId-length(group.columns)+1:columnId])
                    else
                        for (instanceName, instanceResults) in combination.instancesResults
                            setBoldValuesInGroup(group, instanceResults.displayedValues[columnId-length(group.columns)+1:columnId])
                        end
                    end
                end
            end

            if setNextColumn

                columnsId += 1
                column = columns[columnsId]
                if typeof(column) == ColumnGroup
                    groupId = 1
                    column = column.columns[groupId]
                end
            end

            columnId += 1
        end # while !allColumnsConsidered
    end # for combination in combinationResults
end

"""
Compute the value in column "column" for an instance using the results of the methods in "methodResults".
methodResults only contains 1 entry for each resolution method required to compute the value of the column.

Input
- column: the column in which the result must be computed
- methodResults: dictionary of results for the considered instance indexed by method name
  methodResults[methodName][resultName]: value of entry "resultName" in the result files for method "methodName"
"""
function computeValue(column::Column, methodResults::Dict{String, Dict{String, Any}}, instancePath::String)

    # Values used to compute the value in the column
    values = Vector{Any}()
    unavailableInformation = Vector{ValueInformation}([])

    # For each information required to compute the value of the column
    for valInfo in column.valInfos

        methodResult = methodResults[valInfo.resolutionMethod]
        if !haskey(methodResult, valInfo.key)
            push!(unavailableInformation, valInfo)
        else

            parameter = methodResult[valInfo.key]

            # Number of dimensions expected in the array
            parameterDimension = length(size(valInfo.indexes))

            value = parameter

            # If no dimensions are specified
            if valInfo.indexes == []

                # For each array dimension of the parameter, take the first value
                for dimId in 1:parameterDimension
                    if isa(value, Array)
                        value = selectdim(value, 1, 1)
                    end
                end
            else
                indexesDimension = length(valInfo.indexes)

                if indexesDimension != parameterDimension
                    print("Warning: ", indexesDimension, " dimensions are specified for parameter \"", valInfo.key, "\" used to compute the value of column \"", column.columnName, "\". However, this parameter contains ", parameterDimension, " dimensions.")

                    if indexesDimension > parameterDimension
                        println(" Ignoring the additional indexes.")
                    else
                        println(" Index 1 is considered for the unspecified dimensions.")
                    end
                end

                # Select the indexes for the minimum of both dimensions
                for dimId in 1:min(indexesDimension, parameterDimension)
                    value = selectdim(value, 1, indexesDimension[dimId])
                end

                # If the parameter dimension is greater, select index 1 for the next dimensions
                for dimId in min(indexesDimension, parameterDimension)+1:parameterDimension
                    value = selectdim(value, 1, 1)
                end
            end
            push!(values, value)
        end
    end

    computedValue = nothing

    if length(unavailableInformation) > 0
        print("Warning: missing entries to compute the value of column ", uniqueColumnName(column), "\n\tList of the ", length(unavailableInformation), " missing entries:")
        for valInfo in unavailableInformation
            print("\"", valInfo.key, "\" for method \"", valInfo.resolutionMethod, "\", ")
        end
        println("\n\tList of the available results: ", methodResults, "\n\tInstance Path: ", instancePath)
    else
        if column.computeValue != emptyFunction
            computedValue = column.computeValue(values)
        elseif length(values) == 0
            println("Warning: no value obtained for a cell in column ", uniqueColumnName(column), ".")
        else
            computedValue = values[1]
            if length(values) > 1
                println("Warning: more than one values obtained for a cell in column ", uniqueColumnName(column), ": ", values, ". Only keeping the first one")
            end
        end
    end

    return computedValue
end

"""
Compute all the values for an instance and a combination in a column.
There can be several of these values.
E.g.: If we have results when combining two parameters in a knapsack problem:
1 - number of knapsacks: 2 and 3;
2 -  size of the:  10 and 20.

We get 4 combinations of parameters: (2, 10), (3, 10), (2, 20), (3, 20).

If we create a latex table in which:
- the number of knapsacks is a row parameter;
- the size of the knapsacks is not;
for each instance we will obtain two result lines: 2 and 3.
For each of these lines we will have 2 values (2, 10) and (2, 20) in the first and (3, 10) and (3, 20) in the second.

Input
- parameters: the parameters of the experiment;
- instanceResults: results obtained during this experiment for the instance and the combination considered;
- column: the column for which we compute the values.
"""
function computeInstanceValues(parameters::ExpeParameters, instanceResults::InstanceResults, column::Column)

    # Get all the resolution methods required to compute the value in column "column"
    vResolutionMethods = Vector{String}()

    for val in column.valInfos
        if !(val.resolutionMethod in vResolutionMethods)
            push!(vResolutionMethods, val.resolutionMethod)
        end
    end

    # Represents all the values computed for this instance
    instanceComputedResults = Vector{Any}()

    # If the first resolution method required to compute values in this column does not have any result
    if haskey(instanceResults.methodResults, vResolutionMethods[1])

        # For each result obtained for this instance for the first resolution method
        for methodResult in instanceResults.methodResults[vResolutionMethods[1]].results

            ## Check if there is a result with the same value of parameters for all the other resolution methods required to compute the value in this column

            # Dictionary of compatible results used to compute a value in the column
            # methodResults[methodName][resultName]: value of the entry "resultName" for the method "methodName"
            methodResults = Dict{String, Dict{String, Any}}()
            methodResults[vResolutionMethods[1]] = methodResult

            methodId = 2
            missingMethod = false

            # While all the methods have not been tested and no method result is missing yet
            while methodId <= length( vResolutionMethods) && !missingMethod

                currentMethodName = vResolutionMethods[methodId]
                currentMethodResults = instanceResults.methodResults[currentMethodName]

                resultFound = false
                resultId = 1

                # While:
                # - all the results of the current method have not been tested; and
                # - a compatible result has not been found.
                while resultId <= length(currentMethodResults.results) && !resultFound

                    currentResult = Dict{String, Any}(currentMethodResults.results[resultId])

                    # If this result corresponds to the same parameters than the result of the first method
                    isResultValid = true
                    parameterId = 1

                    parametersNames = collect(keys(parameters.parametersToCombine))

                    while parameterId < length(parametersNames) && isResultValid
                        parameterName  = parametersNames[parameterId]

                        if !haskey(currentResult, parameterName)
                            isResultValid = false
                        else
                            parameterValue = currentResult[parameterName]
                            referenceValue = methodResult[parameterName]

                            if parameterValue != referenceValue
                                isResultValid = false
                            end
                        end

                        parameterId += 1
                    end

                    if isResultValid
                        resultFound = true
                        methodResults[currentMethodName] = currentResult
                    end
                    resultId += 1
                end

                if !resultFound
                    missingMethod = true
                end

                methodId += 1
            end

            # If compatible results have been found for all the resolution methods required
            if !missingMethod

                # Compute the corresponding values and add them to the instance results
                value = computeValue(column, methodResults, instanceResults.instancePath)

                if value != nothing
                    append!(instanceComputedResults, value)
                end
            end
        end # for methodResult in instanceResults.methodResults[vResolutionMethods[1]]
    end

    push!(instanceResults.computedResults, instanceComputedResults)

end

"""
Compute the average value of a vector of results
"""
function computeTableValue(parameters::ExpeParameters, results::Vector{Any}, column::Column)

    numericalResults = numericalVector(results)

    isNumerical = true

    if length(numericalResults) > 0

        meanValue = sum(numericalResults) / length(numericalResults)
        meanValue *= column.multiplier

        if column.digits == 0
            return round(Int, meanValue), isNumerical
        else
            return round(meanValue, digits=column.digits), isNumerical
        end
    else
        isNumerical = false
        filteredValues = unique(results)
        if length(filteredValues) == 1
            if filteredValues[1] != nothing
                return filteredValues[1], isNumerical
            else
                return nothing, isNumerical
            end
        else
            return nothing, isNumerical
        end
    end
end

"""
Find the best numerical values in the set of values of a group and set them to bold.
Non-numerical values are ignored.

Input
- group: Column group in which the columns are located
- groupValues: TableValues in the group
"""
function setBoldValuesInGroup(group::ColumnGroup, groupValues::Vector{TableValue})

    bestValues = Vector{TableValue}()

    for tableValue in groupValues
        if tableValue.isNumerical && !tableValue.ignoreBold
            isAmongTheBest = length(bestValues) == 0 ||
                (tableValue.value  >= bestValues[1].value && !group.isBestValueMinimal) ||
                (tableValue.value  <= bestValues[1].value && group.isBestValueMinimal)

            if isAmongTheBest
                isTheOnlyBest = length(bestValues) == 0 ||
                    (tableValue.value  > bestValues[1].value && !group.isBestValueMinimal) ||
                    (tableValue.value  < bestValues[1].value && group.isBestValueMinimal)
                if isTheOnlyBest
                    empty!(bestValues)
                end
                push!(bestValues, tableValue)
            end
        end
    end

    for tableValue in bestValues
        tableValue.displayedValue = raw"""\mathbf{""" * tableValue.displayedValue * "}"
    end
end

function createTableHeader(tableParam, rowVariables, columns, containColumnGroups)

    # Create the table header
    tableHeader = raw"""
    \begin{table}[h!]
    \begin{center}
    $\begin{array}{"""

    if tableParam.leftVline
        tableHeader *= "|"
    end

    if length(rowVariables) != 0
        tableHeader *= "*{" * string(length(rowVariables)) * "}{r}"
    end

    if tableParam.expandInstances
        tableHeader *= "r"
    end

    if tableParam.vlineAfterRowParams
        tableHeader *= "|"
    end

    lastColumnHasVline = false

    for column in columns
        if typeof(column) == ColumnGroup
            for col in column.columns
                tableHeader *= "r"

                if col.vlineAfter || col == column.columns[length(column.columns)] && column.vlineAfter
                    tableHeader *= "|"
                    lastColumnHasVline = true
                else
                    lastColumnHasVline = false
                end
            end

            if !lastColumnHasVline && column.vlineAfter
                tableHeader *= "|"
                lastColumnHasVline = true
            end
        else
            tableHeader *= "r"

            if column.vlineAfter
                tableHeader *= "|"
                lastColumnHasVline = true
            else
                lastColumnHasVline = false
            end
        end
    end

    if !lastColumnHasVline && tableParam.rightVline
        tableHeader *= "|"
    end

    # Add an empty last column to ease rows construction
    tableHeader *= "@{}r@{}}\n"
    tableHeader *= raw"""\hline"""

    # For each row variable
    for rowVar in rowVariables

        if containColumnGroups
            tableHeader *= raw"""\multirow{2}{*}{\textbf{""" * rowVar.displayedName * "}} & "
        else
            tableHeader *= raw"""\textbf{""" * rowVar.displayedName * "} & "
        end
    end

    if tableParam.expandInstances
        if containColumnGroups
            tableHeader *= raw"""\multirow{2}{*}{\textbf{Instance}} & """
        else
            tableHeader *= raw"""\textbf{Instance} & """
        end
    end

    # For each result column
    for column in columns
        if typeof(column) == ColumnGroup
            tableHeader *= raw"""\multicolumn{""" * string(length(column.columns)) * "}{c"

            # If
            # - a line is specified after this group of columns; or if
            # - the last column of the group has a line after; or if
            # - this group is the last of the table and the table has a line on its right
            if column.vlineAfter || column.columns[end].vlineAfter || column == columns[end] && tableParam.rightVline
                tableHeader *= "|"
            end

            tableHeader *= raw"""}{\textbf{""" * column.groupName * "}} & "
        else
            if containColumnGroups
                tableHeader *= raw"""\multirow{2}{*}{\textbf{\mbox{\textbf{""" * join(column.columnName) * "}}}} & "
            else
                tableHeader *= raw"""\textbf{\mbox{\textbf{""" * join(column.columnName) * "}}} & "
            end
        end
    end

    # If there are column groups, the table header contains a second row
    if containColumnGroups
        tableHeader *= raw"""\\\\"""
        tableHeader *= "\n"

        for i in 1:length(rowVariables)
            tableHeader *= "&"
        end

        if tableParam.expandInstances
            tableHeader *= "&"
        end

        for column in columns
            if typeof(column) == ColumnGroup
                for col in column.columns
                    tableHeader *= raw""" \mbox{\textbf{""" * col.columnName * "}} & "
                end
            else
                tableHeader *= "&"
            end
        end
    end

    tableHeader *= raw"""\\\hline""" * "\n\n"

    return tableHeader
end

"""
Test if any resolution methods used to compute the value of a column has missing values for an instance.

Input
- column: the column
- instanceResults: the result of the considered instance
"""
function hasMissingResults(column::Column, instanceResults::InstanceResults)

    hasMissingResults = false

    # For each resolution method used in the column
    for valueInfo in column.valInfos
        if haskey(instanceResults.methodResults, valueInfo.resolutionMethod)

            # If the instance has missing values for this method
            if !instanceResults.methodResults[valueInfo.resolutionMethod].hasAllResults
                hasMissingResults = true
            end
        end
    end

    return hasMissingResults
end

"""
Add to a String which represents the current row of a table the value of a vector of TableValue.
If the table starts before this row, print the table header.
If the table ends after this row, print the table footer.

Input
- tableValues: the value to add to the row
- mustReprintHeader: true if the header must be printed before the row;
- outputstream: stream in which the row must be printed
- tableaHeader: header of the table
- latexRow: the string of the result row which already included the value dedicated to the row parameters
- rowCount: number of rows currently in the table
- tableHasMissingValues: true if the current table has missing values
- tableParam: parameters of the table
"""
function addResultsToRow(tableValues::Vector{TableValue}, mustReprintHeader::Bool, outputstream, tableHeader::String, latexRow::String, rowCount::Int, tableHasMissingValues::Bool, tableParam::TableParameters, containColumnGroups::Bool)

    if mustReprintHeader
        println(outputstream, tableHeader)
        mustReprintHeader = false
    end

    for tableValue in tableValues
        latexRow *= tableValue.displayedValue * " & "
        if tableValue.isMissingMethodResults
            tableHasMissingValues = true
        end
    end

    println(outputstream, latexRow * "\\\\\n\n")
    rowCount += 1
    latexRow = ""

    if rowCount >= tableParam.maxRowsInATable

        println(outputstream, getTableFooter(tableParam.caption, hasMissingValues=tableHasMissingValues))
        mustReprintHeader = true

        tableHasMissingValues = false
        rowCount = 1

        if containColumnGroups
            rowCount = 2
        end
    end

    return rowCount, mustReprintHeader, tableHasMissingValues
end

function displayStringDict(arg::Dict{String, Any})
    result = ""

    for (key, value) in arg
        result *= key * " = " * string(value) * "\n"
    end
    return result
end

max_stars_repo_name: ZacharieALES/AutoExpe

max_issues_repo_name: ZacharieALES/AutoExpe

max_forks_repo_name: ZacharieALES/AutoExpe


text:
# This file was generated, do not modify it. # hide
train, test = partition(eachindex(y), 0.70, shuffle=true); # 70:30 split
fit!(cmach, rows=train)
ŷ = predict(cmach, rows=test)
ŷ[1:3]

max_stars_repo_name: tlienart/MLJTutorials

max_issues_repo_name: tlienart/MLJTutorials

max_forks_repo_name: tlienart/MLJTutorials


text:
try
    @eval using Revise
catch
end

max_stars_repo_name: RePsychLing/Docker

max_issues_repo_name: RePsychLing/Docker

max_forks_repo_name: RePsychLing/Docker


text:
@testset "Random Notes Sequence"

    # Dummy sequence
    tpq = 960
    sixt = subdivision(16, tpq)
    snare = 0x26
    tom1 = 0x30
    tom3 = 0x2b

    m1 = Note(tom1, 100, 0, sixt)
    motif1 = [
    Note(tom1, 100, 0, sixt),
    Note(snare, 100, sixt, sixt),
    Note(snare, 100, 2sixt, sixt),
    Note(snare, 100, 3sixt, sixt),
    Note(snare, 100, 4sixt, sixt)
    ]
    motif2 = [
    Note(tom1, 100, 0, sixt),
    Note(snare, 100, sixt, sixt),
    Note(tom1, 100, 2sixt, sixt),
    Note(snare, 100, 3sixt, sixt),
    Note(snare, 100, 4sixt, sixt)
    ]
    motif3 = [
    Note(tom1, 100, 0, sixt),
    Note(snare, 100, sixt, sixt),
    Note(snare, 100, 2sixt, sixt),
    ]
    motif4 = [
    Note(tom1, 100, 0, sixt),
    Note(snare, 100, sixt, sixt),
    Note(snare, 100, 2sixt, sixt),
    Note(snare, 100, 3sixt, sixt),
    ]

    motifs = Notes.([motif1, motif2, motif3, motif4], tpq)

    for q = [20, 30]
        notes = random_notes_sequence(motifs, q*tpq)

        @test length(notes) == 4*q
        @test Int(notes[end].position+notes[end].duration) == q*tpq
    end

    @test_throws ArgumentError random_notes_sequence(motifs, 50)
end


text:
function sxt_badpix_merge(l2evtfilelist::String)
   evtfiles = readlines(l2evtfilelist)
   # Read fits event files
   f=Array{FITS}(length(evtfiles))
   for i=1:length(evtfiles)
     f[i]=FITS(evtfiles[i], "r+")
     write_key(f[i][2],"TFORM10","1I")
     write_key(f[i][4],"TFORM5","1I")
     close(f[i])
   end
   f=FITS.(evtfiles, "r+")
   badpixlist=Array{TableHDU}(length(evtfiles))
   sxt_badpix_mergedf=Array{DataFrame}(length(evtfiles))
   for i=1:length(f)
     badpixlist[i]=f[i][4]
     badpix_df[i]=sxtbadpixlist2df(badpixlist[i])
     write_key(f[i][2],"TFORM10","16X")
     write_key(f[i][4],"TFORM5","16X")
     close(f[i])
   end
   badpixlist_merged=vcat(df)
   unique!(badpixlist_merged)
   return badpixlist_merged
 end

max_stars_repo_name: gulabd/SXTMerger

max_issues_repo_name: gulabd/SXTMerger


text:
@testset "'LiuScore' ........................... " begin

  ss = SampleSpace(5:20, 60)
  prior = p -> Distributions.pdf(Distributions.Beta(3 , 5), p)
  params = LiuScore(ss, .2, .3, prior, .05, .2, 2.0, 0.2)
  design = optimaldesign(15, params, solver)
  @test power(design, .2) <= .05

end

max_stars_repo_name: kkmann/binaryTwoStageDesigns

max_issues_repo_name: kkmann/binaryTwoStageDesigns

max_forks_repo_name: kkmann/binaryTwoStageDesigns


text:
using Test
using Quon
using Yao

function yb_fidelity(a1, b1, c1)
    circ1 = chain(1, Rz(-a1*im), Rx((-b1*im)), Rz(-c1*im))
    (a2, b2, c2) = yang_baxter_param(a1, b1, c1)
    circ2 = chain(1, Rx(-a2*im), Rz((-b2*im)), Rx(-c2*im))

    return operator_fidelity(matblock(mat(circ1') * mat(circ2)), I2)
end

a1 = rand()*π*im
b1 = rand()*π*im
c1 = rand()*π*im
a1, b1, c1 = rand(ComplexF64, 3)

a2, b2, c2 = yang_baxter_param(a1, b1, c1)
yb_fidelity(a1, b1, c1)

@test yb_fidelity(a1, b1, c1) ≈ 1
@test yb_fidelity(a2, b2, c2) ≈ 1

c1 = -a1
a2, b2, c2 = yang_baxter_param(a1, b1, c1)
@test yb_fidelity(a1, b1, c1) ≈ 1
@test yb_fidelity(a2, b2, c2) ≈ 1

c1 = π*im - a1
a2, b2, c2 = yang_baxter_param(a1, b1, c1)
@test yb_fidelity(a1, b1, c1) ≈ 1
@test yb_fidelity(a2, b2, c2) ≈ 1

@test_throws ErrorException yang_baxter_param(change_direction(pi/3*im), pi/3*im, change_direction(pi/3*im))


text:
# Save some bytes by using ints for switch variable
const UpdateObservable = "0"
const OnjsCallback = "1"
const EvalJavascript = "2"
const JavascriptError = "3"
const JavascriptWarning = "4"

const JSCall = "5"
const JSGetIndex = "6"
const JSSetIndex = "7"
const JSDoneLoading = "8"
const FusedMessage = "9"

"""
    request_to_sessionid(request; throw = true)

Returns the session and browser id from request.
With throw = false, it can be used to check if a request
contains a valid session/browser id for a websocket connection.
Will return nothing if request is invalid!
"""
function request_to_sessionid(request; throw = true)
    if length(request.target) >= 1 + 36 + 1 + 3 + 1 # for /36session_id/4browser_id/
        session_browser = split(request.target, "/", keepempty = false)
        if length(session_browser) == 2
            sessionid, browserid = string.(session_browser)
            if length(sessionid) == 36 && length(browserid) == 4
                return sessionid, browserid
            end
        end
    end
    if throw
        error("Invalid sessionid: $(request.target)")
    else
        return nothing
    end
end

function dom2html(session::Session, sessionid::String, dom)
    return sprint() do io
        dom2html(io, session, sessionid, dom)
    end
end

function dom2html(io::IO, session::Session, sessionid::String, dom)
    js_dom = DOM.div(jsrender(session, dom), id="application-dom")
    # register resources (e.g. observables, assets)
    register_resource!(session, js_dom)

    html = repr(MIME"text/html"(), Hyperscript.Pretty(js_dom))

    print(io, """
        <html>
        <head>
        """
    )
    # Insert all script/css dependencies into the header
    serialize_readable(io, session.dependencies)
    # insert the javascript for JSCall
    print(io, """
        <script>
            window.js_call_session_id = '$(sessionid)';
        """)
    if !isempty(server_proxy_url[])
        print(io, """
            window.websocket_proxy_url = '$(server_proxy_url[])';
        """)
    end
    println(io, "    </script>")

    serialize_readable(io, MsgPackLib)
    serialize_readable(io, JSCallLibLocal)

    println(io, """
        <script>
        function __on_document_load__(){
    """)
    # create a function we call in body onload =, which loads all
    # on_document_load javascript
    serialize_readable(io, session.on_document_load)
    queued_as_script(io, session)
    print(io, """
        };
        </script>
        """
    )

    print(io, """
        </head>
        <body"""
    )
    # insert on document load
    print(io, " onload = '__on_document_load__()'")
    println(io, html)
    print(io, """
        </body>
        </html>
        """
    )
end

include("mimetypes.jl")

function file_server(context)
    path = context.request.target
    if haskey(AssetRegistry.registry, path)
        filepath = AssetRegistry.registry[path]
        if isfile(filepath)
            header = ["Access-Control-Allow-Origin" => "*",
                      "Content-Type" => file_mimetype(filepath)]
            return HTTP.Response(200, header, body = read(filepath))
        end
    end
    return HTTP.Response(404)
end

function html(body)
    return HTTP.Response(200, ["Content-Type" => "text/html"], body = body)
end

function response_404(body = "Not Found")
    return HTTP.Response(404, ["Content-Type" => "text/html"], body = body)
end

"""
    handle_ws_message(session::Session, message)

Handles the incoming websocket messages from the frontend
"""
function handle_ws_message(session::Session, message)
    isempty(message) && return
    data = MsgPack.unpack(message)
    typ = data["msg_type"]
    if typ == UpdateObservable
        registered, obs = session.observables[data["id"]]
        @assert registered # must have been registered to come from frontend
        # update observable without running into cycles (e.g. js updating obs
        # julia updating js, ...)
        Base.invokelatest(update_nocycle!, obs, data["payload"])
    elseif typ == JavascriptError
        @error "Error in Javascript: $(data["message"])\n with exception:\n$(data["exception"])"
    elseif typ == JavascriptWarning
        @warn "Error in Javascript: $(data["message"])\n)"
    elseif typ == JSDoneLoading
        session.on_websocket_ready(session)
    else
        @error "Unrecognized message: $(typ) with type: $(typeof(type))"
    end
end

"""
    wait_timeout(condition, error_msg, timeout = 5.0)
Wait until `condition` function returns true. If running out of time throws `error_msg`!
"""
function wait_timeout(condition::Function, error_msg::String, timeout = 5.0)
    start_time = time()
    while !condition()
        sleep(0.001)
        if (time() - start_time) > timeout
            error(error_msg)
        end
    end
    return
end

function handle_ws_connection(session::Session, websocket::WebSocket)
    # TODO, do we actually need to wait here?!
    wait_timeout(()-> isopen(websocket), "Websocket not open after waiting 5s")
    while isopen(websocket)
        try
            handle_ws_message(session, read(websocket))
        catch e
            # IOErrors
            if !(e isa WebSockets.WebSocketClosedError || e isa Base.IOError)
                @warn "error in websocket handler!" exception=e
            end
        end
    end
    close(session)
end

"""
    handles a new websocket connection to a session
"""
function websocket_handler(context, websocket::WebSocket)
    request = context.request; application = context.application
    sessionid_browserid = request_to_sessionid(request, throw=true)
    sessionid, browserid = sessionid_browserid
    # Look up the connection in our sessions
    if haskey(application.sessions, sessionid)
        browser_sessions = application.sessions[sessionid]
        session = browser_sessions["base"]
        # We can have multiple sessions for a client
        push!(session, websocket)
        handle_ws_connection(session, websocket)
    else
        error("Unregistered session id: $sessionid. Sessions: $(collect(keys(application.sessions)))")
    end
end


text:
using Test
using EarthModels

@testset "1D models" begin
    @testset "Find layer" begin
        @test_throws DomainError EarthModels.findlayer(PREM, -1)
        @test_throws DomainError EarthModels.findlayer(PREM, 1e6)
    end
end