From ec74c68f4863e9f56cc214d4b8cc05eebc3b58cf Mon Sep 17 00:00:00 2001 From: Jakub Holy Date: Sun, 6 Oct 2024 00:34:53 +0200 Subject: [PATCH] Experiment: Try apply standard-clojure-style (v 0.5.0) --- notebooks/advanced/customizing_wolframite.clj | 5 +- notebooks/advanced/index.clj | 3 +- notebooks/advanced/packages.clj | 12 +- notebooks/clojure_primer/index.clj | 15 +- notebooks/differences_from_clojuratica.clj | 2 +- notebooks/faq.clj | 8 +- .../for_developers/demo_analysis_cycling.clj | 22 +- notebooks/for_developers/index.clj | 5 +- .../for_developers/wolfram_for_clojurians.clj | 32 +- notebooks/for_scientists/cavity_physics.clj | 32 +- notebooks/for_scientists/index.clj | 12 +- notebooks/gotchas.clj | 4 +- notebooks/index.clj | 6 +- notebooks/lowlevel.clj | 12 +- notebooks/quickstart.clj | 26 +- notebooks/understanding_wolframite.clj | 35 +- .../scicloj/wolframite/config.edn | 2 +- src/wolframite/api/v1.clj | 8 +- src/wolframite/base/cep.clj | 38 +- src/wolframite/base/convert.clj | 290 +++++++--------- src/wolframite/base/evaluate.clj | 100 +++--- src/wolframite/base/expr.clj | 30 +- src/wolframite/base/express.clj | 46 +-- src/wolframite/base/package.clj | 12 +- src/wolframite/base/parse.clj | 324 ++++++++---------- src/wolframite/core.clj | 10 +- src/wolframite/impl/jlink_instance.clj | 8 +- src/wolframite/impl/jlink_proto_impl.clj | 33 +- src/wolframite/impl/protocols.clj | 5 +- src/wolframite/impl/wolfram_syms/intern.clj | 14 +- .../impl/wolfram_syms/wolfram_syms.clj | 11 +- src/wolframite/impl/wolfram_syms/write_ns.clj | 41 +-- .../impl/wolfram_syms/write_ns/includes.clj | 10 +- src/wolframite/lib/helpers.clj | 5 +- src/wolframite/lib/options.clj | 78 ++--- src/wolframite/runtime/defaults.clj | 23 +- src/wolframite/runtime/jlink.clj | 8 +- src/wolframite/runtime/system.clj | 14 +- src/wolframite/specs.clj | 19 +- src/wolframite/tools/clerk_helper.clj | 15 +- src/wolframite/tools/experimental.clj | 14 +- src/wolframite/tools/graphics.clj | 19 +- src/wolframite/tools/hiccup.clj | 34 +- src/wolframite/tools/portal.clj | 5 +- src/wolframite/wolfram.clj | 9 +- src/wolframite/wolfram_extended.clj | 7 +- 46 files changed, 703 insertions(+), 760 deletions(-) diff --git a/notebooks/advanced/customizing_wolframite.clj b/notebooks/advanced/customizing_wolframite.clj index 41fd6575..7e952130 100644 --- a/notebooks/advanced/customizing_wolframite.clj +++ b/notebooks/advanced/customizing_wolframite.clj @@ -1,6 +1,7 @@ (ns advanced.customizing-wolframite - (:require [scicloj.kindly.v4.kind :as k] - [wolframite.core :as wl])) + (:require + [scicloj.kindly.v4.kind :as k] + [wolframite.core :as wl])) (k/md "## Customizing Wolframite diff --git a/notebooks/advanced/index.clj b/notebooks/advanced/index.clj index 330aad03..6e23595c 100644 --- a/notebooks/advanced/index.clj +++ b/notebooks/advanced/index.clj @@ -1,4 +1,5 @@ (ns advanced.index - (:require [scicloj.kindly.v4.kind :as k])) + (:require + [scicloj.kindly.v4.kind :as k])) (k/md "See the other pages in this section.") diff --git a/notebooks/advanced/packages.clj b/notebooks/advanced/packages.clj index 75dd5d36..754f644e 100644 --- a/notebooks/advanced/packages.clj +++ b/notebooks/advanced/packages.clj @@ -1,9 +1,9 @@ (ns advanced.packages "A notebook demonstrating how to import and use Wolfram packages in Wolframite." (:require - [scicloj.kindly.v4.kind :as k] - [wolframite.core :as wl] - [wolframite.wolfram :as w])) + [scicloj.kindly.v4.kind :as k] + [wolframite.core :as wl] + [wolframite.wolfram :as w])) (k/md "# Packages @@ -29,11 +29,11 @@ additional[y_]:=3*y (wl/< / < <= = == > >= fn - Byte Character Integer Number Short String Thread]])) + [scicloj.kindly.v4.kind :as k] + [wolframite.core :as wl] + [wolframite.wolfram :as w :refer :all :exclude [* + - -> / < <= = == > >= Byte Character Integer Number Short String Thread fn]])) (wl/start!) diff --git a/notebooks/for_developers/demo_analysis_cycling.clj b/notebooks/for_developers/demo_analysis_cycling.clj index 619f51f2..10d56cdd 100644 --- a/notebooks/for_developers/demo_analysis_cycling.clj +++ b/notebooks/for_developers/demo_analysis_cycling.clj @@ -4,14 +4,13 @@ (:require [clojure.java.io :as io] [clojure.string :as str] + [scicloj.kindly.v4.kind :as k] [wolframite.core :as wl] - [wolframite.lib.helpers :as h] - [wolframite.wolfram :as w :refer :all - :exclude [* + - -> / < <= = == > >= fn - Byte Character Integer Number Short String Thread]] [wolframite.impl.wolfram-syms.intern :as intern] - [scicloj.kindly.v4.kind :as k]) - (:import (java.util.zip GZIPInputStream))) + [wolframite.lib.helpers :as h] + [wolframite.wolfram :as w :refer :all :exclude [* + - -> / < <= = == > >= Byte Character Integer Number Short String Thread fn]]) + (:import + (java.util.zip GZIPInputStream))) (k/md " We introduce you, the motivated Clojure developer, to using the Wolfram programming language as a Clojure library. Following some brief inspiration (why on earth should you do this?), and some getting started notes, we outline a 'real' workflow using the example of analysing data about bike trips.") @@ -57,10 +56,10 @@ Thus we will need a more DIY and lower-level approach to getting the data in, le Sadly, it cannot handle a gzpipped files (as far as I know) so we need to unzip it first:") (when-not (.exists (io/file "/tmp/huge.csv")) - (let [zip (io/file "docs-buildtime-data/202304_divvy_tripdata_first100k.csv.gz")] - (with-open [zis (GZIPInputStream. (io/input-stream zip))] - (io/copy zis (io/file "/tmp/huge.csv")))) - :extracted) + (let [zip (io/file "docs-buildtime-data/202304_divvy_tripdata_first100k.csv.gz")] + (with-open [zis (GZIPInputStream. (io/input-stream zip))] + (io/copy zis (io/file "/tmp/huge.csv")))) + :extracted) (k/md " Now we are ready to read the data in. We will store them into a Wolfram-side var so that we can work with them further. @@ -72,7 +71,6 @@ For readability and auto-completion, we will define vars for the names of the Wo (def rawRows "Wolf var - unparsed data rows" 'rawRows) (def rows "Wolf var - parsed data rows" 'rows) - (wl/eval (w/do (w/= 'f (w/OpenRead "/tmp/huge.csv")) (w/= csv (w/ReadList 'f w/Word @@ -83,7 +81,6 @@ For readability and auto-completion, we will define vars for the names of the Wo ;; Let's return only the length instead of all the large data: (w/Length csv))) - (k/md (str "We leverage the flexibility of [ReadList](" (first (h/help! w/ReadList :links true)) "), instructing it to read \"Words\" separated by `,` (instead of applying the normal word separating characters), thus reading individual column values. It reads them as records, separated by the given separators (which are the same as the default, shown here for clarity). @@ -163,7 +160,6 @@ Let's see how it works:") (let [header->idx' (zipmap loc-ks (next (range)))] (w/fn [row] (mapv #(w/Part row (header->idx' %)) col-names)))) - ;; For me, it took ±0.8s to extract the 2 columns as text and 1.5s to parse them into numbers. With `ToExpression` it would take ±5s. ;; #### Starting positions in a map diff --git a/notebooks/for_developers/index.clj b/notebooks/for_developers/index.clj index 3e8d4be1..31224d56 100644 --- a/notebooks/for_developers/index.clj +++ b/notebooks/for_developers/index.clj @@ -1,10 +1,11 @@ ^:kindly/hide-code (ns for-developers.index - (:require [scicloj.kindly.v4.kind :as k])) + (:require + [scicloj.kindly.v4.kind :as k])) (k/md "# Wolframite for Clojure developers {#sec-for-developers} This section is intended for you, a developer familiar with Clojure, who may know little or nothing about Wolfram. You may decide whether you want to learn a little about Wolfram first in @sec-wolfram-for-clojurians or jump right to -an example analysis of data with Wolfram in @sec-demo-analysis-cycling.") \ No newline at end of file +an example analysis of data with Wolfram in @sec-demo-analysis-cycling.") diff --git a/notebooks/for_developers/wolfram_for_clojurians.clj b/notebooks/for_developers/wolfram_for_clojurians.clj index db892664..3e35eb9e 100644 --- a/notebooks/for_developers/wolfram_for_clojurians.clj +++ b/notebooks/for_developers/wolfram_for_clojurians.clj @@ -1,11 +1,10 @@ ;; # Brief introduction into Wolfram Language for Clojure developers {#sec-wolfram-for-clojurians} (ns for-developers.wolfram-for-clojurians - (:require [scicloj.kindly.v4.kind :as k] - [wolframite.core :as wl] - [wolframite.wolfram :as w :refer :all - :exclude [* + - -> / < <= = == > >= fn - Byte Character Integer Number Short String Thread]])) + (:require + [scicloj.kindly.v4.kind :as k] + [wolframite.core :as wl] + [wolframite.wolfram :as w :refer :all :exclude [* + - -> / < <= = == > >= Byte Character Integer Number Short String Thread fn]])) (wl/start!) @@ -56,7 +55,6 @@ may use `#, #1, #2, ...` or `(Slot 1), (Slot 1), (Slot 2), ...` equivalent to Cl In Wolframite, you'll typically use `w/fn` or leverage the _operator form_ of functions (see @sec-operator-form). ") - (k/md "#### [Lists](https://www.wolfram.com/language/fast-introduction-for-programmers/en/lists/) ") ; A Wolfram List `{1, "hello", 3.14}` becomes a vector in Wolframite: `[1, "hello", 3.14]`. @@ -141,16 +139,16 @@ See [Functionals & Operators](https://www.wolfram.com/language/fast-introduction (k/md "#### Clojure <-> Wolfram ") ^:kindly/hide-code -(k/table {:column-names [:Clojure :Wolfram :Comments], - :row-vectors [["apply" "Apply"] - ["comp" "Composition"] - ["count" "Length"] - ["filter" "Select"] - ["nth" "Part" "1-based indexing"] - ["map" "Map"] - ["partial" "operator form" "(see above)"] - ["reduce" "Fold"] - ["take" "Part"]]}) +(k/table {:column-names [:Clojure :Wolfram :Comments] + ,:row-vectors [["apply" "Apply"] + ["comp" "Composition"] + ["count" "Length"] + ["filter" "Select"] + ["nth" "Part" "1-based indexing"] + ["map" "Map"] + ["partial" "operator form" "(see above)"] + ["reduce" "Fold"] + ["take" "Part"]]}) (k/md "#### Additional resources @@ -158,4 +156,4 @@ Read more in the online booklet [The Wolfram Language: Fast Introduction for Pro which we have borrowed heavily from. The dense one-page [Wolfram Language Syntax](https://reference.wolfram.com/language/guide/Syntax.html) may also be of use, especially when reading Wolfram code. -") \ No newline at end of file +") diff --git a/notebooks/for_scientists/cavity_physics.clj b/notebooks/for_scientists/cavity_physics.clj index d81a1582..e110a9ab 100644 --- a/notebooks/for_scientists/cavity_physics.clj +++ b/notebooks/for_scientists/cavity_physics.clj @@ -2,13 +2,11 @@ "The second part of Wolframite for scientists. Here, we consider a real physics problem as a demonstration of how one might use Wolframite in practice." (:require - [clojure.math :as math] - [scicloj.kindly.v4.kind :as k] - [wolframite.core :as wl] - [wolframite.tools.hiccup :as wh] - [wolframite.wolfram :as w :refer :all - :exclude [* + - -> / < <= = == > >= fn - Byte Character Integer Number Short String Thread]])) + [clojure.math :as math] + [scicloj.kindly.v4.kind :as k] + [wolframite.core :as wl] + [wolframite.tools.hiccup :as wh] + [wolframite.wolfram :as w :refer :all :exclude [* + - -> / < <= = == > >= Byte Character Integer Number Short String Thread fn]])) (k/md "# Wolframite for scientists II (Cavity physics) @@ -119,7 +117,7 @@ At position three, the field undergoes reflection and so now carries an addition (k/md "Substitution and simplification can then be used to arrive at the transmission and reflection, respectively.") (def T (-> (x>> (w/* E2 't2) - e4) + e4) w/>>_<<)) (TeX->> T (w/== 'T)) @@ -257,17 +255,17 @@ For this, we will define a few utility functions, that also demonstrate Wolfram' (wl/eval (w/_= 'nums (mapv Efield--transmission-phase (coordinates - (math/sqrt 50E-6) - (math/sqrt 700E-6) - -0.001 - 0.001)))) + (math/sqrt 50E-6) + (math/sqrt 700E-6) + -0.001 + 0.001)))) (wh/view - (w/ListPlot3D 'nums - (w/-> PlotRange All) - (w/-> Boxed false) - (w/-> AxesLabel - ["Mirror transmission" "Phase" "Intracavity intensity"]))) + (w/ListPlot3D 'nums + (w/-> PlotRange All) + (w/-> Boxed false) + (w/-> AxesLabel + ["Mirror transmission" "Phase" "Intracavity intensity"]))) (k/md "And there you have it! It turns out that if you get the phase right and you buy high quality mirrors then you can massively amplify the laser light. In fact, if you add a 'gain' material in the middle then such light amplification by the stimulated emission of radiation has a catchier name: it's called a *Laser*!") diff --git a/notebooks/for_scientists/index.clj b/notebooks/for_scientists/index.clj index 7197e509..6e277bc3 100644 --- a/notebooks/for_scientists/index.clj +++ b/notebooks/for_scientists/index.clj @@ -1,13 +1,11 @@ (ns for-scientists.index "An introduction to the library, that might even be suitable for physicists." (:require - [scicloj.kindly.v4.kind :as k] - [wolframite.core :as wl] - [wolframite.impl.wolfram-syms.write-ns :as write] - [wolframite.tools.hiccup :as wh] - [wolframite.wolfram :as w :refer :all - :exclude [* + - -> / < <= = == > >= fn - Byte Character Integer Number Short String Thread]])) + [scicloj.kindly.v4.kind :as k] + [wolframite.core :as wl] + [wolframite.impl.wolfram-syms.write-ns :as write] + [wolframite.tools.hiccup :as wh] + [wolframite.wolfram :as w :refer :all :exclude [* + - -> / < <= = == > >= Byte Character Integer Number Short String Thread fn]])) (k/md "# Wolframite for scientists I (Introduction) {#sec-scientists}") (k/md "## Abstract diff --git a/notebooks/gotchas.clj b/notebooks/gotchas.clj index ec9c0564..a31c3839 100644 --- a/notebooks/gotchas.clj +++ b/notebooks/gotchas.clj @@ -1,6 +1,6 @@ (ns gotchas - [:require - [scicloj.kindly.v4.kind :as k]]) + (:require + [scicloj.kindly.v4.kind :as k])) (k/md "# Gotchas... {#sec-gotchas} Although we try to avoid such things, sometimes, when you're fighting the host language, it's just not practical to carry over the original conventions. Here we will try to keep an up-to-date list of possible surprises, when coming from Wolfram to Wolframite (that are not easy to 'fix'). diff --git a/notebooks/index.clj b/notebooks/index.clj index 55574020..afe36615 100644 --- a/notebooks/index.clj +++ b/notebooks/index.clj @@ -2,8 +2,8 @@ (ns index "Entrypoint for the Clay-generated docs -> docs/generated/index.htm" (:require - [scicloj.kindly.v4.kind :as kind] - [scicloj.kindly.v4.api :as kindly])) + [scicloj.kindly.v4.api :as kindly] + [scicloj.kindly.v4.kind :as kind])) ^:kindly/hide-code (defmacro slurp-markdown [path] @@ -15,4 +15,4 @@ (kind/md (slurp-markdown "./README.md"))) ;; ## Further documentation -;; See the book content menu on the left side \ No newline at end of file +;; See the book content menu on the left side diff --git a/notebooks/lowlevel.clj b/notebooks/lowlevel.clj index 555ba8a7..7b0ad01e 100644 --- a/notebooks/lowlevel.clj +++ b/notebooks/lowlevel.clj @@ -1,11 +1,11 @@ (ns lowlevel "Documenting operations that are carried out at a low level of abstraction." (:require - [wolframite.core :as wl] - [wolframite.wolfram :as w] - [wolframite.impl.jlink-instance :as jlink-instance] - [wolframite.base.parse :as parse] - [scicloj.kindly.v4.kind :as k])) + [scicloj.kindly.v4.kind :as k] + [wolframite.base.parse :as parse] + [wolframite.core :as wl] + [wolframite.impl.jlink-instance :as jlink-instance] + [wolframite.wolfram :as w])) (k/md "# Low level interoperation @@ -18,7 +18,7 @@ Although it shouldn't normally be necessary, we can also intern Wolfram function The standard way of doing this is something like") (def greetings (wl/eval - (w/fn [x] (w/StringJoin "Hello, " x "! This is a Mathematica function's output.")))) + (w/fn [x] (w/StringJoin "Hello, " x "! This is a Mathematica function's output.")))) (greetings "Stephen") (k/md "But this can also be done at a lower level, e.g.") diff --git a/notebooks/quickstart.clj b/notebooks/quickstart.clj index 10cc3726..9e9c6fe7 100644 --- a/notebooks/quickstart.clj +++ b/notebooks/quickstart.clj @@ -13,14 +13,12 @@ (:require [clojure.set :as set] [clojure.string :as str] + [scicloj.kindly.v4.kind :as k] [wolframite.core :as wl] [wolframite.lib.helpers :as h] [wolframite.runtime.defaults :as defaults] [wolframite.tools.hiccup :as wh] - [wolframite.wolfram :as w :refer :all - :exclude [* + - -> / < <= = == > >= fn - Byte Character Integer Number Short String Thread]] - [scicloj.kindly.v4.kind :as k])) + [wolframite.wolfram :as w :refer :all :exclude [* + - -> / < <= = == > >= Byte Character Integer Number Short String Thread fn]])) (k/md "## Init @@ -101,21 +99,21 @@ In a nutshell, this is how Wolframite is normally used, but, of course, this bar In particular, the flagship product of Wolfram, the one you've probably heard of, is Mathematica. And, as the name suggests, this entire system was built around the performance of abstract calculation and the manipulation of equations, e.g. ") (k/tex - (-> (w/== 'E (w/* 'm (Power 'c 2))) - TeXForm - ToString - wl/eval)) + (-> (w/== 'E (w/* 'm (Power 'c 2))) + TeXForm + ToString + wl/eval)) (k/md "originally answered the question 'what is mass?'") (k/tex - (-> (w/== 'E (w/* 'm (Power 'c 2))) - (Solve 'm) - First First + (-> (w/== 'E (w/* 'm (Power 'c 2))) + (Solve 'm) + First First - TeXForm - ToString - wl/eval)) + TeXForm + ToString + wl/eval)) (k/md "This is where Wolfram, and so Wolframite, really shines. And if you're interested in exploring this further, have a look at one of our longer tutorials. ") diff --git a/notebooks/understanding_wolframite.clj b/notebooks/understanding_wolframite.clj index 15979ed1..c9d3e94f 100644 --- a/notebooks/understanding_wolframite.clj +++ b/notebooks/understanding_wolframite.clj @@ -8,16 +8,15 @@ ;; ;; First, we need some namespaces: (ns understanding-wolframite - (:require [scicloj.kindly.v4.api :as kindly] - [scicloj.kindly.v4.kind :as kind] - [scicloj.kindly.v4.kind :as k] - [clojure.repl :as repl] - [wolframite.core :as wl] - [wolframite.lib.helpers :as h] - [wolframite.wolfram :as w :refer :all - :exclude [* + - -> / < <= = == > >= fn - Byte Character Integer Number Short String Thread]] - wolframite.runtime.defaults)) + (:require + [clojure.repl :as repl] + [scicloj.kindly.v4.api :as kindly] + [scicloj.kindly.v4.kind :as kind] + [scicloj.kindly.v4.kind :as k] + [wolframite.core :as wl] + [wolframite.lib.helpers :as h] + [wolframite.runtime.defaults] + [wolframite.wolfram :as w :refer :all :exclude [* + - -> / < <= = == > >= Byte Character Integer Number Short String Thread fn]])) (k/md "Next, we need to actually start a [Wolfram Kernel](https://reference.wolfram.com/language/ref/program/WolframKernel.html) and connect to it:") @@ -139,11 +138,11 @@ There is one more form, the _Wolfram string form_, which is the raw Wolfram code ^:kindly/hide-code (k/hidden (def recommended-exclusions (set (wl/ns-exclusions)))) -(k/table {:column-names [:Alias :Wolfram "Can be used without `w/` prefix?"], - :row-vectors (-> wolframite.runtime.defaults/all-aliases - (dissoc '-) - (assoc '- "Minus or Subtract") - (->> (map (fn [[k v]] [k v (when-not (contains? recommended-exclusions k) "✅")]))))}) +(k/table {:column-names [:Alias :Wolfram "Can be used without `w/` prefix?"] + ,:row-vectors (-> wolframite.runtime.defaults/all-aliases + (dissoc '-) + (assoc '- "Minus or Subtract") + (->> (map (fn [[k v]] [k v (when-not (contains? recommended-exclusions k) "✅")]))))}) (k/md "Thus, the following two expressions are equivalent") @@ -190,8 +189,8 @@ convenience functions of the evaluated form: ^:kindly/hide-code (quote (wl/eval (Import "demo.csv.gz" - ["Data" (Span 1 3)], - (w/-> "HeaderLines" 1)))) + ["Data" (Span 1 3)] + ,(w/-> "HeaderLines" 1)))) (k/md "## Errors @@ -238,7 +237,7 @@ We could instead ask for the link(s):") #_"TODO(Jakub) the below shall replace the above when https://github.com/scicloj/clay/issues/171 fixed" #_ (kindly/hide-code - (k/md (h/help! w/ArithmeticGeometricMean :links true)) + (k/md (h/help! w/ArithmeticGeometricMean :links true)) false) (k/md "`h/help!` also works on whole expressions, providing docs for each symbol:") diff --git a/src/clj-kondo.exports/scicloj/wolframite/config.edn b/src/clj-kondo.exports/scicloj/wolframite/config.edn index 1c8df27d..eacb4db1 100644 --- a/src/clj-kondo.exports/scicloj/wolframite/config.edn +++ b/src/clj-kondo.exports/scicloj/wolframite/config.edn @@ -1,3 +1,3 @@ -{;:hooks {:macroexpand {wolframite.lib.options/let-options clojurativa.lib.options/let-options}} +{; :hooks {:macroexpand {wolframite.lib.options/let-options clojurativa.lib.options/let-options}} :lint-as {wolframite.wolfram/fn clojure.core/fn} :linters {:unresolved-symbol {:exclude []}}} diff --git a/src/wolframite/api/v1.clj b/src/wolframite/api/v1.clj index 1ba746a8..e2b1c65e 100644 --- a/src/wolframite/api/v1.clj +++ b/src/wolframite/api/v1.clj @@ -1,7 +1,8 @@ (ns wolframite.api.v1 (:refer-clojure :exclude [eval]) - (:require [wolframite.core :as core] - [wolframite.lib.helpers :as helper])) + (:require + [wolframite.core :as core] + [wolframite.lib.helpers :as helper])) (def start! "Initialize Wolframite and start! the underlying Wolfram Kernel - required once before you make any eval calls. @@ -88,5 +89,4 @@ (comment (start!) (! '(+ 1 1)) - (help! '(+ 1 1)) ;; => Doesn't seem to be opening a browser for me? - ) + (help! '(+ 1 1))) ;; => Doesn't seem to be opening a browser for me? diff --git a/src/wolframite/base/cep.clj b/src/wolframite/base/cep.clj index 0877dcb5..76cdd4a0 100644 --- a/src/wolframite/base/cep.clj +++ b/src/wolframite/base/cep.clj @@ -1,24 +1,22 @@ -(ns wolframite.base.cep - (:require - [wolframite.lib.options :as options] - [wolframite.base.convert :as convert] - [wolframite.base.evaluate :as evaluate] - [wolframite.base.parse :as parse])) - -(defn- identity-first [x & _] x) - -(defn cep +(ns wolframite.base.cep + (:require + [wolframite.base.convert :as convert] + [wolframite.base.evaluate :as evaluate] + [wolframite.base.parse :as parse] + [wolframite.lib.options :as options])) +(defn- identity-first [x & _] x) +(defn cep "Convert-Evaluate-Parse pipeline. Convert: from clj data to jlink Expr Evaluate: the Expr on (some) Wolfram Engine Parse: returned result into clj data. - Each stage can be skipped with appropriate `opts` `:flag` e.g. `:no-parse`" - [expr {:keys [flags] - :as opts}] - (let [convert (if (options/flag?' flags :convert) convert/convert identity-first) - evaluate (if (options/flag?' flags :evaluate) evaluate/evaluate identity-first) - parse (if (options/flag?' flags :parse) parse/parse identity-first)] - (-> expr - (convert opts) - (evaluate opts) - (parse opts)))) + Each stage can be skipped with appropriate `opts` `:flag` e.g. `:no-parse`" + [expr {:keys [flags] + :as opts}] + (let [convert (if (options/flag?' flags :convert) convert/convert identity-first) + evaluate (if (options/flag?' flags :evaluate) evaluate/evaluate identity-first) + parse (if (options/flag?' flags :parse) parse/parse identity-first)] + (-> expr + (convert opts) + (evaluate opts) + (parse opts)))) diff --git a/src/wolframite/base/convert.clj b/src/wolframite/base/convert.clj index 529eb255..508845e4 100644 --- a/src/wolframite/base/convert.clj +++ b/src/wolframite/base/convert.clj @@ -1,178 +1,152 @@ -(ns wolframite.base.convert - "Convert a Clojure expression into a Wolfram JLink expression" - (:require [wolframite.impl.jlink-instance :as jlink-instance] - [wolframite.impl.protocols :as proto] - [wolframite.impl.wolfram-syms.intern :as intern] - [wolframite.lib.options :as options] - [wolframite.base.express :as express] - [wolframite.base.expr :as expr] - [wolframite.runtime.defaults :as defaults])) - +(ns wolframite.base.convert + "Convert a Clojure expression into a Wolfram JLink expression" + (:require + [wolframite.base.expr :as expr] + [wolframite.base.express :as express] + [wolframite.impl.jlink-instance :as jlink-instance] + [wolframite.impl.protocols :as proto] + [wolframite.impl.wolfram-syms.intern :as intern] + [wolframite.lib.options :as options] + [wolframite.runtime.defaults :as defaults])) ;; (remove-ns 'wolframite.base.convert) - ;; * defmulti and dispatch - -(defn- primitive? - "Is `x` a 'primitive' value that can be directly turned into an Expr?" - [x] - (or (number? x) - (string? x))) - -(defn- supported-primitive-array? [xs] +(defn- primitive? + "Is `x` a 'primitive' value that can be directly turned into an Expr?" + [x] + (or (number? x) + (string? x))) +(defn- supported-primitive-array? [xs] ;; See jlink.Expr constructor for the types actually supported - (some-> xs .getClass .componentType (#{Byte/TYPE Double/TYPE Integer/TYPE Long/TYPE Short/TYPE}))) - -(defn- dispatch [obj] - (cond (and (list? obj) - (empty? obj)) :null - (seq? obj) :expr - (or (vector? obj) - (list? obj)) :list - (ratio? obj) :rational - (primitive? obj) :primitive - (map? obj) :hash-map - (symbol? obj) :symbol - (nil? obj) :null - (fn? obj) :fn-obj - :else nil)) - -(defmulti convert - "Convert a Wolframite clj expression into a JLink object representation" + (some-> xs .getClass .componentType (#{Byte/TYPE Double/TYPE Integer/TYPE Long/TYPE Short/TYPE}))) +(defn- dispatch [obj] + (cond (and (list? obj) + (empty? obj)) :null + (seq? obj) :expr + (or (vector? obj) + (list? obj)) :list + (ratio? obj) :rational + (primitive? obj) :primitive + (map? obj) :hash-map + (symbol? obj) :symbol + (nil? obj) :null + (fn? obj) :fn-obj + :else nil)) +(defmulti convert + "Convert a Wolframite clj expression into a JLink object representation" (fn [clj-expr _opts] (dispatch clj-expr))) ; TODO Pass jlink-instance in explicitly inst. of fetching from the global - ;; * Helpers - -(defn- simple-vector? [coll _] - (and (sequential? coll) - (not-any? dispatch coll))) - -(defn- simple-matrix? [coll opts] - (and (sequential? coll) - (every? #(simple-vector? % opts) coll))) - -(defn- cexpr-from-postfix-form [cexprs] - (assert (sequential? cexprs)) - (loop [cexpr (first cexprs) - remaining (rest cexprs)] - (if (seq remaining) - (recur (list (first remaining) cexpr) (rest remaining)) - cexpr))) - -(defn- cexpr-from-prefix-form [cexprs] - (assert (sequential? cexprs)) - (cexpr-from-postfix-form (reverse cexprs))) - +(defn- simple-vector? [coll _] + (and (sequential? coll) + (not-any? dispatch coll))) +(defn- simple-matrix? [coll opts] + (and (sequential? coll) + (every? #(simple-vector? % opts) coll))) +(defn- cexpr-from-postfix-form [cexprs] + (assert (sequential? cexprs)) + (loop [cexpr (first cexprs) + remaining (rest cexprs)] + (if (seq remaining) + (recur (list (first remaining) cexpr) (rest remaining)) + cexpr))) +(defn- cexpr-from-prefix-form [cexprs] + (assert (sequential? cexprs)) + (cexpr-from-postfix-form (reverse cexprs))) ;; * Method impls - -(defmethod convert nil [obj _] +(defmethod convert nil [obj _] ;; A fall-back implementation, for anything we do not handle directly ourselves elsewhere here. ;; Also triggered for any other unknown/unhandled type, e.g. a :kwd - (cond - (proto/expr? (jlink-instance/get) obj) + (cond + (proto/expr? (jlink-instance/get) obj) obj ; already a jlink Expr - - (supported-primitive-array? obj) - (proto/expr (jlink-instance/get) obj) - - :else - (proto/->expr (jlink-instance/get) obj))) - -(defmethod convert :fn-obj [obj opts] + (supported-primitive-array? obj) + (proto/expr (jlink-instance/get) obj) + :else + (proto/->expr (jlink-instance/get) obj))) +(defmethod convert :fn-obj [obj opts] ;; This normally means that the expression contained a reference to a var in wolframite.wolfram, ;; which has not been turned into a symbol for some reason, typically b/c it was not a fn call ;; (those do 'symbolify' themselves) => we check and do this here - (if-let [fn-name (intern/interned-var-val->symbol obj)] - (convert fn-name opts) - (throw (IllegalArgumentException. - (str "An expression contains a function object, which is not intern/wolfram-fn => " - "don't know how to turn it into a symbol that Wolfram could interpret: " - obj))))) - -(defmethod convert :null [_ opts] - (convert 'Null opts)) - -(defmethod convert :rational [n opts] - (convert (list 'Rational (.numerator n) (.denominator n)) opts)) - -(defmethod convert :primitive [primitive _opts] - (proto/expr (jlink-instance/get) primitive)) - -(defn ->wolfram-str-expr + (if-let [fn-name (intern/interned-var-val->symbol obj)] + (convert fn-name opts) + (throw (IllegalArgumentException. + (str "An expression contains a function object, which is not intern/wolfram-fn => " + "don't know how to turn it into a symbol that Wolfram could interpret: " + obj))))) +(defmethod convert :null [_ opts] + (convert 'Null opts)) +(defmethod convert :rational [n opts] + (convert (list 'Rational (.numerator n) (.denominator n)) opts)) +(defmethod convert :primitive [primitive _opts] + (proto/expr (jlink-instance/get) primitive)) +(defn ->wolfram-str-expr "Turn `str` (a raw Wolfram expression string) into a value that `convert` will - properly process as an expression, and not as a primitive string." - [str] - {::wolfram-str-expr str}) - -(defn- wolfram-str-expr->jlink-expr [wolfram-str-expr-map opts] - (do (assert (= 1 (count wolfram-str-expr-map)) "::wolfram-str-expr must stand on its own") - (assert (-> wolfram-str-expr-map vals first string?) "::wolfram-str-expr value must be a string") - (express/express (::wolfram-str-expr wolfram-str-expr-map) opts))) - -(defmethod convert :hash-map [map opts] + properly process as an expression, and not as a primitive string." + [str] + {::wolfram-str-expr str}) +(defn- wolfram-str-expr->jlink-expr [wolfram-str-expr-map opts] + (do (assert (= 1 (count wolfram-str-expr-map)) "::wolfram-str-expr must stand on its own") + (assert (-> wolfram-str-expr-map vals first string?) "::wolfram-str-expr value must be a string") + (express/express (::wolfram-str-expr wolfram-str-expr-map) opts))) +(defmethod convert :hash-map [map opts] ;; A map could be either normal map value the user supplied, or it could be our magical ;; map used to mark the actual value for special treatment, namely to be interpreted as ;; Wolfram code in a string; see `->wolfram-str-expr` - (if (::wolfram-str-expr map) - (wolfram-str-expr->jlink-expr map opts) - (convert (apply list 'Association (for [[key value] map] (list 'Rule key value))) opts))) - -(defmethod convert :symbol [sym {:keys [aliases] ::keys [args] :as opts}] - (let [all-aliases (merge defaults/all-aliases aliases)] - (if-let [alias-sym-or-fn (all-aliases sym)] - (if (defaults/experimental-fn-alias? alias-sym-or-fn) - (convert (alias-sym-or-fn args) opts) - (convert alias-sym-or-fn opts)) + (if (::wolfram-str-expr map) + (wolfram-str-expr->jlink-expr map opts) + (convert (apply list 'Association (for [[key value] map] (list 'Rule key value))) opts))) +(defmethod convert :symbol [sym {:keys [aliases] ::keys [args] :as opts}] + (let [all-aliases (merge defaults/all-aliases aliases)] + (if-let [alias-sym-or-fn (all-aliases sym)] + (if (defaults/experimental-fn-alias? alias-sym-or-fn) + (convert (alias-sym-or-fn args) opts) + (convert alias-sym-or-fn opts)) ;; Numbered args of shorthand lambdas - Clojure's `#(+ %1 %2)` => Wolfs #1 and #1 = Slot[1] and Slot[2] - (if-let [[_ ^String n] (re-matches #"%(\d*)" (str sym))] - (let [n (Long/valueOf (if (= "" n) "1" n))] - (convert (list 'Slot n) opts)) - ;(let [s (str-utils/replace (str sym) #"\|(.*?)\|" #(str "\\\\[" (second %) "]"))] ) - (let [s (str sym)] - (if (re-find #"[^a-zA-Z0-9$\/]" s) - (throw (ex-info (str "Unsupported symbol / unknown alias: Symbols passed to Mathematica must be alphanumeric" - " (apart from forward slashes and dollar signs). Other symbols may" - " only be used if there is defined a Wolframite alias for them." - " Passed: " s - " Known aliases: " (or (-> aliases keys sort seq) "N/A")) - {:unknown-symbol s - :known-symbols (keys aliases)})) - (proto/expr (jlink-instance/get) :Expr/SYMBOL s))))))) - -(defn- convert-non-simple-list [elms opts] - (let [converted-parts (map #(cond-> % (dispatch %) (convert opts)) elms)] - (if (every? (partial proto/expr? (jlink-instance/get)) converted-parts) - (proto/expr (jlink-instance/get) - (cons (convert 'List opts) - converted-parts)) - (convert (to-array converted-parts) opts)))) - -(defmethod convert :list [coll opts] - (cond (simple-matrix? coll opts) (convert (to-array-2d coll) opts) - (simple-vector? coll opts) (convert (to-array coll) opts) - :else (convert-non-simple-list coll opts))) - -(defmethod convert :expr [[head & tail :as clj-expr] opts] - (let [macro head - arg (first tail)] - (cond (= 'clojure.core/deref macro) (convert (cexpr-from-prefix-form arg) opts) - (= 'clojure.core/meta macro) (convert (cexpr-from-postfix-form arg) opts) - (= 'var macro) (convert (list 'Function arg) opts) + (if-let [[_ ^String n] (re-matches #"%(\d*)" (str sym))] + (let [n (Long/valueOf (if (= "" n) "1" n))] + (convert (list 'Slot n) opts)) + ; (let [s (str-utils/replace (str sym) #"\|(.*?)\|" #(str "\\\\[" (second %) "]"))] ) + (let [s (str sym)] + (if (re-find #"[^a-zA-Z0-9$\/]" s) + (throw (ex-info (str "Unsupported symbol / unknown alias: Symbols passed to Mathematica must be alphanumeric" + " (apart from forward slashes and dollar signs). Other symbols may" + " only be used if there is defined a Wolframite alias for them." + " Passed: " s + " Known aliases: " (or (-> aliases keys sort seq) "N/A")) + {:unknown-symbol s + :known-symbols (keys aliases)})) + (proto/expr (jlink-instance/get) :Expr/SYMBOL s))))))) +(defn- convert-non-simple-list [elms opts] + (let [converted-parts (map #(cond-> % (dispatch %) (convert opts)) elms)] + (if (every? (partial proto/expr? (jlink-instance/get)) converted-parts) + (proto/expr (jlink-instance/get) + (cons (convert 'List opts) + converted-parts)) + (convert (to-array converted-parts) opts)))) +(defmethod convert :list [coll opts] + (cond (simple-matrix? coll opts) (convert (to-array-2d coll) opts) + (simple-vector? coll opts) (convert (to-array coll) opts) + :else (convert-non-simple-list coll opts))) +(defmethod convert :expr [[head & tail :as clj-expr] opts] + (let [macro head + arg (first tail)] + (cond (= 'clojure.core/deref macro) (convert (cexpr-from-prefix-form arg) opts) + (= 'clojure.core/meta macro) (convert (cexpr-from-postfix-form arg) opts) + (= 'var macro) (convert (list 'Function arg) opts) ;; convert '(whatever...) ;; Quoted symbol intended to be sent as Wolfram symbol - (and (= 'quote macro) - (symbol? arg)) (convert arg opts) - (= 'quote macro) (throw (ex-info (str "Unsupported quoted expression:" - (pr-str clj-expr)) - {:expr clj-expr})) + (and (= 'quote macro) + (symbol? arg)) (convert arg opts) + (= 'quote macro) (throw (ex-info (str "Unsupported quoted expression:" + (pr-str clj-expr)) + {:expr clj-expr})) ;; Originally we called `(express/express arg opts)` but it fails b/c it only handles strings - :else (expr/expr-from-parts - (cons (convert head - (cond-> opts - (symbol? head) - (assoc ::args tail))) - (doall (map #(convert % opts) tail))))))) - -(comment - (convert '(whatever 1) nil) - (convert '(- 12 1 2) {}) - (convert '(Plus 1 2) {})) + :else (expr/expr-from-parts + (cons (convert head + (cond-> opts + (symbol? head) + (assoc ::args tail))) + (doall (map #(convert % opts) tail))))))) +(comment + (convert '(whatever 1) nil) + (convert '(- 12 1 2) {}) + (convert '(Plus 1 2) {})) diff --git a/src/wolframite/base/evaluate.clj b/src/wolframite/base/evaluate.clj index 9d75a4be..e4963658 100644 --- a/src/wolframite/base/evaluate.clj +++ b/src/wolframite/base/evaluate.clj @@ -1,58 +1,54 @@ -(ns wolframite.base.evaluate - "The core of evaluation: send a converted JLink expression to a Wolfram Kernel for evaluation and return the result." - (:require [wolframite.impl.protocols :as proto] - [wolframite.lib.options :as options] - [wolframite.base.convert :as convert])) - -(declare evaluate) - -(defn process-state [pid-expr {:keys [flags] :as opts}] - (assert (options/flag?' flags :serial)) - (let [state-expr (evaluate (convert/convert (list 'ProcessState pid-expr) opts) opts) - state-prefix (first (.toString state-expr))] - (cond (= \r state-prefix) [:running nil] - (= \f state-prefix) [:finished (.part state-expr 1)] - (= \q state-prefix) [:queued nil] - :else - (throw (Exception. (str "Error! State unrecognized: " state-expr)))))) - -(defn queue-run-or-wait [{:keys [flags config] :as opts}] - (assert (options/flag?' flags :serial)) - (let [lqr-atom (atom nil) - lqr-time @lqr-atom - nano-pi (* 1000000 (:poll-interval config)) - run-in (when lqr-time (- (+ lqr-time nano-pi) (System/nanoTime)))] - (if (or (nil? run-in) (neg? run-in)) - (do +(ns wolframite.base.evaluate + "The core of evaluation: send a converted JLink expression to a Wolfram Kernel for evaluation and return the result." + (:require + [wolframite.base.convert :as convert] + [wolframite.impl.protocols :as proto] + [wolframite.lib.options :as options])) +(declare evaluate) +(defn process-state [pid-expr {:keys [flags] :as opts}] + (assert (options/flag?' flags :serial)) + (let [state-expr (evaluate (convert/convert (list 'ProcessState pid-expr) opts) opts) + state-prefix (first (.toString state-expr))] + (cond (= \r state-prefix) [:running nil] + (= \f state-prefix) [:finished (.part state-expr 1)] + (= \q state-prefix) [:queued nil] + :else + (throw (Exception. (str "Error! State unrecognized: " state-expr)))))) +(defn queue-run-or-wait [{:keys [flags config] :as opts}] + (assert (options/flag?' flags :serial)) + (let [lqr-atom (atom nil) + lqr-time @lqr-atom + nano-pi (* 1000000 (:poll-interval config)) + run-in (when lqr-time (- (+ lqr-time nano-pi) (System/nanoTime)))] + (if (or (nil? run-in) (neg? run-in)) + (do ;; TODO: add debug logging "QueueRunning at time" - (evaluate (convert/convert '(QueueRun) opts) opts) - (swap! lqr-atom (fn [_] (System/nanoTime)))) + (evaluate (convert/convert '(QueueRun) opts) opts) + (swap! lqr-atom (fn [_] (System/nanoTime)))) ;; TODO: else branch: add debug logging "Sleeping for" - (Thread/sleep ^long (quot run-in 1000000))))) - -(defn evaluate [expr {:keys [jlink-instance] - :as opts}] - {:pre [jlink-instance]} - (assert (proto/expr? jlink-instance expr)) - - (if (options/flag?' (:flags opts) :serial) - (proto/evaluate! jlink-instance expr) + (Thread/sleep ^long (quot run-in 1000000))))) +(defn evaluate [expr {:keys [jlink-instance] + :as opts}] + {:pre [jlink-instance]} + (assert (proto/expr? jlink-instance expr)) + (if (options/flag?' (:flags opts) :serial) + (proto/evaluate! jlink-instance expr) (let [opts' (update opts :flags conj :serial) ;; FIXME: make sure this is supposed to be `:serial`, it's what I gather from previous version of the code ;; Generate a new, unique symbol for a ref to the submitted computation (~ Java Future?) - pid-expr (evaluate (convert/convert - (list 'Unique + pid-expr (evaluate (convert/convert + (list 'Unique ; Beware: technically, this is an invalid clj symbol due to the slashes: - (symbol "Wolframite/Concurrent/process")) opts') - opts)] + (symbol "Wolframite/Concurrent/process")) opts') + opts)] ;; Submit the expr for evaluation on the next available parallel kernel - (evaluate (convert/convert (list '= pid-expr (list 'ParallelSubmit expr)) opts') opts) - (evaluate (convert/convert '(QueueRun) opts') opts) - (loop [] - (let [[state result] (process-state pid-expr opts)] - (if (not= :finished state) - (do - (queue-run-or-wait opts) - (recur)) - (do - (evaluate (convert/convert (list 'Remove pid-expr) opts') opts) - result))))))) + (evaluate (convert/convert (list '= pid-expr (list 'ParallelSubmit expr)) opts') opts) + (evaluate (convert/convert '(QueueRun) opts') opts) + (loop [] + (let [[state result] (process-state pid-expr opts)] + (if (not= :finished state) + (do + (queue-run-or-wait opts) + (recur)) + (do + (evaluate (convert/convert (list 'Remove pid-expr) opts') opts) + result))))))) diff --git a/src/wolframite/base/expr.clj b/src/wolframite/base/expr.clj index 4e0badf3..72d11fb4 100644 --- a/src/wolframite/base/expr.clj +++ b/src/wolframite/base/expr.clj @@ -1,17 +1,13 @@ -(ns wolframite.base.expr - (:require [wolframite.impl.jlink-instance :as jlink-instance] - [wolframite.impl.protocols :as proto])) - -(defn head-str [expr] - (assert (proto/expr? (jlink-instance/get) expr)) - (.toString (.head expr))) - -(defn parts [expr] - (assert (proto/expr? (jlink-instance/get) expr)) - (cons (.head expr) (seq (.args expr)))) - -(defn expr-from-parts [expr-coll] - (assert (every? #(proto/expr? (jlink-instance/get) %) expr-coll)) - (proto/expr (jlink-instance/get) expr-coll)) - - +(ns wolframite.base.expr + (:require + [wolframite.impl.jlink-instance :as jlink-instance] + [wolframite.impl.protocols :as proto])) +(defn head-str [expr] + (assert (proto/expr? (jlink-instance/get) expr)) + (.toString (.head expr))) +(defn parts [expr] + (assert (proto/expr? (jlink-instance/get) expr)) + (cons (.head expr) (seq (.args expr)))) +(defn expr-from-parts [expr-coll] + (assert (every? #(proto/expr? (jlink-instance/get) %) expr-coll)) + (proto/expr (jlink-instance/get) expr-coll)) diff --git a/src/wolframite/base/express.clj b/src/wolframite/base/express.clj index 39d62180..8fd77e3e 100644 --- a/src/wolframite/base/express.clj +++ b/src/wolframite/base/express.clj @@ -1,24 +1,24 @@ -(ns wolframite.base.express - (:require [wolframite.impl.protocols :as proto])) - -(defn express - "??? Convert a _string_ expression into JLink.Expr using Wolfram itself" - [s {:keys [jlink-instance]}] - {:pre [jlink-instance]} - (assert (string? s) (str "Expected '" (pr-str s) "' to be a string but is " (type s))) +(ns wolframite.base.express + (:require + [wolframite.impl.protocols :as proto])) +(defn express + "??? Convert a _string_ expression into JLink.Expr using Wolfram itself" + [s {:keys [jlink-instance]}] + {:pre [jlink-instance]} + (assert (string? s) (str "Expected '" (pr-str s) "' to be a string but is " (type s))) ;; TODO: debug log: "express string>" - (let [held-s (str "HoldComplete[" s "]") - link (or (proto/kernel-link jlink-instance) - (throw (IllegalStateException. - (str "Something is wrong - there is no JLink instance, which shouldn't" - " be possible. Try to stop and start Wolframite again.")))) - output (io! - (locking link - (doto link - (.evaluate held-s) - (.waitForAnswer)) - (.. link getExpr args)))] ; TODO (jakub) get rid of reflection, move into the protocol - (if (= (count output) 1) - (first output) - (throw (Exception. (format "Invalid expression `%s`. Output lenght expected 1, was %d" - s (count output))))))) \ No newline at end of file + (let [held-s (str "HoldComplete[" s "]") + link (or (proto/kernel-link jlink-instance) + (throw (IllegalStateException. + (str "Something is wrong - there is no JLink instance, which shouldn't" + " be possible. Try to stop and start Wolframite again.")))) + output (io! + (locking link + (doto link + (.evaluate held-s) + (.waitForAnswer)) + (.. link getExpr args)))] ; TODO (jakub) get rid of reflection, move into the protocol + (if (= (count output) 1) + (first output) + (throw (Exception. (format "Invalid expression `%s`. Output lenght expected 1, was %d" + s (count output))))))) diff --git a/src/wolframite/base/package.clj b/src/wolframite/base/package.clj index 3fafe182..982a2a64 100644 --- a/src/wolframite/base/package.clj +++ b/src/wolframite/base/package.clj @@ -2,10 +2,10 @@ "The place to be for loading and manipulating Wolfram packages. " (:require - [babashka.fs :as fs] - [clojure.string :as str] - [wolframite.impl.wolfram-syms.intern :as wi] - [wolframite.wolfram :as w])) + [babashka.fs :as fs] + [clojure.string :as str] + [wolframite.impl.wolfram-syms.intern :as wi] + [wolframite.wolfram :as w])) (defn intern-context! "Finds all the public symbols in the given Wolfram `context` (a string) and interns them to a Clojure namespace named by the given`alias` (a symbol; default: the context's name). The namespace is created @@ -37,7 +37,7 @@ - `alias` - Clojure symbol to be used for accessing the Wolfram context. This will effectively become a Clojure namespace See [[intern-context!]] for details of turning the Wolfram context into a Clojure namespace." -;; TODO: Should check that the symbol isn't already being used. + ;; TODO: Should check that the symbol isn't already being used. ([wl-eval path] (let [context (-> path fs/file-name fs/strip-ext)] (load! wl-eval path context (symbol context)))) @@ -55,4 +55,4 @@ See [[intern-context!]] for details of turning the Wolfram context into a Clojur (comment (load! wolframite.core/eval "/tmp/fake.wl") - (load! wolframite.core/eval "./resources/WolframPackageDemo.wl")) \ No newline at end of file + (load! wolframite.core/eval "./resources/WolframPackageDemo.wl")) diff --git a/src/wolframite/base/parse.clj b/src/wolframite/base/parse.clj index fe444e9b..3c45eeb5 100644 --- a/src/wolframite/base/parse.clj +++ b/src/wolframite/base/parse.clj @@ -1,182 +1,160 @@ -(ns wolframite.base.parse - "Translate a jlink.Expr returned from an evaluation into Clojure data" - (:require - [clojure.set :as set] - [wolframite.impl.jlink-instance :as jlink-instance] - [wolframite.impl.protocols :as proto] - [wolframite.lib.options :as options] - [wolframite.base.expr :as expr] - [clojure.string :as str])) - -(declare parse) - -(defn pascal->kebab - [s] - (some-> s - (str/replace #"\w(\p{Upper})" (fn [[[p l] _]] (str p "-" (str/lower-case l)))) - str/lower-case)) - -(defn entity-type->keyword [expr opts] - (let [head (pascal->kebab (expr/head-str expr)) - parts (reduce (fn [acc part] - (into acc (cond - (string? part) [(pascal->kebab part)] - (coll? part) (reverse (map pascal->kebab part)) - :else nil))) - [head] - (map #(parse % opts) (.args expr)))] +(ns wolframite.base.parse + "Translate a jlink.Expr returned from an evaluation into Clojure data" + (:require + [clojure.set :as set] + [clojure.string :as str] + [wolframite.base.expr :as expr] + [wolframite.impl.jlink-instance :as jlink-instance] + [wolframite.impl.protocols :as proto] + [wolframite.lib.options :as options])) +(declare parse) +(defn pascal->kebab + [s] + (some-> s + (str/replace #"\w(\p{Upper})" (fn [[[p l] _]] (str p "-" (str/lower-case l)))) + str/lower-case)) +(defn entity-type->keyword [expr opts] + (let [head (pascal->kebab (expr/head-str expr)) + parts (reduce (fn [acc part] + (into acc (cond + (string? part) [(pascal->kebab part)] + (coll? part) (reverse (map pascal->kebab part)) + :else nil))) + [head] + (map #(parse % opts) (.args expr)))] ;; BAIL: if any of the parts are not recognized, ;; let it go through normal parsing - (when (not-any? nil? parts) - (keyword (str/join "." (butlast parts)) - (last parts))))) - -(defn custom-parse-dispatch [expr _opts] - (let [head (symbol (expr/head-str expr))] - head)) - -(defn atom? [expr] - (not (.listQ expr))) - -(defn simple-vector-type [expr] - (proto/expr-element-type (jlink-instance/get) :vector expr)) - -(defn simple-matrix-type [expr] - (proto/expr-element-type (jlink-instance/get) :matrix expr)) - -(defn simple-array-type [expr] - (or (simple-vector-type expr) (simple-matrix-type expr))) - + (when (not-any? nil? parts) + (keyword (str/join "." (butlast parts)) + (last parts))))) +(defn custom-parse-dispatch [expr _opts] + (let [head (symbol (expr/head-str expr))] + head)) +(defn atom? [expr] + (not (.listQ expr))) +(defn simple-vector-type [expr] + (proto/expr-element-type (jlink-instance/get) :vector expr)) +(defn simple-matrix-type [expr] + (proto/expr-element-type (jlink-instance/get) :matrix expr)) +(defn simple-array-type [expr] + (or (simple-vector-type expr) (simple-matrix-type expr))) ;; FIXME: change name (it's more of a concrete type map) -(defn bound-map [f coll {:keys [flags] :as opts}] - (if (options/flag?' flags :vectors) - (mapv #(f % opts) coll) - (map #(f % opts) coll))) - -(defn parse-complex-list [expr opts] - (bound-map parse (.args expr) opts)) - -(defn parse-integer [expr] - (let [i (.asLong expr)] - (if (and (<= i Integer/MAX_VALUE) - (>= i Integer/MIN_VALUE)) - (int i) - (long i)))) - -(defn parse-rational [expr] - (let [numer (parse-integer (.part expr 1)) - denom (parse-integer (.part expr 2))] - (/ numer denom))) - -(defn parse-symbol [expr {:keys [aliases/base-list]}] - (let [alias->wolf (options/aliases base-list) - smart-aliases (keep (fn [[alias wolf]] - (when (fn? wolf) alias)) - alias->wolf) - wolf->smart-alias (into {} (for [[alias smart-fn] (select-keys alias->wolf smart-aliases) - wolf-sym (-> smart-fn meta :wolframite.alias/targets) - :when wolf-sym] - [wolf-sym alias])) - wolf->alias (-> (apply dissoc alias->wolf smart-aliases) - set/map-invert - (merge wolf->smart-alias)) - s (.toString expr) - sym (symbol (apply str (replace {\` \/} s)))] - (if-let [alias (get wolf->alias sym)] - alias - (cond (= "True" s) true - (= "False" s) false - (= "Null" s) nil - :else sym)))) - -(defn parse-hash-map [expr opts] - (let [inside (first (.args expr)) +(defn bound-map [f coll {:keys [flags] :as opts}] + (if (options/flag?' flags :vectors) + (mapv #(f % opts) coll) + (map #(f % opts) coll))) +(defn parse-complex-list [expr opts] + (bound-map parse (.args expr) opts)) +(defn parse-integer [expr] + (let [i (.asLong expr)] + (if (and (<= i Integer/MAX_VALUE) + (>= i Integer/MIN_VALUE)) + (int i) + (long i)))) +(defn parse-rational [expr] + (let [numer (parse-integer (.part expr 1)) + denom (parse-integer (.part expr 2))] + (/ numer denom))) +(defn parse-symbol [expr {:keys [aliases/base-list]}] + (let [alias->wolf (options/aliases base-list) + smart-aliases (keep (fn [[alias wolf]] + (when (fn? wolf) alias)) + alias->wolf) + wolf->smart-alias (into {} (for [[alias smart-fn] (select-keys alias->wolf smart-aliases) + wolf-sym (-> smart-fn meta :wolframite.alias/targets) + :when wolf-sym] + [wolf-sym alias])) + wolf->alias (-> (apply dissoc alias->wolf smart-aliases) + set/map-invert + (merge wolf->smart-alias)) + s (.toString expr) + sym (symbol (apply str (replace {\` \/} s)))] + (if-let [alias (get wolf->alias sym)] + alias + (cond (= "True" s) true + (= "False" s) false + (= "Null" s) nil + :else sym)))) +(defn parse-hash-map [expr opts] + (let [inside (first (.args expr)) ;; inside (first (.args expr)) - all-rules? (every? true? (map #(= "Rule" (expr/head-str %)) (.args expr))) - rules (cond (some-> inside (.listQ)) (parse inside opts) - all-rules? (into {} - (map (fn [kv] - (bound-map (fn [x _opts] (parse x opts)) kv opts)) - (.args expr))) - (= "Dispatch" (expr/head-str inside)) (parse (first (.args inside)) opts) - :else (assert (or (.listQ inside) - (= "Dispatch" (expr/head-str inside))))) - keys (map second rules) - vals (map (comp second #(nth % 2)) rules)] - (if (map? rules) - rules - (zipmap keys vals)))) - -(defn parse-simple-atom [expr type opts] - (cond (= type :Expr/BIGINTEGER) (.asBigInteger expr) - (= type :Expr/BIGDECIMAL) (.asBigDecimal expr) - (= type :Expr/INTEGER) (parse-integer expr) - (= type :Expr/REAL) (.asDouble expr) - (= type :Expr/STRING) (.asString expr) - (= type :Expr/RATIONAL) (parse-rational expr) - (= type :Expr/SYMBOL) (parse-symbol expr opts))) - + all-rules? (every? true? (map #(= "Rule" (expr/head-str %)) (.args expr))) + rules (cond (some-> inside (.listQ)) (parse inside opts) + all-rules? (into {} + (map (fn [kv] + (bound-map (fn [x _opts] (parse x opts)) kv opts)) + (.args expr))) + (= "Dispatch" (expr/head-str inside)) (parse (first (.args inside)) opts) + :else (assert (or (.listQ inside) + (= "Dispatch" (expr/head-str inside))))) + keys (map second rules) + vals (map (comp second #(nth % 2)) rules)] + (if (map? rules) + rules + (zipmap keys vals)))) +(defn parse-simple-atom [expr type opts] + (cond (= type :Expr/BIGINTEGER) (.asBigInteger expr) + (= type :Expr/BIGDECIMAL) (.asBigDecimal expr) + (= type :Expr/INTEGER) (parse-integer expr) + (= type :Expr/REAL) (.asDouble expr) + (= type :Expr/STRING) (.asString expr) + (= type :Expr/RATIONAL) (parse-rational expr) + (= type :Expr/SYMBOL) (parse-symbol expr opts))) ;; parameters list used to be: [expr & [type]] (??) -(defn parse-simple-vector [expr type {:keys [flags] :as opts}] - (let [type (or type (simple-vector-type expr))] - (if (and (options/flag?' flags :N) - (some #{:Expr/INTEGER :Expr/BIGINTEGER :Expr/REAL :Expr/BIGDECIMAL} #{type})) - ((if (options/flag?' flags :vectors) vec seq) - (.asArray expr (proto/->expr-type (jlink-instance/get) :Expr/REAL) 1)) - (bound-map (fn [e _opts] (parse-simple-atom e type opts)) (.args expr) opts)))) - -(defn parse-simple-matrix [expr type opts] - (let [type (or type (simple-matrix-type expr))] - (bound-map (fn process-bound-map [a _opts] - (parse-simple-vector a type opts)) - (.args expr) - opts))) - -(defn parse-fn +(defn parse-simple-vector [expr type {:keys [flags] :as opts}] + (let [type (or type (simple-vector-type expr))] + (if (and (options/flag?' flags :N) + (some #{:Expr/INTEGER :Expr/BIGINTEGER :Expr/REAL :Expr/BIGDECIMAL} #{type})) + ((if (options/flag?' flags :vectors) vec seq) + (.asArray expr (proto/->expr-type (jlink-instance/get) :Expr/REAL) 1)) + (bound-map (fn [e _opts] (parse-simple-atom e type opts)) (.args expr) opts)))) +(defn parse-simple-matrix [expr type opts] + (let [type (or type (simple-matrix-type expr))] + (bound-map (fn process-bound-map [a _opts] + (parse-simple-vector a type opts)) + (.args expr) + opts))) +(defn parse-fn "Return a function that invokes the Wolfram expression `expr` (typically just a symbol naming a fn), converting any arguments given to it from Clojure to Wolfram and does the opposite conversion on the result. Ex.: `((parse/parse-fn 'Plus {:jlink-instance (jlink-instance/get)}) 1 2) ; => 3` - Beware: Nesting such fns would result in multiple calls to Wolfram, which is inefficient. Prefer wl/eval in such cases." - [expr opts] - (fn [& args] - (let [cep-fn (requiring-resolve `wolframite.base.cep/cep)] - (cep-fn (apply list expr args) opts #_(update opts :flags #(options/set-flag % :as-expression)))))) - -(defn parse-generic-expression [expr opts] - (-> (list) ;must start with a real list because the promise is that expressions will be converted to lists - (into (map #(parse % opts) (rseq (vec (.args expr))))) - (conj (parse (.head expr) opts)))) - -(defn parse-complex-atom [expr {:keys [flags] :as opts}] - (let [head (expr/head-str expr)] - (cond (.bigIntegerQ expr) (.asBigInteger expr) - (.bigDecimalQ expr) (.asBigDecimal expr) - (.integerQ expr) (parse-integer expr) - (.realQ expr) (.asDouble expr) - (.stringQ expr) (.asString expr) - (.rationalQ expr) (parse-rational expr) - (.symbolQ expr) (parse-symbol expr opts) - (= "Association" head) (parse-hash-map expr opts) #_(parse-generic-expression expr opts) - (= "Function" head) (parse-generic-expression expr opts) - ;(if (and (options/flag?' flags :functions) + Beware: Nesting such fns would result in multiple calls to Wolfram, which is inefficient. Prefer wl/eval in such cases." + [expr opts] + (fn [& args] + (let [cep-fn (requiring-resolve `wolframite.base.cep/cep)] + (cep-fn (apply list expr args) opts #_(update opts :flags #(options/set-flag % :as-expression)))))) +(defn parse-generic-expression [expr opts] + (-> (list) ; must start with a real list because the promise is that expressions will be converted to lists + (into (map #(parse % opts) (rseq (vec (.args expr))))) + (conj (parse (.head expr) opts)))) +(defn parse-complex-atom [expr {:keys [flags] :as opts}] + (let [head (expr/head-str expr)] + (cond (.bigIntegerQ expr) (.asBigInteger expr) + (.bigDecimalQ expr) (.asBigDecimal expr) + (.integerQ expr) (parse-integer expr) + (.realQ expr) (.asDouble expr) + (.stringQ expr) (.asString expr) + (.rationalQ expr) (parse-rational expr) + (.symbolQ expr) (parse-symbol expr opts) + (= "Association" head) (parse-hash-map expr opts) #_(parse-generic-expression expr opts) + (= "Function" head) (parse-generic-expression expr opts) + ; (if (and (options/flag?' flags :functions) ; (not (options/flag?' flags :full-form))) ; (parse-fn expr opts) ; (parse-generic-expression expr opts)) - :else (parse-generic-expression expr opts)))) - -(defn standard-parse [expr {:keys [flags] :as opts}] - (assert (proto/expr? (jlink-instance/get) expr)) - (cond - ;(options/flag?' flags :as-function) (parse-fn expr opts) - (or (atom? expr) (options/flag?' flags :full-form)) (parse-complex-atom expr opts) - (simple-vector-type expr) (parse-simple-vector expr nil opts) - (simple-matrix-type expr) (parse-simple-matrix expr nil opts) - :else (parse-complex-list expr opts))) - -(ns-unmap *ns* 'custom-parse) -(defmulti custom-parse + :else (parse-generic-expression expr opts)))) +(defn standard-parse [expr {:keys [flags] :as opts}] + (assert (proto/expr? (jlink-instance/get) expr)) + (cond + ; (options/flag?' flags :as-function) (parse-fn expr opts) + (or (atom? expr) (options/flag?' flags :full-form)) (parse-complex-atom expr opts) + (simple-vector-type expr) (parse-simple-vector expr nil opts) + (simple-matrix-type expr) (parse-simple-matrix expr nil opts) + :else (parse-complex-list expr opts))) +(ns-unmap *ns* 'custom-parse) +(defmulti custom-parse "Modify how Wolfram response is parsed into Clojure data. The dispatch-val should be a symbol, matched against the first one of the result list. @@ -192,11 +170,9 @@ (wl/eval '(Hyperlink \"foo\" \"https://www.google.com\")) ; => #object[java.net.URI 0x3f5e5a46 \"https://www.google.com\"] - ```" - #'custom-parse-dispatch) - -(defmethod custom-parse :default [expr opts] - (standard-parse expr opts)) - -(defn parse [expr opts] - (custom-parse expr opts)) + ```" + #'custom-parse-dispatch) +(defmethod custom-parse :default [expr opts] + (standard-parse expr opts)) +(defn parse [expr opts] + (custom-parse expr opts)) diff --git a/src/wolframite/core.clj b/src/wolframite/core.clj index 0cc3e777..33ecc92f 100644 --- a/src/wolframite/core.clj +++ b/src/wolframite/core.clj @@ -113,10 +113,10 @@ Requires [[start!]] to be called first." [] (zipmap - [:wolfram-version :wolfram-kernel-name :max-license-processes] - (eval '[$VersionNumber - (SystemInformation "Kernel", "ProductKernelName") - (SystemInformation "Kernel", "MaxLicenseProcesses")]))) + [:wolfram-version :wolfram-kernel-name :max-license-processes] + (eval '[$VersionNumber + (SystemInformation "Kernel", "ProductKernelName") + (SystemInformation "Kernel", "MaxLicenseProcesses")]))) (defn start! "Initialize Wolframite and start! the underlying Wolfram Kernel - required once before you make any eval calls. @@ -247,7 +247,7 @@ - `alias` - Clojure symbol to be used for accessing the Wolfram context. This will effectively become a Clojure namespace See `package/intern-context!` for details of turning the Wolfram context into a Clojure namespace." -;; TODO: Should check that the symbol isn't already being used. + ;; TODO: Should check that the symbol isn't already being used. ([path] (let [context (-> path fs/file-name fs/strip-ext)] (package/load! eval path context (symbol context)))) diff --git a/src/wolframite/impl/jlink_instance.clj b/src/wolframite/impl/jlink_instance.clj index c9503919..5179f1e9 100644 --- a/src/wolframite/impl/jlink_instance.clj +++ b/src/wolframite/impl/jlink_instance.clj @@ -1,7 +1,9 @@ (ns wolframite.impl.jlink-instance - (:require [wolframite.impl.protocols :as proto]) - (:import [wolframite.impl.protocols JLink]) - (:refer-clojure :exclude [get reset!])) + (:refer-clojure :exclude [get reset!]) + (:require + [wolframite.impl.protocols :as proto]) + (:import + (wolframite.impl.protocols JLink))) ;; The actual impl. of the JLink interface, set at runtime (defonce jlink-instance (atom nil)) diff --git a/src/wolframite/impl/jlink_proto_impl.clj b/src/wolframite/impl/jlink_proto_impl.clj index a0457fc9..e0ac8171 100644 --- a/src/wolframite/impl/jlink_proto_impl.clj +++ b/src/wolframite/impl/jlink_proto_impl.clj @@ -1,11 +1,12 @@ (ns wolframite.impl.jlink-proto-impl "The 'real' implementation of JLink, which does depend on JLink classes and thus cannot be loaded/required until JLink is on the classpath." - (:require [clojure.tools.logging :as log] - [wolframite.impl.protocols :as proto]) - (:import (clojure.lang BigInt) - [com.wolfram.jlink Expr KernelLink MathCanvas MathLink MathLinkException MathLinkFactory - PacketListener PacketArrivedEvent PacketPrinter])) + (:require + [clojure.tools.logging :as log] + [wolframite.impl.protocols :as proto]) + (:import + (clojure.lang BigInt) + (com.wolfram.jlink Expr KernelLink MathCanvas MathLink MathLinkException MathLinkFactory PacketArrivedEvent PacketListener PacketPrinter))) (defn- array? [x] (some-> x class .isArray)) @@ -15,8 +16,8 @@ (cond (sequential? primitive-or-exprs) (Expr. - ^Expr (first primitive-or-exprs) - ^"[Lcom.wolfram.jlink.Expr;" (into-array Expr (rest primitive-or-exprs))) + ^Expr (first primitive-or-exprs) + ^"[Lcom.wolfram.jlink.Expr;" (into-array Expr (rest primitive-or-exprs))) ;; Here, primitive-or-exprs could be an int, a String, long[], or similar (array? primitive-or-exprs) @@ -69,7 +70,7 @@ (let [expr (.getExpr link)] (when-not (.symbolQ expr) ;; not sure why these are sent, not useful; e.g. Get when a Get call failed etc. - {:type :message :content expr})) + {:type :message :content expr})) nil) (swap! capture conj))) @@ -77,7 +78,7 @@ (comment (let [link (proto/kernel-link ((requiring-resolve 'wolframite.impl.jlink-instance/get)))] - ;(.removePacketListener link packet-listener) + ; (.removePacketListener link packet-listener) (.addPacketListener link packet-listener) ,) ,) @@ -153,7 +154,7 @@ ;; Note: The call below ensures we actually try to connect to the kernel (.discardAnswer)) (reset! kernel-link-atom))] - ;(.getError kernel-link) (.getErrorMessage kernel-link) + ; (.getError kernel-link) (.getErrorMessage kernel-link) kernel-link) (catch MathLinkException e (if (= (ex-message e) "MathLink connection was lost.") @@ -193,9 +194,9 @@ ^String (apply str (replace {\/ \`} name)))) (->expr [_this obj] ; fallback for transforming anything we don't handle manually, via JLink itself (.getExpr - (doto (MathLinkFactory/createLoopbackLink) - (.put obj) - (.endPacket)))) + (doto (MathLinkFactory/createLoopbackLink) + (.put obj) + (.endPacket)))) (expr? [_this x] (instance? Expr x)) (expr-element-type [_this container-type expr] @@ -242,6 +243,6 @@ (defn create [kernel-link-atom opts] (map->JLinkImpl - {:opts opts - :kernel-link-atom kernel-link-atom - :packet-listener (->InfoPacketCaptureListener (atom nil))})) + {:opts opts + :kernel-link-atom kernel-link-atom + :packet-listener (->InfoPacketCaptureListener (atom nil))})) diff --git a/src/wolframite/impl/protocols.clj b/src/wolframite/impl/protocols.clj index e6bb94f0..ff77730d 100644 --- a/src/wolframite/impl/protocols.clj +++ b/src/wolframite/impl/protocols.clj @@ -1,5 +1,6 @@ (ns wolframite.impl.protocols - (:import (java.awt Component))) + (:import + (java.awt Component))) (defprotocol JLink "A protocol to divorce the code from a direct dependency on JLink, so that it can be loaded @@ -60,4 +61,4 @@ (^Component make-math-canvas! [this kernel-link] (throw (IllegalStateException. "JLink not loaded!"))) (jlink-package-name [this] - (throw (IllegalStateException. "JLink not loaded!")))) + (throw (IllegalStateException. "JLink not loaded!")))) diff --git a/src/wolframite/impl/wolfram_syms/intern.clj b/src/wolframite/impl/wolfram_syms/intern.clj index b7f2bf8d..ab9b8f00 100644 --- a/src/wolframite/impl/wolfram_syms/intern.clj +++ b/src/wolframite/impl/wolfram_syms/intern.clj @@ -1,8 +1,10 @@ (ns wolframite.impl.wolfram-syms.intern "Interning of Wolfram symbols as Clojure vars, for convenience." - (:require [clojure.walk :as walk] - [clojure.walk]) - (:import (clojure.lang IMeta))) + (:require + [clojure.walk :as walk] + [clojure.walk]) + (:import + (clojure.lang IMeta))) (defn interned-var-val->symbol "Turns the value of an [[clj-intern]]-ed Wolfram symbols into said symbol, if possible - otherwise, returns nil. @@ -74,8 +76,8 @@ (clojure.walk/postwalk #(if (and (args-set %) (not (::quoted? (meta %)))) - (list 'quote (with-meta % {::quoted? true})) ; add meta to that we can avoid recursion - %) + (list 'quote (with-meta % {::quoted? true})) ; add meta to that we can avoid recursion + %) body))) (defn- unqualify @@ -87,4 +89,4 @@ (if (qualified-symbol? form) (symbol (name form)) form)) - form)) \ No newline at end of file + form)) diff --git a/src/wolframite/impl/wolfram_syms/wolfram_syms.clj b/src/wolframite/impl/wolfram_syms/wolfram_syms.clj index c1efb62a..973338f9 100644 --- a/src/wolframite/impl/wolfram_syms/wolfram_syms.clj +++ b/src/wolframite/impl/wolfram_syms/wolfram_syms.clj @@ -1,6 +1,7 @@ (ns wolframite.impl.wolfram-syms.wolfram-syms "Support for loading available symbols (fns & more) from Wolfram" - (:require [wolframite.impl.wolfram-syms.intern :as intern])) + (:require + [wolframite.impl.wolfram-syms.intern :as intern])) (defn fetch-all-wolfram-symbols [wl-eval] (doall (->> (wl-eval '(EntityValue (WolframLanguageData) ["Name", "PlaintextUsage"] "EntityPropertyAssociation")) @@ -32,7 +33,7 @@ (doall (->> (fetch-all-wolfram-symbols wl-eval) (map (fn [{:keys [sym doc]}] (intern/clj-intern - sym - {:intern/ns-sym ns-sym - :intern/extra-meta {:doc (when (string? doc) ; could be `(Missing "NotAvailable")` - doc)}})))))) + sym + {:intern/ns-sym ns-sym + :intern/extra-meta {:doc (when (string? doc) ; could be `(Missing "NotAvailable")` + doc)}})))))) diff --git a/src/wolframite/impl/wolfram_syms/write_ns.clj b/src/wolframite/impl/wolfram_syms/write_ns.clj index 582e5e2b..321121de 100644 --- a/src/wolframite/impl/wolfram_syms/write_ns.clj +++ b/src/wolframite/impl/wolfram_syms/write_ns.clj @@ -3,14 +3,15 @@ autocompletion (even in editors using static code analysis) and linters (clj-kondo only does static code)" (:require - [clojure.string :as str] - [clojure.java.io :as io] - [clojure.edn :as edn] - [wolframite.core :as core] - [wolframite.impl.wolfram-syms.intern :as intern] - [wolframite.impl.wolfram-syms.wolfram-syms :as wolfram-syms] - [wolframite.runtime.defaults :as defaults]) - (:import (java.io FileNotFoundException PushbackReader))) + [clojure.edn :as edn] + [clojure.java.io :as io] + [clojure.string :as str] + [wolframite.core :as core] + [wolframite.impl.wolfram-syms.intern :as intern] + [wolframite.impl.wolfram-syms.wolfram-syms :as wolfram-syms] + [wolframite.runtime.defaults :as defaults]) + (:import + (java.io FileNotFoundException PushbackReader))) (comment (-> (io/resource "wolframite/impl/wolfram_syms/write_ns/includes.clj") @@ -31,7 +32,7 @@ (keep #(when-let [kw (and (list? %) (keyword? (first %)) (first %))] - [kw (rest %)])) + [kw (rest %)])) (into {}))] (update incl-ns :refer-clojure #(apply hash-map %)))) @@ -97,24 +98,24 @@ (spit path (str/join "\n" (concat - (map pr-str wolfram-ns-heading) + (map pr-str wolfram-ns-heading) ;; Add version info, similar to clojure's *clojure-version*; marked dynamic so ;; that clj doesn't complain about those *..* - [(format "(def ^:dynamic *wolfram-version* %s)" wolfram-version)] - [(format "(def ^:dynamic *wolfram-kernel-name* \"%s\")" wolfram-kernel-name)] - (map pr-str (make-defs all-syms)) - (map pr-str (make-wolfram-ns-footer all-syms)) - [(inclusions-body-str!)] ; the w/fn macro etc. - (some-> aliases (aliases->defs all-syms) (->> (map pr-str)))))) + [(format "(def ^:dynamic *wolfram-version* %s)" wolfram-version)] + [(format "(def ^:dynamic *wolfram-kernel-name* \"%s\")" wolfram-kernel-name)] + (map pr-str (make-defs all-syms)) + (map pr-str (make-wolfram-ns-footer all-syms)) + [(inclusions-body-str!)] ; the w/fn macro etc. + (some-> aliases (aliases->defs all-syms) (->> (map pr-str)))))) (catch FileNotFoundException e (throw (ex-info (format "Could not write to %s - does the parent dir exist?" path) {:path path, :cause (ex-message e)}))))))) -;(defmacro make-wolf-defs [] +; (defmacro make-wolf-defs [] ; `(do ~@(make-defs))) ; USAGE: Use -;(macroexpand '(make-wolf-defs)) +; (macroexpand '(make-wolf-defs)) (comment @@ -125,5 +126,5 @@ (load-file "src/wolframite/wolfram.clj")) (write-ns! - "src/wolframite/wolfram.clj" - {:aliases '{I Integrate}})) + "src/wolframite/wolfram.clj" + {:aliases '{I Integrate}})) diff --git a/src/wolframite/impl/wolfram_syms/write_ns/includes.clj b/src/wolframite/impl/wolfram_syms/write_ns/includes.clj index fcc1f872..48e0f2f8 100644 --- a/src/wolframite/impl/wolfram_syms/write_ns/includes.clj +++ b/src/wolframite/impl/wolfram_syms/write_ns/includes.clj @@ -1,18 +1,18 @@ (ns wolframite.impl.wolfram-syms.write-ns.includes "The content is to be included in the generated `wolframite.wolfram` ns." + (:refer-clojure :only [defmacro let list]) (:require - clojure.walk - wolframite.impl.wolfram-syms.intern) - (:refer-clojure :only [defmacro let list])) + [clojure.walk] + [wolframite.impl.wolfram-syms.intern])) ;; Cursive: resolve as defn; Kondo: TBD ;; TODO: Do we want to enable specs?! -;(s/fdef fn +; (s/fdef fn ; :args (s/cat :params :clojure.core.specs.alpha/params+body ; :body any?) ; :ret any?) -;;--INCLUDE-START-- +;; --INCLUDE-START-- (defmacro fn "Creates a Wolfram anonymous function with the given arguments and single expression body. Example usage: `(wl/eval (w/Map (w/fn [x] (w/Plus x 1)) [1 2 3]))`" diff --git a/src/wolframite/lib/helpers.clj b/src/wolframite/lib/helpers.clj index 138bfe6d..32b02175 100644 --- a/src/wolframite/lib/helpers.clj +++ b/src/wolframite/lib/helpers.clj @@ -1,6 +1,7 @@ (ns wolframite.lib.helpers - (:require [clojure.java.browse :refer [browse-url]] - [wolframite.impl.wolfram-syms.intern :as intern])) + (:require + [clojure.java.browse :refer [browse-url]] + [wolframite.impl.wolfram-syms.intern :as intern])) (defn help-link [fn-sym] (format "https://reference.wolfram.com/language/ref/%s.html" fn-sym)) diff --git a/src/wolframite/lib/options.clj b/src/wolframite/lib/options.clj index 5bd815db..cb7b3d93 100644 --- a/src/wolframite/lib/options.clj +++ b/src/wolframite/lib/options.clj @@ -1,46 +1,36 @@ -(ns wolframite.lib.options - (:require [wolframite.runtime.defaults :as defaults])) - -(defn filter-params [current-options] - (into {} (filter #(keyword? (key %)) current-options))) - -(defn filter-flags [current-options] - (into {} (filter #(set? (first %)) current-options))) - -(defn filter-flag-sets - [flag current-options] - (filter #(some #{flag} %) (keys (filter-flags current-options)))) - -(defn flags-into +(ns wolframite.lib.options + (:require + [wolframite.runtime.defaults :as defaults])) +(defn filter-params [current-options] + (into {} (filter #(keyword? (key %)) current-options))) +(defn filter-flags [current-options] + (into {} (filter #(set? (first %)) current-options))) +(defn filter-flag-sets + [flag current-options] + (filter #(some #{flag} %) (keys (filter-flags current-options)))) +(defn flags-into "Conjoins each flag in flags into hash-map *flags*, setting each such flag as the value - of any keys that contain flag as an element." - [current-options flags] - (into current-options - (for [flag flags - active-set (filter-flag-sets flag current-options)] - [active-set flag]))) - -(defn params-into + of any keys that contain flag as an element." + [current-options flags] + (into current-options + (for [flag flags + active-set (filter-flag-sets flag current-options)] + [active-set flag]))) +(defn params-into "Conjoins each key-value pair in options into hash-map *options*, conjoining each such pair - only if options-keys contains that key." - [current-options params] - (into current-options - (filter #(some #{(first %)} (keys (filter-params current-options))) params))) - -(defn options-into [current-options params flags] - (flags-into (params-into current-options params) flags)) - -(defn flag? [current-options flag] - (not (nil? (some #{flag} (vals (filter-flags current-options)))))) - -(defn flag?' [user-flags flag] - (flag? (flags-into defaults/default-options user-flags) flag)) - -;; - -(defn set-flag [flags f] - (flags-into flags [f])) - -(defn aliases [base-list] - (or (defaults/default-options base-list) - (defaults/default-options :clojure-aliases))) + only if options-keys contains that key." + [current-options params] + (into current-options + (filter #(some #{(first %)} (keys (filter-params current-options))) params))) +(defn options-into [current-options params flags] + (flags-into (params-into current-options params) flags)) +(defn flag? [current-options flag] + (not (nil? (some #{flag} (vals (filter-flags current-options)))))) +(defn flag?' [user-flags flag] + (flag? (flags-into defaults/default-options user-flags) flag)) +;; +(defn set-flag [flags f] + (flags-into flags [f])) +(defn aliases [base-list] + (or (defaults/default-options base-list) + (defaults/default-options :clojure-aliases))) diff --git a/src/wolframite/runtime/defaults.clj b/src/wolframite/runtime/defaults.clj index d4f5fa0d..2166adbf 100644 --- a/src/wolframite/runtime/defaults.clj +++ b/src/wolframite/runtime/defaults.clj @@ -1,7 +1,8 @@ (ns wolframite.runtime.defaults "Flags and aliases for the Wolfram runtime." - (:require clojure.set - [clojure.walk :as walk])) + (:require + [clojure.set] + [clojure.walk :as walk])) "TODO: - Consider function that finds all non-numeric symbols (and not '- ') that start with a '-' and replace them with Minus[] @@ -13,14 +14,14 @@ #{:parse :no-parse} :parse #{:evaluate :no-evaluate} :evaluate #{:convert :no-convert} :convert - ;#{:hash-maps :no-hash-maps} :hash-maps - ;#{:functions :no-functions} :functions ;; ?? parse (Function ...) into our parse-fn instance?! + ; #{:hash-maps :no-hash-maps} :hash-maps + ; #{:functions :no-functions} :functions ;; ?? parse (Function ...) into our parse-fn instance?! #{:aliases :no-aliases} :aliases #{:N :no-N} :no-N ; :N -> use Expr.asArray on matrix' rows - ;#{:verbose :no-verbose} :no-verbose - ;#{:as-function + ; #{:verbose :no-verbose} :no-verbose + ; #{:as-function ; :as-expression} :as-expression - ;#{:restore-defaults + ; #{:restore-defaults ; :no-restore-defaults} :no-restore-defaults #{:full-form :clojure-form} :clojure-form}) @@ -104,7 +105,7 @@ :parse true :functions true :aliases true - ;:as-expression true + ; :as-expression true :clojure-form true ;; FIXME: better name :N false}) @@ -140,9 +141,9 @@ (tree-seq list? seq) (remove (some-fn list? nil?)) (filter #(when (symbol? %) - (->> % - str - (re-matches #"-.+")))) + (->> % + str + (re-matches #"-.+")))) distinct) syms-base (map (fn [sym] (-> sym str char-array rest (#(apply str %)) symbol)) syms)] diff --git a/src/wolframite/runtime/jlink.clj b/src/wolframite/runtime/jlink.clj index b04545af..644eed04 100644 --- a/src/wolframite/runtime/jlink.clj +++ b/src/wolframite/runtime/jlink.clj @@ -13,10 +13,10 @@ os - keyword " (:require - [babashka.fs :as fs] - [clojure.tools.logging :as log] - ;clojure.repl.deps ; required dynamically, for a better error on old clj - [wolframite.runtime.system :as system])) + [babashka.fs :as fs] + [clojure.tools.logging :as log] + ;clojure.repl.deps ; required dynamically, for a better error on old clj + [wolframite.runtime.system :as system])) (def ^:private default-jlink-path-under-root "SystemFiles/Links/JLink/JLink.jar") diff --git a/src/wolframite/runtime/system.clj b/src/wolframite/runtime/system.clj index f60e1aff..c8ad38f4 100644 --- a/src/wolframite/runtime/system.clj +++ b/src/wolframite/runtime/system.clj @@ -1,10 +1,10 @@ (ns wolframite.runtime.system "For dealing with the runtime system, primarily the operating system specific default paths." (:require - [babashka.fs :as fs] - [clojure.string :as str]) + [babashka.fs :as fs] + [clojure.string :as str]) (:import - (java.lang System))) + (java.lang System))) (def supported-OS #{:linux :mac :windows}) @@ -69,7 +69,7 @@ distinct (some #(let [paths (-> (fs/glob "/" (format "**/%s" %)))] - (when (seq paths) paths))) + (when (seq paths) paths))) first str)) @@ -135,8 +135,8 @@ (#{"osx" "macos" "Mac OS X" "mac"} os-str) :mac (or (#{"win" "windows"} os-str) (str/starts-with? os-str "Windows")) :windows :else (throw - (ex-info (str "Did not recognise " os-str " as a supported OS.") - {:os os-str}))))) + (ex-info (str "Did not recognise " os-str " as a supported OS.") + {:os os-str}))))) (defn detect-os "Tries to determine the current operating system. @@ -191,7 +191,7 @@ The longest parental directory path that is common to both input paths. " [path1 path2] - ;; TODO: This should be moved to some utility library. + ;; TODO: This should be moved to some utility library. (let [sep "/" rx-sep (re-pattern sep)] (->> [path1 path2] diff --git a/src/wolframite/specs.clj b/src/wolframite/specs.clj index 012901fc..26a323a5 100644 --- a/src/wolframite/specs.clj +++ b/src/wolframite/specs.clj @@ -1,8 +1,9 @@ (ns wolframite.specs - (:require [wolframite.runtime.defaults :as defaults] - [clojure.spec.alpha :as s] - [clojure.spec.test.alpha :as st] - clojure.set)) + (:require + [clojure.set] + [clojure.spec.alpha :as s] + [clojure.spec.test.alpha :as st] + [wolframite.runtime.defaults :as defaults])) (s/def :wl/flag defaults/all-flags) (s/def :wl/flags (s/coll-of :wl/flag)) @@ -13,8 +14,8 @@ (s/def :wl/opts-map (s/keys :opt [:wl/flags :wl/aliases])) (s/def :wl/args (s/alt - :no-options (s/cat :body any?) - :with-options (s/cat :opts-kw? :wl/opts-kw :opts-map (s/nilable :wl/opts-map) :body any?))) + :no-options (s/cat :body any?) + :with-options (s/cat :opts-kw? :wl/opts-kw :opts-map (s/nilable :wl/opts-map) :body any?))) (comment ;; spec examples @@ -33,12 +34,8 @@ (s/conform :wl/args [:opts {:flags [:parse/as-function :debug/verbose :convert/hash-maps]} '(Dot [1 3 4] [5 4 6])]) - (let [args #_['(Dot [1 3 4] [5 4 6])] [:opts {:flags [:parse/as-function :debug/verbose :convert/hash-maps]} '(Dot [1 3 4] [5 4 6])] [options-flag {:keys [opts-map body]}] (s/conform :wl/args args)] (if (= :with-options options-flag) [opts-map body] - [body])) - - - ) + [body]))) diff --git a/src/wolframite/tools/clerk_helper.clj b/src/wolframite/tools/clerk_helper.clj index efc92364..456c7435 100644 --- a/src/wolframite/tools/clerk_helper.clj +++ b/src/wolframite/tools/clerk_helper.clj @@ -1,11 +1,12 @@ (ns wolframite.tools.clerk-helper - (:require [wolframite.core :as wl] - [nextjournal.clerk :as clerk] - [nextjournal.clerk.webserver :as webserver] - [nextjournal.beholder :as beholder] - ;; [clj-http.client :as client] - [clojure.java.io :as io] - [wolframite.tools.hiccup :as h])) + (:require + ;; [clj-http.client :as client] + [clojure.java.io :as io] + [nextjournal.beholder :as beholder] + [nextjournal.clerk :as clerk] + [nextjournal.clerk.webserver :as webserver] + [wolframite.core :as wl] + [wolframite.tools.hiccup :as h])) (defn view [form & {:keys [folded?]}] (clerk/html (h/view* form folded?))) diff --git a/src/wolframite/tools/experimental.clj b/src/wolframite/tools/experimental.clj index 2e3eb711..5f77658a 100644 --- a/src/wolframite/tools/experimental.clj +++ b/src/wolframite/tools/experimental.clj @@ -1,11 +1,13 @@ (ns wolframite.tools.experimental "Don't use. Subject to change without prior notice." - (:require [wolframite.core :as wl] - [wolframite.impl.jlink-instance :as jlink-instance] - [wolframite.impl.protocols :as proto]) - (:import [javax.swing JFrame JPanel SwingUtilities] - [java.awt Dimension] - [com.wolfram.jlink MathGraphicsJPanel])) + (:require + [wolframite.core :as wl] + [wolframite.impl.jlink-instance :as jlink-instance] + [wolframite.impl.protocols :as proto]) + (:import + (com.wolfram.jlink MathGraphicsJPanel) + (java.awt Dimension) + (javax.swing JFrame JPanel SwingUtilities))) (defonce ^:private app (promise)) diff --git a/src/wolframite/tools/graphics.clj b/src/wolframite/tools/graphics.clj index 75df755d..5d4ea3b6 100644 --- a/src/wolframite/tools/graphics.clj +++ b/src/wolframite/tools/graphics.clj @@ -3,14 +3,16 @@ ;; Wolfram has You use MathCanvas when you want an AWT component and MathGraphicsJPanel when you ;; want a Swing component - see https://reference.wolfram.com/language/JLink/tutorial/CallingJavaFromTheWolframLanguage.html#20608 ;; Notice that KernelLink also has evaluateToImage() and evaluateToTypeset() methods - (:require [wolframite.impl.jlink-instance :as jlink-instance] - [wolframite.impl.protocols :as proto] - [wolframite.core :as wl]) - (:import (java.awt Color Component Frame) - (java.awt.image BufferedImage) - (javax.imageio ImageIO) - (java.io ByteArrayInputStream) - (java.awt.event WindowAdapter ActionEvent))) + (:require + [wolframite.core :as wl] + [wolframite.impl.jlink-instance :as jlink-instance] + [wolframite.impl.protocols :as proto]) + (:import + (java.awt Color Component Frame) + (java.awt.event ActionEvent WindowAdapter) + (java.awt.image BufferedImage) + (java.io ByteArrayInputStream) + (javax.imageio ImageIO))) (defn scaled [x factor] @@ -79,4 +81,3 @@ (ImageIO/read (ByteArrayInputStream. (.evaluateToImage (proto/kernel-link (jlink-instance/get)) "GeoGraphics[]" (int width) (int height) 600 true)))))) ;; doesn't make much difference (maybe a bit), seems like we can go lower dpi, but we already get maximum by default (?) - diff --git a/src/wolframite/tools/hiccup.clj b/src/wolframite/tools/hiccup.clj index e38f2b3b..a5a317e2 100644 --- a/src/wolframite/tools/hiccup.clj +++ b/src/wolframite/tools/hiccup.clj @@ -1,9 +1,11 @@ (ns wolframite.tools.hiccup - (:require [wolframite.core :as wl] - [scicloj.kindly.v4.kind :as kind] - [wolframite.impl.jlink-instance :as jlink-instance] - [wolframite.impl.protocols :as proto]) - (:import (java.util Base64))) + (:require + [scicloj.kindly.v4.kind :as kind] + [wolframite.core :as wl] + [wolframite.impl.jlink-instance :as jlink-instance] + [wolframite.impl.protocols :as proto]) + (:import + (java.util Base64))) (defn bytes->b64encodedString [^bytes bs] @@ -11,23 +13,23 @@ (defn img [b64img] (kind/hiccup - [:img {:src (format "data:image/jpeg;base64,%s" b64img) - :style {:margin-top "1rem"}}])) + [:img {:src (format "data:image/jpeg;base64,%s" b64img) + :style {:margin-top "1rem"}}])) (defn view* [form folded?] (let [wl-str (wl/->wl form {:output-fn str}) input-img (.evaluateToImage (proto/kernel-link (jlink-instance/get)) wl-str 0 0 0 true) b64img (bytes->b64encodedString input-img)] (kind/hiccup - [:div - [:details {:open (not folded?)} - [:summary [:h5 {:style {:display "inline" - :cursor "pointer" - :padding "0.5rem 1rem 0 1rem"}} - wl-str]] - [:div.wl-results - [:hr] - (img b64img)]]]))) + [:div + [:details {:open (not folded?)} + [:summary [:h5 {:style {:display "inline" + :cursor "pointer" + :padding "0.5rem 1rem 0 1rem"}} + wl-str]] + [:div.wl-results + [:hr] + (img b64img)]]]))) (defn view "View a given Wolframite `form` as Hiccup, Kindly compatible. diff --git a/src/wolframite/tools/portal.clj b/src/wolframite/tools/portal.clj index 07d24a65..a77aa17e 100644 --- a/src/wolframite/tools/portal.clj +++ b/src/wolframite/tools/portal.clj @@ -1,7 +1,8 @@ (ns wolframite.tools.portal "Integration with https://djblue.github.io/portal/" - (:require [portal.api :as p] - [wolframite.tools.hiccup :as h])) + (:require + [portal.api :as p] + [wolframite.tools.hiccup :as h])) (defn view [form & {:keys [folded?]}] (p/submit (with-meta (h/view* form folded?) diff --git a/src/wolframite/wolfram.clj b/src/wolframite/wolfram.clj index 63a5ec02..5551b8cf 100644 --- a/src/wolframite/wolfram.clj +++ b/src/wolframite/wolfram.clj @@ -1,4 +1,9 @@ -(ns wolframite.wolfram "[GENERATED - see `...wolfram-syms.write-ns/write-ns!`]\n Vars for all Wolfram functions (and their Clojurite aliases, where those exist).\n These can be composed into expressions and passed to `wl/eval`.\n\n BEWARE: This is based off a particular version of Wolfram and you may need to refresh it." (:require wolframite.impl.wolfram-syms.intern clojure.walk) (:refer-clojure :only [ns-unmap map let defmacro list])) +(ns wolframite.wolfram + "[GENERATED - see `...wolfram-syms.write-ns/write-ns!`]\n Vars for all Wolfram functions (and their Clojurite aliases, where those exist).\n These can be composed into expressions and passed to `wl/eval`.\n\n BEWARE: This is based off a particular version of Wolfram and you may need to refresh it." + (:refer-clojure :only [defmacro let list map ns-unmap]) + (:require + [clojure.walk] + [wolframite.impl.wolfram-syms.intern])) (do (clojure.core/ns-unmap clojure.core/*ns* (quote Byte)) (clojure.core/ns-unmap clojure.core/*ns* (quote Character)) (clojure.core/ns-unmap clojure.core/*ns* (quote Integer)) (clojure.core/ns-unmap clojure.core/*ns* (quote Number)) (clojure.core/ns-unmap clojure.core/*ns* (quote Short)) (clojure.core/ns-unmap clojure.core/*ns* (quote String)) (clojure.core/ns-unmap clojure.core/*ns* (quote Thread))) (def ^:dynamic *wolfram-version* 14.0) (def ^:dynamic *wolfram-kernel-name* "Mathematica 14.0.0 Kernel") @@ -6456,4 +6461,4 @@ (clojure.core/assert (clojure.core/vector? args)) ;; If there is any w/ symbol such as w/Plus, replace it just with the fn name, `Plus` - ;; we need the whole body to be pure symbols and primitives only, which Wolfram will understand - `(list (quote ~'Function) (quote ~args) ~(wolframite.impl.wolfram-syms.intern/quote-args body-sexp args)))) \ No newline at end of file + `(list (quote ~'Function) (quote ~args) ~(wolframite.impl.wolfram-syms.intern/quote-args body-sexp args)))) diff --git a/src/wolframite/wolfram_extended.clj b/src/wolframite/wolfram_extended.clj index 69a85bd2..7540f174 100644 --- a/src/wolframite/wolfram_extended.clj +++ b/src/wolframite/wolfram_extended.clj @@ -1,4 +1,9 @@ -(ns wolframite.wolfram-extended "[GENERATED - see `...wolfram-syms.write-ns/write-ns!`]\n Vars for all Wolfram functions (and their Clojurite aliases, where those exist).\n These can be composed into expressions and passed to `wl/eval`.\n\n BEWARE: This is based off a particular version of Wolfram and you may need to refresh it." (:require wolframite.impl.wolfram-syms.intern clojure.walk) (:refer-clojure :only [ns-unmap map let defmacro list])) +(ns wolframite.wolfram-extended + "[GENERATED - see `...wolfram-syms.write-ns/write-ns!`]\n Vars for all Wolfram functions (and their Clojurite aliases, where those exist).\n These can be composed into expressions and passed to `wl/eval`.\n\n BEWARE: This is based off a particular version of Wolfram and you may need to refresh it." + (:refer-clojure :only [defmacro let list map ns-unmap]) + (:require + [clojure.walk] + [wolframite.impl.wolfram-syms.intern])) (do (clojure.core/ns-unmap clojure.core/*ns* (quote Byte)) (clojure.core/ns-unmap clojure.core/*ns* (quote Character)) (clojure.core/ns-unmap clojure.core/*ns* (quote Integer)) (clojure.core/ns-unmap clojure.core/*ns* (quote Number)) (clojure.core/ns-unmap clojure.core/*ns* (quote Short)) (clojure.core/ns-unmap clojure.core/*ns* (quote String)) (clojure.core/ns-unmap clojure.core/*ns* (quote Thread))) (def ^:dynamic *wolfram-version* 13.0) (def ^:dynamic *wolfram-kernel-name* "Mathematica 13.0.1 Kernel")