0

I have a function that tests if another nested pair of parenthesis exits or not.

(defun next-pair-exists-in (func)
  "Check if inner or outer pair exists.
Function employs `up-list` or `down-list` as argument to work."
  (progn
    (forward-char)
    (let ((rslt
           (not (eql (point)
                     (progn (ignore-errors (funcall func))
                            (point))))))
      (backward-char)
      rslt)))

I use it to implement parenthesis integrity in Chinese texts, where double quotes must be nested within single quotes and vice versa 「『「」』」.

(defun paren-integrity ()
  "Double quote must follow single quote."
  (interactive)
  (outer-paren) ;; bring cursor to outermost parenthesis
  ;; check that two levels exist for the test.
  (if (and (next-pair-exists-in #'down-list)
           (next-pair-exists-in #'down-list))
      (progn (single-quote-test) ;; make sure single quote is at point and move inwards.
             (double-quote-test))
    (single-quote-test)))

single-quote-test and double-quote-test just makes sure the character at point is a single or double quote and move to the next nested pair of quotes, if they exist.

One problem I encounter is that running (and (next-pair-exists-in #'down-list)(next-pair-exists-in #'down-list)) itself brings the cursor inwards by 2 levels.

|「1「2「3「4「5」」」」」--> 「1「|2「3「4「5」」」」」

So when the actual code starts to run, it runs on 3 and 4 instead of the intended 1 and 2 the logic test is meant for:

「1「2「3『4「5」4』3」2」1」

How do I get (next-pair-exists-in #'down-list) to return a t/nil value without moving the cursor?


Desired Outcome:

「1『2「3『4「5」4』3」2』1」


The Whole Codebase as it sits at present for testing purposes

;;;; Nested Parentheses 「『』」

(use-package smartparens)

(defun outer-paren ()
  "Move point to the outermost parenthesis."
  (interactive)
  (ignore-errors
    (while t
      (up-list)))
  (sp-backward-sexp))

(defun next-pair-exists-in (func)
  "Check if inner or outer pair exists. 
Function employs `up-list` or `down-list` as argument to work."
  (progn 
    (forward-char)
    (let ((rslt
       (not (eql (point)
             (progn (ignore-errors (funcall func))
                (point))))))
      (backward-char)
      rslt)))

(defun is-single-quote ()
  "Check if symbol at point is 「."
  (eq ?「 (char-after)))

  (defun is-double-quote ()
    "Check if symbol at point is 『."
    (eq ?『 (char-after)))

    (defun is-open-quote ()
      "Check if symbol at point is 「."
      (or (eq ?「 (char-after))
          (eq ?『 (char-after))))

;;;;; Logic Functions

      (defun single-quote-test ()
        "Test to see if current pair is 「」. 
Replace『』 with 「」 if necessary. 
Move cursor to inner quote if it exists. "
        (interactive)
        ;; First condition: if it is 『』, change to 「」. 
        (cond ((is-double-quote)
           (let ((current-paren (remove-paren))) ;; `remove-paren` would execute the removal. 
             ;; subtract 1 from close-position (ie. second element of `current-paren`) 
             (setf (nth 1 current-paren) (1- (nth 1 current-paren)))          
             ;; subtract 2 to every point in `current-paren` to derive insertion position.
                    ;  (setq current-paren (mapcar (apply-partially #'- 2) current-paren))
             ;; insert relevant symbol to positions by pair.
             (insert-single-quote current-paren)
             (sp-backward-sexp)
             (if (next-pair-exists-in #'down-list)
             (progn (down-list)
                (backward-char)))))
          ;; Second condition: if it is a 「」, move to next nested if it exists. 
          ((and (is-single-quote) 
            (next-pair-exists-in #'down-list))      
           (down-list)
           (backward-char))
          ;; `down-list` needs to be called twice because it brings point to content within the quote rather than on the quote itself.  
          (t nil)))

      (defun double-quote-test ()
        "Test to see if current pair is 「」. 
Replace『』 with 「」 if necessary. 
Move cursor to inner quote if it exists. "
        (interactive)
        ;; First condition: if it is 『』, change to 「」. 
        (cond ((is-single-quote)
           (let ((current-paren (remove-paren))) ;; `remove-paren` would execute the removal. 
             ;; subtract 1 from close-position (ie. second element of `current-paren`) 
             (setf (nth 1 current-paren) (1- (nth 1 current-paren)))          
             ;; subtract 2 to every point in `current-paren` to derive insertion position.
                    ;  (setq current-paren (mapcar (apply-partially #'- 2) current-paren))
             ;; insert relevant symbol to positions by pair.
             (insert-double-quote current-paren)
             (sp-backward-sexp)
             (if (next-pair-exists-in #'down-list)
             (progn (down-list)
                (backward-char)))))
          ;; Second condition: if it is a 「」, move to next nested if it exists. 
          ((and (is-double-quote) 
            (next-pair-exists-in #'down-list))      
           (down-list)
           (backward-char))
          ;; `down-list` needs to be called twice because it brings point to content within the quote rather than on the quote itself.  
          (t nil)))

;;;;; Functions that handle 「」『』 in pairs

      (defun paren-position ()
        "Returns a list of positions where parenthesis has been sitting."
        (interactive)
        (if (is-open-quote)
        (setq paren-pos (list (point)
                      (progn (sp-forward-sexp) (point))))
          (setq paren-pos (list
                   (progn (sp-backward-sexp) (point))
                   (progn (sp-forward-sexp) (point))))))

      (defun remove-paren ()
        "Delete pair of parenthesis at point. 
Return the position of point and its counterpart."
        (interactive)
        (let ((current-paren (paren-position)))
          (if (is-open-quote)
          (forward-char)
        (backward-char))
          (sp-unwrap-sexp)
          current-paren))

      (defun insert-single-quote (current-paren)
            (progn (goto-char (nth 0 current-paren))
           (insert "「")
           (goto-char (nth 1 current-paren))
           (insert "」")))

      (defun insert-double-quote (current-paren)
            (progn (goto-char (nth 0 current-paren))
           (insert "『")
           (goto-char (nth 1 current-paren))
           (insert "』")))

      ;; Set the rule

      (defun paren-integrity ()
        "Double quote must follow single quote."
        (interactive)
        (save-excursion
          (outer-paren)
          (while (next-pair-exists-in #'down-list)
        (backward-up-list)
        (if (and (next-pair-exists-in #'down-list)
             (next-pair-exists-in #'down-list))
            (progn (backward-up-list 2)
               (single-quote-test)
               (double-quote-test))
          (if (next-pair-exists-in #'down-list)
              (single-quote-test))))))

  • "suppressing its side-effect" could mean absolutely anything in general, so the question title isn't very useful as-is (as there is no magic "suppress all side-effects" feature). In this very specific case, are you looking for save-excursion ? – phils Feb 12 at 1:51
  • I have save-excursion in my original code. Removed it to demonstrate how the cursor travels. It doesn't really make a difference. I think what it does is just to bring the cursor back to its original position when the code has run its course. What I need is a function that suppresses the functional component of down-list when (next-pair-exists-in #'down-list) is called. symbol-value doesn't work either. – Sati Feb 12 at 3:32
  • I have no idea what you're asking. If you're calling a function but you don't want its "functional component" to do anything, then why are you calling it? If you want some of it to have an effect but not all of it, then say exactly what it is that you want to suppress or revert, otherwise how can anyone answer the question? – phils Feb 12 at 3:41
  • I want (next-pair-exists-in #'down-list) to just return a t or nil value without down-list moving the cursor inwards. – Sati Feb 12 at 3:48
  • 1
    If need be you can always remember the position you (potentially) want to return to, and use goto-char to jump back to it more directly. E.g. (let (pos) ... (setq pos (point)) ... (if condition (goto-char pos) ...) ...) – phils Feb 12 at 5:21
1

further to my comment on your own answer something like this makes more sense to me as the check doesnt alter anything.

    (defun next-pair-exists-in (func)
      "Check if inner or outer pair exists.
    Function employs `up-list` or `down-list` as argument to work."
      (setq-local reslt nil)
      (save-excursion
        (forward-char)
        (setq rslt
              (not (eql (point)
                        (progn (ignore-errors (funcall func))
                               (point))))))
      rslt)

Obviously you would need to undo your "undo" code in the calling function and also specifically advance in your calling code.

| improve this answer | |
  • What's the difference between (setq-local reslt nil) and (let ...) in terms of keeping variables local? Does it matter if result and rslt don't match? – Sati Feb 12 at 8:41
  • How do I rewrite this code so that I can use (next-pair-exists-in #'down-list 2) to call it twice like (backward-up-list 2)? – Sati Feb 12 at 8:47
0

Here is my solution to the problem. I use backward-up-list to undo the cursor movement during the conditional before running the actual code.

(defun paren-integrity ()
  "Double quote must follow single quote."
  (interactive)
  (save-excursion
    (outer-paren)
    (while (next-pair-exists-in #'down-list)
      (backward-up-list)
      (if (and (next-pair-exists-in #'down-list)
               (next-pair-exists-in #'down-list))
          (progn (backward-up-list 2)
                 (single-quote-test)
                 (double-quote-test))
        (if (next-pair-exists-in #'down-list)
            (single-quote-test))))))

Please let me know if there is a better way to do this.

| improve this answer | |
  • Could you please provide the code for your quote exists functions. I would suggest that next-pair-exists-in should be called pair-exists-in (you can pass multiple functions to it) and look after restoring cursor position itself (it is, after all, a predicate that should have no side effects) - and then you specifically advance when it returns true – – RichieHH Feb 12 at 6:44
  • @RichieHH I am sharing the rest of the code for testing purposes. Please help see if there is anything else I can improve on. paren-integrity is working correctly now. Next thing I would like to do is write a function for lateral testing 「『』『』」 and join the two together. – Sati Feb 12 at 8:35

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.