#lang scheme/gui
(require "util.ss")
(provide
debug-callback
compile-for-debug
start-debugger
repeat-action-callback
break-callback
inspect-value
update-breakpoints)
(define (debug-callback defs parent settings)
(define fname (send defs get-filename))
(define tab (send defs get-tab))
(define frame (send tab get-frame))
(define-values (file-path file-name file-is-dir?)
(split-path fname))
(define executable-name
(compile-for-debug
parent
settings
(if (eq? (system-type 'os) 'windows)
(ocaml:strip-crlf fname)
fname)))
(if executable-name
(begin
(send defs ocaml:clean-up)
(send frame ocaml:show-debug)
(send frame ensure-rep-shown #t)
(let-values ([(proc in out err) (start-debugger executable-name tab settings)])
(send tab ocaml:set-debug-process (ocaml:make-process proc in out err)))
(update-breakpoints tab (send (send tab get-defs) ocaml:debug:get-breakpoints))
(start-debug-threads tab)
(send (send defs get-keymap) chain-to-keymap ocaml:debugger-keymap #t))
(message-box
"Compilation error"
"This program cannot compile. Please fix the errors and try again.")))
(define (start-debug-threads tab)
(define defs (send tab get-defs))
(define process-obj (send tab ocaml:get-debug-process))
(define proc (ocaml:process-proc process-obj))
(define in (ocaml:process-in process-obj))
(define out (ocaml:process-out process-obj))
(define err (ocaml:process-err process-obj))
(send defs ocaml:debug:set-in-handler (thread (debugger-output-handler tab in)))
(send defs ocaml:debug:set-err-handler (thread (debugger-output-handler tab err))))
(define (compile-for-debug parent settings filename)
(with-handlers
([exn:fail? (λ (exn) (ocaml:not-installed) #f)])
(define executable-name (path-replace-suffix filename ".exe"))
(if (ocaml:lang-settings? settings)
(let*-values
([(lsm) (ocaml:lang-settings-modules settings)]
[(lsi)
(let
([path-list
(filter
(λ (x) (not (regexp-match "^[ \t\n]*$" x)))
(regexp-split ";" (ocaml:lang-settings-includes settings)))])
(apply string-append (map (λ (x) (format "-I ~a " x)) path-list)))]
[(args)
(filter
(λ (x) (not (equal? x "")))
(list
#f #f #f
(ocaml:lang-settings-compiler settings)
"-g"
"-o" (path->string executable-name)
lsm
lsi
"-impl" (path->string filename)))]
[(proc in out err) (apply subprocess args)])
(subprocess-wait proc)
(if (= (subprocess-status proc) 0)
executable-name
(begin
(message-box
"Compilation error"
"This program cannot compile. Please fix the errors and try again.")
#f)))
(begin
(message-box
"Wrong language"
"You can only use the debugger with the OCaml language.")
#f))))
(define (start-debugger executable-name tab settings)
(with-handlers
([exn:fail?
(λ (exn)
(ocaml:not-installed)
(send tab ocaml:kill-debug)
(values #f #f #f #f))])
(define-values (file-path file-name file-is-dir?) (split-path executable-name))
(define ocamldebug-executable (ocaml:lang-settings-debugger settings))
(define _1 (current-directory file-path))
(define _2 (putenv "PATH" (string-append "/usr/local/bin:/usr/bin:/bin:" (getenv "PATH"))))
(define-values (proc in out err)
(subprocess
#f #f #f
ocamldebug-executable
"-emacs"
(path->string file-name)))
(send (send tab get-frame) open-status-line 'ocaml:debugger)
(write-string "frame\n" out)
(flush-output out)
(ocaml:clear-all-text err)
(ocaml:clear-all-text in)
(values proc in out err)))
(define (repeat-action-callback tab message direction)
(define process-obj (send tab ocaml:get-debug-process))
(define out (ocaml:process-out process-obj))
(send (send tab get-defs) ocaml:reset-highlighting)
(write-string message out)
(newline out)
(flush-output out))
(define (ocaml:display-debugger-output tab message)
(define ints (send tab get-ints))
(define (thunk)
(send ints display-results message))
(send ints run-in-evaluation-thread thunk))
(define (debugger-output-handler tab port)
(λ ()
(define defs (send tab get-defs))
(define ints (send tab get-ints))
(define-values (in-pipe out-pipe) (make-pipe))
(define (read-chars-loop)
(define process-obj (send tab ocaml:get-debug-process))
(when process-obj
(let ([out (ocaml:process-out process-obj)]
[next (read-char port)])
(when (not (eq? next eof))
(write-char next out-pipe)
(ocaml:display-debugger-output tab (list next)))
(when (eq? next #\newline)
(handle out (read-line in-pipe)))
(read-chars-loop))))
(define (handle out response-text)
(cond
[(regexp-match "Breakpoint : ([0-9]+)" response-text)]
[(regexp-match "Time : ([0-9]+) - pc : [0-9]+ - module [A-Z][a-z]*" response-text)
=>
(λ (x) (send defs ocaml:set-debug-time (string->number (second x))))]
[(regexp-match "Time : ([0-9]+)" response-text)
=>
(λ (x) (send defs ocaml:set-debug-time (string->number (second x))))]
[(equal? response-text "Beginning of program.")
(send defs ocaml:set-debug-status "At beginning of program")
(send defs scroll-to-position 0)]
[(equal? response-text "Program exit.")
(send defs ocaml:set-debug-status "At end of program")
(send defs scroll-to-position (send defs last-position))]
[(equal? "H" response-text)]
[(and
(>= (string-length response-text) 3)
(equal? (substring response-text 0 3) "M"))
(local ((define parts
(regexp-split
":"
(substring
response-text
(if (eq? (system-type 'os) 'windows)
3
5))))
(define drive-prefix
(and
(eq? (system-type 'os) 'windows)
(substring response-text 3 5)))
(define file-path
(normal-case-path
(string->path
(if (eq? (system-type 'os) 'windows)
(string-append drive-prefix (car parts)) (car parts))))) (define start (string->number (second parts)))
(define lame-ocaml-version (< (length parts) 4))
(define end
(if lame-ocaml-version
start
(string->number (third parts))))
(define event-type
(if lame-ocaml-version
(third parts)
(fourth parts)))
(define-values (debug-path debug-filename debug-dir?)
(split-path file-path))
(define-values (defs-path defs-filename defs-dir?)
(split-path (send defs get-filename))))
(if (or (equal? debug-filename defs-filename)
(equal? debug-filename (path-replace-suffix defs-filename ".crlf.ml")))
(begin
(send defs scroll-to-position start #f end)
(send defs ocaml:set-debug-highlighting start end)
(send defs ocaml:set-debug-status (format "Type ~a" event-type)))
(let ([direction (send defs ocaml:get-direction)])
(cond
[(eq? direction 'forward)
(write-string "step" out)
(newline out)
(flush-output out)]
[(eq? direction 'backward)
(write-string "backstep" out)
(newline out)
(flush-output out)]
[else
(write-string "reverse" out)
(newline out)
(flush-output out)]))))]))
(read-chars-loop)))
(define (break-callback tab)
(define defs (send tab get-defs))
(define process-obj (send tab ocaml:get-debug-process))
(define proc (ocaml:process-proc process-obj))
(define in (ocaml:process-in process-obj))
(define out (ocaml:process-out process-obj))
(define err (ocaml:process-err process-obj))
(when (subprocess? proc)
(send defs ocaml:debug:pause-handlers)
(subprocess-kill proc #f)
(ocaml:clear-all-text in)
(ocaml:clear-all-text err)
(send defs ocaml:debug:resume-handlers)))
(define (inspect-value tab name)
(define frame (send tab get-frame))
(define defs (send tab get-defs))
(define ints (send tab get-ints))
(define process-obj (send tab ocaml:get-debug-process))
(define proc (ocaml:process-proc process-obj))
(define in (ocaml:process-in process-obj))
(define out (ocaml:process-out process-obj))
(define err (ocaml:process-err process-obj))
(define (loop message)
(define-values (more? response)
(if (ocaml:sync-char-ready? err)
(ocaml:sync-read-line-avail err)
(ocaml:sync-read-line-avail in)))
(if (or (equal? response "Interrupted.")
(equal? response "")
(equal? response "(ocd) "))
(begin
(ocaml:clear-all-text in)
(ocaml:clear-all-text err)
message)
(loop (string-append message response))))
(when (subprocess? proc)
(send defs ocaml:debug:pause-handlers)
(write-string (format "print ~a~n" name) out)
(flush-output out)
(send defs ocaml:set-debug-status (format "~a" (loop "")))
(send defs ocaml:debug:resume-handlers)))
(define (remove-all-breakpoints tab)
(define frame (send tab get-frame))
(define defs (send tab get-defs))
(define ints (send tab get-ints))
(define process-obj (send tab ocaml:get-debug-process))
(define proc (ocaml:process-proc process-obj))
(define in (ocaml:process-in process-obj))
(define out (ocaml:process-out process-obj))
(define err (ocaml:process-err process-obj))
(define-values (response? response-text)
(begin
(write-string "delete\n" out)
(flush-output)
(ocaml:sync-read-line-avail in)))
(when (equal? response-text "Delete all breakpoints ? (y or n) ")
(write-string "y\n" out)
(flush-output)))
(define (update-breakpoints tab breakpoints)
(define frame (send tab get-frame))
(define defs (send tab get-defs))
(define ints (send tab get-ints))
(define process-obj (send tab ocaml:get-debug-process))
(define proc (ocaml:process-proc process-obj))
(define in (ocaml:process-in process-obj))
(define out (ocaml:process-out process-obj))
(define err (ocaml:process-err process-obj))
(define module-name
(let-values
([(_ filename __) (split-path (send defs get-filename))])
(path-replace-suffix filename "")))
(define (update-one pos value)
(when value
(write-string (format "break @ ~a # ~a~n" module-name pos) out)
(flush-output out)))
(remove-all-breakpoints tab)
(hash-for-each breakpoints update-one))