From 5940d694263080807a44328f02a171049ab31a21 Mon Sep 17 00:00:00 2001 From: Michiel Borkent Date: Mon, 4 Mar 2024 14:49:43 +0100 Subject: [PATCH] Fix #626: add `cljs.core/exists?` (#918) --- CHANGELOG.md | 4 ++++ src/sci/impl/namespaces.cljc | 30 +++++++++++++++++++++++++++++- test/sci/core_test.cljc | 18 ++++++++++++++++-- 3 files changed, 49 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ebaa5ca0..8b7c36c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,10 @@ SCI is used in [babashka](https://github.com/babashka/babashka), [joyride](https://github.com/BetterThanTomorrow/joyride/) and many [other](https://github.com/babashka/sci#projects-using-sci) projects. +## Unreleased + +- Fix [#626](https://github.com/babashka/sci/issues/626): add `cljs.core/exists?` + ## 0.8.41 (2023-11-24) - Bump edamame to 1.3.23 diff --git a/src/sci/impl/namespaces.cljc b/src/sci/impl/namespaces.cljc index 519ac48c..8a218ac8 100644 --- a/src/sci/impl/namespaces.cljc +++ b/src/sci/impl/namespaces.cljc @@ -12,7 +12,8 @@ print-dup #?(:cljs alter-meta!) memfn - time]) + time + exists?]) (:require #?(:clj [clojure.edn :as edn] :cljs [cljs.reader :as edn]) @@ -20,6 +21,7 @@ #?(:clj [sci.impl.proxy :as proxy]) #?(:clj [sci.impl.copy-vars :refer [copy-core-var copy-var macrofy new-var]] :cljs [sci.impl.copy-vars :refer [new-var]]) + #?(:cljs [sci.impl.resolve]) [clojure.set :as set] [clojure.string :as str] [clojure.walk :as walk] @@ -998,6 +1000,30 @@ " msecs")) ret#)) +#?(:cljs + (defn exists? + "Return true if argument exists, analogous to usage of typeof operator + in JavaScript." + [_ _&env ctx x] + (if (symbol? x) + (if (qualified-symbol? x) + (if (= "js" (namespace x)) + (let [splits (str/split (name x) ".")] + (list* 'cljs.core/and + (map (fn [accessor] + (list 'cljs.core/not (list 'cljs.core/undefined? (symbol "js" (str accessor))))) + (reduce (fn [acc split] + (let [new-sym (let [la (last acc)] + (str la (when la ".") split))] + (conj acc new-sym))) + [] splits)))) + (boolean (try (sci.impl.resolve/resolve-symbol ctx x nil nil) + (catch :default _ nil)))) + (or (boolean (sci-find-ns ctx x)) + (boolean (try (sci.impl.resolve/resolve-symbol ctx x nil nil) + (catch :default _ nil))))) + `(some? ~x)))) + #?(:clj (defn system-time [] (System/nanoTime))) @@ -1281,6 +1307,8 @@ 'ex-info (copy-core-var ex-info) 'ex-message (copy-core-var ex-message) 'ex-cause (copy-core-var ex-cause) + #?@(:cljs ['exists? (copy-var exists? clojure-core-ns {:macro true + :ctx true :name 'exists?})]) 'find-ns (copy-var sci-find-ns clojure-core-ns {:ctx true :name 'find-ns}) 'create-ns (copy-var sci-create-ns clojure-core-ns {:ctx true :name 'create-ns}) 'in-ns (copy-var sci-in-ns clojure-core-ns {:ctx true :name 'in-ns}) diff --git a/test/sci/core_test.cljc b/test/sci/core_test.cljc index a77d7199..91e23e67 100644 --- a/test/sci/core_test.cljc +++ b/test/sci/core_test.cljc @@ -1677,8 +1677,22 @@ (let [output (atom "") print-fn #(swap! output str %)] (is (= 1 (sci/binding [sci/print-fn print-fn] (sci/eval-string "(time 1)" {:classes {'js js/globalThis :allow :all}})))) - (is (re-matches #"\"Elapsed time: \d\.\d+ msecs\"\s*" @output)))) - ) + (is (re-matches #"\"Elapsed time: \d\.\d+ msecs\"\s*" @output))))) + +#?(:cljs + (deftest exists?-test + (is (true? (sci/eval-string "(exists? cljs.core.first)"))) + (is (true? (sci/eval-string "(exists? cljs.core/first)"))) + (is (true? (sci/eval-string "(exists? js/console)" {:classes {'js js/globalThis + :allow :all}}))) + (is (true? (sci/eval-string "(exists? js/console.log)" {:classes {'js js/globalThis + :allow :all}}))) + (is (false? (sci/eval-string "(exists? js/foo.bar)" {:classes {'js js/globalThis + :allow :all}}))) + (is (false? (sci/eval-string "(exists? js/console.log.foobar)" {:classes {'js js/globalThis + :allow :all}}))) + (is (false? (sci/eval-string "(exists? console.log)" {:classes {'js js/globalThis + :allow :all}}))))) ;;;; Scratch