|
|
- ;;; haskell-indentation.el --- indentation module for Haskell Mode -*- lexical-binding: t -*-
-
- ;; Copyright (C) 2013 Kristof Bastiaensen, Gergely Risko
-
- ;; Author: Kristof Bastiaensen <kristof.bastiaensen@vleeuwen.org>
- ;; Author: Gergely Risko <errge@nilcons.com>
- ;; Keywords: indentation haskell
- ;; URL: https://github.com/haskell/haskell-mode
-
- ;; This file is not part of GNU Emacs.
-
- ;; This file is free software; you can redistribute it and/or modify
- ;; it under the terms of the GNU General Public License as published by
- ;; the Free Software Foundation; either version 3, or (at your option)
- ;; any later version.
-
- ;; This file is distributed in the hope that it will be useful,
- ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
- ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- ;; GNU General Public License for more details.
-
- ;; You should have received a copy of the GNU General Public License
- ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
-
- ;;; Commentary:
-
- ;; Installation:
- ;;
- ;; To turn indentation on for all Haskell buffers under Haskell mode add
- ;; this to your configuration file:
- ;;
- ;; (add-hook haskell-mode-hook 'haskell-indentation-mode)
- ;;
- ;; Otherwise, call `haskell-indentation-mode'.
-
- ;;; Code:
-
- ;; TODO eliminate magic number 2 where possible, use a variable
-
- ;; TODO `haskell-indentation-find-indentation' — fix it, get rid of "safe"
- ;; version
-
- (require 'cl-lib)
- (require 'haskell-lexeme)
-
- ;;;###autoload
- (defgroup haskell-indentation nil
- "Haskell indentation."
- :link '(custom-manual "(haskell-mode)Indentation")
- :group 'haskell
- :prefix "haskell-indentation-")
-
- (defcustom haskell-indentation-layout-offset 2
- "Extra indentation to add before expressions in a Haskell layout list."
- :type 'integer
- :group 'haskell-indentation)
-
- (defcustom haskell-indentation-starter-offset 2
- "Extra indentation after an opening keyword (e.g. \"let\")."
- :type 'integer
- :group 'haskell-indentation)
-
- (defcustom haskell-indentation-left-offset 2
- "Extra indentation after an indentation to the left (e.g. after \"do\")."
- :type 'integer
- :group 'haskell-indentation)
-
- (defcustom haskell-indentation-where-pre-offset 2
- "Extra indentation before the keyword \"where\"."
- :type 'integer
- :group 'haskell-indentation)
-
- (defcustom haskell-indentation-where-post-offset 2
- "Extra indentation after the keyword \"where\"."
- :type 'integer
- :group 'haskell-indentation)
-
- (defcustom haskell-indentation-electric-flag nil
- "Non-nil means insertion of some characters may auto reindent the line.
- If the variable `electric-indent-mode' is non-nil then this variable is
- overridden."
- :type 'symbol
- :group 'haskell-indentation)
- (make-variable-buffer-local 'haskell-indentation-electric-flag)
-
- (defvar haskell-indentation-mode-map
- (let ((map (make-sparse-keymap)))
- (define-key map (kbd "RET") #'haskell-indentation-newline-and-indent)
- (define-key map (kbd "<backtab>") #'haskell-indentation-indent-backwards)
- (define-key map (kbd ",") #'haskell-indentation-common-electric-command)
- (define-key map (kbd ";") #'haskell-indentation-common-electric-command)
- (define-key map (kbd ")") #'haskell-indentation-common-electric-command)
- (define-key map (kbd "}") #'haskell-indentation-common-electric-command)
- (define-key map (kbd "]") #'haskell-indentation-common-electric-command)
- map)
- "Keymap for `haskell-indentation-mode'.")
-
- ;;;###autoload
- (define-minor-mode haskell-indentation-mode
- "Haskell indentation mode that deals with the layout rule.
- It rebinds RET, DEL and BACKSPACE, so that indentations can be
- set and deleted as if they were real tabs."
- :keymap haskell-indentation-mode-map
- (kill-local-variable 'indent-line-function)
- (kill-local-variable 'indent-region-function)
-
- (when haskell-indentation-mode
- (when (and (bound-and-true-p haskell-indent-mode)
- (fboundp 'turn-off-haskell-indent))
- (turn-off-haskell-indent))
- (setq-local indent-line-function #'haskell-indentation-indent-line)
- (setq-local indent-region-function #'haskell-indentation-indent-region)))
-
- ;;;###autoload
- (defun turn-on-haskell-indentation ()
- "Turn on the haskell-indentation minor mode."
- (interactive)
- (haskell-indentation-mode t))
-
- (make-obsolete 'turn-on-haskell-indentation
- 'haskell-indentation-mode
- "2015-05-25")
-
- (defvar haskell-literate) ; defined in haskell-mode.el
-
- (defun haskell-indentation-bird-p ()
- "Return t if this is a literate Haskell buffer in bird style, NIL otherwise."
- (eq haskell-literate 'bird))
-
- ;;----------------------------------------------------------------------------
- ;; UI starts here
-
- (defun haskell-indentation-reindent-to (col &optional move)
- "Reindent current line to COL, move the point there if MOVE is non-NIL."
- (let* ((ci (haskell-indentation-current-indentation)))
- (save-excursion
- (move-to-column ci)
- (if (<= ci col)
- (insert-before-markers (make-string (- col ci) ? ))
- (delete-char (- col ci))))
- (when move
- (move-to-column col))))
-
- (defun haskell-indentation-indent-rigidly (start end arg)
- "Indent all lines starting in the region sideways by ARG columns.
- Called from a program, takes three arguments, START, END and ARG.
- You can remove all indentation from a region by giving a large
- negative ARG. Handles bird style literate Haskell too."
- (interactive "*r\np")
- (save-excursion
- (goto-char end)
- (let ((end-marker (point-marker)))
- (goto-char start)
- (or (bolp) (forward-line 0))
- (while (< (point) end-marker)
- (let ((ci (haskell-indentation-current-indentation)))
- (when (and t
- (eq (char-after) ?>))
- (forward-char 1))
- (skip-syntax-forward "-")
- (unless (eolp)
- (haskell-indentation-reindent-to (max 0 (+ ci arg))))
- (forward-line 1)))
- (move-marker end-marker nil))))
-
- (defun haskell-indentation-current-indentation ()
- "Column position of first non-whitespace character in current line."
- (save-excursion
- (beginning-of-line)
- (when (haskell-indentation-bird-p)
- (forward-char))
- (skip-syntax-forward "-")
- (current-column)))
-
- (defun haskell-indentation-bird-outside-code-p ()
- "Non-NIL if we are in bird literate mode, but outside of code."
- (and (haskell-indentation-bird-p)
- (or (< (current-column) 2)
- (save-excursion
- (beginning-of-line)
- (not (eq (char-after) ?>))))))
-
- (defun haskell-indentation-newline-and-indent ()
- "Insert newline and indent."
- (interactive "*")
- ;; On RET (or C-j), we:
- ;; - just jump to the next line if literate haskell, but outside code
- (if (haskell-indentation-bird-outside-code-p)
- (progn
- (delete-horizontal-space)
- (newline))
- ;; - save the current column
- (let ((ci (haskell-indentation-current-indentation)))
- ;; - jump to the next line and reindent to at the least same level
- (delete-horizontal-space)
- (newline)
- ;; calculate indentation after newline is inserted because if we
- ;; break an identifier we might create a keyword, for example
- ;; "dowhere" => "do where"
- (let ((indentations (or (haskell-indentation-find-indentations)
- '(0))))
- (when (haskell-indentation-bird-p)
- (insert "> "))
- (haskell-indentation-reindent-to
- (haskell-indentation-next-indentation (- ci 1) indentations 'nofail)
- 'move)))))
-
- (defun haskell-indentation-next-indentation (col indentations &optional nofail)
- "Find the leftmost indentation which is greater than COL.
- Indentations are taken from INDENTATIONS, which should be a
- list. Return the last indentation if there are no bigger ones and
- NOFAIL is non-NIL."
- (when (null indentations)
- (error "haskell-indentation-next-indentation called with empty list"))
- (or (cl-find-if (lambda (i) (> i col)) indentations)
- (when nofail
- (car (last indentations)))))
-
- (defun haskell-indentation-previous-indentation (col indentations &optional nofail)
- "Find the rightmost indentation less than COL from INDENTATIONS.
- When no indentations are less than COL, return the rightmost indentation
- if NOFAIL is non-nil, or nil otherwise."
- (when (null indentations)
- (error "haskell-indentation-previous-indentation called with empty list"))
- (let ((rev (reverse indentations)))
- (or (cl-find-if (lambda (i) (< i col)) rev)
- (when nofail
- (car rev)))))
-
- (defvar haskell-indentation-dyn-last-direction nil
- "") ; FIXME
- (defvar haskell-indentation-dyn-last-indentations nil
- "") ; FIXME
-
- (defun haskell-indentation-indent-line ()
- "Indent current line, cycle though indentation positions.
- Do nothing inside multiline comments and multiline strings.
- Start enumerating the indentation points to the right. The user
- can continue by repeatedly pressing TAB. When there is no more
- indentation points to the right, we switch going to the left."
- (interactive "*")
- ;; try to repeat
- (when (not (haskell-indentation-indent-line-repeat))
- (setq haskell-indentation-dyn-last-direction nil)
- ;; parse error is intentionally not cought here, it may come from
- ;; `haskell-indentation-find-indentations', but escapes the scope
- ;; and aborts the opertaion before any moving happens
- (let* ((cc (current-column))
- (ci (haskell-indentation-current-indentation))
- (inds (save-excursion
- (move-to-column ci)
- (or (haskell-indentation-find-indentations)
- '(0))))
- (valid (memq ci inds))
- (cursor-in-whitespace (< cc ci)))
-
- (if (and valid cursor-in-whitespace)
- (move-to-column ci)
- (haskell-indentation-reindent-to
- (haskell-indentation-next-indentation ci inds 'nofail)
- cursor-in-whitespace))
- (setq haskell-indentation-dyn-last-direction 'right
- haskell-indentation-dyn-last-indentations inds))))
-
- (defun haskell-indentation-indent-line-repeat ()
- "Cycle though indentation positions."
- (cond
- ((and (memq last-command
- '(indent-for-tab-command
- haskell-indentation-indent-backwards))
- (eq haskell-indentation-dyn-last-direction 'region))
- (let ((mark-even-if-inactive t))
- (haskell-indentation-indent-rigidly
- (region-beginning)
- (region-end)
- 1))
- t)
- ((and (eq last-command 'indent-for-tab-command)
- (memq haskell-indentation-dyn-last-direction '(left right))
- haskell-indentation-dyn-last-indentations)
- (let ((ci (haskell-indentation-current-indentation)))
- (if (eq haskell-indentation-dyn-last-direction 'left)
- (haskell-indentation-reindent-to
- (haskell-indentation-previous-indentation
- ci haskell-indentation-dyn-last-indentations 'nofail))
- ;; right
- (if (haskell-indentation-next-indentation
- ci haskell-indentation-dyn-last-indentations)
- (haskell-indentation-reindent-to
- (haskell-indentation-next-indentation
- ci haskell-indentation-dyn-last-indentations 'nofail))
- ;; but failed, switch to left
- (setq haskell-indentation-dyn-last-direction 'left)
- (haskell-indentation-indent-line-repeat)))
- t))
- (t nil)))
-
- (defun haskell-indentation-indent-region (_start _end)
- "This function does nothing.
-
- It is better to do nothing to indent region in Haskell than to
- break the semantics of indentation. This function is used for
- `indent-region-function' because the default is to call
- `indent-line-function' on every line from START to END and that
- also produces catastrophic results.
-
- Someday we will have indent region that preserves semantics and
- fixes up only indentation."
- nil)
-
- (defun haskell-indentation-indent-backwards ()
- "Indent the current line to the previous indentation point."
- (interactive "*")
- (cond
- ((and (memq last-command
- '(indent-for-tab-command haskell-indentation-indent-backwards))
- (eq haskell-indentation-dyn-last-direction 'region))
- (let ((mark-even-if-inactive t))
- (haskell-indentation-indent-rigidly (region-beginning) (region-end) -1)))
- ((use-region-p)
- (setq haskell-indentation-dyn-last-direction 'region)
- (haskell-indentation-indent-rigidly (region-beginning) (region-end) -1)
- (message "Press TAB or S-TAB again to indent the region more"))
- (t
- (setq haskell-indentation-dyn-last-direction nil)
- (let* ((cc (current-column))
- (ci (haskell-indentation-current-indentation))
- (inds (save-excursion
- (move-to-column ci)
- (or (haskell-indentation-find-indentations)
- '(0))))
- (cursor-in-whitespace (< cc ci))
- (pi (haskell-indentation-previous-indentation ci inds)))
- (if (null pi)
- ;; if there are no more indentations to the left, just go to column 0
- (haskell-indentation-reindent-to
- (car (haskell-indentation-first-indentation)) cursor-in-whitespace)
- (haskell-indentation-reindent-to pi cursor-in-whitespace))))))
-
- (defun haskell-indentation-common-electric-command (arg)
- "Call `self-insert-command' to insert the character typed ARG times
- and indent when all of the following are true:
- 1) The character is the first non-whitespace character on the line.
- 2) There is only one possible indentation position.
- 3) The variable `electric-indent-mode' or `haskell-indentation-electric-flag'
- is non-nil.
- 4) The point is not in a comment, string, or quasiquote."
- (interactive "*p")
- (let ((col (current-column))
- ind)
- (self-insert-command arg)
- (when (and (or haskell-indentation-electric-flag
- electric-indent-mode)
- (= (haskell-indentation-current-indentation)
- col)
- (> arg 0)
- (not (nth 8 (syntax-ppss)))
- (= 1 (save-excursion
- (move-to-column col)
- (length (setq ind (haskell-indentation-find-indentations))))))
- (haskell-indentation-reindent-to (car ind)))))
-
- ;;----------------------------------------------------------------------------
- ;; Parser Starts Here
-
- ;; The parser is implemented as a recursive descent parser. Each parser
- ;; advances the point to after the expression it parses, and sets the
- ;; dynamic scoped variables containing the information about the
- ;; indentations. The dynamic scoping allows transparent backtracking to
- ;; previous states of these variables. A new state can be set using `let'.
- ;; When the scope of this function ends, the variable is automatically
- ;; reverted to its old value.
-
- ;; This is basicly a performance hack. It would have been possible to
- ;; thread this state using a association-list through the parsers, but it
- ;; would be probably more complicated and slower due to the lack of real
- ;; closures in Emacs Lisp.
- ;;
- ;; When finished parsing, the tokenizer returns 'end-token, and
- ;; following-token is set to the token after point. The parser adds its
- ;; indentations to possible-indentations and returns to it's parent, or
- ;; exits non-locally by throwing parse-end, so that the parent will not add
- ;; new indentations to it.
-
- ;; the parse state:
- (defvar following-token) ;; the next token after parsing finished
- ;; the token at the current parser point or a pseudo-token (see
- ;; `haskell-indentation-read-next-token')
- (defvar current-token)
- (defvar previous-token)
- (defvar left-indent) ;; most left possible indentation
- (defvar starter-indent) ;; column at a keyword
- (defvar current-indent) ;; the most right indentation
- (defvar layout-indent) ;; the column of the layout list
- (defvar possible-indentations) ;; the return value of the indentations
- (defvar indentation-point) ;; where to stop parsing
- (defvar implicit-layout-active) ;; is "off-side" rule active?
-
- (defun haskell-indentation-goto-least-indentation ()
- "" ; FIXME
- (beginning-of-line)
- (if (haskell-indentation-bird-p)
- (catch 'return
- (while t
- (when (not (eq (char-after) ?>))
- (forward-line)
- (forward-char 2)
- (throw 'return nil))
- (let ((ps (nth 8 (syntax-ppss))))
- (when ps ;; inside comment or string
- (goto-char ps)
- (beginning-of-line)))
- (when (and (>= 2 (haskell-indentation-current-indentation))
- (not (looking-at ">\\s-*$")))
- (forward-char 2)
- (throw 'return nil))
- (when (bobp)
- (forward-char 2)
- (throw 'return nil))
- (forward-line -1)))
- ;; not bird style
- (catch 'return
- (while (not (bobp))
- (let ((point (point)))
- ;; (forward-comment -1) gets lost if there are unterminated
- ;; string constants and does not move point anywhere. We fix
- ;; that case with (forward-line -1)
- (forward-comment (- (buffer-size)))
- (if (equal (point) point)
- (forward-line -1)
- (beginning-of-line)))
- (let* ((ps (syntax-ppss))
- (start-of-comment-or-string (nth 8 ps))
- (start-of-list-expression (nth 1 ps)))
- (cond
- (start-of-comment-or-string
- ;; inside comment or string
- (goto-char start-of-comment-or-string))
- (start-of-list-expression
- ;; inside a parenthesized expression
- (goto-char start-of-list-expression))
- ((= 0 (haskell-indentation-current-indentation))
- (throw 'return nil))))))
- (beginning-of-line)
- (when (bobp)
- (forward-comment (buffer-size)))))
-
- (defun haskell-indentation-parse-to-indentations ()
- "" ; FIXME
- (save-excursion
- (skip-syntax-forward "-")
- (let ((indentation-point (point))
- (layout-indent 0)
- (current-indent haskell-indentation-layout-offset)
- (starter-indent haskell-indentation-layout-offset)
- (left-indent haskell-indentation-layout-offset)
- (case-fold-search nil)
- current-token
- previous-token
- following-token
- possible-indentations
- implicit-layout-active)
- (haskell-indentation-goto-least-indentation)
- (if (<= indentation-point (point))
- (haskell-indentation-first-indentation)
- (setq current-token (haskell-indentation-peek-token))
- (catch 'parse-end
- (haskell-indentation-toplevel))
- possible-indentations))))
-
- (defun haskell-indentation-first-indentation ()
- "Return column of first indentation."
- (list (if (haskell-indentation-bird-p) 2 0)))
-
- (defun haskell-indentation-find-indentations ()
- "Return list of indentation positions corresponding to actual cursor position."
- (let ((ppss (syntax-ppss)))
- (cond
- ((nth 3 ppss)
- (if (save-excursion
- (and (forward-line -1)
- (< (nth 8 ppss) (point))))
- ;; if this string goes over more than one line we want to
- ;; sync with the last line, not the first one
- (list (save-excursion
- (forward-line -1)
- (current-indentation)))
-
- (append
- (haskell-indentation-first-indentation)
- (list (save-excursion
- (goto-char (nth 8 ppss))
- (current-column))))))
- ((nth 4 ppss)
- (if (save-excursion
- (and (skip-syntax-forward "-")
- (eolp)
- (not (> (forward-line 1) 0))
- (not (nth 4 (syntax-ppss)))))
- (haskell-indentation-parse-to-indentations)
- (haskell-indentation-first-indentation)))
- (t
- (haskell-indentation-parse-to-indentations)))))
-
- (defconst haskell-indentation-unicode-tokens
- '(("→" . "->") ;; #x2192 RIGHTWARDS ARROW
- ("∷" . "::") ;; #x2237 PROPORTION
- ("←" . "<-") ;; #x2190 LEFTWARDS ARROW
- ("⇒" . "=>") ;; #x21D2 RIGHTWARDS DOUBLE ARROW
- ("∀" . "forall") ;; #x2200 FOR ALL
- ("⤙" . "-<") ;; #x2919 LEFTWARDS ARROW-TAIL
- ("⤚" . ">-") ;; #x291A RIGHTWARDS ARROW-TAIL
- ("⤛" . "-<<") ;; #x291B LEFTWARDS DOUBLE ARROW-TAIL
- ("⤜" . ">>-") ;; #x291C RIGHTWARDS DOUBLE ARROW-TAIL
- ("★" . "*")) ;; #x2605 BLACK STAR
- "Translation from UnicodeSyntax tokens to their ASCII representation.")
-
- (defconst haskell-indentation-toplevel-list
- `(("module" . haskell-indentation-module)
- ("signature" . haskell-indentation-module)
- ("data" . haskell-indentation-data)
- ("type" . haskell-indentation-data)
- ("newtype" . haskell-indentation-data)
- ("import" . haskell-indentation-import)
- ("foreign" . haskell-indentation-foreign)
- ("where" . haskell-indentation-toplevel-where)
- ("class" . haskell-indentation-class-declaration)
- ("instance" . haskell-indentation-class-declaration)
- ("deriving" . haskell-indentation-deriving))
- "Alist of toplevel keywords with associated parsers.")
-
- (defconst haskell-indentation-type-list
- `(("::" .
- ,(apply-partially 'haskell-indentation-with-starter
- (apply-partially 'haskell-indentation-separated
- 'haskell-indentation-type '("->" "=>"))))
- ("(" .
- ,(apply-partially 'haskell-indentation-list
- 'haskell-indentation-type ")" ","))
- ("[" .
- ,(apply-partially 'haskell-indentation-list
- 'haskell-indentation-type "]" ","))
- ("{" .
- ,(apply-partially 'haskell-indentation-list
- 'haskell-indentation-type "}" ",")))
- "Alist of tokens in type declarations with associated parsers.")
-
- (defconst haskell-indentation-expression-list
- `(("data" . haskell-indentation-data)
- ("type" . haskell-indentation-data)
- ("newtype" . haskell-indentation-data)
- ("if" . haskell-indentation-if)
- ("let" .
- ,(apply-partially 'haskell-indentation-phrase
- '(haskell-indentation-declaration-layout
- "in" haskell-indentation-expression)))
- ("do" .
- ,(apply-partially 'haskell-indentation-with-starter
- 'haskell-indentation-expression-layout))
- ("mdo" .
- ,(apply-partially 'haskell-indentation-with-starter
- 'haskell-indentation-expression-layout))
- ("rec" .
- ,(apply-partially 'haskell-indentation-with-starter
- 'haskell-indentation-expression-layout))
- ("case" .
- ,(apply-partially 'haskell-indentation-phrase
- '(haskell-indentation-expression
- "of" haskell-indentation-case-layout)))
- ("\\" .
- ,(apply-partially 'haskell-indentation-with-starter
- 'haskell-indentation-lambda-maybe-lambdacase))
- ("proc" .
- ,(apply-partially 'haskell-indentation-phrase
- '(haskell-indentation-expression
- "->" haskell-indentation-expression)))
- ("where" .
- ,(apply-partially 'haskell-indentation-with-starter
- 'haskell-indentation-declaration-layout nil t))
- ("::" . haskell-indentation-scoped-type)
- ("=" .
- ,(apply-partially 'haskell-indentation-statement-right
- 'haskell-indentation-expression))
- ("<-" .
- ,(apply-partially 'haskell-indentation-statement-right
- 'haskell-indentation-expression))
- ("(" .
- ,(apply-partially 'haskell-indentation-list
- 'haskell-indentation-expression
- ")"
- '(list "," "->")))
- ("[" .
- ,(apply-partially 'haskell-indentation-list
- 'haskell-indentation-expression "]" "," "|"))
- ("{" .
- ,(apply-partially 'haskell-indentation-list
- 'haskell-indentation-expression "}" ",")))
- "Alist of keywords in expressions with associated parsers.")
-
- (defun haskell-indentation-expression-layout ()
- "Parse layout list with expressions, such as after \"do\"."
- (haskell-indentation-layout #'haskell-indentation-expression))
-
- (defun haskell-indentation-declaration-layout ()
- "Parse layout list with declarations, such as after \"where\"."
- (haskell-indentation-layout #'haskell-indentation-declaration))
-
- (defun haskell-indentation-case-layout ()
- "Parse layout list with case expressions."
- (haskell-indentation-layout #'haskell-indentation-case))
-
- (defun haskell-indentation-lambda-maybe-lambdacase ()
- "Parse lambda or lambda-case expression.
- After a lambda (backslash) there are two possible cases:
-
- - the new lambdacase expression, that can be recognized by the
- next token being \"case\";
-
- - or simply an anonymous function definition in the form of
- \"expression -> expression\"."
- (if (string= current-token "case")
- (haskell-indentation-with-starter
- #'haskell-indentation-case-layout)
- (haskell-indentation-phrase-rest
- '(haskell-indentation-expression "->" haskell-indentation-expression))))
-
- (defun haskell-indentation-fundep ()
- "Parse functional dependency."
- (haskell-indentation-with-starter
- (apply-partially #'haskell-indentation-separated
- #'haskell-indentation-fundep1 ",")))
-
- (defun haskell-indentation-fundep1 ()
- "Parse an item in functional dependency declaration."
- (let ((current-indent (current-column)))
- (while (member current-token '(value "->"))
- (haskell-indentation-read-next-token))
- (when (and (eq current-token 'end-tokens)
- (member following-token '(value "->")))
- (haskell-indentation-add-indentation current-indent))))
-
- (defun haskell-indentation-toplevel ()
- "Parse toplevel statements."
- (haskell-indentation-layout
- (lambda ()
- (let ((parser (assoc current-token haskell-indentation-toplevel-list)))
- (if parser
- (funcall (cdr parser))
- (haskell-indentation-declaration))))))
-
- (defun haskell-indentation-type ()
- "Parse type declaration."
- (let ((current-indent (current-column)))
- (catch 'return
- (while t
- (cond
- ((member current-token '(value operator "->" "=>"))
- (haskell-indentation-read-next-token))
-
- ((eq current-token 'end-tokens)
- (when (member following-token
- '(value operator no-following-token
- "(" "[" "{" "::"))
- (if (equal following-token "=>")
- (haskell-indentation-add-indentation starter-indent)
- (haskell-indentation-add-indentation current-indent))
- (haskell-indentation-add-indentation left-indent))
- (throw 'return nil))
- (t (let ((parser (assoc current-token haskell-indentation-type-list)))
- (if (not parser)
- (throw 'return nil)
- (funcall (cdr parser))))))))))
-
- (defun haskell-indentation-type-1 ()
- "Parse a single type declaration."
- (let ((current-indent (current-column)))
- (catch 'return
- (cond
- ((member current-token '(value operator "->" "=>"))
- (haskell-indentation-read-next-token))
-
- ((eq current-token 'end-tokens)
- (when (member following-token
- '(value operator no-following-token
- "->" "=>" "(" "[" "{" "::"))
- (haskell-indentation-add-indentation current-indent))
- (throw 'return nil))
- (t (let ((parser (assoc current-token haskell-indentation-type-list)))
- (if (not parser)
- (throw 'return nil)
- (funcall (cdr parser)))))))))
-
- (defun haskell-indentation-scoped-type ()
- "Parse scoped type declaration.
-
- For example
- let x :: Int = 12
- do x :: Int <- return 12"
- (haskell-indentation-with-starter
- (apply-partially #'haskell-indentation-separated #'haskell-indentation-type '("->" "=>")))
- (when (member current-token '("<-" "="))
- (haskell-indentation-statement-right #'haskell-indentation-expression)))
-
- (defun haskell-indentation-data ()
- "Parse data or type declaration."
- (haskell-indentation-read-next-token)
- (when (string= current-token "instance")
- (haskell-indentation-read-next-token))
- (haskell-indentation-type)
- (cond ((eq current-token 'end-tokens)
- (when (member following-token '("=" "where"))
- (haskell-indentation-add-indentation current-indent)
- (throw 'parse-end nil)))
- ((string= current-token "=")
- (let ((starter-indent-inside (current-column)))
- (haskell-indentation-with-starter
- (lambda ()
- (haskell-indentation-separated
- #'haskell-indentation-expression "|")))
- (cond
- ((equal current-token 'end-tokens)
- (when (string= following-token "deriving")
- (haskell-indentation-push-indentation starter-indent-inside)
- (haskell-indentation-add-left-indent)))
- ((equal current-token "deriving")
- (haskell-indentation-with-starter
- #'haskell-indentation-type-1)))))
- ((string= current-token "where")
- (haskell-indentation-with-starter
- #'haskell-indentation-expression-layout nil)
- (cond
- ((equal current-token 'end-tokens)
- (when (string= following-token "deriving")
- (haskell-indentation-add-left-indent)))
- ((equal current-token "deriving")
- (haskell-indentation-with-starter
- #'haskell-indentation-type-1))))))
-
- (defun haskell-indentation-import ()
- "Parse import declaration."
- (haskell-indentation-with-starter #'haskell-indentation-expression))
-
- (defun haskell-indentation-foreign ()
- "Parse foreign import declaration."
- (haskell-indentation-with-starter (apply-partially #'haskell-indentation-expression '(value operator "import"))))
-
- (defun haskell-indentation-class-declaration ()
- "Parse class declaration."
- (haskell-indentation-with-starter
- (lambda ()
- (haskell-indentation-type)
- (when (string= current-token "|")
- (haskell-indentation-fundep))
- (when (string= current-token "where")
- (haskell-indentation-with-starter
- #'haskell-indentation-declaration-layout nil)))))
-
- (defun haskell-indentation-deriving ()
- "Parse standalone declaration."
- (haskell-indentation-with-starter
- (lambda ()
- (when (string= "instance" current-token)
- (haskell-indentation-read-next-token))
- (when (equal current-token 'end-tokens)
- (haskell-indentation-add-left-indent)
- (throw 'parse-end nil))
- (haskell-indentation-type)
- (when (string= current-token "|")
- (haskell-indentation-fundep)))))
-
- (defun haskell-indentation-module ()
- "Parse module declaration."
- (haskell-indentation-with-starter
- (lambda ()
- (haskell-indentation-read-next-token)
- (when (equal current-token 'layout-item)
- (haskell-indentation-read-next-token))
- (when (string= current-token "(")
- (haskell-indentation-list
- #'haskell-indentation-module-export
- ")" ","))
- (if (string= current-token "where")
- (haskell-indentation-read-next-token)
-
- (when (eq current-token 'end-tokens)
- (when (member following-token '(value no-following-token "("))
- (haskell-indentation-add-indentation
- (+ starter-indent haskell-indentation-starter-offset))
- (haskell-indentation-add-indentation
- (+ left-indent haskell-indentation-starter-offset))
- (throw 'parse-end nil))
- (haskell-indentation-add-layout-indent)
- (throw 'parse-end nil))))))
-
- (defun haskell-indentation-toplevel-where ()
- "Parse 'where' that we may hit as a standalone in module declaration."
- (haskell-indentation-read-next-token)
-
- (when (eq current-token 'end-tokens)
- (haskell-indentation-add-layout-indent)
- (throw 'parse-end nil)))
-
- (defun haskell-indentation-module-export ()
- "Parse export list."
- (cond ((string= current-token "module")
- (let ((current-indent (current-column)))
- (haskell-indentation-read-next-token)
- (cond ((eq current-token 'end-tokens)
- (haskell-indentation-add-indentation current-indent))
- ((eq current-token 'value)
- (haskell-indentation-read-next-token)))))
- (t (haskell-indentation-type))))
-
- (defun haskell-indentation-list (parser end sep &optional stmt-sep)
- "Parse a list, pair or other expression containing multiple
- items parsed by PARSER, separated by SEP or STMT-SEP, and ending
- with END."
- ;; note that we use macro expansion here to preserver Emacs 23
- ;; compatibility and its lack of lexical binding
- (haskell-indentation-with-starter
- `(lambda ()
- (let ((implicit-layout-active nil))
- (haskell-indentation-separated
- #',parser ,sep ,stmt-sep)))
- end))
-
- (defun haskell-indentation-with-starter (parser &optional end where-expr?)
- "Parse an expression starting with a keyword or parenthesis.
- Skip the keyword or parenthesis." ; FIXME: better description needed
- (let ((starter-column (current-column))
- (current-indent current-indent)
- (left-indent
- (if (= (current-column) (haskell-indentation-current-indentation))
- (current-column)
- left-indent)))
- (haskell-indentation-read-next-token)
- (when (eq current-token 'end-tokens)
- (cond ((equal following-token end)
- ;; indent before keyword or parenthesis
- (haskell-indentation-add-indentation starter-column))
- (where-expr?
- ;; left indent + where post indent
- (haskell-indentation-add-where-post-indent left-indent))
- (t
- (haskell-indentation-add-left-indent)))
- (throw 'parse-end nil))
- (let* ((current-indent (current-column))
- (starter-indent (min starter-column current-indent))
- (left-indent
- (if end
- (+ starter-indent haskell-indentation-starter-offset)
- left-indent)))
- (funcall parser)
- (cond ((eq current-token 'end-tokens)
- (when (equal following-token end)
- ;; indent before keyword or parenthesis
- (haskell-indentation-add-indentation starter-indent))
- ;; add no more indentations if we expect a closing keyword
- (when end
- (throw 'parse-end nil)))
- ((equal current-token end)
- (haskell-indentation-read-next-token))))))
-
- (defun haskell-indentation-case-alternative ()
- "" ; FIXME
- (setq left-indent (current-column))
- (haskell-indentation-separated #'haskell-indentation-expression "," nil)
- (cond ((eq current-token 'end-tokens)
- (haskell-indentation-add-indentation current-indent))
- ((string= current-token "->")
- (haskell-indentation-statement-right #'haskell-indentation-expression))
- ;; otherwise fallthrough
- ))
-
- (defun haskell-indentation-case ()
- "" ; FIXME
- (haskell-indentation-expression)
- (cond ((eq current-token 'end-tokens)
- (haskell-indentation-add-indentation current-indent))
- ((string= current-token "|")
- (haskell-indentation-with-starter
- (apply-partially #'haskell-indentation-separated
- #'haskell-indentation-case-alternative "|" nil)
- nil))
- ((string= current-token "->")
- (haskell-indentation-statement-right #'haskell-indentation-expression))
- ;; otherwise fallthrough
- ))
-
- (defun haskell-indentation-statement-right (parser)
- "Process right side of a statement.
- Set `current-indent' to the current column and calls the given
- parser. If parsing ends here, set indentation to left-indent."
- (haskell-indentation-read-next-token)
- (when (eq current-token 'end-tokens)
- (haskell-indentation-add-left-indent)
- (haskell-indentation-add-indentation current-indent)
- (throw 'parse-end nil))
- (funcall parser)
- (when (equal current-token "where")
- (haskell-indentation-with-starter
- #'haskell-indentation-expression-layout nil)))
-
- (defun haskell-indentation-guard ()
- "Parse \"guard\" statement."
- (setq left-indent (current-column))
- (haskell-indentation-separated
- #'haskell-indentation-expression "," nil))
-
- (defun haskell-indentation-declaration ()
- "Parse function or type declaration."
- (haskell-indentation-separated #'haskell-indentation-expression "," nil)
- (when (string= current-token "|")
- (haskell-indentation-with-starter
- (apply-partially #'haskell-indentation-separated
- #'haskell-indentation-guard "|" nil)
- nil))
- (when (eq current-token 'end-tokens)
- (when (member following-token '("|" "=" "::" ","))
- (haskell-indentation-add-indentation current-indent)
- (throw 'parse-end nil))))
-
- (defun haskell-indentation-layout (parser)
- "Parse layout list, where each layout item is parsed by parser."
- (if (string= current-token "{")
- (haskell-indentation-list parser "}" ";") ; explicit layout
- (haskell-indentation-implicit-layout-list parser)))
-
- (defun haskell-indentation-expression-token-p (token)
- "Return non-NIL value if TOKEN is an expression token."
- (member token
- '("if" "let" "do" "case" "\\" "(" "{" "[" "::"
- value operator no-following-token)))
-
- (defun haskell-indentation-expression (&optional accepted-tokens)
- "Parse an expression until an unknown token is encountered."
- (catch 'return
- (let ((current-indent (current-column)))
- (unless accepted-tokens
- (setq accepted-tokens '(value operator)))
- (while t
- (cond
- ((memq current-token accepted-tokens)
- (haskell-indentation-read-next-token))
- ((eq current-token 'end-tokens)
- (cond ((string= following-token "where")
- (haskell-indentation-add-where-pre-indent)) ; before a where
- ((haskell-indentation-expression-token-p following-token)
- ;; a normal expression can be either continued or have
- ;; left indent
- (haskell-indentation-add-indentation
- current-indent)
- (haskell-indentation-add-indentation
- left-indent)))
- (throw 'return nil))
- (t (let ((parser (assoc current-token
- haskell-indentation-expression-list)))
- (when (null parser)
- (throw 'return nil)) ; not expression token, so exit
- (funcall (cdr parser)) ; run parser
-
- ;; after an 'open' expression such as 'if', exit
- (unless (member (car parser) '("(" "[" "{" "case"))
- (throw 'return nil)))))))))
-
- (defun haskell-indentation-separated (parser separator &optional stmt-separator)
- "Evaluate PARSER separated by SEPARATOR and STMT-SEPARATOR.
- If STMT-SEPARATOR is not NIL, it will be used to set a new starter-indent.
-
- For example:
-
- [ i | i <- [1..10]
- ,"
- (catch 'return
- (unless (listp separator)
- (setq separator (list separator)))
- (unless (listp stmt-separator)
- (setq stmt-separator (list stmt-separator)))
- (while t
- (funcall parser)
- (cond ((member current-token separator)
- (haskell-indentation-at-separator))
-
- ((member current-token stmt-separator)
- (setq starter-indent (current-column))
- (haskell-indentation-at-separator))
-
- ((eq current-token 'end-tokens)
- (when (or (member following-token separator)
- (member following-token stmt-separator))
- ;; Set an indentation before a separator, for example:
- ;; [ 1 or [ 1 | a
- ;; , 2 , 20
- (haskell-indentation-add-indentation starter-indent)
- (when (< left-indent starter-indent)
- (haskell-indentation-add-indentation left-indent))
- (throw 'parse-end nil))
- (when (equal following-token 'no-following-token)
- ;; Set an indentation before a separator, for example:
- ;; [ 1 or [ 1 | a
- ;; , 2 , 20
- (haskell-indentation-add-indentation starter-indent)
- (haskell-indentation-add-indentation left-indent))
- (throw 'return nil))
- (t (throw 'return nil))))))
-
- (defun haskell-indentation-at-separator ()
- "At a separator.
-
- If at a new line, set starter-indent at the separator
- and current-indent after the separator, for example:
-
- l = [ 1
- , 2
- , -- start now here."
- (let ((separator-column
- (and (= (current-column) (haskell-indentation-current-indentation))
- (current-column))))
- (haskell-indentation-read-next-token)
- (cond ((eq current-token 'end-tokens)
- (haskell-indentation-add-indentation current-indent)
- (haskell-indentation-add-indentation left-indent)
- (throw 'return nil))
- (separator-column ; on the beginning of the line
- (setq current-indent (current-column))
- (setq starter-indent separator-column)
- (setq left-indent
- (+ starter-indent haskell-indentation-starter-offset))))))
-
- (defun haskell-indentation-implicit-layout-list (parser)
- "An implicit layout list, elements are parsed with PARSER.
- This sets the `layout-indent' variable to the column where the
- layout starts."
- (let* ((layout-indent (current-column))
- (current-indent (current-column))
- (left-indent (current-column))
- (implicit-layout-active t))
- (catch 'return
- (while t
- (let ((left-indent left-indent))
- (funcall parser))
- (cond ((member current-token '(layout-item ";"))
- (haskell-indentation-read-next-token))
- ((eq current-token 'end-tokens)
- (when (or (and
- (not (member following-token '("{" operator)))
- (not (member previous-token '(operator)))
- (haskell-indentation-expression-token-p following-token))
- (string= following-token ";")
- (and (equal layout-indent 0)
- (member following-token (mapcar #'car haskell-indentation-toplevel-list))
- (not (string= following-token "where"))))
- (haskell-indentation-add-layout-indent))
- (throw 'return nil))
- (t (throw 'return nil))))))
- ;; put `haskell-indentation-read-next-token' outside the current-indent
- ;; definition so it will not return 'layout-end again
- (when (eq current-token 'layout-end)
- (let ((implicit-layout-active t))
- ;; leave layout at 'layout-end or illegal token
- (haskell-indentation-read-next-token))))
-
- (defun haskell-indentation-if ()
- "" ; FIXME
- (haskell-indentation-with-starter
- (lambda ()
- (if (string= current-token "|")
- (haskell-indentation-with-starter
- (lambda ()
- (haskell-indentation-separated
- #'haskell-indentation-case-alternative "|" nil))
- nil)
- (haskell-indentation-phrase-rest
- '(haskell-indentation-expression
- "then" haskell-indentation-expression
- "else" haskell-indentation-expression))))
- nil))
-
- (defun haskell-indentation-phrase (phrase)
- "" ; FIXME
- (haskell-indentation-with-starter
- (apply-partially #'haskell-indentation-phrase-rest phrase)
- nil))
-
- (defun haskell-indentation-phrase-rest (phrase1)
- "" ; FIXME
- (while phrase1
- (let ((phrase phrase1))
- (setq phrase1 nil)
- (let ((current-indent (current-column))
- (left-indent left-indent)
- (layout-indent layout-indent))
- (funcall (car phrase)))
- (cond
- ((eq current-token 'end-tokens)
- (cond ((null (cdr phrase))) ;; fallthrough
- ((equal following-token (cadr phrase))
- (haskell-indentation-add-indentation starter-indent)
- (unless (member following-token '("," ";"))
- ;; we want to keep comma and semicolon aligned always
- (haskell-indentation-add-indentation left-indent))
- (throw 'parse-end nil))
- ((string= (cadr phrase) "in")
- (when (= left-indent layout-indent)
- (haskell-indentation-add-layout-indent)
- (throw 'parse-end nil)))
- (t (throw 'parse-end nil))))
- ((null (cdr phrase)))
- ((equal (cadr phrase) current-token)
- (haskell-indentation-read-next-token)
- (when (eq current-token 'end-tokens)
- (haskell-indentation-add-indentation
- (+ starter-indent haskell-indentation-starter-offset))
- (haskell-indentation-add-indentation
- (+ left-indent haskell-indentation-starter-offset))
- (throw 'parse-end nil))
- (setq phrase1 (cddr phrase)))
- ((string= (cadr phrase) "in"))))))
-
- (defun haskell-indentation-add-indentation (indent)
- "" ; FIXME
- (haskell-indentation-push-indentation
- (if (<= indent layout-indent)
- (+ layout-indent haskell-indentation-layout-offset)
- indent)))
-
- (defun haskell-indentation-add-layout-indent ()
- "" ; FIXME
- (haskell-indentation-push-indentation layout-indent))
-
- (defun haskell-indentation-add-where-pre-indent ()
- "" ; FIXME
- (haskell-indentation-push-indentation
- (+ layout-indent haskell-indentation-where-pre-offset))
- (if (= layout-indent haskell-indentation-layout-offset)
- (haskell-indentation-push-indentation
- haskell-indentation-where-pre-offset)))
-
- (defun haskell-indentation-add-where-post-indent (indent)
- "" ; FIXME
- (haskell-indentation-push-indentation
- (+ indent haskell-indentation-where-post-offset)))
-
- (defun haskell-indentation-add-left-indent ()
- "" ; FIXME
- (haskell-indentation-add-indentation
- (+ left-indent haskell-indentation-left-offset)))
-
- (defun haskell-indentation-push-indentation (indent)
- "Add INDENT to list of possible indentations.
-
- Add INDENT to `possible-indentations' if it is not there
- yet. Keep the list in ascending order."
- (unless (member indent possible-indentations)
- (setq possible-indentations
- (sort (cons indent possible-indentations) #'<))))
-
- (defun haskell-indentation-read-next-token ()
- "Go to the next token and set current-token to the next token.
-
- The following symbols are used as pseudo tokens:
-
- 'layout-item: A new item in a layout list. The next token
- will be the first token from the item.
-
- 'layout-end: the end of a layout list. Next token will be
- the first token after the layout list.
-
- 'end-tokens: back at point where we started, following-token
- will be set to the next token.
-
- Pseudo tokens are used only when implicit-layout-active is
- t. That is the case only after keywords \"do\", \"where\",
- \"let\" and \"of\".
-
- If we are at a new line, parse-line is increased, and
- current-indent and left-indent are set to the indentation of the
- line."
- (cond ((and implicit-layout-active
- (eq current-token 'end-tokens))
- 'end-tokens)
- ((and implicit-layout-active
- (eq current-token 'layout-end))
- (cond ((> layout-indent (current-column))
- 'layout-end)
- ((= layout-indent (current-column))
- (setq current-token 'layout-item))
- ((< layout-indent (current-column))
- (setq current-token (haskell-indentation-peek-token)))))
- ((and implicit-layout-active
- (eq current-token 'layout-item))
- (setq current-token (haskell-indentation-peek-token)))
- ((and implicit-layout-active
- (> layout-indent (current-column)))
- (setq current-token 'layout-end))
- (t
- (setq previous-token (haskell-indentation-peek-token))
- (haskell-indentation-skip-token)
- (if (>= (point) indentation-point)
- (progn
- (setq following-token
- (if (and (not (eobp))
- (= (point) indentation-point))
- (haskell-indentation-peek-token)
- 'no-following-token))
- (setq current-token 'end-tokens))
- (when (= (current-column) (haskell-indentation-current-indentation))
- ;; on a new line
- (setq current-indent (current-column)))
- (cond ((and implicit-layout-active
- (> layout-indent (current-column)))
- (setq current-token 'layout-end))
- ((and implicit-layout-active
- (= layout-indent (current-column)))
- (setq current-token 'layout-item))
- (t (setq current-token (haskell-indentation-peek-token))))))))
-
- (defun haskell-indentation-peek-token ()
- "Return token starting at point."
- (cond ((looking-at "\\(if\\|then\\|else\\|let\\|in\\|mdo\\|rec\\|do\\|proc\\|case\\|of\\|where\\|module\\|signature\\|deriving\\|import\\|data\\|type\\|newtype\\|class\\|instance\\)\\([^[:alnum:]'_]\\|$\\)")
- (match-string-no-properties 1))
- ((looking-at "[][(){}[,;]")
- (match-string-no-properties 0))
- ((looking-at "\\(\\\\\\|->\\|<-\\|::\\|=\\||\\|=>\\)\\([^-:!#$%&*+./<=>?@\\\\^|~]\\|$\\)")
- (match-string-no-properties 1))
- ((looking-at "\\(→\\|←\\|∷\\|⇒\\)\\([^-:!#$%&*+./<=>?@\\\\^|~]\\|$\\)")
- (let ((tok (match-string-no-properties 1)))
- (or (cdr (assoc tok haskell-indentation-unicode-tokens)) tok)))
- ((looking-at"[-:!#$%&*+./<=>?@\\\\^|~`]" )
- 'operator)
- (t 'value)))
-
- (defun haskell-indentation-skip-token ()
- "Skip to the next token."
- (if (haskell-lexeme-looking-at-token)
- (goto-char (match-end 0))
- ;; otherwise skip until space found
- (skip-syntax-forward "^-"))
- ;; we have to skip unterminated string fence at the end of line
- (skip-chars-forward "\n")
- (forward-comment (buffer-size))
- (while (and (haskell-indentation-bird-p)
- (bolp)
- (eq (char-after) ?>))
- (forward-char)
- (forward-comment (buffer-size))))
-
- (provide 'haskell-indentation)
-
- ;; Local Variables:
- ;; tab-width: 8
- ;; End:
-
- ;;; haskell-indentation.el ends here
|