GithubHelp home page GithubHelp logo

mfikes / ambly Goto Github PK

View Code? Open in Web Editor NEW
541.0 541.0 21.0 621 KB

ClojureScript REPL into embedded JavaScriptCore

Home Page: http://ambly.fikesfarm.com

License: Eclipse Public License 1.0

Ruby 1.37% Objective-C 54.12% Clojure 44.51%

ambly's People

Contributors

elibdev avatar mfikes avatar pepasflo avatar sherbondy avatar swannodette avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ambly's Issues

Stackframes for REPL-defined functions

@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:

  1. wrap_one should be resolved to cljs.user/wrap-one
    2, 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.

source map support

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.

Transparently support IP address changes

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.

REPL runs fine without locally-built master ClojureScript

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.

Improve repeated `require` for same path

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

Put startup façade in Ambly src

Currently the demo app has some code in its application:didFinishLaunchingWithOptions:.

Move this into some reusable startup façade in Ambly src proper.

If error occurs in -setup, code moves on with REPL in undefined state

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> 

NPE on :cljs/quit

ClojureScript:cljs.user> :cljs/quit

NullPointerException   ambly.repl.jsc.JscEnv (jsc.clj:144)

Support REPL against real device, and sort out how `require` works

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).

Can crash iOS side with "absurd length"

  1. Add [org.clojure/core.logic "0.8.8"] to project.clj :dependencies
  2. Start up script/jscrepljs
  3. (require 'cljs.core.logic)
  4. (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

`:cljs/quit` message appears before REPL is live

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.)

After an error, the REPL dies

Should investigate whatever is provided to JSContext for catching errors globally and preventing the REPL from crashing completely.

AirDrop causes JmDNS to spew hex

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']

Neither Ctrl-C nor Ctrl-D exit the REPL

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.

A stale mount point can cause issues

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)

JmDNS warning about unhanded protocol message

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.

Provide a way to pick target device/simulator

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.

Warn on undeclared Var after :reload

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

Let app go to background and then foreground, can't connect

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.

Support multiple app instances on a device

  • Put the app name in the Bonjour advertisement.
  • Make the TCP port number be the WebDAV port + 1
  • When picking WebDAV port, skip forward until we can find a port (and subsequent) available for listening

Support JSContext being on another thread

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).

REPL hangs when connection refused

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

NPE printing error while in Cursive

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)

Allow client code to control JSContext and what's added to it

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.

If second REPL connect to device, first connection hangs

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

  • prevent the second connection
  • allow the second connection but close the first without hanging
  • allow both connections (probably not a good idea owing to complexity and potential for compiler state corruption)

Disable device sleeping

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

Need to set a *print-fn*

ClojureScript:cljs.user> (println "ab")
Error: No *print-fn* fn set for evaluation environment

If TCP cannot bind, need to error out and not advertise

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.

`require` `:reload` failing in Cursive

  1. Add [com.cemerick/piggieback "0.1.5"] to your project dependencies.
  2. Mix in the Piggyback middleware by adding :repl-options {:nrepl-middleware [cemerick.piggieback/wrap-cljs-repl]} to your project.clj.
  3. Issue the following two forms in the Cursive REPL
(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 defs.

Perhaps something with the auto-configuration of :output-dir with nREPL needs to be looked at.

Errors logged when using timeout

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.

Can't reconnect after `:cljs/quit` owing to `(shutdown-agents)`

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.

:cljs/quit is blocking

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.

(require 'my.namespace :reload) fails to actually reload

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.

Doesn't work in Cursive

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.

  • I get the same error if I try to start Weasel directly (but don't if I use simple-brepl with Weasel).
  • @bhauman is sorting through perhaps similar stuff for Figwheel

Fails with large response

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

Regression with handling exceptions

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.

Sort out how app bootstrapping works

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.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.