1 ;;; geiser-syntax.el -- utilities for parsing scheme syntax
3 ;; Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2019, 2020 Jose Antonio Ortega Ruiz
5 ;; This program is free software; you can redistribute it and/or
6 ;; modify it under the terms of the Modified BSD License. You should
7 ;; have received a copy of the license along with this program. If
8 ;; not, see <http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5>.
10 ;; Start date: Sun Feb 08, 2009 15:03
15 (require 'geiser-impl
)
16 (require 'geiser-popup
)
17 (require 'geiser-base
)
21 (eval-when-compile (require 'cl-lib
))
26 (defmacro geiser-syntax--scheme-indent
(&rest pairs
)
27 `(progn ,@(mapcar (lambda (p)
28 `(put ',(car p
) 'scheme-indent-function
',(cadr p
)))
31 (geiser-syntax--scheme-indent
45 (match-let scheme-let-indent
)
59 (test-group-with-cleanup 1)
60 (test-runner-on-bad-count! 1)
61 (test-runner-on-bad-end-name! 1)
62 (test-runner-on-final! 1)
63 (test-runner-on-group-begin! 1)
64 (test-runner-on-group-end! 1)
65 (test-runner-on-test-begin! 1)
66 (test-runner-on-test-end! 1)
71 (with-exception-handler 1)
75 ;;; Extra syntax keywords
77 (defconst geiser-syntax--builtin-keywords
81 "define-condition-type"
82 "define-immutable-record-type"
106 "test-group-with-cleanup"
110 "with-exception-handler"
111 "with-input-from-file"
112 "with-output-to-file"))
114 (defun geiser-syntax--simple-keywords (keywords)
115 "Return `font-lock-keywords' to highlight scheme KEYWORDS.
116 KEYWORDS should be a list of strings."
118 `((,(format "[[(]%s\\>" (regexp-opt keywords
1)) .
1))))
120 (defun geiser-syntax--keywords ()
122 (geiser-syntax--simple-keywords geiser-syntax--builtin-keywords
)
123 `(("\\[\\(else\\)\\>" .
1)
124 (,(rx "(" (group "define-syntax-rule") eow
(* space
)
125 (?
"(") (?
(group (1+ word
))))
126 (1 font-lock-keyword-face
)
127 (2 font-lock-function-name-face nil t
)))))
129 (font-lock-add-keywords 'scheme-mode
(geiser-syntax--keywords))
131 (geiser-impl--define-caller geiser-syntax--impl-kws keywords
()
132 "A variable (or thunk returning a value) giving additional,
133 implementation-specific entries for font-lock-keywords.")
135 (geiser-impl--define-caller geiser-syntax--case-sensitive case-sensitive
()
136 "A flag saying whether keywords are case sensitive.")
138 (defun geiser-syntax--add-kws (&optional global-p
)
139 (unless (bound-and-true-p quack-mode
)
140 (let ((kw (geiser-syntax--impl-kws geiser-impl--implementation
))
141 (cs (geiser-syntax--case-sensitive geiser-impl--implementation
)))
142 (when kw
(font-lock-add-keywords nil kw
))
143 (when global-p
(font-lock-add-keywords nil
(geiser-syntax--keywords)))
144 (setq font-lock-keywords-case-fold-search
(not cs
)))))
146 (defun geiser-syntax--remove-kws ()
147 (unless (bound-and-true-p quack-mode
)
148 (let ((kw (geiser-syntax--impl-kws geiser-impl--implementation
)))
150 (font-lock-remove-keywords nil kw
)))))
153 ;;; A simple scheme reader
155 (defvar geiser-syntax--read
/buffer-limit nil
)
157 (defsubst geiser-syntax--read
/eos
()
159 (and geiser-syntax--read
/buffer-limit
160 (<= geiser-syntax--read
/buffer-limit
(point)))))
162 (defsubst geiser-syntax--read
/next-char
()
163 (unless (geiser-syntax--read/eos
)
167 (defsubst geiser-syntax--read
/token
(token)
168 (geiser-syntax--read/next-char
)
169 (if (listp token
) token
(list token
)))
171 (defsubst geiser-syntax--read
/elisp
()
172 (ignore-errors (read (current-buffer))))
174 (defun geiser-syntax--read/symbol
()
175 (with-syntax-table scheme-mode-syntax-table
176 (when (re-search-forward "\\(\\sw\\|\\s_\\)+" nil t
)
177 (make-symbol (match-string-no-properties 0)))))
179 (defun geiser-syntax--read/matching
(open close
)
182 (while (and (> count
0)
183 (geiser-syntax--read/next-char
))
184 (cond ((looking-at-p open
) (setq count
(1+ count
)))
185 ((looking-at-p close
) (setq count
(1- count
)))))
186 (buffer-substring-no-properties p
(point))))
188 (defsubst geiser-syntax--read
/unprintable
()
189 (geiser-syntax--read/token
190 (cons 'unprintable
(geiser-syntax--read/matching
"<" ">"))))
192 (defun geiser-syntax--read/skip-comment
()
193 (while (and (geiser-syntax--read/next-char
)
194 (nth 8 (syntax-ppss))))
195 (geiser-syntax--read/next-token
))
197 (defun geiser-syntax--read/next-token
()
198 (skip-syntax-forward "->")
199 (if (geiser-syntax--read/eos
) '(eob)
200 (cl-case (char-after)
201 (?\
; (geiser-syntax--read/skip-comment))
202 ((?\
( ?\
[) (geiser-syntax--read/token
'lparen
))
203 ((?\
) ?\
]) (geiser-syntax--read/token
'rparen
))
204 (?.
(if (memq (car (syntax-after (1+ (point)))) '(0 11 12))
205 (geiser-syntax--read/token
'dot
)
206 (cons 'atom
(geiser-syntax--read/elisp
))))
207 (?\
# (cl-case (geiser-syntax--read/next-char
)
209 (?|
(geiser-syntax--read/skip-comment
))
210 (?
: (if (geiser-syntax--read/next-char
)
211 (cons 'kwd
(geiser-syntax--read/symbol
))
213 (?
\\ (cons 'char
(geiser-syntax--read/elisp
)))
214 (?\
( (geiser-syntax--read/token
'vectorb
))
215 (?\
< (geiser-syntax--read/unprintable
))
216 ((?
' ?
` ?
,) (geiser-syntax--read/next-token
))
217 (t (let ((tok (geiser-syntax--read/symbol
)))
218 (cond ((equal (symbol-name tok
) "t") '(boolean .
:t
))
219 ((equal (symbol-name tok
) "f") '(boolean .
:f
))
220 (tok (cons 'atom tok
))
221 (t (geiser-syntax--read/next-token
)))))))
222 (?|
(cl-case (geiser-syntax--read/next-char
) ;; gambit style block comments
224 (?
# (geiser-syntax--read/skip-comment
))
225 (t (let ((tok (geiser-syntax--read/symbol
)))
226 (cond ((equal (symbol-name tok
) "t") '(boolean .
:t
))
227 ((equal (symbol-name tok
) "f") '(boolean .
:f
))
228 (tok (cons 'atom tok
))
229 (t (geiser-syntax--read/next-token
)))))))
230 (?
\' (geiser-syntax--read/token
'(quote . quote
)))
231 (?\
` (geiser-syntax--read/token
232 `(backquote .
,backquote-backquote-symbol
)))
233 (?
, (if (eq (geiser-syntax--read/next-char
) ?
@)
234 (geiser-syntax--read/token
235 `(splice .
,backquote-splice-symbol
))
236 `(unquote .
,backquote-unquote-symbol
)))
237 (?
\" (cons 'string
(geiser-syntax--read/elisp
)))
238 (t (let ((x (geiser-syntax--read/elisp
)))
239 (cons 'atom
(if (atom x
) x
(geiser-syntax--read/symbol
))))))))
241 (defsubst geiser-syntax--read
/match
(&rest tks
)
242 (let ((token (geiser-syntax--read/next-token
)))
243 (if (memq (car token
) tks
) token
244 (error "Unexpected token: %s" token
))))
246 (defsubst geiser-syntax--read
/skip-until
(&rest tks
)
248 (while (and (not (memq (car token
) tks
))
249 (not (eq (car token
) 'eob
)))
250 (setq token
(geiser-syntax--read/next-token
)))
253 (defsubst geiser-syntax--read
/try
(&rest tks
)
255 (tk (ignore-errors (apply 'geiser-syntax--read
/match tks
))))
256 (unless tk
(goto-char p
))
259 (defun geiser-syntax--read/list
()
260 (cond ((geiser-syntax--read/try
'dot
)
261 (let ((tail (geiser-syntax--read)))
262 (geiser-syntax--read/skip-until
'eob
'rparen
)
264 ((geiser-syntax--read/try
'rparen
'eob
) nil
)
265 (t (cons (geiser-syntax--read)
266 (geiser-syntax--read/list
)))))
268 (defun geiser-syntax--read ()
269 (let ((token (geiser-syntax--read/next-token
))
270 (max-lisp-eval-depth (max max-lisp-eval-depth
3000)))
273 (lparen (geiser-syntax--read/list
))
274 (vectorb (apply 'vector
(geiser-syntax--read/list
)))
275 ((quote backquote unquote splice
) (list (cdr token
)
276 (geiser-syntax--read)))
277 (kwd (make-symbol (format ":%s" (cdr token
))))
278 (unprintable (format "#<%s>" (cdr token
)))
279 ((char string atom
) (cdr token
))
280 (boolean (cdr token
))
281 (t (error "Reading scheme syntax: unexpected token: %s" token
)))))
283 (defun geiser-syntax--read-from-string (string &optional start end
)
284 (when (stringp string
)
285 (let* ((start (or start
0))
286 (end (or end
(length string
)))
287 (max-lisp-eval-depth (min 20000
288 (max max-lisp-eval-depth
(- end start
)))))
290 (save-excursion (insert string
))
291 (cons (ignore-errors (geiser-syntax--read)) (point))))))
293 (defun geiser-syntax--form-from-string (s)
294 (car (geiser-syntax--read-from-string s
)))
296 (defsubst geiser-syntax--form-after-point
(&optional boundary
)
297 (let ((geiser-syntax--read/buffer-limit
(and (numberp boundary
) boundary
)))
298 (save-excursion (list (geiser-syntax--read) (point)))))
300 (defun geiser-syntax--mapconcat (fun lst sep
)
301 (cond ((null lst
) "")
302 ((not (listp lst
)) (format ".%s%s" sep
(funcall fun lst
)))
303 ((null (cdr lst
)) (format "%s" (funcall fun
(car lst
))))
305 (funcall fun
(car lst
))
307 (geiser-syntax--mapconcat fun
(cdr lst
) sep
)))))
312 (defsubst geiser-syntax--symbol-at-point
()
313 (and (not (nth 8 (syntax-ppss)))
314 (car (geiser-syntax--read-from-string (thing-at-point 'symbol
)))))
316 (defsubst geiser-syntax--skip-comment
/string
()
317 (let ((pos (nth 8 (syntax-ppss))))
318 (goto-char (or pos
(point)))
321 (defsubst geiser-syntax--nesting-level
()
322 (or (nth 0 (syntax-ppss)) 0))
324 (defun geiser-syntax--pop-to-top ()
326 (while (> (geiser-syntax--nesting-level) 0) (backward-up-list))))
328 (defsubst geiser-syntax--in-string-p
()
329 (nth 3 (syntax-ppss)))
331 (defsubst geiser-syntax--pair-length
(p)
332 (if (cdr (last p
)) (1+ (safe-length p
)) (length p
)))
334 (defun geiser-syntax--shallow-form (boundary)
335 (when (looking-at-p "\\s(")
340 (while (< (point) boundary
)
341 (skip-syntax-forward "-<>")
342 (when (<= (point) boundary
)
344 (let ((s (thing-at-point 'symbol
)))
345 (unless (equal "." s
)
346 (push (car (geiser-syntax--read-from-string s
)) elems
))))))
349 (defsubst geiser-syntax--keywordp
(s)
350 (and s
(symbolp s
) (string-match "^:.+" (symbol-name s
))))
352 (defsubst geiser-syntax--symbol-eq
(s0 s1
)
353 (and (symbolp s0
) (symbolp s1
) (equal (symbol-name s0
) (symbol-name s1
))))
355 (defun geiser-syntax--scan-sexps (&optional begin
)
356 (let* ((fst (geiser-syntax--symbol-at-point))
357 (smth (or fst
(not (looking-at-p "[\s \s)\s>\s<\n]"))))
358 (path (and fst
`((,fst
0)))))
360 (while (> (or (geiser-syntax--nesting-level) 0) 0)
361 (let ((boundary (point)))
362 (geiser-syntax--skip-comment/string
)
364 (let ((form (geiser-syntax--shallow-form boundary
)))
365 (when (and (listp form
) (car form
) (symbolp (car form
)))
366 (let* ((len (geiser-syntax--pair-length form
))
367 (pos (if smth
(1- len
) (progn (setq smth t
) len
)))
368 (prev (and (> pos
1) (nth (1- pos
) form
)))
369 (prev (and (geiser-syntax--keywordp prev
)
371 (push `(,(car form
) ,pos
,@prev
) path
)))))))
373 (cons (substring-no-properties (format "%s" (car e
))) (cdr e
)))
376 (defsubst geiser-syntax--binding-form-p
(bfs sbfs f
)
378 (let ((f (symbol-name f
)))
379 (or (member f
'("define" "define*" "define-syntax"
380 "syntax-rules" "lambda" "case-lambda"
381 "let" "let*" "let-values" "let*-values"
382 "letrec" "letrec*" "parameterize"))
386 (defsubst geiser-syntax--binding-form
*-p
(sbfs f
)
388 (let ((f (symbol-name f
)))
389 (or (member f
'("let*" "let*-values" "letrec" "letrec*"))
392 (defsubst geiser-syntax--if-symbol
(x) (and (symbolp x
) x
))
393 (defsubst geiser-syntax--if-list
(x) (and (listp x
) x
))
395 (defsubst geiser-syntax--normalize
(vars)
397 (let ((i (if (listp i
) (car i
) i
)))
398 (and (symbolp i
) (symbol-name i
))))
401 (defun geiser-syntax--linearize (form)
402 (cond ((not (listp form
)) (list form
))
404 (t (cons (car form
) (geiser-syntax--linearize (cdr form
))))))
406 (defun geiser-syntax--scan-locals (bfs sbfs form nesting locals
)
407 (if (or (null form
) (not (listp form
)))
408 (geiser-syntax--normalize locals
)
409 (if (not (geiser-syntax--binding-form-p bfs sbfs
(car form
)))
410 (geiser-syntax--scan-locals bfs sbfs
413 (let* ((head (car form
))
414 (name (geiser-syntax--if-symbol (cadr form
)))
415 (names (if name
(geiser-syntax--if-list (caddr form
))
416 (geiser-syntax--if-list (cadr form
))))
418 (geiser-syntax--binding-form-p bfs sbfs
(car names
))))
419 (rest (if (and name
(not bns
)) (cdddr form
) (cddr form
)))
420 (use-names (and (or rest
422 (geiser-syntax--binding-form*-p sbfs head
))
424 (when name
(push name locals
))
425 (when (geiser-syntax--symbol-eq head
'case-lambda
)
426 (dolist (n (and (> nesting
0) (caar (last form
))))
427 (when n
(push n locals
)))
428 (setq rest
(and (> nesting
0) (cdr form
)))
429 (setq use-names nil
))
430 (when (geiser-syntax--symbol-eq head
'syntax-rules
)
431 (dolist (n (and (> nesting
0) (cdaar (last form
))))
432 (when n
(push n locals
)))
433 (setq rest
(and (> nesting
0) (cdr form
))))
435 (dolist (n (geiser-syntax--linearize names
))
436 (let ((xs (if (and (listp n
) (listp (car n
))) (car n
) (list n
))))
437 (dolist (x xs
) (when x
(push x locals
))))))
438 (dolist (f (butlast rest
))
440 (geiser-syntax--symbol-eq (car f
) 'define
)
442 (push (cadr f
) locals
)))
443 (geiser-syntax--scan-locals bfs sbfs
444 (car (last (or rest names
)))
448 (defun geiser-syntax--locals-around-point (bfs sbfs
)
449 (when (eq major-mode
'scheme-mode
)
451 (let ((sym (unless (geiser-syntax--skip-comment/string
)
452 (thing-at-point 'symbol
))))
453 (skip-syntax-forward "->")
454 (let ((boundary (point))
455 (nesting (geiser-syntax--nesting-level)))
456 (geiser-syntax--pop-to-top)
457 (cl-destructuring-bind (form end
)
458 (geiser-syntax--form-after-point boundary
)
460 (geiser-syntax--scan-locals bfs
467 ;;; Display and fontify strings as Scheme code:
469 (defun geiser-syntax--display (a)
470 (cond ((null a
) "()")
473 ((geiser-syntax--keywordp a
) (format "#%s" a
))
474 ((symbolp a
) (format "%s" a
))
475 ((equal a
"...") "...")
476 ((stringp a
) (format "%S" a
))
477 ((and (listp a
) (symbolp (car a
))
478 (equal (symbol-name (car a
)) "quote"))
479 (format "'%s" (geiser-syntax--display (cadr a
))))
482 (geiser-syntax--mapconcat 'geiser-syntax--display a
" ")))
483 (t (format "%s" a
))))
485 (defconst geiser-syntax--font-lock-buffer-name
" *geiser font lock*")
487 (defun geiser-syntax--font-lock-buffer-p (&optional buffer
)
488 (equal (buffer-name buffer
) geiser-syntax--font-lock-buffer-name
))
490 (defun geiser-syntax--font-lock-buffer ()
491 (or (get-buffer geiser-syntax--font-lock-buffer-name
)
492 (let ((buffer (get-buffer-create geiser-syntax--font-lock-buffer-name
)))
494 (let ((geiser-default-implementation
495 (or geiser-default-implementation
496 (car geiser-active-implementations
))))
500 (defun geiser-syntax--fontify (&optional beg end
)
501 (let ((font-lock-verbose nil
)
502 (beg (or beg
(point-min)))
503 (end (or end
(point-max))))
504 (if (fboundp 'font-lock-flush
)
505 (font-lock-flush beg end
)
506 (with-no-warnings (font-lock-fontify-region beg end
)))))
508 ;; derived from org-src-font-lock-fontify-block (org-src.el)
509 (defun geiser-syntax--fontify-syntax-region (start end
)
510 "Fontify region as Scheme."
511 (let ((string (buffer-substring-no-properties start end
))
512 (modified (buffer-modified-p))
514 (geiser-buffer (current-buffer)))
516 (get-buffer-create " *geiser-repl-fontification*")
517 (let ((inhibit-modification-hooks nil
))
519 ;; Add string and a final space to ensure property change.
521 ;; prevent geiser prompt
522 (let ((geiser-default-implementation
523 (or geiser-default-implementation
524 (car geiser-active-implementations
))))
526 (geiser--font-lock-ensure)
527 (let ((pos (point-min)) next
)
528 (while (setq next
(next-property-change pos
))
529 ;; Handle additional properties from font-lock, so as to
530 ;; preserve, e.g., composition.
531 (dolist (prop (cons 'face font-lock-extra-managed-props
))
532 (let ((new-prop (get-text-property pos prop
))
533 (start-point (+ start
(1- pos
)))
534 (end-point (1- (+ start next
))))
535 (put-text-property start-point end-point prop new-prop geiser-buffer
)))
539 '(font-lock-fontified t
541 font-lock-multiline t
))
542 (set-buffer-modified-p modified
)))
544 (defun geiser-syntax--scheme-str (str)
546 (set-buffer (geiser-syntax--font-lock-buffer))
549 (geiser-syntax--fontify)
553 (provide 'geiser-syntax
)