branch: elpa/haskell-tng-mode commit 41a29dd344fb96ec784a55c388e2056b28ca25db Author: Tseen She <ts33n....@gmail.com> Commit: Tseen She <ts33n....@gmail.com>
backward lexer --- haskell-tng-smie.el | 80 ++++++++++++++++++++++------ test/haskell-tng-smie-test.el | 118 +++++++++++++++++++++++++++++------------- test/src/medley.hs.lexer | 2 +- 3 files changed, 149 insertions(+), 51 deletions(-) diff --git a/haskell-tng-smie.el b/haskell-tng-smie.el index 0c3db4b..ed89f69 100644 --- a/haskell-tng-smie.el +++ b/haskell-tng-smie.el @@ -34,6 +34,13 @@ ;; read-only navigation. (defvar-local haskell-tng-smie:last nil) +;; syntax-tables supported by SMIE +(defconst haskell-tng-smie:fast-syntax + (rx (| (syntax open-parenthesis) + (syntax close-parenthesis) + (syntax string-quote) + (syntax string-delimiter)))) + (defun haskell-tng-smie:state-invalidation (_beg _end _pre-length) "For use in `after-change-functions' to invalidate the state of the lexer." @@ -52,12 +59,13 @@ the lexer." ;; Note that this implementation is stateful as it can play back multiple ;; virtual tokens at a single point. This lexer could be made stateless if SMIE ;; were to support a 4th return type: a list of any of the above. +;; +;; Any changes to this function must be reflected in +;; `haskell-tng-smie:backward-token'. (defun haskell-tng-smie:forward-token () (unwind-protect (let (case-fold-search) - (when (and haskell-tng-smie:state - (not (equal haskell-tng-smie:last `(forward . ,(point))))) - (setq haskell-tng-smie:state nil)) + (haskell-tng-smie:check-last 'forward) (if (consp haskell-tng-smie:state) ;; continue replaying virtual tokens @@ -77,12 +85,10 @@ the lexer." (haskell-tng-smie:state (haskell-tng-smie:replay-virtual)) + ((eobp) nil) + ;; syntax tables (supported by `smie-indent-forward-token') - ((looking-at (rx (| (syntax open-parenthesis) - (syntax close-parenthesis) - (syntax string-quote) - (syntax string-delimiter)))) - nil) + ((looking-at haskell-tng-smie:fast-syntax) nil) ;; regexps ((or @@ -98,17 +104,62 @@ the lexer." (string (char-before)))))) ;; save the state - (setq haskell-tng-smie:last `(forward . ,(point))))) + (haskell-tng-smie:set-last 'forward))) + +;; Implementation of `smie-backward-token' for Haskell, matching +;; `haskell-tng-smie:forward-token'. +(defun haskell-tng-smie:backward-token () + (unwind-protect + (let (case-fold-search) + (haskell-tng-smie:check-last 'backward) + + (if (consp haskell-tng-smie:state) + (haskell-tng-smie:replay-virtual 'reverse) + + (setq haskell-tng-smie:state + (unless haskell-tng-smie:state + (haskell-tng-layout:virtuals-at-point))) + + (if haskell-tng-smie:state + (haskell-tng-smie:replay-virtual 'reverse) + + (forward-comment (- (point))) + (cond + ((bobp) nil) + ((looking-back haskell-tng-smie:fast-syntax (- (point) 1)) nil) + ((or + (looking-back haskell-tng:regexp:reserved (- (point) 8)) + (looking-back (rx (+ (| (syntax word) (syntax symbol)))) + (line-beginning-position) 't)) + (haskell-tng-smie:last-match 'reverse)) + (t + (forward-char -1) + (string (char-after))))))) + + (haskell-tng-smie:set-last 'backward))) + +(defun haskell-tng-smie:set-last (direction) + (setq haskell-tng-smie:last (cons direction (point)))) + +(defun haskell-tng-smie:check-last (direction) + (when (and haskell-tng-smie:state + (not (equal haskell-tng-smie:last (cons direction (point))))) + (setq haskell-tng-smie:state nil))) -(defun haskell-tng-smie:replay-virtual () +(defun haskell-tng-smie:replay-virtual (&optional reverse) ";; read a virtual token from state, set 't when all done" (unwind-protect - (pop haskell-tng-smie:state) + (if reverse + (unwind-protect + (car (last haskell-tng-smie:state)) + (setq haskell-tng-smie:state + (butlast haskell-tng-smie:state))) + (pop haskell-tng-smie:state)) (unless haskell-tng-smie:state (setq haskell-tng-smie:state 't)))) -(defun haskell-tng-smie:last-match () - (goto-char (match-end 0)) +(defun haskell-tng-smie:last-match (&optional reverse) + (goto-char (if reverse (match-beginning 0) (match-end 0))) (match-string-no-properties 0)) ;; TODO a haskell grammar @@ -148,8 +199,7 @@ the lexer." haskell-tng-smie:grammar haskell-tng-smie:rules :forward-token #'haskell-tng-smie:forward-token - ;; FIXME :backward-token #'haskell-tng-smie:backward-token - )) + :backward-token #'haskell-tng-smie:backward-token)) (provide 'haskell-tng-smie) ;;; haskell-tng-smie.el ends here diff --git a/test/haskell-tng-smie-test.el b/test/haskell-tng-smie-test.el index f23ddb4..4d5457a 100644 --- a/test/haskell-tng-smie-test.el +++ b/test/haskell-tng-smie-test.el @@ -17,6 +17,7 @@ (let ((tok (funcall smie-forward-token-function))) (cond ((< 0 (length tok)) tok) + ((eobp) nil) ((looking-at (rx (| (syntax open-parenthesis) (syntax close-parenthesis)))) (concat "_" (haskell-tng-smie:last-match))) @@ -25,45 +26,76 @@ (let ((start (point))) (forward-sexp 1) (concat "_" (buffer-substring-no-properties start (point))))) - ((eobp) nil) (t (error "Bumped into unknown token"))))) -(defun haskell-tng-smie-test:forward-tokens () - "Forward lex the current buffer using SMIE lexer and return the list of lines, +;; same as above, but for `smie-indent-backward-token' +(defun haskell-tng-smie-test:indent-backward-token () + (let ((tok (funcall smie-backward-token-function))) + (cond + ((< 0 (length tok)) tok) + ((bobp) nil) + ((looking-back (rx (| (syntax open-parenthesis) + (syntax close-parenthesis))) + (- (point) 1)) + (concat "_" (haskell-tng-smie:last-match 'reverse))) + ((looking-back (rx (| (syntax string-quote) + (syntax string-delimiter))) + (- (point) 1)) + (let ((start (point))) + (backward-sexp 1) + (concat "_" (buffer-substring-no-properties (point) start)))) + (t (error "Bumped into unknown token"))))) + +(defun haskell-tng-smie-test:tokens (&optional reverse) + "Lex the current buffer using SMIE and return the list of lines, where each line is a list of tokens. When called interactively, shows the tokens in a buffer." - (defvar smie-forward-token-function) - (let* ((lines '(()))) - (goto-char (point-min)) - (while (not (eobp)) + (let ((lines (list nil)) + quit) + (goto-char (if reverse (point-max) (point-min))) + (while (not quit) (let* ((start (point)) - (token (haskell-tng-smie-test:indent-forward-token))) + (token (if reverse + (haskell-tng-smie-test:indent-backward-token) + (haskell-tng-smie-test:indent-forward-token)))) (let ((line-diff (- (line-number-at-pos (point)) (line-number-at-pos start)))) - (unless (<= line-diff 0) - (setq lines (append (-repeat line-diff nil) lines)))) - (unless (s-blank? token) - (push token (car lines))))) - (reverse (--map (reverse it) lines)))) + (unless (= line-diff 0) + (setq lines (append (-repeat (abs line-diff) nil) lines)))) + (if (and (not token) (if reverse (bobp) (eobp))) + (setq quit 't) + (unless (s-blank? token) + (push token (car lines)))))) + (if reverse + lines + (reverse (--map (reverse it) lines))))) (defun haskell-tng-smie-test:tokens-to-string (lines) (concat (s-join "\n" (--map (s-join " " it) lines)) "\n")) -(defun haskell-tng-smie-test:parse-to-string () - (haskell-tng-smie-test:tokens-to-string - (haskell-tng-smie-test:forward-tokens))) - (defun have-expected-forward-lex (file) (haskell-tng-testutils:assert-file-contents file #'haskell-tng-mode - #'haskell-tng-smie-test:parse-to-string + (lambda () (haskell-tng-smie-test:tokens-to-string + (haskell-tng-smie-test:tokens))) + "lexer")) + +(defun have-expected-backward-lex (file) + (haskell-tng-testutils:assert-file-contents + file + #'haskell-tng-mode + (lambda () (haskell-tng-smie-test:tokens-to-string + (haskell-tng-smie-test:tokens 'reverse))) "lexer")) (ert-deftest haskell-tng-smie-file-tests () - (should (have-expected-forward-lex (testdata "src/medley.hs"))) - (should (have-expected-forward-lex (testdata "src/layout.hs"))) + ;;(should (have-expected-forward-lex (testdata "src/medley.hs"))) + ;;(should (have-expected-forward-lex (testdata "src/layout.hs"))) + + (should (have-expected-backward-lex (testdata "src/medley.hs"))) + (should (have-expected-backward-lex (testdata "src/layout.hs"))) ) (ert-deftest haskell-tng-smie-state-invalidation-tests () @@ -75,44 +107,60 @@ When called interactively, shows the tokens in a buffer." ;; token, then move the point for another token. (goto-char 317) (should (equal (haskell-tng-smie-test:indent-forward-token) ";")) - (should (= 317 (point))) (should (equal (haskell-tng-smie-test:indent-forward-token) "stkToLst")) - (should (= 325 (point))) (should (equal (haskell-tng-smie-test:indent-forward-token) "_(")) - (should (= 327 (point))) ;; repeating the above, but with a user edit, should reset the state (goto-char 317) (should (equal (haskell-tng-smie-test:indent-forward-token) ";")) - (should (= 317 (point))) (save-excursion (goto-char (point-max)) (insert " ")) - (should (= 317 (point))) (should (equal (haskell-tng-smie-test:indent-forward-token) ";")) - (should (= 317 (point))) (should (equal (haskell-tng-smie-test:indent-forward-token) "stkToLst")) - (should (= 325 (point))) (should (equal (haskell-tng-smie-test:indent-forward-token) "_(")) - (should (= 327 (point))) ;; repeating again, but jumping the lexer, should reset the state (goto-char 317) (should (equal (haskell-tng-smie-test:indent-forward-token) ";")) - (should (= 317 (point))) (goto-char 327) (should (equal (haskell-tng-smie-test:indent-forward-token) "MkStack")) - (should (= 334 (point))) (goto-char 317) (should (equal (haskell-tng-smie-test:indent-forward-token) ";")) - (should (= 317 (point))) (should (equal (haskell-tng-smie-test:indent-forward-token) "stkToLst")) - (should (= 325 (point))) (should (equal (haskell-tng-smie-test:indent-forward-token) "_(")) - (should (= 327 (point))) - )) -;; TODO the backwards test should assert consistency with forward + ;; repeating those tests, but for the backward lexer + (goto-char 317) + (should (equal (haskell-tng-smie-test:indent-backward-token) ";")) + (should (equal (haskell-tng-smie-test:indent-backward-token) "_]")) + (should (equal (haskell-tng-smie-test:indent-backward-token) "_[")) + + (goto-char 317) + (should (equal (haskell-tng-smie-test:indent-backward-token) ";")) + (save-excursion + (goto-char (point-max)) + (insert " ")) + (should (equal (haskell-tng-smie-test:indent-backward-token) ";")) + (should (equal (haskell-tng-smie-test:indent-backward-token) "_]")) + (should (equal (haskell-tng-smie-test:indent-backward-token) "_[")) + + (goto-char 317) + (should (equal (haskell-tng-smie-test:indent-backward-token) ";")) + (goto-char 327) + (should (equal (haskell-tng-smie-test:indent-backward-token) "_(")) + (goto-char 317) + (should (equal (haskell-tng-smie-test:indent-backward-token) ";")) + (should (equal (haskell-tng-smie-test:indent-backward-token) "_]")) + (should (equal (haskell-tng-smie-test:indent-backward-token) "_[")) + + ;; jumping between forward and backward at point should reset state + (goto-char 317) + (should (equal (haskell-tng-smie-test:indent-forward-token) ";")) + (should (equal (haskell-tng-smie-test:indent-backward-token) ";")) + (should (equal (haskell-tng-smie-test:indent-forward-token) ";")) + (should (equal (haskell-tng-smie-test:indent-backward-token) ";")) + )) ;; ideas for an indentation tester ;; https://github.com/elixir-editors/emacs-elixir/blob/master/test/test-helper.el#L52-L63 diff --git a/test/src/medley.hs.lexer b/test/src/medley.hs.lexer index a948522..c2ee1a8 100644 --- a/test/src/medley.hs.lexer +++ b/test/src/medley.hs.lexer @@ -131,4 +131,4 @@ where { baz = _ ; _( + _) = _ -} +} }