From 7e0726cf4c231fd3a1bee600243138356cbd4bda Mon Sep 17 00:00:00 2001 From: Eshel Yaron Date: Sun, 26 Nov 2023 11:33:57 +0100 Subject: [PATCH] Respect 'syntax-table' text properties Replace all usages of (char-syntax (char-before/after ...)) with 'syntax-after' calls, since the former doesn't take into account the 'syntax-table' text property that 'syntax-propertize-function' adds. In particular, this fixes an issue with 'forward-sexp' over character literals such as "0'c". * sweeprolog.el (sweeprolog-syntax-propertize): Replace with... (sweeprolog-syntax-propertize-function): New 'defconst'. (sweeprolog-mode): Use it. (sweeprolog--op-p, sweeprolog-syntax-class-at): New 'defsubst'. (sweeprolog--parse-context, sweeprolog--completion-at-point) (sweeprolog-next-token-boundaries, sweeprolog-last-token-boundaries) (sweeprolog--forward-term, sweeprolog--backward-term) (sweeprolog--precedence-at-point,sweeprolog-align-spaces) (sweeprolog-indent-line): Respect 'syntax-table' text properties. * sweeprolog-tests.el (backward-sexp-over-char-literal): Test it. --- sweeprolog-tests.el | 22 +++++- sweeprolog.el | 188 +++++++++++++++++++++++--------------------- 2 files changed, 120 insertions(+), 90 deletions(-) diff --git a/sweeprolog-tests.el b/sweeprolog-tests.el index 5c57520..074d2dd 100644 --- a/sweeprolog-tests.el +++ b/sweeprolog-tests.el @@ -1832,7 +1832,7 @@ head, ")) (sweeprolog-deftest forward-sexp-with-adjacent-operators () - "Tests detecting the fullstop in presence of `.=.'." + "Test `forward-sexp' with adjacent operators." "a,+b." (goto-char (point-min)) (sweeprolog--forward-sexp) @@ -1841,6 +1841,26 @@ head, (sweeprolog--backward-sexp) (should (= (point) 4))) +(sweeprolog-deftest backward-sexp-over-char-literal () + "Test `backward-sexp' over a charater literal." + "foo :- A is 0'b + 0'a + 0'r-!-." + (backward-sexp) + (should (= (point) 25)) + (backward-sexp) + (should (= (point) 19)) + (backward-sexp) + (should (= (point) 13)) + (forward-sexp) + (should (= (point) 16)) + (forward-sexp) + (should (= (point) 22)) + (backward-sexp 2) + (should (= (point) 13)) + (backward-sexp) + (should (= (point) 8)) + (backward-sexp) + (should (= (point) 1))) + (sweeprolog-deftest usage-example-comment () "Tests adding usage example comments." "\nfoo." diff --git a/sweeprolog.el b/sweeprolog.el index b39680b..4f122da 100644 --- a/sweeprolog.el +++ b/sweeprolog.el @@ -1273,6 +1273,9 @@ command prompts for MOD." (find-file file)) (user-error "Module %s is not defined in a source file!" mod))) +(defsubst sweeprolog-syntax-class-at (pos) + "Return the syntax class of the character at POS." + (syntax-class (syntax-after pos))) ;;;; Completion at point @@ -1380,9 +1383,8 @@ Prolog buffers." (ppre (sweeprolog-op-prefix-precedence op))) (cond ((and (string= "." op) - (or (not (char-after (1+ obeg))) - (member (char-syntax (char-after (1+ obeg))) - '(?> ? )))) + (let ((sa (sweeprolog-syntax-class-at (1+ obeg)))) + (or (null sa) (member sa '(0 12))))) nil) ((string= "," op) (setq pos @@ -1915,8 +1917,8 @@ Used for `completion-at-point' candidates when point is not inside a comment, string or quoted atom." (if (bobp) (sweeprolog--first-term-completion-at-point) - (pcase (char-syntax (char-before)) - ((or ?w ?_) + (pcase (sweeprolog-syntax-class-at (1- (point))) + ((or 2 3) (let ((symbol-beg (save-excursion (skip-syntax-backward "w_") (point))) @@ -1928,31 +1930,31 @@ inside a comment, string or quoted atom." symbol-end) (sweeprolog--atom-or-functor-completion-at-point symbol-beg symbol-end)))) - (?. (sweeprolog--operator-completion-at-point)) - (?\( (pcase (char-before) - (?\( (when-let ((prev (char-before (1- (point))))) - (pcase (char-syntax prev) - ((or ?w ?_) - (sweeprolog--after-functor-completion-at-point)) - (?\" - (when (= prev ?\') - (sweeprolog--after-quoted-functor-completion-at-point))) - (_ (sweeprolog--term-completion-at-point))))) - (?\[ (sweeprolog--first-list-argument-completion-at-point)) - (?\{ (when-let ((prev (char-before (1- (point))))) - (pcase (char-syntax prev) - ((or ?w ?_) - (sweeprolog--first-dict-argument-completion-at-point)) - (_ (sweeprolog--after-curly-brace-completion-at-point))))))) - ((or ?\) ?\") (sweeprolog--after-term-completion-at-point)) - (?\s (pcase (sweeprolog-last-token-boundaries) - ('nil (sweeprolog--first-term-completion-at-point)) - (`(open ,_ ,_) (sweeprolog--term-completion-at-point)) - (`(functor ,_ ,_) (sweeprolog--after-functor-completion-at-point)) - (`(operator ,obeg ,oend) (sweeprolog--after-operator-completion-at-point obeg oend)) - (`(symbol ,obeg ,oend) (sweeprolog--after-atom-or-variable-completion-at-point obeg oend)) - (`(close ,_ ,_) (sweeprolog--after-term-completion-at-point)) - (`(string ,_ ,_) (sweeprolog--after-term-completion-at-point))))))) + (1 (sweeprolog--operator-completion-at-point)) + (4 (pcase (char-before) + (?\( (when-let ((prev (char-before (1- (point))))) + (pcase (sweeprolog-syntax-class-at (- (point) 2)) + ((or 2 3) + (sweeprolog--after-functor-completion-at-point)) + (7 + (when (= prev ?\') + (sweeprolog--after-quoted-functor-completion-at-point))) + (_ (sweeprolog--term-completion-at-point))))) + (?\[ (sweeprolog--first-list-argument-completion-at-point)) + (?\{ (when-let ((prev (char-before (1- (point))))) + (pcase (sweeprolog-syntax-class-at (- (point) 2)) + ((or 2 3) + (sweeprolog--first-dict-argument-completion-at-point)) + (_ (sweeprolog--after-curly-brace-completion-at-point))))))) + ((or 5 7) (sweeprolog--after-term-completion-at-point)) + (0 (pcase (sweeprolog-last-token-boundaries) + ('nil (sweeprolog--first-term-completion-at-point)) + (`(open ,_ ,_) (sweeprolog--term-completion-at-point)) + (`(functor ,_ ,_) (sweeprolog--after-functor-completion-at-point)) + (`(operator ,obeg ,oend) (sweeprolog--after-operator-completion-at-point obeg oend)) + (`(symbol ,obeg ,oend) (sweeprolog--after-atom-or-variable-completion-at-point obeg oend)) + (`(close ,_ ,_) (sweeprolog--after-term-completion-at-point)) + (`(string ,_ ,_) (sweeprolog--after-term-completion-at-point))))))) ;;;; Packages @@ -3341,18 +3343,14 @@ modified." (font-lock-fontify-keywords-region start (point) verbose)) `(jit-lock-bounds ,start . ,(point))))))) -(defun sweeprolog-syntax-propertize (start end) - (goto-char start) - (let ((case-fold-search nil)) - (funcall - (syntax-propertize-rules - ((rx (group-n 1 "\\") anychar) - (1 (unless (save-excursion (nth 8 (syntax-ppss (match-beginning 0)))) - (string-to-syntax ".")))) - ((rx bow (group-n 1 "0'" anychar)) - (1 (unless (save-excursion (nth 8 (syntax-ppss (match-beginning 0)))) - (string-to-syntax "w"))))) - start end))) +(defconst sweeprolog-syntax-propertize-function + (syntax-propertize-rules + ((rx (group-n 1 (one-or-more "\\"))) + (1 (unless (save-excursion (nth 8 (syntax-ppss (match-beginning 0)))) + (string-to-syntax ".")))) + ((rx (not alnum) (group-n 2 "0'" anychar)) + (2 (unless (save-excursion (nth 8 (syntax-ppss (match-beginning 0)))) + (string-to-syntax "w")))))) (defun sweeprolog-highlight-variable (point &optional var) "Highlight occurrences of the variable VAR in the clause at POINT. @@ -4026,8 +4024,8 @@ See also `sweeprolog-backward-hole'." (let ((op (buffer-substring-no-properties obeg oend))) (or (and (string= "." op) (or (not (char-after (1+ obeg))) - (member (char-syntax (char-after (1+ obeg))) - '(?> ? ))) + (member (sweeprolog-syntax-class-at (1+ obeg)) + '(0 12))) 1200) (sweeprolog-op-infix-precedence op) (sweeprolog-op-prefix-precedence op) @@ -4510,46 +4508,61 @@ work." (sweeprolog-read-predicate-documentation mod fun ari neck))) (_ (user-error "No predicate found at point")))) +(defsubst sweeprolog--op-p (beg end) + "Check if there is an operator between BEG and END in the current buffer." + (sweeprolog--query-once + "sweep" "sweep_op_info" + (cons (buffer-substring-no-properties beg end) (buffer-file-name)))) + (defun sweeprolog-next-token-boundaries (&optional pos) + "Return a list (KIND BEG END) describing the Prolog token after POS, if any. + +KIND is one of `symbol', `functor' `string', `operator', `open', +`close' and `else'. BEG and END are the token boundaries. + +If there is no token after POS, return nil." (let ((point (or pos (point)))) (save-excursion (goto-char point) (while (forward-comment 1)) (unless (eobp) (let ((beg (point)) - (syn (char-syntax (char-after)))) + (syn (sweeprolog-syntax-class-at (point)))) (cond - ((or (= syn ?w) (= syn ?_)) + ((member syn '(0 12)) (skip-syntax-forward "w_") - (if (= (char-syntax (char-after)) ?\() + (if (= (sweeprolog-syntax-class-at (point)) 4) (progn (forward-char) (list 'functor beg (point))) (list 'symbol beg (point)))) - ((= syn ?\") - (forward-char) - (while (and (not (eobp)) (nth 3 (syntax-ppss))) - (forward-char)) - (list 'string beg (point))) - ((or (= syn ?.) - (= syn ?\\)) + ((= syn 7) + (unless (nth 8 (syntax-ppss)) + (forward-char) + (while (and (not (eobp)) (nth 3 (syntax-ppss))) + (forward-char)) + (list 'string beg (point)))) + ((member syn '(1 9)) (skip-syntax-forward ".") (let ((end (point))) (while (and (< beg (point)) - (not (sweeprolog--query-once - "sweep" "sweep_op_info" - (cons (buffer-substring-no-properties beg (point)) - (buffer-file-name))))) + (not (sweeprolog--op-p beg (point)))) (forward-char -1)) (list 'operator beg (if (= beg (point)) end (point))))) - ((= syn ?\() + ((= syn 4) (list 'open beg (1+ beg))) - ((= syn ?\)) + ((= syn 5) (list 'close beg (1+ beg))) - ((= syn ?>) nil) + ((= syn 12) nil) (t (list 'else beg (1+ beg))))))))) (defun sweeprolog-last-token-boundaries (&optional pos) + "Return a list (KIND BEG END) describing the Prolog token before POS, if any. + +KIND is one of `symbol', `functor' `string', `operator', `open', +`close' and `else'. BEG and END are the token boundaries. + +If there is no token before POS, return nil." (let ((point (or pos (point))) (go t)) (save-excursion @@ -4564,32 +4577,28 @@ work." (setq go nil)))) (unless (bobp) (let ((end (1+ (point))) - (syn (char-syntax (char-after)))) + (syn (sweeprolog-syntax-class-at (point)))) (cond - ((or (= syn ?w) (= syn ?_)) + ((member syn '(2 3)) (skip-syntax-backward "w_") (list 'symbol (point) end)) - ((= syn ?\") - (list 'string (nth 8 (syntax-ppss)) end)) - ((and (= syn ?\() - (or (= (char-syntax (char-before)) ?w) - (= (char-syntax (char-before)) ?_))) + ((= syn 7) + (when-let ((beg (nth 8 (syntax-ppss)))) + (list 'string beg end))) + ((and (= syn 4) + (member (sweeprolog-syntax-class-at (1- (point))) '(2 3))) (skip-syntax-backward "w_") (list 'functor (point) end)) - ((or (= syn ?.) - (= syn ?\\)) ; specifically, the backslash character + ((member syn '(1 9)) (skip-syntax-backward ".") (let ((beg (point))) (while (and (< (point) end) - (not (sweeprolog--query-once - "sweep" "sweep_op_info" - (cons (buffer-substring-no-properties (point) end) - (buffer-file-name))))) + (not (sweeprolog--op-p (point) end))) (forward-char 1)) (list 'operator (if (= end (point)) beg (point)) end))) - ((= syn ?\() + ((= syn 4) (list 'open (1- end) end)) - ((= syn ?\)) + ((= syn 5) (list 'close (1- end) end)) (t (list 'else (1- end) end)))))))) @@ -4615,7 +4624,8 @@ work." (sweeprolog--forward-term pre)) (`(operator ,obeg ,oend) (if (and (string= "." (buffer-substring-no-properties obeg oend)) - (member (char-syntax (char-after (1+ obeg))) '(?> ? ))) + (let ((sa (sweeprolog-syntax-class-at (1+ obeg)))) + (or (null sa) (member sa '(0 12))))) (signal 'scan-error (list "Cannot scan beyond fullstop." obeg @@ -4692,8 +4702,8 @@ work." (`(operator ,obeg ,oend) (if (and (string= "." (buffer-substring-no-properties obeg oend)) (or (not (char-after (1+ obeg))) - (member (char-syntax (char-after (1+ obeg))) - '(?> ? )))) + (member (sweeprolog-syntax-class-at (1+ obeg)) + '(0 12)))) (signal 'scan-error (list "Cannot scan backwards beyond fullstop." obeg @@ -4740,8 +4750,7 @@ work." (setq infix-flag nil)))) (`(close ,lbeg ,_lend) (goto-char (nth 1 (syntax-ppss lbeg))) - (when (or (= (char-syntax (char-before)) ?w) - (= (char-syntax (char-before)) ?_)) + (when (member (sweeprolog-syntax-class-at (1- (point))) '(2 3)) (skip-syntax-backward "w_")) (setq infix-flag nil)) (`(,_ ,lbeg ,_) @@ -4964,14 +4973,16 @@ if-then-else constructs and other common layouts in SWI-Prolog." (let* ((lend (point)) (lbeg (save-excursion (while (and (< bol (point)) - (not (= (char-syntax (char-before)) - ? ))) + (not + (= (sweeprolog-syntax-class-at (1- (point))) + 0))) (forward-char -1)) (point))) (num (- 4 (% (- lend lbeg) 4)))) (insert (make-string (if (< 0 num) num - 4) ? ))))) + 4) + ?\s))))) (pcase (sweeprolog-last-token-boundaries) (`(,_ ,lbeg ,lend) (when (<= bol lend) @@ -5035,7 +5046,7 @@ certain contexts to maintain conventional Prolog layout." (setq-local beginning-of-defun-function #'sweeprolog-beginning-of-top-term) (setq-local end-of-defun-function #'sweeprolog-end-of-top-term) (setq-local forward-sexp-function #'sweeprolog-forward-sexp-function) - (setq-local syntax-propertize-function #'sweeprolog-syntax-propertize) + (setq-local syntax-propertize-function sweeprolog-syntax-propertize-function) (setq-local indent-line-function #'sweeprolog-indent-line) (setq-local adaptive-fill-regexp "[ \t]*") (setq-local fill-indent-according-to-mode t) @@ -5262,13 +5273,12 @@ accordingly." (let ((column (if (nth 8 (syntax-ppss)) 'noindent (if-let ((open (and (not (eobp)) - (= (char-syntax (char-after)) ?\)) + (= (sweeprolog-syntax-class-at (point)) 5) (nth 1 (syntax-ppss))))) (save-excursion (goto-char open) - (when (and (char-before) - (or (= (char-syntax (char-before)) ?w) - (= (char-syntax (char-before)) ?_))) + (when (member (sweeprolog-syntax-class-at (1- (point))) + '(2 3)) (when (save-excursion (forward-char) (skip-syntax-forward " " (pos-eol)) -- 2.39.2