larsbrinkhoff / forth-mode

Wants to be the SLIME of Forth
GNU General Public License v3.0
61 stars 17 forks source link

Supporting a microcontroller Forth running in serial-term #103

Open dcnorris opened 3 months ago

dcnorris commented 3 months ago

To interact with FlashForth running in a serial-term, I've modified forth-interaction-send-raw-result as below. Basic things work nicely, such as sourcing regions via C-c C-r, for which I had formerly depended onisend-mode. As I smooth out the rough edges in this, I will aim to work toward submitting a PR.

It seems to me that supporting interaction with a term would be entirely in-scope for a SLIME-for-Forth, but may present some [useful!] challenges to assumptions that may have been made in forth-mode that a resident Forth was running. So I wanted to start a conversation on that point.

Has anyone else pursued this kind of effort to use forth-mode with a microcontroller Forth?

(defun forth-interaction-send-raw-result (&rest strings)
  (let* ((proc (forth-ensure))
     (forth-result nil)
     (forth-interaction-callback (lambda (x)
                       (setq forth-result (concat forth-result x))
                       ""))
     (end-time (+ (float-time) .4)))
    (if (buffer-match-p '(derived-mode . term-mode) forth-interaction-buffer)
        (with-current-buffer forth-interaction-buffer
          (progn
            (message "Invoking special case for term-mode FIB")
            (dolist (s strings) (insert s))
            (term-send-input) ; ends w/ (funcall term-input-sender proc input)
            ;; While I would have liked to pretend that 'term-send-input'
            ;; behaves basically like 'comint-send-string', the two types
            ;; of buffer do really act differently.  (Note that 'term-mode'
            ;; was "based on" 'comint-mode', not formally *derived from* it!)
            ;; Facing the fact that 'term' is not 'comint', and especially
            ;; that it offers no preoutput filter hook, I here explicitly
            ;; extract the output substring:
            (setq mark1 (marker-position term-last-input-end))
            (accept-process-output proc 0.1)
            (setq mark2 (marker-position (process-mark proc)))
            (funcall forth-interaction-callback
                     (buffer-substring-no-properties mark1 mark2))
            ;; (But to preserve as much of the original design as possible,
            ;; I continue to invoke the 'forth-interaction-callback' here
            ;; in order to yield the 'forth-result'.)
            ))
      ;; the following 'else' code is the original default
      (dolist (s strings)
        (comint-send-string proc s))
      (comint-send-string proc "\n")
      (while (< (float-time) end-time)
        (accept-process-output proc 0.1); Accepting process output triggers
                                        ; 'forth-interaction-preoutput-filter',
                                        ; and thence 'forth-interaction-callback'.
        ))
    (setq forth-words-cache nil)
    forth-result))

I've also got a mode hook set up like this, establishing in particular the forth-interaction-buffer:

(defun ff-tty ()
  "Find the TTY where FF50 is."
  (interactive)
  (let ((tty (car (mapcar 'file-truename
                          (file-expand-wildcards "/dev/serial/by-id/*FF50*")))))
    (message "%s" tty)
    tty))

(defun ff-shell ()
  "Start serial-term with right params for FlashForth interaction."
  (interactive)
  (serial-term (ff-tty) ; port
               38400    ; baud
               t))      ; use term-line-mode

(add-hook 'forth-mode-hook
          (lambda ()
            (setq fill-column 80)
            (display-fill-column-indicator-mode)
            (dolist (w '("for" "next" "[i" "i]" ";i"))
              (forth-syntax--define w #'forth-syntax--state-font-lock-keyword))
            (isend-mode)
            (set (make-local-variable 'isend-skip-comments) t)
            (when (ff-tty)
              (setq forth-implementation "FlashForth")
              (unless (get-buffer (ff-tty))
                      (ff-shell))
              (setq forth-interaction-buffer (get-buffer (ff-tty)))
              (isend-associate (ff-tty))
              (with-current-buffer forth-interaction-buffer
                (setq inhibit-read-only t)
             )
            )))
larsbrinkhoff commented 3 months ago

Thanks for the heads up. I don't think anyone has raised this issue before.

dcnorris commented 3 months ago

If you have Conklin & Rather's Forth Programmer's Handbook 3ed., §7.5 'Interactive Programming' is extremely interesting vis-à-vis your SLIME concept. (Ch. 7 is about Forth Cross compilers, where a microcontroller target is programmed by a PC host.) Here's an excerpt:

The head of compiled definitions are retained in the host, along with an image of the target's CDATA and IDATA. Thus, the host can "know" at all times what the target's code and initialized data space contains.

This white paper is publicly available, in case you don't have Conklin & Rather.

It has occurred to me that your SLIME concept might best come into its own in supporting precisely this kind of interaction with a microcontroller Forth. I am working on the PIC24 platform, for which FF implements only a limited assembler and SEE. The Forth, Inc. perspective of course imagines the host to be a Forth system as well (what other programming languages exist?) but there's no reason it can't be Emacs.