1 ;;; lint --- Preemptive checks for coding errors in Guile Scheme code
3 ;; Copyright (C) 2002, 2006, 2011 Free Software Foundation, Inc.
5 ;; This program is free software; you can redistribute it and/or
6 ;; modify it under the terms of the GNU Lesser General Public License
7 ;; as published by the Free Software Foundation; either version 3, or
8 ;; (at your option) any later version.
10 ;; This program is distributed in the hope that it will be useful,
11 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
12 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 ;; Lesser General Public License for more details.
15 ;; You should have received a copy of the GNU Lesser General Public
16 ;; License along with this software; see the file COPYING.LESSER. If
17 ;; not, write to the Free Software Foundation, Inc., 51 Franklin
18 ;; Street, Fifth Floor, Boston, MA 02110-1301 USA
20 ;;; Author: Neil Jerram
24 ;; Usage: lint FILE1 FILE2 ...
26 ;; Perform various preemptive checks for coding errors in Guile Scheme
29 ;; Right now, there is only one check available, for unresolved free
30 ;; variables. The intention is that future lint-like checks will be
31 ;; implemented by adding to this script file.
33 ;; Unresolved free variables
34 ;; -------------------------
36 ;; Free variables are those whose definitions come from outside the
37 ;; module under investigation. In Guile, these definitions are
38 ;; imported from other modules using `#:use-module' forms.
40 ;; This tool scans the specified files for unresolved free variables -
41 ;; i.e. variables for which you may have forgotten the appropriate
42 ;; `#:use-module', or for which the module that is supposed to export
45 ;; It isn't guaranteed that the scan will find absolutely all such
46 ;; errors. Quoted (and quasiquoted) expressions are skipped, since
47 ;; they are most commonly used to describe constant data, not code, so
48 ;; code that is explicitly evaluated using `eval' will not be checked.
49 ;; For example, the `unresolved-var' in `(eval 'unresolved-var
50 ;; (current-module))' would be missed.
52 ;; False positives are also possible. Firstly, the tool doesn't
53 ;; understand all possible forms of implicit quoting; in particular,
54 ;; it doesn't detect and expand uses of macros. Secondly, it picks up
55 ;; explicit compatibility code like `(if (defined? 'x) (define y x))'.
56 ;; Thirdly, there are occasional oddities like `next-method'.
57 ;; However, the number of false positives for realistic code is
58 ;; hopefully small enough that they can be individually considered and
64 ;; Note: most of the unresolved variables found in this example are
65 ;; false positives, as you would hope. => scope for improvement.
67 ;; $ guild lint `guild`
68 ;; No unresolved free variables in PROGRAM
69 ;; No unresolved free variables in autofrisk
70 ;; No unresolved free variables in display-commentary
71 ;; Unresolved free variables in doc-snarf:
73 ;; No unresolved free variables in frisk
74 ;; No unresolved free variables in generate-autoload
75 ;; No unresolved free variables in lint
76 ;; No unresolved free variables in punify
77 ;; No unresolved free variables in read-scheme-source
78 ;; Unresolved free variables in snarf-check-and-output-texi:
99 ;; No unresolved free variables in use2dot
103 (define-module (scripts lint)
104 #:use-module (ice-9 common-list)
105 #:use-module (ice-9 format)
108 (define %include-in-guild-list #f)
109 (define %summary "Check for bugs and style errors in a Scheme file.")
111 (define (lint filename)
112 (let ((module-name (scan-file-for-module-name filename))
113 (free-vars (uniq (scan-file-for-free-variables filename))))
114 (let ((module (resolve-module module-name))
116 (format #t "Resolved module: ~S\n" module)
117 (let loop ((free-vars free-vars))
118 (or (null? free-vars)
122 (eval (car free-vars) module))
126 "Unresolved free variables in ~A:\n"
129 (write (car free-vars))
131 (set! all-resolved? #f)))
132 (loop (cdr free-vars)))))
135 "No unresolved free variables in ~A\n"
138 (define (scan-file-for-module-name filename)
139 (with-input-from-file filename
141 (let loop ((x (read)))
142 (cond ((eof-object? x) #f)
144 (eq? (car x) 'define-module))
146 (else (loop (read))))))))
148 (define (scan-file-for-free-variables filename)
149 (with-input-from-file filename
151 (let loop ((x (read)) (fvlists '()))
153 (apply append fvlists)
154 (loop (read) (cons (detect-free-variables x '()) fvlists)))))))
156 ; guile> (detect-free-variables '(let ((a 1)) a) '())
158 ; guile> (detect-free-variables '(let ((a 1)) b) '())
160 ; guile> (detect-free-variables '(let ((a 1) (b a)) b) '())
162 ; guile> (detect-free-variables '(let* ((a 1) (b a)) b) '())
164 ; guile> (detect-free-variables '(define a 1) '())
166 ; guile> (detect-free-variables '(define a b) '())
168 ; guile> (detect-free-variables '(define (a b c) b) '())
170 ; guile> (detect-free-variables '(define (a b c) e) '())
173 (define (detect-free-variables x locals)
174 ;; Given an expression @var{x} and a list @var{locals} of local
175 ;; variables (symbols) that are in scope for @var{x}, return a list
176 ;; of free variable symbols.
178 (if (memq x locals) '() (list x)))
182 ((define-module define-generic quote quasiquote)
183 ;; No code of interest in these expressions.
187 ;; Check for named let. If there is a name, transform the
188 ;; expression so that it looks like an unnamed let with
189 ;; the name as one of the bindings.
190 (if (symbol? (cadr x))
191 (set-cdr! x (cons (cons (list (cadr x) #f) (caddr x))
193 ;; Unnamed let processing.
194 (let ((letrec? (eq? (car x) 'letrec))
195 (locals-for-let-body (append locals (map car (cadr x)))))
196 (append (apply append
197 (map (lambda (binding)
198 (detect-free-variables (cadr binding)
204 (map (lambda (bodyform)
205 (detect-free-variables bodyform
206 locals-for-let-body))
210 ;; Handle bindings recursively.
213 (map (lambda (bodyform)
214 (detect-free-variables bodyform locals))
216 (append (detect-free-variables (cadr (caadr x)) locals)
217 (detect-free-variables `(let* ,(cdadr x) ,@(cddr x))
218 (cons (caaadr x) locals)))))
220 ((define define-public define-macro)
223 (set! locals (cons (caadr x) locals))
224 (detect-free-variables `(lambda ,(cdadr x) ,@(cddr x))
227 (set! locals (cons (cadr x) locals))
228 (detect-free-variables (caddr x) locals))))
231 (let ((locals-for-lambda-body (let loop ((locals locals)
233 (cond ((null? args) locals)
235 (loop (cons (car args) locals)
238 (cons args locals))))))
240 (map (lambda (bodyform)
241 (detect-free-variables bodyform
242 locals-for-lambda-body))
246 (let ((locals-for-receive-body (append locals (cadr x))))
248 (detect-free-variables (caddr x) locals)
249 (map (lambda (bodyform)
250 (detect-free-variables bodyform
251 locals-for-receive-body))
254 ((define-method define*)
255 (let ((locals-for-method-body (let loop ((locals locals)
257 (cond ((null? args) locals)
259 (loop (cons (if (pair? (car args))
265 (cons args locals))))))
267 (map (lambda (bodyform)
268 (detect-free-variables bodyform
269 locals-for-method-body))
273 ;; Avoid picking up slot names at the start of slot
276 (map (lambda (slot/option)
277 (detect-free-variables-noncar (if (pair? slot/option)
285 (detect-free-variables (cadr x) locals)
287 (detect-free-variables (cdr case) locals))
290 ((unquote unquote-splicing else =>)
291 (detect-free-variables-noncar (cdr x) locals))
293 (else (append (detect-free-variables (car x) locals)
294 (detect-free-variables-noncar (cdr x) locals)))))
298 (define (detect-free-variables-noncar x locals)
299 ;; Given an expression @var{x} and a list @var{locals} of local
300 ;; variables (symbols) that are in scope for @var{x}, return a list
301 ;; of free variable symbols.
303 (if (memq x locals) '() (list x)))
308 (detect-free-variables-noncar (cdr x) locals))
310 (else (append (detect-free-variables (car x) locals)
311 (detect-free-variables-noncar (cdr x) locals)))))
315 (define (main . files)
316 (for-each lint files))