Add a Clojure interop example
Calls Clojure from Nim through its documented Java API, which needs no Clojure-specific support in the library -- clojure.java.api.Clojure/var to resolve a var into a clojure.lang.IFn, then IFn/invoke to call it. Requiring a namespace, reading a keyword and walking a returned map are the same pair aimed at clojure.core. The one sharp edge worth showing: IFn/invoke is Object-typed, so primitives have to be boxed through java.lang.Long.valueOf before they can be passed. deps.edn pins the dependency; `just clojure-cp` resolves it with `clojure -Spath` into build/clojure-classpath, which the example reads. .cpcache/ is ignored and cleaned. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
0f4b377 parent: 066bac4 modified
.gitignore +4 -0 | @@ -2,8 +2,12 @@ | ||
| 2 | 2 | build/ |
| 3 | 3 | out/ |
| 4 | 4 | nimcache/ |
| 5 | +.cpcache/ | |
| 5 | 6 | *.class |
| 6 | 7 | |
| 7 | 8 | # Compiled test/example binaries (nim c -r drops these next to the source) |
| 8 | 9 | /tests/tlibjava |
| 9 | 10 | /examples/demo |
| 11 | + | |
| 12 | +# Clojure example binary | |
| 13 | +/examples/clojure_demo | |
| @@ -2,8 +2,12 @@ | |||
| 2 | build/ | 2 | build/ |
| 3 | out/ | 3 | out/ |
| 4 | nimcache/ | 4 | nimcache/ |
| 5 | +.cpcache/ | ||
| 5 | *.class | 6 | *.class |
| 6 | 7 | ||
| 7 | # Compiled test/example binaries (nim c -r drops these next to the source) | 8 | # Compiled test/example binaries (nim c -r drops these next to the source) |
| 8 | /tests/tlibjava | 9 | /tests/tlibjava |
| 9 | /examples/demo | 10 | /examples/demo |
| 11 | + | ||
| 12 | +# Clojure example binary | ||
| 13 | +/examples/clojure_demo | ||
modified
README.md +26 -0 | @@ -42,6 +42,32 @@ this code is itself a shared library loaded *by* a JVM. | ||
| 42 | 42 | | `libjava/core` | strings, exceptions, typed calls, signatures | |
| 43 | 43 | | `libjava/vm` | JVM lifecycle, per-thread `JNIEnv` | |
| 44 | 44 | |
| 45 | +| Example | | | |
| 46 | +|---|---| | |
| 47 | +| `examples/demo.nim` | Java: statics, objects, fields, exceptions, collections | | |
| 48 | +| `examples/clojure_demo.nim` | Clojure via `clojure.java.api.Clojure` | | |
| 49 | + | |
| 50 | +## Other JVM languages | |
| 51 | + | |
| 52 | +Anything that compiles to JVM bytecode is reachable without a line of | |
| 53 | +language-specific support — it's just jars on the classpath. | |
| 54 | +`examples/clojure_demo.nim` calls Clojure through its documented Java API, | |
| 55 | +which is two members wide: | |
| 56 | + | |
| 57 | +```nim | |
| 58 | +# clojure.java.api.Clojure/var -> a clojure.lang.IFn; then IFn/invoke | |
| 59 | +let greet = env.callStatic[:JObjectRef](clojure, "var", | |
| 60 | + "(Ljava/lang/Object;Ljava/lang/Object;)Lclojure/lang/IFn;", "demo.core", "greet") | |
| 61 | +discard env.call[:JObjectRef](greet, ifn, "invoke", | |
| 62 | + "(Ljava/lang/Object;)Ljava/lang/Object;", arg) | |
| 63 | +``` | |
| 64 | + | |
| 65 | +Requiring a namespace, reading a keyword and walking a Clojure map are all that | |
| 66 | +same pair pointed at `clojure.core`. Run it with `just clojure`, which needs the | |
| 67 | +`clojure` CLI on `PATH`; `deps.edn` pins the dependency and `just clojure-cp` | |
| 68 | +resolves it into `build/clojure-classpath`. Note that `IFn/invoke` takes | |
| 69 | +`Object`, so primitives must be boxed — `java.lang.Long.valueOf` in the example. | |
| 70 | + | |
| 45 | 71 | ## Calling |
| 46 | 72 | |
| 47 | 73 | Method signatures are JVM descriptors. `javap -s SomeClass` prints the exact |
| @@ -42,6 +42,32 @@ this code is itself a shared library loaded *by* a JVM. | |||
| 42 | | `libjava/core` | strings, exceptions, typed calls, signatures | | 42 | | `libjava/core` | strings, exceptions, typed calls, signatures | |
| 43 | | `libjava/vm` | JVM lifecycle, per-thread `JNIEnv` | | 43 | | `libjava/vm` | JVM lifecycle, per-thread `JNIEnv` | |
| 44 | 44 | ||
| 45 | +| Example | | | ||
| 46 | +|---|---| | ||
| 47 | +| `examples/demo.nim` | Java: statics, objects, fields, exceptions, collections | | ||
| 48 | +| `examples/clojure_demo.nim` | Clojure via `clojure.java.api.Clojure` | | ||
| 49 | + | ||
| 50 | +## Other JVM languages | ||
| 51 | + | ||
| 52 | +Anything that compiles to JVM bytecode is reachable without a line of | ||
| 53 | +language-specific support — it's just jars on the classpath. | ||
| 54 | +`examples/clojure_demo.nim` calls Clojure through its documented Java API, | ||
| 55 | +which is two members wide: | ||
| 56 | + | ||
| 57 | +```nim | ||
| 58 | +# clojure.java.api.Clojure/var -> a clojure.lang.IFn; then IFn/invoke | ||
| 59 | +let greet = env.callStatic[:JObjectRef](clojure, "var", | ||
| 60 | + "(Ljava/lang/Object;Ljava/lang/Object;)Lclojure/lang/IFn;", "demo.core", "greet") | ||
| 61 | +discard env.call[:JObjectRef](greet, ifn, "invoke", | ||
| 62 | + "(Ljava/lang/Object;)Ljava/lang/Object;", arg) | ||
| 63 | +``` | ||
| 64 | + | ||
| 65 | +Requiring a namespace, reading a keyword and walking a Clojure map are all that | ||
| 66 | +same pair pointed at `clojure.core`. Run it with `just clojure`, which needs the | ||
| 67 | +`clojure` CLI on `PATH`; `deps.edn` pins the dependency and `just clojure-cp` | ||
| 68 | +resolves it into `build/clojure-classpath`. Note that `IFn/invoke` takes | ||
| 69 | +`Object`, so primitives must be boxed — `java.lang.Long.valueOf` in the example. | ||
| 70 | + | ||
| 45 | ## Calling | 71 | ## Calling |
| 46 | 72 | ||
| 47 | Method signatures are JVM descriptors. `javap -s SomeClass` prints the exact | 73 | Method signatures are JVM descriptors. `javap -s SomeClass` prints the exact |
added
deps.edn +5 -0 | new file mode 100644 | ||
| @@ -0,0 +1,5 @@ | ||
| 1 | +;; Dependencies for the Clojure interop example (examples/clojure_demo.nim). | |
| 2 | +;; `just clojure` turns this into build/clojure-classpath via `clojure -Spath`. | |
| 3 | +;; The Nim library itself needs none of this -- only a JDK. | |
| 4 | +{:paths ["examples/clojure"] | |
| 5 | + :deps {org.clojure/clojure {:mvn/version "1.12.5"}}} | |
| new file mode 100644 | |||
| @@ -0,0 +1,5 @@ | |||
| 1 | +;; Dependencies for the Clojure interop example (examples/clojure_demo.nim). | ||
| 2 | +;; `just clojure` turns this into build/clojure-classpath via `clojure -Spath`. | ||
| 3 | +;; The Nim library itself needs none of this -- only a JDK. | ||
| 4 | +{:paths ["examples/clojure"] | ||
| 5 | + :deps {org.clojure/clojure {:mvn/version "1.12.5"}}} | ||
added
examples/clojure/demo/core.clj +33 -0 | new file mode 100644 | ||
| @@ -0,0 +1,33 @@ | ||
| 1 | +(ns demo.core | |
| 2 | + (:require [clojure.string :as str])) | |
| 3 | + | |
| 4 | +(defn greet | |
| 5 | + "Plain function, plain arguments — the easy case from JNI." | |
| 6 | + [name times] | |
| 7 | + (str/join " " (repeat times (str "Hello, " name "!")))) | |
| 8 | + | |
| 9 | +(defn stats | |
| 10 | + "Returns a Clojure map. From Nim this arrives as a clojure.lang.IPersistentMap, | |
| 11 | + which is navigable with clojure.core/get rather than any Java accessor." | |
| 12 | + [xs] | |
| 13 | + (let [xs (seq xs) | |
| 14 | + n (count xs)] | |
| 15 | + {:count n | |
| 16 | + :sum (reduce + xs) | |
| 17 | + :mean (/ (double (reduce + xs)) n) | |
| 18 | + :max (apply max xs)})) | |
| 19 | + | |
| 20 | +(defn fizzbuzz | |
| 21 | + "Returns a lazy seq, realised on the Nim side by calling into it." | |
| 22 | + [n] | |
| 23 | + (map (fn [i] | |
| 24 | + (cond (zero? (mod i 15)) "fizzbuzz" | |
| 25 | + (zero? (mod i 3)) "fizz" | |
| 26 | + (zero? (mod i 5)) "buzz" | |
| 27 | + :else (str i))) | |
| 28 | + (range 1 (inc n)))) | |
| 29 | + | |
| 30 | +(defn explode | |
| 31 | + "Clojure's ex-info carries a data map; it still surfaces in Nim as a JavaError." | |
| 32 | + [] | |
| 33 | + (throw (ex-info "deliberate failure from Clojure" {:origin :demo.core}))) | |
| new file mode 100644 | |||
| @@ -0,0 +1,33 @@ | |||
| 1 | +(ns demo.core | ||
| 2 | + (:require [clojure.string :as str])) | ||
| 3 | + | ||
| 4 | +(defn greet | ||
| 5 | + "Plain function, plain arguments — the easy case from JNI." | ||
| 6 | + [name times] | ||
| 7 | + (str/join " " (repeat times (str "Hello, " name "!")))) | ||
| 8 | + | ||
| 9 | +(defn stats | ||
| 10 | + "Returns a Clojure map. From Nim this arrives as a clojure.lang.IPersistentMap, | ||
| 11 | + which is navigable with clojure.core/get rather than any Java accessor." | ||
| 12 | + [xs] | ||
| 13 | + (let [xs (seq xs) | ||
| 14 | + n (count xs)] | ||
| 15 | + {:count n | ||
| 16 | + :sum (reduce + xs) | ||
| 17 | + :mean (/ (double (reduce + xs)) n) | ||
| 18 | + :max (apply max xs)})) | ||
| 19 | + | ||
| 20 | +(defn fizzbuzz | ||
| 21 | + "Returns a lazy seq, realised on the Nim side by calling into it." | ||
| 22 | + [n] | ||
| 23 | + (map (fn [i] | ||
| 24 | + (cond (zero? (mod i 15)) "fizzbuzz" | ||
| 25 | + (zero? (mod i 3)) "fizz" | ||
| 26 | + (zero? (mod i 5)) "buzz" | ||
| 27 | + :else (str i))) | ||
| 28 | + (range 1 (inc n)))) | ||
| 29 | + | ||
| 30 | +(defn explode | ||
| 31 | + "Clojure's ex-info carries a data map; it still surfaces in Nim as a JavaError." | ||
| 32 | + [] | ||
| 33 | + (throw (ex-info "deliberate failure from Clojure" {:origin :demo.core}))) | ||
added
examples/clojure_demo.nim +111 -0 | new file mode 100644 | ||
| @@ -0,0 +1,111 @@ | ||
| 1 | +## Calling Clojure from Nim. | |
| 2 | +## | |
| 3 | +## Run with: just clojure | |
| 4 | +## | |
| 5 | +## There is no Clojure-specific JNI. Clojure is a jar on the classpath, and the | |
| 6 | +## whole language is reachable through two members of its documented Java API: | |
| 7 | +## | |
| 8 | +## clojure.java.api.Clojure/var -- look a var up by namespace and name, | |
| 9 | +## handing back a clojure.lang.IFn | |
| 10 | +## clojure.lang.IFn/invoke -- call it | |
| 11 | +## | |
| 12 | +## Everything else here -- requiring a namespace, reading a keyword, pulling a | |
| 13 | +## value out of a Clojure map -- is those two calls applied to `clojure.core`. | |
| 14 | + | |
| 15 | +import std/[strformat, os, strutils] | |
| 16 | +import libjava | |
| 17 | + | |
| 18 | +proc classPath(): seq[string] = | |
| 19 | + ## Resolved from deps.edn by `clojure -Spath`; `just clojure` puts it here. | |
| 20 | + let f = "build/clojure-classpath" | |
| 21 | + if not fileExists(f): | |
| 22 | + quit "missing " & f & " -- run `just clojure` rather than nim directly" | |
| 23 | + result = readFile(f).strip().split(':') | |
| 24 | + | |
| 25 | +proc main() = | |
| 26 | + let jvm = startJVM(classPath = classPath(), options = ["-Xmx512m"]) | |
| 27 | + defer: jvm.destroy() | |
| 28 | + let env = jvm.env | |
| 29 | + | |
| 30 | + # --- the two entry points ------------------------------------------------- | |
| 31 | + let clojure = env.findClass("clojure/java/api/Clojure") | |
| 32 | + let ifn = env.findClass("clojure/lang/IFn") | |
| 33 | + | |
| 34 | + proc lookup(ns, name: string): JObjectRef = | |
| 35 | + ## clojure.java.api.Clojure/var -- note both parameters are Object, so the | |
| 36 | + ## strings we pass are boxed as java.lang.String and read by Clojure itself. | |
| 37 | + env.callStatic[:JObjectRef](clojure, "var", | |
| 38 | + "(Ljava/lang/Object;Ljava/lang/Object;)Lclojure/lang/IFn;", ns, name) | |
| 39 | + | |
| 40 | + proc read(s: string): JObjectRef = | |
| 41 | + ## clojure.java.api.Clojure/read -- the reader, for keywords and literals. | |
| 42 | + env.callStatic[:JObjectRef](clojure, "read", | |
| 43 | + "(Ljava/lang/String;)Ljava/lang/Object;", s) | |
| 44 | + | |
| 45 | + proc invoke(f: JObjectRef, a: JObjectRef): JObjectRef = | |
| 46 | + env.call[:JObjectRef](f, ifn, "invoke", | |
| 47 | + "(Ljava/lang/Object;)Ljava/lang/Object;", a) | |
| 48 | + | |
| 49 | + proc invoke(f: JObjectRef, a, b: JObjectRef): JObjectRef = | |
| 50 | + env.call[:JObjectRef](f, ifn, "invoke", | |
| 51 | + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", a, b) | |
| 52 | + | |
| 53 | + # Boxing: Clojure's IFn takes Objects, so primitives must become java.lang.Long. | |
| 54 | + let longCls = env.findClass("java/lang/Long") | |
| 55 | + proc box(n: int64): JObjectRef = | |
| 56 | + env.callStatic[:JObjectRef](longCls, "valueOf", "(J)Ljava/lang/Long;", asLong(n)) | |
| 57 | + | |
| 58 | + # --- load our namespace --------------------------------------------------- | |
| 59 | + # (require 'demo.core) -- the symbol comes from the reader, not from a string. | |
| 60 | + block: | |
| 61 | + let require = lookup("clojure.core", "require") | |
| 62 | + discard require.invoke(read("demo.core")) | |
| 63 | + echo "required demo.core" | |
| 64 | + | |
| 65 | + # --- call a plain function ------------------------------------------------ | |
| 66 | + block: | |
| 67 | + let greet = lookup("demo.core", "greet") | |
| 68 | + let s = greet.invoke(env.newJString("world"), box(2)) | |
| 69 | + echo "greet = ", env.toStringOf(s) | |
| 70 | + | |
| 71 | + # --- a function returning a Clojure map ----------------------------------- | |
| 72 | + block: | |
| 73 | + env.withLocalFrame(16): | |
| 74 | + let stats = lookup("demo.core", "stats") | |
| 75 | + let vec = lookup("clojure.core", "vector") | |
| 76 | + let data = env.call[:JObjectRef](vec, ifn, "invoke", | |
| 77 | + "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", | |
| 78 | + box(3), box(1), box(4), box(1)) | |
| 79 | + let m = stats.invoke(data) | |
| 80 | + echo "stats = ", env.toStringOf(m) | |
| 81 | + | |
| 82 | + # Navigate the map with clojure.core/get rather than a Java accessor. | |
| 83 | + let get = lookup("clojure.core", "get") | |
| 84 | + let sum = get.invoke(m, read(":sum")) | |
| 85 | + echo " :sum = ", env.toStringOf(sum) | |
| 86 | + | |
| 87 | + # Unbox properly: every Clojure number is a java.lang.Number. | |
| 88 | + let numberCls = env.findClass("java/lang/Number") | |
| 89 | + echo " :sum as Nim int = ", | |
| 90 | + env.call[:JLong](sum, numberCls, "longValue", "()J") | |
| 91 | + | |
| 92 | + # --- a lazy seq, realised from Nim ---------------------------------------- | |
| 93 | + block: | |
| 94 | + env.withLocalFrame(32): | |
| 95 | + let fizzbuzz = lookup("demo.core", "fizzbuzz") | |
| 96 | + let s = fizzbuzz.invoke(box(15)) | |
| 97 | + # pr-str gives us Clojure's own printer rather than Java's toString. | |
| 98 | + let prStr = lookup("clojure.core", "pr-str") | |
| 99 | + echo "fizzbuzz = ", env.toStringOf(prStr.invoke(s)) | |
| 100 | + | |
| 101 | + # --- ex-info arriving as a Nim exception ---------------------------------- | |
| 102 | + block: | |
| 103 | + let explode = lookup("demo.core", "explode") | |
| 104 | + try: | |
| 105 | + discard env.call[:JObjectRef](explode, ifn, "invoke", "()Ljava/lang/Object;") | |
| 106 | + except JavaError as e: | |
| 107 | + echo &"caught {e.className}: {e.javaMessage}" | |
| 108 | + | |
| 109 | + echo "done" | |
| 110 | + | |
| 111 | +main() | |
| new file mode 100644 | |||
| @@ -0,0 +1,111 @@ | |||
| 1 | +## Calling Clojure from Nim. | ||
| 2 | +## | ||
| 3 | +## Run with: just clojure | ||
| 4 | +## | ||
| 5 | +## There is no Clojure-specific JNI. Clojure is a jar on the classpath, and the | ||
| 6 | +## whole language is reachable through two members of its documented Java API: | ||
| 7 | +## | ||
| 8 | +## clojure.java.api.Clojure/var -- look a var up by namespace and name, | ||
| 9 | +## handing back a clojure.lang.IFn | ||
| 10 | +## clojure.lang.IFn/invoke -- call it | ||
| 11 | +## | ||
| 12 | +## Everything else here -- requiring a namespace, reading a keyword, pulling a | ||
| 13 | +## value out of a Clojure map -- is those two calls applied to `clojure.core`. | ||
| 14 | + | ||
| 15 | +import std/[strformat, os, strutils] | ||
| 16 | +import libjava | ||
| 17 | + | ||
| 18 | +proc classPath(): seq[string] = | ||
| 19 | + ## Resolved from deps.edn by `clojure -Spath`; `just clojure` puts it here. | ||
| 20 | + let f = "build/clojure-classpath" | ||
| 21 | + if not fileExists(f): | ||
| 22 | + quit "missing " & f & " -- run `just clojure` rather than nim directly" | ||
| 23 | + result = readFile(f).strip().split(':') | ||
| 24 | + | ||
| 25 | +proc main() = | ||
| 26 | + let jvm = startJVM(classPath = classPath(), options = ["-Xmx512m"]) | ||
| 27 | + defer: jvm.destroy() | ||
| 28 | + let env = jvm.env | ||
| 29 | + | ||
| 30 | + # --- the two entry points ------------------------------------------------- | ||
| 31 | + let clojure = env.findClass("clojure/java/api/Clojure") | ||
| 32 | + let ifn = env.findClass("clojure/lang/IFn") | ||
| 33 | + | ||
| 34 | + proc lookup(ns, name: string): JObjectRef = | ||
| 35 | + ## clojure.java.api.Clojure/var -- note both parameters are Object, so the | ||
| 36 | + ## strings we pass are boxed as java.lang.String and read by Clojure itself. | ||
| 37 | + env.callStatic[:JObjectRef](clojure, "var", | ||
| 38 | + "(Ljava/lang/Object;Ljava/lang/Object;)Lclojure/lang/IFn;", ns, name) | ||
| 39 | + | ||
| 40 | + proc read(s: string): JObjectRef = | ||
| 41 | + ## clojure.java.api.Clojure/read -- the reader, for keywords and literals. | ||
| 42 | + env.callStatic[:JObjectRef](clojure, "read", | ||
| 43 | + "(Ljava/lang/String;)Ljava/lang/Object;", s) | ||
| 44 | + | ||
| 45 | + proc invoke(f: JObjectRef, a: JObjectRef): JObjectRef = | ||
| 46 | + env.call[:JObjectRef](f, ifn, "invoke", | ||
| 47 | + "(Ljava/lang/Object;)Ljava/lang/Object;", a) | ||
| 48 | + | ||
| 49 | + proc invoke(f: JObjectRef, a, b: JObjectRef): JObjectRef = | ||
| 50 | + env.call[:JObjectRef](f, ifn, "invoke", | ||
| 51 | + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", a, b) | ||
| 52 | + | ||
| 53 | + # Boxing: Clojure's IFn takes Objects, so primitives must become java.lang.Long. | ||
| 54 | + let longCls = env.findClass("java/lang/Long") | ||
| 55 | + proc box(n: int64): JObjectRef = | ||
| 56 | + env.callStatic[:JObjectRef](longCls, "valueOf", "(J)Ljava/lang/Long;", asLong(n)) | ||
| 57 | + | ||
| 58 | + # --- load our namespace --------------------------------------------------- | ||
| 59 | + # (require 'demo.core) -- the symbol comes from the reader, not from a string. | ||
| 60 | + block: | ||
| 61 | + let require = lookup("clojure.core", "require") | ||
| 62 | + discard require.invoke(read("demo.core")) | ||
| 63 | + echo "required demo.core" | ||
| 64 | + | ||
| 65 | + # --- call a plain function ------------------------------------------------ | ||
| 66 | + block: | ||
| 67 | + let greet = lookup("demo.core", "greet") | ||
| 68 | + let s = greet.invoke(env.newJString("world"), box(2)) | ||
| 69 | + echo "greet = ", env.toStringOf(s) | ||
| 70 | + | ||
| 71 | + # --- a function returning a Clojure map ----------------------------------- | ||
| 72 | + block: | ||
| 73 | + env.withLocalFrame(16): | ||
| 74 | + let stats = lookup("demo.core", "stats") | ||
| 75 | + let vec = lookup("clojure.core", "vector") | ||
| 76 | + let data = env.call[:JObjectRef](vec, ifn, "invoke", | ||
| 77 | + "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", | ||
| 78 | + box(3), box(1), box(4), box(1)) | ||
| 79 | + let m = stats.invoke(data) | ||
| 80 | + echo "stats = ", env.toStringOf(m) | ||
| 81 | + | ||
| 82 | + # Navigate the map with clojure.core/get rather than a Java accessor. | ||
| 83 | + let get = lookup("clojure.core", "get") | ||
| 84 | + let sum = get.invoke(m, read(":sum")) | ||
| 85 | + echo " :sum = ", env.toStringOf(sum) | ||
| 86 | + | ||
| 87 | + # Unbox properly: every Clojure number is a java.lang.Number. | ||
| 88 | + let numberCls = env.findClass("java/lang/Number") | ||
| 89 | + echo " :sum as Nim int = ", | ||
| 90 | + env.call[:JLong](sum, numberCls, "longValue", "()J") | ||
| 91 | + | ||
| 92 | + # --- a lazy seq, realised from Nim ---------------------------------------- | ||
| 93 | + block: | ||
| 94 | + env.withLocalFrame(32): | ||
| 95 | + let fizzbuzz = lookup("demo.core", "fizzbuzz") | ||
| 96 | + let s = fizzbuzz.invoke(box(15)) | ||
| 97 | + # pr-str gives us Clojure's own printer rather than Java's toString. | ||
| 98 | + let prStr = lookup("clojure.core", "pr-str") | ||
| 99 | + echo "fizzbuzz = ", env.toStringOf(prStr.invoke(s)) | ||
| 100 | + | ||
| 101 | + # --- ex-info arriving as a Nim exception ---------------------------------- | ||
| 102 | + block: | ||
| 103 | + let explode = lookup("demo.core", "explode") | ||
| 104 | + try: | ||
| 105 | + discard env.call[:JObjectRef](explode, ifn, "invoke", "()Ljava/lang/Object;") | ||
| 106 | + except JavaError as e: | ||
| 107 | + echo &"caught {e.className}: {e.javaMessage}" | ||
| 108 | + | ||
| 109 | + echo "done" | ||
| 110 | + | ||
| 111 | +main() | ||
modified
justfile +12 -1 | @@ -17,6 +17,17 @@ test: | ||
| 17 | 17 | demo: |
| 18 | 18 | nimble demo |
| 19 | 19 | |
| 20 | +# Resolve the Clojure classpath from deps.edn into build/clojure-classpath | |
| 21 | +clojure-cp: | |
| 22 | + #!/usr/bin/env bash | |
| 23 | + set -euo pipefail | |
| 24 | + mkdir -p build | |
| 25 | + clojure -Spath > build/clojure-classpath | |
| 26 | + | |
| 27 | +# Run the Clojure interop example (needs the `clojure` CLI on PATH) | |
| 28 | +clojure: clojure-cp | |
| 29 | + nim c -r --path:src examples/clojure_demo.nim | |
| 30 | + | |
| 20 | 31 | # Compile the example binary into out/ (release build, nothing left in-tree) |
| 21 | 32 | build: java |
| 22 | 33 | nim c -d:release --path:src --nimcache:build/nimcache -o:out/demo examples/demo.nim |
| @@ -36,7 +47,7 @@ docs: | ||
| 36 | 47 | |
| 37 | 48 | # Remove build outputs and compiled test/example binaries |
| 38 | 49 | clean: |
| 39 | - rm -rf build nimcache out | |
| 50 | + rm -rf build nimcache out .cpcache | |
| 40 | 51 | rm -f tests/tlibjava examples/demo |
| 41 | 52 | |
| 42 | 53 | # Print the JDK this build will link against |
| @@ -17,6 +17,17 @@ test: | |||
| 17 | demo: | 17 | demo: |
| 18 | nimble demo | 18 | nimble demo |
| 19 | 19 | ||
| 20 | +# Resolve the Clojure classpath from deps.edn into build/clojure-classpath | ||
| 21 | +clojure-cp: | ||
| 22 | + #!/usr/bin/env bash | ||
| 23 | + set -euo pipefail | ||
| 24 | + mkdir -p build | ||
| 25 | + clojure -Spath > build/clojure-classpath | ||
| 26 | + | ||
| 27 | +# Run the Clojure interop example (needs the `clojure` CLI on PATH) | ||
| 28 | +clojure: clojure-cp | ||
| 29 | + nim c -r --path:src examples/clojure_demo.nim | ||
| 30 | + | ||
| 20 | # Compile the example binary into out/ (release build, nothing left in-tree) | 31 | # Compile the example binary into out/ (release build, nothing left in-tree) |
| 21 | build: java | 32 | build: java |
| 22 | nim c -d:release --path:src --nimcache:build/nimcache -o:out/demo examples/demo.nim | 33 | nim c -d:release --path:src --nimcache:build/nimcache -o:out/demo examples/demo.nim |
| @@ -36,7 +47,7 @@ docs: | |||
| 36 | 47 | ||
| 37 | # Remove build outputs and compiled test/example binaries | 48 | # Remove build outputs and compiled test/example binaries |
| 38 | clean: | 49 | clean: |
| 39 | - rm -rf build nimcache out | 50 | + rm -rf build nimcache out .cpcache |
| 40 | rm -f tests/tlibjava examples/demo | 51 | rm -f tests/tlibjava examples/demo |
| 41 | 52 | ||
| 42 | # Print the JDK this build will link against | 53 | # Print the JDK this build will link against |