ocamlの関数の補完をするelisp

まだ完成していないけど、途中経過をUpしてみます。
長いので、別のところにupした方が良かったかもしれません。

使用方法

  • ファイルの冒頭にあるコメントの部分を.emacsに貼り付ける
  • (好きなキーにbindする)
    • anything-ocaml-reference-completionsが補完
    • anything-ocaml-referenceで使用できそうな関数を調べる
    • anything-ocaml-reference-module-insertでopenを設定((決定する前に、一度tabでアクションを変更しないと

open ;;という風に出力されません。(あとで直すと思います><)))

  • anything-ocaml-reference--delete-memoryで生成したファイルを削除できます。

問題

  • 最初の起動に時間がかかる(一度ファイルを作成してからだとそれほどでもないです)
  • helpの表示がしょぼい
    • (定義元へのlinkとか貼れたらいいなとは思う)
  • すべての関数を適切に読み取れているか分からない

コード

(require 'anything)
(require 'tuareg)

;; .emacsに
;; (require 'anything-ocaml-reference
;; (add-hook 'tuareg-mode-hook
;; 	  (lambda ()
;; 	    (anything-ocaml-reference-setup)
;; 	    (define-key tuareg-mode-map "\C-ci" 'anything-ocaml-reference)
;; 	    (define-key tuareg-mode-map "\C-co" 'anything-ocaml-reference-module-insert)
;; 	    define-key tuareg-mode-map "\C-c\C-i" 'anything-ocaml-reference-completions)
;; 	  )

;; ;;debug用
;; (defun d (e)
;;   (with-output-to-temp-buffer "*debug*"
;;     (print e)))

(defvar ocaml-reference-base   "~/.emacs.d/ocaml-reference/")

(defvar ocaml-functions-list-path 
  (concat ocaml-reference-base "ocaml-functions-list"))

(defvar ocaml-module->mli-file-alist-path
  (concat ocaml-reference-base "relation-between-module-and-mli"))

(defvar anything-ocaml-reference-save-p t)

;;無かったらdirectoryを作成
(let ((dir (file-name-directory  ocaml-functions-list-path)))
  (unless (file-exists-p dir)
    (unless (file-exists-p (file-name-directory dir))
      (make-directory (file-name-directory dir)))
    (make-directory dir)))

(defvar ocaml-library-pathes '("/usr/local/lib/ocaml" "/usr/lib/ocaml"))

(defvar ocaml-mli-file-list nil)

(defun directory-files2 (file)
  ".と..をなくしたファイルを返す.絶対pathで"
  (mapcar #'(lambda (f) (expand-file-name f file))
	  (cddr (directory-files file))))

(defun set-all-mli-files (files)
  (dolist (f files)
    (cond ((file-directory-p f)
	   (set-all-mli-files (directory-files2 f)))
	  ((equal (file-name-extension f) "mli")
	   (push f ocaml-mli-file-list)))))

(defvar ocaml-functions-list nil)

(defvar ocaml-reference-search-functoin-regexp 
  "^[ \t]*\\(val\\|exception\\|type\\|external\\) *\\([^ \n\t]+\\) *[ :\n]")

(defun get-ocaml-functions-lists-from-mli-file (file)
  "return list. such as (<module1> ((<func1> <s-point> <e-point>) (<func2> <s-point> <e-point>) ...))
OR, when module definition is nested, then return list of such as it.
"
  (let ((result '()))
    (save-excursion
      (set-buffer (find-file-noselect file))
      (goto-char (point-min))
      (while (re-search-forward ocaml-reference-search-functoin-regexp  (point-max) t 1)
	(let ((start (match-beginning 1))
	      (name (substring-no-properties (match-string 2))))
	  (push (cons name start) result)))
      (push (cons "*END*" (point-max)) result))
    (let ((module (get-ocaml-module-from-mli-file file))
	  (functions 
	   (mapcar* #'(lambda (next this) 
			(let ((func (car this))
			      (s-point (cdr this))
			      (e-point (cdr next)))
			  (list func s-point e-point)))
		    result (cdr result))))
      (let ((buf (file-name-nondirectory file))) ;;開いたbufferを閉じる
	(when (get-buffer buf) (kill-buffer buf)))
      (if (stringp module) ;file内にmodule定義が無かった場合
	  (cons module functions)
	(let ((alist (mapcar #'(lambda (e) (list e '())) module)))
					;alist = (module (func-list ...)
	  (dolist (f-list functions)
	    (let ((f-s-point (s-point f-list))
		  (f-e-point (e-point f-list)))
	      (dolist (a alist)
		(let ((m (car a)))
		  (cond ((and (< (s-point m) f-s-point)
			      (> (e-point m) f-e-point))
			 (push f-list (cadr a))))))))

	  alist)))))
(defun s-point (l) (cadr l))
(defun e-point (l) (caddr l))

;; (d (get-ocaml-functions-lists-from-mli-file
;;     "/usr/lib/ocaml/3.10.0/weak.mli"))

(defun get-ocaml-module-from-mli-file (file)
  (let ((result '()))
    (set-buffer (find-file-noselect file))
    (goto-char (point-min))
    (push (cons nil (point-min)) result) ;はじめの部分
    (while (re-search-forward "module[ \t]+\\([^ ]+\\)[ \t]:" (point-max) t 1)
      (push (cons (match-string-no-properties 1) (match-beginning 1))
	    result))
    (let ((base-module 	(get-ocaml-module-name file)))
      (if (null (caar result)) ;module定義がファイル内にみつかなかったら
	  base-module
	(progn (push (cons "*END*" (point-max)) result)
	       (mapcar* #'(lambda (next this)
			    (let ((module (if (null (car this)) ;;nestされたmoduleじゃないもののための分岐
					      base-module
					    (concat base-module "." (car this))))
				  (s-point (cdr this))
				  (e-point (cdr next)))
			      (list module s-point e-point)))
			result (cdr result)))))))

;(d (get-ocaml-module-from-mli-file  "/usr/lib/ocaml/3.10.0/weak.mli"))
;(get-ocaml-module-from-mli-file  "~/test/ocaml-all.mli")

(defun get-ocaml-module-name (file-name)
  "(get-ocaml-module-name \"enum.mli\") => \"Enum\""
  (let ((module (car (split-string (file-name-nondirectory file-name) "\\."))))
    (concat (capitalize (substring module 0 1)) (substring module 1))))

(defun get-ocaml-mli-name (module)
  "(get-ocaml-mli-name \"Enum\") => \"enum.mli\""
  (cadr (assoc module module->mli-file-alist)))


(defun get-ocaml-function-search (function lst)
  "e.g.
function = \"Array.map\"
lst = (<module> '((<func1> <s-point> <e-point>) 
	    (<func2> <s-point> <e-point>)
	    ...))"
  (when (string-match "\\([^ ]+\\)\\\.\\([^ ]+\\)$" function)
    (let ((module (match-string 1 function))
	  (func (match-string 2 function)))
      (let ((func-list (cdr (assoc module lst))))
	(when func-list
	  (let ((module (car (split-string module "\\."))))
	    (cons module (assoc func func-list))))))))

(defun get-ocaml-function-info (module func from to)
  (let ((file  (get-ocaml-mli-name module))
	(buf "*ocaml info*"))
    (when (get-buffer buf)
      (kill-buffer buf))
    (set-buffer (find-file-noselect file))
    (let ((content (buffer-substring from to)))
      (anything-ocaml-reference--view content))))

(defun anything-ocaml-reference--view (content)
  (set-buffer (get-buffer-create buf))
  (insert content)
					;  (insert "\n   from:`" file "'")
  (insert (concat  "\nmodule:" module "\n"))
  (insert (concat "\n   from:(find-file\"" file "\")"))
  (tuareg-mode)
  (view-buffer-other-window buf t)
  (other-window 1))

(defun make-module->mli-file-alist (files)
  (setq module->mli-file-alist
	(mapcar #'(lambda (f) (list (get-ocaml-module-name f) f))
		files)))

(defun read-from-file (file)
  (set-buffer (find-file-noselect file))
  (goto-char (point-min))
  (read (current-buffer)))

(defun delete-file&buffer (file)
  (when (file-exists-p file)
    (delete-file file))
  (let ((buf (file-name-nondirectory file)))
    (when (get-buffer buf)
      (kill-buffer buf))))

(defun anything-ocaml-reference--delete-memory () 
  (interactive)
  (setq anything-ocaml-reference-setup-p nil
	ocaml-mli-file-list nil
	ocaml-functions-list nil
	module->mli-file-alist nil
	ocaml-mli-file-list nil
	anything-ocaml-reference-source nil
	anything-ocaml-module-source nil)
  (dolist (file (list ocaml-functions-list-path
		      ocaml-module->mli-file-alist-path))
    (delete-file&buffer file)))

(defun set-ocaml-mli-file-list ()
  (dolist (d (mapcar #'directory-files2 ocaml-library-pathes))
    (set-all-mli-files d)))

(defun set-ocaml-relation-between-module&file ()
  (if (and anything-ocaml-reference-save-p
	   (file-exists-p ocaml-module->mli-file-alist-path))
      (setq module->mli-file-alist (read-from-file ocaml-module->mli-file-alist-path))
    (progn
      (unless ocaml-mli-file-list
	(set-ocaml-mli-file-list))
      (make-module->mli-file-alist ocaml-mli-file-list)
      (when anything-ocaml-reference-save-p
	(save-value-of-variable module->mli-file-alist ocaml-module->mli-file-alist-path)))))

(defun set-ocaml-function-list ()
  (if (and anything-ocaml-reference-save-p
	   (file-exists-p ocaml-functions-list-path))
      (setq ocaml-functions-list (read-from-file ocaml-functions-list-path))
    (progn
      (unless ocaml-mli-file-list
	(set-ocaml-mli-file-list))
      (dolist (mli ocaml-mli-file-list)
	(let ((function-list (get-ocaml-functions-lists-from-mli-file mli)))
	  (if (stringp (car function-list))
	      (push  function-list ocaml-functions-list)
	    (dolist (f-list function-list)
	      (push (cons (caar f-list) (cadr f-list)) ocaml-functions-list)))))
      (when anything-ocaml-reference-save-p
	(save-value-of-variable ocaml-functions-list ocaml-functions-list-path)))))
  
(defun save-value-of-variable (variable file)
  (delete-file&buffer file)
  (set-buffer (find-file-noselect file))
  (goto-char (point-min))
  (print variable #'insert))

(defvar anything-ocaml-reference-setup-p nil)

(defun anything-ocaml-reference-setup ()
  (or anything-ocaml-reference-setup-p
      (progn
	(setq anything-ocaml-reference-setup-p t)
	(set-ocaml-function-list)
	(set-ocaml-relation-between-module&file)
	(anything-ocaml-reference-source)
	(anything-ocaml-reference-module-source)
	)))

(defvar anything-ocaml-reference-source nil)

(defun anything-ocaml-reference-source () 
  (or anything-ocaml-reference-source
      (dolist (e ocaml-functions-list)
	(let ((module (car e)))
	  (dolist (func-list (cdr e))
	    (let ((word (concat module "." (car func-list))))
	      (push word anything-ocaml-reference-source)))))
      anything-ocaml-reference-source))

(defvar anything-c-source-ocaml-reference
      '((name . "Function")
	(requires-pattern . 2)
;	(delayed)
	(candidates . (lambda () (anything-ocaml-reference-source)))
	(action . (("view" . (lambda (candidate)
			       (apply #'get-ocaml-function-info 
				      (get-ocaml-function-search candidate ocaml-functions-list))))
		   ))))

(defvar anything-ocaml-reference-module-source nil)

(defun anything-ocaml-reference-module-source ()
  (or anything-ocaml-reference-module-source
      (progn (setq anything-ocaml-reference-module-source
		   (mapcar #'car module->mli-file-alist))
	     anything-ocaml-reference-module-source)))

(defvar anything-c-source-ocaml-module
      '((name . "Module")
	(candidates . (lambda () (anything-ocaml-reference-module-source)))
	(action . (("view" . (lambda (c)
			       (find-file (get-ocaml-mli-name c)))))
		)))

(defvar anything-c-source-ocaml-module--insert
      '((name . "Module")
	(candidates . (lambda () (anything-ocaml-reference-module-source)))
	(action . (("Insert" . (lambda (c)
				 (delete-backward-char 
				  (length anything-initial-input))
				 (insert c)))
		   ("Open Module" . (lambda (c)
				 (insert (concat "open " c ";;"))))
		   ("View" . (lambda (c)
			       (find-file (get-ocaml-mli-name c)))))

		)))

(defmacro defun-anything-caller (name sources)
  `(defun ,name () (interactive)
     (let ((anything-sources ,sources)
	   (anything-initial-input (ocaml-reference-current-function)))
       (anything))))

(defun-anything-caller anything-ocaml-reference-module-insert
  (list anything-c-source-ocaml-module--insert))

(defun-anything-caller anything-ocaml-reference
  (list anything-c-source-ocaml-module anything-c-source-ocaml-reference))

(provide 'anything-ocaml-reference)

(defun ocaml-reference-current-function () (interactive)
  (let ((end (point))
	(start
	 (save-excursion
	   (+ 1 (re-search-backward "[\n\t )(]" (point-at-bol -1) t 1)
	      ))))
    (buffer-substring-no-properties start end)))

(defun anything-ocaml-reference-function--insert (candidate)
  (delete-backward-char (length anything-initial-input))
;  (insert (car (reverse (split-string candidate "\\.")))))
  (insert candidate))

(defun ocaml-completions-c-sources-maker (module function-list)
  `((name . ,module)
    (candidates . ,function-list)
    (action . (("insert" . anything-ocaml-reference-function--insert)
	       ("view" . (lambda (c)
			   (let ((function (concat ,module "." c)))
			     (apply #'get-ocaml-function-info 
				    (get-ocaml-function-search function ocaml-functions-list))))))

)))

(defun anything-ocaml-reference-completions () (interactive)
  (let ((function&module 
	 (reverse (split-string  (ocaml-reference-current-function) "\\."))))
    (let* ((function (car function&module))
	   (anything-initial-input function)
	   (current_module_p (null (cdr function&module)))
	   (modules (if current_module_p
			(ocaml-reference--search-open)
		      (let ((ms (delete function function&module)))
			(revappend ms
			 (mapcar* (lambda (x y) (concat x "." y))
				  (ocaml-reference--search-open) ms))))))
      (let ((anything-sources
	     (mapcar (lambda (module)
		       (ocaml-completions-c-sources-maker
			module
			(mapcar #'car 
				(cdr (assoc module ocaml-functions-list)))))
		     modules)))
	(if current_module_p
	    (let ((anything-sources
		   (cons anything-c-source-ocaml-module--insert
			 anything-sources)))
	      (anything))
	  (anything))))))


(defun ocaml-reference--search-open ()
  (let ((end (point))
	(re " *open +\\([^ ]+\\) *;;")
	(result '()))
    (save-excursion
      (goto-char (point-min))
      (while (re-search-forward re end t)
	(push (match-string-no-properties 1) result))
      result)))