-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathcexp.el
179 lines (164 loc) · 7.2 KB
/
cexp.el
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
;;; cexp.el --- Combined expressions, i.e., combinations of regexp and sexp -*- lexical-binding: t; -*-
;; Copyright (C) 2016 Tobias.Zawada (aka Tobias.Naehring)
;; Author: Tobias.Zawada <[email protected]>
;; Keywords: lisp, matching
;; Version: 0.0.1
;; URL: https://github.com/TobiasZawada/cexp
;; Package-Requires: ((emacs "25.1"))
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;; Poorman's implementation of combined expressions.
;; Combined expressions are combinations of regular expressions and balanced expressions.
;; You can use cexp-search-forward for searching combined expressions.
;; Some way of storing the match-data and the balanced expressions is provided.
;; Example:
;; (setq var-match-data (cexp-search-forward "foo\\!(\\`(\\([[:alpha:]][[:alnum:]]*\\)\\(,[[:alpha:]][[:alnum:]]*\\)*)\\'\\!)"))
;; This combined expression matches the following string:
;; foo(x,y34,zoo)
;; Investigate the match data:
;; (set-match-data var-match-data)
;; For an example get the function name:
;; (match-string-no-properties 0)
;; And get the balanced expression inclusive the delimiters which are assured to be `(' and `)' in this example:
;; (match-string-no-properties 1)
;;
;;; Changes:
;;
;; 2012-01-25: Upload.
;;
;; 2016-12-19:
;; - Added functionality of the arguments BOUND NOERROR and COUNT
;; - Return start position of match instead of match-data
;;
;; 2019-10-13:
;; - Make cexp ready for melpa.
;;
;;; Code:
(defcustom cexp-start "!("
"Start control string of balanced expression within regular expression."
:type 'string
:group 'cexp)
(defcustom cexp-end "!)"
"End control string of balanced expression within regular expression."
:type 'string
:group 'cexp)
(defun cexp-find-special (cexp &optional cexp-re start)
"Find first balanced special string for sexp within CEXP after START.
CEXP-RE is the regular expression matching either beginning or end of a sexp.
Returns cons with start position of the special string and the special
string itself."
(unless cexp-re
(setq cexp-re (concat "\\(" cexp-start "\\|" cexp-end "\\)")))
(while (and (setq start (string-match (concat "\\\\" cexp-re) cexp start))
(> start 0)
(= ?\\ (elt cexp (1- start))))
(setq start (1+ start)))
(and start (cons start (match-string-no-properties 1 cexp))))
(defun cexp-find-top-sexp (cexp &optional start)
"Return info about the first top level sexp in CEXP after START.
The return value is a cons (BEG . END)
of the beginning position BEG and the end position END of that sexp.
The sexp control string is delimited
by the strings defined in `cexp-start' and `cexp-end'.
BEG points to the start string `cexp-start' within cexp and
END points at the character behind end string `cexp-end' or to the end of CEXP."
(let* ((cexp-re (concat "\\(" cexp-start "\\|" cexp-end "\\)"))
(c (cexp-find-special cexp cexp-re start))
(b (car c)))
(if (setq start b)
(progn
(setq start (1+ start))
(unless (string= (cdr c) cexp-start) (error "Unbalanced cexp"))
(let ((cnt 1))
(while (and
(> cnt 0)
(car (setq c (cexp-find-special cexp cexp-re start))))
(if (string= (cdr c) cexp-start)
(setq cnt (1+ cnt))
(setq cnt (1- cnt)))
(setq start (1+ (car c))))
(when (> cnt 0) (error "Unbalanced cexp %d" cnt)))
(cons b (+ (car c) 1 (length cexp-end))))
nil)))
(defun cexp-search-forward1 (cexp &optional var-match-data bound)
"Internal driver for `cexp-search-forward'.
Decomposes CEXP into regexps and sexps, searches for the regexps
and recursively the stuff of the sexps which can be again regexps and sexps.
VAR-MATCH-DATA contains the match data collected so far.
BOUND delimits the search like in `search-forward'.
Returns the match data for all matching groups."
(let* ((sexpBegEnd (cexp-find-top-sexp cexp))
(endRegexp (if sexpBegEnd (car sexpBegEnd)))
(re (substring cexp 0 endRegexp)))
;; Handle leading regexp
(when (search-forward-regexp re bound t)
(setq var-match-data (append var-match-data (match-data)))
;; Handle sexp and tail
(if sexpBegEnd
(let
((buf-sexpBeg (point))
(buf-sexpEnd (condition-case nil (scan-sexps (point) 1) (scan-error nil))))
(when buf-sexpEnd
(goto-char buf-sexpEnd)
(setq var-match-data (append var-match-data (list (set-marker (make-marker) buf-sexpBeg) (set-marker (make-marker) buf-sexpEnd))))
;; Handle sexp
(save-excursion
(save-restriction
(narrow-to-region buf-sexpBeg buf-sexpEnd)
(goto-char (point-min))
(setq var-match-data (cexp-search-forward1 (substring cexp (+ (length cexp-start) (car sexpBegEnd) 1) (- (cdr sexpBegEnd) (length cexp-end) 1)) var-match-data bound))))
;; Handle tail if there is any. (The tail may be anything...)
(when (and var-match-data (not (= (length cexp) (cdr sexpBegEnd))))
(setq var-match-data (cexp-search-forward1 (substring cexp (cdr sexpBegEnd)) var-match-data bound)))
var-match-data))
var-match-data))))
;;;###autoload
(defun cexp-search-forward (cexp &optional bound noerror count)
"Search for combined regular and balanced expression CEXP.
The syntax of CEXP is almost that of a regular expression
with the exception that the string \\!( introduces a balanced expression
and \\!) closes a balanced expression.
The matched balanced expressions and the matches for the regular expressions
before, in between, and after the sexps appear in the match data.
Regular expression braces \\( and \\) may not include balanced expressions.
On the other hand balanced expressions may include
regular expressions with groups.
The optional parameters BOUND, NOERROR, AND COUNT
work like for `search-forward'."
(interactive "sCombined regexp and sexp:")
(let (var-match-data
(counter (or count 1))
(pos (point)))
(save-excursion
(while (if (setq var-match-data (cexp-search-forward1 cexp nil bound)) ; may fail after partial match
(> (setq counter (1- counter)) 0)
(and (> (point) pos)
(< (point) (or bound (point-max)))))
(setq pos (point))))
(if var-match-data
(let ((group0 (list (car var-match-data) (apply 'max var-match-data))))
(setq var-match-data (append group0 var-match-data))
(set-match-data var-match-data)
(goto-char (cadr var-match-data)))
(if noerror
(progn
(unless (eq noerror t)
(goto-char (or bound (point-max))))
(set-match-data nil))
(signal 'search-failed cexp)))))
(defun cexp-isearch-forward ()
"Call function `isearch-forward' with variable `isearch-search-fun-function' bound to symbol `cexp-search-forward'."
(interactive)
(let ((isearch-search-fun-function (lambda () #'cexp-search-forward)))
(isearch-forward)))
(provide 'cexp)
;;; cexp.el ends here