Skip to content

Commit 3a0a727

Browse files
committed
Merge pull request #35 from MicahChalmer/fix-raw-string-bugs
Fix raw string bugs
2 parents 8d99bf8 + 2e7c6dc commit 3a0a727

File tree

2 files changed

+113
-10
lines changed

2 files changed

+113
-10
lines changed

rust-mode-tests.el

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -963,6 +963,59 @@ list of substrings of `STR' each followed by its face."
963963
"r\"With a backslash at the end\\\"" font-lock-string-face
964964
"r##\"With two hashes\"##" font-lock-string-face)))
965965

966+
(ert-deftest font-lock-raw-string-with-inner-hash ()
967+
(rust-test-font-lock
968+
"r##\"I've got an octothorpe (#)\"##; foo()"
969+
'("r##\"I've got an octothorpe (#)\"##" font-lock-string-face)))
970+
971+
(ert-deftest font-lock-raw-string-with-inner-quote-and-hash ()
972+
(rust-test-font-lock
973+
"not_the_string(); r##\"string \"# still same string\"##; not_the_string()"
974+
'("r##\"string \"# still same string\"##" font-lock-string-face)))
975+
976+
(ert-deftest font-lock-string-ending-with-r-not-raw-string ()
977+
(rust-test-font-lock
978+
"fn f() {
979+
\"Er\";
980+
}
981+
982+
fn g() {
983+
\"xs\";
984+
}"
985+
'("fn" font-lock-keyword-face
986+
"f" font-lock-function-name-face
987+
"\"Er\"" font-lock-string-face
988+
"fn" font-lock-keyword-face
989+
"g" font-lock-function-name-face
990+
"\"xs\"" font-lock-string-face)))
991+
992+
(ert-deftest font-lock-raw-string-trick-ending-followed-by-string-with-quote ()
993+
(rust-test-font-lock
994+
"r\"With what looks like the start of a raw string at the end r#\";
995+
not_a_string();
996+
r##\"With \"embedded\" quote \"##;"
997+
'("r\"With what looks like the start of a raw string at the end r#\"" font-lock-string-face
998+
"r##\"With \"embedded\" quote \"##" font-lock-string-face)))
999+
1000+
(ert-deftest font-lock-raw-string-starter-inside-raw-string ()
1001+
;; Check that it won't look for a raw string beginning inside another raw string.
1002+
(rust-test-font-lock
1003+
"r#\"In the first string r\" in the first string \"#;
1004+
not_in_a_string();
1005+
r##\"In the second string\"##;"
1006+
'("r#\"In the first string r\" in the first string \"#" font-lock-string-face
1007+
"r##\"In the second string\"##" font-lock-string-face)))
1008+
1009+
(ert-deftest font-lock-raw-string-starter-inside-comment ()
1010+
;; Check that it won't look for a raw string beginning inside another raw string.
1011+
(rust-test-font-lock
1012+
"// r\" this is a comment
1013+
\"this is a string\";
1014+
this_is_not_a_string();)"
1015+
'("// " font-lock-comment-delimiter-face
1016+
"r\" this is a comment\n" font-lock-comment-face
1017+
"\"this is a string\"" font-lock-string-face)))
1018+
9661019
(ert-deftest indent-method-chains-no-align ()
9671020
(let ((rust-indent-method-chain nil)) (test-indent
9681021
"

rust-mode.el

Lines changed: 60 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010

1111
;;; Code:
1212

13-
(eval-when-compile (require 'misc))
13+
(eval-when-compile (require 'misc)
14+
(require 'rx))
1415

1516
;; for GNU Emacs < 24.3
1617
(eval-when-compile
@@ -51,13 +52,6 @@
5152

5253
table))
5354

54-
(defvar rust-mode-inside-raw-string-syntax-table
55-
(let ((table (make-syntax-table rust-mode-syntax-table)))
56-
(modify-syntax-entry ?\" "_" table)
57-
(modify-syntax-entry ?\\ "_" table)
58-
59-
table))
60-
6155
(defgroup rust-mode nil
6256
"Support for Rust code."
6357
:link '(url-link "http://www.rust-lang.org/")
@@ -318,6 +312,63 @@
318312
("fn" . font-lock-function-name-face)
319313
("static" . font-lock-constant-face)))))
320314

315+
(defun rust-look-for-raw-string (bound)
316+
;; Find a raw string, but only if it's not in the middle of another string or
317+
;; a comment
318+
319+
(let* ((raw-str-regexp
320+
(rx
321+
(seq
322+
;; The "r" starts the raw string. Capture it as group 1 to mark it as such syntactically:
323+
(group "r")
324+
325+
;; Then either:
326+
(or
327+
;; a sequence at least one "#" (followed by quote). Capture all
328+
;; but the last "#" as group 2 for this case.
329+
(seq (group (* "#")) "#\"")
330+
331+
;; ...or a quote without any "#". Capture it as group 3. This is
332+
;; used later to match the opposite quote only if this capture
333+
;; occurred
334+
(group "\""))
335+
336+
;; The contents of the string:
337+
(*? anything)
338+
339+
;; If there are any backslashes at the end of the string, capture
340+
;; them as group 4 so we can suppress the normal escape syntax
341+
;; parsing:
342+
(group (* "\\"))
343+
344+
;; Then the end of the string--the backreferences ensure that we
345+
;; only match the kind of ending that corresponds to the beginning
346+
;; we had:
347+
(or
348+
;; There were "#"s - capture the last one as group 5 to mark it as
349+
;; the end of the string:
350+
(seq "\"" (backref 2) (group "#"))
351+
352+
;; No "#"s - capture the ending quote (using a backref to group 3,
353+
;; so that we can't match a quote if we had "#"s) as group 6
354+
(group (backref 3))))))
355+
;; If it matches, it ends up with the starting character of the string
356+
;; as group 1, any ending backslashes as group 4, and the ending
357+
;; character as either group 5 or group 6.
358+
359+
(ret-list (save-excursion
360+
(let* ((match-end (re-search-forward raw-str-regexp bound t))
361+
(ret-list (and match-end (list match-end (match-beginning 0) (match-data) (point)))))
362+
(when (and ret-list
363+
(save-excursion
364+
(goto-char (nth 1 ret-list))
365+
(not (rust-in-str-or-cmnt))))
366+
ret-list)))))
367+
(when ret-list
368+
(goto-char (nth 3 ret-list))
369+
(set-match-data (nth 2 ret-list))
370+
(nth 0 ret-list))))
371+
321372
(defvar rust-mode-font-lock-syntactic-keywords
322373
(append
323374
;; Handle single quoted character literals:
@@ -328,8 +379,7 @@
328379
"\\('\\)\\\\u[[:xdigit:]]\\{4\\}\\('\\)"
329380
"\\('\\)\\\\U[[:xdigit:]]\\{8\\}\\('\\)"))
330381
;; Handle raw strings:
331-
`(("\\(r\\)\"\\([^\"]*\\)\\(\"\\)" (1 "|") (2 ,rust-mode-inside-raw-string-syntax-table) (3 "|"))
332-
("\\(r\\)#\\(#*\\)\\(\"[^#]*\"\\2\\)\\(#\\)" (1 "|") (3 ,rust-mode-inside-raw-string-syntax-table) (4 "|")))))
382+
`((rust-look-for-raw-string (1 "|") (4 "_" nil t) (5 "|" nil t) (6 "|" nil t)))))
333383

334384
(defun rust-fill-prefix-for-comment-start (line-start)
335385
"Determine what to use for `fill-prefix' based on what is at the beginning of a line."

0 commit comments

Comments
 (0)