text:
# Native intrinsics

#
# Support functionality
#

# TODO: compute capability checks

# how to map primitive Julia types to LLVM data types
const llvmtypes = Dict{Type,Symbol}(
    Void    => :void,
    Int8    => :i8,
    Int16   => :i16,
    Int32   => :i32,
    Int64   => :i64,
    UInt8   => :i8,
    UInt16  => :i16,
    UInt32  => :i32,
    UInt64  => :i64,
    Float32 => :float,
    Float64 => :double
)
const LLVMTypes = Union{keys(llvmtypes)...}     # for dispatch

# the inverse, ie. which Julia types map a given LLVM types
const jltypes = Dict{Symbol,Type}(
    :void   => Void,
    :i8     => Int8,
    :i16    => Int16,
    :i32    => Int32,
    :i64    => Int64,
    :float  => Float32,
    :double => Float64
)

"""
Decode an expression of the form:

   function(arg::arg_type, arg::arg_type, ... arg::arg_type)::return_type

Returns a tuple containing the function name, a vector of argument, a vector of argument
types and the return type (all in symbolic form).
"""
function decode_call(e)
    @assert e.head == :(::)

    # decode the return type expression: single symbol (the LLVM type), or a tuple of 2
    # symbols (the LLVM and corresponding Julia type)
    retspec = e.args[2]
    if isa(retspec, Symbol)
        rettype = retspec
    else
        @assert retspec.head == :tuple
        @assert length(retspec.args) == 2
        rettype = (retspec.args[1], retspec.args[2])
    end

    call = e.args[1]
    @assert call.head == :call

    fn = Symbol(call.args[1])
    args = Symbol[arg.args[1] for arg in call.args[2:end]]
    argtypes = Symbol[arg.args[2] for arg in call.args[2:end]]

    return fn, args, argtypes, rettype
end

"""
Generate a `llvmcall` statement calling an intrinsic specified as follows:

    intrinsic(arg::arg_type, arg::arg_type, ... arg::arg_type)::return_type [attr]

The argument types should be valid LLVM type identifiers (eg. i32, float, double).
Conversions to the corresponding Julia type are automatically generated; make sure the
actual arguments are of the same type to make these conversions no-ops. The optional
argument `attr` indicates which LLVM function attributes (such as `readnone` or `nounwind`)
to add to the intrinsic declaration.

For example, the following call:
    `@wrap __some_intrinsic(x::float, y::double)::float`

will yield the following `llvmcall`:
```
    Base.llvmcall(("declare float @__somme__intr(float, double)",
                   "%3 = call float @__somme__intr(float %0, double %1)
                    ret float %3"),
                  Float32, Tuple{Float32,Float64},
                  convert(Float32,x), convert(Float64,y))
```
"""
macro wrap(call, attrs="")
    intrinsic, args, argtypes, rettype = decode_call(call)

    # decide on intrinsic return type
    if isa(rettype, Symbol)
        # only LLVM return type specified, match against known LLVM/Julia type combinations
        llvm_ret_typ = rettype
        julia_ret_typ = jltypes[rettype]
    else
        # both specified (for when there is a mismatch, eg. i32 -> UInt32)
        llvm_ret_typ = rettype[1]
        julia_ret_typ = rettype[2]
    end

    llvm_args = String["%$i" for i in 0:length(argtypes)]
    if llvm_ret_typ == :void
        llvm_ret_asgn = ""
        llvm_ret = "void"
    else
        llvm_ret_var = "%$(length(argtypes)+1)"
        llvm_ret_asgn = "$llvm_ret_var = "
        llvm_ret = "$llvm_ret_typ $llvm_ret_var"
    end
    llvm_declargs = join(argtypes, ", ")
    llvm_defargs = join(("$t $arg" for (t,arg) in zip(argtypes, llvm_args)), ", ")

    julia_argtypes = (jltypes[t] for t in argtypes)
    julia_args = (:(convert($argtype, $(esc(arg)))) for (arg, argtype) in zip(args, julia_argtypes))

    return quote
        Base.llvmcall(
            ($"""declare $llvm_ret_typ @$intrinsic($llvm_declargs)""",
             $"""$llvm_ret_asgn call $llvm_ret_typ @$intrinsic($llvm_defargs)
                 ret $llvm_ret"""),
            $julia_ret_typ, Tuple{$(julia_argtypes...)}, $(julia_args...))
    end
end

"""
Make a string literal safe to embed in LLVM IR.

This is a custom, simplified version of Base.escape_string, replacing non-printable
characters with their two-digit hex code.
"""
function escape_llvm_string(io, s::AbstractString, esc::AbstractString)
    i = start(s)
    while !done(s,i)
        c, j = next(s,i)
        c == '\\'       ? print(io, "\\\\") :
        c in esc        ? print(io, '\\', c) :
        isprint(c)      ? print(io, c) :
                          print(io, "\\", hex(c, 2))
        i = j
    end
end
escape_llvm_string(s::AbstractString) = sprint(endof(s), escape_llvm_string, s, "\"")

#
# I/O
#

export
    @cuprintf

const cuprintf_fmts = Vector{String}()

"""
Print a formatted string in device context on the host standard output:

    @cuprintf("%Fmt", args...)

Note that this is not a fully C-compliant `printf` implementation; see the CUDA
documentation for supported options and inputs.

Also beware that it is an untyped, and unforgiving `printf` implementation. Type widths need
to match, eg. printing a 64-bit Julia integer requires the `%ld` formatting string.

"""
macro cuprintf(fmt::String, args...)
    # NOTE: we can't pass fmt by Val{}, so save it in a global buffer
    push!(cuprintf_fmts, "$fmt\0")
    id = length(cuprintf_fmts)

    return :(generated_cuprintf(Val{$id}, $(map(esc, args)...)))
end

function emit_vprintf(id::Integer, argtypes, args...)
    fmt = cuprintf_fmts[id]
    fmtlen = length(fmt)

    llvm_argtypes = [llvmtypes[jltype] for jltype in argtypes]

    decls = Vector{String}()
    push!(decls, """declare i32 @vprintf(i8*, i8*)""")
    push!(decls, """%print$(id)_argtyp = type { $(join(llvm_argtypes, ", ")) }""")
    push!(decls, """@print$(id)_fmt = private unnamed_addr constant [$fmtlen x i8] c"$(escape_llvm_string(fmt))", align 1""")

    ir = Vector{String}()
    push!(ir, """%args = alloca %print$(id)_argtyp""")
    arg = 0
    tmp = length(args)+1
    for jltype in argtypes
        llvmtype = llvmtypes[jltype]
        push!(ir, """%$tmp = getelementptr inbounds %print$(id)_argtyp, %print$(id)_argtyp* %args, i32 0, i32 $arg""")
        push!(ir, """store $llvmtype %$arg, $llvmtype* %$tmp, align 4""")
        arg+=1
        tmp+=1
    end
    push!(ir, """%argptr = bitcast %print$(id)_argtyp* %args to i8*""")
    push!(ir, """%$tmp = call i32 @vprintf(i8* getelementptr inbounds ([$fmtlen x i8], [$fmtlen x i8]* @print$(id)_fmt, i32 0, i32 0), i8* %argptr)""")
    push!(ir, """ret void""")

    return quote
        Base.@_inline_meta
        Base.llvmcall(($(join(decls, "\n")),
                       $(join(ir,    "\n"))),
                      Void, Tuple{$argtypes...}, $(args...)
                     )
    end
end

@generated function generated_cuprintf{ID}(::Type{Val{ID}}, argspec...)
    args = [:( argspec[$i] ) for i in 1:length(argspec)]
    return emit_vprintf(ID, argspec, args...)
end

#
# Indexing and dimensions
#

export
    threadIdx, blockDim, blockIdx, gridDim,
    warpsize

for dim in (:x, :y, :z)
    # Thread index
    fn = Symbol("threadIdx_$dim")
    @eval @inline $fn() = (ccall($"llvm.nvvm.read.ptx.sreg.tid.$dim", llvmcall, UInt32, ()))+UInt32(1)

    # Block size (#threads per block)
    fn = Symbol("blockDim_$dim")
    @eval @inline $fn() =  ccall($"llvm.nvvm.read.ptx.sreg.ntid.$dim", llvmcall, UInt32, ())

    # Block index
    fn = Symbol("blockIdx_$dim")
    @eval @inline $fn() = (ccall($"llvm.nvvm.read.ptx.sreg.ctaid.$dim", llvmcall, UInt32, ()))+UInt32(1)

    # Grid size (#blocks per grid)
    fn = Symbol("gridDim_$dim")
    @eval @inline $fn() =  ccall($"llvm.nvvm.read.ptx.sreg.nctaid.$dim", llvmcall, UInt32, ())
end

# Tuple accessors
@inline threadIdx() = CUDAdrv.CuDim3(threadIdx_x(), threadIdx_y(), threadIdx_z())
@inline blockDim() =  CUDAdrv.CuDim3(blockDim_x(),  blockDim_y(),  blockDim_z())
@inline blockIdx() =  CUDAdrv.CuDim3(blockIdx_x(),  blockIdx_y(),  blockIdx_z())
@inline gridDim() =   CUDAdrv.CuDim3(gridDim_x(),   gridDim_y(),   gridDim_z())

@inline warpsize() = ccall("llvm.nvvm.read.ptx.sreg.warpsize", llvmcall, UInt32, ())

#
# Parallel Synchronization and Communication
#

export
    sync_threads,
    vote_all, vote_any, vote_ballot

## barriers

@inline sync_threads() = ccall("llvm.nvvm.barrier0", llvmcall, Void, ())

## voting

const all_asm = """{
    .reg .pred %p1;
    .reg .pred %p2;
    setp.ne.u32 %p1, \$1, 0;
    vote.all.pred %p2, %p1;
    selp.s32 \$0, 1, 0, %p2;
}"""
@inline function vote_all(pred::Bool)
    return Base.llvmcall(
        """%2 = call i32 asm sideeffect "$all_asm", "=r,r"(i32 %0)
           ret i32 %2""",
        Int32, Tuple{Int32}, convert(Int32, pred)) != Int32(0)
end

const any_asm = """{
    .reg .pred %p1;
    .reg .pred %p2;
    setp.ne.u32 %p1, \$1, 0;
    vote.any.pred %p2, %p1;
    selp.s32 \$0, 1, 0, %p2;
}"""
@inline function vote_any(pred::Bool)
    return Base.llvmcall(
        """%2 = call i32 asm sideeffect "$any_asm", "=r,r"(i32 %0)
           ret i32 %2""",
        Int32, Tuple{Int32}, convert(Int32, pred)) != Int32(0)
end

const ballot_asm = """{
   .reg .pred %p1;
   setp.ne.u32 %p1, \$1, 0;
   vote.ballot.b32 \$0, %p1;
}"""
@inline function vote_ballot(pred::Bool)
    return Base.llvmcall(
        """%2 = call i32 asm sideeffect "$ballot_asm", "=r,r"(i32 %0)
           ret i32 %2""",
        UInt32, Tuple{Int32}, convert(Int32, pred))
end

#
# Shared memory
#

export
    @cuStaticSharedMem, @cuDynamicSharedMem

# FIXME: `shmem_id` increment in the macro isn't correct, as multiple parametrically typed
#        functions will alias the id (but the size might be a parameter). but incrementing in
#        the @generated function doesn't work, as it is supposed to be pure and identical
#        invocations will erroneously share (and even cause multiple shmem globals).
shmem_id = 0

# add a shared memory definition to the module, and get a pointer to the first item
# FIXME: this adds module-scope declarations by means of `llvmcall`, which is unsupported
function emit_shmem(id, llvmtyp, len, align)
    var = Symbol(:@shmem, id)
    jltyp = jltypes[llvmtyp]
    quote
        Base.llvmcall(
            ($"""$var = external addrspace(3) global [$len x $llvmtyp], align $align""",
             $"""%1 = getelementptr inbounds [$len x $llvmtyp], [$len x $llvmtyp] addrspace(3)* $var, i64 0, i64 0
                 %2 = addrspacecast $llvmtyp addrspace(3)* %1 to $llvmtyp addrspace(0)*
                 ret $llvmtyp* %2"""),
            Ptr{$jltyp}, Tuple{})
    end
end

# IDEA: merge static and dynamic shared memory, specializing on whether the shape is known
#       (ie. a number) or an expression/symbol, communicating the necessary dynamic memory
#       to `@cuda`

"""
    @cuStaticSharedMem(typ::Type, dims) -> CuDeviceArray{typ}

Get an array of type `typ` and dimensions `dims` (either an integer length or tuple shape)
pointing to a statically-allocated piece of shared memory. The type should be statically
inferable and the dimensions should be constant (without requiring constant propagation, see
JuliaLang/julia#5560), or an error will be thrown and the generator function will be called
dynamically.

Multiple statically-allocated shared memory arrays can be requested by calling this macro
multiple times.
"""
macro cuStaticSharedMem(typ, dims)
    global shmem_id
    id = shmem_id::Int += 1

    return :(generate_static_shmem(Val{$id}, $(esc(typ)), Val{$(esc(dims))}))
end

# types with known corresponding LLVM type
function emit_static_shmem{N, T<:LLVMTypes}(id::Integer, jltyp::Type{T}, shape::NTuple{N,Int})
    llvmtyp = llvmtypes[jltyp]

    len = prod(shape)
    align = sizeof(jltyp)

    return quote
        Base.@_inline_meta
        ptr = $(emit_shmem(id, llvmtyp, len, align))
        CuDeviceArray{$jltyp}($shape, ptr)
    end
end

# fallback for unknown types
function emit_static_shmem{N}(id::Integer, jltyp::Type, shape::NTuple{N,<:Integer})
    if !isbits(jltyp)
        error("cuStaticSharedMem: non-isbits type '$jltyp' is not supported")
    end

    len = prod(shape) * sizeof(jltyp)
    align = sizeof(jltyp)

    return quote
        Base.@_inline_meta
        ptr = $(emit_shmem(id, :i8, len, align))
        CuDeviceArray{$jltyp}($shape, Base.unsafe_convert(Ptr{$jltyp}, ptr))
    end
end

@generated function generate_static_shmem{ID,T,D}(::Type{Val{ID}}, ::Type{T}, ::Type{Val{D}})
    return emit_static_shmem(ID, T, tuple(D...))
end

"""
    @cuDynamicSharedMem(typ::Type, dims, offset::Integer=0) -> CuDeviceArray{typ}

Get an array of type `typ` and dimensions `dims` (either an integer length or tuple shape)
pointing to a dynamically-allocated piece of shared memory. The type should be statically
inferable and the dimension and offset parameters should be constant (without requiring
constant propagation, see JuliaLang/julia#5560), or an error will be thrown and the
generator function will be called dynamically.

Dynamic shared memory also needs to be allocated beforehand, when calling the kernel.

Optionally, an offset parameter indicating how many bytes to add to the base shared memory
pointer can be specified. This is useful when dealing with a heterogeneous buffer of dynamic
shared memory; in the case of a homogeneous multi-part buffer it is preferred to use `view`.

Note that calling this macro multiple times does not result in different shared arrays; only
a single dynamically-allocated shared memory array exists.
"""
macro cuDynamicSharedMem(typ, dims, offset=0)
    global shmem_id
    id = shmem_id::Int += 1

    return :(generate_dynamic_shmem(Val{$id}, $(esc(typ)), $(esc(dims)), $(esc(offset))))
end

# TODO: boundscheck against %dynamic_smem_size (currently unsupported by LLVM)

# types with known corresponding LLVM type
function emit_dynamic_shmem{T<:LLVMTypes}(id::Integer, jltyp::Type{T}, shape::Union{Expr,Symbol}, offset)
    llvmtyp = llvmtypes[jltyp]

    align = sizeof(jltyp)

    return quote
        Base.@_inline_meta
        ptr = $(emit_shmem(id, llvmtyp, 0, align)) + $offset
        CuDeviceArray{$jltyp}($shape, ptr)
    end
end

# fallback for unknown types
function emit_dynamic_shmem(id::Integer, jltyp::Type, shape::Union{Expr,Symbol}, offset)
    if !isbits(jltyp)
        error("cuDynamicSharedMem: non-isbits type '$jltyp' is not supported")
    end

    align = sizeof(jltyp)

    return quote
        Base.@_inline_meta
        ptr = $(emit_shmem(id, :i8, 0, align)) + $offset
        CuDeviceArray{$jltyp}($shape, Base.unsafe_convert(Ptr{$jltyp}, ptr))
    end
end

@generated function generate_dynamic_shmem{ID,T}(::Type{Val{ID}}, ::Type{T}, dims, offset)
    return emit_dynamic_shmem(ID, T, :(dims), :(offset))
end

# IDEA: a neater approach (with a user-end macro for hiding the `Val{N}`):
#
#   for typ in ((Int64,   :i64),
#               (Float32, :float),
#               (Float64, :double))
#       T, U = typ
#       @eval begin
#           cuSharedMem{T}(::Type{$T}) = Base.llvmcall(
#               ($"""@shmem_$U = external addrspace(3) global [0 x $U]""",
#                $"""%1 = getelementptr inbounds [0 x $U], [0 x $U] addrspace(3)* @shmem_$U, i64 0, i64 0
#                    %2 = addrspacecast $U addrspace(3)* %1 to $U addrspace(0)*
#                    ret $U* %2"""),
#               Ptr{$T}, Tuple{})
#           cuSharedMem{T,N}(::Type{$T}, ::Val{N}) = Base.llvmcall(
#               ($"""@shmem_$U = internal addrspace(3) global [$N x $llvmtyp] zeroinitializer, align 4""",
#                $"""%1 = getelementptr inbounds [$N x $U], [$N x $U] addrspace(3)* @shmem_$U, i64 0, i64 0
#                    %2 = addrspacecast $U addrspace(3)* %1 to $U addrspace(0)*
#                    ret $U* %2"""),
#               Ptr{$T}, Tuple{})
#       end
#   end
#
# Requires a change to `llvmcall`, as calling the static case twice references the same memory.

#
# Data movement and conversion
#

## shuffling

# TODO: should shfl_idx conform to 1-based indexing?

# narrow
for typ in ((Int32,   :i32, :i32),
            (UInt32,  :i32, :i32),
            (Float32, :f32, :float))
    jl, intr, llvm = typ

    # TODO: these functions should dispatch based on the actual warp size
    ws = Int32(32)

    for op in ((:up,   Int32(0x00)),
               (:down, Int32(0x1f)),
               (:bfly, Int32(0x1f)),
               (:idx,  Int32(0x1f)))
        mode, mask = op
        fname = Symbol("shfl_$mode")
        pack_expr = :((($ws - Int32(width)) << 8) | $mask)
        intrinsic = Symbol("llvm.nvvm.shfl.$mode.$intr")

        @eval begin
            export $fname
            @inline $fname(val::$jl, srclane::Integer, width::Integer=$ws) =
                ccall($"$intrinsic", llvmcall, $jl,
                      ($jl, Int32, Int32), val, Int32(srclane), $pack_expr)
        end

    end
end

@inline decode(val::UInt64) = trunc(UInt32,  val & 0x00000000ffffffff),
                              trunc(UInt32, (val & 0xffffffff00000000)>>32)

@inline encode(x::UInt32, y::UInt32) = UInt64(x) | UInt64(y)<<32

# wide
# NOTE: we only reuse the i32 shuffle, does it make any difference using eg. f32 shuffle for f64 values?
for typ in (Int64, UInt64, Float64)
    # TODO: these functions should dispatch based on the actual warp size
    ws = Int32(32)

    for mode in (:up, :down, :bfly, :idx)
        fname = Symbol("shfl_$mode")
        @eval begin
            export $fname
            @inline function $fname(val::$typ, srclane::Integer, width::Integer=$ws)
                x,y = decode(reinterpret(UInt64, val))
                x = $fname(x, srclane, width)
                y = $fname(y, srclane, width)
                reinterpret($typ, encode(x,y))
            end
        end
    end
end

#
# Math
#

## trigonometric

@inline cos(x::Float64) = @wrap __nv_cos(x::double)::double
@inline cos(x::Float32) = @wrap __nv_cosf(x::float)::float

@inline cospi(x::Float64) = @wrap __nv_cospi(x::double)::double
@inline cospi(x::Float32) = @wrap __nv_cospif(x::float)::float

@inline sin(x::Float64) = @wrap __nv_sin(x::double)::double
@inline sin(x::Float32) = @wrap __nv_sinf(x::float)::float

@inline sinpi(x::Float64) = @wrap __nv_sinpi(x::double)::double
@inline sinpi(x::Float32) = @wrap __nv_sinpif(x::float)::float

@inline tan(x::Float64) = @wrap __nv_tan(x::double)::double
@inline tan(x::Float32) = @wrap __nv_tanf(x::float)::float

## inverse trigonometric

@inline acos(x::Float64) = @wrap __nv_acos(x::double)::double
@inline acos(x::Float32) = @wrap __nv_acosf(x::float)::float

@inline asin(x::Float64) = @wrap __nv_asin(x::double)::double
@inline asin(x::Float32) = @wrap __nv_asinf(x::float)::float

@inline atan(x::Float64) = @wrap __nv_atan(x::double)::double
@inline atan(x::Float32) = @wrap __nv_atanf(x::float)::float

@inline atan2(x::Float64, y::Float64) = @wrap __nv_atan2(x::double, y::double)::double
@inline atan2(x::Float32, y::Float32) = @wrap __nv_atan2f(x::float, y::float)::float

## hyperbolic

@inline cosh(x::Float64) = @wrap __nv_cosh(x::double)::double
@inline cosh(x::Float32) = @wrap __nv_coshf(x::float)::float

@inline sinh(x::Float64) = @wrap __nv_sinh(x::double)::double
@inline sinh(x::Float32) = @wrap __nv_sinhf(x::float)::float

@inline tanh(x::Float64) = @wrap __nv_tanh(x::double)::double
@inline tanh(x::Float32) = @wrap __nv_tanhf(x::float)::float

## inverse hyperbolic

@inline acosh(x::Float64) = @wrap __nv_acosh(x::double)::double
@inline acosh(x::Float32) = @wrap __nv_acoshf(x::float)::float

@inline asinh(x::Float64) = @wrap __nv_asinh(x::double)::double
@inline asinh(x::Float32) = @wrap __nv_asinhf(x::float)::float

@inline atanh(x::Float64) = @wrap __nv_atanh(x::double)::double
@inline atanh(x::Float32) = @wrap __nv_atanhf(x::float)::float

## logarithmic

@inline log(x::Float64) = @wrap __nv_log(x::double)::double
@inline log(x::Float32) = @wrap __nv_logf(x::float)::float

@inline log10(x::Float64) = @wrap __nv_log10(x::double)::double
@inline log10(x::Float32) = @wrap __nv_log10f(x::float)::float

@inline log1p(x::Float64) = @wrap __nv_log1p(x::double)::double
@inline log1p(x::Float32) = @wrap __nv_log1pf(x::float)::float

@inline log2(x::Float64) = @wrap __nv_log2(x::double)::double
@inline log2(x::Float32) = @wrap __nv_log2f(x::float)::float

@inline logb(x::Float64) = @wrap __nv_logb(x::double)::double
@inline logb(x::Float32) = @wrap __nv_logbf(x::float)::float

@inline ilogb(x::Float64) = @wrap __nv_ilogb(x::double)::i32
@inline ilogb(x::Float32) = @wrap __nv_ilogbf(x::float)::i32

## exponential

@inline exp(x::Float64) = @wrap __nv_exp(x::double)::double
@inline exp(x::Float32) = @wrap __nv_expf(x::float)::float

@inline exp2(x::Float64) = @wrap __nv_exp2(x::double)::double
@inline exp2(x::Float32) = @wrap __nv_exp2f(x::float)::float

@inline exp10(x::Float64) = @wrap __nv_exp10(x::double)::double
@inline exp10(x::Float32) = @wrap __nv_exp10f(x::float)::float

@inline expm1(x::Float64) = @wrap __nv_expm1(x::double)::double
@inline expm1(x::Float32) = @wrap __nv_expm1f(x::float)::float

@inline ldexp(x::Float64, y::Int32) = @wrap __nv_ldexp(x::double, y::i32)::double
@inline ldexp(x::Float32, y::Int32) = @wrap __nv_ldexpf(x::float, y::i32)::float

## error

@inline erf(x::Float64) = @wrap __nv_erf(x::double)::double
@inline erf(x::Float32) = @wrap __nv_erff(x::float)::float

@inline erfinv(x::Float64) = @wrap __nv_erfinv(x::double)::double
@inline erfinv(x::Float32) = @wrap __nv_erfinvf(x::float)::float

@inline erfc(x::Float64) = @wrap __nv_erfc(x::double)::double
@inline erfc(x::Float32) = @wrap __nv_erfcf(x::float)::float

@inline erfcinv(x::Float64) = @wrap __nv_erfcinv(x::double)::double
@inline erfcinv(x::Float32) = @wrap __nv_erfcinvf(x::float)::float

@inline erfcx(x::Float64) = @wrap __nv_erfcx(x::double)::double
@inline erfcx(x::Float32) = @wrap __nv_erfcxf(x::float)::float

## integer handling (bit twiddling)

@inline brev(x::Int32) =   @wrap __nv_brev(x::i32)::i32
@inline brev(x::Int64) =   @wrap __nv_brevll(x::i64)::i64

@inline clz(x::Int32) =   @wrap __nv_clz(x::i32)::i32
@inline clz(x::Int64) =   @wrap __nv_clzll(x::i64)::i32

@inline ffs(x::Int32) = @wrap __nv_ffs(x::i32)::i32
@inline ffs(x::Int64) = @wrap __nv_ffsll(x::i64)::i32

@inline byte_perm(x::Int32, y::Int32, z::Int32) = @wrap __nv_byte_perm(x::i32, y::i32, z::i32)::i32

@inline popc(x::Int32) = @wrap __nv_popc(x::i32)::i32
@inline popc(x::Int64) = @wrap __nv_popcll(x::i64)::i32

## floating-point handling

@inline isfinite(x::Float32) = (@wrap __nv_finitef(x::float)::i32) != 0
@inline isfinite(x::Float64) = (@wrap __nv_isfinited(x::double)::i32) != 0

@inline isinf(x::Float32) = (@wrap __nv_isinfd(x::double)::i32) != 0
@inline isinf(x::Float64) = (@wrap __nv_isinff(x::float)::i32) != 0

@inline isnan(x::Float32) = (@wrap __nv_isnand(x::double)::i32) != 0
@inline isnan(x::Float64) = (@wrap __nv_isnanf(x::float)::i32) != 0

@inline nearbyint(x::Float64) = @wrap __nv_nearbyint(x::double)::double
@inline nearbyint(x::Float32) = @wrap __nv_nearbyintf(x::float)::float

@inline nextafter(x::Float64, y::Float64) = @wrap __nv_nextafter(x::double, y::double)::double
@inline nextafter(x::Float32, y::Float32) = @wrap __nv_nextafterf(x::float, y::float)::float

## sign handling

@inline signbit(x::Float64) = (@wrap __nv_signbitd(x::double)::i32) != 0
@inline signbit(x::Float32) = (@wrap __nv_signbitf(x::float)::i32) != 0

@inline copysign(x::Float64, y::Float64) = @wrap __nv_copysign(x::double, y::double)::double
@inline copysign(x::Float32, y::Float32) = @wrap __nv_copysignf(x::float, y::float)::float

@inline abs(x::Int32) =   @wrap __nv_abs(x::i32)::i32
@inline abs(f::Float64) = @wrap __nv_fabs(f::double)::double
@inline abs(f::Float32) = @wrap __nv_fabsf(f::float)::float
@inline abs(x::Int64) =   @wrap __nv_llabs(x::i64)::i64

## roots and powers

@inline sqrt(x::Float64) = @wrap __nv_sqrt(x::double)::double
@inline sqrt(x::Float32) = @wrap __nv_sqrtf(x::float)::float

@inline rsqrt(x::Float64) = @wrap __nv_rsqrt(x::double)::double
@inline rsqrt(x::Float32) = @wrap __nv_rsqrtf(x::float)::float

@inline cbrt(x::Float64) = @wrap __nv_cbrt(x::double)::double
@inline cbrt(x::Float32) = @wrap __nv_cbrtf(x::float)::float

@inline rcbrt(x::Float64) = @wrap __nv_rcbrt(x::double)::double
@inline rcbrt(x::Float32) = @wrap __nv_rcbrtf(x::float)::float

@inline pow(x::Float64, y::Float64) = @wrap __nv_pow(x::double, y::double)::double
@inline pow(x::Float32, y::Float32) = @wrap __nv_powf(x::float, y::float)::float
@inline pow(x::Float64, y::Int32) =   @wrap __nv_powi(x::double, y::i32)::double
@inline pow(x::Float32, y::Int32) =   @wrap __nv_powif(x::float, y::i32)::float

## rounding and selection

# TODO: differentiate in return type, map correctly
# @inline round(x::Float64) = @wrap __nv_llround(x::double)::i64
# @inline round(x::Float32) = @wrap __nv_llroundf(x::float)::i64
# @inline round(x::Float64) = @wrap __nv_round(x::double)::double
# @inline round(x::Float32) = @wrap __nv_roundf(x::float)::float

# TODO: differentiate in return type, map correctly
# @inline rint(x::Float64) = @wrap __nv_llrint(x::double)::i64
# @inline rint(x::Float32) = @wrap __nv_llrintf(x::float)::i64
# @inline rint(x::Float64) = @wrap __nv_rint(x::double)::double
# @inline rint(x::Float32) = @wrap __nv_rintf(x::float)::float

# TODO: would conflict with trunc usage in this module
# @inline trunc(x::Float64) = @wrap __nv_trunc(x::double)::double
# @inline trunc(x::Float32) = @wrap __nv_truncf(x::float)::float

@inline ceil(x::Float64) = @wrap __nv_ceil(x::double)::double
@inline ceil(x::Float32) = @wrap __nv_ceilf(x::float)::float

@inline floor(f::Float64) = @wrap __nv_floor(f::double)::double
@inline floor(f::Float32) = @wrap __nv_floorf(f::float)::float

@inline min(x::Int32, y::Int32) = @wrap __nv_min(x::i32, y::i32)::i32
@inline min(x::Int64, y::Int64) = @wrap __nv_llmin(x::i64, y::i64)::i64
@inline min(x::UInt32, y::UInt32) = convert(UInt32, @wrap __nv_umin(x::i32, y::i32)::i32)
@inline min(x::UInt64, y::UInt64) = convert(UInt64, @wrap __nv_ullmin(x::i64, y::i64)::i64)
@inline min(x::Float64, y::Float64) = @wrap __nv_fmin(x::double, y::double)::double
@inline min(x::Float32, y::Float32) = @wrap __nv_fminf(x::float, y::float)::float

@inline max(x::Int32, y::Int32) = @wrap __nv_max(x::i32, y::i32)::i32
@inline max(x::Int64, y::Int64) = @wrap __nv_llmax(x::i64, y::i64)::i64
@inline max(x::UInt32, y::UInt32) = convert(UInt32, @wrap __nv_umax(x::i32, y::i32)::i32)
@inline max(x::UInt64, y::UInt64) = convert(UInt64, @wrap __nv_ullmax(x::i64, y::i64)::i64)
@inline max(x::Float64, y::Float64) = @wrap __nv_fmax(x::double, y::double)::double
@inline max(x::Float32, y::Float32) = @wrap __nv_fmaxf(x::float, y::float)::float

@inline saturate(x::Float32) = @wrap __nv_saturatef(x::float)::float

## division and remainder

@inline mod(x::Float64, y::Float64) = @wrap __nv_fmod(x::double, y::double)::double
@inline mod(x::Float32, y::Float32) = @wrap __nv_fmodf(x::float, y::float)::float

@inline rem(x::Float64, y::Float64) = @wrap __nv_remainder(x::double, y::double)::double
@inline rem(x::Float32, y::Float32) = @wrap __nv_remainderf(x::float, y::float)::float

## gamma function

@inline lgamma(x::Float64) = @wrap __nv_lgamma(x::double)::double
@inline lgamma(x::Float32) = @wrap __nv_lgammaf(x::float)::float

@inline tgamma(x::Float64) = @wrap __nv_tgamma(x::double)::double
@inline tgamma(x::Float32) = @wrap __nv_tgammaf(x::float)::float

## Bessel

@inline j0(x::Float64) = @wrap __nv_j0(x::double)::double
@inline j0(x::Float32) = @wrap __nv_j0f(x::float)::float

@inline j1(x::Float64) = @wrap __nv_j1(x::double)::double
@inline j1(x::Float32) = @wrap __nv_j1f(x::float)::float

@inline jn(n::Int32, x::Float64) = @wrap __nv_jn(n::i32, x::double)::double
@inline jn(n::Int32, x::Float32) = @wrap __nv_jnf(n::i32, x::float)::float

@inline y0(x::Float64) = @wrap __nv_y0(x::double)::double
@inline y0(x::Float32) = @wrap __nv_y0f(x::float)::float

@inline y1(x::Float64) = @wrap __nv_y1(x::double)::double
@inline y1(x::Float32) = @wrap __nv_y1f(x::float)::float

@inline yn(n::Int32, x::Float64) = @wrap __nv_yn(n::i32, x::double)::double
@inline yn(n::Int32, x::Float32) = @wrap __nv_ynf(n::i32, x::float)::float

## distributions

@inline normcdf(x::Float64) = @wrap __nv_normcdf(x::double)::double
@inline normcdf(x::Float32) = @wrap __nv_normcdff(x::float)::float

@inline normcdfinv(x::Float64) = @wrap __nv_normcdfinv(x::double)::double
@inline normcdfinv(x::Float32) = @wrap __nv_normcdfinvf(x::float)::float

#
# Unsorted
#

@inline hypot(x::Float64, y::Float64) = @wrap __nv_hypot(x::double, y::double)::double
@inline hypot(x::Float32, y::Float32) = @wrap __nv_hypotf(x::float, y::float)::float

@inline fma(x::Float64, y::Float64, z::Float64) = @wrap __nv_fma(x::double, y::double, z::double)::double
@inline fma(x::Float32, y::Float32, z::Float32) = @wrap __nv_fmaf(x::float, y::float, z::float)::float

@inline sad(x::Int32, y::Int32, z::Int32) = @wrap __nv_sad(x::i32, y::i32, z::i32)::i32
@inline sad(x::UInt32, y::UInt32, z::UInt32) = convert(UInt32, @wrap __nv_usad(x::i32, y::i32, z::i32)::i32)

@inline dim(x::Float64, y::Float64) = @wrap __nv_fdim(x::double, y::double)::double
@inline dim(x::Float32, y::Float32) = @wrap __nv_fdimf(x::float, y::float)::float

@inline mul24(x::Int32, y::Int32) = @wrap __nv_mul24(x::i32, y::i32)::i32
@inline mul24(x::UInt32, y::UInt32) = convert(UInt32, @wrap __nv_umul24(x::i32, y::i32)::i32)

@inline mul64hi(x::Int64, y::Int64) = @wrap __nv_mul64hi(x::i64, y::i64)::i64
@inline mul64hi(x::UInt64, y::UInt64) = convert(UInt64, @wrap __nv_umul64hi(x::i64, y::i64)::i64)
@inline mulhi(x::Int32, y::Int32) = @wrap __nv_mulhi(x::i32, y::i32)::i32
@inline mulhi(x::UInt32, y::UInt32) = convert(UInt32, @wrap __nv_umulhi(x::i32, y::i32)::i32)

@inline hadd(x::Int32, y::Int32) = @wrap __nv_hadd(x::i32, y::i32)::i32
@inline hadd(x::UInt32, y::UInt32) = convert(UInt32, @wrap __nv_uhadd(x::i32, y::i32)::i32)

@inline rhadd(x::Int32, y::Int32) = @wrap __nv_rhadd(x::i32, y::i32)::i32
@inline rhadd(x::UInt32, y::UInt32) = convert(UInt32, @wrap __nv_urhadd(x::i32, y::i32)::i32)

@inline scalbn(x::Float64, y::Int32) = @wrap __nv_scalbn(x::double, y::i32)::double
@inline scalbn(x::Float32, y::Int32) = @wrap __nv_scalbnf(x::float, y::i32)::float


text:
using BenchmarkTools
BenchmarkTools.DEFAULT_PARAMETERS.samples = 100

compute(n::Int)::Int = sum(powermod.(1:n, 1:n, 10_000_000_000)) % 10_000_000_000

compute(10)

compute(1_000)

@benchmark compute(1_000)

ext: jl

max_stars_repo_name: Dynortice/Project-Euler

max_issues_repo_name: Dynortice/Project-Euler

max_forks_repo_name: Dynortice/Project-Euler


text:
module js

import GR

pxwidth = 640
pxheight = 480

@static if VERSION < v"0.7.0-DEV.4762"
    macro cfunction(f, rt, tup)
        :(Base.cfunction($(esc(f)), $(esc(rt)), Tuple{$(esc(tup))...}))
    end
end

id_count = 0
js_running = false

mutable struct JSTermWidget
    identifier::String
    width::Int
    height::Int
    visible::Bool
end

function inject_js()
  global comm
  comm = nothing
  _js_fallback = "https://gr-framework.org/downloads/gr-latest.js"
  _gr_js = if isfile(joinpath(ENV["GRDIR"], "lib", "gr.js"))
    _gr_js = try
      _gr_js = open(joinpath(ENV["GRDIR"], "lib", "gr.js")) do f
        _gr_js = read(f, String)
        _gr_js
      end
    catch e
      nothing
    end
    _gr_js
  end
  _jsterm = """
    if (typeof grJSTermRunning === 'undefined') {
      function JSTerm() {
        this.BOXZOOM_THRESHOLD = 5;
        this.BOXZOOM_TRIGGER_THRESHHOLD = 1000;

        function init() {
          this.onready = [];
          this.gr = [];
          this.args = [];
          this.canvas = [];
          this.overlay_canvas = [];
          this.comm = undefined;
          this.waiting = [];
          this.oncanv = [];
          this.panning = false;
          this.prev_mouse_pos = undefined;
          this.boxzoom = false;
          this.keep_aspect_ratio = true;
          this.boxzoom_point = [0, 0];
          this.boxzoomTriggerTimeout = undefined;
        }

        init();

        function saveLoad(url, callback, maxtime) {
          let script = document.createElement('script');
          script.onload = function() {
            callback();
          };
          script.onerror = function() {
            console.error(url + ' can not be loaded.');
          };
          script.src = url;
          document.head.appendChild(script);
          setTimeout(function() {
            if (!grJstermReady) {
              console.error(url + ' can not be loaded.');
            }
          }, maxtime);
        }

        function getCoords(canv, event) {
          let rect = canv.getBoundingClientRect();
          //TODO mind the canvas-padding if necessary!
          return [event.clientX - rect.left, event.clientY - rect.top];
        }

        function jsLoaded() {
          grJstermReady = true;
          for (let i = 0; i < this.onready.length; i++) {
            this.onready[i]();
          }
          this.onready = [];
        }

        function canvas_removed(id) {
          this.comm.send({
            "type": "removed",
            "content": id
          })
        }

        function handlewheel(event, canv, comm, gr, args) {
          if (typeof this.boxzoomTriggerTimeout !== 'undefined') {
            clearTimeout(this.boxzoomTriggerTimeout);
          }
          let mouseargs = gr.newmeta();
          let coords = getCoords(canv, event);
          gr.meta_args_push(mouseargs, "x", "i", [coords[0]]);
          gr.meta_args_push(mouseargs, "y", "i", [coords[1]]);
          gr.meta_args_push(mouseargs, "angle_delta", "d", [event.deltaY]);
          gr.dumpmeta_json(mouseargs, gr.get_stdout());
          gr.inputmeta(mouseargs);
          gr.plotmeta();
          event.preventDefault();
        }

        function handleMousedown(event, canv, comm, gr, args) {
          if (typeof this.boxzoomTriggerTimeout !== 'undefined') {
            clearTimeout(this.boxzoomTriggerTimeout);
          }
          if (event.button == 0) {
            canv.style.cursor = 'move';
            this.panning = true;
            this.boxzoom = false;
            this.prev_mouse_pos = getCoords(canv, event);
            this.boxzoomTriggerTimeout = setTimeout(function() {startBoxzoom(event, canv)}, this.BOXZOOM_TRIGGER_THRESHHOLD);
          } else if (event.button == 2) {
            startBoxzoom(event, canv);
          }
        }

        function startBoxzoom(event, canv) {
          this.panning = false;
          this.boxzoom = true;
          if (event.ctrlKey) {
            this.keep_aspect_ratio = false;
          }
          this.boxzoom_point = getCoords(canv, event);
          canv.style.cursor = 'nwse-resize';
        }

        function handleMouseup(event, canv, comm, gr, args) {
          if (typeof this.boxzoomTriggerTimeout !== 'undefined') {
            clearTimeout(this.boxzoomTriggerTimeout);
          }
          if (this.boxzoom) {
            let coords = getCoords(canv, event);
            if ((Math.abs(this.boxzoom_point[0] - coords[0]) >= this.BOXZOOM_THRESHOLD) && (Math.abs(this.boxzoom_point[1] - coords[1]) >= this.BOXZOOM_THRESHOLD)) {
              let mouseargs = gr.newmeta();
              if (this.boxzoom_point[0] < coords[0]) {
                gr.meta_args_push(mouseargs, "left", "i", [this.boxzoom_point[0]]);
                gr.meta_args_push(mouseargs, "right", "i", [coords[0]]);
              } else {
                gr.meta_args_push(mouseargs, "right", "i", [this.boxzoom_point[0]]);
                gr.meta_args_push(mouseargs, "left", "i", [coords[0]]);
              }
              if (this.boxzoom_point[1] < coords[1]) {
                gr.meta_args_push(mouseargs, "top", "i", [this.boxzoom_point[1]]);
                gr.meta_args_push(mouseargs, "bottom", "i", [coords[1]]);
              } else {
                gr.meta_args_push(mouseargs, "bottom", "i", [this.boxzoom_point[1]]);
                gr.meta_args_push(mouseargs, "top", "i", [coords[1]]);
              }
              if (this.keep_aspect_ratio) {
                gr.meta_args_push(mouseargs, "keep_aspect_ratio", "i", [1]);
              } else {
                gr.meta_args_push(mouseargs, "keep_aspect_ratio", "i", [0]);
              }
              gr.inputmeta(mouseargs);
              gr.plotmeta();
            }
          } else if (this.panning) {
            this.prev_mouse_pos = undefined;
          }
          canv.style.cursor = 'auto';
          this.panning = false;
          this.boxzoom = false;
          this.keep_aspect_ratio = true;
          let context = canv.getContext('2d');
          context.clearRect(0, 0, canv.width, canv.height);
        }

        function handleMouseleave(event, canv, comm, gr, args) {
          if (typeof this.boxzoomTriggerTimeout !== 'undefined') {
            clearTimeout(this.boxzoomTriggerTimeout);
          }
          canv.style.cursor = 'auto';
          this.panning = false;
          this.prev_mouse_pos = undefined;
          if (this.boxzoom) {
            let context = canv.getContext('2d');
            context.clearRect(0, 0, canv.width, canv.height);
          }
          this.boxzoom = false;
          this.keep_aspect_ratio = true;
        }

        function handleMousemove(event, canv, comm, gr, args) {
          let coords = getCoords(canv, event);
          if (this.panning) {
            if (typeof this.boxzoomTriggerTimeout !== 'undefined') {
              clearTimeout(this.boxzoomTriggerTimeout);
            }
            let mouseargs = gr.newmeta();
            gr.meta_args_push(mouseargs, "x", "i", [this.prev_mouse_pos[0]]);
            gr.meta_args_push(mouseargs, "y", "i", [this.prev_mouse_pos[1]]);
            gr.meta_args_push(mouseargs, "xshift", "i", [coords[0] - this.prev_mouse_pos[0]]);
            gr.meta_args_push(mouseargs, "yshift", "i", [coords[1] - this.prev_mouse_pos[1]]);
            gr.inputmeta(mouseargs);
            gr.plotmeta();
            this.prev_mouse_pos = [coords[0], coords[1]];
            event.preventDefault();
          } else if (this.boxzoom) {
            let context = canv.getContext('2d');
            let mousex = coords[0];
            let mousey = coords[1];
            let diff = [mousex - this.boxzoom_point[0], mousey - this.boxzoom_point[1]];
            if (this.keep_aspect_ratio) {
              if (Math.abs(diff[0]) / canv.width > Math.abs(diff[1]) / canv.height) {
                let factor = Math.abs(coords[0] - this.boxzoom_point[0]) / canv.width
                diff[1] = Math.sign(diff[1]) * factor * canv.height;
              } else {
                let factor = Math.abs(coords[1] - this.boxzoom_point[1]) / canv.height;
                diff[0] = Math.sign(diff[0]) * factor * canv.width;
              }
            }
            context.clearRect(0, 0, canv.width, canv.height);
            if (diff[0] * diff[1] >= 0) {
              canv.style.cursor = 'nwse-resize';
            } else {
              canv.style.cursor = 'nesw-resize';
            }
            context.fillStyle = '#FFAAAA';
            context.strokeStyle = '#FF0000';
            context.beginPath();
            context.rect(this.boxzoom_point[0], this.boxzoom_point[1], diff[0], diff[1]);
            context.globalAlpha = 0.2;
            context.fill();
            context.globalAlpha = 1.0;
            context.stroke();
            context.closePath();
            event.preventDefault();
          }
        }

        function handleDoubleclick(event, canv, comm, gr, args) {
          let mouseargs = gr.newmeta();
          let coords = getCoords(canv, event);
          gr.meta_args_push(mouseargs, "x", "i", [coords[0]]);
          gr.meta_args_push(mouseargs, "y", "i", [coords[1]]);
          gr.meta_args_push(mouseargs, "key", "s", "r");
          gr.inputmeta(mouseargs);
          gr.plotmeta();
          event.preventDefault();
        }

        function draw(msg) {
          if (typeof this.waiting['jsterm-' + msg.content.data.canvasid] === 'undefined') {
            this.waiting['jsterm-' + msg.content.data.canvasid] = false;
          }
          if (!grJstermReady) {
            this.onready.push(function() {
              return draw(msg);
            });
          } else if (!GR.is_ready) {
            GR.ready(function() {
              return draw(msg);
            });
          } else if (this.waiting['jsterm-' + msg.content.data.canvasid]) {
            this.oncanv['jsterm-' + msg.content.data.canvasid] = function() {
              return draw(msg);
            };
          } else {
            if (document.getElementById('jsterm-' + msg.content.data.canvasid) == null) {
              canvas_removed(msg.content.data.canvasid);
              this.canvas['jsterm-' + msg.content.data.canvasid] = undefined;
              this.waiting['jsterm-' + msg.content.data.canvasid] = true;
              this.oncanv['jsterm-' + msg.content.data.canvasid] = undefined;
              this.oncanv['jsterm-' + msg.content.data.canvasid] = function() {
                return draw(msg);
              };
              setTimeout(function() {
                refr_plot('jsterm-' + msg.content.data.canvasid, msg, 0);
              }, 100);
            } else {
              if (typeof this.canvas['jsterm-' + msg.content.data.canvasid] === 'undefined') {
                this.canvas['jsterm-' + msg.content.data.canvasid] = document.getElementById('jsterm-' + msg.content.data.canvasid);
                this.overlay_canvas['jsterm-' + msg.content.data.canvasid] = document.getElementById('jsterm-overlay-' + msg.content.data.canvasid);
                this.overlay_canvas['jsterm-' + msg.content.data.canvasid].addEventListener('DOMNodeRemoved', function() {
                  canvas_removed(msg.content.data.canvasid);
                  this.waiting['jsterm-' + msg.content.data.canvasid] = true;
                  this.oncanv['jsterm-' + msg.content.data.canvasid] = undefined;
                });
                this.overlay_canvas['jsterm-' + msg.content.data.canvasid].addEventListener('wheel', function(evt) {
                  handlewheel(evt, this.overlay_canvas['jsterm-' + msg.content.data.canvasid], this.comm, this.gr['jsterm-' + msg.content.data.canvasid], this.args['jsterm-' + msg.content.data.canvasid]);
                }.bind(this));
                this.overlay_canvas['jsterm-' + msg.content.data.canvasid].addEventListener('mousedown', function(evt) {
                  handleMousedown(evt, this.overlay_canvas['jsterm-' + msg.content.data.canvasid], this.comm, this.gr['jsterm-' + msg.content.data.canvasid], this.args['jsterm-' + msg.content.data.canvasid]);
                }.bind(this));
                this.overlay_canvas['jsterm-' + msg.content.data.canvasid].addEventListener('mousemove', function(evt) {
                  handleMousemove(evt, this.overlay_canvas['jsterm-' + msg.content.data.canvasid], this.comm, this.gr['jsterm-' + msg.content.data.canvasid], this.args['jsterm-' + msg.content.data.canvasid]);
                }.bind(this));
                this.overlay_canvas['jsterm-' + msg.content.data.canvasid].addEventListener('mouseup', function(evt) {
                  handleMouseup(evt, this.overlay_canvas['jsterm-' + msg.content.data.canvasid], this.comm, this.gr['jsterm-' + msg.content.data.canvasid], this.args['jsterm-' + msg.content.data.canvasid]);
                }.bind(this));
                this.overlay_canvas['jsterm-' + msg.content.data.canvasid].addEventListener('mouseleave', function(evt) {
                  handleMouseleave(evt, this.overlay_canvas['jsterm-' + msg.content.data.canvasid], this.comm, this.gr['jsterm-' + msg.content.data.canvasid], this.args['jsterm-' + msg.content.data.canvasid]);
                }.bind(this));
                this.overlay_canvas['jsterm-' + msg.content.data.canvasid].addEventListener('dblclick', function(evt) {
                  handleDoubleclick(evt, this.overlay_canvas['jsterm-' + msg.content.data.canvasid], this.comm, this.gr['jsterm-' + msg.content.data.canvasid], this.args['jsterm-' + msg.content.data.canvasid]);
                }.bind(this));
                this.overlay_canvas['jsterm-' + msg.content.data.canvasid].addEventListener('contextmenu', function(event) {
                  event.preventDefault();
                  return false;
                });
                this.overlay_canvas['jsterm-' + msg.content.data.canvasid].style.cursor = 'auto';
              }
              if (typeof this.gr['jsterm-' + msg.content.data.canvasid] === 'undefined') {
                this.gr['jsterm-' + msg.content.data.canvasid] = new GR('jsterm-' + msg.content.data.canvasid);
              }
              if (!this.args.hasOwnProperty('jsterm-' + msg.content.data.canvasid) || typeof this.args['jsterm-' + msg.content.data.canvasid] === 'undefined') {
                this.args['jsterm-' + msg.content.data.canvasid] = this.gr['jsterm-' + msg.content.data.canvasid].newmeta();
              }
              this.waiting['jsterm-' + msg.content.data.canvasid] = false;
              this.gr['jsterm-' + msg.content.data.canvasid].current_canvas = document.getElementById('jsterm-' + msg.content.data.canvasid);
              this.gr['jsterm-' + msg.content.data.canvasid].current_context = this.gr['jsterm-' + msg.content.data.canvasid].current_canvas.getContext('2d');
              this.gr['jsterm-' + msg.content.data.canvasid].select_canvas();
              this.gr['jsterm-' + msg.content.data.canvasid].meta_args_push(this.args['jsterm-' + msg.content.data.canvasid], "size", "dd", [this.canvas['jsterm-' + msg.content.data.canvasid].width, this.canvas['jsterm-' + msg.content.data.canvasid].height]);
              this.gr['jsterm-' + msg.content.data.canvasid].readmeta(this.args['jsterm-' + msg.content.data.canvasid], msg.content.data.json);
              this.gr['jsterm-' + msg.content.data.canvasid].plotmeta(this.args['jsterm-' + msg.content.data.canvasid]);
            }
          }
        }

        function refr_plot(id, msg, count) {
          /*if (count >= 100) {
            return;
          }*/
          // TODO: global count, that resets on new message / activity
          if (document.getElementById(id) == null) {
            setTimeout(function() {
              refr_plot(id, msg, count + 1);
            }, 100);
          } else {
            this.waiting['jsterm-' + msg.content.data.canvasid] = false;
            if (typeof this.oncanv['jsterm-' + msg.content.data.canvasid] !== 'undefined') {
              this.oncanv['jsterm-' + msg.content.data.canvasid]();
            }
          }
        }

        function register_comm(kernel) {
          kernel.comm_manager.register_target('jsterm_comm', function(comm) {
            comm.on_msg(function(msg) {
              draw(msg);
            });
            comm.on_close(function() {});
            window.addEventListener('beforeunload', function(e) {
              comm.close();
            });
            this.comm = comm;
          });
        }

        function onLoad() {
          let kernel = Jupyter.notebook.kernel;
          if (typeof kernel === 'undefined' || kernel == null) {
            console.error('JSTerm: No kernel detected');
            return;
          }
          register_comm(kernel);
          Jupyter.notebook.events.on('kernel_ready.Kernel', function() {
            kernel = IPython.notebook.kernel;
            init();
            register_comm(kernel);
          });
        }
        onLoad();
      }
      JSTerm();
    }
    var grJSTermRunning = true;"""
  if _gr_js === nothing
      _gr_js = string("""
        JSTerm.saveLoad('""", _js_fallback, """', jsLoaded, 10000);
        var grJstermReady = false;
      """)
  else
    _gr_js = string(_gr_js, "var grJstermReady = true;")
  end
  display(HTML(string("""
    <script type="text/javascript">
      """, _gr_js, """
      """, _jsterm, """
    </script>
  """)))
end

function JSTermWidget(name::String, id::Int64)
  global id_count, js_running
  if GR.isijulia()
    id_count += 1
    if !js_running
      inject_js()
      js_running = true
    end
    JSTermWidget(string(name, id), pxwidth, pxheight, false)
  else
    error("JSTermWidget is only available in IJulia environments")
  end
end

function jsterm_display(widget::JSTermWidget)
  global pxwidth, pxheight
  if GR.isijulia()
    display(HTML(string("<div style=\"position: relative; width: ", widget.width, "px; height: ", widget.height, "px;\"><canvas id=\"jsterm-overlay-", widget.identifier, "\" style=\"position:absolute; top: 0; right: 0; z-index: 1;\" width=\"", widget.width, "\" height=\"", widget.height, "\"></canvas><canvas id=\"jsterm-", widget.identifier, "\" style=\"position: absolute; top: 0; right: 0; z-index: 0;\"width=\"", widget.width, "\" height=\"", widget.height, "\"></canvas>")))
    widget.visible = true
  else
    error("jsterm_display is only available in IJulia environments")
  end
end

comm = nothing

function jsterm_send(widget::JSTermWidget, data::String)
  global js_running, draw_condition, comm, PXWIDTH, PXHEIGHT
  if GR.isijulia()
    if comm === nothing
      comm = Main.IJulia.Comm("jsterm_comm")
      comm.on_close = function comm_close_callback(msg)
        global js_running
        js_running = false
      end
      comm.on_msg = function comm_msg_callback(msg)
        if haskey(msg.content["data"], "type")
          if msg.content["data"]["type"] == "removed"
            jswidgets[msg.content["data"]["content"]].visible = false
            jsterm_display(widget)
          end
        end
      end
    end
    Main.IJulia.send_comm(comm, Dict("json" => data, "canvasid" => widget.identifier))
  else
    error("jsterm_send is only available in IJulia environments")
  end
end

function recv(name::Cstring, id::Int64, msg::Cstring)
    # receives string from C and sends it to JS via Comm
    global js_running
    if !js_running
      inject_js()
      js_running = true
    end
    _name = unsafe_string(name)
    if haskey(jswidgets, string(_name, id))
        widget = jswidgets[string(_name, id)]
    else
        widget = JSTermWidget(_name, id)
        jswidgets[string(_name, id)] = widget
    end
    if !widget.visible
      jsterm_display(widget)
    end
    jsterm_send(widget, unsafe_string(msg))
    return convert(Int32, 1)
end

function send(name::Cstring, id::Int64)
    # Dummy function, not in use
    return convert(Cstring, "String")
end

jswidgets = nothing
send_c = nothing
recv_c = nothing

function initjs()
    global jswidgets, send_c, recv_c

    jswidgets = Dict{String, JSTermWidget}()
    send_c = @cfunction(send, Cstring, (Cstring, Int64))
    recv_c = @cfunction(recv, Int32, (Cstring, Int64, Cstring))
    send_c, recv_c
end

end # module


text:
"""
    number_of_dimensions(all_vars...)

Return the number of unique dimensions for the given variables `all_vars`.
"""
function number_of_dimensions(all_vars::Vararg{Unitful.Dimensions})
    return length(unique_dims(all_vars...))
end

number_of_dimensions(all_vars::Vararg{Pair{<:AbstractString,<:QuantityOrUnitlike}}) =
number_of_dimensions([var.second for var in all_vars]...)

number_of_dimensions(all_vars::Vararg{<:QuantityOrUnits}) =
number_of_dimensions(dimension.(all_vars)...)

"""
    number_of_dimensionless(all_vars...)

Return the number of dimensionless numbers that can be constructed for a problem characterized by the variables `all_vars`.
"""
function number_of_dimensionless(all_vars::Vararg{<:QuantityOrUnitlike})
    return length(all_vars) - number_of_dimensions(all_vars...)
end

number_of_dimensionless(all_vars::Vararg{Pair{<:AbstractString,<:QuantityOrUnitlike}}) =
number_of_dimensionless([var.second for var in all_vars]...)

"""
    dimensionless(quantity, basis)

Make a `quantity` dimensionless using a dimensional `basis`.
"""
function dimensionless(quantity::Unitful.AbstractQuantity, basis::QuantityDimBasis)
    dim_mat = dim_matrix(basis.basis_dims, quantity)
    basis_facs = prod(basis.basis_vectors .^ (basis.dim_mat \ dim_mat))
    return ustrip(uconvert(Unitful.NoUnits, quantity / basis_facs))
end

"""
    dimensionful(value, unit, basis)

Restore the `unit`s of a dimensionless `value` using a dimensional `basis`.
"""
function dimensionful(value::Number, unit::Unitful.Units, basis::QuantityDimBasis)
    dim_vec = dim_matrix(basis.basis_dims, dimension(unit))
    return uconvert(unit, value * prod(basis.basis_vectors .^ (basis.dim_mat \ dim_vec)))
end

"""
    current_to_new_fac(dims, basis, new_basis)

Return the factor that is needed to transform specified dimensions `dims` from a current `basis` to a `new_basis`.
"""
function current_to_new_fac(dims::Unitful.Dimensions, basis::QuantityDimBasis, new_basis::QuantityDimBasis)
    dim_mat = dim_matrix(basis.basis_dims, dims)
    new_dim_mat = dim_matrix(new_basis.basis_dims, dims)
    basis_fac = prod(basis.basis_vectors .^ (basis.dim_mat \ dim_mat))
    new_basis_multipler = prod(new_basis.basis_vectors .^ (new_basis.dim_mat \ new_dim_mat))
    current_to_new_fac = new_basis_multipler / basis_fac
    return ustrip(uconvert(Unitful.NoUnits, current_to_new_fac))
end

"""
    change_basis(var, basis, new_basis)

Transform the specified quantity or unit `var` from a current `basis` to a `new_basis`.
"""
change_basis(var::Unitful.AbstractQuantity, basis::QuantityDimBasis, new_basis::QuantityDimBasis) =
var * current_to_new_fac(dimension(var), basis, new_basis)

change_basis(var::Unitful.Units, basis::QuantityDimBasis, new_basis::QuantityDimBasis) =
current_to_new_fac(dimension(var), basis, new_basis)


text:
"""
`FNode` is a node used for formatting which does not have a `CSTParser` equivalent.
"""
@enum(
    FNode,

    # leaf nodes
    NEWLINE,
    SEMICOLON,
    WHITESPACE,
    PLACEHOLDER,
    NOTCODE,
    INLINECOMMENT,
    TRAILINGCOMMA,
    TRAILINGSEMICOLON,
    INVERSETRAILINGSEMICOLON,

    # no equivalent in CSTParser
    MacroBlock,
)

@enum(NestBehavior, AllowNest, AlwaysNest, NeverNest, NeverNestNode)

"""
Formatted Syntax Tree
"""
mutable struct FST
    typ::Union{CSTParser.Head,FNode}

    # Start and end lines of the node
    # in the original source file.
    startline::Int
    endline::Int

    indent::Int
    len::Int
    val::Union{Nothing,AbstractString}
    nodes::Union{Nothing,Vector{FST}}
    ref::Union{Nothing,Ref{CSTParser.EXPR}}
    nest_behavior::NestBehavior

    # Extra margin caused by parent nodes.
    # i.e. `(f(arg))`
    #
    # `f(arg)` would have `extra_margin` = 1
    # due to `)` after `f(arg)`.
    extra_margin::Int
    line_offset::Int
end

FST(cst::CSTParser.EXPR, indent::Int) =
    FST(cst.typ, -1, -1, indent, 0, nothing, FST[], Ref(cst), AllowNest, 0, -1)

FST(typ::CSTParser.Head, indent::Int) =
    FST(typ, -1, -1, indent, 0, nothing, FST[], nothing, AllowNest, 0, -1)

function FST(
    cst::CSTParser.EXPR,
    line_offset::Int,
    startline::Int,
    endline::Int,
    val::AbstractString,
)
    FST(
        cst.typ,
        startline,
        endline,
        0,
        length(val),
        val,
        nothing,
        Ref(cst),
        AllowNest,
        0,
        line_offset,
    )
end

function FST(
    typ::CSTParser.Head,
    line_offset::Int,
    startline::Int,
    endline::Int,
    val::AbstractString,
)
    FST(
        typ,
        startline,
        endline,
        0,
        length(val),
        val,
        nothing,
        nothing,
        AllowNest,
        0,
        line_offset,
    )
end

@inline function Base.setindex!(fst::FST, node::FST, ind::Int)
    fst.len -= fst.nodes[ind].len
    fst.nodes[ind] = node
    fst.len += node.len
end
@inline Base.getindex(fst::FST, inds...) = fst.nodes[inds...]
@inline Base.lastindex(fst::FST) = length(fst.nodes)
@inline Base.firstindex(fst::FST) = 1
@inline Base.length(fst::FST) = fst.len
@inline function Base.iterate(fst::FST, state = 1)
    if state > length(fst.nodes)
        return nothing
    end
    return fst.nodes[state], state + 1
end

@inline function Base.insert!(fst::FST, ind::Int, node::FST)
    insert!(fst.nodes, ind, node)
    fst.len += node.len
    return
end

@inline Newline(; length = 0, nest_behavior = AllowNest) =
    FST(NEWLINE, -1, -1, 0, length, "\n", nothing, nothing, nest_behavior, 0, -1)
@inline Semicolon() = FST(SEMICOLON, -1, -1, 0, 1, ";", nothing, nothing, AllowNest, 0, -1)
@inline TrailingComma() =
    FST(TRAILINGCOMMA, -1, -1, 0, 0, "", nothing, nothing, AllowNest, 0, -1)
@inline TrailingSemicolon() =
    FST(TRAILINGSEMICOLON, -1, -1, 0, 0, "", nothing, nothing, AllowNest, 0, -1)
@inline InverseTrailingSemicolon() =
    FST(INVERSETRAILINGSEMICOLON, -1, -1, 0, 1, ";", nothing, nothing, AllowNest, 0, -1)
@inline Whitespace(n) =
    FST(WHITESPACE, -1, -1, 0, n, " "^n, nothing, nothing, AllowNest, 0, -1)
@inline Placeholder(n) =
    FST(PLACEHOLDER, -1, -1, 0, n, " "^n, nothing, nothing, AllowNest, 0, -1)
@inline Notcode(startline, endline) =
    FST(NOTCODE, startline, endline, 0, 0, "", nothing, nothing, AllowNest, 0, -1)
@inline InlineComment(line) =
    FST(INLINECOMMENT, line, line, 0, 0, "", nothing, nothing, AllowNest, 0, -1)

@inline must_nest(fst::FST) = fst.nest_behavior === AlwaysNest
@inline cant_nest(fst::FST) = fst.nest_behavior === NeverNest
@inline can_nest(fst::FST) = fst.nest_behavior === AllowNest

@inline is_leaf(cst::CSTParser.EXPR) = cst.args === nothing
@inline is_leaf(fst::FST) = fst.nodes === nothing

@inline is_punc(cst::CSTParser.EXPR) = CSTParser.ispunctuation(cst)
@inline is_punc(fst::FST) = fst.typ === CSTParser.PUNCTUATION
@inline is_end(x) = x.typ === CSTParser.KEYWORD && x.val == "end"
@inline is_colon(x) = x.typ === CSTParser.OPERATOR && x.val == ":"
@inline is_comma(fst::FST) =
    (fst.typ === CSTParser.PUNCTUATION && fst.val == ",") || fst.typ === TRAILINGCOMMA
@inline is_comment(fst::FST) = fst.typ === INLINECOMMENT || fst.typ === NOTCODE

@inline is_colon_op(cst::CSTParser.EXPR) =
    (cst.typ === CSTParser.BinaryOpCall && cst[2].kind === Tokens.COLON) ||
    cst.typ === CSTParser.ColonOpCall

@inline is_colon_op(fst::FST) =
    (fst.typ === CSTParser.BinaryOpCall && op_kind(fst) === Tokens.COLON) ||
    fst.typ === CSTParser.ColonOpCall

@inline function is_number(cst::CSTParser.EXPR)
    cst.typ === CSTParser.LITERAL || return false
    return cst.kind === Tokens.INTEGER || cst.kind === Tokens.FLOAT
end

function is_multiline(fst::FST)
    fst.typ === CSTParser.StringH && return true
    if fst.typ === CSTParser.x_Str && fst[2].typ === CSTParser.StringH
        return true
    elseif fst.typ === CSTParser.x_Cmd && fst[2].typ === CSTParser.StringH
        return true
    elseif fst.typ === CSTParser.Vcat && fst.endline > fst.startline
        return true
    elseif fst.typ === CSTParser.TypedVcat && fst.endline > fst.startline
        return true
    end
    false
end

function is_importer_exporter(fst::FST)
    fst.typ === CSTParser.Import && return true
    fst.typ === CSTParser.Export && return true
    fst.typ === CSTParser.Using && return true
    return false
end

@inline is_macrocall(fst::FST) = fst.typ === CSTParser.MacroCall || fst.typ === MacroBlock

function is_macrodoc(cst::CSTParser.EXPR)
    return cst.typ === CSTParser.MacroCall &&
           length(cst) == 3 &&
           cst[1].typ === CSTParser.MacroName &&
           cst[1][2].val == "doc" &&
           is_str(cst[2])
end

# f a function which returns a bool
function parent_is(cst::CSTParser.EXPR, valid; ignore = _ -> false)
    p = cst.parent
    p === nothing && return false
    while p !== nothing && ignore(p)
        p = p.parent
    end
    valid(p)
end

contains_comment(nodes::Vector{FST}) = findfirst(is_comment, nodes) !== nothing
function contains_comment(fst::FST)
    is_leaf(fst) && return false
    contains_comment(fst.nodes)
end

# TODO: Remove once this is fixed in CSTParser.
# https://github.com/julia-vscode/CSTParser.jl/issues/108
function get_args(cst::CSTParser.EXPR)
    if cst.typ === CSTParser.MacroCall ||
       cst.typ === CSTParser.TypedVcat ||
       cst.typ === CSTParser.Ref ||
       cst.typ === CSTParser.Curly ||
       cst.typ === CSTParser.Call
        return get_args(cst.args[2:end])
    elseif cst.typ === CSTParser.WhereOpCall
        # get the arguments in B of `A where B`
        return get_args(cst.args[3:end])
    elseif cst.typ === CSTParser.Braces ||
           cst.typ === CSTParser.Vcat ||
           cst.typ === CSTParser.BracesCat ||
           cst.typ === CSTParser.TupleH ||
           cst.typ === CSTParser.Vect ||
           cst.typ === CSTParser.InvisBrackets ||
           cst.typ === CSTParser.Parameters
        return get_args(cst.args)
    end
    CSTParser.get_args(cst)
end

function get_args(args::Vector{CSTParser.EXPR})
    args0 = CSTParser.EXPR[]
    for a in args
        CSTParser.ispunctuation(a) && continue
        if CSTParser.typof(a) === CSTParser.Parameters
            for j = 1:length(a.args)
                parg = a[j]
                CSTParser.ispunctuation(parg) && continue
                push!(args0, parg)
            end
        else
            push!(args0, a)
        end
    end
    args0
end
@inline n_args(x) = length(get_args(x))

function add_node!(t::FST, n::FST, s::State; join_lines = false, max_padding = -1)
    if n.typ === SEMICOLON
        join_lines = true
        loc =
            s.offset > length(s.doc.text) && t.typ === CSTParser.TopLevel ?
            cursor_loc(s, s.offset - 1) : cursor_loc(s)
        for l = t.endline:loc[1]
            if has_semicolon(s.doc, l)
                n.startline = l
                n.endline = l
                break
            end
        end

        # If there's no semicolon, treat it
        # as a FNode
        if n.startline == -1
            t.len += length(n)
            n.startline = t.endline
            n.endline = t.endline
            push!(t.nodes, n)
            return
        end
    elseif n.typ === TRAILINGCOMMA
        en = t.nodes[end]
        if en.typ === CSTParser.Generator ||
           en.typ === CSTParser.Filter ||
           en.typ === CSTParser.Flatten ||
           en.typ === CSTParser.MacroCall ||
           en.typ === MacroBlock ||
           (is_comma(en) && t.typ === CSTParser.TupleH && n_args(t.ref[]) == 1)
            # don't insert trailing comma in these cases
        elseif is_comma(en)
            t.nodes[end] = n
            t.len -= 1
        else
            t.len += length(n)
            n.startline = t.startline
            n.endline = t.endline
            push!(t.nodes, n)
        end
        return
    elseif n.typ === NOTCODE
        n.indent = s.indent
        push!(t.nodes, n)
        return
    elseif n.typ === INLINECOMMENT
        push!(t.nodes, n)
        return
    elseif n.typ isa FNode && is_leaf(n)
        t.len += length(n)
        n.startline = t.startline
        n.endline = t.endline
        push!(t.nodes, n)
        return
    end

    if n.typ === CSTParser.Block && length(n) == 0
        push!(t.nodes, n)
        return
    elseif n.typ === CSTParser.Parameters
        # unpack Parameters arguments into the parent node
        if n_args(t.ref[]) == n_args(n.ref[])
            # There are no arguments prior to params
            # so we can remove the initial placeholder.
            idx = findfirst(n -> n.typ === PLACEHOLDER, t.nodes)
            idx !== nothing && (t[idx] = Whitespace(0))
        end
        add_node!(t, Semicolon(), s)

        if length(n.nodes) > 0
            nws = 1
            if (t.typ === CSTParser.Curly || t.typ === CSTParser.WhereOpCall) &&
               !s.opts.whitespace_typedefs
                nws = 0
            end
            multi_arg = n_args(t.ref[]) > 0
            multi_arg ? add_node!(t, Placeholder(nws), s) : add_node!(t, Whitespace(nws), s)
        end
        for nn in n.nodes
            add_node!(t, nn, s, join_lines = true)
        end
        return
    elseif s.opts.import_to_using && n.typ === CSTParser.Import
        usings = import_to_usings(n, s)
        if length(usings) > 0
            for nn in usings
                add_node!(t, nn, s, join_lines = false, max_padding = 0)
            end
            return
        end
    elseif n.typ === CSTParser.BinaryOpCall &&
           n[1].typ === CSTParser.BinaryOpCall &&
           n[1][end].typ === CSTParser.WhereOpCall
        # normalize FST representation for WhereOpCall
        binaryop_to_whereop!(n, s)
    end

    if length(t.nodes) == 0
        t.startline = n.startline
        t.endline = n.endline
        t.len += length(n)
        t.line_offset = n.line_offset
        push!(t.nodes, n)
        return
    end

    if !is_prev_newline(t.nodes[end])
        current_line = t.endline
        notcode_startline = current_line + 1
        notcode_endline = n.startline - 1
        nt = t.nodes[end].typ

        if notcode_startline <= notcode_endline
            # If there are comments in between node elements
            # nesting is forced in an effort to preserve them.

            rm_block_nl =
                s.opts.remove_extra_newlines &&
                t.typ !== CSTParser.ModuleH &&
                (n.typ === CSTParser.Block || is_end(n))

            if remove_empty_notcode(t) || rm_block_nl
                nest = false
                for l = notcode_startline:notcode_endline
                    if hascomment(s.doc, l)
                        nest = true
                        break
                    end
                end
                if !nest
                    if rm_block_nl
                        add_node!(t, Newline(), s)
                    end
                    @goto add_node_end
                end
            end

            t.nest_behavior = AlwaysNest

            # If the previous node type is WHITESPACE - reset it.
            # This fixes cases similar to the one shown in issue #51.
            nt === WHITESPACE && (t.nodes[end] = Whitespace(0))

            hs = hascomment(s.doc, current_line)
            hs && add_node!(t, InlineComment(current_line), s)
            if nt !== PLACEHOLDER
                add_node!(t, Newline(nest_behavior = AlwaysNest), s)
            elseif hs && nt === PLACEHOLDER
                # swap PLACEHOLDER (will be NEWLINE) with INLINECOMMENT node
                idx = length(t.nodes)
                t.nodes[idx-1], t.nodes[idx] = t.nodes[idx], t.nodes[idx-1]
            end
            add_node!(t, Notcode(notcode_startline, notcode_endline), s)
            add_node!(t, Newline(nest_behavior = AlwaysNest), s)
        elseif !join_lines
            if hascomment(s.doc, current_line) && current_line != n.startline
                add_node!(t, InlineComment(current_line), s)
            end
            add_node!(t, Newline(nest_behavior = AlwaysNest), s)
        elseif nt === PLACEHOLDER &&
               current_line != n.startline &&
               hascomment(s.doc, current_line)
            t.nest_behavior = AlwaysNest
            add_node!(t, InlineComment(current_line), s)
            # swap PLACEHOLDER (will be NEWLINE) with INLINECOMMENT node
            idx = length(t.nodes)
            t.nodes[idx-1], t.nodes[idx] = t.nodes[idx], t.nodes[idx-1]
        end
    end

    @label add_node_end

    if n.startline < t.startline || t.startline == -1
        t.startline = n.startline
    end
    if n.endline > t.endline || t.endline == -1
        t.endline = n.endline
    end

    if !join_lines && is_end(n)
        # end keyword isn't useful w.r.t margin lengths
    elseif t.typ === CSTParser.StringH
        # The length of this node is the length of
        # the longest string. The length of the string is
        # only considered "in the positive" when it's past
        # the hits the initial """ offset, i.e. `t.indent`.
        t.len = max(t.len, n.indent + length(n) - t.indent)
    elseif is_multiline(n)
        is_iterable(t) && n_args(t.ref[]) > 1 && (t.nest_behavior = AlwaysNest)
        t.len += length(n)
    elseif max_padding >= 0
        t.len = max(t.len, length(n) + max_padding)
    else
        t.len += length(n)
    end
    push!(t.nodes, n)
    nothing
end

@inline function is_prev_newline(fst::FST)
    if fst.typ === NEWLINE
        return true
    elseif is_leaf(fst) || length(fst.nodes) == 0
        return false
    end
    is_prev_newline(fst[end])
end

"""
    `length_to(x::FST, ntyps; start::Int = 1)`

Returns the length to any node type in `ntyps` based off the `start` index.
"""
@inline function length_to(fst::FST, ntyps; start::Int = 1)
    fst.typ in ntyps && return 0, true
    is_leaf(fst) && return length(fst), false
    len = 0
    for i = start:length(fst.nodes)
        l, found = length_to(fst.nodes[i], ntyps)
        len += l
        found && return len, found
    end
    return len, false
end

@inline is_closer(fst::FST) =
    fst.typ === CSTParser.PUNCTUATION &&
    (fst.val == "}" || fst.val == ")" || fst.val == "]")
@inline is_closer(cst::CSTParser.EXPR) =
    cst.kind === Tokens.RBRACE || cst.kind === Tokens.RPAREN || cst.kind === Tokens.RSQUARE

@inline is_opener(fst::FST) =
    fst.typ === CSTParser.PUNCTUATION &&
    (fst.val == "{" || fst.val == "(" || fst.val == "[")
@inline is_opener(cst::CSTParser.EXPR) =
    cst.kind === Tokens.LBRACE || cst.kind === Tokens.LPAREN || cst.kind === Tokens.LSQUARE

@inline is_str(cst::CSTParser.EXPR) = is_str_or_cmd(cst.kind) || is_str_or_cmd(cst.typ)

function is_iterable(x::Union{CSTParser.EXPR,FST})
    x.typ === CSTParser.TupleH && return true
    x.typ === CSTParser.Vect && return true
    x.typ === CSTParser.Vcat && return true
    x.typ === CSTParser.Braces && return true
    x.typ === CSTParser.Call && return true
    x.typ === CSTParser.Curly && return true
    x.typ === CSTParser.Comprehension && return true
    x.typ === CSTParser.TypedComprehension && return true
    x.typ === CSTParser.MacroCall && return true
    x.typ === CSTParser.InvisBrackets && return true
    x.typ === CSTParser.Ref && return true
    x.typ === CSTParser.TypedVcat && return true
    x.typ === CSTParser.Import && return true
    x.typ === CSTParser.Using && return true
    x.typ === CSTParser.Export && return true
    return false
end

function is_block(x::Union{CSTParser.EXPR,FST})
    x.typ === CSTParser.If && return true
    x.typ === CSTParser.Do && return true
    x.typ === CSTParser.Try && return true
    x.typ === CSTParser.Begin && return true
    x.typ === CSTParser.For && return true
    x.typ === CSTParser.While && return true
    x.typ === CSTParser.Let && return true
    (x.typ === CSTParser.Quote && x[1].val == "quote") && return true
    return false
end

function is_opcall(x::Union{CSTParser.EXPR,FST})
    x.typ === CSTParser.BinaryOpCall && return true
    x.typ === CSTParser.Comparison && return true
    x.typ === CSTParser.ChainOpCall && return true
    # InvisBrackets are often mixed with operators
    # so kwargs are propagated through its related
    # functions
    x.typ === CSTParser.InvisBrackets && return true
    return false
end

# Generator typ
# (x for x in 1:10)
# (x for x in 1:10 if x % 2 == 0)
function is_gen(x::Union{CSTParser.EXPR,FST})
    x.typ === CSTParser.Generator && return true
    x.typ === CSTParser.Filter && return true
    x.typ === CSTParser.Flatten && return true
    # x.typ === CSTParser.InvisBrackets && return true
    return false
end

function is_assignment(x::Union{FST,CSTParser.EXPR})
    if x.typ === CSTParser.BinaryOpCall && is_assignment(op_kind(x))
        return true
    end

    if (
        x.typ === CSTParser.Const ||
        x.typ === CSTParser.Local ||
        x.typ === CSTParser.Global ||
        x.typ === CSTParser.Outer
    ) && is_assignment(x[end])
        return true
    end

    return false
end
is_assignment(kind::Tokens.Kind) = CSTParser.precedence(kind) == CSTParser.AssignmentOp
is_assignment(::Nothing) = false

function is_function_or_macro_def(cst::CSTParser.EXPR)
    CSTParser.defines_function(cst) && return true
    cst.typ === CSTParser.Macro && return true
    cst.typ === CSTParser.WhereOpCall && return true
    return false
end

function nest_block(cst::CSTParser.EXPR)
    cst.typ === CSTParser.If && return true
    cst.typ === CSTParser.Do && return true
    cst.typ === CSTParser.Try && return true
    cst.typ === CSTParser.For && return true
    cst.typ === CSTParser.While && return true
    cst.typ === CSTParser.Let && return true
    return false
end

function remove_empty_notcode(fst::FST)
    is_iterable(fst) && return true
    fst.typ === CSTParser.BinaryOpCall && return true
    fst.typ === CSTParser.ConditionalOpCall && return true
    fst.typ === CSTParser.Comparison && return true
    fst.typ === CSTParser.ChainOpCall && return true
    return false
end

nest_assignment(cst::CSTParser.EXPR) = is_assignment(cst[2].kind)

function unnestable_arg(cst::CSTParser.EXPR)
    is_iterable(cst) && return true
    is_str(cst) && return true
    cst.typ === CSTParser.LITERAL && return true
    cst.typ === CSTParser.UnaryOpCall && cst[2].kind === Tokens.DDDOT && return true
    cst.typ === CSTParser.BinaryOpCall && cst[2].kind === Tokens.DOT && return true
    return false
end

function nestable(::S, cst::CSTParser.EXPR) where {S<:AbstractStyle}
    CSTParser.defines_function(cst) && cst[1].typ !== CSTParser.UnaryOpCall && return true
    nest_assignment(cst) && return !is_str(cst[3])
    true
end

function nest_rhs(cst::CSTParser.EXPR)::Bool
    if CSTParser.defines_function(cst)
        rhs = cst[3]
        rhs.typ === CSTParser.Block && (rhs = rhs[1])
        return nest_block(rhs)
    end
    false
end

function op_kind(cst::CSTParser.EXPR)::Union{Nothing,Tokens.Kind}
    if cst.typ === CSTParser.BinaryOpCall ||
       cst.typ === CSTParser.Comparison ||
       cst.typ === CSTParser.ChainOpCall
        return cst[2].kind
    elseif cst.typ === CSTParser.UnaryOpCall
        return cst[1].typ === CSTParser.OPERATOR ? cst[1].kind : cst[2].kind
    end
    return nothing
end
op_kind(::Nothing) = nothing
function op_kind(fst::FST)::Union{Nothing,Tokens.Kind}
    fst.ref === nothing ? nothing : op_kind(fst.ref[])
end

get_op(fst::FST) = findfirst(n -> n.typ === CSTParser.OPERATOR, fst.nodes)
get_op(cst::CSTParser.EXPR) = cst[2]

is_lazy_op(kind) = kind === Tokens.LAZY_AND || kind === Tokens.LAZY_OR

"""
    is_standalone_shortcircuit(cst::CSTParser.EXPR)

Returns `true` if the `cst` is a short-circuit expression (uses `&&`, `||`)
and is *standalone*, meaning it's not directly associated with another statement or
expression.

### Examples

```julia
# this IS a standalone short-circuit
a && b

# this IS NOT a standalone short-circuit
if a && b
end

# this IS NOT a standalone short-circuit
var = a && b

# this IS NOT a standalone short-circuit
@macro a && b

# operation inside parenthesis IS NOT a standalone short-circuit
# operation outside parenthesis IS a standalone short-circuit
(a && b) && c
```
"""
function is_standalone_shortcircuit(cst::CSTParser.EXPR)
    kind = op_kind(cst)
    is_lazy_op(kind) || return false

    function valid(n)
        n === nothing && return true
        n.typ === CSTParser.InvisBrackets && return false
        n.typ === CSTParser.MacroCall && return false
        n.typ === CSTParser.Return && return false
        n.typ === CSTParser.If && return false
        n.typ === CSTParser.Block && nest_assignment(n.parent) && return false
        n.typ === CSTParser.BinaryOpCall && nest_assignment(n) && return false
        return true
    end

    function ignore(n::CSTParser.EXPR)
        n.typ === CSTParser.InvisBrackets && return false
        n.typ === CSTParser.If && return false
        n.typ === CSTParser.Block && return false
        n.typ === CSTParser.MacroCall && return false
        n.typ === CSTParser.Return && return false
        n.typ === CSTParser.BinaryOpCall && nest_assignment(n) && return false
        return true
    end

    return parent_is(cst, valid, ignore = ignore)
end


text:
println("--------- statring Robotics.jl tests ---------")
include("test/runtests.jl")
println("✓✓✓✓✓✓✓✓ all Robotics.jl tests passed ✓✓✓✓✓✓✓✓")


text:
module MaGe

using Parsers
using Mmap: mmap

import Base

# Hits
export Hit, location, energy, time, particleid, trackid, trackparentid

# Events
export Event, hits, hitcount, eventnum, primarycount

# For FileIO someday: add_format(format"ROOTHITS", (), ".root.hits", [:MaGe])

include("Event.jl")
include("root-hit-files.jl")

end # module


text:
# https://github.com/stan-dev/blob/master/basic_estimators/bernoulli.stan

"
data {
  int<lower=0> N;
  int<lower=0,upper=1> y[N];
}
parameters {
  real<lower=0,upper=1> theta;
}
model {
  theta ~ beta(1,1);
  for (n in 1:N)
    y[n] ~ bernoulli(theta);
}
"

@model bermodel(y) = begin
 theta ~ Beta(1,1)
 for n = 1:length(y)
   y[n] ~ Bernoulli(theta)
 end
 return theta
end


text:
# AUTO GENERATED FILE - DO NOT EDIT

export dashbio_needleplot

"""
    dashbio_needleplot(;kwargs...)

A NeedlePlot component.
The Needle Plot component is used to visualize large datasets
containing categorical or numerical data. The lines and markers in
the plot correspond to bars in a histogram.
Keyword arguments:
- `id` (String; optional): The ID of this component, used to identify dash components
in callbacks. The ID needs to be unique across all of the
components in an app.
- `domainStyle` (optional): Options for the protein domain coloring. domainStyle has the following type: lists containing elements 'domainColor', 'displayMinorDomains'.
Those elements have the following types:
  - `domainColor` (Array; optional)
  - `displayMinorDomains` (Bool; optional)
- `mutationData` (optional): The data that are displayed on the plot. mutationData has the following type: lists containing elements 'x', 'y', 'mutationGroups', 'domains'.
Those elements have the following types:
  - `x` (String | Array; optional)
  - `y` (String | Array; optional)
  - `mutationGroups` (Array of Strings; optional)
  - `domains` (Array; optional)
- `needleStyle` (optional): Options for the needle marking single site mutations. needleStyle has the following type: lists containing elements 'stemColor', 'stemThickness', 'stemConstHeight', 'headSize', 'headColor', 'headSymbol'.
Those elements have the following types:
  - `stemColor` (String; optional)
  - `stemThickness` (Real; optional)
  - `stemConstHeight` (Bool; optional)
  - `headSize` (Real; optional)
  - `headColor` (Array | String; optional)
  - `headSymbol` (Array | String; optional)
- `rangeSlider` (Bool; optional): If true, enables a rangeslider for the x-axis.
- `xlabel` (String; optional): Title of the x-axis.
- `ylabel` (String; optional): Title of the y-axis.
"""
function dashbio_needleplot(; kwargs...)
        available_props = Symbol[:id, :domainStyle, :mutationData, :needleStyle, :rangeSlider, :xlabel, :ylabel]
        wild_props = Symbol[]
        return Component("dashbio_needleplot", "NeedlePlot", "dash_bio", available_props, wild_props; kwargs...)
end


text:
# Hello world example, similar to the Boost.Python hello world

using CxxWrap
using Base.Test

# Wrap the functions defined in C++
wrap_modules(CxxWrap._l_hello)

# Output:
@show CppHello.greet()

# Test the result
@test CppHello.greet() == "hello, world"


text:
# -----------------------------------------------------------------------------
# Integration with AtomsBase
# -----------------------------------------------------------------------------

# Convert arbitrary AbstractSystem to a System
function Molly.System(system::AbstractSystem{D}; kwargs...) where {D}
    @assert hcat(bounding_box(system)...) == bounding_box(system)[1][1] * I(D)
    @assert all(periodicity(system))
    atoms_data = (species_type(system) <: AtomsBase.Atom) ? [AugmentedAtomData(atomic_symbol(a); a.data...) for a ∈ system] :
                 (species_type(system) <: Molly.Atom) ? copy(system.atoms_data) :
                 AugmentedAtomData.(atomic_symbol(system))
    velocities = ismissing(velocity(system)) ? [@SVector zeros(VELOCITY_TYPE, 3) for _ ∈ 1:length(system)] :
                 velocity(system)
    System(;
        atoms=[Molly.Atom(index=i, mass=m) for (i, m) ∈ enumerate(atomic_mass(system))],
        atoms_data,
        coords=position(system),
        velocities,
        box_size=SVector{D}(bounding_box(system)[i][i] for i ∈ 1:D),
        force_units=FORCE_UNIT,
        energy_units=ENERGY_UNIT,
        kwargs...
    )
end

# Alternative atom data type to store arbitrary fields
struct AugmentedAtomData
    element::Symbol
    data::Dict{Symbol,Any}
end
AugmentedAtomData(element::Symbol; data...) = AugmentedAtomData(element, Dict(data...))
Base.hasproperty(data::AugmentedAtomData, x::Symbol) = hasfield(AugmentedAtomData, x) || haskey(data.data, x)
Base.getproperty(data::AugmentedAtomData, x::Symbol) = hasfield(AugmentedAtomData, x) ? getfield(data, x) : getindex(data.data, x)
function Base.propertynames(data::AugmentedAtomData, private::Bool=false)
    if private
        (fieldnames(AugmentedAtomData)..., keys(data.data)...)
    else
        (filter(!isequal(:data), fieldnames(AugmentedAtomData))..., keys(data.data)...)
    end
end

Base.hasproperty(view::AtomView{<:System}, x::Symbol) = hasfield(AtomView, x) || haskey(getfield(view, :system).atoms_data[getfield(view, :index)].data, x)
Base.getproperty(view::AtomView{<:System}, x::Symbol) = hasfield(AtomView, x) ? getfield(view, x) : getindex(getfield(view, :system).atoms_data[getfield(view, :index)].data, x)
Base.propertynames(view::AtomView{<:System}, private::Bool=false) = (fieldnames(AtomView)..., keys(getfield(view, :system).atoms_data[getfield(view, :index)].data)...)

# Convert Molly Atom to AtomsBase Atom
AtomsBase.Atom(d::AugmentedAtomData, p::SVector{3,<:Unitful.Length}, v::SVector{3,<:Unitful.Velocity}) = AtomsBase.Atom(d.element, p, v; d.data...)


text:
#  Mixture of Conjugates functionality

function default_target_computation(
    ::BasicPosteriorTarget,
    ::EBayesSample,
    ::DiscreteNonParametric
)
    Conjugate()
end

function marginalize(Z::EBayesSample, prior::DiscreteNonParametric)
    πs = probs(prior)
    distributions = likelihood_distribution.(Z, support(prior))
    #TODO: note that e.g. for Binomial this could directly return another DiscreteNonParametric.
    # In most cases this would not be worth the initial conversion cost?
    MixtureModel(distributions, πs)
end

function posterior(Z::EBayesSample, prior::DiscreteNonParametric)
    πs = probs(prior)
    fs = likelihood.(Z, support(prior))
    posterior_πs = πs .* fs
    posterior_πs /= sum(posterior_πs)
    DiscreteNonParametric(support(prior), posterior_πs)
end

function default_target_computation(
    ::BasicPosteriorTarget,
    ::EBayesSample,
    ::MixtureModel
)
    #TODO NOT always true.
    Conjugate()
end

function marginalize(Z::EBayesSample, prior::MixtureModel)
    πs = probs(prior)
    marginal_components = marginalize.(Z, components(prior))
    MixtureModel(marginal_components, πs)
end

function posterior(Z::EBayesSample, prior::MixtureModel)
    πs = probs(prior)
    fs = pdf.(components(prior), Z)
    posterior_πs = πs .* fs
    posterior_πs /= sum(posterior_πs)
    posterior_components = posterior.(Z, components(prior))
    MixtureModel(posterior_components, posterior_πs)
end


text:
# Undergraduate Student: Arturo Burgos
# Professor: João Rodrigo Andrade
# Federal University of Uberlândia - UFU, Fluid Mechanics Laboratory - MFLab, Block 5P, Uberlândia, MG, Brazil

using Printf # Just the same as import a module
using Statistics
using CPUTime

# A = rand(1:50,3,3)
# B = rand(1:50,3,3)

A = [1 2; 3 4]
println("\n\n\nThe A Matrix is: \n\n",A)

B = [1 2; 3 4]
println("\n\nThe B Matrix is: \n\n",B)

R = zeros(2,2) # Here I create a zero matrix just to get the multiply of A and B

D = A*B # This is the vectorized way for this problem, like MATLAB
C = A.*B # This multiplies point by point

println("\n\nThe D Matrix is: \n\n",D)
println("\n\nThe C Matrix is: \n\n",C)

# println("\n\n",D[:,1])

# i represents lines and j represents columns. k represents
# the line or column 11element11 indices

function multiloop(mtx1,mtx2,mtx3)

    for i = 1:2
        for j = 1:2
            soma = 0;
            for k = 1:2
                soma = soma + mtx1[i,k]*mtx2[k,j];
                mtx3[i,j] = soma;
            end
        end
    end
    return mtx3 # Returns a value to the variable
end

R = multiloop(A,B,R);
println("\n\nThe R Matrix is: \n\n",R)
println("\n\n")

@time @CPUtime multiloop(A,B,R)

max_stars_repo_name: bangyen/Comparing-Languages

max_issues_repo_name: bangyen/Comparing-Languages

max_forks_repo_name: bangyen/Comparing-Languages


text:
#!/usr/bin/env julia

if VERSION >= v"0.5.0-"
    using Base.Test
else
    using BaseTestNext
end

using LibSndFile
using SampledSignals
using FileIO
using FixedPointNumbers

include("testhelpers.jl")

"""Generates a 100-sample 2-channel signal"""
function gen_reference(srate)
    t = collect(0:99) / srate
    phase = [2pi*440t 2pi*880t]

    0.5sin(phase)
end

try
    @testset "LibSndFile Tests" begin
        srate = 44100Hz
        # reference file generated with Audacity. Careful to turn dithering off
        # on export for deterministic output!
        reference_wav = Pkg.dir("LibSndFile", "test", "440left_880right_0.5amp.wav")
        reference_wav_float = Pkg.dir("LibSndFile", "test", "440left_880right_0.5amp_float.wav")
        reference_wav_double = Pkg.dir("LibSndFile", "test", "440left_880right_0.5amp_double.wav")
        reference_wav_pcm24 = Pkg.dir("LibSndFile", "test", "440left_880right_0.5amp_pcm24.wav")
        reference_ogg = Pkg.dir("LibSndFile", "test", "440left_880right_0.5amp.ogg")
        reference_flac = Pkg.dir("LibSndFile", "test", "440left_880right_0.5amp.flac")
        reference_buf = gen_reference(srate/Hz)

        @testset "Read errors" begin
            @test_throws ErrorException load("doesnotexist.wav")
        end

        @testset "WAV file detection" begin
            open(reference_wav) do stream
                @test LibSndFile.detectwav(stream)
            end
            open(reference_flac) do stream
                @test !LibSndFile.detectwav(stream)
            end
        end
        @testset "WAV file reading" begin
            buf = load(reference_wav)
            @test samplerate(buf) == srate
            @test nchannels(buf) == 2
            @test nframes(buf) == 100
            @test isapprox(Float64[x/s for x in domain(buf)], collect(0:99)/(srate/Hz))
            @test mse(buf, reference_buf) < 1e-10
        end

        @testset "Reading different sample types" begin
            buf = load(reference_wav)
            @test eltype(buf) == Fixed{Int16, 15}

            buf_float = load(reference_wav_float)
            @test eltype(buf_float) == Float32
            @test mse(buf_float, reference_buf) < 1e-10

            buf_double = load(reference_wav_double)
            @test eltype(buf_double) == Float64
            @test mse(buf_double, reference_buf) < 1e-10

            buf_pcm24 = load(reference_wav_pcm24)
            @test eltype(buf_pcm24) == Fixed{Int32, 31}
            @test mse(buf_pcm24, reference_buf) < 1e-10
        end

        @testset "FLAC file reading" begin
            buf = load(reference_flac)
            @test samplerate(buf) == srate
            @test nchannels(buf) == 2
            @test nframes(buf) == 100
            @test isapprox(Float64[x/s for x in domain(buf)], collect(0:99)/(srate/Hz))
            @test mse(buf, reference_buf) < 1e-10
        end

        @testset "OGG file reading" begin
            buf = load(reference_ogg)
            @test samplerate(buf) == srate
            @test nchannels(buf) == 2
            @test nframes(buf) == 100
            @test isapprox(Float64[x/s for x in domain(buf)], collect(0:99)/(srate/Hz))
            # lossy compression, so relax the accuracy a bit
            @test mse(buf, reference_buf) < 1e-5
        end

        @testset "Streaming reading" begin
            str = loadstream(reference_wav)
            @test nframes(str) == 100
            @test mse(read(str, 50), reference_buf[1:50, :]) < 1e-10
            @test mse(read(str, 50), reference_buf[51:100, :]) < 1e-10
            close(str)
            # now with do syntax
            loadstream(reference_wav) do str
                @test mse(read(str, 50), reference_buf[1:50, :]) < 1e-10
                @test mse(read(str, 50), reference_buf[51:100, :]) < 1e-10
            end
        end

        @testset "WAV file writing" begin
            fname = string(tempname(), ".wav")
            testbuf = SampleBuf(rand(100, 2)-0.5, srate)
            save(fname, testbuf)
            buf = load(fname)
            @test samplerate(buf) == srate
            @test nchannels(buf) == 2
            @test nframes(buf) == 100
            # strip units because isapprox doesn't support them...
            @test isapprox(Float64[x/s for x in domain(buf)], collect(0:99)/(srate/Hz))
            @test mse(buf, testbuf) < 1e-10
        end

        @testset "OGG file writing" begin
            fname = string(tempname(), ".ogg")
            testbuf = SampleBuf(rand(Float32, 100, 2)-0.5, srate)
            save(fname, testbuf)
            buf = load(fname)
            @test samplerate(buf) == srate
            @test nchannels(buf) == 2
            @test nframes(buf) == 100
            # strip units because isapprox doesn't support them...
            @test isapprox(Float64[x/s for x in domain(buf)], collect(0:99)/(srate/Hz))
            # noise doesn't compress very well...
            @test mse(buf, testbuf) < 0.05
        end

        @testset "FLAC file writing" begin
            fname = string(tempname(), ".flac")
            # TODO: this conversion fails sometimes because of FixedPointNumbers.jl#37
            arr = convert(Array{Fixed{Int16, 15}}, rand(100, 2)-0.5)
            testbuf = SampleBuf(arr, srate)
            save(fname, testbuf)
            buf = load(fname)
            @test samplerate(buf) == srate
            @test nchannels(buf) == 2
            @test nframes(buf) == 100
            # strip units because isapprox doesn't support them...
            @test isapprox(Float64[x/s for x in domain(buf)], collect(0:99)/(srate/Hz))
            @test mse(buf, testbuf) < 1e-10
        end

        @testset "Writing $T data" for T in [Fixed{Int16, 15}, Fixed{Int32, 31}, Float32, Float64]
            fname = string(tempname(), ".wav")
            arr = convert(Array{T}, rand(100, 2)-0.5)
            testbuf = SampleBuf(arr, srate)
            save(fname, testbuf)
            buf = load(fname)
            @test eltype(buf) == T
            @test mse(buf, testbuf) < 1e-10
        end

        @testset "Write errors" begin
            testbuf = SampleBuf(rand(Float32, 100, 2)-0.5, srate)
            flacname = string(tempname(), ".flac")
            @test_throws ErrorException save(abspath(joinpath("does", "not", "exist.wav")), testbuf)
            @test_throws ErrorException save(flacname, testbuf)
        end

        @testset "Streaming writing" begin
            fname = string(tempname(), ".wav")
            testbuf = SampleBuf(rand(Float32, 100, 2)-0.5, srate)
            # set up a 2-channel Float32 stream
            stream = savestream(fname, 2, srate, Float32)
            write(stream, testbuf[1:50, :])
            write(stream, testbuf[51:100, :])
            close(stream)
            buf = load(fname)
            @test mse(buf, testbuf) < 1e-10

            # now with do syntax
            fname = string(tempname(), ".wav")
            testbuf = SampleBuf(rand(Float32, 100, 2)-0.5, srate)
            # set up a 2-channel Float32 stream
            savestream(fname, 2, srate, Float32) do stream
                write(stream, testbuf[1:50, :])
                write(stream, testbuf[51:100, :])
            end
            buf = load(fname)
            @test mse(buf, testbuf) < 1e-10

        end

        @testset "Sink Display" begin
            fname = string(tempname(), ".wav")
            testbuf = SampleBuf(rand(Float32, 10000, 2)-0.5, srate)
            # set up a 2-channel Float32 stream
            stream = savestream(fname, 2, srate, Float32)
            io = IOBuffer()
            show(io, stream)
            @test takebuf_string(io) == """
            LibSndFile.SndFileSink{Float32}
              path: "$fname"
              channels: 2
              samplerate: 44100 s⁻¹
              position: 0 of 0 frames
                        0.00 of 0.00 seconds"""
            write(stream, testbuf)
            show(io, stream)
            @test takebuf_string(io) == """
            LibSndFile.SndFileSink{Float32}
              path: "$fname"
              channels: 2
              samplerate: 44100 s⁻¹
              position: 10000 of 10000 frames
                        0.23 of 0.23 seconds"""
        end

        @testset "Source Display" begin
            fname = string(tempname(), ".wav")
            testbuf = SampleBuf(rand(Float32, 10000, 2)-0.5, srate)
            save(fname, testbuf)
            # set up a 2-channel Float32 stream
            stream = loadstream(fname)
            io = IOBuffer()
            show(io, stream)
            @test takebuf_string(io) == """
            LibSndFile.SndFileSource{Float32}
              path: "$fname"
              channels: 2
              samplerate: 44100 s⁻¹
              position: 0 of 10000 frames
                        0.00 of 0.23 seconds"""
            read(stream, 5000)
            show(io, stream)
            @test takebuf_string(io) == """
            LibSndFile.SndFileSource{Float32}
              path: "$fname"
              channels: 2
              samplerate: 44100 s⁻¹
              position: 5000 of 10000 frames
                        0.11 of 0.23 seconds"""
        end

        # TODO: check out what happens when samplerate, channels, etc. are wrong
        # when reading/writing

        #
        #     # test seeking
        #
        #     # test rendering as an AudioNode
        #     AudioIO.open(fname) do f
        #         # pretend we have a stream at the same rate as the file
        #         bufsize = 1024
        #         input = zeros(AudioSample, bufsize)
        #         test_info = DeviceInfo(srate, bufsize)
        #         node = FilePlayer(f)
        #         # convert to floating point because that's what AudioIO uses natively
        #         expected = convert(AudioBuf, reference ./ (2^15))
        #         buf = render(node, input, test_info)
        #         @fact expected[1:bufsize] => buf[1:bufsize]
        #         buf = render(node, input, test_info)
        #         @fact expected[bufsize+1:2*bufsize] => buf[1:bufsize]
        #     end
        # end
        #
        # @testset "Stereo file reading" begin
        #     fname = Pkg.dir("AudioIO", "test", "440left_880right.wav")
        #     srate = 44100
        #     t = [0 : 2 * srate - 1] / srate
        #     expected = int16((2^15-1) * hcat(sin(2pi*t*440), sin(2pi*t*880)))
        #
        #     AudioIO.open(fname) do f
        #         buf = read(f)
        #         @fact buf => mse(expected, 5)
        #     end
        # end
        #
        # # note - currently AudioIO just mixes down to Mono. soon we'll support this
        # # new-fangled stereo sound stuff
        # @testset "Stereo file rendering" begin
        #     fname = Pkg.dir("AudioIO", "test", "440left_880right.wav")
        #     srate = 44100
        #     bufsize = 1024
        #     input = zeros(AudioSample, bufsize)
        #     test_info = DeviceInfo(srate, bufsize)
        #     t = [0 : 2 * srate - 1] / srate
        #     expected = convert(AudioBuf, 0.5 * (sin(2pi*t*440) + sin(2pi*t*880)))
        #
        #     AudioIO.open(fname) do f
        #         node = FilePlayer(f)
        #         buf = render(node, input, test_info)
        #         @fact buf[1:bufsize] => mse(expected[1:bufsize])
        #         buf = render(node, input, test_info)
        #         @fact buf[1:bufsize] => mse(expected[bufsize+1:2*bufsize])
        #     end
        # end
    end
catch err
    exit(-1)
end