debug.ss
#lang scheme

(provide debug
         dprintf
         begin/debug
         define/debug
         define/private/debug
         define/public/debug
         define/override/debug
         define/augment/debug
         let/debug
         let*/debug
         letrec/debug
         let-values/debug
         let*-values/debug
         letrec-values/debug
         with-syntax/debug
         with-syntax*/debug
         parameterize/debug
         with-debugging)

(require "syntax.ss"
         (for-syntax scheme/match syntax/parse "syntax.ss"))

(define-syntax (let/debug stx)
  (syntax-parse stx
    [(_ (~optional loop:id) ... ([lhs:id rhs:expr] ...) body:expr ... last:expr)
     #`(with-debugging
        #:name '#,(if (attribute loop) #'loop #'let/debug)
        #:source (quote-srcloc #,stx)
        (let #,@(if (attribute loop) (list #'loop) null)
          ([lhs (with-debugging #:name 'lhs rhs)] ...)
          (debug body) ... (debug last)))]))

(define-syntaxes
  [ let*/debug
    letrec/debug
    let-values/debug
    let*-values/debug
    letrec-values/debug
    with-syntax/debug
    with-syntax*/debug
    parameterize/debug ]

  (let ()

    (define ((expander binder-id) stx)
      (with-syntax ([binder binder-id])
        (syntax-parse stx
          [(binder/debug:id ([lhs rhs:expr] ...) body:expr ... last:expr)
           #`(with-debugging
              #:name 'binder/debug
              #:source (quote-srcloc #,stx)
              (binder
               ([lhs (with-debugging #:name 'lhs rhs)] ...)
               (debug body) ... (debug last)))])))

    (values (expander #'let*)
            (expander #'letrec)
            (expander #'let-values)
            (expander #'let*-values)
            (expander #'letrec-values)
            (expander #'with-syntax)
            (expander #'with-syntax*)
            (expander #'parameterize))))

(define-syntaxes
  [ define/debug
    define/private/debug
    define/public/debug
    define/override/debug
    define/augment/debug ]

  (let ()

    (define-syntax-class header
      #:attributes [name]
      (pattern (name:id . _))
      (pattern (inner:header . _) #:attr name (attribute inner.name)))

    (define ((expander definer-id) stx)
      (with-syntax ([definer definer-id])
        (syntax-parse stx
          [(definer/debug:id name:id body:expr)
           #`(definer name
               (with-debugging
                #:name 'name
                #:source (quote-srcloc #,stx)
                body))]
          [(definer/debug:id spec:header body:expr ... last:expr)
           #`(definer spec
               (with-debugging
                #:name 'spec.name
                #:source (quote-srcloc #,stx)
                (let () body ... last)))])))

    (values (expander #'define)
            (expander #'define/private)
            (expander #'define/public)
            (expander #'define/override)
            (expander #'define/augment))))

(define-syntax (begin/debug stx)
  (syntax-parse stx
    [(_ term:expr ...)
     #`(with-debugging
        #:name 'begin/debug
        #:source (quote-srcloc #,stx)
        (begin (debug term) ...))]))

(define-syntax (debug stx)
  (syntax-parse stx
    [(_ term:expr)
     (syntax (with-debugging term))]))

(define-syntax (with-debugging stx)
  (syntax-parse stx
    [(_ (~or (~optional (~seq #:name name:expr))
             (~optional (~seq #:source source:expr)))
        ...
        body:expr)
     (with-syntax* ([name (or (attribute name) #'(quote body))]
                    [source (or (attribute source) #'(quote-srcloc body))])
       #'(with-debugging/proc
          name
          source
          (quote body)
          (lambda () (#%expression body))))]))

(define (srcloc->string src)
  (match src
    [(struct srcloc [source line col pos span])
     (format "~a~a"
       (or source "")
       (if line
         (if col
           (format ":~a.~a" line col)
           (format ":~a" line))
         (if pos
           (if span
             (format "::~a-~a" pos (+ pos span))
             (format "::~a" pos))
           "")))]))

(define (srcloc->prefix src)
  (let* ([str (srcloc->string src)])
    (if (string=? str "") "" (string-append str ": "))))

(define (with-debugging/proc name source term thunk)
  (let* ([src (srcloc->prefix (src->srcloc source))])
    (begin
      (dprintf ">> ~a~s" src name)
      (begin0
        (parameterize ([current-debug-depth
                        (add1 (current-debug-depth))])
          (call-with-values thunk
            (lambda results
              (match results
                [(list v) (dprintf "~s" v)]
                [(list vs ...)
                 (dprintf "(values~a)"
                          (apply string-append
                            (for/list ([v (in-list vs)])
                              (format " ~s" v))))])
              (apply values results))))
        (dprintf "<< ~a~s" src name)))))

(define (dprintf fmt . args)
  (let* ([message (apply format fmt args)]
         [prefix (make-string (* debug-indent (current-debug-depth)) #\space)]
         [indented
          (string-append
           prefix
           (regexp-replace* "\n" message (string-append "\n" prefix)))])
    (log-debug indented)))

(define current-debug-depth (make-parameter 0))
(define debug-indent 2)