-
Notifications
You must be signed in to change notification settings - Fork 46
/
Copy pathnim-syntax.el
393 lines (358 loc) · 15.1 KB
/
nim-syntax.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
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
;;; nim-syntax.el --- -*- lexical-binding: t -*-
;;
;; 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 2, 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; see the file COPYING. If not, write to
;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth
;; Floor, Boston, MA 02110-1301, USA.
;;
;;; Commentary:
;; Sorry, this implementation is pretty much black magic ish...
;; Maybe understanding Emacs' Syntax Class Table would help.
;; my memo: seems like posix_other_consts.nim is good example to test
;; fortify limitation; adding new face would cause unfinished
;; fortification. From user's side, they could solve by
;; `revert-buffer', but I'm not sure this is good approach...
;;; Code:
(require 'nim-vars)
(require 'nim-rx)
(defvar nim-font-lock-keywords
`((,(nim-rx (or line-start ";") (* " ")
defun (+ " ")
(group (or identifier quoted-chars) (* " ") (? (group "*"))))
(1 (if (match-string 2)
'nim-font-lock-export-face
font-lock-function-name-face)
keep t)
;; (8 font-lock-type-face keep t) TODO nim-rx needs a proper colon-type expression
)
;; Highlight everything that starts with a capital letter as type.
(,(rx symbol-start (char upper) (* (char alnum "_")) symbol-end) . (0 font-lock-type-face keep))
;; Warning face for tab characters.
(" +" . (0 font-lock-warning-face))
;; This only works if it’s one line
(,(nim-rx (or line-start ";") (* " ")
(or "var" "let" "const" "type") (+ " ")
(group (or identifier quoted-chars) (* " ") (? (group "*"))))
. (1 (if (match-string 2)
'nim-font-lock-export-face
font-lock-variable-name-face))))
"Font lock expressions for Nim mode.")
(defvar nim-font-lock-keywords-extra
`(;; export properties
(,(nim-rx
line-start (1+ " ")
(? "case" (+ " "))
(group
(or identifier quoted-chars) "*"
(? (and "[" word "]"))
(0+ (and "," (? (0+ " "))
(or identifier quoted-chars) "*")))
(0+ " ") (or ":" "{." "=") (0+ nonl)
line-end)
(1 'nim-font-lock-export-face))
;; Number literal
(,(nim-rx nim-numbers)
(0 'nim-font-lock-number-face))
;; Highlight identifier enclosed by "`"
(nim-backtick-matcher
(10 font-lock-constant-face prepend))
;; Highlight $# and $[0-9]+ inside string
(nim-format-$-matcher 0 font-lock-preprocessor-face prepend)
;; Highlight word after ‘is’ and ‘distinct’
(,(nim-rx " " (or "is" "distinct") (+ " ")
(group identifier))
(1 font-lock-type-face))
;; pragma
(nim-pragma-matcher . (0 'nim-font-lock-pragma-face)))
"Extra font-lock keywords.
If you feel uncomfortable because of this font-lock keywords,
set nil to this value by ‘nim-mode-init-hook’.")
(defun nim--convert-to-nim-style-insensitive (str)
(let ((first-str (substring str 0 1))
(rest-str (substring str 1 (length str))))
(format "%s_?%s" first-str
(mapconcat
(lambda (s)
(if (string-match "[a-zA-Z]" s)
(format "[%s%s]" (downcase s) (upcase s))
s))
(split-string rest-str (rx not-word-boundary))
"_?"))))
(defun nim--format-keywords (keywords)
(format "\\_<\\(%s\\)\\_>"
(mapconcat
'nim--convert-to-nim-style-insensitive
(cl-typecase keywords
(symbol (symbol-value keywords))
(list keywords))
"\\|")))
(defvar nim-font-lock-keywords-2
(append
(cl-loop
with pairs = `((nim-types . font-lock-type-face)
(nim-variables . font-lock-variable-name-face)
(nim-exceptions . 'error)
(nim-constants . font-lock-constant-face)
(nim-builtin-functions . font-lock-builtin-face)
(nim-nonoverloadable-builtins . 'nim-non-overloadable-face)
(nim-keywords . font-lock-keyword-face))
for (keywords . face) in pairs
collect (cons (nim--format-keywords keywords) face))
`((,(rx symbol-start "result" symbol-end) . font-lock-variable-name-face))))
(defvar nim-font-lock-keywords-3
(list (cons (nim--format-keywords 'nim-builtins-without-nimscript)
font-lock-builtin-face)))
(defvar nimscript-keywords
(append
`(,(cons (nim--format-keywords 'nimscript-builtins)
font-lock-builtin-face)
,(cons (nim--format-keywords 'nimscript-variables)
font-lock-variable-name-face))
`((,(rx symbol-start "task" symbol-end (1+ " ")
(group symbol-start (or "build" "tests" "bench") symbol-end))
(1 font-lock-builtin-face))
("\\_<ScriptMode\\_>" (0 font-lock-type-face)))))
(defsubst nim-syntax-count-quotes (quote-char &optional point limit)
"Count number of quotes around point (max is 3).
QUOTE-CHAR is the quote char to count. Optional argument POINT is
the point where scan starts (defaults to current point), and LIMIT
is used to limit the scan."
(let ((i 0))
(while (and (< i 3)
(or (not limit) (< (+ point i) limit))
(eq (char-after (+ point i)) quote-char))
(setq i (1+ i)))
i))
(defconst nim-syntax-propertize-function
(syntax-propertize-rules
;; single/multi line comment
((rx (or (group (or line-start (not (any "]#\"")))
(group "#" (? "#") "["))
(group "]" "#" (? "#"))
(group "#")))
(0 (ignore (nim-syntax-commentify))))
;; Char
;; Put syntax entry("\"") for character type to highlight
;; when only the character-delimiter regex matched.
((nim-rx character-delimiter)
(1 "\"") ; opening quote
(2 "\"")) ; closing quote
;; String
((nim-rx string-delimiter)
(0 (ignore (nim-syntax-stringify))))))
(defun nim-pretty-triple-double-quotes (pbeg pend &optional close-quote)
(when (and nim-pretty-triple-double-quotes
(bound-and-true-p prettify-symbols-mode))
(compose-region pbeg pend
(if close-quote
(or (cdr nim-pretty-triple-double-quotes)
(car nim-pretty-triple-double-quotes))
(car nim-pretty-triple-double-quotes)))))
(defun nim-syntax--raw-string-p (pos)
"Return non-nil if char of before POS is not word syntax class."
;; See also #212
(when (> pos 1)
(eq ?w (char-syntax (char-before pos)))))
(defun nim-syntax-stringify ()
"Put `syntax-table' property correctly on single/triple double quotes."
(unless (nth 4 (syntax-ppss))
(let* ((num-quotes (length (match-string-no-properties 1)))
(ppss (prog2
(backward-char num-quotes)
(syntax-ppss)
(forward-char num-quotes)))
(string-start (and (not (nth 4 ppss)) (nth 8 ppss)))
(quote-starting-pos (- (point) num-quotes))
(quote-ending-pos (point))
(num-closing-quotes
(and string-start
(nim-syntax-count-quotes
(char-before) string-start quote-starting-pos))))
(cond ((and string-start (= num-closing-quotes 0))
;; This set of quotes doesn't match the string starting
;; kind. Do nothing.
nil)
((not string-start)
;; This set of quotes delimit the start of a string.
(put-text-property quote-starting-pos (1+ quote-starting-pos)
'syntax-table (string-to-syntax "|"))
(when (eq num-quotes 3)
(nim-pretty-triple-double-quotes
quote-starting-pos (+ quote-starting-pos 3))))
((and string-start (< string-start (- (point) 2)) ;; avoid r""
(not (eq 3 num-closing-quotes))
;; Skip "" in the raw string literal
(nim-syntax--raw-string-p string-start)
(or
;; v point is here
;; ""
(and
(eq ?\" (char-before (1- (point))))
(eq ?\" (char-before (point))))
;; v point is here
;; ""
(and
(eq ?\" (char-before (point)))
(eq ?\" (char-after (point))))))
nil)
((= num-quotes num-closing-quotes)
;; This set of quotes delimit the end of a string.
;; If there are some double quotes after quote-ending-pos,
;; shift the point to right number of `extra-quotes' times.
(let* ((extra-quotes 0))
;; Only count extra quotes when the double quotes is 3 to prevent
;; wrong highlight for r"foo""bar" forms.
(when (eq num-quotes 3)
(while (eq ?\" (char-after (+ quote-ending-pos extra-quotes)))
(setq extra-quotes (1+ extra-quotes))))
;; #212 Change syntax class of "\" before end of double quote because
;; Nim support end of "\" in raw string literal.
;; """str""" will be handled by regex of string delimiter on nim-rx.el
(when (and
(eq num-closing-quotes 1)
(nim-syntax--raw-string-p string-start)
(eq ?\\ (char-after (- (point) 2))))
(put-text-property (- (point) 2) (1- (point))
'syntax-table (string-to-syntax ".")))
(let ((pbeg (+ (1- quote-ending-pos) extra-quotes))
(pend (+ quote-ending-pos extra-quotes)))
(put-text-property
pbeg pend 'syntax-table (string-to-syntax "|"))
(when (eq num-quotes 3)
(nim-pretty-triple-double-quotes (- pend 3) pend t)))))
((> num-quotes num-closing-quotes)
;; This may only happen whenever a triple quote is closing
;; a single quoted string. Add string delimiter syntax to
;; all three quotes.
(put-text-property quote-starting-pos quote-ending-pos
'syntax-table (string-to-syntax "|")))))))
(defun nim-syntax-commentify ()
"Put comment syntax property for Nim's single and multi line comment."
(let* ((hash (or (match-string-no-properties 2)
(match-string-no-properties 3)
(match-string-no-properties 4)))
(start-pos (- (point) (length hash)))
(ppss (syntax-ppss))
(start-len (save-excursion
(when (nth 8 ppss)
(goto-char (nth 8 ppss))
(looking-at "##?\\[")
(length (match-string 0))))))
(cond
;; single line comment
((and (eq nil (nth 4 ppss)) (eq 1 (length hash)))
;; comment start
(put-text-property start-pos (1+ start-pos)
'syntax-table (string-to-syntax "<"))
;; comment end
;; #112, make sure ‘comment-indent-new-line’ (C-M-j key)
(put-text-property (point-at-eol) (point-at-eol)
'syntax-table (string-to-syntax ">")))
;; ignore
((or (eq t (nth 4 ppss)) ; t means single line comment
(<= (length hash) 1)
;; don't put syntax comment start or end
;; if it’s "#[" or "]#" inside ##[]##
(and start-len (= 3 start-len) (= 2 (length hash))))
nil)
;; multi comment line start
((eq ?# (string-to-char hash))
(put-text-property start-pos (1+ start-pos)
'syntax-table (string-to-syntax "< bn")))
;; multi comment line end
((eq ?\] (string-to-char hash))
(put-text-property (1- (point)) (point)
'syntax-table (string-to-syntax "> bn"))))))
(defun nim-syntax-context-type (&optional syntax-ppss)
"Return the context type using SYNTAX-PPSS.
The type returned can be `comment', `string' or `paren'."
(let ((ppss (or syntax-ppss (syntax-ppss))))
(cond
((nth 8 ppss) (if (nth 4 ppss) 'comment 'string))
((nth 1 ppss) 'paren))))
(defun nim-syntax--context-compiler-macro (form type &optional syntax-ppss)
(pcase type
(`'comment
`(let ((ppss (or ,syntax-ppss (syntax-ppss))))
(and (nth 4 ppss) (nth 8 ppss))))
(`'string
`(let ((ppss (or ,syntax-ppss (syntax-ppss))))
(and (nth 3 ppss) (nth 8 ppss))))
(`'paren
`(nth 1 (or ,syntax-ppss (syntax-ppss))))
(_ form)))
(defun nim-syntax-context (type &optional syntax-ppss)
"Return non-nil if point is on TYPE using SYNTAX-PPSS.
TYPE can be `comment', `string' or `paren'. It returns the start
character address of the specified TYPE."
(declare (compiler-macro nim-syntax--context-compiler-macro))
(let ((ppss (or syntax-ppss (syntax-ppss))))
(pcase type
(`comment (and (nth 4 ppss) (nth 8 ppss)))
(`string (and (nth 3 ppss) (nth 8 ppss)))
(`paren (nth 1 ppss))
(_ nil))))
(defsubst nim-syntax-comment-or-string-p (&optional ppss)
"Return non-nil if PPSS is inside 'comment or 'string."
(nth 8 (or ppss (syntax-ppss))))
(defsubst nim-syntax-closing-paren-p ()
"Return non-nil if char after point is a closing paren."
(= (syntax-class (syntax-after (point)))
(syntax-class (string-to-syntax ")"))))
;;;;;;;;;;;;;;;;;;;;;;;
;; Highlight matcher
(defun nim-backtick-matcher (&optional limit)
"Highlight matcher for ``symbol`` in comment."
(let (res)
(while
(and
(setq res (re-search-forward
(nim-rx backticks) limit t))
(not (nth 4 (syntax-ppss)))))
res))
(defconst nim--string-interpolation-regex
;; I think two digit is enough...
(rx "$" (or "#" (and (in "1-9") (? num)))))
(defun nim-format-$-matcher (&optional limit)
"Highlight matcher for $# and $[1-9][0-9]? in string within LIMIT."
(let (res)
(while
(and
(setq res (re-search-forward
nim--string-interpolation-regex limit t))
(not (nth 3 (syntax-ppss)))))
res))
(defun nim-inside-pragma-p ()
(let* ((ppss (syntax-ppss))
(pos (nth 1 ppss)))
(and
;; not in comment or string
(not (or (nth 3 ppss) (nth 4 ppss)))
;; there is an open brace
pos
;; open brace is curly
(eq ?\{ (char-after pos))
;; followed by a dot
(eq ?. (char-after (1+ pos))))))
(defconst nim-pragma-regex (nim--format-keywords (mapcar 'car nim-pragmas)))
(defun nim-pragma-matcher (&optional limit)
"Highlight pragma."
(let (res)
(while
(and
(setq res (re-search-forward
nim-pragma-regex limit t))
(not (nim-inside-pragma-p))))
res))
(provide 'nim-syntax)
;;; nim-syntax.el ends here