Skip to content
This repository has been archived by the owner on Jun 4, 2022. It is now read-only.

Commit

Permalink
LUMO-26: doc REPL special
Browse files Browse the repository at this point in the history
  • Loading branch information
anmonteiro committed Nov 21, 2016
1 parent 74aa13d commit 1a762d1
Show file tree
Hide file tree
Showing 8 changed files with 160 additions and 42 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -11,6 +11,7 @@
classpath in which to call a `-main` function with all args after it ([#27](https://github.com/anmonteiro/lumo/issues/27)).
- Add a `-a / --elide-asserts` command option that defines whether asserts in
evaluated code should be ignored ([#37](https://github.com/anmonteiro/lumo/issues/37)).
- `doc` REPL special function ([#26](https://github.com/anmonteiro/lumo/issues/26)).

### Changes

Expand Down
3 changes: 2 additions & 1 deletion build.boot
Expand Up @@ -116,7 +116,8 @@
#"^cljs[\\\/](test\.cljc|core\.cljs\.cache\.aot\.edn|spec(\.cljc|[\\\/]test\.clj[sc]|[\\\/]impl[\\\/]gen\.cljc))$"}
:move {#"^main.out[\\\/]((cljs|clojure|cognitect|lumo|lazy_map).*)" "$1"})
(sift :include #{#"^main.js" #"^bundle.js" #"^cljs(?!\.js)"
#"^clojure" #"^cognitect" #"^lumo[\\\/]" #"^lazy_map[\\\/]"})
#"^clojure" #"^cognitect" #"^lumo[\\\/]" #"^lazy_map[\\\/]"}
:to-resource #{#"^lumo[\\\/]repl\.clj$"})
(sift :include #{#"^cljs[\\\/]core\.cljs\.cache\.json$"} :invert true)))

(deftask compile-cljs []
Expand Down
4 changes: 3 additions & 1 deletion scripts/aot-bundle-macros.bat
Expand Up @@ -11,8 +11,10 @@ echo "### Compiling Macro Namespaces"

mkdir lumo-cljs\out\macros-tmp || goto :error

echo (require-macros 'clojure.template 'cljs.spec 'cljs.spec.impl.gen 'cljs.test) | build\lumo.exe --quiet -c target -sdk lumo-cljs/out/macros-tmp || goto :error
echo (require-macros 'lumo.repl 'clojure.template 'cljs.spec 'cljs.spec.impl.gen 'cljs.test) | build\lumo.exe --quiet -c target -sdk lumo-cljs/out/macros-tmp || goto :error

mv lumo-cljs/out/macros-tmp/lumo_SLASH_repl\$macros.js target/lumo/repl\$macros.js || goto :error
mv lumo-cljs/out/macros-tmp/lumo_SLASH_repl\$macros.cache.json target/lumo/repl\$macros.cache.json || goto :error
mv lumo-cljs\out\macros-tmp\clojure_SLASH_template$macros.js target\clojure\template$macros.js || goto :error
mv lumo-cljs\out\macros-tmp\clojure_SLASH_template$macros.cache.json target\clojure\template$macros.cache.json || goto :error
mv lumo-cljs\out\macros-tmp\cljs_SLASH_test$macros.js target\cljs\test$macros.js || goto :error
Expand Down
4 changes: 3 additions & 1 deletion scripts/aot-bundle-macros.sh
Expand Up @@ -14,9 +14,11 @@ echo "### Compiling Macro Namespaces"
mkdir -p lumo-cljs/out/macros-tmp

$(pwd)/build/lumo --quiet -c target -sdk lumo-cljs/out/macros-tmp <<REPL_INPUT
(require-macros 'clojure.template 'cljs.spec 'cljs.spec.impl.gen 'cljs.test)
(require-macros 'lumo.repl 'clojure.template 'cljs.spec 'cljs.spec.impl.gen 'cljs.test)
REPL_INPUT

mv lumo-cljs/out/macros-tmp/lumo_SLASH_repl\$macros.js target/lumo/repl\$macros.js
mv lumo-cljs/out/macros-tmp/lumo_SLASH_repl\$macros.cache.json target/lumo/repl\$macros.cache.json
mv lumo-cljs/out/macros-tmp/clojure_SLASH_template\$macros.js target/clojure/template\$macros.js
mv lumo-cljs/out/macros-tmp/clojure_SLASH_template\$macros.cache.json target/clojure/template\$macros.cache.json
mv lumo-cljs/out/macros-tmp/cljs_SLASH_test\$macros.js target/cljs/test\$macros.js
Expand Down
6 changes: 6 additions & 0 deletions src/cljs/lumo/repl.clj
@@ -0,0 +1,6 @@
(ns lumo.repl)

(defmacro doc
"Prints documentation for a var or special form given its name"
[name]
`(lumo.repl/doc* '~name))
161 changes: 136 additions & 25 deletions src/cljs/lumo/repl.cljs
@@ -1,9 +1,12 @@
(ns lumo.repl
(:refer-clojure :exclude [load-file*])
(:require-macros [cljs.env.macros :as env]
[lumo.repl])
(:require [cljs.analyzer :as ana]
[cljs.env :as env]
[cljs.js :as cljs]
[cljs.reader :as reader]
[cljs.repl]
[cljs.tagged-literals :as tags]
[cljs.tools.reader :as r]
[cljs.tools.reader.reader-types :as rt]
Expand Down Expand Up @@ -125,6 +128,7 @@
cljs.nodejs
cljs.pprint
cljs.reader
cljs.repl
cljs.source-map
cljs.source-map.base64
cljs.source-map.base64-vlq
Expand Down Expand Up @@ -155,6 +159,7 @@
'#{cljs.core
cljs.js
cljs.pprint
cljs.repl
cljs.env.macros
cljs.analyzer.macros
cljs.compiler.macros
Expand Down Expand Up @@ -420,6 +425,67 @@
;; --------------------
;; REPL specials

(defn- drop-macros-suffix
[ns-name]
(if (string/ends-with? ns-name "$macros")
(apply str (drop-last 7 ns-name))
ns-name))

(defn- add-macros-suffix
[sym]
(symbol (str (name sym) "$macros")))

(defn- all-ns
"Returns a sequence of all namespaces."
[]
(keys (::ana/namespaces @st)))

(defn- all-macros-ns []
(->> (all-ns)
(filter #(string/ends-with? (str %) "$macros"))))

(defn- get-namespace
"Gets the AST for a given namespace."
[ns]
{:pre [(symbol? ns)]}
(get-in @st [::ana/namespaces ns]))

(defn- resolve-var
"Given an analysis environment resolve a var. Analogous to
clojure.core/resolve"
[env sym]
{:pre [(map? env) (symbol? sym)]}
(try
(ana/resolve-var env sym
(ana/confirm-var-exists-throw))
(catch :default _
(ana/resolve-macro-var env sym))))

(defn- get-macro-var
[env sym macros-ns]
{:pre [(symbol? macros-ns)]}
(when-let [macro-var (env/with-compiler-env st
(resolve-var env (symbol macros-ns (name sym))))]
(assoc macro-var :ns macros-ns)))

(defn- get-var
[env sym]
(binding [ana/*cljs-warning-handlers* nil]
(let [var (or (env/with-compiler-env st (resolve-var env sym))
(some #(get-macro-var env sym %) (all-macros-ns)))]
(when var
(-> (cond-> var
(not (:ns var))
(assoc :ns (symbol (namespace (:name var))))
(= (namespace (:name var)) (str (:ns var)))
(update :name #(symbol (name %))))
(update :ns (comp symbol drop-macros-suffix str)))))))

(defn- get-aenv []
(assoc (ana/empty-env)
:ns (get-namespace @current-ns)
:context :expr))

(defn- compiler-state-backup []
{:st @st
:loaded @cljs/*loaded*})
Expand Down Expand Up @@ -509,6 +575,69 @@
(defn- repl-special? [form]
(and (seq? form) (contains? repl-special-fns (first form))))

(defn- special-doc [name-symbol]
(assoc (special-doc-map name-symbol)
:name name-symbol
:special-form true))

(defn- repl-special-doc [name-symbol]
(assoc (repl-special-doc-map name-symbol)
:name name-symbol
:repl-special-function true))

(defn- undo-reader-conditional-spacing
"Undoes the effect that wrapping a reader conditional around
a defn has on a docstring."
[s]
;; We look for five spaces (or six, in case that the docstring
;; is not aligned under the first quote) after the first newline
;; (or two, in case the doctring has an unpadded blank line
;; after the first), and then replace all five (or six) spaces
;; after newlines with two.
(when-not (nil? s)
(if (re-find #"[^\n]*\n\n?\s{5,6}\S.*" s)
(string/replace-all s #"\n ?" "\n ")
s)))

;; TODO: proper spec support (due to $macros), need to write own print-doc fn
(defn- doc* [name]
(if-let [special-name ('{& fn catch try finally try} name)]
(doc* special-name)
(cond
(special-doc-map name)
(cljs.repl/print-doc (special-doc-map name))

(repl-special-doc-map name)
(cljs.repl/print-doc (repl-special-doc name))

(get-namespace name)
(cljs.repl/print-doc (select-keys (get-namespace name) [:name :doc]))

(get-var (get-aenv) name)
(cljs.repl/print-doc
(let [aenv (get-aenv)
var (get-var aenv name)
m (-> (select-keys var
[:ns :name :doc :forms :arglists :macro :url])
(update-in [:doc] undo-reader-conditional-spacing)
(merge
{:forms (-> var :meta :forms second)
:arglists (-> var :meta :arglists second)}))]
(cond-> (update-in m [:name] clojure.core/name)
(:protocol-symbol var)
(assoc :protocol true
:methods
(->> (get-in var [:protocol-info :methods])
(map (fn [[fname sigs]]
[fname {:doc (:doc
(get-var aenv
(symbol (str (:ns var)) (str fname))))
:arglists (seq sigs)}]))
(into {})))))))))

;; --------------------
;; Code evaluation

(defn make-eval-opts []
(let [{:keys [verbose static-fns]} @app-opts]
{:ns @current-ns
Expand Down Expand Up @@ -562,7 +691,7 @@
(handle-repl-error (ex-info (str "Could not load file " filename) {}))))))

(defn- execute-text
[source {:keys [expression? filename] :as opts}]
[source {:keys [expression? print-nil-result? filename] :as opts}]
(try
(binding [cljs/*eval-fn* caching-node-eval
cljs/*load-fn* load
Expand Down Expand Up @@ -590,7 +719,9 @@
(fn [{:keys [ns value error] :as ret}]
(if-not error
(when expression?
(println (pr-str value))
(when (or print-nil-result?
(not (nil? value)))
(println (pr-str value)))
(vreset! current-ns ns))
(handle-repl-error error)))))))
(catch :default e
Expand All @@ -604,11 +735,12 @@
(execute-text source-or-path opts)))

(defn ^:export execute
[type source-or-path expression? setNS]
[type source-or-path expression? print-nil-result? setNS]
(when setNS
(vreset! current-ns (symbol setNS)))
(execute-source source-or-path {:type type
:expression? expression?}))
:expression? expression?
:print-nil-result? print-nil-result?}))

(defn ^:export is-readable?
[form]
Expand Down Expand Up @@ -667,27 +799,6 @@
;; --------------------
;; Autocompletion

(defn- drop-macros-suffix
[ns-name]
(if (string/ends-with? ns-name "$macros")
(apply str (drop-last 7 ns-name))
ns-name))

(defn- add-macros-suffix
[sym]
(symbol (str (name sym) "$macros")))

(defn- all-ns
"Returns a sequence of all namespaces."
[]
(keys (::ana/namespaces @st)))

(defn- get-namespace
"Gets the AST for a given namespace."
[ns]
{:pre [(symbol? ns)]}
(get-in @st [::ana/namespaces ns]))

(defn- completion-candidates-for-ns
[ns-sym allow-private?]
(map (comp str key)
Expand Down
10 changes: 0 additions & 10 deletions src/cljs/lumo/repl_resources.cljs
Expand Up @@ -108,11 +108,6 @@
:doc "The symbol must resolve to a var, and the Var object
itself (not its value) is returned. The reader macro #'x expands to (var x)."}})

#_(defn- special-doc [name-symbol]
(assoc (or (special-doc-map name-symbol) (meta (resolve name-symbol)))
:name name-symbol
:special-form true))

(def repl-special-doc-map
'{in-ns {:arglists ([name])
:doc "Sets *cljs-ns* to the namespace named by the symbol, creating it if needed."}
Expand All @@ -122,8 +117,3 @@ itself (not its value) is returned. The reader macro #'x expands to (var x)."}})
:doc "Loads Clojure code from resources in classpath. A path is interpreted as
classpath-relative if it begins with a slash or relative to the root
directory for the current namespace otherwise."}})

#_(defn- repl-special-doc [name-symbol]
(assoc (repl-special-doc-map name-symbol)
:name name-symbol
:repl-special-function true))
13 changes: 9 additions & 4 deletions src/js/cljs.js
Expand Up @@ -102,9 +102,11 @@ function initClojureScriptEngine(opts: CLIOptsType): void {
export function execute(code: string,
type: string = 'text',
expression: boolean = true,
printNilResult: boolean = true,
setNS: ?string): void {
// $FlowIssue: context can have globals
return ClojureScriptContext.lumo.repl.execute(type, code, expression, setNS);
return ClojureScriptContext.lumo.repl.execute(
type, code, expression, printNilResult, setNS);
}
/* eslint-enable indent */

Expand All @@ -127,7 +129,10 @@ export function indentSpaceCount(text: string): number {
return ClojureScriptContext.lumo.repl.indent_space_count(text);
}

export function getHighlightCoordinates(text: string[], pos: number): [number, number] {
/* eslint-disable indent */
export function getHighlightCoordinates(text: string[],
pos: number): [number, number] {
/* eslint-enable indent */
// $FlowIssue: context can have globals
return ClojureScriptContext.lumo.repl.get_highlight_coordinates(text, pos);
}
Expand All @@ -154,8 +159,6 @@ export default function startClojureScriptEngine(opts: CLIOptsType): void {
if (scripts.length > 0) {
initClojureScriptEngine(opts);
executeScripts(scripts);
// $FlowIssue: context can have globals
ClojureScriptContext.lumo.repl.set_ns('cljs.user');
}

if (mainScript) {
Expand All @@ -171,6 +174,8 @@ export default function startClojureScriptEngine(opts: CLIOptsType): void {
if (repl) {
process.nextTick(() => {
initClojureScriptEngine(opts);
execute('(require \'[lumo.repl :refer-macros [doc]])',
'text', true, false, 'cljs.user');
});

return startREPL(opts);
Expand Down

0 comments on commit 1a762d1

Please sign in to comment.