text:
module X86Assembly

data = """
PUSH:
  FF /6               M   V/V
    PUSH r/m16
  FF /6               M   N/V
    PUSH r/m32
  FF /6               M   V/N
    PUSH r/m64
  50+rw               O   V/V
    PUSH r16
  50+rd               O   N/V
    PUSH r32
  50+rd               O   V/N
    PUSH r64
  6A ib               I   V/V
    PUSH imm8
  68 iw               I   V/V
    PUSH imm16
  68 id               I   V/V
    PUSH imm32
  0E                  ZO  I/V
    PUSH CS
  16                  ZO  I/V
    PUSH SS
  1E                  ZO  I/V
    PUSH DS
  06                  ZO  I/V
    PUSH ES
  0F A0               ZO  V/V
    PUSH FS
  0F A8               ZO  V/V
    PUSH GS
POP:
  8F /0               M   V/V
    POP r/m16
  8F /0               M   N/V
    POP r/m32
  8F /0               M   V/N
    POP r/m64
  58+rw               O   V/V
    POP r16
  58+rd               O   N/V
    POP r32
  58+rd               O   V/N
    POP r64
  1F                  ZO  I/V
    POP DS
  07                  ZO  I/V
    POP ES
  17                  ZO  I/V
    POP SS
  0F A1               ZO  V/V
    POP FS
  0F A1               ZO  N/V
    POP FS
  0F A1               ZO  V/N
    POP FS
  0F A9               ZO  V/V
    POP GS
  0F A9               ZO  N/V
    POP GS
  0F A9               ZO  V/N
    POP GS
MOV:
  88 /r               MR  V/V
    MOV r/m8, r8
  REX 88 /r           MR  V/N
    MOV r/m8, r8
  89 /r               MR  V/V
    MOV r/m16, r16
  89 /r               MR  V/V
    MOV r/m32, r32
  REX.W 89 /r         MR  V/N
    MOV r/m64, r64
  8A /r               RM  V/V
    MOV r8,r/m8
  REX 8A /r           RM  V/N
      MOV r8,r/m8
  8B /r               RM  V/V
      MOV r16,r/m16
  8B /r               RM  V/V
      MOV r32,r/m32
  REX.W 8B /r         RM  V/V
      MOV r64,r/m64
  8C /r               MR  V/V
      MOV r/m16,Sreg
  REX.W 8C /r         MR  V/V
      MOV r/m64,Sreg
  8E /r               RM  V/V
      MOV Sreg,r/m16
  REX.W 8E /r         RM  V/V
      MOV Sreg,r/m64
  A0                  FD  V/V
      MOV AL,moffs8
  REX.W A0            FD  V/N
      MOV AL,moffs8
  A1                  FD  V/V
      MOV AX,moffs16
  A1                  FD  V/V
      MOV EAX,moffs32
  REX.W A1            FD  V/N
      MOV RAX,moffs64
  A2                  TD  V/V
      MOV moffs8,AL
  REX.W A2            TD  V/N
      MOV moffs8,AL
  A3                  TD  V/V
      MOV moffs16,AX
  A3                  TD  V/V
      MOV moffs32,EAX
  REX.W A3            TD  V/N
      MOV moffs64,RAX
  B0 rb ib            OI  V/V
      MOV r8, imm8
  REX B0 rb ib        OI  V/N
      MOV r8, imm8
  B8 rw iw            OI  V/V
      MOV r16, imm16
  B8 rd id            OI  V/V
      MOV r32, imm32
  REX.W B8 rd io      OI  V/N
      MOV r64, imm64
  C6 /0 ib            MI  V/V
      MOV r/m8, imm8
  REX C6 /0 ib        MI  V/N
      MOV r/m8, imm8
  C7 /0 iw            MI  V/V
      MOV r/m16, imm16
  C7 /0 id            MI  V/V
      MOV r/m32, imm32
  REX.W C7 /0 id      MI  V/N
      MOV r/m64, imm32
RET:
  C3                  ZO  V/V
      RET
  CB                  ZO  V/V
      RET
  C2 iw               I   V/V
      RET imm16
  CA iw               I   V/V
      RET imm16
ADDSS:
  F3 0F 58 /r                         RM   V/V
      ADDSS xmm1, xmm2/m32
  VEX.NDS.128.F3.0F.WIG 58 /r         RVM  V/V
      VADDSS xmm1, xmm2, xmm3/m32
  EVEX.NDS.LIG.F3.0F.W0 58 /r         T1S  V/V
      VADDSS xmm1{k1}{z}, xmm2, xmm3/m32{er}
"""

# Corner cases:
# Q: What happens when there's an optional prefix after a mandatory prefix?
#     e.g. F3 66 0F 58 ED
# A: Get's decoded as as if prefixes reversed
#
# Q: Can an instruction meaningfully have more than one group 1 prefix?
#     e.g. F0 F2 64 83 0C 25 08 03 00 00 10 or
#          F2 F0 64 83 0C 25 08 03 00 00 10 or
# A: Yes, this gets interpreted as xacquire/xrelease
#
# Q: What happens with multiple group 2 prefixes?
#     e.g. 0x26 0x2e 0x8b 0x45 0x20
#       or 0x2e 0x26 0x8b 0x45 0x20
# A: The prefix closest to the opcode prevails
#
# Q: What happens when mandatory prefixes are in conflict?
#     e.g. 0xF2 0x66 0x0F 0x12 0xED vs
#          0x66 0xF2 0x0F 0x12 0xED
#
# A: F2/F3 Takes precedence
#
# Q: What happens with legacy prefixes on (E)VEX instructions?
#
# See architecture manual Vol 2. Prefixes LOCK, 66/F2/F3, REX will #UD,
# segment overrides and 67h are allowed

struct InstrProperties
    # Has support for embedded rounding control
    er::Bool
    # Has support for suppress-all-exceptions
    sae::Bool
    # Has support for zero-merging option
    z::Bool
    # Has support for masking
    masking::Bool
    # Operand encoding category
    enc::String
end

basic_table = Dict{UInt8,Any}()
table_f20f = Dict{UInt8,Any}()
table_f30f = Dict{UInt8,Any}()
table_660f = Dict{UInt8,Any}()

# First level is indexed by vex 11 bits:
# L'LWppmmmm
const vex_opcode_table = Dict{UInt16,Any}()
const evex_opcode_table = Dict{UInt16,Any}()
lines = split(data, '\n')
i = 1

function add_to_table!(table, vex_byte, opc, data)
    if !haskey(table, vex_byte)
        table[vex_byte] = Dict{UInt8,Any}()
    end
    table[vex_byte][opc] = data
end

function add_to_table_W!(table, base_vex, opc, data, W)
    if W in ["W0", "WIG"]
        add_to_table!(table, base_vex, opc, data)
    end
    if W in ["W1", "WIG"]
        add_to_table!(table, base_vex | (1 << 8), opc, data)
    end
end

while i <= length(lines)
    line = lines[i]
    isempty(line) && (i += 1; continue)
    if line[1] != ' '
        i += 1; continue
    end
    range = search(line, "  ", 3)
    opcodes = split(strip(line[1:first(range)-1], ' '), ' ')
    enc  = split(strip(line[last(range)+1:end]),' ')[1]
    desc = strip(lines[i+1])
    j = 1
    begin
      idx = findfirst(desc, ' ')
      ops = map(strip, split(desc[idx+1:end], ','))
      ops = map(ops) do op
          op[1] == 'r' ? 0 :
          op[1] == 'x' ? 1 :
          2
      end
    end
    er = contains(desc, "{er}")
    sae = contains(desc, "{sae}") || contains(desc, "{er}")
    z = contains(desc, "{z}")
    masking = contains(desc, "{k1}")
    data = (desc, ops, InstrProperties(er, sae, z, masking, enc))
    while true
        opcode = opcodes[j]
        if startswith(opcode, "REX")
            j += 1
            continue
        end
        if startswith(opcode, "VEX") || startswith(opcode, "EVEX")
            parts = split(opcode, '.')
            @assert parts[1] in ["VEX", "EVEX"]
            @assert parts[2] in ["NDS","NDD","DDS"]
            @assert (parts[1] == "EVEX" && parts[3] == "512") ||
              parts[3] in ["128","256","LIG"]
            @assert parts[4] in ["66","F2","F3"]
            @assert parts[5] in ["0F","0F3A","0F38"]
            @assert parts[6] in ["W0","W1","WIG"]
            base_vex =  parts[4] == "66" ? 0b01000000 :
                        parts[4] == "F3" ? 0b10000000 :
                                           0b11000000
            base_vex |= parts[5] == "0F" ?       0b01 :
                        parts[5] == "0F3A" ?     0b11 :
                                                 0b10
            j += 1
            opc = parse(Int, opcodes[j], 16)
            table = parts[1] == "EVEX" ? evex_opcode_table : vex_opcode_table
            if parts[3] in ["128","LIG"]
                add_to_table_W!(table, base_vex, opc, data, parts[6])
            end
            if parts[3] in ["256","LIG"]
                add_to_table_W!(table, base_vex | (1<<9), opc, data, parts[6])
            end
            if (parts[1] == "EVEX") && parts[3] in ["512","LIG"]
                add_to_table_W!(table, base_vex | (0b10<<9), opc, data, parts[6])
                add_to_table_W!(table, base_vex | (0b11<<9), opc, data, parts[6])
            end
            break
        end
        if contains(opcode, "+")
          base, addend = split(opcode,'+')
          opc_base = parse(UInt8, base, 16)
          for opc in opc_base:opc_base+7
            basic_table[opc] = data
          end
        else
          opc = parse(UInt8, opcode, 16)
          this_opcode_table = basic_table
          at_start = true
          while length(opcodes) >= j+1
              j += 1
              next_opcode = opcodes[j]
              if next_opcode[1] in ['i', 'r', '/']
                  break
              end
              last_opc = opc
              opc = parse(UInt8, next_opcode, 16)
              # Mandatory prefixes
              if at_start && (last_opc == 0xF2 || last_opc == 0xF3 || last_opc == 0x66)
                  @assert opc == 0x0F
                  this_opcode_table = last_opc == 0xF2 ? table_f20f :
                                      last_opc == 0xF3 ? table_f30f :
                                                         table_660f
                  j += 1
                  opc = parse(UInt8, opcodes[j], 16)
                  at_start = false
                  continue
              end
              at_start = false
              if !haskey(this_opcode_table, last_opc)
                  this_opcode_table[last_opc] = Dict{UInt8,Any}()
              end
              this_opcode_table = this_opcode_table[last_opc]
          end
          if opcodes[j] != "/0" && opcodes[j] != "/6" # Skip for now
            this_opcode_table[opc] = data
          end
        end
        break
    end
    i += 2
end

simplef = [
  0x55,                     # push   %rbp
  0x48, 0x89, 0xe5,         # mov    %rsp,%rbp
  0x89, 0x7d, 0xfc,         # mov    %edi,-0x4(%rbp)
  0x48, 0x89, 0x75, 0xf0,   # mov    %rsi,-0x10(%rbp)
  0x8b, 0x45, 0xfc,         # mov    -0x4(%rbp),%eax
  0x5d,                     # pop    %rbp
  0xc3,                     # retq
]

struct Operand{S, T}
    deref::Bool
    # -1%UInt8 indicates absolute
    base::S
    scale::S
    index::S
    disp::T
end
representative(op::Operand{UInt8, <:Integer}) = op

struct QualifiedOperand
    # 0 - general purpose
    # 1 - vector
    # 2 - other
    category::UInt8
    op::Operand
end

struct AVX512Operand
    # (-1)%UInt8: None by instr def
    mask
    zero_combining
    # Suppres all exceptions
    sae
    # Static round overwrite
    er
    rounding_mode
end
representative(op::AVX512Operand) = op

function Base.show(io::IO, op::AVX512Operand)
    op = representative(op)
    (op.mask != -1%UInt8 && op.mask != 0) && print(io, "{k",op.mask,"}")
    op.zero_combining != 0 && print(io, "{z}")
    if op.er != 0
      @assert op.rounding_mode <= 0b11
      print(io, op.rounding_mode == 0b00 ? "{rn-sae}" :
                  op.rounding_mode == 0b01 ? "{rd-sae}" :
                  op.rounding_mode == 0b10 ? "{ru-sae}" :
                                             "{rz-sae}")
    elseif op.sae != 0
        print(io, "{sae}")
    end
end

gpregs = ["rax", "rcx", "rdx", "rbx", "rsp", "rbp", "rsi", "rdi",
          "r8",  "r9", "r10", "r11", "r12", "r13", "r14", "r15"]

function print_reg(io::IO, category, reg)
    if category == 0
        print(io, gpregs[reg+1])
    elseif category == 1
        print(io, "xmm$reg")
    else
        print(io, "<unknown register category>")
    end
end

function Base.show(io::IO, op::Operand, category=0)
    op = representative(op)
    if !op.deref
        print_reg(io, category, op.base)
    elseif op.base == -1%UInt8
        print(io, "[", op.disp, "]")
    else
        print(io, "(")
        print_reg(io, category, op.base)
        print(io, " ", op.scale, "*")
        print_reg(io, category, op.index)
        print(io, " + ", op.disp, ")")
    end
end

function Base.show(io::IO, op::QualifiedOperand)
    show(io, op.op, op.category)
end

macro invalid(reason)
    :(return nothing)
end

# This is produced by the (hand-coded) fast prefix decoder and consumed by
# the general instruction decoder
struct prefix_status{T}
    nprefix_bytes::UInt8
    first_f2f3::UInt8
    last_f2f3::UInt8
    last_valid_seg_prefix::UInt8
    has_lock::Bool
    has_66h::Bool
    has_67h::Bool
    rex::T
    first_non_prefix_byte::T
end

# Compress prefix flags into a bitwise representation
#
# ffllssso67rrrrr
#  | |\|/|||\_ REX bytes, 0 = no prefix, 1X for 0x4X
#  | | | ||\__ Saw 0x67 prefix
#  | | | |\___ Saw 0x66 prefix
#  | | | \____ Saw LOCK prefix
#  | | \______ Last valid segment prefix:
#  | |          000: None
#  | |          001: 0x2E  010: 0x26
#  | |          011: 0x3E  100: 0x36
#  | |          101: 0x64  110: 0x65
#  | \________ Last F2/F3 Prefix (0: none; 01: F2; 10: F3)
#  \__________ First F2/F3 Prefix (0: none; 01: F2; 10: F3)

struct compressed_prefix_status{T,S}
    nprefix_bytes::UInt8
    flags::T
    first_non_prefix_byte::S
end

rex_bits(c) = c.flags & 0b11111
sssbits(c) = (c.flags >> 8) & 0b111
last_f2f3(c) = (c.flags >> 11) & 0b11
first_f2f3(c) = (c.flags >> 13) & 0b11
function validate_compressed_prefix(c)
    if rex_bits(c) in collect(0x1:0x8)
        return false
    elseif sssbits(c) == 0b111
        return false
    elseif last_f2f3(c) == 0b11 || first_f2f3(c) == 0b11 || (
        (last_f2f3(c) == 0 && first_f2f3(c) != 0) ||
        (first_f2f3(c) == 0 && last_f2f3(c) != 0)
      )
        return false
    end
    return true
end

const MAX_INSTR_BYTES = 15

compress_f2f3(f2f3) = f2f3 == 0xf2 ? 0b01 :
                      f2f3 == 0xf3 ? 0b10 :
                                     0b00

compress_seg_prefix(sp) = sp == 0x00 ? 0b000 :
                          sp == 0x2E ? 0b001 :
                          sp == 0x26 ? 0b010 :
                          sp == 0x3E ? 0b011 :
                          sp == 0x36 ? 0b100 :
                          sp == 0x64 ? 0b101 :
                          sp == 0x65 ? 0b110 :
                          error()

function compressed_prefix_status(nprefix_bytes, first_f2f3, last_f2f3, last_valid_seg_prefix,
              has_lock, has_66h, has_67h, rex, first_non_prefix_byte)
    flags = UInt16(compress_f2f3(first_f2f3))                     << 13 |
            UInt16(compress_f2f3(last_f2f3))                      << 11 |
            UInt16(compress_seg_prefix(last_valid_seg_prefix))    <<  8 |
            UInt8(has_lock)                                       <<  7 |
            UInt8(has_66h)                                        <<  6 |
            UInt8(has_67h)                                        <<  5 |
            (rex & 0xF0) >> 2  | (rex & 0x0F)
    compressed_prefix_status(nprefix_bytes, flags, first_non_prefix_byte)
end

# This is shared between the fast and the slow path - performance matters
@inline function _fast_prefix_scanner(prefix_status, input, mode)
    nprefix_bytes::UInt8 = 0
    first_f2f3::UInt8 = 0
    last_f2f3::UInt8 = 0
    last_valid_seg_prefix::UInt8 = 0
    has_lock::Bool = 0
    has_66h::Bool = false
    has_67h::Bool = false
    rex::UInt8 = 0
    first_non_prefix_byte::UInt8 = 0
    @assert mode.Lflag
    while !eof(input) && nprefix_bytes < MAX_INSTR_BYTES
        c::UInt8 = read(input, UInt8)
        if c == 0x66
            # Operand size override prefix
            has_66h = true
            rex = 0
        elseif c == 0x67
            # Address size override prefix
            has_67h = true
            rex = 0
        elseif c == 0x2E || c == 0x3E || c == 0x26 || c == 0x36
            # Legacy segment prefixes - ignored in 64bit mode
            !(mode.Lflag) && (last_valid_seg_prefix = c)
            rex = 0
        elseif c == 0x64 || c == 0x65
            last_valid_seg_prefix = c
            rex = 0
        elseif c == 0xF0
            has_lock = true
            rex = 0
        elseif c == 0xF2 || c == 0xF3
            first_f2f3 == 0 && (first_f2f3 = c)
            last_f2f3 = c
        elseif mode.Lflag && 0x40 <= c <= 0x4F
            rex = c
        else
            first_non_prefix_byte = c
            break
        end
    end
    prefix_status(nprefix_bytes, first_f2f3, last_f2f3, last_valid_seg_prefix,
                  has_lock, has_66h, has_67h, rex, first_non_prefix_byte)
end
function fast_compressed_prefix_scanner(io, mode = processor_modes(true,true))
    _fast_prefix_scanner(compressed_prefix_status, io, mode)
end
fast_prefix_scanner(io, mode = processor_modes(true, true)) =
    _fast_prefix_scanner(prefix_status, io, mode)

rex_w(rex) = (rex & 0b1000)
rex_b(rex) = ((rex&0b001) << 3)
rex_x(rex) = ((rex&0b010) << 2)
rex_r(rex) = ((rex&0b100) << 1)
rex_r′(rex) = ((rex&0b10000000) >> 4)

readImmediate(input::IO, T) = read(input, T)

# Returns (Reg, R/M)
function parseModRM(rex, io, evex = false)
    modrmb = read(io, UInt8)
    mod = (0b11000000 & modrmb) >> 6
    reg = (0b00111000 & modrmb) >> 3
    rm  = (0b00000111 & modrmb)
    op1 = Operand(false, reg | rex_r(rex) | (rex_r′(rex) << 1), UInt8(0), UInt8(0), 0)
    if mod == 0b11
        (evex || rex_x(rex) == 0) || @invalid "Only allowed with evex encoding"
        op2 = Operand(false, rm | rex_b(rex) | (rex_x(rex) << 1), UInt8(0), UInt8(0), 0)
        return (op1, op2)
    end
    op2_base::typeof(rm) = rm | rex_b(rex)
    load_scale::typeof(rm) = 1
    disp::Int32 = 0
    sib_index::typeof(rm) = 0
    if rm == 0b100
        # With sib byte
        sib = read(io, UInt8)
        sib_base = (sib & 0b111)
        op2_base = sib_base | rex_b(rex)
        load_scale = ((sib & 0b11000000) >> 6)
        sib_index = (sib & 0b00111000) >> 3 | rex_x(rex)
        if sib_index == 0b11 # index = sp special case
            load_scale = 0
        end
        if mod == 0b00 && sib_base == 0b101 # base = bp special case
            disp = readImmediate(io, UInt32)%Int32
            op2_base = -1 % UInt8
        end
    end
    if mod == 0b00
        if rm == 0b101
            disp = readImmediate(io, UInt32)%Int32
            op2 = Operand(true, -1%UInt8, 1, 0, disp)
            return (op1, op2)
        end
    elseif mod == 0b01
        disp = Int32(readImmediate(io, UInt8)%Int8)
    elseif mod == 0b10
        disp = readImmediate(io, UInt32)%Int32
    end
    op2 = Operand(true, op2_base, sib_index, load_scale, disp)
    (op1, op2)
end

macro maybe(expr)
    x = gensym()
    esc(quote
        $x = $expr
        $x === nothing && return nothing
        $x
    end)
end

function parseOperands(enc,rex,vex,opc,ops,data,evex)
    if enc == "MR"
        a,b = @maybe parseModRM(rex, data, evex)
        (QualifiedOperand(ops[1],b), QualifiedOperand(ops[2],a))
    elseif enc == "RM"
        a,b = @maybe parseModRM(rex, data, evex)
        (QualifiedOperand(ops[1],a), QualifiedOperand(ops[2],b))
    elseif enc == "RVM" || enc == "T1S"
        a,b = @maybe parseModRM(rex, data, evex)
        op2 = Operand(false, vex, UInt8(0), UInt8(0), 0)
        (QualifiedOperand(ops[1],a), QualifiedOperand(ops[2],op2), QualifiedOperand(ops[3],b))
    elseif enc == "O"
        (QualifiedOperand(ops[1],
          Operand(false, (opc & 0b111)|rex_b(rex), 0%UInt8, 0%UInt8, 0%UInt32)),)
    elseif enc == "OI"
      (QualifiedOperand(ops[1],
        Operand(false, (opc & 0b111)|rex_b(rex), 0%UInt8, 0%UInt8, 0%UInt32)),
        readImmediate(data, UInt32))
    elseif enc == "ZO"
        ()
    elseif enc == "FD" || enc == "TD"
        #TODO: These two are wrong
        (readImmediate(data, UInt32),)
    elseif enc == "I"
        (readImmediate(data, UInt32),)
    else
        error("Unimplemented encoding '$enc'")
    end
end

struct processor_modes
    Dflag::Bool
    Lflag::Bool
end

had_f2f3(prefix) = prefix.last_f2f3 != 0
had_f2f3(prefix::compressed_prefix_status) = last_f2f3(prefix) != 0
had_66(prefix) = prefix.has_66h
had_66(prefix::compressed_prefix_status) = ((prefix.flags >> 6) & 0b1) != 0
was_lastf2(prefix) = prefix.last_f2f3 == 0xF2
was_lastf2(prefix::compressed_prefix_status) = last_f2f3(prefix) == 0b01
was_lastf3(prefix) = prefix.last_f2f3 == 0xF3
was_lastf3(prefix::compressed_prefix_status) = last_f2f3(prefix) == 0b10
make_rex(prefix) = prefix.rex
make_rex(prefix::compressed_prefix_status) = (rex_bits(prefix) | ((rex_bits(prefix) & 0b10000) << 2)) % UInt8

function dispatch_simple_op(input, prefix, opc)
    table = basic_table
    next_opc::typeof(opc) = 0
    if opc == 0x0F && (had_f2f3(prefix) != 0 || had_66(prefix))
        next_opc = read(input, UInt8)
        table = table[opc]
        if was_lastf2(prefix)
            if haskey(table_f20f, next_opc)
                table = table_f20f
            end
        elseif was_lastf3(prefix)
            if haskey(table_f30f, next_opc)
                table = table_f30f
            end
        elseif had_66(prefix)
            if haskey(table_660f, next_opc)
                table = table_660f
            end
        end
        opc = next_opc
    end
    !haskey(table, opc) && @invalid "Opcode not in table"
    table_or_data = table[opc]
    while isa(table_or_data, Dict)
        # table, not data
        opc = read(input, UInt8)
        !haskey(table_or_data, opc) && @invalid "Opcode not in table"
        table_or_data = table_or_data[opc]
    end
    # data, not table
    desc, ops, data = table_or_data
    operands = @maybe parseOperands(data.enc, make_rex(prefix), 0, opc, ops, input, false)
    return sprint() do io
      print(io, desc, " ")
      println(io, operands)
    end
end

concretize!(x::UInt8) = x

parseOpc(data::Vector{UInt8}, mode = processor_modes(true,true)) =
  parseOpc(IOBuffer(data), mode)

function parseOpc(input::IO, mode = processor_modes(true,true))
    # Only supported for far
    @assert mode.Lflag
    operand_size = 4
    address_size = 8
    prefix_status = fast_prefix_scanner(input)
    _parseOpc(input, prefix_status, mode)
end

function validate_legacy_prefixes_for_vex(prefix)
    prefix.last_f2f3 == 0 || @invalid "No legacy prefixes allowed with (e)vex"
    !prefix.has_66h || @invalid "No operand size override allowed with (e)vex"
    !prefix.has_lock || @invalid "No lock prefix allowed with (e)vex"
    prefix.rex == 0 || @invalid "No REX prefix allowed with (e)vex"
    true
end

function validate_legacy_prefixes_for_vex(prefix::compressed_prefix_status)
    #                 ffllssso67rrrrr
    (prefix.flags & 0b111100011011111) == 0 || @invalid "Disallowed prefixes"
    true
end

function _parseOpc(input::IO, prefix, mode)
    c = prefix.first_non_prefix_byte
    if c == 0xc4
        @maybe validate_legacy_prefixes_for_vex(prefix)
        # 3-byte VEX prefix
        V2 = read(input, UInt8)
        (V2 & 0b1100 == 0) || @invalid "Invalid value for mmmm"
        V3 = read(input, UInt8)
        L = UInt16((V3 & 0b100) >> 2)
        W = UInt16((V3 & 0b10000000) >> 7)
        vex_byte = UInt16((V3 & 0b11) << 6) | UInt16(V2 & 0b0011) | L << 9 | W << 8
        fake_rex = ~((V2 & 0b11100000) >> 5)
        vvvv = ~((V3 & 0b01111000) >> 3) & 0b1111
        opc =  read(input, UInt8)
        # Our generation scheme is naive and doesn't realize that rex_x
        # matters. Tell it manually for now.
        haskey(vex_opcode_table, vex_byte) || @invalid "Invalid opcode"
        haskey(vex_opcode_table[vex_byte], opc) || @invalid "Invalid opcode"
        desc, ops, data = vex_opcode_table[vex_byte][opc]
        operands = @maybe parseOperands(data.enc, fake_rex, vvvv, opc, ops, input, false)
        return sprint() do io
          print(io, desc, " ")
          println(io, operands)
        end
    elseif c == 0xc5
        @maybe validate_legacy_prefixes_for_vex(prefix)
        # 2-byte VEX prefix
        V2 = read(input, UInt8)
        L = UInt16((V2 & 0b100) >> 2)
        vex_byte = UInt16((V2 & 0b11) << 6) | 0b1 | L << 9
        fake_rex = ((~V2 & 0b10000000) >> 5)
        vvvv = ~((V2 & 0b01111000) >> 3) & 0b1111
        opc = read(input, UInt8)
        # Our generation scheme is naive and doesn't realize that rex_x
        # matters. Tell it manually for now.
        haskey(vex_opcode_table, vex_byte) || @invalid "Invalid opcode"
        haskey(vex_opcode_table[vex_byte], opc) || @invalid "Invalid opcode"
        desc, ops, data = vex_opcode_table[vex_byte][opc]
        operands = @maybe parseOperands(data.enc, fake_rex, vvvv, opc, ops, input, false)
        return sprint() do io
          print(io, desc, " ")
          println(io, operands)
        end
    elseif c == 0x62
        @maybe validate_legacy_prefixes_for_vex(prefix)
        # 4-byte EVEX prefix
        P0 = read(input, UInt8)
        P1 = read(input, UInt8)
        P2 = read(input, UInt8)
        z = (P2 & 0b10000000) >> 7
        b = (P2 & 0b00010000) >> 4
        aaa = (P2 & 0b00000111)
        L′L = UInt16((P2 & 0b01100000) >> 5)
        W = UInt16((P1 & 0b10000000) >> 7)
        vex_byte = UInt16(P0 & 0b11) | UInt16((P1 & 0b11) << 6) | L′L << 9 | W << 8
        # We encode R′ in the high bit of fake rex
        fake_rex = ~((P0 >> 5) | (P0 & 0b00010000) << 3)
        vvvv = (((~P1 & 0b01111000) >> 3) | ((~P2 & 0b1000) << 1))
        opc = read(input, UInt8)
        haskey(evex_opcode_table, vex_byte) || @invalid "Invalid opcode"
        haskey(evex_opcode_table[vex_byte], opc) || @invalid "Invalid opcode"
        desc, ops, data = evex_opcode_table[vex_byte][opc]
        avx512o = AVX512Operand(aaa, z, (UInt8(data.sae) & b), (UInt8(data.er) & b),
          ((UInt8(data.er) & b) != 0) ? L′L : 0)
        operands = @maybe parseOperands(data.enc, fake_rex, vvvv, opc, ops, input, true)
        return sprint() do io
          print(io, desc, " ")
          println(io, tuple(avx512o, operands...))
        end
    else
        return dispatch_simple_op(input, prefix, c)
    end
    return nothing
end

parseAllOpc(data::Vector{UInt8}) = parseAllOpc(IOBuffer(data))
function parseAllOpc(input::IO)
    while !eof(input)
        x = parseOpc(input)
        @assert x !== nothing
        println(x)
    end
end

end # module


text:
mutable struct DragState
    dragging::Bool
    down::Bool

    position_down::Geometry.Point{Float64}
    position_up::Geometry.Point{Float64}

    position::Geometry.Point{Float64}
    delta::Geometry.Point{Float64}

    map_point::Geometry.Point{Float64}

    function DragState()
        o = new()
        o.position_down = Geometry.Point{Float64}()
        o.position_up = Geometry.Point{Float64}()
        o.delta = Geometry.Point{Float64}()
        o.position = Geometry.Point{Float64}()
        o.map_point = Geometry.Point{Float64}()
        o.dragging = false
        o.down = false
        o
    end
end

function is_dragging(state::DragState)
    state.dragging
end

function set_motion_state!(drag::DragState, x::Int32, y::Int32, node::Ranger.AbstractNode)
    if drag.down
        drag.dragging = true
        # We need to map to parent space for dragging because the parent may contain
        # a scaling factor. Using view-space will result in drifting from scale difference.
        Nodes.map_device_to_node!(node.base.world, x, y, node.base.parent, drag.map_point)

        Geometry.set!(drag.delta, drag.map_point.x - drag.position.x, drag.map_point.y - drag.position.y)
        Geometry.set!(drag.position, drag.map_point.x, drag.map_point.y)
    else
        drag.dragging = false
    end
end

function set_button_state!(drag::DragState,
        x::Int32, y::Int32,
        button::UInt8, state::UInt8,
        node::Ranger.AbstractNode)
    drag.down = button == 1 && state == 1

    Nodes.map_device_to_node!(node.base.world, x, y, node.base.parent, drag.map_point)

    if drag.down
        Geometry.set!(drag.position_down, drag.map_point.x, drag.map_point.y)
        Geometry.set!(drag.position, drag.position_down)
    else
        Geometry.set!(drag.position_up, drag.map_point.x, drag.map_point.y)
    end
end

function set_motion_state!(state::DragState, x::Float64, y::Float64)
    if state.down
        state.dragging = true
        Geometry.set!(state.delta, x - state.position.x, y - state.position.y)
        Geometry.set!(state.position, x, y)
    else
        state.dragging = false
    end
end

function set_button_state!(drag::DragState, x::Float64, y::Float64, button::UInt8, state::UInt8)
    drag.down = button == 1 && state == 1

    if drag.down
        Geometry.set!(drag.position_down, x, y)
        Geometry.set!(drag.position, drag.position_down)
    else
        Geometry.set!(drag.position_up, x, y)
    end
end

ext: jl

max_stars_repo_name: wdevore/Ranger-Julia-SDL

max_issues_repo_name: wdevore/Ranger-Julia-SDL

max_forks_repo_name: wdevore/Ranger-Julia-SDL


text:
"""
    send(callback::Union{Function,Void}, tgt::Target, method::String ; args...)

Asynchronous version of `send`, returns immediatly. When Chromium responds,
the `callback` function is called with the response as parameter.
"""
function send(callback::Union{Function,Void},
              tgt::AbstractTarget, method::String ; args...)
  command_id = length(chromiumHandle.id2callbacks) == 0 ? 1 :
                  maximum(keys(chromiumHandle.id2callbacks)) + 1

  stringuri = @sprintf("%s", tgt.chromiumWebSocket)
  msg = Dict{Any,Any}( t[1] => t[2] for t in args )
  wrappermsg = Dict("command" => "send",
                    "session" => stringuri,
                    "payload" => Dict(:id => command_id,
                                      :method => method,
                                      :params => msg))

  chromiumHandle.id2callbacks[command_id] = callback

  put!(chromiumHandle.outchan, JSON.json(wrappermsg))
  nothing
end

type TimeoutError <: Exception end

"""
    send(tgt::Target, method::String ; args...)

Synchronous version of `send`, returns only when Chromium responds. The keyword
argument `timeout` (default 5 sec) controls how much time we should wait for
Chromium to respond before raising a TimeoutError.
"""
function send(tgt::AbstractTarget, method::String ; timeout=5, args...)
  endcond = Condition()

  Timer(t -> notify(endcond, TimeoutError()), timeout)

  send(tgt, method; args...) do resp
    ret = if haskey(resp, "error")
            ErrorException(resp["error"]["message"])
          else
            resp
          end
    notify(endcond, ret)
  end

  ret = wait(endcond)
  isa(ret, Exception) && throw(ret)
  ret
end


text:
using NeuralOperators
using Test
using Flux

@testset "NeuralOperators.jl" begin
    include("Transform/Transform.jl")
    include("operator_kernel.jl")
    include("model.jl")
    include("deeponet.jl")
end

#=
    　 ／l、    meow?
    ﾞ（ﾟ､ ｡ ７
    　l、ﾞ ~ヽ
    　じしf_, )ノ
=#


text:
@symbol_func function phi_FT(cur_reactor::AbstractReactor)
  cur_phi = V_loop(cur_reactor)

  cur_phi *= cur_reactor.tau_FT

  cur_phi
end


text:
using ConstraintSolver, JuMP, GLPK
CS = ConstraintSolver

function main(filename; benchmark = false, time_limit = 100)
    lp_optimizer = optimizer_with_attributes(GLPK.Optimizer, "msg_lev" => GLPK.GLP_MSG_OFF)
    m = Model(optimizer_with_attributes(
        CS.Optimizer,
        "time_limit" => time_limit,
        "lp_optimizer" => lp_optimizer,
    ))

    lines = readlines(filename)
    num_colors = 0
    x = nothing
    max_color = nothing
    degrees = nothing
    for line in lines
        isempty(line) && continue
        parts = split(line)
        if parts[1] == "p"
            num_colors = parse(Int, parts[3])
            @variable(m, 1 <= max_color <= num_colors, Int)
            @variable(m, 1 <= x[1:num_colors] <= num_colors, Int)
            degrees = zeros(Int, num_colors)
        elseif parts[1] == "e"
            f = parse(Int, parts[2])
            t = parse(Int, parts[3])
            @constraint(m, x[f] != x[t])
            degrees[f] += 1
            degrees[t] += 1
        end
    end
    max_degree = maximum(degrees)

    println("max_degree: ", max_degree)
    println("num_colors: ", num_colors)
    @constraint(m, max_color <= max_degree)

    @constraint(m, max_color .>= x)
    @objective(m, Min, max_color)

    optimize!(m)

    status = JuMP.termination_status(m)
    if status == MOI.OPTIMAL
        print("$status, $(JuMP.objective_value(m)), $(JuMP.solve_time(m))")
    else
        print("$status, NaN, $(time_limit)")
    end
end


text:
using Term.Progress
import Term.Progress: AbstractColumn, getjob, get_columns
import Term: install_term_logger, uninstall_term_logger

using ProgressLogging

@testset "\e[34mProgress - jobs" begin
    pbar = ProgressBar()

    j1 = addjob!(pbar; description = "test", N = 10)

    @test j1.id == 1
    @test j1.N == 10
    @test j1.i == 1
    @test j1.description == "test"
    @test j1.started
    @test j1.columns isa Vector{AbstractColumn}

    update!(j1)
    @test j1.i == 2
    update!(j1; i = 10)
    @test j1.i == 12

    @test getjob(pbar, j1.id).id == j1.id

    removejob!(pbar, j1)
    @test length(pbar.jobs) == 0
end

@testset "\e[34mProgress basic" begin
    @test_nothrow begin
        pbar = ProgressBar()
        with(pbar) do
            job = addjob!(pbar; N = 10)
            for i in 1:10
                update!(job)
                sleep(0.001)
            end
        end
    end

    @test_nothrow begin
        pbar2 = ProgressBar(; transient = true)
        with(pbar2) do
            job = addjob!(pbar2; N = 10)
            job2 = nothing
            for i in 1:10
                i == 50 && (job2 = addjob!(pbar2; N = 10))
                i ≥ 50 && update!(job2)
                i == 75 && removejob!(pbar2, job2)

                update!(job)
                sleep(0.001)
            end
        end
    end

    @test_nothrow begin
        @track for i in 1:10
            sleep(0.001)
        end
    end
end

@testset "\e[34mProgress columns" begin
    for colinfo in (:minimal, :default, :spinner, :detailed)
        pbar = ProgressBar(; columns = colinfo)
        @test pbar.columns == get_columns(colinfo)
        @test pbar.columns isa Vector{DataType}

        job = addjob!(pbar; N = colinfo == :spinner ? nothing : 10)
        @test job.columns isa Vector{AbstractColumn}

        with(pbar) do
            for i in 1:10
                update!(job)
                sleep(0.01)
            end
        end
    end

    colkwargs = Dict(:DescriptionColumn => Dict(:style => "red"))
    pbar = ProgressBar(; columns_kwargs = colkwargs)
    job = addjob!(pbar; N = 10)
    @test job.columns[1].segments[1].text == "\e[31mRunning...\e[39m"
end

# @testset "\e[34mProgress ProgressLogging" begin

#     install_term_logger()

#     @test_nothrow begin
#         @progress "inner... $i" for j in  1:10
#             sleep(0.01)
#         end
#     end
# end


text:
const hrep_doc = """
Return the equivalent halfspace representation of the convex hull, i.e.
matrix ``A`` and vector ``b`` such that the set of points inside the hull
is

```math
\\left\\{ x \\mid A x \\le b \\right\\}
```
"""

"""
$(SIGNATURES)

$hrep_doc

If `hull` is backed by a statically sized vector of vertices, the output `(A, b)`
will be statically sized as well. If the vector of vertices is additionally
immutable (e.g., a `StaticArrays.SVector`), then `hrep` will not perform any
dynamic memory allocation.
"""
hrep(hull::ConvexHull) = _hrep(hull, Length(vertices(hull)))

function _hrep(hull::ConvexHull, ::Length{N}) where N
    T = eltype(hull)
    R = arithmetic_closure(T)
    vertices = hull.vertices
    if N === StaticArrays.Dynamic()
        n = length(vertices)
        A = similar(vertices, R, n, 2)
        b = similar(vertices, R, n)
        hrep!(A, b, hull)
        return A, b
    else
        Amut = similar(vertices, R, Size(N, 2))
        bmut = similar(vertices, R, Size(N))
        hrep!(Amut, bmut, hull)
        A = convert(similar_type(vertices, R, Size(Amut)), Amut)
        b = convert(similar_type(vertices, R, Size(bmut)), bmut)
        return A, b
    end
end

"""
$(SIGNATURES)

$hrep_doc

This function stores its output in the (mutable) matrix `A` and vector `b`.
"""
@inline function hrep!(A::AbstractMatrix, b::AbstractVector, hull::ConvexHull)
    signop = edge_normal_sign_operator(hull)
    vertices = hull.vertices
    n = length(vertices)
    @boundscheck begin
        size(A, 1) >= n || throw(DimensionMismatch())
        size(A, 2) == 2 || throw(DimensionMismatch())
        size(A, 1) == length(b) || throw(DimensionMismatch)
    end
    if length(b) != n
        @inbounds A .= 0
        @inbounds b .= 0
    end
    @inbounds @simd for i = Base.OneTo(n)
        v1 = vertices[i]
        v2 = vertices[ifelse(i == n, 1, i + 1)]
        δ = v2 - v1
        δx, δy = unpack(δ)
        outward_normal = signop(SVector(δy, -δx))
        Ai = transpose(outward_normal)
        bi = Ai * v1
        A[i, 1] = Ai[1]
        A[i, 2] = Ai[2]
        b[i] = bi
    end
    nothing
end


text:
using AWS
using AWS.AWSExceptions: AWSException
using AWS.AWSServices: s3
using AWSS3
using Test
using Dates
using Retry
using HTTP
using FilePathsBase
using FilePathsBase: /, join
using FilePathsBase.TestPaths
using UUIDs: uuid4
using JSON3

is_aws(config) = config isa AWSConfig

# Load the test functions
include("s3path.jl") # creates `awss3_tests(config)`
include("awss3.jl") # creates `s3path_tests(config)`

@testset "AWSS3.jl" begin
    if VERSION >= v"1.5"
        using Minio
        AWS.aws_account_number(::Minio.MinioConfig) = "123"

        # We run most tests under Minio. This can be done locally by those
        # without access to the s3 bucket under which CI is performed.
        # We then run all tests with s3 directly.

        port = 9005
        minio_server = Minio.Server([mktempdir()]; address="localhost:$port")

        try
            run(minio_server; wait=false)
            sleep(0.5)  # give the server just a bit of time, though it is amazingly fast to start
            config = global_aws_config(
                MinioConfig(
                    "http://localhost:$port"; username="minioadmin", password="minioadmin"
                ),
            )
            @testset "Minio" begin
                awss3_tests(config)
                s3path_tests(config)
            end
        finally
            # Make sure we kill the server even if a test failed.
            kill(minio_server)
        end
    end

    # Set `AWSConfig` as the default for the following tests
    aws = global_aws_config(AWSConfig())
    @testset "S3" begin
        awss3_tests(aws)
        s3path_tests(aws)
    end
end


text:
# deprecated in 0.7:

export displaymode

function displaymode(; format = display_params.format,
                    decorations = display_params.decorations,
                    sigfigs::Integer = display_params.sigfigs)
    Base.depwarn("`displaymode(format=f)` is deprecated. Use `setformat(f)` instead.", :setformat)

    setformat(format; decorations=decorations, sigfigs=sigfigs)
end

@deprecate infimum inf
@deprecate supremum sup
@deprecate interval_part interval


text:
global kRPCBuild = true
include("../src/kRPC.jl") #load in the library; will load because build initialized
println("Trying to connect to KSP to initialize library; game must be running and kRPC started.")

conn = kRPC.kRPCConnect("kRPC.jl build process")


text:
module ExtendedVariantModeExtraJumpRefill

using ..Ahorn, Maple

@mapdef Entity "ExtendedVariantMode/ExtraJumpRefill" ExtraJumpRefill(x::Integer, y::Integer,
    extraJumps::Int=1, capped::Bool=false, cap::Int=-1, oneUse::Bool=false, texture::String="ExtendedVariantMode/jumprefillblue", respawnTime::Number=2.5)

const placements = Ahorn.PlacementDict(
    "Extra Jump Refill (Extended Variant Mode)" => Ahorn.EntityPlacement(
        ExtraJumpRefill
    )
)

function Ahorn.selection(entity::ExtraJumpRefill)
    sprite = "objects/" * get(entity.data, "texture", "ExtendedVariantMode/jumprefillblue") * "/idle00"
    x, y = Ahorn.position(entity)
    return Ahorn.getSpriteRectangle(sprite, x, y)
end

function Ahorn.render(ctx::Ahorn.Cairo.CairoContext, entity::ExtraJumpRefill, room::Maple.Room)
    sprite = "objects/" * get(entity.data, "texture", "ExtendedVariantMode/jumprefillblue") * "/idle00"
    Ahorn.drawSprite(ctx, sprite, 0, 0)
end

end

max_stars_repo_name: Kokasgui/Everest-ExtendedVariants

max_issues_repo_name: Kokasgui/Everest-ExtendedVariants

max_forks_repo_name: Kokasgui/Everest-ExtendedVariants


text:
export groupby

# --------- Group and aggregate --------------------------------
#
# want to support
#
# groupby(iter, n, (:GeneId,); Sum = Sum(:Width), Avg = Avg(:Width))
#
# n = 0 genome
# n > 0 bp in chrom, i.e.  Pos % n
#
# groupby(::iter, ::binsize, ::groupcols; kwargs...)
#
# needs to go through whole ::group, then report stats
#

using DataStructures: DefaultDict

struct GroupBy{I, ET}
    iter::I
    n::Int64
    groupcols::Vector
    aggregates::Vector{Pair}
end

# output of groupby is
# (Chrom, bpStart, bpEnd, GC1, ..., Agg1, ...)
#
# what to do if aggregate returns struct type? Should we flatten it?

Base.IteratorEltype(::Type{GroupBy{T,ET}}) where {T,ET} = Base.HasEltype()
Base.eltype(::Type{GroupBy{T,ET}}) where {T,ET} = ET
Base.IteratorSize(::Type{GroupBy{T,ET}}) where {T,ET} = Base.SizeUnknown()

Tables.istable(g::GroupBy{T,ET}) where {T,ET} = true
Tables.rowaccess(g::GroupBy{T,ET}) where {T,ET} = true
Tables.schema(g::GroupBy{T,ET}) where {T,ET} = Tables.Schema(ET)

function groupby_(iter, n = 0, groupcols = []; aggregates...)
    # find eltype
    # (Chrom, bpStart, bpEnd, GC1, ..., AggVal1, ...)
    sch = Tables.schema(iter)
    names2types = Dict(zip(sch.names, sch.types))
    names = Symbol[:Chrom, :bpStart, :bpEnd]
    types = [String, Int64, Int64]
    for col in groupcols
        push!(names, col)
        push!(types, names2types[col])
    end
    for (k,v) in aggregates
        push!(names, k)
        push!(types, eltype(v))
    end
    ET = NamedTuple{Tuple(names), Tuple{types...}}

    GroupBy{typeof(iter), ET}(iter, n, groupcols, collect(aggregates))
end

"""
    rows |> groupby(n=0, groupcols = []; aggregates...)

Group genome ordered stream by position window and `groupcols`. Summarize groups using `aggregates`.

For each window and combination of grouping columns, compute the online-statistics specified.

# Arguments

- `n::Int` : group by window of size `n`, genomewide for `n=0`
- `groupcols::Vector{Symbol}`: additional grouping columns
- `aggregates` : online statistics to compute
"""
groupby(n=0, groupcols = []; aggregates...) = rows -> groupby_(rows, n, groupcols; aggregates...)

"""
    genomicbin(g::GroupBy, row)

return genomic bin `(chrom, start, end)` to which `row` belongs
(based on `g.n`)
"""
function genomicbin(g::GroupBy, row)
    if g.n == 0
        bin = ("genome", 0, 3000000000)
    else
        idx = div(row[2], g.n)
        bin = (row[1], idx * g.n, (idx+1)*g.n - 1)
    end
    bin
end

"extract values of g.groupcols from row"
groupkey(g::GroupBy, row) = Tuple([row[col] for col in g.groupcols])

# bin: genomic bin
# groupcols: group columns
# statsdict: Dict{groupkeys -> Dict{Symbol -> Aggregator}}
#
# output [(Chrom, bpStart, bpEnd, <Groupcol1 value>, ..., <Aggregator1 value>, ...)]
function create_aggregator_summary(bin, groupcols, statsdict, ET)
    res = ET[]
    tmp1 = (Chrom = bin[1], bpStart = bin[2], bpEnd = bin[3])
    for (groupkeys, aggdict) in statsdict
        tmp2 = (;zip(groupcols, groupkeys)...)
        tmp3 = (;zip(first.(aggdict), value.(last.(aggdict)))...)
        push!(res, ET((tmp1..., tmp2..., tmp3...)))
    end
    res
end

function advance_group(g::GroupBy{I, ET}, row_iterstate) where {I,ET}

    # determine bin to create statistics for
    bin = genomicbin(g, row_iterstate[1])

    # reset aggregators
    statsdict = DefaultDict(() -> deepcopy(g.aggregates))

    # loop through g.iter, updating aggregators,
    # until next bin or end of strem encountered
    while true

        key = groupkey(g, row_iterstate[1])
        fit!(statsdict[key], row_iterstate[1])

        row_iterstate = iterate(g.iter, row_iterstate[2])
        row_iterstate == nothing && break

        nextbin = genomicbin(g, row_iterstate[1])
        nextbin != bin && break

    end

    stats = create_aggregator_summary(bin, g.groupcols, statsdict, ET)
    (row_iterstate, stats, 1)

end

function Base.iterate(g::GroupBy)
    row_iterstate = iterate(g.iter)
    row_iterstate == nothing && return nothing

    state = row_iterstate, [], 1
    iterate(g, state)
end

function Base.iterate(g::GroupBy, state)
    row_iterstate, stats, statsidx = state

    while true

        if statsidx <= length(stats)
            return stats[statsidx], (row_iterstate, stats, statsidx + 1)
        end

        row_iterstate == nothing && return nothing

        row_iterstate, stats, statsidx = advance_group(g, row_iterstate)

    end
end


text:
using Test
using GraphQL

function testLexerInitializesStartOfFile()
  lexer = Lexer("")
  @test lexer.currentToken.kind === Symbol("<SOF>")
end

function testLexerDefaultQuery()
  lexer = Lexer(DEFAULT_QUERY)
  @test lexer.currentToken.kind === Symbol("<SOF>")
  advanceLexer(lexer)
  @test_skip lexer.currentToken.kind === Symbol("String") # query
  advanceLexer(lexer)
  @test_skip lexer.currentToken.kind === Symbol("String") # Ping
  advanceLexer(lexer)
  @test_skip lexer.currentToken.kind === Symbol("String") # healthcheck
  advanceLexer(lexer)
  @test_skip lexer.currentToken.kind === Symbol("String") # ping
end

@testset "Language" begin
  @info "Running language tests..."
  testLexerInitializesStartOfFile()
  testLexerDefaultQuery()
end


text:
using OutputCollectors
using Test

# Output of a few scripts we are going to run
const simple_out = "1\n2\n3\n4\n"
const long_out = join(["$(idx)\n" for idx in 1:100], "")
const newlines_out = join(["marco$(d)polo$(d)" for d in ("\n","\r","\r\n")], "")

# Helper function to strip out color codes from strings to make it easier to
# compare output within tests that has been colorized
function strip_colorization(s)
    return replace(s, r"(\e\[\d+m)"m => "")
end

# Helper function to strip out log timestamps from strings
function strip_timestamps(s)
    return replace(s, r"^(\[\d\d:\d\d:\d\d\] )"m => "")
end

@testset "OutputCollectors.jl" begin
    @test_logs (:warn, r"Could not spawn") @test_throws Base.IOError OutputCollector(`does not exist`)

    if Sys.isunix()
        oc = OutputCollector(`echo`; verbose = true)
        @test merge(oc) == "\n"

        red = Base.text_colors[:red]
        def = Base.text_colors[:default]

        stdout_msg = "Print to stdout"
        stderr_msg = "Print to stderr"
        cmd = """
              echo $(stdout_msg)
              echo $(stderr_msg) > /dev/stderr
              """
        oc = OutputCollector(`sh -c $cmd`)
        @test merge(oc) == stdout_msg * "\n" * stderr_msg * "\n"
        @test merge(oc; colored = true) == stdout_msg * "\n" * red * stderr_msg * "\n" * def
        @test tail(oc; len = 1) == stderr_msg * "\n"
        @test tail(oc; len = 2, colored = true) == red * stderr_msg * "\n" * def
        @test collect_stdout(oc) == stdout_msg * "\n"
        @test collect_stderr(oc) == stderr_msg * "\n"

        cd("output_tests") do
            # Collect the output of `simple.sh``
            oc = OutputCollector(`sh ./simple.sh`)

            # Ensure we can wait on it and it exited properly
            @test wait(oc)

            # Ensure further waits are fast and still return 0
            let
                tstart = time()
                @test wait(oc)
                @test time() - tstart < 0.1
            end

            # Test that we can merge properly
            @test merge(oc) == simple_out

            # Test that merging twice works
            @test merge(oc) == simple_out

            # Test that `tail()` gives the same output as well
            @test tail(oc) == simple_out

            # Test that colorization works
            gt = "1\n$(red)2\n$(def)3\n4\n"
            @test merge(oc; colored=true) == gt
            @test tail(oc; colored=true) == gt

            # Test that we can grab stdout and stderr separately
            @test collect_stdout(oc) == "1\n3\n4\n"
            @test collect_stderr(oc) == "2\n"
        end

        # Next test a much longer output program
        cd("output_tests") do
            oc = OutputCollector(`sh ./long.sh`)

            # Test that it worked, we can read it, and tail() works
            @test wait(oc)
            @test merge(oc) == long_out
            @test tail(oc; len=10) == join(["$(idx)\n" for idx in 91:100], "")
        end

        # Next, test a command that fails
        cd("output_tests") do
            oc = OutputCollector(`sh ./fail.sh`)

            @test !wait(oc)
            @test merge(oc) == "1\n2\n"
        end

        # Next, test a command that kills itself (NOTE: This doesn't work on windows.  sigh.)
        @static if !Sys.iswindows()
            cd("output_tests") do
                oc = OutputCollector(`sh ./kill.sh`)

                @test !wait(oc)
                @test collect_stdout(oc) == "1\n2\n"
            end
        end

        # Next, test reading the output of a pipeline()
        grepline = pipeline(
            `sh -c 'printf "Hello\nWorld\nJulia\n"'`,
            `sh -c 'while read line; do case $line in *ul*) echo $line; esac; done'`
        )
        oc = OutputCollector(grepline)

        @test wait(oc)
        @test merge(oc) == "Julia\n"

        # Next, test that \r and \r\n are treated like \n
        cd("output_tests") do
            oc = OutputCollector(`sh ./newlines.sh`)

            @test wait(oc)
            @test collect_stdout(oc) == newlines_out
        end

        # Next, test that tee'ing to a stream works
        cd("output_tests") do
            ios = IOBuffer()
            oc = OutputCollector(`sh ./simple.sh`; tee_stream=ios, verbose=true)
            @test wait(oc)
            @test merge(oc) == simple_out

            seekstart(ios)
            tee_out = String(read(ios))
            tee_out = strip_colorization(tee_out)
            tee_out = strip_timestamps(tee_out)
            @test tee_out == simple_out
        end

        # Also test that auto-tail'ing can be can be directed to a stream
        cd("output_tests") do
            ios = IOBuffer()
            oc = OutputCollector(`sh ./fail.sh`; tee_stream=ios)

            @test !wait(oc)
            @test merge(oc) == "1\n2\n"
            seekstart(ios)
            tee_out = String(read(ios))
            tee_out = strip_colorization(tee_out)
            tee_out = strip_timestamps(tee_out)
            @test tee_out == "1\n2\n"
        end

        # Also test that auto-tail'ing can be turned off
        cd("output_tests") do
            ios = IOBuffer()
            oc = OutputCollector(`sh ./fail.sh`; tee_stream=ios, tail_error=false)

            @test !wait(oc)
            @test merge(oc) == "1\n2\n"

            seekstart(ios)
            @test String(read(ios)) == ""
        end

    end
end


text:
module Duckietown

using YAML
using GymSpaces
using Distributions
using Random
using RayTracer
using Zygote
using LinearAlgebra

include("utils.jl")

include("graphics.jl")
using .Graphics

include("collision.jl")
include("objmesh.jl")
include("objects.jl")
include("map.jl")
include("randomization/randomizer.jl")
include("fixed_params.jl")
include("simulator.jl")

include("envs/duckietown_env.jl")
include("envs/multimap_env.jl")

export Simulator, step!, reset!, render_obs,
       DuckietownEnv, MultiMapEnv

end # module


text:
function ptdf_networkflow(
    psi_container::PSIContainer,
    branches::IS.FlattenIteratorWrapper{B},
    buses::IS.FlattenIteratorWrapper{PSY.Bus},
    expression::Symbol,
    PTDF::PSY.PTDF,
) where {B <: PSY.Branch}
    time_steps = model_time_steps(psi_container)
    network_flow =
        add_cons_container!(psi_container, :network_flow, PTDF.axes[1], time_steps)
    nodal_balance =
        add_cons_container!(psi_container, :nodal_balance, PTDF.axes[2], time_steps)
    nodal_balance_expressions = psi_container.expressions[expression]

    branch_types = typeof.(branches)

    remove_undef!(nodal_balance_expressions)

    for btype in Set(branch_types)
        typed_branches = IS.FlattenIteratorWrapper(
            btype,
            Vector([[b for b in branches if isa(b, btype)]]),
        )
        flow_variables!(psi_container, StandardPTDFModel, typed_branches)
    end

    for t in time_steps
        for br in branches
            flow_variable = get_variable(psi_container, FLOW_ACTIVE_POWER, typeof(br))
            name = PSY.get_name(br)
            flow_expression = sum(
                PTDF[name, PSY.get_number(b)] *
                nodal_balance_expressions[PSY.get_number(b), t] for b in buses
            )
            network_flow[name, t] = JuMP.@constraint(
                psi_container.JuMPmodel,
                flow_variable[name, t] == flow_expression
            )
        end

        # The process is done in two separate loops to avoid modifying the nodal_balance_expressions
        # before making the flow constraints. If this two operations are done in the same loop
        # then the PTDF will multiply an expression that contains the flow variable.
        for br in branches
            name = PSY.get_name(br)
            from_number = PSY.get_number(PSY.get_arc(br).from)
            to_number = PSY.get_number(PSY.get_arc(br).to)
            flow_variable = get_variable(psi_container, FLOW_ACTIVE_POWER, typeof(br))
            add_to_expression!(
                nodal_balance_expressions,
                from_number,
                t,
                flow_variable[name, t],
                -1.0,
            )
            add_to_expression!(
                nodal_balance_expressions,
                to_number,
                t,
                flow_variable[name, t],
                1.0,
            )

        end

        for b in buses
            number = PSY.get_number(b)
            nodal_balance[number, t] = JuMP.@constraint(
                psi_container.JuMPmodel,
                nodal_balance_expressions[number, t] == 0
            )
        end
    end
    return
end


text:
using LinearAlgebra

"""
    SGD(;lr=0.001,gclip=0)
    update!(w,g,p::SGD)
    update!(w,g;lr=0.001)

Container for parameters of the Stochastic gradient descent (SGD)
optimization algorithm used by [`update!`](@ref).

SGD is an optimization technique to minimize an objective function by
updating its weights in the opposite direction of their gradient. The
learning rate (lr) determines the size of the step.  SGD updates the
weights with the following formula:

    w = w - lr * g

where `w` is a weight array, `g` is the gradient of the loss function
w.r.t `w` and `lr` is the learning rate.

If `norm(g) > gclip > 0`, `g` is scaled so that its norm is equal
to `gclip`.  If `gclip==0` no scaling takes place.

SGD is used by default if no algorithm is specified in the two
argument version of `update!`[@ref].
"""
mutable struct SGD
    lr::AbstractFloat
    gclip::AbstractFloat
end

const SGDLR = 0.001

SGD(; lr=SGDLR, gclip=0) = SGD(lr,gclip)

@deprecate Sgd SGD

"""
    Momentum(;lr=0.001, gclip=0, gamma=0.9)
    update!(w,g,p::Momentum)

Container for parameters of the Momentum optimization algorithm used
by [`update!`](@ref).

The Momentum method tries to accelerate SGD by adding a velocity term
to the update.  This also decreases the oscillation between successive
steps. It updates the weights with the following formulas:

    velocity = gamma * velocity + lr * g
    w = w - velocity

where `w` is a weight array, `g` is the gradient of the objective
function w.r.t `w`, `lr` is the learning rate, `gamma` is the momentum
parameter, `velocity` is an array with the same size and type of `w`
and holds the accelerated gradients.

If `norm(g) > gclip > 0`, `g` is scaled so that its norm is equal
to `gclip`.  If `gclip==0` no scaling takes place.

Reference: [Qian,
N. (1999)](http://doi.org/10.1016/S0893-6080(98)00116-6). On the
momentum term in gradient descent learning algorithms.  Neural
Networks : The Official Journal of the International Neural Network
Society, 12(1), 145–151.

"""
mutable struct Momentum
    lr::AbstractFloat
    gclip::AbstractFloat
    gamma::AbstractFloat
    velocity
end

Momentum(; lr=0.001, gclip=0, gamma=0.9)=Momentum(lr, gclip, gamma, nothing)

"""
    Nesterov(; lr=0.001, gclip=0, gamma=0.9)
    update!(w,g,p::Momentum)

Container for parameters of Nesterov's momentum optimization algorithm used
by [`update!`](@ref).

It is similar to standard [`Momentum`](@ref) but with a slightly different update
rule:

    velocity = gamma * velocity_old - lr * g
    w = w_old - velocity_old + (1+gamma) * velocity

where `w` is a weight array, `g` is the gradient of the objective
function w.r.t `w`, `lr` is the learning rate, `gamma` is the momentum
parameter, `velocity` is an array with the same size and type of `w`
and holds the accelerated gradients.

If `norm(g) > gclip > 0`, `g` is scaled so that its norm is equal
to `gclip`.  If `gclip == 0` no scaling takes place.

Reference Implementation : [Yoshua Bengio, Nicolas Boulanger-Lewandowski and Razvan P
ascanu](https://arxiv.org/pdf/1212.0901.pdf)
"""
mutable struct Nesterov
    lr::AbstractFloat
    gclip::AbstractFloat
    gamma::AbstractFloat
    velocity
end

Nesterov(; lr=0.001, gclip=0, gamma=0.9) = Nesterov(lr, gclip, gamma, nothing)

"""
    Adagrad(;lr=0.1, gclip=0, eps=1e-6)
    update!(w,g,p::Adagrad)

Container for parameters of the Adagrad optimization algorithm used by
[`update!`](@ref).

Adagrad is one of the methods that adapts the learning rate to each of
the weights.  It stores the sum of the squares of the gradients to
scale the learning rate.  The learning rate is adapted for each weight
by the value of current gradient divided by the accumulated
gradients. Hence, the learning rate is greater for the parameters
where the accumulated gradients are small and the learning rate is
small if the accumulated gradients are large. It updates the weights
with the following formulas:

    G = G + g .^ 2
    w = w - g .* lr ./ sqrt(G + eps)

where `w` is the weight, `g` is the gradient of the objective function
w.r.t `w`, `lr` is the learning rate, `G` is an array with the same
size and type of `w` and holds the sum of the squares of the
gradients. `eps` is a small constant to prevent a zero value in the
denominator.

If `norm(g) > gclip > 0`, `g` is scaled so that its norm is equal
to `gclip`.  If `gclip==0` no scaling takes place.

Reference: [Duchi, J., Hazan, E., & Singer,
Y. (2011)](http://jmlr.org/papers/v12/duchi11a.html). Adaptive
Subgradient Methods for Online Learning and Stochastic Optimization.
Journal of Machine Learning Research, 12, 2121–2159.

"""
mutable struct Adagrad
    lr::AbstractFloat
    gclip::AbstractFloat
    eps::AbstractFloat
    G
end

Adagrad(; lr=0.1, gclip=0, eps=1e-6)=Adagrad(lr, gclip, eps, nothing)

"""
    Adadelta(;lr=0.01, gclip=0, rho=0.9, eps=1e-6)
    update!(w,g,p::Adadelta)

Container for parameters of the Adadelta optimization algorithm used by
[`update!`](@ref).

Adadelta is an extension of Adagrad that tries to prevent the decrease
of the learning rates to zero as training progresses. It scales the
learning rate based on the accumulated gradients like Adagrad and
holds the acceleration term like Momentum. It updates the weights with
the following formulas:

    G = (1-rho) * g .^ 2 + rho * G
    update = g .* sqrt(delta + eps) ./ sqrt(G + eps)
    w = w - lr * update
    delta = rho * delta + (1-rho) * update .^ 2

where `w` is the weight, `g` is the gradient of the objective function
w.r.t `w`, `lr` is the learning rate, `G` is an array with the same
size and type of `w` and holds the sum of the squares of the
gradients. `eps` is a small constant to prevent a zero value in the
denominator.  `rho` is the momentum parameter and `delta` is an array
with the same size and type of `w` and holds the sum of the squared
updates.

If `norm(g) > gclip > 0`, `g` is scaled so that its norm is equal
to `gclip`.  If `gclip==0` no scaling takes place.

Reference: [Zeiler,
M. D. (2012)](http://arxiv.org/abs/1212.5701). ADADELTA: An Adaptive
Learning Rate Method.

"""
mutable struct Adadelta
    lr::AbstractFloat
    gclip::AbstractFloat
    rho::AbstractFloat
    eps::AbstractFloat
    G
    delta
end

Adadelta(; lr=0.01, gclip=0, rho=0.9, eps=1e-6)=Adadelta(lr, gclip, rho, eps, nothing, nothing)

"""
    Rmsprop(;lr=0.001, gclip=0, rho=0.9, eps=1e-6)
    update!(w,g,p::Rmsprop)

Container for parameters of the Rmsprop optimization algorithm used by
[`update!`](@ref).

Rmsprop scales the learning rates by dividing the root mean squared of
the gradients. It updates the weights with the following formula:

    G = (1-rho) * g .^ 2 + rho * G
    w = w - lr * g ./ sqrt(G + eps)

where `w` is the weight, `g` is the gradient of the objective function
w.r.t `w`, `lr` is the learning rate, `G` is an array with the same
size and type of `w` and holds the sum of the squares of the
gradients. `eps` is a small constant to prevent a zero value in the
denominator.  `rho` is the momentum parameter and `delta` is an array
with the same size and type of `w` and holds the sum of the squared
updates.

If `norm(g) > gclip > 0`, `g` is scaled so that its norm is equal
to `gclip`.  If `gclip==0` no scaling takes place.

Reference: [Tijmen Tieleman and Geoffrey Hinton
(2012)](https://dirtysalt.github.io/images/nn-class-lec6.pdf). "Lecture
6.5-rmsprop: Divide the gradient by a running average of its recent
magnitude."  COURSERA: Neural Networks for Machine Learning 4.2.

"""
mutable struct Rmsprop
    lr::AbstractFloat
    gclip::AbstractFloat
    rho::AbstractFloat
    eps::AbstractFloat
    G
end

Rmsprop(; lr=0.001, gclip=0, rho=0.9, eps=1e-6)=Rmsprop(lr, gclip, rho, eps, nothing)

"""
    Adam(;lr=0.001, gclip=0, beta1=0.9, beta2=0.999, eps=1e-8)
    update!(w,g,p::Adam)

Container for parameters of the Adam optimization algorithm used by
[`update!`](@ref).

Adam is one of the methods that compute the adaptive learning rate. It
stores accumulated gradients (first moment) and the sum of the squared
of gradients (second).  It scales the first and second moment as a
function of time. Here is the update formulas:

    m = beta1 * m + (1 - beta1) * g
    v = beta2 * v + (1 - beta2) * g .* g
    mhat = m ./ (1 - beta1 ^ t)
    vhat = v ./ (1 - beta2 ^ t)
    w = w - (lr / (sqrt(vhat) + eps)) * mhat

where `w` is the weight, `g` is the gradient of the objective function
w.r.t `w`, `lr` is the learning rate, `m` is an array with the same
size and type of `w` and holds the accumulated gradients. `v` is an
array with the same size and type of `w` and holds the sum of the
squares of the gradients. `eps` is a small constant to prevent a zero
denominator. `beta1` and `beta2` are the parameters to calculate bias
corrected first and second moments. `t` is the update count.

If `norm(g) > gclip > 0`, `g` is scaled so that its norm is equal
to `gclip`.  If `gclip==0` no scaling takes place.

Reference: [Kingma, D. P., & Ba,
J. L. (2015)](https://arxiv.org/abs/1412.6980). Adam: a Method for
Stochastic Optimization. International Conference on Learning
Representations, 1–13.

"""
mutable struct Adam
    lr::AbstractFloat
    gclip::AbstractFloat
    beta1::AbstractFloat
    beta2::AbstractFloat
    eps::AbstractFloat
    t::Int
    fstm
    scndm
end

Adam(; lr=0.001, gclip=0, beta1=0.9, beta2=0.999, eps=1e-8)=Adam(lr, gclip, beta1, beta2, eps, 0, nothing, nothing)

"""
    update!(weights, gradients, params)
    update!(weights, gradients; lr=0.001, gclip=0)

Update the `weights` using their `gradients` and the optimization
algorithm parameters specified by `params`.  The 2-arg version
defaults to the [`SGD`](@ref) algorithm with learning rate `lr` and
gradient clip `gclip`.  `gclip==0` indicates no clipping. The
`weights` and possibly `gradients` and `params` are modified in-place.

`weights` can be an individual numeric array or a collection of arrays
represented by an iterator or dictionary.  In the individual case,
`gradients` should be a similar numeric array of `size(weights)` and
`params` should be a single object.  In the collection case, each
individual weight array should have a corresponding params object.
This way different weight arrays can have their own optimization
state, different learning rates, or even different optimization
algorithms running in parallel.  In the iterator case, `gradients` and
`params` should be iterators of the same length as `weights` with
corresponding elements.  In the dictionary case, `gradients` and
`params` should be dictionaries with the same keys as `weights`.

Individual optimization parameters can be one of the following
types. The keyword arguments for each type's constructor and their
default values are listed as well.

* [`SGD`](@ref)`(;lr=0.001, gclip=0)`
* [`Momentum`](@ref)`(;lr=0.001, gclip=0, gamma=0.9)`
* [`Nesterov`](@ref)`(;lr=0.001, gclip=0, gamma=0.9)`
* [`Rmsprop`](@ref)`(;lr=0.001, gclip=0, rho=0.9, eps=1e-6)`
* [`Adagrad`](@ref)`(;lr=0.1, gclip=0, eps=1e-6)`
* [`Adadelta`](@ref)`(;lr=0.01, gclip=0, rho=0.9, eps=1e-6)`
* [`Adam`](@ref)`(;lr=0.001, gclip=0, beta1=0.9, beta2=0.999, eps=1e-8)`

# Example:

    w = rand(d)                 # an individual weight array
    g = lossgradient(w)         # gradient g has the same shape as w
    update!(w, g)               # update w in-place with SGD()
    update!(w, g; lr=0.1)       # update w in-place with SGD(lr=0.1)
    update!(w, g, SGD(lr=0.1))  # update w in-place with SGD(lr=0.1)

    w = (rand(d1), rand(d2))    # a tuple of weight arrays
    g = lossgradient2(w)        # g will also be a tuple
    p = (Adam(), SGD())         # p has params for each w[i]
    update!(w, g, p)            # update each w[i] in-place with g[i],p[i]

    w = Any[rand(d1), rand(d2)] # any iterator can be used
    g = lossgradient3(w)        # g will be similar to w
    p = Any[Adam(), SGD()]      # p should be an iterator of same length
    update!(w, g, p)            # update each w[i] in-place with g[i],p[i]

    w = Dict(:a => rand(d1), :b => rand(d2)) # dictionaries can be used
    g = lossgradient4(w)
    p = Dict(:a => Adam(), :b => SGD())
    update!(w, g, p)

"""
function update! end

for T in (Array{Float32},Array{Float64},KnetArray{Float32},KnetArray{Float64}); @eval begin

    function update!(w::$T, g::$T, p::SGD)
        gclip!(g, p.gclip)
        axpy!(-p.lr, g, w)
    end

    # Two arg defaults to SGD
    function update!(w::$T, g::$T; lr=SGDLR, gclip=0)
        gclip!(g, gclip)
        axpy!(-lr, g, w)
    end

    function update!(w::$T, g::$T, p::Momentum)
        gclip!(g, p.gclip)
        if p.velocity===nothing; p.velocity=zero(w); end
        lmul!(p.gamma, p.velocity)
        axpy!(p.lr, g, p.velocity)
        axpy!(-1, p.velocity, w)
    end

    # https://arxiv.org/pdf/1212.0901.pdf Eq. (7)
    function update!(w::$T, g::$T, p::Nesterov)
        gclip!(g, p.gclip)
        p.velocity ===nothing && (p.velocity = zero(w))
        lmul!(p.gamma, p.velocity)
        axpy!(-1, p.velocity, w)
        axpy!(-p.lr, g, p.velocity)
        axpy!(1+p.gamma, p.velocity, w)
    end

    function update!(w::$T, g::$T, p::Adam)
        gclip!(g, p.gclip)
        if p.fstm===nothing; p.fstm=zero(w); p.scndm=zero(w); end
        p.t += 1
        lmul!(p.beta1, p.fstm)
        axpy!(1-p.beta1, g, p.fstm)
        lmul!(p.beta2, p.scndm)
        axpy!(1-p.beta2, g .* g, p.scndm)
        fstm_corrected = p.fstm / (1 - p.beta1 ^ p.t)
        scndm_corrected = p.scndm / (1 - p.beta2 ^ p.t)
        axpy!(-p.lr, (fstm_corrected ./ (sqrt.(scndm_corrected) .+ p.eps)), w)
    end

    function update!(w::$T, g::$T, p::Adagrad)
        gclip!(g, p.gclip)
        if p.G===nothing; p.G=zero(w); end
        axpy!(1, g .* g, p.G)
        axpy!(-p.lr, g ./ sqrt.(p.G .+ p.eps), w)
    end

    function update!(w::$T, g::$T, p::Adadelta)
        gclip!(g, p.gclip)
        if p.G===nothing; p.G=zero(w); p.delta=zero(w); end
        lmul!(p.rho, p.G)
        axpy!(1-p.rho, g .* g, p.G)
        dw = g .* sqrt.(p.delta .+ p.eps) ./ sqrt.(p.G .+ p.eps)
        lmul!(p.rho, p.delta)
        axpy!(1-p.rho, dw .* dw , p.delta)
        axpy!(-p.lr, dw, w)
    end

    function update!(w::$T, g::$T, p::Rmsprop)
        gclip!(g, p.gclip)
        if p.G===nothing; p.G=zero(w); end
        lmul!(p.rho, p.G)
        axpy!(1-p.rho, g .* g, p.G)
        axpy!(-p.lr, g ./ sqrt.(p.G .+ p.eps), w)
    end

    # If type of g does not match, something may be wrong
    update!(w::$T, g, p)=error("Gradient type mismatch: w::$(typeof(w)) g::$(typeof(g))")
    update!(w::$T, g; o...)=error("Gradient type mismatch: w::$(typeof(w)) g::$(typeof(g))")

    # AutoGrad may return Nothing for a zero gradient
    update!(w::$T, g::Nothing, p)=w
    update!(w::$T, g::Nothing; o...)=w

end; end

# AutoGrad may return Nothing for a zero gradient
update!(w, g::Nothing, p)=w
update!(w, g::Nothing; o...)=w

# This takes care of arrays, tuples, iterators in general.
function update!(w,g,p)
    if !(length(w)==length(g)==length(p))
        error("weight, gradient, and optimization parameters not the same length.")
    end
    if isbitstype(eltype(w))
        error("Bad args: $((typeof(w),typeof(g),typeof(p)))")
    end
    for (wi,gi,pi) in zip(w,g,p)
        update!(wi,gi,pi)
    end
end

# We still need an extra method for Dict.
function update!(w::AbstractDict,g::AbstractDict,p::AbstractDict)
    # g may have some keys missing!
    # if !(length(w)==length(g)==length(p))
    #     error("weight, gradient, and optimization parameters not the same length.")
    # end
    for k in keys(g)
        update!(w[k],g[k],p[k])
    end
end

# Two arg version defaults to SGD.
function update!(w,g;lr=SGDLR,gclip=0)
    if !(length(w)==length(g))
        error("weight, gradient not the same length.")
    end
    for (wi,gi) in zip(w,g)
        update!(wi,gi;lr=lr,gclip=gclip)
    end
end

# Two arg version defaults to SGD.
function update!(w::AbstractDict,g::AbstractDict;lr=SGDLR,gclip=0)
    # g may have some keys missing!
    # if !(length(w)==length(g))
    #     error("weight, gradient not the same length.")
    # end
    for k in keys(g)
        update!(w[k],g[k];lr=lr,gclip=gclip)
    end
end

function gclip!(g, gclip)
    if gclip == 0
        g
    else
        gnorm = norm(g)
        if gnorm <= gclip
            g
        else
            lmul!(gclip/gnorm, g)
        end
    end
end

"""
    optimizers(model, otype; options...)

Given parameters of a `model`, initialize and return corresponding
optimization parameters for a given optimization type `otype` and
optimization options `options`. This is useful because each numeric
array in model needs its own distinct optimization
parameter. `optimizers` makes the creation of optimization parameters
that parallel model parameters easy when all of them use the same type
and options.

"""
optimizers(::KnetArray{<:Number},otype; o...) = otype(;o...)
optimizers(::AbstractArray{<:Number},otype; o...) = otype(;o...)
optimizers(a::AbstractDict,otype; o...)=Dict([ k=>optimizers(v,otype;o...) for (k,v) in a ])
optimizers(a::Tuple,otype; o...)=map(x->optimizers(x,otype;o...), a)
optimizers(a::AbstractArray,otype; o...)=map(x->optimizers(x,otype;o...), a)
optimizers(a,otype;o...)=nothing


text:
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
#
# Desription
# ==============================================================================
#
#   Tests related to the support of Julia API (iterators, broadcast, etc.).
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

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

# Broadcast
# ---------

@testset "Broadcast" begin
    function quat_bcast!(v::AbstractVector, q::Quaternion)
        v .= q
        return nothing
    end

    # Precompile to avoid counting allocations related to compilation.
    precompile(quat_bcast!, (Vector{Float64}, Quaternion{Float64}))
    precompile(quat_bcast!, (Vector{Float32}, Quaternion{Float32}))

    # Float64, Float32
    # --------------------------------------------------------------------------

    for T in (Float32, Float64)
        q = Quaternion{T}(randn(), randn(), randn(), randn())
        q = q / norm(q)
        v = zeros(T, 4)

        alloc = @allocated quat_bcast!(v, q)

        @test v[1]  == q[1]
        @test v[2]  == q[2]
        @test v[3]  == q[3]
        @test v[4]  == q[4]
        @test alloc == 0
    end
end

@testset "Iteration" begin
    q = Quaternion(1.0, 2.0, 3.0, 4.0)

    ret = iterate(q)
    @test ret === (1.0, 1)
    ret = iterate(q, ret[2])
    @test ret === (2.0, 2)
    ret = iterate(q, ret[2])
    @test ret === (3.0, 3)
    ret = iterate(q, ret[2])
    @test ret === (4.0, 4)
    ret = iterate(q, ret[2])
    @test ret === nothing
end

@testset "setindex!" begin
    function quat_setindex!(v::AbstractVector, q::Quaternion)
        v[4:7] = q[:]
        return nothing
    end

    # Precompile to avoid counting allocations related to compilation.
    precompile(quat_setindex!, (Vector{Float64}, Quaternion{Float64}))
    precompile(quat_setindex!, (Vector{Float32}, Quaternion{Float32}))

    # Float64, Float32
    # --------------------------------------------------------------------------

    for T in (Float32, Float64)
        q = Quaternion{T}(randn(), randn(), randn(), randn())
        q = q / norm(q)
        v = zeros(T, 20)

        alloc = @allocated quat_setindex!(v, q)

        @test v[4]  == q[1]
        @test v[5]  == q[2]
        @test v[6]  == q[3]
        @test v[7]  == q[4]
        @test alloc == 0
    end
end

@testset "Other API functions" begin
    q = Quaternion(1.0, 2.0, 3.0, 4.0)

    @test q[end] === 4.0
    @test axes(q) === (Base.OneTo(4),)
    @test first(q) === 1.0
    @test last(q) === 4.0
    @test ndims(q) === 1
    @test ndims(Quaternion) === 1
    @test length(q) === 4
    @test size(q) === (4,)
    @test Broadcast.broadcastable(q) === q
    @test Base.IndexStyle(Quaternion) === IndexLinear()

    q_conv = convert(Quaternion{Float32}, q)
    @test q_conv.q0 == Float32(q.q0)
    @test q_conv.q1 == Float32(q.q1)
    @test q_conv.q2 == Float32(q.q2)
    @test q_conv.q3 == Float32(q.q3)
    @test eltype(q_conv) === Float32
end


text:
using Test
using ParserCombinator
using GraphIO.DOT
using Graphs.Experimental

@testset "DOT" begin
    g = complete_graph(6)
    dg = DiGraph(4)
    for e in [Edge(1,2), Edge(1,3), Edge(2,2), Edge(2,3), Edge(4,1), Edge(4,3)]
        add_edge!(dg, e)
    end
    fname = joinpath(testdir, "testdata", "twographs.dot")
    read_test(DOTFormat(), g, "g1", fname, testfail=true)
    read_test(DOTFormat(), dg, "g2", fname)
    read_test_mult(DOTFormat(), Dict{String,AbstractGraph}("g1"=>g, "g2"=>dg), fname)

	#tests for multiple graphs

	fname = joinpath(testdir, "testdata", "saved3graphs.dot")
	#connected graph
	g1 = SimpleGraph(5,10)
	#disconnected graph
	g2 = SimpleGraph(5,2)
	#directed graph
	dg = SimpleDiGraph(5,8)
	GraphDict = Dict("g1" => g1, "g2" => g2, "dg" => dg)
	write_test(DOTFormat(), GraphDict, fname, remove = false, silent = true)

	#adding this test because currently the Parser returns unordered vertices
	@test has_isomorph(loadgraph(fname, "g1", DOTFormat()), g1)
	@test has_isomorph(loadgraph(fname, "g2", DOTFormat()), g2)
	@test has_isomorph(loadgraph(fname, "dg", DOTFormat()), dg)

	rm(fname)

	#tests for single graph

	fname1 = joinpath(testdir, "testdata", "saved1graph.dot")
	write_test(DOTFormat(), g1, "g1", fname1, remove = false, silent = true)
	@test has_isomorph(loadgraph(fname1, "g1", DOTFormat()), g1)
	fname2 = joinpath(testdir, "testdata", "saved1digraph.dot")
	write_test(DOTFormat(), dg, "dg", fname2, remove = false, silent = true)
	@test has_isomorph(loadgraph(fname2, "dg", DOTFormat()), dg)

	rm(fname1)
	rm(fname2)
end


text:
mutable struct BodyConstants{T <: Real}
    m::T # Mass
    I_B::Matrix{T} # Central mass moment of inertia in body coordinates
    r_mb_B::Vector{T} # Position of center of mass wrt body reference point in body coordinates
    Δr::Vector{T} # Random position perturbations (m) (for initialization)
    Δv::Vector{T} # Random velocity perturbations (m/s)
    Δq::Vector{T} # Random orientation perturbations (~rad)
    Δω::Vector{T} # Random rotation rate perturbations (rad/s)
end

mutable struct BodyState{T <: Real} <: ModelState
    r_be_I::Vector{T} # Position of center of mass wrt center of mass of earth in ICRF's coordinates
    v_be_I::Vector{T} # Rate of change of above (as viewed from the inertia frame)
    q_BI::Vector{T}   # Attitude quaternion of body frame wrt ICRF
    ω_BI_B::Vector{T} # Rotation rate of body frame wrt ICRF, in body coordinates
end

"""
Create a DynamicalModel configured to act like a rigid body.
"""
function Body()

    # Upon initialization, Bodies can perturb their nominal states
    # by the specified amount (defaults to 0).
    function init(t, constants, state, draws)
        state.r_be_I += constants.Δr .* draws[1:3]
        state.v_be_I += constants.Δv .* draws[4:6]
        state.q_BI   =  qcomp(mrp2q(constants.Δq .* draws[7:9], 4.), state.q_BI)
        state.ω_BI_B += constants.Δω .* draws[10:12]
        return state
    end

    # Bodies are the top-lever dynamical objects; they produce effects for
    # others to consume and will see all of the effects of others in their
    # derivatives fuction.
    function effects(t, constants, state, draws, implicit)
        return (BodyStateEffect(state.r_be_I, state.v_be_I, state.q_BI, state.ω_BI_B),)
    end

    # Calculate the derivatives of the given state as acted on by the given
    # forces. Note that the effects used must be compatible with the models
    # expected to consume them. This Body models consumes Gravity, BodyForce,
    # and BodyTorque at the moment.
    function derivatives(t, constants, state, draws, implicit, effects, effects_bus)

        # Extract
        q_BI   = state.q_BI
        ω_BI_B = state.ω_BI_B

        # Body-fixed magnetic dipole

        # Torque due to magnetic dipole in planet's field
        #b_E = filter(e -> isa(e, MagneticField), effects)

        # Gather up the various forces and torques.

        # Gravity (special case field specific force)
        g_I = zeros(3)
        accumulate!(g_I, effects, Gravity, g -> g.specific_force)

        # Torques
        τ_B = zeros(3)
        accumulate!(τ_B, effects, BodyTorque, τ -> τ.torque)

        # Body-fixed forces
        f_B = zeros(3)
        for e in values(effects)
            for f in e
                if isa(f, BodyForce)
                    f_B[:] += f.force
                    τ_B[:] += (f.position - constants.r_mb_B) × f.force
                end
            end
        end

        # Assemble the derivatives.
        a_me_I     = qrot(qinv(q_BI), f_B) / constants.m + g_I
        q_BI_dot   = qdot(q_BI, ω_BI_B)
        ω_BI_B_dot = constants.I_B \ (τ_B - ω_BI_B × (constants.I_B * ω_BI_B))

        # Create the derivative of the state in a way that the simulation can
        # understand.
        return BodyState(state.v_be_I, a_me_I, q_BI_dot, ω_BI_B_dot) # Pretends that b is m. TODO: Add rotation once we actually treat with the center of mass.

    end

    # Create the set of constants used by the rigid body.
    constants = BodyConstants(50., Matrix(Diagonal([5.0; 5; 5])), zeros(3),
                              zeros(3), zeros(3), zeros(3), zeros(3))

    DynamicalModel(
        "body",
        init = init,
        effects = effects,
        derivatives = derivatives,
        timing = ModelTiming(0.01),    # maximum-allowable time step
        constants = constants,
        state = BodyState([6378137., 0., 0.], # position
                          [0., 7600., 0.],    # velocity
                          [0., 0., 0., 1.],   # attitude
                          [0., 0., 0.]),      # rotation rate
        rand = RandSpec(init=12)) # We need 3 Gaussian draws for the init fcn.
end

mutable struct Vehicle
    name::String
    body::DynamicalModel
    components::Vector{DynamicalModel}
    computers::Vector{Computer}
    Vehicle(name, body, components, computers) = new(name, body, components, computers)
    Vehicle() = Vehicle("default_vehicle",
                        Body(),
                        Vector{DynamicalModel}(undef, 0),
                        Vector{Computer}(undef, 0))
end


text:
# Borrowed from the JuliaDocs org (https://raw.githubusercontent.com/JuliaDocs/juliadocs.github.io/source/make.jl)
# Types and functions to generate a Markdown table with links to package badges etc.
struct PackageDefinition
    name::String
    url::String
    docs::Vector{Pair{String,String}} # type => URL
    buildbadges::Vector{Pair{String,String}} # badge => URL
end

function markdown_nodocs(p::PackageDefinition)
    row = Any[]
    push!(row, Markdown.Link(p.name, p.url))
    push!(
        row,
        [Markdown.Link([Markdown.Image(image, "")], url) for (image, url) in p.buildbadges],
    )
end

function package_table_markdown_nodocs(packages)
    titles = map(["Package", "Build Status"]) do s
        Markdown.Bold(s)
    end
    table = Markdown.Table([titles], [:l, :c])
    for p in packages
        push!(table.rows, markdown_nodocs(p))
    end
    Markdown.MD(table)
end

const LYCEUM_PACKAGE_DEFS = [
    PackageDefinition(
        "Lyceum",
        "https://github.com/Lyceum/Lyceum.jl",
        [
            "stable" => "https://lyceum.github.io/Lyceum.jl/stable/",
            "dev" => "https://lyceum.github.io/Lyceum.jl/dev/",
        ],
        [
            "https://github.com/Lyceum/Lyceum.jl/workflows/CI/badge.svg" =>
                "https://github.com/Lyceum/Lyceum.jl/actions",
        ],
    ),
    PackageDefinition(
        "LyceumBase",
        "https://github.com/Lyceum/LyceumBase.jl",
        [
            "stable" => "https://lyceum.github.io/LyceumBase.jl/stable/",
            "dev" => "https://lyceum.github.io/LyceumBase.jl/dev/",
        ],
        [
            "https://github.com/Lyceum/LyceumBase.jl/workflows/CI/badge.svg" =>
                "https://github.com/Lyceum/LyceumBase.jl/actions",
        ],
    ),
    PackageDefinition(
        "LyceumAI",
        "https://github.com/Lyceum/LyceumAI.jl",
        [
            "stable" => "https://lyceum.github.io/LyceumAI.jl/stable/",
            "dev" => "https://lyceum.github.io/LyceumAI.jl/dev/",
        ],
        [
            "https://github.com/Lyceum/LyceumAI.jl/workflows/CI/badge.svg" =>
                "https://github.com/Lyceum/LyceumAI.jl/actions",
        ],
    ),
    PackageDefinition(
        "LyceumMuJoCo",
        "https://github.com/Lyceum/LyceumMuJoCo.jl",
        [
            "stable" => "https://lyceum.github.io/LyceumMuJoCo.jl/stable/",
            "dev" => "https://lyceum.github.io/LyceumMuJoCo.jl/dev/",
        ],
        [
            "https://github.com/Lyceum/LyceumMuJoCo.jl/workflows/CI/badge.svg" =>
                "https://github.com/Lyceum/LyceumMuJoCo.jl/actions",
        ],
    ),
    PackageDefinition(
        "LyceumMuJoCoViz",
        "https://github.com/Lyceum/LyceumMuJoCoViz.jl",
        [
            "stable" => "https://lyceum.github.io/LyceumMuJoCoViz.jl/stable/",
            "dev" => "https://lyceum.github.io/LyceumMuJoCoViz.jl/dev/",
        ],
        [
            "https://github.com/Lyceum/LyceumMuJoCoViz.jl/workflows/CI/badge.svg" =>
                "https://github.com/Lyceum/LyceumMuJoCoViz.jl/actions",
        ],
    ),
    PackageDefinition(
        "Shapes",
        "https://github.com/Lyceum/Shapes.jl",
        [
            "stable" => "https://lyceum.github.io/Shapes.jl/stable/",
            "dev" => "https://lyceum.github.io/Shapes.jl/dev/",
        ],
        [
            "https://github.com/Lyceum/Shapes.jl/workflows/CI/badge.svg" =>
                "https://github.com/Lyceum/Shapes.jl/actions",
        ],
    ),
    PackageDefinition(
        "UniversalLogger",
        "https://github.com/Lyceum/UniversalLogger.jl",
        [
            "stable" => "https://lyceum.github.io/UniversalLogger.jl/stable/",
            "dev" => "https://lyceum.github.io/UniversalLogger.jl/dev/",
        ],
        [
            "https://github.com/Lyceum/UniversalLogger.jl/workflows/CI/badge.svg" =>
                "https://github.com/Lyceum/UniversalLogger.jl/actions",
        ],
    ),
    PackageDefinition(
        "MuJoCo",
        "https://github.com/Lyceum/MuJoCo.jl",
        [
            "stable" => "https://lyceum.github.io/MuJoCo.jl/stable/",
            "dev" => "https://lyceum.github.io/MuJoCo.jl/dev/",
        ],
        [
            "https://github.com/Lyceum/MuJoCo.jl/workflows/CI/badge.svg" =>
                "https://github.com/Lyceum/MuJoCo.jl/actions",
        ],
    ),
]


text:
# This file is auto-generated by AWSMetadata.jl
using AWS
using AWS.AWSServices: snow_device_management
using AWS.Compat
using AWS.UUIDs

"""
    cancel_task(task_id)
    cancel_task(task_id, params::Dict{String,<:Any})

Sends a cancel request for a specified task. You can cancel a task only if it's still in a
QUEUED state. Tasks that are already running can't be cancelled.  A task might still run if
it's processed from the queue before the CancelTask operation changes the task's state.

# Arguments
- `task_id`: The ID of the task that you are attempting to cancel. You can retrieve a task
  ID by using the ListTasks operation.

"""
function cancel_task(taskId; aws_config::AbstractAWSConfig=global_aws_config())
    return snow_device_management(
        "POST",
        "/task/$(taskId)/cancel";
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function cancel_task(
    taskId, params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()
)
    return snow_device_management(
        "POST",
        "/task/$(taskId)/cancel",
        params;
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    create_task(command, targets)
    create_task(command, targets, params::Dict{String,<:Any})

Instructs one or more devices to start a task, such as unlocking or rebooting.

# Arguments
- `command`: The task to be performed. Only one task is executed on a device at a time.
- `targets`: A list of managed device IDs.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"clientToken"`: A token ensuring that the action is called only once with the specified
  details.
- `"description"`: A description of the task and its targets.
- `"tags"`: Optional metadata that you assign to a resource. You can use tags to categorize
  a resource in different ways, such as by purpose, owner, or environment.
"""
function create_task(command, targets; aws_config::AbstractAWSConfig=global_aws_config())
    return snow_device_management(
        "POST",
        "/task",
        Dict{String,Any}(
            "command" => command, "targets" => targets, "clientToken" => string(uuid4())
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function create_task(
    command,
    targets,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return snow_device_management(
        "POST",
        "/task",
        Dict{String,Any}(
            mergewith(
                _merge,
                Dict{String,Any}(
                    "command" => command,
                    "targets" => targets,
                    "clientToken" => string(uuid4()),
                ),
                params,
            ),
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    describe_device(managed_device_id)
    describe_device(managed_device_id, params::Dict{String,<:Any})

Checks device-specific information, such as the device type, software version, IP
addresses, and lock status.

# Arguments
- `managed_device_id`: The ID of the device that you are checking the information of.

"""
function describe_device(managedDeviceId; aws_config::AbstractAWSConfig=global_aws_config())
    return snow_device_management(
        "POST",
        "/managed-device/$(managedDeviceId)/describe";
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function describe_device(
    managedDeviceId,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return snow_device_management(
        "POST",
        "/managed-device/$(managedDeviceId)/describe",
        params;
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    describe_device_ec2_instances(instance_ids, managed_device_id)
    describe_device_ec2_instances(instance_ids, managed_device_id, params::Dict{String,<:Any})

Checks the current state of the Amazon EC2 instances. The output is similar to
describeDevice, but the results are sourced from the device cache in the Amazon Web
Services Cloud and include a subset of the available fields.

# Arguments
- `instance_ids`: A list of instance IDs associated with the managed device.
- `managed_device_id`: The ID of the managed device.

"""
function describe_device_ec2_instances(
    instanceIds, managedDeviceId; aws_config::AbstractAWSConfig=global_aws_config()
)
    return snow_device_management(
        "POST",
        "/managed-device/$(managedDeviceId)/resources/ec2/describe",
        Dict{String,Any}("instanceIds" => instanceIds);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function describe_device_ec2_instances(
    instanceIds,
    managedDeviceId,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return snow_device_management(
        "POST",
        "/managed-device/$(managedDeviceId)/resources/ec2/describe",
        Dict{String,Any}(
            mergewith(_merge, Dict{String,Any}("instanceIds" => instanceIds), params)
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    describe_execution(managed_device_id, task_id)
    describe_execution(managed_device_id, task_id, params::Dict{String,<:Any})

Checks the status of a remote task running on one or more target devices.

# Arguments
- `managed_device_id`: The ID of the managed device.
- `task_id`: The ID of the task that the action is describing.

"""
function describe_execution(
    managedDeviceId, taskId; aws_config::AbstractAWSConfig=global_aws_config()
)
    return snow_device_management(
        "POST",
        "/task/$(taskId)/execution/$(managedDeviceId)";
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function describe_execution(
    managedDeviceId,
    taskId,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return snow_device_management(
        "POST",
        "/task/$(taskId)/execution/$(managedDeviceId)",
        params;
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    describe_task(task_id)
    describe_task(task_id, params::Dict{String,<:Any})

Checks the metadata for a given task on a device.

# Arguments
- `task_id`: The ID of the task to be described.

"""
function describe_task(taskId; aws_config::AbstractAWSConfig=global_aws_config())
    return snow_device_management(
        "POST", "/task/$(taskId)"; aws_config=aws_config, feature_set=SERVICE_FEATURE_SET
    )
end
function describe_task(
    taskId, params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()
)
    return snow_device_management(
        "POST",
        "/task/$(taskId)",
        params;
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    list_device_resources(managed_device_id)
    list_device_resources(managed_device_id, params::Dict{String,<:Any})

Returns a list of the Amazon Web Services resources available for a device. Currently,
Amazon EC2 instances are the only supported resource type.

# Arguments
- `managed_device_id`: The ID of the managed device that you are listing the resources of.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"maxResults"`: The maximum number of resources per page.
- `"nextToken"`: A pagination token to continue to the next page of results.
- `"type"`: A structure used to filter the results by type of resource.
"""
function list_device_resources(
    managedDeviceId; aws_config::AbstractAWSConfig=global_aws_config()
)
    return snow_device_management(
        "GET",
        "/managed-device/$(managedDeviceId)/resources";
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function list_device_resources(
    managedDeviceId,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return snow_device_management(
        "GET",
        "/managed-device/$(managedDeviceId)/resources",
        params;
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    list_devices()
    list_devices(params::Dict{String,<:Any})

Returns a list of all devices on your Amazon Web Services account that have Amazon Web
Services Snow Device Management enabled in the Amazon Web Services Region where the command
is run.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"jobId"`: The ID of the job used to order the device.
- `"maxResults"`: The maximum number of devices to list per page.
- `"nextToken"`: A pagination token to continue to the next page of results.
"""
function list_devices(; aws_config::AbstractAWSConfig=global_aws_config())
    return snow_device_management(
        "GET", "/managed-devices"; aws_config=aws_config, feature_set=SERVICE_FEATURE_SET
    )
end
function list_devices(
    params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()
)
    return snow_device_management(
        "GET",
        "/managed-devices",
        params;
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    list_executions(task_id)
    list_executions(task_id, params::Dict{String,<:Any})

Returns the status of tasks for one or more target devices.

# Arguments
- `task_id`: The ID of the task.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"maxResults"`: The maximum number of tasks to list per page.
- `"nextToken"`: A pagination token to continue to the next page of tasks.
- `"state"`: A structure used to filter the tasks by their current state.
"""
function list_executions(taskId; aws_config::AbstractAWSConfig=global_aws_config())
    return snow_device_management(
        "GET",
        "/executions",
        Dict{String,Any}("taskId" => taskId);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function list_executions(
    taskId, params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()
)
    return snow_device_management(
        "GET",
        "/executions",
        Dict{String,Any}(mergewith(_merge, Dict{String,Any}("taskId" => taskId), params));
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    list_tags_for_resource(resource_arn)
    list_tags_for_resource(resource_arn, params::Dict{String,<:Any})

Returns a list of tags for a managed device or task.

# Arguments
- `resource_arn`: The Amazon Resource Name (ARN) of the device or task.

"""
function list_tags_for_resource(
    resourceArn; aws_config::AbstractAWSConfig=global_aws_config()
)
    return snow_device_management(
        "GET",
        "/tags/$(resourceArn)";
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function list_tags_for_resource(
    resourceArn,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return snow_device_management(
        "GET",
        "/tags/$(resourceArn)",
        params;
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    list_tasks()
    list_tasks(params::Dict{String,<:Any})

Returns a list of tasks that can be filtered by state.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"maxResults"`: The maximum number of tasks per page.
- `"nextToken"`: A pagination token to continue to the next page of tasks.
- `"state"`: A structure used to filter the list of tasks.
"""
function list_tasks(; aws_config::AbstractAWSConfig=global_aws_config())
    return snow_device_management(
        "GET", "/tasks"; aws_config=aws_config, feature_set=SERVICE_FEATURE_SET
    )
end
function list_tasks(
    params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()
)
    return snow_device_management(
        "GET", "/tasks", params; aws_config=aws_config, feature_set=SERVICE_FEATURE_SET
    )
end

"""
    tag_resource(resource_arn, tags)
    tag_resource(resource_arn, tags, params::Dict{String,<:Any})

Adds or replaces tags on a device or task.

# Arguments
- `resource_arn`: The Amazon Resource Name (ARN) of the device or task.
- `tags`: Optional metadata that you assign to a resource. You can use tags to categorize a
  resource in different ways, such as by purpose, owner, or environment.

"""
function tag_resource(resourceArn, tags; aws_config::AbstractAWSConfig=global_aws_config())
    return snow_device_management(
        "POST",
        "/tags/$(resourceArn)",
        Dict{String,Any}("tags" => tags);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function tag_resource(
    resourceArn,
    tags,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return snow_device_management(
        "POST",
        "/tags/$(resourceArn)",
        Dict{String,Any}(mergewith(_merge, Dict{String,Any}("tags" => tags), params));
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    untag_resource(resource_arn, tag_keys)
    untag_resource(resource_arn, tag_keys, params::Dict{String,<:Any})

Removes a tag from a device or task.

# Arguments
- `resource_arn`: The Amazon Resource Name (ARN) of the device or task.
- `tag_keys`: Optional metadata that you assign to a resource. You can use tags to
  categorize a resource in different ways, such as by purpose, owner, or environment.

"""
function untag_resource(
    resourceArn, tagKeys; aws_config::AbstractAWSConfig=global_aws_config()
)
    return snow_device_management(
        "DELETE",
        "/tags/$(resourceArn)",
        Dict{String,Any}("tagKeys" => tagKeys);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function untag_resource(
    resourceArn,
    tagKeys,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return snow_device_management(
        "DELETE",
        "/tags/$(resourceArn)",
        Dict{String,Any}(mergewith(_merge, Dict{String,Any}("tagKeys" => tagKeys), params));
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end