1
Fork 0
mirror of git://git.sv.gnu.org/emacs.git synced 2026-01-03 10:31:37 -08:00

Support (rx (and (regexp EXPR) (literal EXPR))) (Bug#36237)

* lisp/emacs-lisp/rx.el (rx-regexp): Allow non-string forms.
(rx-constituents): Add literal constituent, which is like a plain
STRING form, but allows arbitrary lisp expressions.
(rx-literal): New function.
(rx-compile-to-lisp): New variable.
(rx--subforms): New helper function for handling subforms, including
non-constant case.
(rx-group-if, rx-and, rx-or, rx-=, rx->=, rx-repeat, rx-submatch)
(rx-submatch-n, rx-kleene, rx-atomic-p): Use it to handle non-constant
subforms.
(rx): Document new form, wrap non-constant forms with concat call.
* test/lisp/emacs-lisp/rx-tests.el (rx-tests--match): New macro.
(rx-nonstring-expr, rx-nonstring-expr-non-greedy): New tests.
* etc/NEWS: Announce changes.
This commit is contained in:
Noam Postavsky 2019-06-14 08:43:17 -04:00
parent 29babad728
commit b59ffd2290
3 changed files with 200 additions and 87 deletions

View file

@ -1441,12 +1441,18 @@ when given in a string. Previously, '(any "\x80-\xff")' would match
characters U+0080...U+00FF. Now the expression matches raw bytes in characters U+0080...U+00FF. Now the expression matches raw bytes in
the 128...255 range, as expected. the 128...255 range, as expected.
---
*** The rx 'or' and 'seq' forms no longer require any arguments. *** The rx 'or' and 'seq' forms no longer require any arguments.
(or) produces a regexp that never matches anything, while (seq) (or) produces a regexp that never matches anything, while (seq)
matches the empty string, each being an identity for the operation. matches the empty string, each being an identity for the operation.
This also works for their aliases: '|' for 'or'; ':', 'and' and This also works for their aliases: '|' for 'or'; ':', 'and' and
'sequence' for 'seq'. 'sequence' for 'seq'.
---
*** 'regexp' and new 'literal' accept arbitrary lisp as arguments.
In this case, 'rx' will generate code which produces a regexp string
at run time, instead of a constant string.
** Frames ** Frames
+++ +++

View file

@ -47,57 +47,58 @@
;; Rx translates a sexp notation for regular expressions into the ;; Rx translates a sexp notation for regular expressions into the
;; usual string notation. The translation can be done at compile-time ;; usual string notation. The translation can be done at compile-time
;; by using the `rx' macro. It can be done at run-time by calling ;; by using the `rx' macro. The `regexp' and `literal' forms accept
;; function `rx-to-string'. See the documentation of `rx' for a ;; non-constant expressions, in which case `rx' will translate to a
;; complete description of the sexp notation. ;; `concat' expression. Translation can be done fully at run time by
;; calling function `rx-to-string'. See the documentation of `rx' for
;; a complete description of the sexp notation.
;; ;;
;; Some examples of string regexps and their sexp counterparts: ;; Some examples of string regexps and their sexp counterparts:
;; ;;
;; "^[a-z]*" ;; "^[a-z]*"
;; (rx (and line-start (0+ (in "a-z")))) ;; (rx line-start (0+ (in "a-z")))
;; ;;
;; "\n[^ \t]" ;; "\n[^ \t]"
;; (rx (and "\n" (not (any " \t")))) ;; (rx ?\n (not (in " \t")))
;; ;;
;; "\\*\\*\\* EOOH \\*\\*\\*\n" ;; "\\*\\*\\* EOOH \\*\\*\\*\n"
;; (rx "*** EOOH ***\n") ;; (rx "*** EOOH ***\n")
;; ;;
;; "\\<\\(catch\\|finally\\)\\>[^_]" ;; "\\<\\(catch\\|finally\\)\\>[^_]"
;; (rx (and word-start (submatch (or "catch" "finally")) word-end ;; (rx word-start (submatch (or "catch" "finally")) word-end
;; (not (any ?_)))) ;; (not (in ?_)))
;; ;;
;; "[ \t\n]*:\\([^:]+\\|$\\)" ;; "[ \t\n]*:\\($\\|[^:]+\\)"
;; (rx (and (zero-or-more (in " \t\n")) ":" ;; (rx (* (in " \t\n")) ":"
;; (submatch (or line-end (one-or-more (not (any ?:))))))) ;; (submatch (or line-end (+ (not (in ?:))))))
;; ;;
;; "^content-transfer-encoding:\\(\n?[\t ]\\)*quoted-printable\\(\n?[\t ]\\)*" ;; "^content-transfer-encoding:\\(?:\n?[\t ]\\)*quoted-printable\\(?:\n?[\t ]\\)*"
;; (rx (and line-start ;; (rx line-start
;; "content-transfer-encoding:" ;; "content-transfer-encoding:"
;; (+ (? ?\n)) (any " \t") ;; (* (? ?\n) (in " \t"))
;; "quoted-printable" ;; "quoted-printable"
;; (+ (? ?\n)) (any " \t")) ;; (* (? ?\n) (in " \t")))
;; ;;
;; (concat "^\\(?:" something-else "\\)") ;; (concat "^\\(?:" something-else "\\)")
;; (rx (and line-start (eval something-else))), statically or ;; (rx line-start (regexp something-else))
;; (rx-to-string '(and line-start ,something-else)), dynamically.
;; ;;
;; (regexp-opt '(STRING1 STRING2 ...)) ;; (regexp-opt '(STRING1 STRING2 ...))
;; (rx (or STRING1 STRING2 ...)), or in other words, `or' automatically ;; (rx (or STRING1 STRING2 ...)), or in other words, `or' automatically
;; calls `regexp-opt' as needed. ;; calls `regexp-opt' as needed.
;; ;;
;; "^;;\\s-*\n\\|^\n" ;; "^;;\\s-*\n\\|^\n"
;; (rx (or (and line-start ";;" (0+ space) ?\n) ;; (rx (or (seq line-start ";;" (0+ space) ?\n)
;; (and line-start ?\n))) ;; (seq line-start ?\n)))
;; ;;
;; "\\$[I]d: [^ ]+ \\([^ ]+\\) " ;; "\\$[I]d: [^ ]+ \\([^ ]+\\) "
;; (rx (and "$Id: " ;; (rx "$Id: "
;; (1+ (not (in " "))) ;; (1+ (not (in " ")))
;; " " ;; " "
;; (submatch (1+ (not (in " ")))) ;; (submatch (1+ (not (in " "))))
;; " ")) ;; " ")
;; ;;
;; "\\\\\\\\\\[\\w+" ;; "\\\\\\\\\\[\\w+"
;; (rx (and ?\\ ?\\ ?\[ (1+ word))) ;; (rx "\\\\[" (1+ word))
;; ;;
;; etc. ;; etc.
@ -176,6 +177,7 @@
(not-syntax . (rx-not-syntax 1 1)) ; sregex (not-syntax . (rx-not-syntax 1 1)) ; sregex
(category . (rx-category 1 1 rx-check-category)) (category . (rx-category 1 1 rx-check-category))
(eval . (rx-eval 1 1)) (eval . (rx-eval 1 1))
(literal . (rx-literal 1 1 stringp))
(regexp . (rx-regexp 1 1 stringp)) (regexp . (rx-regexp 1 1 stringp))
(regex . regexp) ; sregex (regex . regexp) ; sregex
(digit . "[[:digit:]]") (digit . "[[:digit:]]")
@ -302,6 +304,10 @@ regular expression strings.")
"Non-nil means produce greedy regular expressions for `zero-or-one', "Non-nil means produce greedy regular expressions for `zero-or-one',
`zero-or-more', and `one-or-more'. Dynamically bound.") `zero-or-more', and `one-or-more'. Dynamically bound.")
(defvar rx--compile-to-lisp nil
"Nil means return a regexp as a string.
Non-nil means we may return a lisp form which produces a
string (used for `rx' macro).")
(defun rx-info (op head) (defun rx-info (op head)
"Return parsing/code generation info for OP. "Return parsing/code generation info for OP.
@ -344,7 +350,7 @@ a standalone symbol."
(> nargs max-args)) (> nargs max-args))
(error "rx form `%s' accepts at most %d args" (error "rx form `%s' accepts at most %d args"
(car form) max-args)) (car form) max-args))
(when (not (null type-pred)) (when type-pred
(dolist (sub-form (cdr form)) (dolist (sub-form (cdr form))
(unless (funcall type-pred sub-form) (unless (funcall type-pred sub-form)
(error "rx form `%s' requires args satisfying `%s'" (error "rx form `%s' requires args satisfying `%s'"
@ -360,8 +366,9 @@ is non-nil."
;; for concatenation ;; for concatenation
((eq group ':) ((eq group ':)
(if (rx-atomic-p (if (rx-atomic-p
(if (string-match (if (and (stringp regexp)
"\\(?:[?*+]\\??\\|\\\\{[0-9]*,?[0-9]*\\\\}\\)\\'" regexp) (string-match
"\\(?:[?*+]\\??\\|\\\\{[0-9]*,?[0-9]*\\\\}\\)\\'" regexp))
(substring regexp 0 (match-beginning 0)) (substring regexp 0 (match-beginning 0))
regexp)) regexp))
(setq group nil))) (setq group nil)))
@ -370,9 +377,10 @@ is non-nil."
;; do anyway ;; do anyway
((eq group t)) ((eq group t))
((rx-atomic-p regexp t) (setq group nil))) ((rx-atomic-p regexp t) (setq group nil)))
(if group (cond ((and group (stringp regexp))
(concat "\\(?:" regexp "\\)") (concat "\\(?:" regexp "\\)"))
regexp)) (group `("\\(?:" ,@regexp "\\)"))
(t regexp)))
(defvar rx-parent) (defvar rx-parent)
@ -384,7 +392,7 @@ is non-nil."
FORM is of the form `(and FORM1 ...)'." FORM is of the form `(and FORM1 ...)'."
(rx-check form) (rx-check form)
(rx-group-if (rx-group-if
(mapconcat (lambda (x) (rx-form x ':)) (cdr form) nil) (rx--subforms (cdr form) ':)
(and (memq rx-parent '(* t)) rx-parent))) (and (memq rx-parent '(* t)) rx-parent)))
@ -396,7 +404,7 @@ FORM is of the form `(and FORM1 ...)'."
((null (cdr form)) regexp-unmatchable) ((null (cdr form)) regexp-unmatchable)
((cl-every #'stringp (cdr form)) ((cl-every #'stringp (cdr form))
(regexp-opt (cdr form) nil t)) (regexp-opt (cdr form) nil t))
(t (mapconcat (lambda (x) (rx-form x '|)) (cdr form) "\\|"))) (t (rx--subforms (cdr form) '| "\\|")))
(and (memq rx-parent '(: * t)) rx-parent))) (and (memq rx-parent '(: * t)) rx-parent)))
@ -669,7 +677,10 @@ If SKIP is non-nil, allow that number of items after the head, i.e.
(unless (and (integerp (nth 1 form)) (unless (and (integerp (nth 1 form))
(> (nth 1 form) 0)) (> (nth 1 form) 0))
(error "rx `=' requires positive integer first arg")) (error "rx `=' requires positive integer first arg"))
(format "%s\\{%d\\}" (rx-form (nth 2 form) '*) (nth 1 form))) (let ((subform (rx-form (nth 2 form) '*)))
(if (stringp subform)
(format "%s\\{%d\\}" subform (nth 1 form))
`(,@subform ,(format "\\{%d\\}" (nth 1 form))))))
(defun rx->= (form) (defun rx->= (form)
@ -679,7 +690,10 @@ If SKIP is non-nil, allow that number of items after the head, i.e.
(unless (and (integerp (nth 1 form)) (unless (and (integerp (nth 1 form))
(> (nth 1 form) 0)) (> (nth 1 form) 0))
(error "rx `>=' requires positive integer first arg")) (error "rx `>=' requires positive integer first arg"))
(format "%s\\{%d,\\}" (rx-form (nth 2 form) '*) (nth 1 form))) (let ((subform (rx-form (nth 2 form) '*)))
(if (stringp subform)
(format "%s\\{%d,\\}" subform (nth 1 form))
`(,@subform ,(format "\\{%d,\\}" (nth 1 form))))))
(defun rx-** (form) (defun rx-** (form)
@ -700,7 +714,10 @@ FORM is either `(repeat N FORM1)' or `(repeat N M FORMS...)'."
(unless (and (integerp (nth 1 form)) (unless (and (integerp (nth 1 form))
(> (nth 1 form) 0)) (> (nth 1 form) 0))
(error "rx `repeat' requires positive integer first arg")) (error "rx `repeat' requires positive integer first arg"))
(format "%s\\{%d\\}" (rx-form (nth 2 form) '*) (nth 1 form))) (let ((subform (rx-form (nth 2 form) '*)))
(if (stringp subform)
(format "%s\\{%d\\}" subform (nth 1 form))
`(,@subform ,(format "\\{%d\\}" (nth 1 form))))))
((or (not (integerp (nth 2 form))) ((or (not (integerp (nth 2 form)))
(< (nth 2 form) 0) (< (nth 2 form) 0)
(not (integerp (nth 1 form))) (not (integerp (nth 1 form)))
@ -708,32 +725,28 @@ FORM is either `(repeat N FORM1)' or `(repeat N M FORMS...)'."
(< (nth 2 form) (nth 1 form))) (< (nth 2 form) (nth 1 form)))
(error "rx `repeat' range error")) (error "rx `repeat' range error"))
(t (t
(format "%s\\{%d,%d\\}" (rx-form (nth 3 form) '*) (let ((subform (rx-form (nth 3 form) '*)))
(nth 1 form) (nth 2 form))))) (if (stringp subform)
(format "%s\\{%d,%d\\}" subform (nth 1 form) (nth 2 form))
`(,@subform ,(format "\\{%d,%d\\}" (nth 1 form) (nth 2 form))))))))
(defun rx-submatch (form) (defun rx-submatch (form)
"Parse and produce code from FORM, which is `(submatch ...)'." "Parse and produce code from FORM, which is `(submatch ...)'."
(concat "\\(" (let ((subforms (rx--subforms (cdr form) ':)))
(if (= 2 (length form)) (if (stringp subforms)
;; Only one sub-form. (concat "\\(" subforms "\\)")
(rx-form (cadr form)) `("\\(" ,@subforms "\\)"))))
;; Several sub-forms implicitly concatenated.
(mapconcat (lambda (re) (rx-form re ':)) (cdr form) nil))
"\\)"))
(defun rx-submatch-n (form) (defun rx-submatch-n (form)
"Parse and produce code from FORM, which is `(submatch-n N ...)'." "Parse and produce code from FORM, which is `(submatch-n N ...)'."
(let ((n (nth 1 form))) (let ((n (nth 1 form))
(subforms (rx--subforms (cddr form) ':)))
(unless (and (integerp n) (> n 0)) (unless (and (integerp n) (> n 0))
(error "rx `submatch-n' argument must be positive")) (error "rx `submatch-n' argument must be positive"))
(concat "\\(?" (number-to-string n) ":" (if (stringp subforms)
(if (= 3 (length form)) (concat "\\(?" (number-to-string n) ":" subforms "\\)")
;; Only one sub-form. `("\\(?" ,(number-to-string n) ":" ,@subforms "\\)"))))
(rx-form (nth 2 form))
;; Several sub-forms implicitly concatenated.
(mapconcat (lambda (re) (rx-form re ':)) (cddr form) nil))
"\\)")))
(defun rx-backref (form) (defun rx-backref (form)
"Parse and produce code from FORM, which is `(backref N)'." "Parse and produce code from FORM, which is `(backref N)'."
@ -761,9 +774,12 @@ is non-nil."
(t "?"))) (t "?")))
(op (cond ((memq (car form) '(* *? 0+ zero-or-more)) "*") (op (cond ((memq (car form) '(* *? 0+ zero-or-more)) "*")
((memq (car form) '(+ +? 1+ one-or-more)) "+") ((memq (car form) '(+ +? 1+ one-or-more)) "+")
(t "?")))) (t "?")))
(subform (rx-form (cadr form) '*)))
(rx-group-if (rx-group-if
(concat (rx-form (cadr form) '*) op suffix) (if (stringp subform)
(concat subform op suffix)
`(,@subform ,(concat op suffix)))
(and (memq rx-parent '(t *)) rx-parent)))) (and (memq rx-parent '(t *)) rx-parent))))
@ -791,15 +807,18 @@ regexps that are atomic but end in operators, such as
be detected without much effort. A guarantee of no false be detected without much effort. A guarantee of no false
negatives would require a theoretic specification of the set negatives would require a theoretic specification of the set
of all atomic regexps." of all atomic regexps."
(let ((l (length r))) (if (and rx--compile-to-lisp
(cond (not (stringp r)))
((<= l 1)) nil ;; Runtime value, we must assume non-atomic.
((= l 2) (= (aref r 0) ?\\)) (let ((l (length r)))
((= l 3) (string-match "\\`\\(?:\\\\[cCsS_]\\|\\[[^^]\\]\\)" r))
((null lax)
(cond (cond
((string-match "\\`\\[\\^?]?\\(?:\\[:[a-z]+:]\\|[^]]\\)*]\\'" r)) ((<= l 1))
((string-match "\\`\\\\(\\(?:[^\\]\\|\\\\[^)]\\)*\\\\)\\'" r))))))) ((= l 2) (= (aref r 0) ?\\))
((= l 3) (string-match "\\`\\(?:\\\\[cCsS_]\\|\\[[^^]\\]\\)" r))
((null lax)
(cond
((string-match "\\`\\[\\^?]?\\(?:\\[:[a-z]+:]\\|[^]]\\)*]\\'" r))
((string-match "\\`\\\\(\\(?:[^\\]\\|\\\\[^)]\\)*\\\\)\\'" r))))))))
(defun rx-syntax (form) (defun rx-syntax (form)
@ -855,9 +874,23 @@ If FORM is `(minimal-match FORM1)', non-greedy versions of `*',
(defun rx-regexp (form) (defun rx-regexp (form)
"Parse and produce code from FORM, which is `(regexp STRING)'." "Parse and produce code from FORM, which is `(regexp STRING)'."
(rx-check form) (cond ((stringp form)
(rx-group-if (cadr form) rx-parent)) (rx-group-if (cadr form) rx-parent))
(rx--compile-to-lisp
;; Always group non-string forms, since we can't be sure they
;; are atomic.
(rx-group-if (cdr form) t))
(t (rx-check form))))
(defun rx-literal (form)
"Parse and produce code from FORM, which is `(literal STRING-EXP)'."
(cond ((stringp form)
;; This is allowed, but makes little sense, you could just
;; use STRING directly.
(rx-group-if (regexp-quote (cadr form)) rx-parent))
(rx--compile-to-lisp
(rx-group-if `((regexp-quote ,(cadr form))) rx-parent))
(t (rx-check form))))
(defun rx-form (form &optional parent) (defun rx-form (form &optional parent)
"Parse and produce code for regular expression FORM. "Parse and produce code for regular expression FORM.
@ -888,12 +921,38 @@ shy groups around the result and some more in other functions."
(t (t
(error "rx syntax error at `%s'" form))))) (error "rx syntax error at `%s'" form)))))
(defun rx--subforms (subforms &optional parent separator)
"Produce code for regular expressions SUBFORMS.
SUBFORMS is a list of regular expression sexps.
PARENT controls grouping, as in `rx-form'.
Insert SEPARATOR between the code from each of SUBFORMS."
(if (null (cdr subforms))
;; Zero or one forms, no need for grouping.
(and subforms (rx-form (car subforms)))
(let ((listify (lambda (x)
(if (listp x) (copy-sequence x)
(list x)))))
(setq subforms (mapcar (lambda (x) (rx-form x parent)) subforms))
(cond ((or (not rx--compile-to-lisp)
(cl-every #'stringp subforms))
(mapconcat #'identity subforms separator))
(separator
(nconc (funcall listify (car subforms))
(mapcan (lambda (x)
(cons separator (funcall listify x)))
(cdr subforms))))
(t (mapcan listify subforms))))))
;;;###autoload ;;;###autoload
(defun rx-to-string (form &optional no-group) (defun rx-to-string (form &optional no-group)
"Parse and produce code for regular expression FORM. "Parse and produce code for regular expression FORM.
FORM is a regular expression in sexp form. FORM is a regular expression in sexp form.
NO-GROUP non-nil means don't put shy groups around the result." NO-GROUP non-nil means don't put shy groups around the result.
In contrast to the `rx' macro, subforms `literal' and `regexp'
will not accept non-string arguments, i.e., (literal STRING)
becomes just a more verbose version of STRING."
(rx-group-if (rx-form form) (null no-group))) (rx-group-if (rx-form form) (null no-group)))
@ -903,8 +962,12 @@ NO-GROUP non-nil means don't put shy groups around the result."
REGEXPS is a non-empty sequence of forms of the sort listed below. REGEXPS is a non-empty sequence of forms of the sort listed below.
Note that `rx' is a Lisp macro; when used in a Lisp program being Note that `rx' is a Lisp macro; when used in a Lisp program being
compiled, the translation is performed by the compiler. compiled, the translation is performed by the compiler. The
See `rx-to-string' for how to do such a translation at run-time. `literal' and `regexp' forms accept subforms that will evaluate
to strings, in addition to constant strings. If REGEXPS include
such forms, then the result is an expression which returns a
regexp string, rather than a regexp string directly. See
`rx-to-string' for performing translation completely at run time.
The following are valid subforms of regular expressions in sexp The following are valid subforms of regular expressions in sexp
notation. notation.
@ -1204,18 +1267,29 @@ enclosed in `(and ...)'.
`(backref N)' `(backref N)'
matches what was matched previously by submatch N. matches what was matched previously by submatch N.
`(literal STRING-EXPR)'
matches STRING-EXPR literally, where STRING-EXPR is any lisp
expression that evaluates to a string.
`(regexp REGEXP-EXPR)'
include REGEXP-EXPR in string notation in the result, where
REGEXP-EXPR is any lisp expression that evaluates to a
string containing a valid regexp.
`(eval FORM)' `(eval FORM)'
evaluate FORM and insert result. If result is a string, evaluate FORM and insert result. If result is a string,
`regexp-quote' it. `regexp-quote' it. Note that FORM is evaluated during
macroexpansion."
`(regexp REGEXP)' (let* ((rx--compile-to-lisp t)
include REGEXP in string notation in the result." (re (cond ((null regexps)
(cond ((null regexps) (error "No regexp"))
(error "No regexp")) ((cdr regexps)
((cdr regexps) (rx-to-string `(and ,@regexps) t))
(rx-to-string `(and ,@regexps) t)) (t
(t (rx-to-string (car regexps) t)))))
(rx-to-string (car regexps) t)))) (if (stringp re)
re
`(concat ,@re))))
(pcase-defmacro rx (&rest regexps) (pcase-defmacro rx (&rest regexps)
@ -1277,14 +1351,6 @@ string as argument to `match-string'."
for var in vars for var in vars
collect `(app (match-string ,i) ,var))))) collect `(app (match-string ,i) ,var)))))
;; ;; sregex.el replacement
;; ;;;###autoload (provide 'sregex)
;; ;;;###autoload (autoload 'sregex "rx")
;; (defalias 'sregex 'rx-to-string)
;; ;;;###autoload (autoload 'sregexq "rx" nil nil 'macro)
;; (defalias 'sregexq 'rx)
(provide 'rx) (provide 'rx)
;;; rx.el ends here ;;; rx.el ends here

View file

@ -115,5 +115,46 @@
;; Test zero-argument `seq'. ;; Test zero-argument `seq'.
(should (equal (rx (seq)) ""))) (should (equal (rx (seq)) "")))
(defmacro rx-tests--match (regexp string &optional match)
(macroexp-let2 nil strexp string
`(ert-info ((format "Matching %S to %S" ',regexp ,strexp))
(should (string-match ,regexp ,strexp))
,@(when match
`((should (equal (match-string 0 ,strexp) ,match)))))))
(ert-deftest rx-nonstring-expr ()
(let ((bee "b")
(vowel "[aeiou]"))
(rx-tests--match (rx "a" (literal bee) "c") "abc")
(rx-tests--match (rx "a" (regexp bee) "c") "abc")
(rx-tests--match (rx "a" (or (regexp bee) "xy") "c") "abc")
(rx-tests--match (rx "a" (or "xy" (regexp bee)) "c") "abc")
(should-not (string-match (rx (or (regexp bee) "xy")) ""))
(rx-tests--match (rx "a" (= 3 (regexp bee)) "c") "abbbc")
(rx-tests--match (rx "x" (= 3 (regexp vowel)) "z") "xeoez")
(should-not (string-match (rx "x" (= 3 (regexp vowel)) "z") "xe[]z"))
(rx-tests--match (rx "x" (= 3 (literal vowel)) "z")
"x[aeiou][aeiou][aeiou]z")
(rx-tests--match (rx "x" (repeat 1 (regexp vowel)) "z") "xaz")
(rx-tests--match (rx "x" (repeat 1 2 (regexp vowel)) "z") "xaz")
(rx-tests--match (rx "x" (repeat 1 2 (regexp vowel)) "z") "xauz")
(rx-tests--match (rx "x" (>= 1 (regexp vowel)) "z") "xaiiz")
(rx-tests--match (rx "x" (** 1 2 (regexp vowel)) "z") "xaiz")
(rx-tests--match (rx "x" (group (regexp vowel)) "z") "xaz")
(rx-tests--match (rx "x" (group-n 1 (regexp vowel)) "z") "xaz")
(rx-tests--match (rx "x" (? (regexp vowel)) "z") "xz")))
(ert-deftest rx-nonstring-expr-non-greedy ()
"`rx's greediness can't affect runtime regexp parts."
(let ((ad-min "[ad]*?")
(ad-max "[ad]*")
(ad "[ad]"))
(rx-tests--match (rx "c" (regexp ad-min) "a") "cdaaada" "cda")
(rx-tests--match (rx "c" (regexp ad-max) "a") "cdaaada" "cdaaada")
(rx-tests--match (rx "c" (minimal-match (regexp ad-max)) "a") "cdaaada" "cdaaada")
(rx-tests--match (rx "c" (maximal-match (regexp ad-min)) "a") "cdaaada" "cda")
(rx-tests--match (rx "c" (minimal-match (0+ (regexp ad))) "a") "cdaaada" "cda")
(rx-tests--match (rx "c" (maximal-match (0+ (regexp ad))) "a") "cdaaada" "cdaaada")))
(provide 'rx-tests) (provide 'rx-tests)
;; rx-tests.el ends here. ;; rx-tests.el ends here.