Personal emacs config
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

601 lines
18 KiB

  1. ;;; lispy-clojure.clj --- lispy support for Clojure.
  2. ;; Copyright (C) 2015-2018 Oleh Krehel
  3. ;; This file is not part of GNU Emacs
  4. ;; This file is free software; you can redistribute it and/or modify
  5. ;; it under the terms of the GNU General Public License as published by
  6. ;; the Free Software Foundation; either version 3, or (at your option)
  7. ;; any later version.
  8. ;; This program is distributed in the hope that it will be useful,
  9. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. ;; GNU General Public License for more details.
  12. ;; For a full copy of the GNU General Public License
  13. ;; see <http://www.gnu.org/licenses/>.
  14. (ns lispy-clojure
  15. (:require [clojure.repl :as repl]
  16. [clojure.pprint]
  17. [clojure.java.io :as io]
  18. [clojure.string :as str])
  19. (:use [cemerick.pomegranate :only (add-dependencies)])
  20. (:import (java.io File LineNumberReader InputStreamReader
  21. PushbackReader FileInputStream)
  22. (clojure.lang RT Reflector)))
  23. (defn use-package [name version]
  24. (add-dependencies
  25. :coordinates [[name version]]
  26. :repositories (merge cemerick.pomegranate.aether/maven-central
  27. {"clojars" "https://clojars.org/repo"})
  28. :classloader (. (. (. Compiler/LOADER deref) getParent) getParent)))
  29. (defn expand-file-name [name dir]
  30. (. (io/file dir name) getCanonicalPath))
  31. (use-package 'compliment "0.3.6")
  32. (require '[compliment.core :as compliment])
  33. (use-package 'me.raynes/fs "1.4.6")
  34. (require '[me.raynes.fs :as fs])
  35. (defmacro xcond [& clauses]
  36. "Common Lisp style `cond'.
  37. It's more structured than `cond', thus exprs that use it are lot more
  38. malleable to refactoring."
  39. (when clauses
  40. (let [clause (first clauses)]
  41. (if (= (count clause) 1)
  42. `(or ~(first clause)
  43. (xcond
  44. ~@(next clauses)))
  45. `(if ~(first clause)
  46. (do ~@(next clause))
  47. (xcond
  48. ~@(next clauses)))))))
  49. (defn fetch-packages []
  50. (xcond ((fs/exists? "deps.edn")
  51. (println "fixme"))
  52. ((fs/exists? "project.clj")
  53. (let [deps (->> (slurp "project.clj")
  54. (read-string)
  55. (drop 3)
  56. (partition 2)
  57. (map vec)
  58. (into {})
  59. :dependencies)]
  60. (doseq [[name ver] deps]
  61. (use-package name ver))))
  62. (:else
  63. (throw
  64. (ex-info "Found no project.clj or deps.edn"
  65. {:cwd fs/*cwd*})))))
  66. (defn expand-home
  67. [path]
  68. (if (.startsWith path "~")
  69. (let [sep (.indexOf path File/separator)]
  70. (str (io/file (System/getProperty "user.home")
  71. (subs path (inc sep)))))
  72. path))
  73. (defn source-fn
  74. "Returns a string of the source code for the given symbol, if it can
  75. find it. This requires that the symbol resolve to a Var defined in
  76. a namespace for which the .clj is in the classpath. Returns nil if
  77. it can't find the source.
  78. Example: (source-fn 'filter)"
  79. [x]
  80. (let [v (resolve x)
  81. m (and v (meta v))
  82. file (or (:l-file m) (:file m))
  83. line (or (:l-line m) (:line m))]
  84. (when (and file line (> line 1))
  85. (let [filepath (expand-home file)
  86. strm (or (.getResourceAsStream (RT/baseLoader) filepath)
  87. (FileInputStream. filepath))]
  88. (with-open [rdr (LineNumberReader. (InputStreamReader. strm))]
  89. (dotimes [_ (dec line)] (.readLine rdr))
  90. (let [text (StringBuilder.)
  91. pbr (proxy [PushbackReader] [rdr]
  92. (read [] (let [i (proxy-super read)]
  93. (.append text (char i))
  94. i)))]
  95. (if (= :unknown *read-eval*)
  96. (throw (IllegalStateException. "Unable to read source while *read-eval* is :unknown."))
  97. (read (PushbackReader. pbr)))
  98. (str text)))))))
  99. (defn symbol-function
  100. "Return the source code for function SYM."
  101. [sym]
  102. (read-string
  103. (source-fn
  104. sym)))
  105. (defn macro? [x]
  106. (:macro (meta (resolve x))))
  107. (defn arity [args]
  108. (if (some #{'&} args)
  109. 1000
  110. (count args)))
  111. (defn flatten-expr
  112. "Flatten a function call EXPR by substituting the arguments."
  113. [expr]
  114. (let [func-name (first expr)
  115. args (rest expr)
  116. func-def (symbol-function func-name)
  117. func-doc (when (string? (nth func-def 2))
  118. (nth func-def 2))
  119. func-rest (drop (if func-doc 3 2) func-def)
  120. func-rest (if (map? (first func-rest))
  121. (rest func-rest)
  122. func-rest)
  123. func-bodies (if (vector? (first func-rest))
  124. (list func-rest)
  125. func-rest)
  126. func-body (first (filter #(>= (arity (first %)) (count args))
  127. (sort (fn [a b] (< (arity (first a))
  128. (arity (first b))))
  129. func-bodies)))
  130. func-args (first func-body)
  131. func-impl (rest func-body)]
  132. (cons 'let
  133. (cons (vec (if (some #{'&} [func-args])
  134. (vector func-args (vec args))
  135. (apply concat
  136. (filter (fn [[a b]]
  137. (not (= a b)))
  138. (partition
  139. 2 (interleave func-args args))))))
  140. func-impl))))
  141. (defn quote-maybe
  142. "Quote X that isn't self-quoting, like symbol or list."
  143. [x]
  144. (if (fn? x)
  145. x
  146. (if (or (symbol? x)
  147. (list? x))
  148. (list 'quote x)
  149. x)))
  150. (defn dest
  151. "Transform `let'-style BINDINGS into a sequence of `def's."
  152. [bindings]
  153. (let [bs (partition 2 (destructure bindings))
  154. as (filterv
  155. #(not (re-matches #"^(vec|map|seq|first)__.*" (name %)))
  156. (map first bs))]
  157. (concat '(do)
  158. (map (fn [[name val]]
  159. `(def ~name ~val))
  160. bs)
  161. [(zipmap (map keyword as) as)])))
  162. (defn get-func-args-defn [func-def n-args]
  163. (let [func-doc (when (string? (nth func-def 2))
  164. (nth func-def 2))
  165. func-rest (drop (if func-doc 3 2) func-def)
  166. func-rest (if (map? (first func-rest))
  167. (rest func-rest)
  168. func-rest)
  169. func-bodies (if (vector? (first func-rest))
  170. (list func-rest)
  171. func-rest)
  172. func-body (first (filter #(>= (arity (first %)) n-args)
  173. (sort (fn [a b] (< (arity (first a))
  174. (arity (first b))))
  175. func-bodies)))
  176. func-args (first func-body)]
  177. func-args))
  178. (defn get-func-args-def [func-def n-args]
  179. (let [body (nth func-def 2)]
  180. (assert (= (first body) 'fn))
  181. (let [args (first (filter vector? body))
  182. args-count (count (vec (remove '#{& &form &env} args)))]
  183. (assert (or (= args-count n-args)
  184. (and (< args-count n-args)
  185. ((set args) '&))))
  186. (vec (remove '#{&form &env} args)))))
  187. (defn get-func-args [func-def n-args]
  188. (xcond ((#{'defn 'defmacro} (first func-def))
  189. (get-func-args-defn func-def n-args))
  190. ((= (first func-def) 'def)
  191. (get-func-args-def func-def n-args))))
  192. (defn shadow-map []
  193. (or (ns-resolve *ns* 'shadows)
  194. (intern *ns* 'shadows {})))
  195. (defn shadow-unmap [nspc]
  196. ;; (ns-unmap nspc 'shadows)
  197. (intern nspc 'shadows {}))
  198. (defmacro with-shadows [& forms]
  199. `(let ~(vec (mapcat (fn [[k _]] [(symbol k) `((shadow-map) ~k)])
  200. (deref (shadow-map))))
  201. ~@forms))
  202. (defn shadow-def
  203. "Give SYM in *ns* shadow value EXPR.
  204. (with-shadows SYM) can be used to retrieve this value."
  205. [sym expr]
  206. (intern
  207. *ns*
  208. 'shadows
  209. (assoc (deref (shadow-map)) (name sym) expr)))
  210. (defn shadow-dest
  211. "Transform `let'-style BINDINGS into a sequence of `shadow-def's."
  212. ([bindings]
  213. (shadow-dest bindings *ns*))
  214. ([bindings nspc]
  215. (let [[_do & forms] (dest bindings)
  216. [defs out] (partition-by map? forms)]
  217. `(let ~(vec (mapcat (fn [[_ n v]] [n v]) defs))
  218. ~@(if (not= *ns* nspc)
  219. `((in-ns '~(ns-name nspc))))
  220. ~@(map
  221. (fn [x]
  222. `(shadow-def '~(second x) ~(second x)))
  223. defs)
  224. ~@out))))
  225. (defn debug-step-in
  226. "Evaluate the function call arugments and sub them into function arguments."
  227. [expr]
  228. (let [func-name (first expr)
  229. args (vec (rest expr))
  230. func-def (symbol-function func-name)
  231. func-args (get-func-args func-def (count args))
  232. func-ns (:ns (meta (resolve func-name)))
  233. eval-form (shadow-dest
  234. [func-args (if (macro? func-name)
  235. (list 'quote args)
  236. args)]
  237. func-ns)]
  238. (eval
  239. `(with-shadows
  240. ~eval-form))))
  241. (defn object-methods [sym]
  242. (distinct
  243. (map #(.getName %)
  244. (xcond
  245. ((instance? java.lang.Class sym)
  246. (. sym getMethods))
  247. ((instance? java.lang.Object sym)
  248. (. (type sym) getMethods))))))
  249. (defn object-fields [sym]
  250. (map #(str "-" (.getName %))
  251. (.getFields (type sym))))
  252. (defmacro object-members [ob]
  253. `(with-shadows
  254. (concat (object-fields ~ob)
  255. (object-methods ~ob))))
  256. (defn get-meth [obj method-name]
  257. (first (filter #(= (.getName %) method-name)
  258. (.getMethods (type obj)))))
  259. (defn method-signature [obj method-name]
  260. (str (get-meth obj method-name)))
  261. (defn get-ctors [obj]
  262. (. obj getDeclaredConstructors))
  263. (defn format-ctor [s]
  264. (let [[_ name args] (re-find #"(?:public|protected) (.*)\((.*)\)" s)]
  265. (str name
  266. "."
  267. (if (= args "")
  268. ""
  269. (str " " (str/replace args #"," " "))))))
  270. (defn ctor-args [sym]
  271. (str/join
  272. "\n"
  273. (map #(str "(" % ")")
  274. (map format-ctor
  275. (map str (get-ctors sym))))))
  276. (defn resolve-sym [sym]
  277. (xcond
  278. [(symbol? sym)
  279. (if (special-symbol? sym)
  280. 'special
  281. (or
  282. (resolve sym)
  283. (first (keep #(ns-resolve % sym) (all-ns)))
  284. (if-let [val (try (load-string (str sym)) (catch Exception e))]
  285. (list 'variable (str val)))))]
  286. [(keyword? sym) 'keyword]
  287. [:else 'unknown]))
  288. (defn class-name [cls]
  289. (str/replace (str cls) #"class " ""))
  290. (defn class-method-static? [method]
  291. (java.lang.reflect.Modifier/isStatic (.getModifiers method)))
  292. (defn class-methods [cname]
  293. (load-string (format "(.getMethods %s)" cname)))
  294. (defn find-method [sym]
  295. (let [[cname mname] (str/split (str sym) #"/")
  296. methods (->>
  297. (and cname
  298. (class-methods cname))
  299. (filter #(= (.getName %) mname)))]
  300. (first methods)))
  301. (defn arglist [sym]
  302. (let [rsym (resolve-sym sym)]
  303. (xcond
  304. ((= 'special rsym)
  305. (->> (with-out-str
  306. (eval (list 'clojure.repl/doc sym)))
  307. (re-find #"\(.*\)")
  308. read-string rest
  309. (map str)
  310. (str/join " ")
  311. (format "[%s]")
  312. list))
  313. ((and (nil? rsym) (re-find #"/" (str sym)))
  314. (let [method (find-method sym)
  315. args (->> method
  316. (.getParameterTypes)
  317. (map class-name)
  318. (str/join " "))]
  319. (format "(%s [%s]) -> %s" sym args
  320. (class-name (. method getReturnType)))))
  321. (:else
  322. (let [args (map str (:arglists (meta rsym)))]
  323. (if (empty? args)
  324. (condp #(%1 %2) (eval sym)
  325. map? "[key]"
  326. set? "[key]"
  327. vector? "[idx]"
  328. "is uncallable")
  329. args))))))
  330. (defmacro ok
  331. "On getting an Exception, just print it."
  332. [& body]
  333. `(try
  334. (eval '~@body)
  335. (catch Exception ~'e (.getMessage ~'e))))
  336. (defn classpath []
  337. (map #(.getAbsolutePath (java.io.File. (.toURI %)))
  338. (.getURLs (java.lang.ClassLoader/getSystemClassLoader))))
  339. (defn reader=
  340. "Equality accounting for reader-generated symbols."
  341. [a b]
  342. (try
  343. (xcond
  344. ((and (symbol? a) (symbol? b))
  345. (or
  346. (= a b)
  347. (and
  348. (re-find #"[0-9]+#$" (name a))
  349. (re-find #"[0-9]+#$" (name b))
  350. true)))
  351. ((and (instance? java.util.regex.Pattern a)
  352. (instance? java.util.regex.Pattern b))
  353. (= (. a toString)
  354. (. b toString)))
  355. ((and (empty? a) (empty? b))
  356. true)
  357. (:else
  358. (and
  359. (reader= (first a) (first b))
  360. (reader= (rest a) (rest b)))))
  361. (catch Exception e
  362. (= a b))))
  363. (defn position [x coll equality]
  364. (letfn [(iter [i coll]
  365. (xcond
  366. ((empty? coll) nil)
  367. ((equality x (first coll))
  368. i)
  369. (:else
  370. (recur (inc i) (rest coll)))))]
  371. (iter 0 coll)))
  372. (defn guess-intent [expr context]
  373. (if (not (or (list? expr)
  374. (vector? expr)))
  375. expr
  376. (let [idx (position expr context reader=)]
  377. (xcond
  378. ((#{'defproject} (first expr))
  379. `(fetch-packages))
  380. ((nil? idx)
  381. expr)
  382. ;; [x |(+ 1 2) y (+ 3 4)] => {:x 3}
  383. ;; TODO: would be better to have 1 level higher context, so that we just check
  384. ;; (= (first context) 'let)
  385. ((and (vector? context)
  386. (= 0 (rem (count context) 2))
  387. (= 0 (rem (inc idx) 2))
  388. (every? (some-fn symbol? vector? map?) (take-nth 2 context)))
  389. (shadow-dest
  390. (take 2 (drop (- idx 1) context))))
  391. ((or (nil? context)
  392. (reader= expr context))
  393. expr)
  394. ((and (#{'doseq 'for} (first context))
  395. (vector? expr)
  396. (= 2 (count expr)))
  397. (shadow-dest
  398. [(first expr) (first (eval `(with-shadows ~(second expr))))]))
  399. ((and (#{'dotimes} (first context))
  400. (vector? expr)
  401. (= 2 (count expr)))
  402. (shadow-dest
  403. [(first expr) 0]))
  404. ((#{'-> '->> 'doto} (first context))
  405. (take (inc idx) context))
  406. (:t
  407. expr)))))
  408. (defn add-location-to-defn [expr file line]
  409. (when (and (list? expr)
  410. (= 'defn (first expr))
  411. file line)
  412. (let [arglist-pos (first (keep-indexed
  413. (fn [i x] (if (or
  414. (vector? x)
  415. (list? x)) i))
  416. expr))
  417. expr-head (take arglist-pos expr)
  418. expr-tail (drop arglist-pos expr)
  419. expr-doc (or (first (filter string? expr-head)) "")
  420. expr-map (or (first (filter map? expr-head)) {})]
  421. `(~'defn ~(nth expr 1)
  422. ~expr-doc
  423. ~(merge {:l-file file
  424. :l-line line}
  425. expr-map)
  426. ~@expr-tail))))
  427. (defn add-location-to-def
  428. [[_def name & args] file line]
  429. (apply list
  430. _def
  431. (with-meta
  432. name
  433. {:l-file file
  434. :l-line line})
  435. (if (> (count args) 1)
  436. args
  437. (cons "" args))))
  438. (defn add-location-to-deflike [expr file line]
  439. (when (and file line (list? expr))
  440. (xcond ((= (first expr) 'def)
  441. (add-location-to-def expr file line))
  442. ((= (first expr) 'defn)
  443. (add-location-to-defn expr file line)))))
  444. (defn read-string-all
  445. "Read all objects from the string S."
  446. [s]
  447. (let [reader (java.io.PushbackReader.
  448. (java.io.StringReader. s))]
  449. (loop [res []]
  450. (if-let [x (try (read reader)
  451. (catch Exception e))]
  452. (recur (conj res x))
  453. res))))
  454. (defn reval [e-str context-str & {:keys [file line]}]
  455. (let [expr (read-string e-str)
  456. context (try
  457. (read-string context-str)
  458. (catch Exception _))
  459. full-expr (read-string (format "[%s]" e-str))
  460. expr1 (xcond
  461. ((nil? context-str)
  462. (cons 'do full-expr))
  463. ((= (count full-expr) 2)
  464. (shadow-dest full-expr))
  465. ((add-location-to-deflike expr file line))
  466. (:else
  467. (guess-intent expr context)))]
  468. (eval `(with-shadows
  469. (try
  470. (do ~expr1)
  471. (catch Exception ~'e
  472. (clojure.core/str "error: " ~ 'e)))))))
  473. (defn file->elisp [f]
  474. (if (fs/exists? f)
  475. f
  476. (. (io/resource f) getPath)))
  477. (defn location [sym]
  478. (let [rs (resolve sym)
  479. m (meta rs)]
  480. (xcond ((:l-file m)
  481. (list (:l-file m) (:l-line m)))
  482. ((and (:file m) (not (re-matches #"^/tmp/" (:file m))))
  483. (list (file->elisp (:file m)) (:line m)))
  484. ;; ((class? rs)
  485. ;; (let [sym (symbol (class-name rs))
  486. ;; info (parser/source-info sym)]
  487. ;; (list
  488. ;; (file->elisp
  489. ;; (:file info))
  490. ;; (:line info))))
  491. ;; ((nil? rs)
  492. ;; (let [name (str sym)
  493. ;; [cls method] (str/split name #"/")
  494. ;; file (-> (clojure.core/symbol cls)
  495. ;; (resolve)
  496. ;; (class-name)
  497. ;; (parser/source-path)
  498. ;; (file->elisp))
  499. ;; line (-> (symbol cls)
  500. ;; (resolve)
  501. ;; (class-name)
  502. ;; (symbol)
  503. ;; (parser/source-info)
  504. ;; (:members)
  505. ;; (get (clojure.core/symbol method))
  506. ;; (vals)
  507. ;; (first)
  508. ;; (:line))]
  509. ;; (list file line)))
  510. )))
  511. (defn pp [expr]
  512. (with-out-str
  513. (clojure.pprint/pprint
  514. expr)))
  515. (defn all-docs [ns]
  516. (str/join
  517. "::"
  518. (->> (filter (fn [v]
  519. (and (var? v)
  520. (fn? (deref v))))
  521. (vals (ns-map ns)))
  522. (map
  523. (fn [v]
  524. (let [m (meta v)]
  525. (str v "\n" (:arglists m) "\n" (:doc m))))))))
  526. (defn complete [prefix]
  527. (compliment/completions
  528. prefix
  529. {:context :same :plain-candidates true}))
  530. (let [dd (fs/parent (:file (meta #'use-package)))
  531. fname (java.io.File. dd "lispy-clojure-test.clj")]
  532. (when (fs/exists? fname)
  533. (load-file (str fname))))