diff --git a/examples/config.nims b/examples/config.nims index b21d0ed..848fb5c 100644 --- a/examples/config.nims +++ b/examples/config.nims @@ -1 +1,2 @@ switch("outdir", "bin") +switch("path", "..") diff --git a/nimjl.nimble b/nimjl.nimble index 77a7072..b7a9ced 100644 --- a/nimjl.nimble +++ b/nimjl.nimble @@ -1,6 +1,6 @@ # Nimjl # Licensed and distributed under MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT). -version = "0.7.6" +version = "0.8.0" author = "Regis Caillaud" description = "Nim Julia bridge" license = "MIT" diff --git a/nimjl/conversions/dict_tuples.nim b/nimjl/conversions/dict_tuples.nim index 9a2a92c..e8f3e29 100644 --- a/nimjl/conversions/dict_tuples.nim +++ b/nimjl/conversions/dict_tuples.nim @@ -34,7 +34,7 @@ proc jlTupleToNim*(val: JlValue, tup: var tuple) = else: raise newException(JlError, "Tuple conversion from Julia to Nim failed ! Fields must identical") -proc jlDictToNim*[U, V: string|SomeNumber|bool](val: JlValue, tab: var Table[U, V]) = +proc jlDictToNim*[U, V](val: JlValue, tab: var Table[U, V]) = # julia> collect(keys(val)) var keys = jlCall("keys", val) keys = jlCall("collect", keys) diff --git a/nimjl/cores.nim b/nimjl/cores.nim index 644a1d3..f9194de 100644 --- a/nimjl/cores.nim +++ b/nimjl/cores.nim @@ -61,7 +61,7 @@ template JlNothing*(): JlValue = jlEval("nothing") template JlCode*(body: string) = block: - discard jleval(body) + discard jlEval(body) proc jlVmIsInit*(): bool = bool(jl_is_initialized()) @@ -78,6 +78,9 @@ proc jlVmExit*(exit_code: cint = 0.cint) = # Do nothing -> atexit_hook must be called once # raise newException(JlError, "jl_atexit_hook() must be called once per process") +# proc jlVmSaveExit*(fpath: string) = +# discard jlEval(fmt"exit_save_sysimage({fpath})") + ######################################### var staticContents: Table[string, string] @@ -94,10 +97,13 @@ proc jlVmInit*() = ## Subsequent calls after the first one will be ignored if not jlVmIsInit(): jl_init() - loadJlRessources() + # loadJlRessources() return # raise newException(JlError, "jl_init() must be called once per process") +# proc jlVmInitWithImg*(fpath: string) = +# jl_init_with_image(JuliaBinDir.cstring, fpath.cstring) + proc jlVmInit*(nthreads: int) = putEnv("JULIA_NUM_THREADS", $nthreads) jlVmInit() @@ -108,7 +114,7 @@ proc jlVmInit(pathToImage: string) {.used.} = if not jlVmIsInit(): let jlBinDir = cstring(JuliaPath / "bin") jl_init_with_image(jlBinDir, pathToImage.cstring) - loadJlRessources() + # loadJlRessources() return # raise newException(JlError, "jl_init_with_image(...) must be called once per process") diff --git a/nimjl/glucose.nim b/nimjl/glucose.nim index a5be7bc..a834377 100644 --- a/nimjl/glucose.nim +++ b/nimjl/glucose.nim @@ -1,6 +1,6 @@ # This file is named glucose because it gives you sugar ;) # It contains most syntactic sugar to ease using Julia inside Nim -import std/[os, strutils, strformat] +import std/[os, strutils, strformat, tables] import ./types import ./cores import ./functions @@ -10,6 +10,50 @@ import ./private/jlcores type Julia* = object +##################################################### +# Interop and utility +##################################################### +proc `$`*(val: JlValue): string = + jlCall("string", val).to(string) + +proc `$`*(val: JlModule): string = + jlCall("string", val).to(string) + +proc `$`*[T](val: JlArray[T]): string = + jlCall("string", val).to(string) + +proc `$`*(val: JlFunc): string = + jlCall("string", val).to(string) + +proc `$`*(val: JlSym): string = + jlCall("string", val).to(string) + +# typeof is taken by Nim already +proc jltypeof*(x: JlValue): JlValue = + ## Call the Julia function typeof + jlCall("typeof", x) + +proc len*(val: JlValue): int = + ##Call length + jlCall("length", val).to(int) + +proc firstindex*(val: JlValue): int = + ## Call firstindex + jlCall("firstindex", val).to(int) + +proc lastindex*(val: JlValue): int = + ## Call lastindex + jlCall("lastindex", val).to(int) + +template getproperty*(val: JlValue, propertyname: string): JlValue = + ## Call getproperty + jlCall("getproperty", val, jlSym(propertyname)) + +template setproperty*(val: JlValue, propertyname: string, newval: untyped) = + ## Call setproperty + discard jlCall("setproperty!", val, jlSym(propertyname), newval) + + proc init*(jl: type Julia, nthreads: int = 1) = jlVmInit(nthreads) @@ -36,6 +80,24 @@ type name, url, path, subdir, rev, version, mode, level: string JlPkgs = seq[JlPkgSpec] +proc checkJlPkgSpec(installed: Table[string, string], package: JlPkgSpec) : bool = + # Check if package is installed with the correct version + + result = false + if installed.contains(package.name): + let installedVer = installed[package.name] + var verCheck = "" + if installedVer != "nothing": + # Split + symbol for some reason Julia.Pkg sometimes use it even if it's outside of semver + verCheck = installedVer.split('+')[0] + + if package.version.isEmptyOrWhitespace(): + # If no Pkg version is specified, package presence is enough + result = true + else: + # Else result is true if semver matches + result = (verCheck == package.version) + # Workaround because named parameters do not work inside closure for proc defined in template # TODO : Should string be static ? proc addImpl(pkgs: var JlPkgs, name: static string, url: static string = "", path: static string = "", subdir: static string = "", rev: static string = "", version: static string = "", mode: static string = "", level: static string = "") = @@ -94,16 +156,26 @@ template init*(jl: type Julia, nthreads: int, body: untyped) = jl_init() # Module installation Julia.useModule("Pkg") + + let + jlExistingPkgStr = "Dict(x[2].name => string(x[2].version) for x in Pkg.dependencies())" + jlPkgsExisting = jlEval(jlExistingPkgStr) + installed = jlPkgsExisting.to(Table[string, string]) + + for pkgspec in packages: + if not checkJlPkgSpec(installed, pkgspec): + var exprs: seq[string] = @[jlExpr(":.", ":Pkg", "QuoteNode(:add)")] + for key, field in pkgspec.fieldPairs(): + let fname = ":" & key + if not isEmptyOrWhitespace(field): + exprs.add jlExpr(":kw", fname, field) + + let strexpr = jlExpr(":call", exprs) + var jlexpr = jlEval(strexpr) + # Will crash if version are invalid + discard jlTopLevelEval(jlexpr) + for pkgspec in packages: - var exprs: seq[string] = @[jlExpr(":.", ":Pkg", "QuoteNode(:add)")] - for key, field in pkgspec.fieldPairs(): - let fname = ":" & key - if not isEmptyOrWhitespace(field): - exprs.add jlExpr(":kw", fname, field) - let strexpr = jlExpr(":call", exprs) - var jlexpr = jlEval(strexpr) - # Will crash if version are invalid - discard jlTopLevelEval(jlexpr) # TODO : handle precompilation ? # Julia.precompile() jlUsing(pkgspec.name) @@ -133,49 +205,6 @@ proc includeFile*(jl: type Julia, fname: string) = # macro loadModule*(jl: type Julia, modname: untyped) = # TODO generate a proc ``modname`` that returns module -##################################################### -# Interop and utility -##################################################### -proc `$`*(val: JlValue): string = - jlCall("string", val).to(string) - -proc `$`*(val: JlModule): string = - jlCall("string", val).to(string) - -proc `$`*[T](val: JlArray[T]): string = - jlCall("string", val).to(string) - -proc `$`*(val: JlFunc): string = - jlCall("string", val).to(string) - -proc `$`*(val: JlSym): string = - jlCall("string", val).to(string) - -# typeof is taken by Nim already -proc jltypeof*(x: JlValue): JlValue = - ## Call the Julia function typeof - jlCall("typeof", x) - -proc len*(val: JlValue): int = - ##Call length - jlCall("length", val).to(int) - -proc firstindex*(val: JlValue): int = - ## Call firstindex - jlCall("firstindex", val).to(int) - -proc lastindex*(val: JlValue): int = - ## Call lastindex - jlCall("lastindex", val).to(int) - -template getproperty*(val: JlValue, propertyname: string): JlValue = - ## Call getproperty - jlCall("getproperty", val, jlSym(propertyname)) - -template setproperty*(val: JlValue, propertyname: string, newval: untyped) = - ## Call setproperty - discard jlCall("setproperty!", val, jlSym(propertyname), newval) - ##################################################### # Syntactic sugar ##################################################### diff --git a/nimjl/private/jlarrays.nim b/nimjl/private/jlarrays.nim index 315aa33..ab0a548 100644 --- a/nimjl/private/jlarrays.nim +++ b/nimjl/private/jlarrays.nim @@ -1,7 +1,10 @@ import ../config import ../types import ./jlcores -import std/strformat +import std/[strformat, complex] + +# template jlType*(T: typedesc[Complex32]): JlDataType = jlEval("ComplexF32") +# template jlType*(T: typedesc[Complex64]): JlDataType = jlEval("ComplexF64") template julia_type(arg: typedesc): ptr jl_datatype = jlType(arg) @@ -28,8 +31,13 @@ proc jl_array_eltype*(x: ptr jl_value): ptr jl_datatype {.importc: "jl_array_elt {.pop.} -proc julia_apply_array_type*[T: SomeNumber|bool|char](dim: int): ptr jl_value = - let jl_type = cast[ptr jl_value](julia_type(T)) +proc julia_apply_array_type*[T: Complex32|Complex64|SomeNumber|bool|char](dim: int): ptr jl_value = + when T is Complex32: + let jl_type = jlEval("ComplexF32") + elif T is Complex64: + let jl_type = jlEval("ComplexF64") + else: + let jl_type = cast[ptr jl_value](julia_type(T)) jl_apply_array_type(jl_type, dim.csize_t) proc julia_make_array*[T](data: ptr UncheckedArray[T], dims: openArray[int]): ptr jl_array =