Subject: `ruby-forward-string' chokes on caret delimiters (PATCH)

From: Cameron Desautels <[email-masked]>
Date: Fri, 6 Dec 2013 11:33:21 -0600

`ruby-forward-string' is a function used to skip past strings in Ruby
mode. Ruby supports using any non-alpha character following a "%" to
delimit a string.  All of the following are valid Ruby syntax:

    - %[foo bar]
    - %/foo bar/
    - %%foo bar%
    - %^foo bar^

Currently ruby-mode can handle all of these except the last one, which
yields the following:

    #     Debugger entered--Lisp error: (invalid-regexp "Unmatched [ or [^")

The root of the problem is the construction of the following invalid
regexp: "[^]" (which is an incomplete negated character class).

This bug manifests itself by breaking `ruby-parse-region' in any
buffer containing a caret-delimited string.  This breaks indentation,
movement commands, and even keeps Emacs from building an Imenu.

I've attached a patch which resolves the issue.  Note that this patch
builds on the test suite for `ruby-forward-string' that I included in
issue #16078.
--
Cameron Desautels <[email-masked]>
*** lisp/progmodes/ruby-mode.el.orig	2013-12-06 11:17:04.000000000 -0600
--- lisp/progmodes/ruby-mode.el     	2013-12-06 11:16:21.000000000 -0600
***************
*** 807,815 ****
  This command assumes the character after point is an opening
  delimiter."
    (let ((n 1) (c (string-to-char term))
!         (re (if expand
!                 (concat "[^\\]\\(\\\\\\\\\\)*\\([" term "]\\|\\(#{\\)\\)")
!               (concat "[^\\]\\(\\\\\\\\\\)*[" term "]"))))
      (while (and (re-search-forward re end no-error)
                  (if (match-beginning 3)
                      (ruby-forward-string "}{" end no-error nil)
--- 807,818 ----
  This command assumes the character after point is an opening
  delimiter."
    (let ((n 1) (c (string-to-char term))
!         (re (concat "[^\\]\\(\\\\\\\\\\)*\\("
!                     (if (string= term "^") ;[^] is not a valid regexp
!                         "\\^"
!                       (concat "[" term "]"))
!                     (when expand "\\|\\(#{\\)")
!                     "\\)")))
      (while (and (re-search-forward re end no-error)
                  (if (match-beginning 3)
                      (ruby-forward-string "}{" end no-error nil)
*** test/automated/ruby-mode-tests.el.orig	2013-12-06 11:17:04.000000000 -0600
--- test/automated/ruby-mode-tests.el     	2013-12-06 11:16:21.000000000 -0600
***************
*** 658,667 ****
  (ert-deftest ruby-forward-string-accepts-single-delimiters ()
    (ruby-forward-string-should-move-to "/foo/bar" "/" 6)
    (ruby-forward-string-should-move-to "|foo|bar" "|" 6)
!   (ruby-forward-string-should-move-to "-foo-bar" "-" 6))
!
! (ert-deftest ruby-forward-string-accepts-carets ()
!   :expected-result :failed
    (ruby-forward-string-should-move-to "^foo^bar" "^" 6))

  (ert-deftest ruby-forward-string-scans-the-shortest-match ()
--- 658,664 ----
  (ert-deftest ruby-forward-string-accepts-single-delimiters ()
    (ruby-forward-string-should-move-to "/foo/bar" "/" 6)
    (ruby-forward-string-should-move-to "|foo|bar" "|" 6)
!   (ruby-forward-string-should-move-to "-foo-bar" "-" 6)
    (ruby-forward-string-should-move-to "^foo^bar" "^" 6))

  (ert-deftest ruby-forward-string-scans-the-shortest-match ()