diff --git a/.gitignore b/.gitignore index cd361929..21acce4b 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,6 @@ clojure.data.json* *.iml obj/ + +/py/ +**__pycache__** \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 24bc2c5d..dd7120e7 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -60,6 +60,26 @@ ":shadow" ] } - } + }, + { + "name": "basilisp", + "projectType": "deps.edn", + "projectRootPath": [ + "." + ], + "customJackInCommandLine": "./py/bin/basilisp nrepl-server", + "jackInEnv": { + "PYTHONPATH": "dev:src:test" + }, + "nReplPortFile": [ + ".nrepl-port", + ], + "menuSelections": { + "cljAliases": [ + ":dev", + ":cider" + ] + } + }, ] } \ No newline at end of file diff --git a/src/examples/data.cljc b/src/examples/data.cljc index 7568aec5..d1c697ab 100644 --- a/src/examples/data.cljc +++ b/src/examples/data.cljc @@ -2,7 +2,8 @@ (:require #?(:clj [clojure.java.io :as io]) #?(:org.babashka/nbb [clojure.core] :default [examples.hacker-news :as hn]) - [clojure.pprint :as pp] + #?(:lpy [portal.runtime] + :default [clojure.pprint :as pp]) [examples.macros :refer [read-file]] [portal.colors :as c] [portal.viewer :as v]) @@ -13,7 +14,9 @@ :org.babashka/nbb (:import) :cljs (:import [goog.math Long]) :cljr (:import [System DateTime Guid Uri] - [System.IO File]))) + [System.IO File]) + :lpy (:import [math :as Math] + [datetime :as datetime]))) #?(:clj (defn slurp-bytes [x] @@ -56,8 +59,8 @@ ::uuid (random-uuid) ::date (js/Date.) ::bigint (js/BigInt "42") - ::js-array #js [0 1 2 3 4] - ::js-object #js {:hello "world"}} + ::js-array (clj->js [0 1 2 3 4]) + ::js-object (clj->js {:hello "world"})} :cljs {::long (.fromString Long "4611681620380904123") ::promise (js/Promise.resolve 123) @@ -65,8 +68,15 @@ ::uuid (random-uuid) ::date (js/Date.) ::bigint (js/BigInt "42") - ::js-array #js [0 1 2 3 4] - ::js-object #js {:hello "world"}})) + ::js-array (clj->js [0 1 2 3 4]) + ::js-object (clj->js {:hello "world"})} + :lpy + {::fractions 22/7 + ::promise (promise) + ::uuid (random-uuid) + ::date (datetime.datetime/now) + ::py-list (python/list [0 1 2 3 4]) + ::py-dict (python/dict {"hello" "world"})})) (def platform-collections #?(:bb nil @@ -107,11 +117,15 @@ (def clojure-data {::regex #"hello-world" - ::sorted-map (sorted-map-by gt 3 "c" 2 "b" 1 "a") - ::sorted-set (sorted-set-by gt 3 2 1) + ::sorted-map #?(:lpy nil + :default (sorted-map-by gt 3 "c" 2 "b" 1 "a")) + ::sorted-set #?(:lpy nil + :default + (sorted-set-by gt 3 2 1)) ::var #'portal.colors/themes ::with-meta (with-meta 'with-meta {:hello :world}) - ::tagged (tagged-literal 'my/tag ["hello, world"]) + ::tagged #?(:lpy nil + :default (tagged-literal 'my/tag ["hello, world"])) {:example/settings 'complex-key} :hello-world ::atom (atom ::hello) ::function println @@ -244,9 +258,11 @@ ::different-value ::new-key}])) (def diff-text-data - (v/diff-text - [(with-out-str (pp/pprint (first diff-data))) - (with-out-str (pp/pprint (second diff-data)))])) + #?(:lpy nil + :default + (v/diff-text + [(with-out-str (pp/pprint (first diff-data))) + (with-out-str (pp/pprint (second diff-data)))]))) (def string-data (v/for diff --git a/src/examples/hacker_news.cljc b/src/examples/hacker_news.cljc index b8afbb0d..8e83cde3 100644 --- a/src/examples/hacker_news.cljc +++ b/src/examples/hacker_news.cljc @@ -1,11 +1,12 @@ (ns examples.hacker-news (:require #?(:clj [portal.sync :as a] :cljs [portal.async :as a] - :cljr [portal.sync :as a]) + :cljr [portal.sync :as a] + :lpy [portal.sync :as a]) #?(:cljs [examples.fetch :refer [fetch]]) - [clojure.core.protocols :refer [nav]] #?(:clj [portal.runtime.json :as json] - :cljr [portal.runtime.json :as json]))) + :cljr [portal.runtime.json :as json] + :lpy [portal.runtime.json :as json]))) (def root "https://hacker-news.firebaseio.com/v0") @@ -38,19 +39,22 @@ #?(:clj (-> url slurp json/read) :cljs (-> (fetch url) (.then #(js->clj (.parse js/JSON %) :keywordize-keys true))) - :cljr (-> url (slurp :enc "utf8") json/read))) + :cljr (-> url (slurp :enc "utf8") json/read) + :lpy (-> url slurp json/read))) (defn as-url [s] #?(:clj (java.net.URL. s) :cljs (js/URL. s) - :cljr (System.Uri. s))) + :cljr (System.Uri. s) + :lpy s)) (defn as-date [^long timestamp] #?(:clj (java.util.Date. timestamp) :cljs (js/Date. timestamp) :cljr (.DateTime (System.DateTimeOffset/FromUnixTimeMilliseconds - timestamp)))) + timestamp)) + :lpy timestamp)) (declare nav-hn) (declare nav-item) @@ -82,32 +86,32 @@ (contains? item :kids) (update :kids vary-meta assoc - `nav #'nav-item + 'clojure.core.protocols/nav #'nav-item :portal.viewer/default :portal.viewer/inspector) (contains? item :submitted) (update :submitted vary-meta assoc - `nav #'nav-item + 'clojure.core.protocols/nav #'nav-item :portal.viewer/default :portal.viewer/inspector))))) (defn fetch-user [user] (a/let [res (fetch-hn (str "/user/" user ".json"))] - (vary-meta res assoc `nav #'nav-hn))) + (vary-meta res assoc 'clojure.core.protocols/nav #'nav-hn))) (defn nav-item [_coll _k v] (a/let [res (fetch-hn (str "/item/" v ".json"))] - (vary-meta res assoc `nav #'nav-hn))) + (vary-meta res assoc 'clojure.core.protocols/nav #'nav-hn))) (def stories (with-meta #{:topstories :newstories :beststories :askstories :showstories :jobstories} - {`nav #'nav-hn + {'clojure.core.protocols/nav #'nav-hn :portal.viewer/default :portal.viewer/inspector})) (defn fetch-stories [type] (a/let [res (fetch-hn (str "/" (name type) ".json"))] - (with-meta (take 15 res) (merge (meta res) {`nav #'nav-item})))) + (with-meta (take 15 res) (merge (meta res) {'clojure.core.protocols/nav #'nav-item})))) (defn nav-hn [coll k v] (cond diff --git a/src/examples/macros.cljc b/src/examples/macros.cljc index 88909db7..34e3510a 100644 --- a/src/examples/macros.cljc +++ b/src/examples/macros.cljc @@ -3,4 +3,5 @@ #?(:clj (defmacro read-file [file-name] (slurp file-name)) :cljs (defn read-file [_file-name] ::missing) - :cljr (defmacro read-file [file-name] (slurp file-name :enc "utf8"))) + :cljr (defmacro read-file [file-name] (slurp file-name :enc "utf8")) + :lpy (defn read-file [file-name] (slurp file-name))) diff --git a/src/portal/api.cljc b/src/portal/api.cljc index 0a6b1dce..405a6625 100644 --- a/src/portal/api.cljc +++ b/src/portal/api.cljc @@ -3,10 +3,12 @@ [portal.runtime.jvm.commands]) #?(:clj [portal.runtime.jvm.launcher :as l] :cljs [portal.runtime.node.launcher :as l] - :cljr [portal.runtime.clr.launcher :as l]) + :cljr [portal.runtime.clr.launcher :as l] + :lpy [portal.runtime.py.launcher :as l]) #?(:clj [portal.sync :as a] :cljs [portal.async :as a] - :cljr [portal.sync :as a]) + :cljr [portal.sync :as a] + :lpy [portal.sync :as a]) #?(:clj [clojure.java.io :as io] :cljs [portal.resources :as io]) [clojure.set :as set] diff --git a/src/portal/client/py.lpy b/src/portal/client/py.lpy index 76d988e8..54774700 100644 --- a/src/portal/client/py.lpy +++ b/src/portal/client/py.lpy @@ -1,12 +1,14 @@ (ns portal.client.py - (:require [basilisp.json :as json]) + (:require [basilisp.json :as json] + [portal.runtime.cson :as cson]) (:import [urllib.request :as request])) (defn- serialize [encoding value] (.encode (try (case encoding - :json (json/write-str value) + :json (json/write-str value) + :cson (cson/write value) :edn (binding [*print-meta* true] (pr-str value))) (catch Exception ex diff --git a/src/portal/runtime.cljc b/src/portal/runtime.cljc index a1e76d85..043edf61 100644 --- a/src/portal/runtime.cljc +++ b/src/portal/runtime.cljc @@ -2,9 +2,11 @@ (:refer-clojure :exclude [read]) (:require #?(:clj [portal.sync :as a] :cljr [portal.sync :as a] - :cljs [portal.async :as a]) + :cljs [portal.async :as a] + :lpy [portal.sync :as a]) #?(:joyride [portal.runtime.datafy :refer [datafy nav]] :org.babashka/nbb [portal.runtime.datafy :refer [datafy nav]] + :lpy [portal.runtime.datafy :refer [datafy nav]] :default [clojure.datafy :refer [datafy nav]]) #?(:joyride [cljs.pprint :as pprint] :default [clojure.pprint :as pprint]) @@ -15,6 +17,7 @@ #?(:joyride nil :org.babashka/nbb nil + :lpy nil :default (defmethod pprint/simple-dispatch tagged-type [value] (if (not= (:tag value) "remote") @@ -74,7 +77,7 @@ (defn- hashable? [value] (try (and (hash value) true) - (catch #?(:clj Exception :cljr Exception :cljs :default) _ + (catch #?(:clj Exception :cljr Exception :cljs :default :lpy Exception) _ false))) #?(:bb (def clojure.lang.Range (type (range 1.0)))) @@ -96,7 +99,13 @@ (try (with-meta value {}) true (catch :default _e false))) - :cljs (implements? IMeta value))) + :cljs (implements? IMeta value) + + :lpy + (try (with-meta value {}) true + (catch Exception _e false)))) + +#?(:lpy (defn- sorted? [_] false)) (defn- hash+ [x] (cond @@ -138,7 +147,8 @@ :cljr (instance? clojure.lang.Atom o) :joyride (= Atom (type o)) :org.babashka/nbb (= Atom (type o)) - :cljs (satisfies? cljs.core/IAtom o))) + :cljs (satisfies? cljs.core/IAtom o) + :lpy (instance? basilisp.lang.atom/Atom o))) (defn- notify [session-id a] (when-let [request @request] @@ -212,7 +222,7 @@ (defn- to-object [buffer value tag rep] (if-not *session* - (cson/-to-json + (cson/to-json* (with-meta (cson/tagged-value "remote" (pr-str value)) (meta value)) @@ -235,9 +245,9 @@ :clj (extend-type java.util.Collection cson/ToJson - (-to-json [value buffer] + (to-json* [value buffer] (if-let [id (value->id? value)] - (cson/-to-json (cson/tagged-value "ref" id) buffer) + (cson/to-json* (cson/tagged-value "ref" id) buffer) (cson/tagged-coll buffer (cond @@ -251,9 +261,9 @@ :clj (extend-type java.util.Map cson/ToJson - (-to-json [value buffer] + (to-json* [value buffer] (if-let [id (value->id? value)] - (cson/-to-json (cson/tagged-value "ref" id) buffer) + (cson/to-json* (cson/tagged-value "ref" id) buffer) (cson/tagged-map buffer "{" @@ -264,15 +274,16 @@ (extend-type #?(:clj Object :cljr Object - :cljs default) + :cljs default + :lpy python/object) cson/ToJson - (-to-json [value buffer] + (to-json* [value buffer] (to-object buffer value :object nil))) (defn- has? [m k] (try (k m) - (catch #?(:clj Exception :cljr Exception :cljs :default) _e))) + (catch #?(:clj Exception :cljr Exception :lpy Exception :cljs :default) _e nil))) (defn- no-cache [value] (or (not (coll? value)) @@ -341,21 +352,24 @@ #_{:clj-kondo/ignore [:unused-private-var]} (defn- runtime [] - #?(:portal :portal :bb :bb :clj :clj :joyride :joyride :org.babashka/nbb :nbb :cljs :cljs :cljr :cljr)) + #?(:portal :portal :bb :bb :clj :clj :joyride :joyride :org.babashka/nbb :nbb :cljs :cljs :cljr :cljr :lpy :py)) (defn- error->data [e] #?(:clj (assoc (Throwable->map e) :runtime (runtime)) :cljr (assoc (Throwable->map e) :runtime (runtime)) - :cljs e)) + :default e)) (defn update-value [new-value] (try (realize-value! new-value) (swap! tap-list conj new-value) - (catch #?(:clj Exception :cljr Exception :cljs :default) e + (catch #?(:clj Exception :cljr Exception :lpy Exception :cljs :default) e (swap! tap-list conj (error->data - (ex-info "Failed to receive value." {:value-type (type new-value)} e)))))) + #?(:lpy + (ex-info "Failed to receive value." {:value-type (type new-value)}) + :default + (ex-info "Failed to receive value." {:value-type (type new-value)} e))))))) (def ^:private runtime-keymap (atom ^::no-cache {})) @@ -372,6 +386,7 @@ #?(:bb "bb" :clj "jvm" :cljr "clr" + :lpy "py" :joyride "joyride" :org.babashka/nbb "nbb" :cljs (cond @@ -431,7 +446,7 @@ (cond-> out (predicate v) (assoc name result)) - (catch #?(:clj Exception :cljr Exception :cljs :default) _ex out)) + (catch #?(:cljs :default :default Exception) _ex out)) (assoc out name result))))) {} @registry) @@ -453,7 +468,7 @@ (a/try (a/let [return (binding [*session* session] (apply f args))] (done (assoc (source-info f) :return return))) - (catch #?(:clj Exception :cljr Exception :cljs js/Error) e + (catch #?(:clj Exception :cljr Exception :cljs js/Error :default Exception) e (done (assoc (source-info f) :error @@ -462,8 +477,7 @@ {::function f ::args args ::found? (some? f) - ::data (ex-data e)} - e) + ::data (ex-data e)}) datafy (assoc :runtime (runtime))))))))) diff --git a/src/portal/runtime/browser.cljc b/src/portal/runtime/browser.cljc index a9610842..bc0e7396 100644 --- a/src/portal/runtime/browser.cljc +++ b/src/portal/runtime/browser.cljc @@ -18,6 +18,12 @@ [portal.runtime.clr.client :as c] [portal.runtime.fs :as fs] [portal.runtime.json :as json] + [portal.runtime.shell :as shell]) + :lpy (:require [clojure.string :as str] + [portal.runtime :as rt] + [portal.runtime.py.client :as c] + [portal.runtime.fs :as fs] + [portal.runtime.json :as json] [portal.runtime.shell :as shell])) #?(:cljr (:import [System.Runtime.InteropServices OSPlatform RuntimeInformation]))) @@ -96,7 +102,7 @@ (when (str/ends-with? file (str app-name ".lnk")) {:app-id (str/replace (fs/basename (fs/dirname file)) "_crx_" "")})) (windows-chrome-web-applications)) - (catch #?(:cljs :default :default Exception) _))) + (catch #?(:cljs :default :default Exception) _ nil))) (defn- get-app-id-profile "Returns app-id and profile if portal is installed as `app-name` under any of the browser profiles" diff --git a/src/portal/runtime/cson.cljc b/src/portal/runtime/cson.cljc index f4ae120f..046402e7 100644 --- a/src/portal/runtime/cson.cljc +++ b/src/portal/runtime/cson.cljc @@ -15,14 +15,21 @@ (:require [goog.crypt.base64 :as Base64] [portal.runtime.json-buffer :as json] - [portal.runtime.macros :as m])) + [portal.runtime.macros :as m]) + :lpy + (:require [portal.runtime.json-buffer :as json])) #?(:clj (:import [java.net URL] [java.util Base64 Date UUID]) :joyride (:import) :org.babashka/nbb (:import) - :cljs (:import [goog.math Long]))) + :cljs (:import [goog.math Long]) + :lpy (:import [basilisp.lang :as lang] + [datetime :as datetime] + [fractions :as fractions] + [math :as math] + [uuid :as uuid]))) -(defprotocol ToJson (-to-json [value buffer])) +(defprotocol ToJson (to-json* [value buffer])) (declare ->value) @@ -33,11 +40,11 @@ (f value) value)) -(defn- to-json [buffer value] (-to-json (transform value) buffer)) +(defn- to-json [buffer value] (to-json* (transform value) buffer)) (defn tag [buffer tag value] (assert tag string?) - (-to-json value (json/push-string buffer tag))) + (to-json* value (json/push-string buffer tag))) (defn- box-long [buffer value] #?(:cljr @@ -59,41 +66,55 @@ :cljs (-> buffer (json/push-string "long") - (json/push-string (str value))))) + (json/push-string (str value))) + :lpy + (let [js-min-int -9007199254740991 + js-max-int 9007199254740991] + (if (<= js-min-int value js-max-int) + (json/push-long buffer value) + (-> buffer + (json/push-string "long") + (json/push-string (str value))))))) #?(:joyride (def Long js/Number)) #?(:org.babashka/nbb (def Long js/Number)) (extend-type #?(:cljr System.Int64 :clj Long - :cljs Long) + :cljs Long + :lpy python/int) ToJson - (-to-json [value buffer] (box-long buffer value))) + (to-json* [value buffer] (box-long buffer value))) (defn- ->long [buffer] #?(:clj (Long/parseLong (json/next-string buffer)) :cljr (System.Int64/Parse (json/next-string buffer)) - :cljs (.fromString Long (json/next-string buffer)))) + :cljs (.fromString Long (json/next-string buffer)) + :lpy (int (json/next-string buffer)))) (defn is-finite? [value] #?(:clj (Double/isFinite ^Double value) :cljr (Double/IsFinite ^Double value) - :cljs (.isFinite js/Number value))) + :cljs (.isFinite js/Number value) + :lpy (math/isfinite value))) (defn nan? [value] #?(:clj (.equals ^Double value ##NaN) :cljr (Double/IsNaN value) - :cljs (.isNaN js/Number value))) + :cljs (.isNaN js/Number value) + :lpy (math/isnan value))) (defn inf? [value] #?(:clj (.equals ^Double value ##Inf) :cljr (Double/IsInfinity value) - :cljs (== ##Inf value))) + :cljs (== ##Inf value) + :lpy (and (math/isinf value) (> value 0)))) (defn -inf? [value] #?(:clj (.equals ^Double value ##-Inf) :cljr (Double/IsNegativeInfinity value) - :cljs (== ##-Inf value))) + :cljs (== ##-Inf value) + :lpy (and (math/isinf value) (< value 0)))) (defn- push-double [buffer value] (cond @@ -110,24 +131,26 @@ (defn ->double [buffer] (double (json/next-double buffer))) -#?(:clj (extend-type Byte ToJson (-to-json [value buffer] (json/push-long buffer value)))) -#?(:clj (extend-type Short ToJson (-to-json [value buffer] (json/push-long buffer value)))) -#?(:clj (extend-type Integer ToJson (-to-json [value buffer] (json/push-long buffer value)))) -#?(:clj (extend-type Float ToJson (-to-json [value buffer] (push-double buffer value)))) -#?(:clj (extend-type Double ToJson (-to-json [value buffer] (push-double buffer value)))) +#?(:clj (extend-type Byte ToJson (to-json* [value buffer] (json/push-long buffer value)))) +#?(:clj (extend-type Short ToJson (to-json* [value buffer] (json/push-long buffer value)))) +#?(:clj (extend-type Integer ToJson (to-json* [value buffer] (json/push-long buffer value)))) +#?(:clj (extend-type Float ToJson (to-json* [value buffer] (push-double buffer value)))) +#?(:clj (extend-type Double ToJson (to-json* [value buffer] (push-double buffer value)))) + +#?(:cljr (extend System.Byte ToJson {:to-json* (fn [value buffer] (json/push-long buffer value))})) +#?(:cljr (extend System.Int16 ToJson {:to-json* (fn [value buffer] (json/push-long buffer value))})) +#?(:cljr (extend System.Int32 ToJson {:to-json* (fn [value buffer] (json/push-long buffer value))})) -#?(:cljr (extend System.Byte ToJson {:-to-json (fn [value buffer] (json/push-long buffer value))})) -#?(:cljr (extend System.Int16 ToJson {:-to-json (fn [value buffer] (json/push-long buffer value))})) -#?(:cljr (extend System.Int32 ToJson {:-to-json (fn [value buffer] (json/push-long buffer value))})) +#?(:cljr (extend-type System.Double ToJson (to-json* [value buffer] (push-double buffer value)))) -#?(:cljr (extend-type System.Double ToJson (-to-json [value buffer] (push-double buffer value)))) +#?(:cljs (extend-type number ToJson (to-json* [value buffer] (push-double buffer value)))) -#?(:cljs (extend-type number ToJson (-to-json [value buffer] (push-double buffer value)))) +#?(:lpy (extend-type python/float ToJson (to-json* [value buffer] (push-double buffer value)))) #?(:clj (extend-type clojure.lang.Ratio ToJson - (-to-json [value buffer] + (to-json* [value buffer] (-> buffer (json/push-string "R") (json/push-long (numerator value)) @@ -135,7 +158,7 @@ :cljr (extend-type clojure.lang.Ratio ToJson - (-to-json [value buffer] + (to-json* [value buffer] (-> buffer (json/push-string "R") (json/push-long (long (numerator value))) @@ -145,7 +168,7 @@ :cljs (deftype Ratio [numerator denominator] ToJson - (-to-json [_ buffer] + (to-json* [_ buffer] (-> buffer (json/push-string "R") (json/push-long numerator) @@ -154,7 +177,15 @@ (-pr-writer [_this writer _opts] (-write writer (str numerator)) (-write writer "/") - (-write writer (str denominator))))) + (-write writer (str denominator)))) + :lpy + (extend-type fractions/Fraction + ToJson + (to-json* [value buffer] + (-> buffer + (json/push-string "R") + (json/push-long (long (numerator value))) + (json/push-long (long (denominator value))))))) (defn ->ratio [buffer] (let [n (json/next-long buffer) @@ -166,9 +197,10 @@ (extend-type #?(:cljr System.String :clj String - :cljs string) + :cljs string + :lpy python/str) ToJson - (-to-json [value buffer] + (to-json* [value buffer] (-> buffer (json/push-string "s") (json/push-string value)))) @@ -176,17 +208,21 @@ #?(:cljr (extend System.Boolean ToJson - {:-to-json (fn [value buffer] (json/push-bool buffer value))}) + {:to-json* (fn [value buffer] (json/push-bool buffer value))}) :clj (extend-type Boolean ToJson - (-to-json [value buffer] (json/push-bool buffer value))) + (to-json* [value buffer] (json/push-bool buffer value))) :cljs (extend-type boolean ToJson - (-to-json [value buffer] (json/push-bool buffer value)))) + (to-json* [value buffer] (json/push-bool buffer value))) + :lpy + (extend-type python/bool + ToJson + (to-json* [value buffer] (json/push-bool buffer value)))) -(extend-type nil ToJson (-to-json [_value buffer] (json/push-null buffer))) +(extend-type nil ToJson (to-json* [_value buffer] (json/push-null buffer))) (defn- can-meta? [value] #?(:clj (instance? clojure.lang.IObj value) @@ -197,7 +233,10 @@ :org.babashka/nbb (try (with-meta value {}) true (catch :default _e false)) - :cljs (implements? IMeta value))) + :cljs (implements? IMeta value) + :lpy + (try (with-meta value {}) true + (catch Exception _e false)))) (defn- ->meta [buffer] (let [m (->value buffer) v (->value buffer)] @@ -205,7 +244,7 @@ (defn- push-meta [buffer m] (if (map? m) - (-to-json m (json/push-string buffer "^")) + (to-json* m (json/push-string buffer "^")) buffer)) (defn- tagged-meta [buffer value] @@ -218,8 +257,8 @@ (defrecord Tagged [tag rep] ToJson - (-to-json [this buffer] - (-to-json (:rep this) + (to-json* [this buffer] + (to-json* (:rep this) (-> buffer (tagged-meta (bb-fix this)) (json/push-string (:tag this)))))) @@ -244,7 +283,7 @@ (defn tagged-value? [x] (instance? Tagged x)) -(defn base64-encode ^String [byte-array] +(defn base64-encode [byte-array] #?(:clj (.encodeToString (Base64/getEncoder) byte-array) :joyride (.toString (.from js/Buffer byte-array) "base64") :org.babashka/nbb (.toString (.from js/Buffer byte-array) "base64") @@ -260,9 +299,10 @@ (extend-type #?(:clj #_:clj-kondo/ignore (Class/forName "[B") :cljr (Type/GetType "System.Byte[]") - :cljs js/Uint8Array) + :cljs js/Uint8Array + :lpy python/bytearray) ToJson - (-to-json [value buffer] + (to-json* [value buffer] (-> buffer (json/push-string "bin") (json/push-string (base64-encode value))))) @@ -272,14 +312,14 @@ #?(:clj (extend-type clojure.lang.BigInt ToJson - (-to-json [value buffer] + (to-json* [value buffer] (-> buffer (json/push-string "N") (json/push-string (str value))))) :cljr (extend-type clojure.lang.BigInt ToJson - (-to-json [value buffer] + (to-json* [value buffer] (-> buffer (json/push-string "N") (json/push-string (str value)))))) @@ -288,7 +328,7 @@ (m/extend-type? js/BigInt ToJson - (-to-json [value buffer] + (to-json* [value buffer] (-> buffer (json/push-string "N") (json/push-string (str value)))))) @@ -301,12 +341,12 @@ #?(:clj (extend-type Character ToJson - (-to-json [value buffer] + (to-json* [value buffer] (tag buffer "C" (int value)))) :cljr (extend System.Char ToJson - {:-to-json (fn [value buffer] + {:to-json* (fn [value buffer] (tag buffer "C" (int value)))}) :joyride nil :org.babashka/nbb nil @@ -314,7 +354,7 @@ :cljs (deftype Character [code] ToJson - (-to-json [_this buffer] + (to-json* [_this buffer] (tag buffer "C" code)) IHash (-hash [_this] code) @@ -346,29 +386,33 @@ (extend-type #?(:clj Date :cljr System.DateTime - :cljs js/Date) + :cljs js/Date + :lpy datetime/datetime) ToJson - (-to-json [value buffer] + (to-json* [value buffer] (-> buffer (json/push-string "inst") (json/push-long - (inst-ms - #?(:cljr (.UtcDateTime (System.DateTimeOffset. value)) :default value)))))) + #?(:cljr (inst-ms (.UtcDateTime (System.DateTimeOffset. value))) + :lpy (int (* 1000 (.timestamp value))) + :default (inst-ms value)))))) (defn- ->inst [buffer] #?(:clj (Date. ^long (json/next-long buffer)) :cljr (.DateTime (System.DateTimeOffset/FromUnixTimeMilliseconds (json/next-long buffer))) - :cljs (js/Date. (json/next-long buffer)))) + :cljs (js/Date. (json/next-long buffer)) + :lpy (datetime.datetime/utcfromtimestamp (/ (json/next-long buffer) 1000.0)))) #?(:joyride (def UUID (type (random-uuid)))) (extend-type #?(:clj UUID :cljr System.Guid - :cljs UUID) + :cljs UUID + :lpy uuid/UUID) ToJson - (-to-json [value buffer] + (to-json* [value buffer] (-> buffer (json/push-string "uuid") (json/push-string (str value))))) @@ -376,19 +420,20 @@ (defn- ->uuid [buffer] #?(:clj (UUID/fromString (json/next-string buffer)) :cljr (System.Guid/Parse (json/next-string buffer)) - :cljs (uuid (json/next-string buffer)))) + :cljs (uuid (json/next-string buffer)) + :lpy (uuid/UUID (json/next-string buffer)))) #?(:clj (extend-type URL ToJson - (-to-json [value buffer] + (to-json* [value buffer] (-> buffer (json/push-string "url") (json/push-string (str value))))) :cljr (extend-type System.Uri ToJson - (-to-json [value buffer] + (to-json* [value buffer] (-> buffer (json/push-string "url") (json/push-string (str value)))))) @@ -397,7 +442,7 @@ (m/extend-type? js/URL ToJson - (-to-json [value buffer] + (to-json* [value buffer] (-> buffer (json/push-string "url") (json/push-string (str value)))))) @@ -411,9 +456,10 @@ (extend-type #?(:clj clojure.lang.Keyword :cljr clojure.lang.Keyword - :cljs Keyword) + :cljs Keyword + :lpy lang.keyword/Keyword) ToJson - (-to-json [value buffer] + (to-json* [value buffer] (if-let [ns (namespace value)] (-> buffer (json/push-string ";") @@ -433,9 +479,10 @@ (extend-type #?(:clj clojure.lang.Symbol :cljr clojure.lang.Symbol - :cljs Symbol) + :cljs Symbol + :lpy lang.symbol/Symbol) ToJson - (-to-json [value buffer] + (to-json* [value buffer] (if-let [ns (namespace value)] (-> buffer (tagged-meta value) @@ -479,7 +526,7 @@ #?(:clj (extend-type clojure.lang.StringSeq ToJson - (-to-json [value buffer] + (to-json* [value buffer] (tagged-coll buffer "(" value)))) (def coll-types @@ -518,6 +565,7 @@ clojure.lang.APersistentMap+KeySeq clojure.lang.APersistentMap+ValSeq clojure.lang.LongRange + clojure.lang.Range clojure.lang.Repeat clojure.lang.PersistentList clojure.lang.PersistentQueue @@ -555,6 +603,7 @@ cljs.core/KeySeq cljs.core/ValSeq cljs.core/Repeat + cljs.core/Range cljs.core/List cljs.core/ChunkedCons cljs.core/ChunkedSeq @@ -564,21 +613,28 @@ cljs.core/PersistentArrayMapSeq cljs.core/PersistentTreeMapSeq cljs.core/NodeSeq - cljs.core/ArrayNodeSeq])) + cljs.core/ArrayNodeSeq] + :lpy + [lang.seq/LazySeq + lang.list/PersistentList])) (doseq [coll-type coll-types] #?(:clj (extend coll-type ToJson - {:-to-json (fn [value buffer] (tagged-coll buffer "(" value))}) + {:to-json* (fn [value buffer] (tagged-coll buffer "(" value))}) :cljr (extend coll-type ToJson - {:-to-json (fn [value buffer] (tagged-coll buffer "(" value))}) + {:to-json* (fn [value buffer] (tagged-coll buffer "(" value))}) :cljs (extend-type coll-type ToJson - (-to-json [value buffer] (tagged-coll buffer "(" value))))) + (to-json* [value buffer] (tagged-coll buffer "(" value))) + :lpy + (extend-type coll-type + ToJson + (to-json* [value buffer] (tagged-coll buffer "(" value))))) #?(:org.babashka/nbb nil :cljs @@ -586,16 +642,12 @@ ^:cljs.analyzer/no-resolve cljs.core/IntegerRange ToJson - (-to-json [value buffer] (tagged-coll buffer "(" value)))) + (to-json* [value buffer] (tagged-coll buffer "(" value)))) -#?(:joyride (def Range (type (range)))) -#?(:org.babashka/nbb (def Range (type (range)))) - -(extend-type #?(:clj clojure.lang.Range - :cljr clojure.lang.Range - :cljs Range) - ToJson - (-to-json [value buffer] (tagged-coll buffer "(" (into [] value)))) +#?(:clj + (extend-type clojure.lang.Range + ToJson + (to-json* [value buffer] (tagged-coll buffer "(" (into [] value))))) (def vector-types #?(:clj @@ -617,21 +669,26 @@ :cljs [cljs.core/PersistentVector cljs.core/Subvec - cljs.core/MapEntry])) + cljs.core/MapEntry] + :lpy [lang.vector/PersistentVector])) (doseq [vector-type vector-types] #?(:clj (extend vector-type ToJson - {:-to-json (fn [value buffer] (tagged-coll buffer "[" value))}) + {:to-json* (fn [value buffer] (tagged-coll buffer "[" value))}) :cljr (extend vector-type ToJson - {:-to-json (fn [value buffer] (tagged-coll buffer "[" value))}) + {:to-json* (fn [value buffer] (tagged-coll buffer "[" value))}) :cljs (extend-protocol ToJson vector-type - (-to-json [value buffer] (tagged-coll buffer "[" value))))) + (to-json* [value buffer] (tagged-coll buffer "[" value))) + :lpy + (extend-protocol ToJson + vector-type + (to-json* [value buffer] (tagged-coll buffer "[" value))))) (defn- ->into [zero buffer] (let [n (json/next-long buffer)] @@ -647,28 +704,39 @@ (extend-type #?(:clj clojure.lang.PersistentHashSet :cljr clojure.lang.PersistentHashSet - :cljs PersistentHashSet) + :cljs PersistentHashSet + :lpy lang.set/PersistentSet) ToJson - (-to-json [value buffer] (tagged-coll buffer "#" value))) + (to-json* [value buffer] (tagged-coll buffer "#" value))) #?(:joyride (def PersistentTreeSet (type (sorted-set)))) #?(:org.babashka/nbb (def PersistentTreeSet (type (sorted-set)))) -(extend-type #?(:clj clojure.lang.PersistentTreeSet - :cljr clojure.lang.PersistentTreeSet - :cljs PersistentTreeSet) - ToJson - (-to-json [value buffer] (tagged-coll buffer "sset" value))) +#?(:clj + (extend-type clojure.lang.PersistentTreeSet + ToJson + (to-json* [value buffer] (tagged-coll buffer "sset" value))) + :cljr + (extend-type clojure.lang.PersistentTreeSet + ToJson + (to-json* [value buffer] (tagged-coll buffer "sset" value))) + :cljs + (extend-type PersistentTreeSet + ToJson + (to-json* [value buffer] (tagged-coll buffer "sset" value)))) (defn- ->sset [buffer] - (let [n (json/next-long buffer) - values (for [_ (range n)] (->value buffer)) - order (zipmap values (range))] - (into - (sorted-set-by - (fn [a b] - (compare (get order a) (get order b)))) - values))) + #?(:lpy + (->into #{} buffer) + :default + (let [n (json/next-long buffer) + values (for [_ (range n)] (->value buffer)) + order (zipmap values (range))] + (into + (sorted-set-by + (fn [a b] + (compare (get order a) (get order b)))) + values)))) (defn tagged-map ([buffer value] @@ -692,38 +760,60 @@ (extend-type #?(:clj clojure.lang.PersistentHashMap :cljr clojure.lang.PersistentHashMap - :cljs PersistentHashMap) + :cljs PersistentHashMap + :lpy lang.map/PersistentMap) ToJson - (-to-json [value buffer] (tagged-map buffer value))) + (to-json* [value buffer] (tagged-map buffer value))) #?(:joyride (def PersistentTreeMap (type (sorted-map)))) #?(:org.babashka/nbb (def PersistentTreeMap (type (sorted-map)))) -(extend-type #?(:clj clojure.lang.PersistentTreeMap - :cljr clojure.lang.PersistentTreeMap - :cljs PersistentTreeMap) - ToJson - (-to-json [value buffer] (tagged-map buffer "smap" value))) +#?(:clj + (extend-type clojure.lang.PersistentTreeMap + ToJson + (to-json* [value buffer] (tagged-map buffer "smap" value))) + :cljr + (extend-type clojure.lang.PersistentTreeMap + ToJson + (to-json* [value buffer] (tagged-map buffer "smap" value))) + :cljs + (extend-type PersistentTreeMap + ToJson + (to-json* [value buffer] (tagged-map buffer "smap" value)))) #?(:clj (extend-type clojure.lang.APersistentMap ToJson - (-to-json [value buffer] (tagged-map buffer value)))) + (to-json* [value buffer] (tagged-map buffer value)))) #?(:joyride (def PersistentArrayMap (type {}))) #?(:org.babashka/nbb (def PersistentArrayMap (type {}))) -(extend-type #?(:clj clojure.lang.PersistentArrayMap - :cljr clojure.lang.PersistentArrayMap - :cljs PersistentArrayMap) - ToJson - (-to-json [value buffer] (tagged-map buffer value))) +#?(:clj + (extend-type clojure.lang.PersistentArrayMap + ToJson + (to-json* [value buffer] (tagged-map buffer value))) + :cljr + (extend-type clojure.lang.PersistentArrayMap + ToJson + (to-json* [value buffer] (tagged-map buffer value))) + :cljs + (extend-type PersistentArrayMap + ToJson + (to-json* [value buffer] (tagged-map buffer value)))) -(extend-type #?(:clj clojure.lang.IRecord - :cljr clojure.lang.IRecord - :cljs cljs.core/IRecord) - ToJson - (-to-json [value buffer] (tagged-map buffer value))) +#?(:clj + (extend-type clojure.lang.IRecord + ToJson + (to-json* [value buffer] (tagged-map buffer value))) + :cljr + (extend-type clojure.lang.IRecord + ToJson + (to-json* [value buffer] (tagged-map buffer value))) + :cljs + (extend-type cljs.core/IRecord + ToJson + (to-json* [value buffer] (tagged-map buffer value)))) (defn- ->map [buffer] (let [n (json/next-long buffer)] @@ -735,33 +825,57 @@ (assoc! m (->value buffer) (->value buffer))))))) (defn- ->sorted-map [buffer] - (let [n (json/next-long buffer) - pairs (for [_ (range n)] - [(->value buffer) (->value buffer)]) - order (zipmap (map first pairs) (range))] - (into - (sorted-map-by - (fn [a b] - (compare (get order a) (get order b)))) - pairs))) + #?(:lpy + (->map buffer) + :default + (let [n (json/next-long buffer) + pairs (for [_ (range n)] + [(->value buffer) (->value buffer)]) + order (zipmap (map first pairs) (range))] + (into + (sorted-map-by + (fn [a b] + (compare (get order a) (get order b)))) + pairs)))) #?(:bb (def clojure.lang.TaggedLiteral (type (tagged-literal 'a :a)))) #?(:joyride (def TaggedLiteral (type (tagged-literal 'f :v)))) #?(:org.babashka/nbb (def TaggedLiteral (type (tagged-literal 'f :v)))) -(extend-type #?(:clj clojure.lang.TaggedLiteral - :cljr clojure.lang.TaggedLiteral - :cljs TaggedLiteral) - ToJson - (-to-json [value buffer] - (-> buffer - (json/push-string "tag") - (json/push-string (name (:tag value))) - (to-json (:form value))))) +#?(:clj + (extend-type clojure.lang.TaggedLiteral + ToJson + (to-json* [value buffer] + (-> buffer + (json/push-string "tag") + (json/push-string (name (:tag value))) + (to-json (:form value))))) + :cljr + (extend-type clojure.lang.TaggedLiteral + ToJson + (to-json* [value buffer] + (-> buffer + (json/push-string "tag") + (json/push-string (name (:tag value))) + (to-json (:form value))))) + :cljs + (extend-type TaggedLiteral + ToJson + (to-json* [value buffer] + (-> buffer + (json/push-string "tag") + (json/push-string (name (:tag value))) + (to-json (:form value)))))) (defn- ->tagged-literal [buffer] - (tagged-literal (symbol (json/next-string buffer)) (->value buffer))) + #?(:lpy nil + :default + (tagged-literal (symbol (json/next-string buffer)) (->value buffer)))) + +(defn- ->list [buffer] + #?(:lpy (or (seq (->into [] buffer)) '()) + :default (or (list* (->into [] buffer)) '()))) #?(:clj (defn- eq ^Boolean [^String a b] (.equals a b))) @@ -770,14 +884,14 @@ (if-not (string? op) op (transform - (#?@(:bb [case] :cljr [case] :clj [condp eq] :cljs [case]) + (#?@(:bb [case] :cljr [case] :clj [condp eq] :cljs [case] :lpy [condp =]) op "s" (json/next-string buffer) ":" (->keyword buffer) "{" (->map buffer) "$" (->symbol buffer) "[" (->into [] buffer) - "(" (or (list* (->into [] buffer)) '()) + "(" (->list buffer) ";" (->keyword-2 buffer) "%" (->symbol-2 buffer) "#" (->into #{} buffer) @@ -810,4 +924,4 @@ ([string] (read string nil)) ([string options] (binding [*options* options] - (->value (json/->reader string))))) + (->value (json/->reader string))))) \ No newline at end of file diff --git a/src/portal/runtime/fs.cljc b/src/portal/runtime/fs.cljc index 7f581425..6a712b7c 100644 --- a/src/portal/runtime/fs.cljc +++ b/src/portal/runtime/fs.cljc @@ -6,33 +6,42 @@ ["os" :as os] ["path" :as path] [clojure.string :as s]) - :cljr (:require [clojure.string :as s])) - #?(:cljr (:import (System.IO Directory File Path)))) + :cljr (:require [clojure.string :as s]) + :lpy (:require [clojure.string :as s])) + #?(:cljr (:import (System.IO Directory File Path)) + :lpy (:import [pathlib :as p] + [os :as os] + [shutil :as shutil]))) (defn cwd [] #?(:clj (System/getProperty "user.dir") :cljs (.cwd js/process) - :cljr (Directory/GetCurrentDirectory))) + :cljr (Directory/GetCurrentDirectory) + :lpy (os/getcwd))) (defn slurp [path] #?(:clj (clojure.core/slurp path) :cljs (fs/readFileSync path "utf8") - :cljr (clojure.core/slurp path :enc "utf8"))) + :cljr (clojure.core/slurp path :enc "utf8") + :lpy (basilisp.core/slurp path))) (defn spit [path content] #?(:clj (clojure.core/spit path content) :cljs (fs/writeFileSync path content) - :cljr (clojure.core/spit path content))) + :cljr (clojure.core/spit path content) + :lpy (basilisp.core/spit path content))) (defn mkdir [path] #?(:clj (.mkdirs (io/file path)) - :cljs (fs/mkdirSync path #js {:recursive true}) - :cljr (Directory/CreateDirectory path))) + :cljs (fs/mkdirSync path (clj->js {:recursive true})) + :cljr (Directory/CreateDirectory path) + :lpy (os/makedirs path))) (defn path [] #?(:clj (System/getenv "PATH") :cljs (.-PATH js/process.env) - :cljr (Environment/GetEnvironmentVariable "PATH"))) + :cljr (Environment/GetEnvironmentVariable "PATH") + :lpy (.get os/environ "PATH"))) (defn separator [] (or #?(:clj (System/getProperty "path.separator") @@ -43,14 +52,16 @@ (defn join [& paths] #?(:clj (.getCanonicalPath ^java.io.File (apply io/file paths)) :cljs (apply path/join paths) - :cljr (Path/Join (into-array String paths)))) + :cljr (Path/Join (into-array String paths)) + :lpy (apply os.path/join paths))) (defn exists [f] (when (and (string? f) #?(:clj (.exists (io/file f)) :cljs (fs/existsSync f) :cljr (or (File/Exists f) - (Directory/Exists f)))) + (Directory/Exists f)) + :lpy (.is_file (p/Path f)))) f)) (defn is-file [f] @@ -58,7 +69,8 @@ #?(:clj (.isFile (io/file f)) :cljs (and (exists f) (.isFile (fs/lstatSync f))) - :cljr (File/Exists f))) + :cljr (File/Exists f) + :lpy (.exists (p/Path f)))) f)) (defn modified [f] @@ -66,7 +78,8 @@ #?(:clj (.lastModified (io/file f)) :cljs (.getTime ^js/Date (.-mtime (fs/lstatSync f))) :cljr (.ToUnixTimeMilliseconds - (DateTimeOffset. (File/GetLastWriteTime f)))))) + (DateTimeOffset. (File/GetLastWriteTime f))) + :lpy (os.path/getmtime f)))) (defn can-execute [f] #?(:clj (let [file (io/file f)] @@ -75,7 +88,8 @@ (try (fs/accessSync f fs/constants.X_OK) (catch :default _e true))) f) - :cljr (exists f))) + :cljr (exists f) + :lpy (os/access f os/X_OK))) (defn paths [] (s/split (or (path) "") (re-pattern (separator)))) @@ -91,21 +105,26 @@ (defn home [] #?(:clj (System/getProperty "user.home") :cljs (os/homedir) - :cljr (Environment/GetFolderPath System.Environment+SpecialFolder/UserProfile))) + :cljr (Environment/GetFolderPath System.Environment+SpecialFolder/UserProfile) + :lpy (p.Path/home))) (defn list [path] #?(:clj (for [^java.io.File f (.listFiles (io/file path))] (.getAbsolutePath f)) :cljs (for [f (fs/readdirSync path)] (join path f)) - :cljr (Directory/GetFileSystemEntries path))) + :cljr (Directory/GetFileSystemEntries path) + :lpy (let [p (cwd)] + (for [path (os/listdir path)] + (join p path))))) (defn rm [path] #?(:clj (let [children (list path)] (doseq [child children] (rm child)) (io/delete-file path)) - :cljs (fs/rmSync path #js {:recursive true}) - :cljr (Directory/Delete path true))) + :cljs (fs/rmSync path (clj->js {:recursive true})) + :cljr (Directory/Delete path true) + :lpy (shutil/rmtree path))) (defn rm-exit [path] #?(:clj (.deleteOnExit (io/file path)) @@ -118,12 +137,15 @@ #?(:clj (.getParent (io/file path)) :cljs (let [root (.-root (path/parse path))] (when-not (= path root) (path/dirname path))) - :cljr (some-> (Directory/GetParent path) str))) + :cljr (some-> (Directory/GetParent path) str) + :lpy (when-not (= "/" path) + (os.path/dirname path)))) (defn basename [path] #?(:clj (.getName (io/file path)) :cljs (path/basename path) - :cljr (Path/GetFileName path))) + :cljr (Path/GetFileName path) + :lpy (os.path/basename path))) (defn file-seq [dir] (tree-seq diff --git a/src/portal/runtime/json.cljc b/src/portal/runtime/json.cljc index 77b660a5..9481ca65 100644 --- a/src/portal/runtime/json.cljc +++ b/src/portal/runtime/json.cljc @@ -3,13 +3,15 @@ #?(:bb (:require [cheshire.core :as json]) :clj (:require [clojure.data.json :as json]) :cljr (:require [portal.runtime.clr.assembly] - [clojure.data.json :as json]))) + [clojure.data.json :as json]) + :lpy (:require [basilisp.json :as json]))) (defn write [value] #?(:bb (json/generate-string value) :clj (json/write-str value) :cljr (json/write-str value) - :cljs (.stringify js/JSON (clj->js value)))) + :cljs (.stringify js/JSON (clj->js value)) + :lpy (json/write-str value))) (defn read ([string] @@ -20,9 +22,10 @@ :cljr (json/read-str string :key-fn (:key-fn opts)) :cljs (js->clj (.parse js/JSON string) :keywordize-keys - (= keyword (:key-fn opts)))))) + (= keyword (:key-fn opts))) + :lpy (json/read-str string :key-fn (:key-fn opts))))) (defn read-stream [stream] #?(:bb (json/parse-stream stream keyword) :clj (json/read stream :key-fn keyword) - :cljs (throw (ex-info "Unsupported in cljs" {:stream stream})))) + :default (throw (ex-info "Unsupported" {:stream stream})))) diff --git a/src/portal/runtime/json_buffer.cljc b/src/portal/runtime/json_buffer.cljc index 76fc1b9c..c6926326 100644 --- a/src/portal/runtime/json_buffer.cljc +++ b/src/portal/runtime/json_buffer.cljc @@ -12,12 +12,13 @@ JsonNode JsonArray JsonValue - JsonNodeOptions)))) + JsonNodeOptions)) + :lpy (:import [json :as json]))) (defn -shift [this] (this)) #?(:cljs (defn shifter [source] - (let [this #js {:n 0}] + (let [this (clj->js {:n 0})] (fn [] (let [n (.-n this) result (aget source n)] @@ -37,37 +38,44 @@ (doto (JsonReader. (StringReader. data)) (.beginArray)) :cljs - (shifter (.parse js/JSON data)))) + (shifter (.parse js/JSON data)) + :lpy + (iter (json/loads data)))) (defn push-null [buffer] #?(:bb (conj! buffer nil) :clj (doto ^JsonWriter buffer (.nullValue)) :cljr (doto ^JsonArray buffer (.Add nil)) - :cljs (doto ^js buffer (.push nil)))) + :cljs (doto ^js buffer (.push nil)) + :lpy (doto buffer (.append nil)))) (defn push-bool [buffer value] #?(:bb (conj! buffer value) :clj (doto ^JsonWriter buffer (.value ^Boolean value)) :cljr (doto ^JsonArray buffer (.Add value)) - :cljs (doto ^js buffer (.push value)))) + :cljs (doto ^js buffer (.push value)) + :lpy (doto buffer (.append value)))) (defn push-long [buffer value] #?(:bb (conj! buffer value) :clj (doto ^JsonWriter buffer (.value ^Long value)) :cljr (doto ^JsonArray buffer (.Add value)) - :cljs (doto ^js buffer (.push value)))) + :cljs (doto ^js buffer (.push value)) + :lpy (doto buffer (.append value)))) (defn push-double [buffer value] #?(:bb (conj! buffer value) :clj (doto ^JsonWriter buffer (.value ^Double value)) :cljr (doto ^JsonArray buffer (.Add value)) - :cljs (doto ^js buffer (.push value)))) + :cljs (doto ^js buffer (.push value)) + :lpy (doto buffer (.append value)))) (defn push-string [buffer value] #?(:bb (conj! buffer value) :clj (doto ^JsonWriter buffer (.value ^String value)) :cljr (doto ^JsonArray buffer (.Add value)) - :cljs (doto ^js buffer (.push value)))) + :cljs (doto ^js buffer (.push value)) + :lpy (doto buffer (.append value)))) (defn push-value [buffer value] (cond @@ -81,7 +89,8 @@ #?(:bb (let [v (first @buffer)] (vswap! buffer rest) v) :clj (.nextNull ^JsonReader buffer) :cljr (do (vswap! buffer rest) nil) - :cljs (-shift buffer))) + :cljs (-shift buffer) + :lpy (python/next buffer))) (defn next-bool [buffer] #?(:bb (let [v (first @buffer)] (vswap! buffer rest) v) @@ -89,7 +98,8 @@ (vswap! buffer rest) (.GetValue v (type-args System.Boolean))) :clj (.nextBoolean ^JsonReader buffer) - :cljs (-shift buffer))) + :cljs (-shift buffer) + :lpy (python/next buffer))) (defn next-long ^long [buffer] #?(:bb (let [v (first @buffer)] (vswap! buffer rest) v) @@ -97,7 +107,8 @@ (vswap! buffer rest) (.GetValue v (type-args System.Int64))) :clj (.nextLong ^JsonReader buffer) - :cljs (-shift buffer))) + :cljs (-shift buffer) + :lpy (python/next buffer))) (defn next-double ^double [buffer] #?(:bb (let [v (first @buffer)] (vswap! buffer rest) v) @@ -105,15 +116,17 @@ (vswap! buffer rest) (.GetValue v (type-args System.Double))) :clj (.nextDouble ^JsonReader buffer) - :cljs (-shift buffer))) + :cljs (-shift buffer) + :lpy (python/next buffer))) -(defn next-string ^String [buffer] +(defn next-string [buffer] #?(:bb (let [v (first @buffer)] (vswap! buffer rest) v) :cljr (let [v ^JsonValue (first @buffer)] (vswap! buffer rest) (.GetValue v (type-args System.String))) :clj (.nextString ^JsonReader buffer) - :cljs (-shift buffer))) + :cljs (-shift buffer) + :lpy (python/next buffer))) (defn next-value [buffer] #?(:bb (let [v (first @buffer)] (vswap! buffer rest) v) @@ -142,7 +155,8 @@ (if (== (.indexOf n 46) -1) (Long/parseLong n) (Double/parseDouble n)))) - :cljs (-shift buffer))) + :cljs (-shift buffer) + :lpy (python/next buffer))) (defn with-buffer [f value] #?(:bb (json/write (persistent! (f (transient []) value))) @@ -156,4 +170,5 @@ (f json value) (.endArray json) (.close json) - (.toString out)))) + (.toString out)) + :lpy (json/dumps (f (python/list) value)))) diff --git a/src/portal/runtime/py/client.lpy b/src/portal/runtime/py/client.lpy new file mode 100644 index 00000000..1bc91b95 --- /dev/null +++ b/src/portal/runtime/py/client.lpy @@ -0,0 +1,99 @@ +(ns ^:no-doc portal.runtime.py.client + (:require [portal.runtime :as rt])) + +(def ops + {:portal.rpc/response + (fn [message _send!] + (let [id (:portal.rpc/id message)] + (when-let [response (get @rt/pending-requests id)] + (deliver response message))))}) + +(def timeout 60000) + +(defn- get-connection [session-id] + (let [p (promise) + watch-key (keyword (gensym))] + (if-let [send! (get @rt/connections session-id)] + (deliver p send!) + (add-watch + rt/connections + watch-key + (fn [_ _ _old new] + (when-let [send! (get new session-id)] + (deliver p send!))))) + (let [result (deref p timeout nil)] + (remove-watch rt/connections watch-key) + result))) + +(defn- request! [session-id message] + (if-let [send! (get-connection session-id)] + (let [id (rt/next-id) + response (promise) + message (assoc message :portal.rpc/id id)] + (swap! rt/pending-requests assoc id response) + (send! message) + (let [response (deref response timeout ::timeout)] + (swap! rt/pending-requests dissoc id) + (if-not (= response ::timeout) + response + (throw (ex-info + "Portal request timeout" + {::timeout true + :session-id session-id + :message message}))))) + (throw (ex-info "No such portal session" + {:session-id session-id :message message})))) + +(defn- broadcast! [message] + (when-let [sessions (keys @rt/connections)] + (let [response (promise)] + (doseq [session-id sessions] + (future + (try + (deliver response (request! session-id message)) + (catch Exception ex + (when (-> ex ex-data ::timeout) + (swap! rt/connections dissoc session-id)) + (deliver response ex))))) + (let [response (deref response timeout ::timeout)] + (cond + (instance? Exception response) + (throw response) + (not= response ::timeout) + response + :else + (throw (ex-info + "Portal request timeout" + {::timeout true + :session-id :all + :message message}))))))) + +(defn request + ([message] + (broadcast! message)) + ([session-id message] + (request! session-id message))) + +(defn- push-state [session-id new-value] + (request session-id {:op :portal.rpc/push-state :state new-value}) + (rt/update-selected session-id [new-value]) + new-value) + +(defrecord Portal [session-id] +;; i/IDeref +;; (deref [_this] (first (get-in @rt/sessions [session-id :selected]))) + +;; IAtom +;; (reset [_this new-value] (push-state session-id new-value)) + +;; (swap [this f] (reset! this (f @this))) +;; (swap [this f a] (reset! this (f @this a))) +;; (swap [this f a b] (reset! this (f @this a b))) +;; (swap [this f a b args] (reset! this (apply f @this a b args))) +;; (compareAndSet [_this _oldv _newv]) + ) + +(defn make-atom [session-id] (Portal. session-id)) + +(defn open? [session-id] + (contains? @rt/connections session-id)) diff --git a/src/portal/runtime/py/launcher.lpy b/src/portal/runtime/py/launcher.lpy new file mode 100644 index 00000000..7667bf55 --- /dev/null +++ b/src/portal/runtime/py/launcher.lpy @@ -0,0 +1,47 @@ +(ns ^:no-doc portal.runtime.py.launcher + (:require [portal.runtime :as rt] + [portal.runtime.browser :as browser] + [portal.runtime.py.client :as c])) + +(defonce ^:private server (atom nil)) + +(defn start [options]) + +(defn stop []) + +(defn open + ([options] + (open nil options)) + ([portal options] + (let [server (start options)] + (browser/open {:portal portal :options options :server server})))) + +(defn clear [portal] + (if (= portal :all) + (c/request {:op :portal.rpc/clear}) + (c/request (:session-id portal) {:op :portal.rpc/clear})) + (rt/cleanup-sessions)) + +(defn close [portal] + (if (= portal :all) + (c/request {:op :portal.rpc/close}) + (c/request (:session-id portal) {:op :portal.rpc/close})) + (rt/close-session (:session-id portal)) + (rt/cleanup-sessions)) + +(defn eval-str [portal msg] + (let [response (if (= portal :all) + (c/request (assoc msg :op :portal.rpc/eval-str)) + (c/request (:session-id portal) + (assoc msg :op :portal.rpc/eval-str)))] + (if-not (:error response) + response + (throw (ex-info (:message response) response))))) + +(defn sessions [] + (for [session-id (rt/active-sessions)] (c/make-atom session-id))) + +(defn url [portal] + (browser/url {:portal portal :server @server})) + +(reset! rt/request c/request) diff --git a/src/portal/runtime/py/server.lpy b/src/portal/runtime/py/server.lpy new file mode 100644 index 00000000..fbc72e1c --- /dev/null +++ b/src/portal/runtime/py/server.lpy @@ -0,0 +1,3 @@ +(ns ^:no-doc portal.runtime.py.server) + +(defmulti route (juxt :request-method :uri)) \ No newline at end of file diff --git a/src/portal/runtime/rpc.cljc b/src/portal/runtime/rpc.cljc index 0404d97e..cc94d54c 100644 --- a/src/portal/runtime/rpc.cljc +++ b/src/portal/runtime/rpc.cljc @@ -1,7 +1,8 @@ (ns ^:no-doc portal.runtime.rpc (:require #?(:clj [portal.runtime.jvm.client :as c] :cljs [portal.runtime.node.client :as c] - :cljr [portal.runtime.clr.client :as c]) + :cljr [portal.runtime.clr.client :as c] + :lpy [portal.runtime.py.client :as c]) [portal.runtime :as rt])) (defn on-open [session send!] diff --git a/src/portal/runtime/shell.cljc b/src/portal/runtime/shell.cljc index 14ac6636..d211ad5f 100644 --- a/src/portal/runtime/shell.cljc +++ b/src/portal/runtime/shell.cljc @@ -1,7 +1,8 @@ (ns ^:no-doc portal.runtime.shell #?(:clj (:require [clojure.java.shell :as shell]) :cljs (:require ["child_process" :as cp]) - :cljr (:require [clojure.clr.shell :as shell]))) + :cljr (:require [clojure.clr.shell :as shell]) + :lpy (:require [basilisp.shell :as shell]))) (defn spawn [bin & args] #?(:clj @@ -17,6 +18,12 @@ (.on ps "error" reject) (.on ps "close" resolve)))) :cljr + (future + (let [{:keys [exit err out]} (apply shell/sh bin args)] + (when-not (zero? exit) + (prn (into [bin] args)) + (println err out)))) + :lpy (future (let [{:keys [exit err out]} (apply shell/sh bin args)] (when-not (zero? exit) @@ -29,4 +36,5 @@ {:exit (.-status result) :out (str (.-stdout result)) :err (str (.-stderr result))}) - :cljr (apply shell/sh bin args))) \ No newline at end of file + :cljr (apply shell/sh bin args) + :lpy (apply shell/sh bin args))) \ No newline at end of file diff --git a/src/portal/sync.cljc b/src/portal/sync.cljc index 366bb934..f5ced6a3 100644 --- a/src/portal/sync.cljc +++ b/src/portal/sync.cljc @@ -2,6 +2,6 @@ (:refer-clojure :exclude [let try])) (defmacro let [bindings & body] - `(clojure.core/let ~bindings ~@body)) + `(~'let ~bindings ~@body)) (defmacro try [& body] `(~'try ~@body)) diff --git a/src/portal/ui/rpc.cljs b/src/portal/ui/rpc.cljs index 51841b56..5eb38f7d 100644 --- a/src/portal/ui/rpc.cljs +++ b/src/portal/ui/rpc.cljs @@ -54,8 +54,8 @@ (when-not js/goog.DEBUG (extend-type default cson/ToJson - (-to-json [value buffer] - (cson/-to-json + (to-json* [value buffer] + (cson/to-json* (with-meta (cson/tagged-value "remote" (pr-str value)) (meta value)) diff --git a/src/portal/ui/rpc/runtime.cljs b/src/portal/ui/rpc/runtime.cljs index f8856937..96c62ef5 100644 --- a/src/portal/ui/rpc/runtime.cljs +++ b/src/portal/ui/rpc/runtime.cljs @@ -22,7 +22,7 @@ (to-object buffer this :runtime-object nil) (if-let [id (->id this)] (cson/tag buffer "ref" id) - (cson/-to-json + (cson/to-json* (cson/tagged-value "remote" (:pr-str object)) buffer))))) @@ -30,7 +30,7 @@ (deftype RuntimeObject [runtime object] Runtime - cson/ToJson (-to-json [this buffer] (runtime-to-json buffer this)) + cson/ToJson (to-json* [this buffer] (runtime-to-json buffer this)) IMeta (-meta [_] (:meta object)) IHash (-hash [_] (:id object)) IEquiv @@ -59,7 +59,7 @@ (deftype RuntimeAtom [runtime object a] Runtime - cson/ToJson (-to-json [this buffer] (runtime-to-json buffer this)) + cson/ToJson (to-json* [this buffer] (runtime-to-json buffer this)) IAtom IDeref diff --git a/src/portal/ui/viewer/diff.cljs b/src/portal/ui/viewer/diff.cljs index f68ad52c..45757694 100644 --- a/src/portal/ui/viewer/diff.cljs +++ b/src/portal/ui/viewer/diff.cljs @@ -13,13 +13,13 @@ (extend-protocol cson/ToJson diff/Deletion - (-to-json [this buffer] (cson/tag buffer "diff/Deletion" (:- this))) + (to-json* [this buffer] (cson/tag buffer "diff/Deletion" (:- this))) diff/Insertion - (-to-json [this buffer] (cson/tag buffer "diff/Insertion" (:+ this))) + (to-json* [this buffer] (cson/tag buffer "diff/Insertion" (:+ this))) diff/Mismatch - (-to-json [this buffer] (cson/tag buffer "diff/Mismatch" ((juxt :- :+) this)))) + (to-json* [this buffer] (cson/tag buffer "diff/Mismatch" ((juxt :- :+) this)))) (defmethod rpc/-read "diff/Deletion" [_ value] (diff/Deletion. value)) (defmethod rpc/-read "diff/Insertion" [_ value] (diff/Insertion. value)) diff --git a/src/portal/viewer.cljc b/src/portal/viewer.cljc index 55cca2ef..6a69d998 100644 --- a/src/portal/viewer.cljc +++ b/src/portal/viewer.cljc @@ -15,7 +15,10 @@ :org.babashka/nbb (try (with-meta value {}) true (catch :default _e false)) - :cljs (implements? IMeta value))) + :cljs (implements? IMeta value) + :lpy + (try (with-meta value {}) true + (catch Exception _e false)))) (defn default "Set the default viewer for a value. diff --git a/test/portal/bench.cljc b/test/portal/bench.cljc index 861e2fdb..5c458797 100644 --- a/test/portal/bench.cljc +++ b/test/portal/bench.cljc @@ -1,6 +1,8 @@ (ns portal.bench #?(:cljs (:refer-clojure :exclude [simple-benchmark])) - #?(:cljs (:require-macros portal.bench))) + #?(:cljs (:require-macros portal.bench)) + #?(:lpy (:import [math :as Math] + [time :as time]))) (defn- now ([] @@ -8,14 +10,17 @@ :cljr (.Ticks (System.DateTime/Now)) :cljs (if (exists? js/process) (.hrtime js/process) - (.now js/Date)))) + (.now js/Date)) + :lpy (time/process_time_ns))) ([a] #?(:clj (/ (- (now) a) 1000000.0) :cljr (/ (- (now) a) 10000.0) :cljs (if (exists? js/process) (let [[a b] (.hrtime js/process a)] (+ (* a 1000.0) (/ b 1000000.0))) - (- (.now js/Date) a))))) + (- (.now js/Date) a)) + :lpy (/ (- (now) a) 1000000.0)))) + (defn floor [v] #?(:cljr (Math/Floor v) :default (Math/floor v))) diff --git a/test/portal/runtime/bench_cson.cljc b/test/portal/runtime/bench_cson.cljc index 9a8cdfcd..553cb239 100644 --- a/test/portal/runtime/bench_cson.cljc +++ b/test/portal/runtime/bench_cson.cljc @@ -19,10 +19,15 @@ :http-requests d/http-requests :http-responses d/http-responses}) -(defn- pr-meta [v] (binding [*print-meta* true] (pr-str v))) +(defn- pr-meta [v] + #?(:lpy (pr-str v) + :default (binding [*print-meta* true] + (pr-str v)))) (def ^:private formats - #?(:org.babashka/nbb [:edn :cson] :default [:transit :edn :cson])) + #?(:org.babashka/nbb [:edn :cson] + :lpy [:cson] + :default [:transit :edn :cson])) (defn run-benchmark [] (doall diff --git a/test/portal/runtime/cson_test.cljc b/test/portal/runtime/cson_test.cljc index e556f1bd..bd32fd7c 100644 --- a/test/portal/runtime/cson_test.cljc +++ b/test/portal/runtime/cson_test.cljc @@ -104,25 +104,27 @@ #{0} {0 0})) -(deftest sorted-collections - (let [a (sorted-map :a 1 :c 3 :b 2) - b (pass a)] - (is (= a b)) - (is (= (keys a) (keys b))) - (is (= (type a) (type b)))) - (let [a (sorted-map-by > 1 "a" 2 "b" 3 "c") - b (pass a)] - (is (= a b)) - (is (= (keys a) (keys b))) - (is (= (type a) (type b)))) - (let [a (sorted-set 1 2 3) - b (pass a)] - (is (= a b)) - (is (= (seq a) (seq b)))) - (let [a (sorted-set-by > 1 2 3) - b (pass a)] - (is (= a b)) - (is (= (seq a) (seq b))))) +#?(:lpy nil + :default + (deftest sorted-collections + (let [a (sorted-map :a 1 :c 3 :b 2) + b (pass a)] + (is (= a b)) + (is (= (keys a) (keys b))) + (is (= (type a) (type b)))) + (let [a (sorted-map-by > 1 "a" 2 "b" 3 "c") + b (pass a)] + (is (= a b)) + (is (= (keys a) (keys b))) + (is (= (type a) (type b)))) + (let [a (sorted-set 1 2 3) + b (pass a)] + (is (= a b)) + (is (= (seq a) (seq b)))) + (let [a (sorted-set-by > 1 2 3) + b (pass a)] + (is (= a b)) + (is (= (seq a) (seq b)))))) (def tagged [#?(:clj (Date.) @@ -131,7 +133,8 @@ #?(:clj (UUID/randomUUID) :cljr (Guid/NewGuid) :cljs (random-uuid)) - (tagged-literal 'tag :value)]) + #?(:lpy nil + :default (tagged-literal 'tag :value))]) (deftest tagged-objects (doseq [value tagged] @@ -197,5 +200,7 @@ (deftest binary (let [bin #?(:clj (.getBytes "hi") :cljr (.GetBytes Encoding/UTF8 "hi") - :cljs (.encode (js/TextEncoder.) "hi"))] - (is (= "[\"bin\",\"aGk=\"]" (cson/write bin))))) \ No newline at end of file + :cljs (.encode (js/TextEncoder.) "hi") + :default nil)] + (when bin + (is (= "[\"bin\",\"aGk=\"]" (cson/write bin)))))) \ No newline at end of file