;;; TREE-IL -> GLIL compiler
-;; Copyright (C) 2001,2008,2009,2010 Free Software Foundation, Inc.
+;; Copyright (C) 2001, 2008, 2009, 2010, 2011, 2012 Free Software Foundation, Inc.
;;;; This library is free software; you can redistribute it and/or
;;;; modify it under the terms of the GNU Lesser General Public
#:use-module (srfi srfi-1)
#:use-module (srfi srfi-9)
#:use-module (srfi srfi-11)
+ #:use-module (srfi srfi-26)
#:use-module (ice-9 vlist)
+ #:use-module (ice-9 match)
#:use-module (system base syntax)
#:use-module (system base message)
#:use-module (system vm program)
;; returns variables referenced in expr
(define (analyze! x proc labels-in-proc tail? tail-call-args)
- (define (step y) (analyze! y proc labels-in-proc #f #f))
+ (define (step y) (analyze! y proc '() #f #f))
(define (step-tail y) (analyze! y proc labels-in-proc tail? #f))
(define (step-tail-call y args) (analyze! y proc labels-in-proc #f
(and tail? args)))
(else '())))
;; allocation: sym -> {lambda -> address}
- ;; lambda -> (nlocs labels . free-locs)
+ ;; lambda -> (labels . free-locs)
+ ;; lambda-case -> (gensym . nlocs)
(define allocation (make-hash-table))
(define (allocate! x proc n)
(vars binding-info-vars) ;; ((GENSYM NAME LOCATION) ...)
(refs binding-info-refs)) ;; (GENSYM ...)
+(define (gensym? sym)
+ ;; Return #t if SYM is (likely) a generated symbol.
+ (string-any #\space (symbol->string sym)))
+
(define unused-variable-analysis
;; Report unused variables in the given tree.
(make-tree-analysis
;; the LOCS location stack.
(loc (or (caddr var)
(find pair? locs))))
- (warning 'unused-variable loc name)))))
+ (if (and (not (gensym? name))
+ (not (eq? name '_)))
+ (warning 'unused-variable loc name))))))
vars)
(vlist-drop vars (length inner-vars)))
(vlist-for-each (lambda (name+loc)
(let ((name (car name+loc))
(loc (cdr name+loc)))
- (warning 'unused-toplevel loc name)))
+ (if (not (gensym? name))
+ (warning 'unused-toplevel loc name))))
unused))))
(make-reference-graph vlist-null vlist-null #f))))
;; the name of the variable being defined; otherwise return #f. This
;; assumes knowledge of the current implementation of `define-class' et al.
(define (toplevel-define-arg args)
- (and (pair? args) (pair? (cdr args)) (null? (cddr args))
- (record-case (car args)
- ((<const> exp)
- (and (symbol? exp) exp))
- (else #f))))
-
- (record-case proc
- ((<module-ref> mod public? name)
- (and (equal? mod '(oop goops))
- (not public?)
- (eq? name 'toplevel-define!)
- (toplevel-define-arg args)))
- ((<toplevel-ref> name)
+ (match args
+ ((($ <const> _ (and (? symbol?) exp)) _)
+ exp)
+ (_ #f)))
+
+ (match proc
+ (($ <module-ref> _ '(oop goops) 'toplevel-define! #f)
+ (toplevel-define-arg args))
+ (($ <toplevel-ref> _ 'toplevel-define!)
;; This may be the result of expanding one of the GOOPS macros within
;; `oop/goops.scm'.
- (and (eq? name 'toplevel-define!)
- (eq? env (resolve-module '(oop goops)))
+ (and (eq? env (resolve-module '(oop goops)))
(toplevel-define-arg args)))
- (else #f)))
+ (_ #f)))
(define unbound-variable-analysis
;; Report possibly unbound variables in the given tree.
(make-toplevel-info (vhash-consq name src refs)
defs))))
((<toplevel-define> name)
- (make-toplevel-info (vhash-delete name refs eq?)
+ (make-toplevel-info (vhash-delq name refs)
(vhash-consq name #t defs)))
((<application> proc args)
(let ((name (goops-toplevel-definition proc args
env)))
(if (symbol? name)
- (make-toplevel-info (vhash-delete name refs
- eq?)
+ (make-toplevel-info (vhash-delq name refs)
(vhash-consq name #t defs))
(make-toplevel-info refs defs))))
(else
(false-if-exception
(module-ref env name))))
proc)))
- (if (or (lambda? proc*) (procedure? proc*))
- (validate-arity proc* application (lambda? proc*)))))
+ (cond ((lambda? proc*)
+ (validate-arity proc* application #t))
+ ((struct? proc*)
+ ;; An applicable struct.
+ (let ((p (struct-ref proc* 0)))
+ (and (procedure? p)
+ (validate-arity p application #f))))
+ ((procedure? proc*)
+ (validate-arity proc* application #f)))))
toplevel-calls)))
(make-arity-info vlist-null vlist-null vlist-null)))
;;; `format' argument analysis.
;;;
+(define &syntax-error
+ ;; The `throw' key for syntax errors.
+ (gensym "format-string-syntax-error"))
+
(define (format-string-argument-count fmt)
;; Return the minimum and maxium number of arguments that should
;; follow format string FMT (or, ahem, a good estimate thereof) or
(let loop ((chars chars)
(tilde? #f))
(if (null? chars)
- chars ;; syntax error?
+ (throw &syntax-error 'unterminated-iteration)
(if tilde?
(if (eq? (car chars) end)
(cdr chars)
(max-count 0))
(if (null? chars)
(if end-group
- (values #f #f) ;; syntax error
+ (throw &syntax-error 'unterminated-conditional)
(values min-count max-count))
(case state
((tilde)
(if (null? maxs)
0
(apply max maxs))))))
- (values #f #f)))))
+ (values 'any 'any))))) ;; XXX: approximation
0 0))
((#\;)
- (loop (cdr chars) 'literal '()
- (cons (cons min-count max-count) conditions)
- end-group
- 0 0))
+ (if end-group
+ (loop (cdr chars) 'literal '()
+ (cons (cons min-count max-count) conditions)
+ end-group
+ 0 0)
+ (throw &syntax-error 'unexpected-semicolon)))
((#\])
(if end-group
(end-group (cdr chars)
(reverse (cons (cons min-count max-count)
conditions)))
- (values #f #f))) ;; syntax error
+ (throw &syntax-error 'unexpected-conditional-termination)))
((#\{) (if (memq #\@ params)
(values min-count 'any)
(loop (drop-group (cdr chars) #\})
;; We don't have enough info to determine the exact number
;; of args, but we could determine a lower bound (TODO).
(values 'any 'any))
+ ((#\h #\H)
+ (let ((argc (if (memq #\: params) 2 1)))
+ (loop (cdr chars) 'literal '()
+ conditions end-group
+ (+ argc min-count)
+ (+ argc max-count))))
(else (loop (cdr chars) 'literal '()
conditions end-group
(+ 1 min-count) (+ 1 max-count)))))
min-count max-count))))
(else (error "computer bought the farm" state))))))
+(define (proc-ref? exp proc special-name env)
+ "Return #t when EXP designates procedure PROC in ENV. As a last
+resort, return #t when EXP refers to the global variable SPECIAL-NAME."
+
+ (define special?
+ (cut eq? <> special-name))
+
+ (match exp
+ (($ <toplevel-ref> _ (? special?))
+ ;; Allow top-levels like: (define _ (cut gettext <> "my-domain")).
+ #t)
+ (($ <toplevel-ref> _ name)
+ (let ((var (module-variable env name)))
+ (and var (variable-bound? var)
+ (eq? (variable-ref var) proc))))
+ (($ <module-ref> _ _ (? special?))
+ #t)
+ (($ <module-ref> _ module name public?)
+ (let* ((mod (if public?
+ (false-if-exception (resolve-interface module))
+ (resolve-module module #:ensure #f)))
+ (var (and mod (module-variable mod name))))
+ (and var (variable-bound? var) (eq? (variable-ref var) proc))))
+ (($ <lexical-ref> _ (? special?))
+ #t)
+ (_ #f)))
+
+(define gettext? (cut proc-ref? <> gettext '_ <>))
+(define ngettext? (cut proc-ref? <> ngettext 'N_ <>))
+
+(define (const-fmt x env)
+ ;; Return the literal format string for X, or #f.
+ (match x
+ (($ <const> _ (? string? exp))
+ exp)
+ (($ <application> _ (? (cut gettext? <> env))
+ (($ <const> _ (? string? fmt))))
+ ;; Gettexted literals, like `(_ "foo")'.
+ fmt)
+ (($ <application> _ (? (cut ngettext? <> env))
+ (($ <const> _ (? string? fmt)) ($ <const> _ (? string?)) _ ..1))
+ ;; Plural gettextized literals, like `(N_ "singular" "plural" n)'.
+
+ ;; TODO: Check whether the singular and plural strings have the
+ ;; same format escapes.
+ fmt)
+ (_ #f)))
+
(define format-analysis
;; Report arity mismatches in the given tree.
(make-tree-analysis
(define (check-format-args args loc)
(pmatch args
((,port ,fmt . ,rest)
- (guard (and (const? fmt) (string? (const-exp fmt))))
- (let ((fmt (const-exp fmt))
+ (guard (const-fmt fmt env))
+ (if (and (const? port)
+ (not (boolean? (const-exp port))))
+ (warning 'format loc 'wrong-port (const-exp port)))
+ (let ((fmt (const-fmt fmt env))
(count (length rest)))
- (let-values (((min max)
- (format-string-argument-count fmt)))
- (and min max
- (or (and (or (eq? min 'any) (>= count min))
- (or (eq? max 'any) (<= count max)))
- (warning 'format loc fmt min max count))))))
- (else #t)))
+ (catch &syntax-error
+ (lambda ()
+ (let-values (((min max)
+ (format-string-argument-count fmt)))
+ (and min max
+ (or (and (or (eq? min 'any) (>= count min))
+ (or (eq? max 'any) (<= count max)))
+ (warning 'format loc 'wrong-format-arg-count
+ fmt min max count)))))
+ (lambda (_ key)
+ (warning 'format loc 'syntax-error key fmt)))))
+ ((,port ,fmt . ,rest)
+ (if (and (const? port)
+ (not (boolean? (const-exp port))))
+ (warning 'format loc 'wrong-port (const-exp port)))
+
+ (match fmt
+ (($ <const> loc* (? (negate string?) fmt))
+ (warning 'format (or loc* loc) 'wrong-format-string fmt))
+
+ ;; Warn on non-literal format strings, unless they refer to
+ ;; a lexical variable named "fmt".
+ (($ <lexical-ref> _ fmt)
+ #t)
+ ((? (negate const?))
+ (warning 'format loc 'non-literal-format-string))))
+ (else
+ (warning 'format loc 'wrong-num-args (length args)))))
+
+ (define (check-simple-format-args args loc)
+ ;; Check the arguments to the `simple-format' procedure, which is
+ ;; less capable than that of (ice-9 format).
+
+ (define allowed-chars
+ '(#\A #\S #\a #\s #\~ #\%))
+
+ (define (format-chars fmt)
+ (let loop ((chars (string->list fmt))
+ (result '()))
+ (match chars
+ (()
+ (reverse result))
+ ((#\~ opt rest ...)
+ (loop rest (cons opt result)))
+ ((_ rest ...)
+ (loop rest result)))))
+
+ (match args
+ ((port ($ <const> _ (? string? fmt)) _ ...)
+ (let ((opts (format-chars fmt)))
+ (or (every (cut memq <> allowed-chars) opts)
+ (begin
+ (warning 'format loc 'simple-format fmt
+ (find (negate (cut memq <> allowed-chars)) opts))
+ #f))))
+ ((port (= (cut const-fmt <> env) (? string? fmt)) args ...)
+ (check-simple-format-args `(,port ,(make-const loc fmt) ,args) loc))
+ (_ #t)))
(define (resolve-toplevel name)
(and (module? env)
(false-if-exception (module-ref env name))))
- (record-case x
- ((<application> proc args src)
- (let ((loc src))
- (record-case proc
- ((<toplevel-ref> name src)
- (let ((proc (resolve-toplevel name)))
- (and (or (eq? proc format)
- (eq? proc (@ (ice-9 format) format)))
- (check-format-args args (or src (find pair? locs))))))
- (else #t)))
- #t)
- (else #t))
+ (match x
+ (($ <application> src ($ <toplevel-ref> _ name) args)
+ (let ((proc (resolve-toplevel name)))
+ (if (or (and (eq? proc (@ (guile) simple-format))
+ (check-simple-format-args args
+ (or src (find pair? locs))))
+ (eq? proc (@ (ice-9 format) format)))
+ (check-format-args args (or src (find pair? locs))))))
+ (($ <application> src ($ <module-ref> _ '(ice-9 format) 'format) args)
+ (check-format-args args (or src (find pair? locs))))
+ (($ <application> src ($ <module-ref> _ '(guile)
+ (or 'format 'simple-format))
+ args)
+ (and (check-simple-format-args args
+ (or src (find pair? locs)))
+ (check-format-args args (or src (find pair? locs)))))
+ (_ #t))
#t)
(lambda (x _ env locs)