;;; geiser-syntax.el -- utilities for parsing scheme syntax

;; Copyright (C) 2009, 2010 Jose Antonio Ortega Ruiz

;; This program is free software; you can redistribute it and/or
;; modify it under the terms of the Modified BSD License. You should
;; have received a copy of the license along with this program. If
;; not, see <http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5>.

;; Start date: Sun Feb 08, 2009 15:03



(require 'geiser-impl)
(require 'geiser-popup)
(require 'geiser-base)

(require 'scheme)

(eval-when-compile (require 'cl))


;;; Indentation:

(defmacro geiser-syntax--scheme-indent (&rest pairs)
  `(progn ,@(mapcar (lambda (p)
                      `(put ',(car p) 'scheme-indent-function ',(cadr p)))
                    pairs)))

(geiser-syntax--scheme-indent
 (begin0 1)
 (c-declare 0)
 (c-lambda 2)
 (case-lambda 0)
 (case-lambda: 0)
 (catch defun)
 (class defun)
 (class* defun)
 (compound-unit/sig 0)
 (define: defun)
 (dynamic-wind 0)
 (for/fold 2)
 (instantiate 2)
 (interface 1)
 (lambda: 1)
 (lambda/kw 1)
 (let*-values 1)
 (let*-values: 1)
 (let+ 1)
 (let: 1)
 (letrec: 1)
 (letrec* 1)
 (letrec-values 1)
 (letrec-values: 1)
 (let-values 1)
 (let-values: 1)
 (let/cc: 1)
 (let/ec 1)
 (match defun)
 (mixin 2)
 (module defun)
 (opt-lambda 1)
 (parameterize 1)
 (parameterize-break 1)
 (parameterize* 1)
 (pmatch defun)
 (quasisyntax/loc 1)
 (receive 2)
 (send* 1)
 (sigaction 1)
 (syntax-case 2)
 (syntax/loc 1)
 (type-case defun)
 (unit defun)
 (unit/sig 2)
 (unless 1)
 (when 1)
 (while 1)
 (with-fluid* 1)
 (with-fluids 1)
 (with-fluids* 1)
 (with-handlers 1)
 (with-handlers: 1)
 (with-method 1)
 (with-syntax 1))


;;; Extra syntax keywords
(defconst geiser-syntax--keywords
  '(("\\[\\(else\\)\\>" . 1)
    ("(\\(parameterize\\)\\>" . 1)))

(font-lock-add-keywords 'scheme-mode geiser-syntax--keywords)

(geiser-impl--define-caller geiser-syntax--impl-kws keywords ()
  "A variable (or thunk returning a value) giving additional,
implementation-specific entries for font-lock-keywords.")

(defun geiser-syntax--add-kws ()
  (let ((kw (geiser-syntax--impl-kws geiser-impl--implementation)))
    (when kw (font-lock-add-keywords nil kw))))


;;; A simple scheme reader

(defvar geiser-syntax--read/buffer-limit nil)

(defsubst geiser-syntax--read/eos ()
  (or (eobp)
      (and geiser-syntax--read/buffer-limit
           (<= geiser-syntax--read/buffer-limit (point)))))

(defsubst geiser-syntax--read/next-char ()
  (unless (geiser-syntax--read/eos)
    (forward-char)
    (char-after)))

(defsubst geiser-syntax--read/token (token)
  (geiser-syntax--read/next-char)
  (if (listp token) token (list token)))

(defsubst geiser-syntax--read/elisp ()
  (ignore-errors (read (current-buffer))))

(defun geiser-syntax--read/symbol ()
  (with-syntax-table scheme-mode-syntax-table
    (when (re-search-forward "\\(\\sw\\|\\s_\\)+" nil t)
      (make-symbol (match-string 0)))))

(defun geiser-syntax--read/matching (open close)
  (let ((count 1)
        (p (1+ (point))))
    (while (and (> count 0)
                (geiser-syntax--read/next-char))
      (cond ((looking-at-p open) (setq count (1+ count)))
            ((looking-at-p close) (setq count (1- count)))))
    (buffer-substring-no-properties p (point))))

(defsubst geiser-syntax--read/unprintable ()
  (geiser-syntax--read/token
   (cons 'unprintable (geiser-syntax--read/matching "<" ">"))))

(defun geiser-syntax--read/skip-comment ()
  (while (and (geiser-syntax--read/next-char)
              (nth 8 (syntax-ppss))))
  (geiser-syntax--read/next-token))

(defun geiser-syntax--read/next-token ()
  (skip-syntax-forward "->")
  (if (geiser-syntax--read/eos) '(eob)
    (case (char-after)
      (?\; (geiser-syntax--read/skip-comment))
      ((?\( ?\[) (geiser-syntax--read/token 'lparen))
      ((?\) ?\]) (geiser-syntax--read/token 'rparen))
      (?. (if (memq (car (syntax-after (1+ (point)))) '(0 11 12))
              (geiser-syntax--read/token 'dot)
            (cons 'atom (geiser-syntax--read/elisp))))
      (?\# (case (geiser-syntax--read/next-char)
             ('nil '(eob))
             (?| (geiser-syntax--read/skip-comment))
             (?: (if (geiser-syntax--read/next-char)
                     (cons 'kwd (geiser-syntax--read/elisp))
                   '(eob)))
             (?\\ (cons 'char (geiser-syntax--read/elisp)))
             (?\( (geiser-syntax--read/token 'vectorb))
             (?\< (geiser-syntax--read/unprintable))
             ((?' ?` ?,) (geiser-syntax--read/next-token))
             (t (let ((tok (geiser-syntax--read/symbol)))
                  (if tok (cons 'atom (intern (format "#%s" tok)))
                    (geiser-syntax--read/next-token))))))
      (?\' (geiser-syntax--read/token '(quote . quote)))
      (?\` (geiser-syntax--read/token
            `(backquote . ,backquote-backquote-symbol)))
      (?, (if (eq (geiser-syntax--read/next-char) ?@)
              (geiser-syntax--read/token
               `(splice . ,backquote-splice-symbol))
            `(unquote . ,backquote-unquote-symbol)))
      (?\" (cons 'string (geiser-syntax--read/elisp)))
      (t (cons 'atom (geiser-syntax--read/symbol))))))

(defsubst geiser-syntax--read/match (&rest tks)
  (let ((token (geiser-syntax--read/next-token)))
    (if (memq (car token) tks) token
      (error "Unexpected token: %s" token))))

(defsubst geiser-syntax--read/skip-until (&rest tks)
  (let (token)
    (while (and (not (memq (car token) tks))
                (not (eq (car token) 'eob)))
      (setq token (geiser-syntax--read/next-token)))
    token))

(defsubst geiser-syntax--read/try (&rest tks)
  (let ((p (point))
        (tk (ignore-errors (apply 'geiser-syntax--read/match tks))))
    (unless tk (goto-char p))
    tk))

(defun geiser-syntax--read/list ()
  (cond ((geiser-syntax--read/try 'dot)
         (let ((tail (geiser-syntax--read)))
           (geiser-syntax--read/skip-until 'eob 'rparen)
           tail))
        ((geiser-syntax--read/try 'rparen 'eob) nil)
        (t (cons (geiser-syntax--read)
                 (geiser-syntax--read/list)))))

(defun geiser-syntax--read ()
  (let ((token (geiser-syntax--read/next-token))
        (max-lisp-eval-depth (max max-lisp-eval-depth 3000)))
    (case (car token)
      (eob nil)
      (lparen (geiser-syntax--read/list))
      (vectorb (apply 'vector (geiser-syntax--read/list)))
      ((quote backquote unquote splice) (list (cdr token)
                                              (geiser-syntax--read)))
      (kwd (intern (format ":%s" (cdr token))))
      (unprintable (format "#<%s>" (cdr token)))
      ((char string atom) (cdr token))
      (t (error "Reading scheme syntax: unexpected token: %s" token)))))

(defun geiser-syntax--read-from-string (string &optional start end)
  (when (stringp string)
    (let* ((start (or start 0))
           (end (or end (length string)))
           (max-lisp-eval-depth (max max-lisp-eval-depth (- end start))))
      (with-temp-buffer
        (save-excursion (insert string))
        (cons (ignore-errors (geiser-syntax--read)) (point))))))

(defsubst geiser-syntax--form-after-point (&optional boundary)
  (let ((geiser-syntax--read/buffer-limit (and (numberp boundary) boundary)))
    (save-excursion (values (geiser-syntax--read) (point)))))


;;; Code parsing:

(defsubst geiser-syntax--symbol-at-point ()
  (and (not (nth 8 (syntax-ppss)))
       (let ((s (thing-at-point 'symbol)))
         (and s
              (not (equal s "."))
              (make-symbol (substring-no-properties s))))))

(defsubst geiser-syntax--skip-comment/string ()
  (let ((pos (nth 8 (syntax-ppss))))
    (goto-char (or pos (point)))
    pos))

(defsubst geiser-syntax--nesting-level ()
  (or (nth 0 (syntax-ppss)) 0))

(defsubst geiser-syntax--pair-length (p)
  (if (cdr (last p)) (1+ (safe-length p)) (length p)))

(defun geiser-syntax--shallow-form (boundary)
  (when (looking-at-p "\\s(")
    (save-excursion
      (forward-char)
      (let ((elems))
        (ignore-errors
          (while (< (point) boundary)
            (skip-syntax-forward "-<>")
            (when (<= (point) boundary)
              (forward-sexp)
              (let ((s (thing-at-point 'symbol)))
                (cond ((not s) (push s elems))
                      ((not (equal "." s)) (push (make-symbol s) elems)))))))
        (nreverse elems)))))

(defun geiser-syntax--scan-sexps (&optional begin)
  (let* ((fst (geiser-syntax--symbol-at-point))
         (smth (or fst (not (looking-at-p "[\s \s)\s>\s<\n]"))))
         (path (and fst `((,fst 0)))))
    (save-excursion
      (while (not (zerop (geiser-syntax--nesting-level)))
        (let ((boundary (point)))
          (geiser-syntax--skip-comment/string)
          (backward-up-list)
          (let ((form (geiser-syntax--shallow-form boundary)))
            (when (and (listp form) (car form) (symbolp (car form)))
              (let* ((len (geiser-syntax--pair-length form))
                     (pos (if smth (1- len) (progn (setq smth t) len)))
                     (prev (and (> pos 1) (nth (1- pos) form)))
                     (prev (and (keywordp prev) (list prev))))
                (push `(,(car form) ,pos ,@prev) path)))))))
    (mapcar (lambda (e) (cons (format "%s" (car e)) (cdr e)))
            (nreverse path))))

(defsubst geiser-syntax--binding-form-p (bfs sbfs f)
  (or (memq f '(define define* define-syntax define-syntax-rule
                lambda let let* letrec parameterize))
      (memq f bfs)
      (memq f sbfs)))

(defsubst geiser-syntax--binding-form*-p (sbfs f)
  (or (eq 'let* f) (memq f sbfs)))

(defsubst geiser-syntax--if-symbol (x) (and (symbolp x) x))
(defsubst geiser-syntax--if-list (x) (and (listp x) x))
(defsubst geiser-syntax--normalize (vars)
  (mapcar (lambda (i) (if (listp i) (car i) i)) vars))

(defun geiser-syntax--linearize (form)
  (cond ((not (listp form)) (list form))
	((null form) nil)
	(t (cons (car form) (geiser-syntax--linearize (cdr form))))))

(defun geiser-syntax--scan-locals (bfs sbfs form partial locals)
  (if (or (null form) (not (listp form)))
      (geiser-syntax--normalize locals)
    (if (not (geiser-syntax--binding-form-p bfs sbfs (car form)))
	(geiser-syntax--scan-locals bfs sbfs
				    (car (last form)) partial locals)
      (let* ((head (car form))
	     (name (geiser-syntax--if-symbol (cadr form)))
	     (names (if name (geiser-syntax--if-list (caddr form))
		      (geiser-syntax--if-list (cadr form))))
	     (rest (if name (cdddr form) (cddr form)))
	     (use-names (or rest
			    (not partial)
			    (geiser-syntax--binding-form*-p sbfs
							    head))))
	(when name (push name locals))
	(when use-names
	  (dolist (n (geiser-syntax--linearize names))
	    (push n locals)))
	(dolist (f (butlast rest))
	  (when (and (listp f) (eq (car f) 'define))
	    (push (cadr f) locals)))
	(geiser-syntax--scan-locals bfs sbfs
				    (car (last (or rest names)))
				    partial
				    locals)))))

(defun geiser-syntax--locals-around-point (bfs sbfs)
  (when (eq major-mode 'scheme-mode)
    (save-excursion
      (let* ((sym (unless (geiser-syntax--skip-comment/string)
		    (symbol-at-point)))
	     (boundary (point)))
        (while (not (zerop (geiser-syntax--nesting-level)))
          (backward-up-list))
        (multiple-value-bind (form end)
            (geiser-syntax--form-after-point boundary)
          (delq sym
		(geiser-syntax--scan-locals bfs sbfs form
					    (> end boundary) '())))))))


;;; Fontify strings as Scheme code:

(defun geiser-syntax--font-lock-buffer ()
  (let ((name " *geiser font lock*"))
    (or (get-buffer name)
        (let ((buffer (get-buffer-create name)))
          (set-buffer buffer)
          (let ((geiser-default-implementation
                 (or geiser-default-implementation
                     (car geiser-active-implementations))))
            (scheme-mode))
          buffer))))

(defun geiser-syntax--scheme-str (str)
  (save-current-buffer
    (set-buffer (geiser-syntax--font-lock-buffer))
    (erase-buffer)
    (insert str)
    (let ((font-lock-verbose nil)) (font-lock-fontify-buffer))
    (buffer-string)))


(provide 'geiser-syntax)
;;; geiser-syntax.el ends here