This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2021-12-30
Channels
- # adventofcode (27)
- # ai (1)
- # announcements (2)
- # aws (66)
- # babashka (2)
- # beginners (34)
- # calva (28)
- # cider (5)
- # clj-kondo (18)
- # clojure (16)
- # clojure-europe (4)
- # clojure-norway (2)
- # clojure-uk (3)
- # clojurescript (11)
- # code-reviews (23)
- # conjure (23)
- # core-logic (1)
- # cursive (12)
- # datalevin (1)
- # datomic (9)
- # introduce-yourself (3)
- # kaocha (3)
- # klipse (4)
- # malli (42)
- # midje (1)
- # minecraft (1)
- # missionary (4)
- # music (1)
- # nextjournal (10)
- # polylith (5)
- # re-frame (2)
- # reitit (1)
- # releases (1)
- # sci (126)
- # shadow-cljs (4)
- # sql (2)
- # tools-deps (11)
I’m using sci in the context of osascript
and I’m having trouble getting sci to evaluate some forms that work fine when compiled. For instance, this expression works when compiled:
(-> (js/Application "Safari")
(.-windows)
(.at 0)
(.-tabs)
(.at 0)
(.url))
;; => ""
But the same expression triggers an error when evaluated by sci:
execution error: Error: #error {:message "Can't convert types.", :data {:type :sci/error, :line 3, :column 1, :message "Can't convert types.", :sci.impl/callstack #object[cljs.core.Volatile {:val ({:line 3, :column 1, :ns #object[cV user], :file nil, :sci.impl/f-meta {:ns #object[cV clojure.core], :name println, :sci/built-in true, :arglists ([& objs]), :doc nil}})}], :file nil, :locals {}}, :cause #object[Error Error: Can't convert types.]} (-2700)
I imagine this is the result of some subtle differences in how sci handles interop, but I’m not sure how to troubleshoot from here.I have a repro:
$ osascript -l JavaScript out/main.js '(+ 1 2 3)'
:hello
:args #js [(+ 1 2 3)]
6
(ns
(:require [sci.core :as sci]))
(defn ^:export run [args]
(println :hello)
(println :args args)
#_(prn (-> (js/Application "Safari")
(.-windows)
(.at 0)
(.-tabs)
(.at 0)
(.url)))
(sci/eval-string (first args)
{:classes {'js goog/global
:allow :all}}))
(set! (.-run goog.global) run)
;; shadow-cljs configuration
{:deps true
:builds
{:main {:target :browser
:output-dir "out"
:modules {:main {:entries []}}
:compiler-options {:optimizations :simple}}}}
npx shadow-cljs release main
$ osascript -l JavaScript out/main.js '(def x (js/Application "Safari")) (def docs (.-documents x)) (some? docs) '
:hello
:args #js [(def x (js/Application "Safari")) (def docs (.-documents x)) (some? docs) ]
out/main.js: execution error: Error: #error {:message "Can't convert types.", :data {:type :sci/error, :line 1, :column 62, :message "Can't convert types.", :sci.impl/callstack #object[cljs.core.Volatile {:val ({:line 1, :column 62, :ns #object[sci.impl.vars.SciNamespace], :file nil, :sci.impl/f-meta {:ns #object[sci.impl.vars.SciNamespace], :name some?, :sci/built-in true, :arglists ([x]), :doc "Returns true if x is not nil, false otherwise."}})}], :file nil, :locals {}}, :cause #object[Error Error: Can't convert types.]} (-2700)
I'm under the impression that some of the behavior of osascript is "lazy" or so:
var safari = Application("Safari");
var docs = safari["documents"];
console.log(docs);
@zane This is kind of what happens in SCI:
var safari = Application("Safari");
var docs = safari["documents"];
var doc = docs[0];
var docurl = doc["url"];
console.log(docurl.apply(doc));
That reproduces the error for me:
/tmp/osa.js: execution error: Error: Error: Can't convert types. (-1700)That explains why my attempts to do something similar with aget
and so on were unsuccessful.
It appears that many of the values in JXA are “Object Specifiers”. They appear to be like “paths” or a “pointers”. They are not like regular JavaScript objects in that when you access a property on one you always get back another object specifier. When you finally call the specifier you get back an actual JavaScript object if the path refers to a value. If the path does not refer to a value you get the error we’ve been seeing.
>> Application("Safari").documents[0].foo.bar.baz()
!! Error: Error: Can't convert types.
This is true regardless of what you provide as arguments.
>> Application("Safari").documents[0].foo.bar.baz("anything")
!! Error: Error: Can't convert types.
So it would seem that when sci attempts to call a function the .apply
returns an object specifier that refers to an invalid location. That object specifier, when called, triggers the error.
I’m wondering whether we can work around this by assigning .apply
to the object specifier prototype.
>> Application("Safari").documents[0].url.__proto__["apply"] = Application("Safari").documents[0].url.__proto__["callAsFunction"]
=> [function anonymous] {
"length":0,
"name":"",
"prototype":{"constructor":[function anonymous]}
}
>> Application("Safari").documents[0].url.apply()
=> " "
>> Application("Safari").documents[0].text.apply()
=> "About\nStore\nGmailImages\n\n\n\n \nAdvertising\nBusiness\nHow Search works\nCarbon neutral since 2007\nPrivacy\nTerms\nSettings\n"
😆you can see the interop code here: https://github.com/babashka/sci/blob/master/src/sci/impl/interop.cljc
or [k]
with k = "documents", perhaps they optimize that direct string access to some special accessor or so
@zane Perhaps you can run with a local/root sci and then insert some debugging statements in there
I've tried and added debugging here:
(js/console.log "getting field name" field-name)
(aget obj field-name)]
:
$ osascript -l JavaScript out/main.js '(def x (.-documents (js/Application "Safari"))) x'
:hello
:args #js [(def x (.-documents (js/Application "Safari"))) x]
out/main.js: execution error: Error: Error: Can't convert types. (-1700)
no output so perhaps it's happening already with the static field access of the global object
$ osascript -l JavaScript out/main.js '(def x (js/Application "Safari")) x'
:hello
:args #js [(def x (js/Application "Safari")) x]
invoking method Application
Application("Safari")
$ osascript -l JavaScript out/main.js '(def x (.-documents (js/Application "Safari"))) x'
:hello
:args #js [(def x (.-documents (js/Application "Safari"))) x]
invoking method Application
getting field name documents
out/main.js: execution error: Error: Error: Can't convert types. (-1700)
printing the type of the resulting field value:
$ osascript -l JavaScript out/main.js '(def x (.-documents (js/Application "Safari"))) x'
:hello
:args #js [(def x (.-documents (js/Application "Safari"))) x]
invoking method Application
res function Object() {
[native code]
}
$ osascript -l JavaScript out/main.js '(def x (.-documents (js/Application "Safari"))) x'
:hello
:args #js [(def x (.-documents (js/Application "Safari"))) x]
invoking method Application
res function Object() {
[native code]
}
getting field name documents
res function Object() {
[native code]
}
out/main.js: execution error: Error: Error: Can't convert types. (-1700)
❯ ./out/obb -e '(.-documents (js/Application "Safari"))'
Application("Safari").documents
That does work. But note:
$ osascript -l JavaScript out/main.js '(def x (.-documents (js/Application "Safari"))) (prn :foo) (def doc (aget x 0)) (prn :dude) (prn (.url doc))'
:hello
:args #js [(def x (.-documents (js/Application "Safari"))) (prn :foo) (def doc (aget x 0)) (prn :dude) (prn (.url doc))]
invoking method Application
res function Object() {
[native code]
}
getting field name documents
res function Object() {
[native code]
}
:foo
out/main.js: execution error: Error: #error {:message "Can't convert types.", :data {:type :sci/error, :line 1, :column 69, :message "Can't convert types.", :sci.impl/callstack #object[cljs.core.Volatile {:val ({:line 1, :column 69, :ns #object[sci.impl.vars.SciNamespace], :file nil, :sci.impl/f-meta {:ns #object[sci.impl.vars.SciNamespace], :name aget, :sci/built-in true, :arglists ([array idx] [array idx & idxs]), :doc "Returns the value at the index/indices. Works on JavaScript arrays.", :sci.impl/inlined #object[Function]}})}], :file nil, :locals {}}, :cause #object[Error Error: Can't convert types.]} (-2700)
Instead of
(set! (.-run goog.global) run)
try just appending
function run(args) { return namespace.of.run(args); }
❯ ./out/obb -e '(-> (js/Application "Safari") (.-documents) (aget 0))'
Application("Safari").(0)
It’s only this that fails for me now:
❯ ./out/obb -e '(-> (js/Application "Safari") (.-documents) (aget 0) (.url))'
./out/obb: execution error: Error: Error: Can't convert types. (-1700)
$ osascript -l JavaScript out/main.js '(-> (js/Application "Safari") (.-documents) (aget 0))'
:hello
:args #js [(-> (js/Application "Safari") (.-documents) (aget 0))]
invoking method Application
res function Object() {
[native code]
}
getting field name documents
res function Object() {
[native code]
}
Application("Safari").(0)
You can deref the “lens” by applying the value. e.g. In the osascript
REPL.
>> Application("Safari").documents[0]()
=> Application("Safari").documents.byName("Google")
I think it should be possible to patch Sci to special case instances of js/ObjectSpecifier
. I’m just not where to apply the patch yet.
Key insights are that object specifiers don’t have .apply
, and will crash if map?
is called on them.
❯ out/obb -e '(-> (js/Application "Safari") (.-documents) (aget 0) (.url))'
favorites://
btw I think you're doing one thing too many here:
+ (aget obj field-name)
+ (gobject/get obj field-name)]
Do you have thoughts about how it should relate to Babashka? e.g I’d be happy to use a different name if you’d prefer.
btw, I think you might get away with a (set! ...)
to replace the existing SCI impl if you don't want to fork.
I'm fine with obb, but perhaps you can come up with a better name, either way I'm fine with it
I've also used the patching technique here: https://github.com/oxalorg/4ever-clojure/blob/80963a2e92321e5b8439e87d36c392e740592788/src/app/sci.cljs#L26
That is for preventing the browser to hang. Just be aware you're relying on impl details, but as long as you have tests and compile with a specific version of SCI, it should be fine
If you find there are (performance) improvements to be made in the interop, feel free to provide feedback.
“Is my video enabled on my Zoom call?”
#!/path/to/obb
(-> (js/Application "System Events")
.-processes
(.byName "")
.-menuBars
(aget 0)
.-menuBarItems
(.byName "Meeting")
.-menus
(aget 0)
.-menuItems
(.byName "Stop Video")
.exists)
Sure thing! Perhaps it would make sense to host this under the babashka organization with you as the main admin/maintainer? I would be ok with this. But also fine if you want to host under your personal account.
@U04V15CAJ I think that hosting it under the babashka organization sounds great! Let’s keep the “obb” name. I like the symmetry with nbb
.
Also, candidly, I don’t have a ton of experience maintaining open-source projects. I’d be grateful any advice or mentorship you’d care to provide.
Possibly stupid question - but does a default sci
environment have any IO access, or is it completely pure?
Seeing that access to stdin/stiout must be provided explicitly (https://github.com/babashka/sci#stdout-and-stdin) I'm inclinded to believe that the default env is pure. But I'd like to be sure :)