mfikes / ambly Goto Github PK
View Code? Open in Web Editor NEWClojureScript REPL into embedded JavaScriptCore
Home Page: http://ambly.fikesfarm.com
License: Eclipse Public License 1.0
ClojureScript REPL into embedded JavaScriptCore
Home Page: http://ambly.fikesfarm.com
License: Eclipse Public License 1.0
ClojureScript:cljs.user> :cljs/quit
NullPointerException ambly.repl.jsc.JscEnv (jsc.clj:144)
@swannodette Considering a potential enhancement. Could use feedback. Low priority IMHO.
A user could define functions in the REPL that call into functions that are defined in source and then call those REPL-defined functions. If an exception occurs, JSC will emit only the function names in frames for REPL-defined functions.
The Clojure REPL supports this, using NO_SOURCE_FILE
and line 1.
To support this would require minor changes to Ambly, and some changes to the underlying ClojureScript REPL as well.
Here is a concrete example showing that it almost works:
In actual source, I have:
(ns hello-ambly.core)
;; some lines have been deleted from here for brevity,
;; so stack line numbers below are off
(defn test-exception-two []
(ffirst 1))
(defn test-exception-one []
(test-exception-two))
And in the Ambly REPL:
(require 'hello-ambly.core)
(defn wrap-two [] (hello-ambly.core/test-exception-one))
(defn wrap-one [] (wrap-two))
Here is it nearly working:
ClojureScript:cljs.user> (wrap-one)
Error: 1 is not ISeqable
cljs.core/seq (out/cljs/core.cljs:727:13)
cljs.core/first (out/cljs/core.cljs:736:7)
cljs.core/ffirst (out/cljs/core.cljs:1155:3)
hello-ambly.core/test-exception-two (out/hello_ambly/core.cljs:16:3)
hello-ambly.core/test-exception-one (out/hello_ambly/core.cljs:19:3)
wrap_two (out/NO_SOURCE_FILE:1:1)
wrap_one (out/NO_SOURCE_FILE:1:1)
nil
The two things wrong:
wrap_one
should be resolved to cljs.user/wrap-one
NO_SOURCE_FILE
needs a little special handling to prevent code assuming it is a file in the :output-dir
: out/NO_SOURCE_FILE
.I did the above by changing Ambly's parser a little to emit:
[{:file "cljs/core.js", :function "seq", :line 4212, :column 17}
{:file "cljs/core.js", :function "first", :line 4242, :column 22}
{:file "cljs/core.js", :function "ffirst", :line 5292, :column 39}
{:file "hello_ambly/core.js", :function "test_exception_two", :line 19, :column 29}
{:file "hello_ambly/core.js", :function "test_exception_one", :line 22, :column 48}
{:file "NO_SOURCE_FILE", :function "wrap_two", :line 1, :column 1}
{:file "NO_SOURCE_FILE", :function "wrap_one", :line 1, :column 1}]
(defn stack-line->canonical-frame
"Parses a stack line into a frame representation, returning nil
if parse failed."
[stack-line opts]
(let [[function file line column]
(rest (re-matches #"(.*)@file://(.*):([0-9]+):([0-9]+)"
stack-line))]
(if-not (#{"" "global code"} stack-line)
{:file (if file
(string/replace
(.getCanonicalFile (io/file file))
(str (System/getProperty "user.dir") File/separator
(util/output-directory opts) File/separator)
"")
"NO_SOURCE_FILE")
:function (or function (string/trim stack-line))
:line (if line (Long/parseLong line) 1)
:column (if column (Long/parseLong column) 1)})))
This is a fairly lengthy description and probably needs some hammock time on whether to even pursue it, so I figured I'd drop it in an enhancement ticket here.
ClojureScript:cljs.user> (range 0 100000)
java.io.EOFException: JSON error (end-of-file inside string)
at clojure.data.json$read_quoted_string.invoke(json.clj:137)
at clojure.data.json$_read.invoke(json.clj:193)
at clojure.data.json$read_object.invoke(json.clj:93)
at clojure.data.json$_read.invoke(json.clj:218)
at clojure.data.json$read.doInvoke(json.clj:270)
at clojure.lang.RestFn.applyTo(RestFn.java:139)
at clojure.core$apply.invoke(core.clj:626)
at clojure.data.json$read_str.doInvoke(json.clj:276)
at clojure.lang.RestFn.invoke(RestFn.java:439)
at ambly.repl.jsc$jsc_eval.invoke(jsc.clj:48)
at ambly.repl.jsc.JscEnv._evaluate(jsc.clj:141)
at cljs.repl$evaluate_form.invoke(repl.clj:205)
at cljs.repl$evaluate_form.invoke(repl.clj:167)
at cljs.repl$eval_and_print.invoke(repl.clj:258)
at cljs.repl$repl_STAR_.invoke(repl.clj:422)
at user$eval3590.invoke(form-init6893156465712411744.clj:4)
at clojure.lang.Compiler.eval(Compiler.java:6703)
at clojure.lang.Compiler.eval(Compiler.java:6666)
at clojure.core$eval.invoke(core.clj:2927)
at clojure.main$repl$read_eval_print__6625$fn__6628.invoke(main.clj:239)
at clojure.main$repl$read_eval_print__6625.invoke(main.clj:239)
at clojure.main$repl$fn__6634.invoke(main.clj:257)
at clojure.main$repl.doInvoke(main.clj:257)
at clojure.lang.RestFn.invoke(RestFn.java:421)
at clojure.main$repl_opt.invoke(main.clj:323)
at clojure.main$main.doInvoke(main.clj:421)
at clojure.lang.RestFn.invoke(RestFn.java:397)
at clojure.lang.Var.invoke(Var.java:375)
at clojure.lang.AFn.applyToHelper(AFn.java:152)
at clojure.lang.Var.applyTo(Var.java:700)
at clojure.main.main(main.java:37)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:483)
at clojure.lang.Reflector.invokeMatchingMethod(Reflector.java:93)
at clojure.lang.Reflector.invokeStaticMethod(Reflector.java:207)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:483)
at clojure.lang.Reflector.invokeMatchingMethod(Reflector.java:93)
at clojure.lang.Reflector.invokeStaticMethod(Reflector.java:207)
at user$eval5.invoke(form-init6893156465712411744.clj:1)
at clojure.lang.Compiler.eval(Compiler.java:6703)
at clojure.lang.Compiler.eval(Compiler.java:6693)
at clojure.lang.Compiler.load(Compiler.java:7130)
at clojure.lang.Compiler.loadFile(Compiler.java:7086)
at clojure.main$load_script.invoke(main.clj:274)
at clojure.main$init_opt.invoke(main.clj:279)
at clojure.main$initialize.invoke(main.clj:307)
at clojure.main$null_opt.invoke(main.clj:342)
at clojure.main$main.doInvoke(main.clj:420)
at clojure.lang.RestFn.invoke(RestFn.java:421)
at clojure.lang.Var.invoke(Var.java:383)
at clojure.lang.AFn.applyToHelper(AFn.java:156)
at clojure.lang.Var.applyTo(Var.java:700)
at clojure.main.main(main.java:37)
java.io.EOFException: JSON error (end-of-file inside string)
nil
From the README:
NOTE: ClojureScript master is currently required. (You will need to get the latest, build it, and update the project.clj file in ambly/Clojure to refer to your locally-built copy.)
My REPL seems to run fine out of the box without any locally-built CLJS master build that I know of.
Bonjour could discover a few available targets. Currently the code simply picks the first and connects to it. Provide a way to let the user choose a discovered device, and a way to refresh, looking for more.
ClojureScript has source map support at runtime on the client or this can be done on the server. Server side support needs a tiny push. This is probably the right way to do it. Then any JavaScript environment that's properly bootstrapped can send the stacktrace and the ClojureScript REPL can map it for you. This should probably be the default and be disable-able for environment likes Node.js that already have good source mapping support.
Logging the set of require
calls when firing up the REPL shows that some JavaScript files are being required multiple times (see cljs/core.js
and goog/string/string.js
repeated for example):
2015-02-04 11:45:02.463 Ambly Demo[2688:69160] require: /Users/mfikes/Documents/Projects/ambly/Clojure/out/goog/base.js
2015-02-04 11:45:02.466 Ambly Demo[2688:69160] require: /Users/mfikes/Documents/Projects/ambly/Clojure/out/goog/deps.js
2015-02-04 11:45:02.493 Ambly Demo[2688:69160] require: /Users/mfikes/Documents/Projects/ambly/Clojure/out/ambly_repl_deps.js
2015-02-04 11:45:02.509 Ambly Demo[2688:69160] require: /Users/mfikes/Documents/Projects/ambly/Clojure/out/goog/../cljs/core.js
2015-02-04 11:45:02.545 Ambly Demo[2688:69160] require: /Users/mfikes/Documents/Projects/ambly/Clojure/out/goog/string/stringbuffer.js
2015-02-04 11:45:02.545 Ambly Demo[2688:69160] require: /Users/mfikes/Documents/Projects/ambly/Clojure/out/goog/array/array.js
2015-02-04 11:45:02.546 Ambly Demo[2688:69160] require: /Users/mfikes/Documents/Projects/ambly/Clojure/out/goog/asserts/asserts.js
2015-02-04 11:45:02.547 Ambly Demo[2688:69160] require: /Users/mfikes/Documents/Projects/ambly/Clojure/out/goog/debug/error.js
2015-02-04 11:45:02.547 Ambly Demo[2688:69160] require: /Users/mfikes/Documents/Projects/ambly/Clojure/out/goog/dom/nodetype.js
2015-02-04 11:45:02.547 Ambly Demo[2688:69160] require: /Users/mfikes/Documents/Projects/ambly/Clojure/out/goog/string/string.js
2015-02-04 11:45:02.549 Ambly Demo[2688:69160] require: /Users/mfikes/Documents/Projects/ambly/Clojure/out/goog/object/object.js
2015-02-04 11:45:02.550 Ambly Demo[2688:69160] require: /Users/mfikes/Documents/Projects/ambly/Clojure/out/goog/string/string.js
2015-02-04 11:45:02.652 Ambly Demo[2688:69160] require: /Users/mfikes/Documents/Projects/ambly/Clojure/out/goog/../cljs/core.js
2015-02-04 11:45:02.657 Ambly Demo[2688:69160] require: /Users/mfikes/Documents/Projects/ambly/Clojure/out/goog/string/stringbuffer.js
2015-02-04 11:45:02.657 Ambly Demo[2688:69160] require: /Users/mfikes/Documents/Projects/ambly/Clojure/out/goog/array/array.js
2015-02-04 11:45:02.658 Ambly Demo[2688:69160] require: /Users/mfikes/Documents/Projects/ambly/Clojure/out/goog/asserts/asserts.js
2015-02-04 11:45:02.658 Ambly Demo[2688:69160] require: /Users/mfikes/Documents/Projects/ambly/Clojure/out/goog/debug/error.js
2015-02-04 11:45:02.658 Ambly Demo[2688:69160] require: /Users/mfikes/Documents/Projects/ambly/Clojure/out/goog/dom/nodetype.js
2015-02-04 11:45:02.659 Ambly Demo[2688:69160] require: /Users/mfikes/Documents/Projects/ambly/Clojure/out/goog/string/string.js
2015-02-04 11:45:02.659 Ambly Demo[2688:69160] require: /Users/mfikes/Documents/Projects/ambly/Clojure/out/goog/object/object.js
2015-02-04 11:45:02.668 Ambly Demo[2688:69160] require: /Users/mfikes/Documents/Projects/ambly/Clojure/out/goog/../cljs/repl.js
Something I did caused this to break.
Currently the code unconditionally returns true.
Ambly doesn't currently work in Cursive. It'd be nice to sort out why/how.
Starting nREPL server...
/Library/Java/JavaVirtualMachines/jdk1.7.0_71.jdk/Contents/Home/bin/java -Dclojure.compile.path=/Users/mfikes/Documents/Projects/ambly/Clojure/target/classes -Dambly.version=0.1.0-SNAPSHOT -Dfile.encoding=UTF-8 -Dclojure.debug=false -Didea.launcher.port=7533 "-Didea.launcher.bin.path=/Applications/IntelliJ IDEA 14 CE.app/Contents/bin" -classpath <long classpath elided> com.intellij.rt.execution.application.AppMain clojure.main -i /private/var/folders/m0/161fm8fx069fk_fny08nrmkm0000gp/T/form-init8814029275895923040.clj
Connecting to local nREPL server...
Clojure 1.6.0
nREPL server started on port 52260 on host 127.0.0.1
(require
'[cljs.repl :as repl]
'[ambly.repl.jsc :as jsc])
=> nil
(repl/repl* (jsc/repl-env)
{:output-dir "out"
:optimizations :none
:cache-analysis true
:source-map true})
To quit, type: :cljs/quit
ClojureScript:cljs.user> DEBUG: unknown status need-input
If you don't have the Ambly Demo app running you will instead get evidence that -startup
is being called:
Starting nREPL server...
/Library/Java/JavaVirtualMachines/jdk1.7.0_71.jdk/Contents/Home/bin/java -Dclojure.compile.path=/Users/mfikes/Documents/Projects/ambly/Clojure/target/classes -Dambly.version=0.1.0-SNAPSHOT -Dfile.encoding=UTF-8 -Dclojure.debug=false -Didea.launcher.port=7534 "-Didea.launcher.bin.path=/Applications/IntelliJ IDEA 14 CE.app/Contents/bin" -classpath <long classpath elided> com.intellij.rt.execution.application.AppMain clojure.main -i /private/var/folders/m0/161fm8fx069fk_fny08nrmkm0000gp/T/form-init5516241156361831447.clj
Connecting to local nREPL server...
Clojure 1.6.0
nREPL server started on port 52292 on host 127.0.0.1
(require
'[cljs.repl :as repl]
'[ambly.repl.jsc :as jsc])
=> nil
(repl/repl* (jsc/repl-env)
{:output-dir "out"
:optimizations :none
:cache-analysis true
:source-map true})
To quit, type: :cljs/quit
ConnectException Connection refused java.net.PlainSocketImpl.socketConnect (PlainSocketImpl.java:-2)
I think this is rooted in nREPL in some way.
[org.clojure/core.logic "0.8.8"]
to project.clj
:dependencies
script/jscrepljs
(require 'cljs.core.logic)
(ns-interns 'cljs.core.logic)
You will be rewarded with a crash:
2015-02-20 21:01:07.351 Ambly Demo[6849:918387] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[NSConcreteMutableData initWithBytes:length:copy:deallocator:]: absurd length: 4294964216, maximum size: 2147483648 bytes'
*** First throw call stack:
(0x2a173137 0x38253c77 0x2a17307d 0x2ae76edf 0x2ae694ef 0x2ae6949b 0x2ae8ed8f 0xd49b7 0xd4e0d 0x2a0e94c7 0x2a0e9421 0x2a13923f 0x2a138653 0x2a136cd1 0x2a082a31 0x2a082843 0x31a2e1a9 0x2d9c560d 0xd6b69 0x3881faaf)
libc++abi.dylib: terminating with uncaught exception of type NSException
See clojure/clojurescript@41f6aa9
IRC 2015-02-19:
[13:17:36] <dnolen> brucehauman: tjakubow: mfikes: one thing to be aware of the coming word, you cannot just call print/println or flush anymore since you don't know under what context the REPL is being used
[13:17:50] <dnolen> opts will always contain the :print and :flush fn that you should use
Also, :print-no-newline
: clojure/clojurescript@7beec09
With #7 I inadvertently added an overly-aggressive load guard causing a given file to only be loaded at most once.
In particular, when
(require 'my.namespace :reload)
is issued, the following JavaScript is evaluated:
goog.require('my.namespace', true);
and the guard needs to be relaxed in the case that the true
parameter is passed.
A typical sequence looks like
orion:Clojure mfikes$ script/jscrepljs
To quit, type: :cljs/quit
[1] Mike's iPod touch
[2] Mike’s Light Grey iPad
[3] iPhone Simulator (Kidss-iMac-2-local)
[R] Refresh
Choice: 3
Connecting to iPhone Simulator (Kidss-iMac-2-local) ...
ClojureScript:cljs.user> (+ 3 4)
7
ClojureScript:cljs.user>
It would probably be better if the To quit, type: :cljs/quit
message appeared after the connection was made, right before the prompt. (Perhaps this is a simple matter of revising the ClojureScript cljs.repl
to print the message upon -setup
completing instead of before calling it.)
Squelch log lines like
Feb 17, 2015 8:09:38 AM javax.jmdns.impl.DNSIncoming readAnswer
WARNING: There was an OPT answer. Not currently handled. Option code: 65002 data: 7D59A55647530597
They are benign noise.
If I set up a project making use of Ambly, and have lein cljsbuild auto dev
running, and define a new symbol in a namespace in my project, say
(defn my-square [x] (* x x))
and then (require 'my.namespace :reload)
then my.namespace/my-square
works (the square is calculated and printed), but I get an undefined Var warn:
ClojureScript:cljs.user> (my.namespace/my-square 3)
WARNING: Use of undeclared Var my.namespace/my-square at line 1 <cljs repl>
9
I can see that -tear-down
is properly unmounting the WebDAV share; perhaps it is blocking on closing the socket or some daemon thread is still running.
While the solution to #8 works, it blocks the sending thread. Instead revise the code to make use of async callback that lets us know when space is available.
Currently a hard-coded :output-dir
is in the code.
This is an issue that warrants some thought, so design page is here.
If you are in the Bonjour discovery phase of starting the Ambly REPL and then go to AirDrop in your finder, you will see the REPL log lots of SEVERE
warnings such as below:
7c0: b196b7cd3bfb7daf d722829b2d52b766 57efe00dd5119043 7715db60b2386bac ....;.}. ."..-R.f W......C w..`.8k.
7e0: 9a60b49e4784640b 02a4088e468ecbbd c6c0f2701cd64071 a08e1cd776dae1c7 .`..G.d. ....F... ...p..@q ....v...
....
Feb 17, 2015 10:39:40 PM javax.jmdns.impl.DNSIncoming readAnswer
SEVERE: Could not find record class. domain: ϿϿϿϿϿϿϿϿ. type: TYPE_IGNORE index 0
dns[response,10.0.1.200:5353, length=7540, id=0x0, flags=0x8400:r:aa, answers=5
answers:
[Text@1214942492 type: TYPE_TXT index 16, class: CLASS_IN index 1-unique, name: orion._device-info._tcp.local. ttl: '4499/4500' text: 'model=MacPro5,1
...']
[Service@1263947368 type: TYPE_SRV index 33, class: CLASS_IN index 1-unique, name: 3e8fc7bc7ca1._airdrop._tcp.local. ttl: '4499/4500' server: 'orion.local.:8770']
When in Cursive, the call to (read-line)
results in DEBUG: unknown status need-input
. For now, I've added a provision to simply use the first discovered device, but this is less than satisfactory because it essentially means you can't use the menu in Cursive.
If you make use of a timeout
(js/setTimeout (fn [] (print "hello")) 3000)
The functionality appears to work, but this gets logged in Xcode
2015-02-05 15:29:56.375 Ambly Demo[6630:130851] [undefined:8:24] ReferenceError: Can't find variable: nil
runTimeout
Perhaps the timeout mechanism I injected into JSC has something amiss with its JavaScript.
For CLJS-1025 need to pass file path relative to the project root. (Currently fully-qualified path is passed.)
I tried Ambly in my corporate work environment and saw strange devices as candidates. It turns out that JmDNS resolves other names that we simply need to filter out as well.
With the change for #26, which involved (shutdown-agents)
, if you run the REPL manually, then after :cljs/quit
you can't reconnect.
If using lein trampoline run -m clojure.main
you will get the following when trying to choose a device to connect to,
RejectedExecutionException Task java.util.concurrent.FutureTask@324e8baa rejected from java.util.concurrent.ThreadPoolExecutor@14df5253[Terminated, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 5] java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution (ThreadPoolExecutor.java:2047)
and if you are running plain lein repl
you will get this when trying to execute (repl/repl* ...)
SocketException The transport's socket appears to have lost its connection to the nREPL server
clojure.tools.nrepl.transport/bencode/fn--5570/fn--5571 (transport.clj:95)
clojure.tools.nrepl.transport/bencode/fn--5570 (transport.clj:95)
clojure.tools.nrepl.transport/fn-transport/fn--5544 (transport.clj:42)
clojure.core/binding-conveyor-fn/fn--4145 (core.clj:1910)
java.util.concurrent.FutureTask.run (FutureTask.java:266)
java.util.concurrent.ThreadPoolExecutor.runWorker (ThreadPoolExecutor.java:1142)
java.util.concurrent.ThreadPoolExecutor$Worker.run (ThreadPoolExecutor.java:617)
java.lang.Thread.run (Thread.java:745)
If you remove the call to (shutdown-agents)
then both of the errors above go away and you can reconnect.
[com.cemerick/piggieback "0.1.5"]
to your project dependencies.:repl-options {:nrepl-middleware [cemerick.piggieback/wrap-cljs-repl]}
to your project.clj
.(require
'[cljs.repl :as repl]
'[cemerick.piggieback]
'[ambly.repl.jsc :as jsc])
(cemerick.piggieback/cljs-repl
:repl-env (jsc/repl-env)
:optimizations :none
:cache-analysis true
:source-map true)
Then edit a file in foo.bar
namespace and issue (require 'foo.bar :reload)
. You won't see new def
s.
Perhaps something with the auto-configuration of :output-dir
with nREPL needs to be looked at.
Currently no checks are made to see if TCP can listen successfully. The code then proceeds to set up WebDAV, advertising via Bonjour. Then if your REPL connects, you will get "connection refused".
Instead, error handling should be done, with the result being that Bonjour is not even advertised in this broken state.
ClojureScript:cljs.user> (println "ab")
Error: No *print-fn* fn set for evaluation environment
You see:
2015-02-05 00:31:24.149 Ambly Demo[6783:182464] no buffer!
This is probably fixable by checking for len == -1
here.
Ctrl-D is a known issue with ClojureScript REPLs. But Ctrl-C not working and looping is problematic. There is no way to exit the REPL if you make a mistake.
Connect a REPL to a device and then connect a second to the same. The second will usurp the first, leaving the first with a hung REPL state.
Ideally we would do one of
Test case in Cursive:
(ffirst 1)
Error: 1 is not ISeqable
seq@file:///cljs/core.js:4212:17
first@file:///cljs/core.js:4242:22
ffirst@file:///cljs/core.js:5292:39
So far, we've focused largely on bootstrapping the REPL in a way that support the REPL itself. Need to consider how the hosting app starts up and how that affects Ambly.
With #50 the changes made broke exception-handling when using the REPL. For example (ffirst 1)
doesn't result in a source-mapped stacktrace because custom :caught
handler is being used.
Adding :caught repl/repl-caught
to :merge-opts
doesn't fix things.
Should investigate whatever is provided to JSContext
for catching errors globally and preventing the REPL from crashing completely.
If you bring a laptop running a simulator into a new network, or similarly bring a device into a new Wi-Fi, have the Objective-C code detect this, reestablish listening on TCP and WebDAV, and advertise new info for that service.
On the REPL Clojure side, support reconnecting when a connection is lost, but more than trying the same IP, ports, look at the new advertisement.
The current implementation of require
simply reads from a fully-qualified filename on the hosting OS X filesystem, which wouldn't work on a real device.
It currently appears that all of the files that get required come from the output-dir in the project, so perhaps one approach would involve having the require
implementation essentially fetch across the TCP socket used by the REPL.
Another approach is DropBox.
Interestingly, React Native's strategy for on-device dev hasn't been fully sorted out (but can work now because it has the app connect to the packaging server via TCP).
To reproduce, start up simulator, and start up REPL and let simulator be discovered. Now quit simulator and try to connect. You will see that exceptions are printed but the cljs.repl
code will move and try to call -load
At this point the REPL is useless, but the prompt comes up, instead of it exiting as it did previously. This appears to be the result of repl-caught
not re-throwing. Perhaps we can simply use this hook to define a new handler so that a throw from -setup will cause REPL startup termination.
Mike-Fikess-MacBook-Pro:Clojure mfikes$ script/jscrepljs
To quit, type: :cljs/quit
[1] iPhone Simulator (Mike-Fikess-MacBook-Pro-local)
[R] Refresh
Choice: 1
Connecting to iPhone Simulator (Mike-Fikess-MacBook-Pro-local) ...
java.net.ConnectException: Connection refused
at java.net.PlainSocketImpl.socketConnect(Native Method)
at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:345)
at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206)
at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188)
at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392)
at java.net.Socket.connect(Socket.java:589)
at java.net.Socket.connect(Socket.java:538)
at java.net.Socket.<init>(Socket.java:434)
at java.net.Socket.<init>(Socket.java:211)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:408)
at clojure.lang.Reflector.invokeConstructor(Reflector.java:180)
at ambly.repl.jsc$socket.invoke(jsc.clj:89)
at ambly.repl.jsc$setup.invoke(jsc.clj:212)
at ambly.repl.jsc.JscEnv._setup(jsc.clj:283)
at cljs.repl$repl_STAR_$fn__3796.invoke(repl.clj:629)
at cljs.repl$repl_STAR_.invoke(repl.clj:628)
at user$eval3937.invoke(form-init5327366463415825986.clj:3)
at clojure.lang.Compiler.eval(Compiler.java:6703)
at clojure.lang.Compiler.eval(Compiler.java:6666)
at clojure.core$eval.invoke(core.clj:2927)
at clojure.main$eval_opt.invoke(main.clj:288)
at clojure.main$initialize.invoke(main.clj:307)
at clojure.main$null_opt.invoke(main.clj:342)
at clojure.main$main.doInvoke(main.clj:420)
at clojure.lang.RestFn.invoke(RestFn.java:421)
at clojure.lang.Var.invoke(Var.java:383)
at clojure.lang.AFn.applyToHelper(AFn.java:156)
at clojure.lang.Var.applyTo(Var.java:700)
at clojure.main.main(main.java:37)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:483)
at clojure.lang.Reflector.invokeMatchingMethod(Reflector.java:93)
at clojure.lang.Reflector.invokeStaticMethod(Reflector.java:207)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:483)
at clojure.lang.Reflector.invokeMatchingMethod(Reflector.java:93)
at clojure.lang.Reflector.invokeStaticMethod(Reflector.java:207)
at user$eval5.invoke(form-init5327366463415825986.clj:1)
at clojure.lang.Compiler.eval(Compiler.java:6703)
at clojure.lang.Compiler.eval(Compiler.java:6693)
at clojure.lang.Compiler.load(Compiler.java:7130)
at clojure.lang.Compiler.loadFile(Compiler.java:7086)
at clojure.main$load_script.invoke(main.clj:274)
at clojure.main$init_opt.invoke(main.clj:279)
at clojure.main$initialize.invoke(main.clj:307)
at clojure.main$null_opt.invoke(main.clj:342)
at clojure.main$main.doInvoke(main.clj:420)
at clojure.lang.RestFn.invoke(RestFn.java:421)
at clojure.lang.Var.invoke(Var.java:383)
at clojure.lang.AFn.applyToHelper(AFn.java:156)
at clojure.lang.Var.applyTo(Var.java:700)
at clojure.main.main(main.java:37)
java.lang.NullPointerException
at ambly.repl.jsc$write.invoke(jsc.clj:99)
at ambly.repl.jsc$jsc_eval.invoke(jsc.clj:164)
at ambly.repl.jsc$load_javascript.invoke(jsc.clj:181)
at ambly.repl.jsc.JscEnv._load(jsc.clj:287)
at cljs.repl$load_namespace.invoke(repl.clj:165)
at cljs.repl$load_dependencies.invoke(repl.clj:171)
at cljs.repl$evaluate_form.invoke(repl.clj:373)
at cljs.repl$repl_STAR_$fn__3805$fn__3806.invoke(repl.clj:659)
at cljs.repl$repl_STAR_$fn__3805.invoke(repl.clj:655)
at cljs.compiler$with_core_cljs.invoke(compiler.clj:912)
at cljs.repl$repl_STAR_.invoke(repl.clj:653)
at user$eval3937.invoke(form-init5327366463415825986.clj:3)
at clojure.lang.Compiler.eval(Compiler.java:6703)
at clojure.lang.Compiler.eval(Compiler.java:6666)
at clojure.core$eval.invoke(core.clj:2927)
at clojure.main$eval_opt.invoke(main.clj:288)
at clojure.main$initialize.invoke(main.clj:307)
at clojure.main$null_opt.invoke(main.clj:342)
at clojure.main$main.doInvoke(main.clj:420)
at clojure.lang.RestFn.invoke(RestFn.java:421)
at clojure.lang.Var.invoke(Var.java:383)
at clojure.lang.AFn.applyToHelper(AFn.java:156)
at clojure.lang.Var.applyTo(Var.java:700)
at clojure.main.main(main.java:37)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:483)
at clojure.lang.Reflector.invokeMatchingMethod(Reflector.java:93)
at clojure.lang.Reflector.invokeStaticMethod(Reflector.java:207)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:483)
at clojure.lang.Reflector.invokeMatchingMethod(Reflector.java:93)
at clojure.lang.Reflector.invokeStaticMethod(Reflector.java:207)
at user$eval5.invoke(form-init5327366463415825986.clj:1)
at clojure.lang.Compiler.eval(Compiler.java:6703)
at clojure.lang.Compiler.eval(Compiler.java:6693)
at clojure.lang.Compiler.load(Compiler.java:7130)
at clojure.lang.Compiler.loadFile(Compiler.java:7086)
at clojure.main$load_script.invoke(main.clj:274)
at clojure.main$init_opt.invoke(main.clj:279)
at clojure.main$initialize.invoke(main.clj:307)
at clojure.main$null_opt.invoke(main.clj:342)
at clojure.main$main.doInvoke(main.clj:420)
at clojure.lang.RestFn.invoke(RestFn.java:421)
at clojure.lang.Var.invoke(Var.java:383)
at clojure.lang.AFn.applyToHelper(AFn.java:156)
at clojure.lang.Var.applyTo(Var.java:700)
at clojure.main.main(main.java:37)
ClojureScript:cljs.user>
If I use clojure/clojurescript@9edde91 and start up REPL in Cursive (using Piggieback), and then type foo
, I get:
foo
WARNING: Use of undeclared Var /foo at line 1 <cljs repl>
SyntaxError: Unexpected token '.'
undefined
But with ClojureScript master, I get:
foo
WARNING: Use of undeclared Var /foo at line 1 <cljs repl>
NullPointerException cljs.repl/evaluate-form (repl.clj:391)
java.lang.NullPointerException
at cljs.repl$display_error.invoke(repl.clj:322)
at cljs.repl$evaluate_form.invoke(repl.clj:386)
at cljs.repl$evaluate_form.invoke(repl.clj:342)
at cemerick.piggieback$cljs_eval$fn__5012.invoke(piggieback.clj:116)
at clojure.lang.AFn.applyToHelper(AFn.java:152)
at clojure.lang.AFn.applyTo(AFn.java:144)
at clojure.core$apply.invoke(core.clj:624)
at clojure.core$with_bindings_STAR_.doInvoke(core.clj:1862)
at clojure.lang.RestFn.invoke(RestFn.java:425)
at cemerick.piggieback$cljs_eval.invoke(piggieback.clj:97)
at clojure.lang.AFn.applyToHelper(AFn.java:165)
at clojure.lang.AFn.applyTo(AFn.java:144)
at clojure.core$apply.invoke(core.clj:626)
at cemerick.piggieback$cljs_repl$fn__5058.doInvoke(piggieback.clj:180)
at clojure.lang.RestFn.invoke(RestFn.java:436)
at clojure.lang.Var.invoke(Var.java:388)
at cljs.user$eval13333.invoke(form-init4843336836104253482.clj:1)
at clojure.lang.Compiler.eval(Compiler.java:6703)
at clojure.lang.Compiler.eval(Compiler.java:6666)
at clojure.core$eval.invoke(core.clj:2927)
at clojure.main$repl$read_eval_print__6625$fn__6628.invoke(main.clj:239)
at clojure.main$repl$read_eval_print__6625.invoke(main.clj:239)
at clojure.main$repl$fn__6634.invoke(main.clj:257)
at clojure.main$repl.doInvoke(main.clj:257)
at clojure.lang.RestFn.invoke(RestFn.java:1096)
at clojure.tools.nrepl.middleware.interruptible_eval$evaluate$fn__591.invoke(interruptible_eval.clj:56)
at clojure.lang.AFn.applyToHelper(AFn.java:152)
at clojure.lang.AFn.applyTo(AFn.java:144)
at clojure.core$apply.invoke(core.clj:624)
at clojure.core$with_bindings_STAR_.doInvoke(core.clj:1862)
at clojure.lang.RestFn.invoke(RestFn.java:425)
at clojure.tools.nrepl.middleware.interruptible_eval$evaluate.invoke(interruptible_eval.clj:41)
at clojure.tools.nrepl.middleware.interruptible_eval$interruptible_eval$fn__632$fn__635.invoke(interruptible_eval.clj:171)
at clojure.core$comp$fn__4192.invoke(core.clj:2402)
at clojure.tools.nrepl.middleware.interruptible_eval$run_next$fn__625.invoke(interruptible_eval.clj:138)
at clojure.lang.AFn.run(AFn.java:22)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:745)
It should just exit but you get this:
orion:Clojure mfikes$ ./script/jscrepljs
To quit, type: :cljs/quit
[1] iPhone Simulator (orion-fikesfarm-com)
[R] Refresh
Choice: 1
Connecting to iPhone Simulator (orion-fikesfarm-com) ...
Exception in thread "main" java.net.ConnectException: Connection refused, compiling:(/private/var/folders/m0/161fm8fx069fk_fny08nrmkm0000gp/T/form-init3078156157478264228.clj:1:124)
at clojure.lang.Compiler.load(Compiler.java:7142)
at clojure.lang.Compiler.loadFile(Compiler.java:7086)
at clojure.main$load_script.invoke(main.clj:274)
at clojure.main$init_opt.invoke(main.clj:279)
at clojure.main$initialize.invoke(main.clj:307)
at clojure.main$null_opt.invoke(main.clj:342)
at clojure.main$main.doInvoke(main.clj:420)
at clojure.lang.RestFn.invoke(RestFn.java:421)
at clojure.lang.Var.invoke(Var.java:383)
at clojure.lang.AFn.applyToHelper(AFn.java:156)
at clojure.lang.Var.applyTo(Var.java:700)
at clojure.main.main(main.java:37)
Caused by: java.net.ConnectException: Connection refused
at java.net.PlainSocketImpl.socketConnect(Native Method)
at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:339)
at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:200)
at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:182)
at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392)
at java.net.Socket.connect(Socket.java:579)
at java.net.Socket.connect(Socket.java:528)
at java.net.Socket.<init>(Socket.java:425)
at java.net.Socket.<init>(Socket.java:208)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:526)
at clojure.lang.Reflector.invokeConstructor(Reflector.java:180)
at ambly.repl.jsc$socket.invoke(jsc.clj:89)
at ambly.repl.jsc$setup.invoke(jsc.clj:210)
at ambly.repl.jsc.JscEnv._setup(jsc.clj:278)
at cljs.repl$repl_STAR_.invoke(repl.clj:610)
at user$eval3926.invoke(form-init3078156157478264228.clj:3)
at clojure.lang.Compiler.eval(Compiler.java:6703)
at clojure.lang.Compiler.eval(Compiler.java:6666)
at clojure.core$eval.invoke(core.clj:2927)
at clojure.main$eval_opt.invoke(main.clj:288)
at clojure.main$initialize.invoke(main.clj:307)
at clojure.main$null_opt.invoke(main.clj:342)
at clojure.main$main.doInvoke(main.clj:420)
at clojure.lang.RestFn.invoke(RestFn.java:421)
at clojure.lang.Var.invoke(Var.java:383)
at clojure.lang.AFn.applyToHelper(AFn.java:156)
at clojure.lang.Var.applyTo(Var.java:700)
at clojure.main.main(main.java:37)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at clojure.lang.Reflector.invokeMatchingMethod(Reflector.java:93)
at clojure.lang.Reflector.invokeStaticMethod(Reflector.java:207)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at clojure.lang.Reflector.invokeMatchingMethod(Reflector.java:93)
at clojure.lang.Reflector.invokeStaticMethod(Reflector.java:207)
at user$eval5.invoke(form-init3078156157478264228.clj:1)
at clojure.lang.Compiler.eval(Compiler.java:6703)
at clojure.lang.Compiler.eval(Compiler.java:6693)
at clojure.lang.Compiler.load(Compiler.java:7130)
... 11 more
Run a copy of Ambly Demo on device. Connect to it, ensure it is working. :cljs/quit
Then put app into the background.
Then run script/jscrepljs
. App won't be listed as discovered (WebDAV impl does a good job of removing Bonjour advertisements when backgrounded). Then bring app back to foreground. Try to connect to it in REPL. Oftentimes you can't.
I think this is a fundamental lack of robustness to backgrounding in the TCP REPL code that could be improved.
Currently the context manager is a bit inflexible, creating a JSContext and unconditionally adding support for logging, timers, require
, etc.
To make things more reusable in broader contexts, the client app code should be able to control the creation of the JSContext and to opt into the things added into it.
I had a stale mount from an older copy of Ambly (because I had Ctrl-C'd out of the REPL).
Stale mounts appear to cause problems like this:
java.io.FileNotFoundException: /Volumes/Ambly-10.0.1.13/cljs/core.cljs (Input/output error)
at java.io.FileOutputStream.open(Native Method)
at java.io.FileOutputStream.<init>(FileOutputStream.java:213)
at clojure.java.io$fn__8704.invoke(io.clj:230)
at clojure.java.io$fn__8641$G__8604__8648.invoke(io.clj:69)
at clojure.java.io$fn__8678.invoke(io.clj:166)
at clojure.java.io$fn__8654$G__8608__8661.invoke(io.clj:69)
at clojure.java.io$writer.doInvoke(io.clj:119)
at clojure.lang.RestFn.invoke(RestFn.java:410)
at clojure.lang.AFn.applyToHelper(AFn.java:154)
at clojure.lang.RestFn.applyTo(RestFn.java:132)
at clojure.core$apply.invoke(core.clj:626)
at clojure.core$spit.doInvoke(core.clj:6403)
at clojure.lang.RestFn.invoke(RestFn.java:425)
at cljs.closure$jar_file_to_disk.invoke(closure.clj:372)
at cljs.closure$compile_from_jar.invoke(closure.clj:389)
at cljs.closure$eval3125$fn__3126.invoke(closure.clj:404)
at cljs.closure$eval3066$fn__3067$G__3057__3074.invoke(closure.clj:305)
at cljs.closure$compile.invoke(closure.clj:336)
at ambly.repl.jsc$setup.invoke(jsc.clj:220)
at ambly.repl.jsc.JscEnv._setup(jsc.clj:281)
at cljs.repl$repl_STAR_$fn__3796.invoke(repl.clj:629)
at cljs.repl$repl_STAR_.invoke(repl.clj:628)
There is currently a hard-coded path in JSContextManager reflecting the checkout path which needs to be eliminated.
A large sleep is currently in the code to avoid it. The odd thing is that I don't recall seeing this earlier.
I think that when you have Xcode connected to your device, the device doesn't sleep.
If disconnect the USB cable, you can actually still develop (at least the ClojureScript aspect), because of the use of TCP-connected REPL and WebDAV. And in this situation clearly the device can go to sleep within a minute or so.
So this ticket is to consider disabling sleep if developing in this mode.
Here is a reference
http://stackoverflow.com/questions/12661004/how-to-disable-enable-the-sleep-mode-programmatically-in-ios
Currently Ambly simply creates a context on the main thread, and all TCP messaging with the REPL is also done on the main thread.
Frameworks like React Native want the JSContext to be on a background thread, and if you look at RCTContextExecutor
you can see that it sets up an event dispatch thread loop. This could perhaps be supported by abstracting over how Ambly sends stuff into the JSContext to be evaluated (so that the app can cause REPL calls to simply be redirected to React Native's RCTContextExecutor
, for example).
Currently the demo app has some code in its application:didFinishLaunchingWithOptions:
.
Move this into some reusable startup façade in Ambly src
proper.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.