3434 table)
3535 " Elixir mode syntax table." )
3636
37+ (defconst elixir-smie-grammar
38+ (smie-prec2->grammar
39+ (smie-merge-prec2s
40+ (smie-bnf->prec2
41+ '((id)
42+ (statements (statement)
43+ (statement " ;" statements))
44+ (statement (" def" non-block-expr " do" statements " end" )
45+ (non-block-expr " fn" match-statement " end" )
46+ (non-block-expr " do" statements " end" )
47+ (" if" non-block-expr " do" statements " else" statements " end" )
48+ (" if" non-block-expr " do" statements " end" )
49+ (" if" non-block-expr " COMMA" " do:" non-block-expr)
50+ (" if" non-block-expr " COMMA"
51+ " do:" non-block-expr " COMMA"
52+ " else:" non-block-expr)
53+ (" try" " do" statements " after" statements " end" )
54+ (" try" " do" statements " catch" match-statements " end" )
55+ (" try" " do" statements " end" )
56+ (" case" non-block-expr " do" match-statements " end" ))
57+ (non-block-expr (non-block-expr " OP" non-block-expr)
58+ (non-block-expr " COMMA" non-block-expr)
59+ (" (" non-block-expr " )" )
60+ (" {" non-block-expr " }" )
61+ (" [" non-block-expr " ]" )
62+ (" STRING" ))
63+ (match-statements (match-statement " MATCH-STATEMENT-DELIMITER"
64+ match-statements)
65+ (match-statement))
66+ (match-statement (non-block-expr " ->" statements)))
67+ '((assoc " if" " do:" " else:" )
68+ (assoc " COMMA" )
69+ (left " OP" )))
70+
71+ (smie-precs->prec2
72+ '((left " ||" )
73+ (left " &&" )
74+ (nonassoc " =~" " ===" " !==" " ==" " !=" " <=" " >=" " <" " >" )
75+ (left " +" " -" " <<<" " >>>" " ^^^" " ~~~" " &&&" " |||" )
76+ (left " *" " /" ))))))
77+
78+ (defvar elixir-smie--operator-regexp
79+ (regexp-opt '(" <<<" " >>>" " ^^^" " ~~~" " &&&" " |||" " ===" " !==" " ==" " !=" " <="
80+ " >=" " <" " >" " &&" " ||" " <>" " ++" " --" " //"
81+ " />" " =~" " |>" " ->" )))
82+
83+ (defvar elixir-smie-indent-basic 2 )
84+
3785(defmacro elixir-smie-debug (message &rest format-args )
3886 `(progn
3987 (when elixir-smie-verbose-p
4088 (message (format , message ,@format-args )))
4189 nil ))
4290
43- (progn
44- (setq elixir-syntax-class-names nil )
45-
46- (defmacro elixir-smie-define-regexp-opt (name &rest table )
47- `(elixir-smie-define-regexp , name (regexp-opt (list ,@table ))))
48-
49- (defmacro elixir-smie-define-regexp (name regexp &optional flag )
50- (let ((regex-name (intern (format " elixir-smie-%s " name))))
51- `(progn
52- (defconst , regex-name
53- , regexp )
54- (pushnew `(,', regex-name . ,(upcase (symbol-name ', name ))) elixir-syntax-class-names))))
55-
56- (elixir-smie-define-regexp-opt op " &&" " ||" " !" )
57- (elixir-smie-define-regexp dot " \\ ." )
58- (elixir-smie-define-regexp comma " ," )
59- (elixir-smie-define-regexp -> " ->" )
60- (elixir-smie-define-regexp << " <<" )
61- (elixir-smie-define-regexp >> " >>" )
62- (elixir-smie-define-regexp-opt parens " (" " )" " {" " }" " [" " ]" " <<" " >>" ))
63-
64- (defconst elixir-smie-block-intro-keywords
65- '(do else catch after rescue -> OP)
66- " Keywords in which newlines cause confusion for the parser." )
67-
68- (defun elixir-skip-comment-backward ()
69- " Skip backwards over all whitespace and comments.
70-
71- Return non-nil if any line breaks were skipped."
72- (let ((start-line-no (line-number-at-pos (point ))))
73- (forward-comment (- (point )))
74- (/= start-line-no (line-number-at-pos (point )))))
75-
76- (defun elixir-skip-comment-forward ()
77- " Skip forward over any whitespace and comments.
78-
79- Return non-nil if any line breaks were skipped."
80- (let ((start-line-no (line-number-at-pos (point ))))
81- (forward-comment (buffer-size ))
82- (/= start-line-no (line-number-at-pos (point )))))
83-
84- (defun elixir-smie-next-token-no-lookaround (forwardp )
85- (block elixir-smie-next-token-no-lookaround
86- ; ; First, skip comments; but if any comments / newlines were
87- ; ; skipped, the upper level needs to check if they were significant:
88- (when (if forwardp
89- (elixir-skip-comment-forward)
90- (elixir-skip-comment-backward))
91- (return-from elixir-smie-next-token-no-lookaround " \n " ))
92- (let* ((found-token-class (find-if
93- (lambda (class-def )
94- (let ((regex (symbol-value (car class-def))))
95- (if forwardp
96- (looking-at regex)
97- (looking-back regex nil t ))))
98- elixir-syntax-class-names))
99- (maybe-token
100- (let ((current-char (if forwardp
101- (following-char )
102- (preceding-char ))))
103- (cond ((member current-char
104- '(?\n ?\; ))
105- (if forwardp
106- (forward-comment (point-max ))
107- (forward-comment (- (point ))))
108- (string current-char))
109- (found-token-class
110- (goto-char (if forwardp
111- (match-end 0 )
112- (match-beginning 0 )))
113- (if (string= " PARENS" (cdr found-token-class))
114- (buffer-substring-no-properties (match-beginning 0 ) (match-end 0 ))
115- (cdr found-token-class)))
116- ((when (= ?\" (char-syntax (if forwardp
117- (following-char )
118- (preceding-char ))))
119- (if forwardp
120- (forward-sexp )
121- (backward-sexp ))
122- " STRING" ))))))
123- (or maybe-token
124- (downcase
125- (buffer-substring-no-properties
126- (point )
127- (if forwardp
128- (progn (skip-syntax-forward " 'w_" )
129- (point ))
130- (progn (skip-syntax-backward " 'w_" )
131- (point )))))))))
91+ (defun elixir-smie--at-dot-call ()
92+ (and (eq ?w (char-syntax (following-char )))
93+ (eq (char-before ) ?. )
94+ (not (eq (char-before (1- (point ))) ?. ))))
13295
133- (defun elixir-smie-next-token (forwardp )
134- (block elixir-smie-next-token
135- (let ((current-token (elixir-smie-next-token-no-lookaround forwardp)))
136- (when (string= " \n " current-token)
137- ; ; This is a newline; if the previous token isn't an OP2, this
138- ; ; means the line end marks the end of a statement & we get to
139- ; ; scan forward until there's a non-newline token; otherwise,
140- ; ; make this line ending something that probably ends the
141- ; ; statement (but see below).
142- (if (save-excursion
143- (block nil
144- (let ((token (elixir-smie-next-token-no-lookaround nil )))
145- (while (and (not (= (point ) (point-min )))
146- (string= " \n " token))
147- (setq token (elixir-smie-next-token-no-lookaround nil )))
148- (when (member (intern token) elixir-smie-block-intro-keywords)
149- (return t )))))
150- ; ; it's a continuation line, return the next token after the newline:
151- (return-from elixir-smie-next-token (elixir-smie-next-token forwardp))
152- (setq current-token " ;" )))
153-
154- ; ; When reading match statements (the ones with expr -> statements),
155- ; ; we need to drop non-; delimiters so the parser knows when a
156- ; ; match statement ends and another begins, so scan around point to
157- ; ; see if there are any -> within the current block's scope.
158-
159- ; ; If the current token is a ";", scan forward to see if the current
160- ; ; potential statement contains a "->". If so, scan back to find a
161- ; ; "do". If there is a -> there, emit a match-statement-delimiter
162- ; ; instead of the ";".
163- (if (and (string= " ;" current-token)
164- ; ; Scan ahead:
165- (let ((level 0 )
166- token)
167- (save-excursion
168- (block nil
169- (while
170- (and
171- ; ; Cursor is not at the end of the buffer...
172- (not (= (point ) (point-max )))
173- ; ; ...and the current token is not an empty string...
174- (not (string= " " token))
175- ; ; ...nor a newline nor a semicolon.
176- (not (or (string= " \n " token) (string= " ;" token))))
177- (setq token (elixir-smie-next-token-no-lookaround t ))
178- ; ; If we're at the top level and the token is "->",
179- ; ; return t
180- (cond ((and (= level 0 ) (string= " ->" token))
181- (return t ))
182- ; ; If token is "do" or "fn", increment level
183- ((find token '(" do" " fn" ) :test 'string= )
184- (incf level))
185- ; ; If token is "end", decrement level
186- ((string= token " end" )
187- (decf level)))))))
188- ; ; Scan behind:
189- (let (token)
190- (save-excursion
191- (block nil
192- (while
193- (and
194- ; ; Cursor is not at the beginning of buffer...
195- (not (= (point ) (point-min )))
196- ; ; ...and token is neither empty string, nor "do"/"fn"
197- (not (string= " " token))
198- (not (string= " do" token))
199- (not (string= " fn" token)))
200- (setq token (elixir-smie-next-token-no-lookaround nil ))
201- (when (string= " ->" token)
202- (return t )))
203- (when (string= token " do" ) t )))))
204- " MATCH-STATEMENT-DELIMITER"
205- current-token))))
96+ (defun elixir-smie--implicit-semi-p ()
97+ (not (or (memq (char-before ) '(?\{ ?\[ ))
98+ (looking-back elixir-smie--operator-regexp (- (point ) 3 ) t ))))
20699
207100(defun elixir-smie-forward-token ()
208- (elixir-smie-next-token t ))
101+ (cond
102+ ((and (looking-at " [\n #]" ) (elixir-smie--implicit-semi-p))
103+ (if (eolp ) (forward-char 1 ) (forward-comment 1 ))
104+ " ;" )
105+ ((looking-at elixir-smie--operator-regexp)
106+ (goto-char (match-end 0 ))
107+ " OP" )
108+ (t (smie-default-forward-token ))))
209109
210110(defun elixir-smie-backward-token ()
211- (elixir-smie-next-token nil ))
212-
213- (defconst elixir-smie-grammar
214- (smie-prec2->grammar
215- (smie-bnf->prec2
216- '((id)
217- (statements (statement)
218- (statement " ;" statements))
219- (statement (" def" non-block-expr " do" statements " end" )
220- (non-block-expr " fn" match-statement " end" )
221- (non-block-expr " do" statements " end" )
222- (" if" non-block-expr " do" statements " else" statements " end" )
223- (" if" non-block-expr " do" statements " end" )
224- (" if" non-block-expr " COMMA" " do:" non-block-expr)
225- (" if" non-block-expr " COMMA"
226- " do:" non-block-expr " COMMA"
227- " else:" non-block-expr)
228- (" try" " do" statements " after" statements " end" )
229- (" try" " do" statements " catch" match-statements " end" )
230- (" try" " do" statements " end" )
231- (" case" non-block-expr " do" match-statements " end" ))
232- (non-block-expr (non-block-expr " OP" non-block-expr)
233- (non-block-expr " COMMA" non-block-expr)
234- (" (" statements " )" )
235- (" {" statements " }" )
236- (" [" statements " ]" )
237- (" STRING" ))
238- (match-statements (match-statement " MATCH-STATEMENT-DELIMITER" match-statements)
239- (match-statement))
240- (match-statement (non-block-expr " ->" statements)))
241- '((assoc " if" " do:" " else:" )
242- (assoc " COMMA" )
243- (left " OP" )))))
244-
245- (defvar elixir-smie-indent-basic 2 )
111+ (let ((pos (point )))
112+ (forward-comment (- (point )))
113+ (cond
114+ ((and (> pos (line-end-position ))
115+ (elixir-smie--implicit-semi-p))
116+ " ;" )
117+ ((looking-back elixir-smie--operator-regexp (- (point ) 3 ) t )
118+ (goto-char (match-beginning 0 ))
119+ " OP" )
120+ (t (smie-default-backward-token )))))
246121
247122(defun verbose-elixir-smie-rules (kind token )
248123 (let ((value (elixir-smie-rules kind token)))
@@ -256,35 +131,25 @@ Return non-nil if any line breaks were skipped."
256131
257132(defun elixir-smie-rules (kind token )
258133 (pcase (cons kind token)
259- (`(:after . " STRING" )
260- (if (smie-rule-prev-p " do:" )
261- (smie-rule-parent 0 )
262- nil ))
263- (`(:elem . basic)
264- (if (smie-rule-hanging-p )
265- 0
266- elixir-smie-indent-basic))
267134 (`(:after . " OP" )
268- (unless (smie-rule-sibling-p )
269- elixir-smie-indent-basic))
135+ (cond
136+ ((smie-rule-sibling-p ) nil )
137+ ((smie-rule-hanging-p ) (smie-rule-parent elixir-smie-indent-basic))
138+ (t elixir-smie-indent-basic)))
270139 (`(:before . " def" ) elixir-smie-indent-basic)
271140 ; ; If the parent token of `->' is `fn' , then we want to align to the
272141 ; ; parent, and offset by `elixir-smie-indent-basic' . Otherwise, indent
273142 ; ; normally. This helps us work with/indent anonymous function blocks
274143 ; ; correctly.
275- (`(:after . " ->" )
276- (when (smie-rule-hanging-p )
277- (if (smie-rule-parent-p " fn" )
278- (smie-rule-parent elixir-smie-indent-basic)
279- elixir-smie-indent-basic)))
280- (`(:after . " do" )
281- elixir-smie-indent-basic)
282- (`(:list-intro . ,(or `" do" `" ;" )) t )
144+ (`(:before . " ;" )
145+ (cond
146+ ((smie-rule-parent-p " after" " catch" " def" " defmodule" " defp" " do" " else"
147+ " fn" " if" " rescue" " try" " unless" )
148+ (smie-rule-parent elixir-smie-indent-basic))))
283149 (`(:after . " ;" )
284150 (if (smie-rule-parent-p " if" )
285151 (smie-rule-parent 0 )))))
286152
287-
288153(define-minor-mode elixir-smie-mode
289154 " SMIE-based indentation and syntax for Elixir"
290155 nil nil nil nil
0 commit comments