branch: externals/poke
commit cdf89905d2a2d700903bf74fd8e6f1a29e8fc8c5
Author: Jose E. Marchesi <[email protected]>
Commit: Jose E. Marchesi <[email protected]>

    emacs: new poke.el Emacs interface based on poked
---
 poke.el | 581 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 581 insertions(+)

diff --git a/poke.el b/poke.el
new file mode 100644
index 0000000000..6b3e5bab43
--- /dev/null
+++ b/poke.el
@@ -0,0 +1,581 @@
+;;; poke.el --- Emacs interface to GNU poke
+
+;; Copyright (C) 2022  Jose E. Marchesi
+;; Author: Jose E. Marchesi <[email protected]>
+;; Maintainer: Jose E. Marchesi <[email protected]>
+;; URL: https://www.jemarch.net/poke
+;; Package-Requires: ((emacs "25"))
+;; Version: 1.1
+
+;; This file is NOT part of GNU Emacs.
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 3, or (at your option)
+;; any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program; see the file COPYING.  If not, write to the
+;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+;; Boston, MA 02110-1301, USA.
+
+;;; Commentary:
+
+;; This file implements a Emacs interface to GNU poke, the extensible
+;; editor for structured binary data.
+;;
+;; It uses the poked (GNU poke daemon) in order to act as a set of
+;; pokelets:
+;;
+;; poke-out
+;;      connected to the poked output channel 1.
+;; poke-cmd
+;;      connected to the poked input channel 2.
+;; poke-code
+;;      connected to the poked input channel 3.
+;; poke-vu
+;;      connected to the poked output channel 2.
+
+;;; Code:
+
+(require 'comint)
+
+(defvar poke-debug-p nil)
+
+;;;; First, some utilities
+
+(defun poke-decode-u64-le (seq)
+  (logior (ash (aref seq 7) 56)
+          (ash (aref seq 6) 48)
+          (ash (aref seq 5) 40)
+          (ash (aref seq 4) 32)
+          (ash (aref seq 3) 24)
+          (ash (aref seq 2) 16)
+          (ash (aref seq 1) 8)
+          (aref seq 0)))
+
+;;;; Faces
+
+(defface poke-integer-face '((t :foreground "green"))
+  "Face for printing Poke integer values.")
+(defface poke-string-face '((t :inherit font-lock-string-face))
+  "Face for printing Poke string values.")
+(defface poke-offset-face '((t :foreground "yellow"))
+  "Face for printing Poke offsets.")
+(defface poke-struct-field-name-face '((t :underline t))
+  "Face for printing Poke struct field names.")
+(defface poke-vu-addr-face '((t :bold t))
+  "Face for printing line addresses in VU mode.")
+(defface poke-vu-ascii-face '((t :foreground "red"))
+  "Face for printing ascii in VU mode.")
+(defface poke-diff-field-name-face '((t :underline t))
+  "Face for printing thunk field names.")
+(defface poke-diff-thunk-header-face '((t :bold t))
+  "Face for thunk headers.")
+(defface poke-diff-minus-face '((t :foreground "red"))
+  "Face for deletion thunk lines.")
+(defface poke-diff-plus-face '((t :foreground "green"))
+  "Face for addition thunk lines.")
+
+(defvar poke-styling-faces
+  '(("integer" poke-integer-face)
+    ("string" poke-string-face)
+    ("offset" poke-offset-face)
+    ("struct-field-name" poke-struct-field-name-face)
+    ("diff-thunk-header"  poke-diff-thunk-header-face)
+    ("diff-minus" poke-diff-minus)
+    ("diff-plus" poke-diff-plus)))
+
+;;;; poked
+
+(defvar poke-poked-program "poked")
+(defvar poke-poked-process nil)
+
+(defun poke-poked ()
+  "Start a poke daemon process"
+  (interactive)
+  (when (not (process-live-p poke-poked-process))
+    (setq poke-poked-process
+          (make-process :name "poked"
+                        :buffer "*poked*"
+                        :command (list poke-poked-program)))
+    (set-process-query-on-exit-flag poke-poked-process nil)))
+
+;;;; generic pokelet stuff
+
+(defvar poked-socket "/tmp/poked.ipc")
+
+(defun poke-make-pokelet-process (name ctrl)
+  (let ((proc (make-network-process :name name
+                                    :buffer (concat "*" name "*")
+                                    :family 'local
+                                    :service poked-socket)))
+    (process-send-string proc ctrl)
+    proc))
+
+;;;; poke-out pokelet
+
+(defvar poke-out-process nil)
+(defvar poke-out-buf "")
+(defvar poke-out-length 0)
+(defvar poke-out-eval "")
+(defvar poke-out-styles nil)
+
+(defconst poke-out-state-waiting-for-length 0)
+(defconst poke-out-state-waiting-for-msg 1)
+(defvar poke-out-state poke-out-state-waiting-for-length)
+
+(defun poke-out-stylize (string)
+  (let ((propertized-string string))
+    (mapcar (lambda (style)
+              (let* ((face-ass (assoc style poke-styling-faces))
+                     (face (when face-ass (cadr face-ass))))
+                (setq propertized-string
+                      (if face
+                          (propertize propertized-string 'font-lock-face face)
+                        propertized-string))))
+            (reverse poke-out-styles))
+    propertized-string))
+
+(defun poke-out-filter (proc string)
+  (setq poke-out-buf (concat poke-out-buf string))
+  (while (or (and (= poke-out-state poke-out-state-waiting-for-length)
+                  (>= (length poke-out-buf) 2))
+             (and (= poke-out-state poke-out-state-waiting-for-msg)
+                  (>= (length poke-out-buf) poke-out-length)))
+    (if (= poke-out-state poke-out-state-waiting-for-length)
+        (progn
+          (setq poke-out-length
+                (logior (ash (aref poke-out-buf 1) 8) (aref poke-out-buf 0)))
+          (setq poke-out-buf (substring poke-out-buf 2))
+          (setq poke-out-state poke-out-state-waiting-for-msg))
+      ;; state is poke-out-state-waiting-for-msg.
+      (when (>= (length poke-out-buf) poke-out-length)
+       ;; Action on the message according to the command.
+       (pcase (aref poke-out-buf 0)
+         (1 ;; Iteration begin
+           (setq poke-out-eval "")
+           (when poke-debug-p
+             (let ((iteration-number (poke-decode-u64-le
+                                      (substring poke-out-buf 1 9))))
+               (when (buffer-live-p (process-buffer proc))
+                 (with-current-buffer (process-buffer proc)
+                   (goto-char (point-max))
+                   (insert (concat "\n//---- iteration begin "
+                                  (number-to-string iteration-number)
+                                   "\n")))))))
+         (2 ;; Process terminal poke output
+           (let ((output (poke-out-stylize
+                          (substring poke-out-buf 1 (- poke-out-length 1)))))
+             (when (buffer-live-p (process-buffer proc))
+               (with-current-buffer (process-buffer proc)
+                 (let ((buffer-read-only nil))
+                   (goto-char (point-max))
+                   (insert output)
+                   (set-marker (process-mark proc) (point)))))))
+          (6 ;; Process eval poke output
+           (let ((output (poke-out-stylize
+                          (substring poke-out-buf 1 (- poke-out-length 1)))))
+             ;; Append the output to the global variable which will be
+             ;; handled at the end of the iteration.
+             (setq poke-out-eval
+                   (concat poke-out-eval output))
+             ;; If there is no repl, output this in the *poke-out*
+             ;; buffer prefixed with >
+             (when (not (process-live-p poke-repl-process))
+               (when (buffer-live-p (process-buffer proc))
+                 (with-current-buffer (process-buffer proc)
+                   (let ((buffer-read-only nil))
+                     (goto-char (point-max))
+                     (insert (concat ">" output))))))))
+          (7 ;; Error output
+           (let ((output (poke-out-stylize
+                          (substring poke-out-buf 1 (- poke-out-length 1)))))
+             ;; Append to the eval output for now.
+             (setq poke-out-eval
+                   (concat poke-out-eval output))
+             ;; If there is no repl, output this in the *poke-out*
+             ;; buffer prefixed with error>
+             (when (not (process-live-p poke-repl-process))
+               (when (buffer-live-p (process-buffer proc))
+                 (with-current-buffer (process-buffer proc)
+                   (let ((buffer-read-only nil))
+                     (goto-char (point-max))
+                     (insert (concat "error>" output))))))))
+          (3 ;; Iteration end
+           (let ((iteration-number (poke-decode-u64-le
+                                    (substring poke-out-buf 1 9))))
+             (when poke-debug-p
+               (when (buffer-live-p (process-buffer proc))
+                 (with-current-buffer (process-buffer proc)
+                   (goto-char (point-max))
+                   (insert (concat "\n//---- iteration end "
+                                  (number-to-string iteration-number)
+                                   "\n")))))
+             (when (process-live-p poke-repl-process)
+               (poke-repl-end-of-iteration))))
+          (4 ;; Styling class begin
+           (let ((style (substring poke-out-buf 1 (- poke-out-length 1))))
+             (setq poke-out-styles (cons style poke-out-styles))))
+          (5 ;; Styling class end
+           (let ((style (substring poke-out-buf 1 (- poke-out-length 1))))
+             (if (or (not poke-out-styles)
+                     (not (equal (car poke-out-styles) style)))
+                 (error "closing a mismatched style")
+               (setq poke-out-styles (cdr poke-out-styles)))))
+         (_ ;; Protocol error
+          (setq poke-out-buf "")
+          (setq poke-out-length 0)
+          (error "pokelet protocol error"))))
+      ;; Discard used portion of poke-out-buf and reset state.
+      (setq poke-out-buf (substring poke-out-buf poke-out-length))
+      (setq poke-out-state poke-out-state-waiting-for-length))))
+
+(defvar poke-out-font-lock nil
+  "Font lock entries for `poke-vu-mode'.")
+
+(defun poke-out-mode ()
+  "A major mode for Poke out buffers.
+
+Commands:
+\\{poke-out-mode-map}"
+  (interactive)
+  (kill-all-local-variables)
+  (setq poke-out-mode-map (make-keymap))
+  (use-local-map  poke-out-mode-map)
+  (setq-local font-lock-defaults '(poke-out-font-lock))
+  (setq mode-name "poke-out")
+  (setq major-mode 'poke-out-mode)
+  (read-only-mode t))
+
+(defun poke-out ()
+  (interactive)
+  (when (not (process-live-p poke-out-process))
+    (setq poke-out-state poke-out-state-waiting-for-length)
+    (setq poke-out-buf "")
+    (setq poke-out-length 0)
+    (setq poke-out-styles nil)
+    (setq poke-out-process
+          (poke-make-pokelet-process "poke-out" "\x81"))
+    (set-process-query-on-exit-flag poke-out-process nil)
+    (set-process-filter poke-out-process #'poke-out-filter)
+    (save-excursion
+      (set-buffer "*poke-out*")
+      (poke-out-mode)))
+  (when (called-interactively-p)
+    (switch-to-buffer-other-window "*poke-out*")))
+
+;;;; poke-cmd pokelet
+
+(defvar poke-cmd-process nil)
+
+(defun poke-cmd-send (string)
+  ;; Send the lenght of string in a 16-bit little-endian unsigned
+  ;; integer, followed by string, to poke-cmd-process.
+  (if (process-live-p poke-cmd-process)
+      (progn
+        (let* ((string-length (length string)))
+          (process-send-string poke-cmd-process
+                               (unibyte-string (logand string-length #xff)
+                                               (logand (ash string-length -8) 
#xff)))
+          (process-send-string poke-cmd-process string)))
+    (error "poke-cmd is not running")))
+
+(defun poke-cmd ()
+  (interactive)
+  (when (not (process-live-p poke-cmd-process))
+    (setq poke-cmd-process (poke-make-pokelet-process
+                            "poke-cmd" "\x02"))
+    (set-process-query-on-exit-flag poke-cmd-process nil))
+  (when (called-interactively-p)
+    (switch-to-buffer-other-window "*poke-cmd*")))
+
+;;;; poke-code pokelet
+
+(defvar poke-code-process nil)
+
+(defun poke-code-send (string)
+  ;; Send the lenght of string in a 16-bit little-endian unsigned
+  ;; integer, followed by string, to poke-code-process.
+  (if (process-live-p poke-code-process)
+      (progn
+        (let* ((string-length (length string)))
+          (process-send-string poke-code-process
+                               (unibyte-string (logand string-length #xff)
+                                               (logand (ash string-length -8) 
#xff)))
+          (process-send-string poke-code-process string)))
+    (error "poke-code is not running")))
+
+(defun poke-code ()
+  (interactive)
+  (when (not (process-live-p poke-code-process))
+    (setq poke-code-process (poke-make-pokelet-process
+                            "poke-code" "\x01"))
+    (set-process-query-on-exit-flag poke-code-process nil))
+  (when (called-interactively-p)
+    (switch-to-buffer-other-window "*poke-code*")))
+
+;;;; poke-vu pokelet
+
+(defvar poke-vu-process nil)
+(defvar poke-vu-buf "")
+(defvar poke-vu-length 0)
+
+(defconst poke-vu-state-waiting-for-length 0)
+(defconst poke-vu-state-waiting-for-msg 1)
+(defvar poke-vu-state poke-vu-state-waiting-for-length)
+
+(defun poke-vu-filter (proc string)
+  (setq poke-vu-buf (concat poke-vu-buf string))
+  (while (or (and (= poke-vu-state poke-vu-state-waiting-for-length)
+                  (>= (length poke-vu-buf) 2))
+             (and (= poke-vu-state poke-vu-state-waiting-for-msg)
+                  (>= (length poke-vu-buf) poke-vu-length)))
+    (if (= poke-vu-state poke-vu-state-waiting-for-length)
+        (progn
+          (setq poke-vu-length
+                (logior (ash (aref poke-vu-buf 1) 8) (aref poke-vu-buf 0)))
+          (setq poke-vu-buf (substring poke-vu-buf 2))
+          (setq poke-vu-state poke-vu-state-waiting-for-msg))
+      ;; state is poke-vu-state-waiting-for-msg.
+      (when (>= (length poke-vu-buf) poke-vu-length)
+       ;; Action on the message according to the command.
+       (pcase (aref poke-vu-buf 0)
+         (1 ;; RESET
+           (when (buffer-live-p (process-buffer proc))
+             (with-current-buffer (process-buffer proc)
+               (let ((buffer-read-only nil))
+                 (delete-region (point-min) (point-max))))))
+          (2 ;; APPEND
+           (when (buffer-live-p (process-buffer proc))
+             (with-current-buffer (process-buffer proc)
+               (let ((buffer-read-only nil))
+                 (insert (substring poke-vu-buf 1 (- poke-vu-length 1)))))))
+          (3 ;; HIGHLIGHT
+           ;; XXX
+           )
+          (4 ;; FILTER
+           ;; XXX
+           )
+          (5 ;; FINISH
+           (when (buffer-live-p (process-buffer proc))
+             (with-current-buffer (process-buffer proc)
+               (goto-char (point-min)))))
+         (_ ;; Protocol error
+          (setq poke-vu-buf "")
+          (setq poke-vu-length 0)
+          (error "pokelet protocol error")))
+       ;; Discard used portion of poke-vu-buf and reset state.
+        (setq poke-vu-buf (substring poke-vu-buf poke-vu-length))
+        (setq poke-vu-state poke-vu-state-waiting-for-length)))))
+
+(defvar poke-vu-font-lock
+  `(("^[0-9a-zA-Z]+:" . 'poke-vu-addr-face)
+    ("  .*$" . 'poke-vu-ascii-face)
+    )
+  "Font lock entries for `poke-vu-mode'.")
+
+(defun poke-vu-prev-line ()
+  (interactive)
+  (if (equal (line-number-at-pos) 1)
+      (progn
+        (setq-local start-byte-offset (- start-byte-offset #x10))
+        (poke-vu-refresh))
+    (previous-line)))
+
+(defun poke-vu-next-line ()
+  (interactive)
+  (next-line))
+
+(defun poke-vu-page-down ()
+  (interactive)
+  (save-excursion
+    (let ((window (get-buffer-window (current-buffer))))
+      (setq-local start-byte-offset
+                  (+ start-byte-offset (* (- (window-height) 1) #x10)))
+      (poke-vu-refresh))))
+
+(defun poke-vu-page-up ()
+  (interactive)
+  (save-excursion
+    (let ((window (get-buffer-window (current-buffer))))
+      (setq-local start-byte-offset
+                  (- start-byte-offset (* (- (window-height) 1) #x10)))
+      (poke-vu-refresh))))
+
+(defun poke-vu-goto-byte (offset)
+  (interactive "nGoto byte: ")
+  (save-excursion
+    (set-buffer "*poke-vu*")
+    (setq-local start-byte-offset offset)
+    (poke-vu-refresh)))
+
+(defun poke-vu-mode ()
+  "A major mode for Poke vu output.
+
+Commands:
+\\{poke-vu-mode-map}"
+  (interactive)
+  (kill-all-local-variables)
+  (setq poke-vu-mode-map (make-keymap))
+  (use-local-map  poke-vu-mode-map)
+  (define-key poke-vu-mode-map "\C-n" 'poke-vu-next-line)
+  (define-key poke-vu-mode-map "\C-p" 'poke-vu-prev-line)
+  (define-key poke-vu-mode-map "\C-v" 'poke-vu-page-down)
+  (define-key poke-vu-mode-map "\M-v" 'poke-vu-page-up)
+  (define-key poke-vu-mode-map "\C-cg" 'poke-vu-goto-byte)
+  (setq-local font-lock-defaults '(poke-vu-font-lock))
+  (setq-local start-byte-offset 0)
+  (setq mode-name "poke-vu")
+  (setq major-mode 'poke-vu-mode)
+  (read-only-mode t))
+
+(defun poke-vu ()
+  (interactive)
+  (when (not (process-live-p poke-vu-process))
+    (setq poke-vu-state poke-vu-state-waiting-for-length)
+    (setq poke-vu-buf "")
+    (setq poke-vu-length 0)
+    (setq poke-vu-process
+          (poke-make-pokelet-process "poke-vu" "\x82"))
+    (set-process-query-on-exit-flag poke-vu-process nil)
+    (set-process-filter poke-vu-process #'poke-vu-filter)
+    (save-excursion
+     (set-buffer "*poke-vu*")
+     (poke-vu-mode)))
+  (when (called-interactively-p)
+    (switch-to-buffer-other-window "*poke-vu*")))
+
+(defun poke-vu-refresh ()
+  (let* ((buffer (get-buffer "*poke-vu*"))
+         (window (get-buffer-window buffer)))
+    (when (and (process-live-p poke-vu-process)
+               window)
+      ;; Note we are assuming each VU line contains 0x10 bytes.
+      (poke-code-send (concat "{vu "
+                              ":from " (number-to-string
+                                        (buffer-local-value 'start-byte-offset 
buffer))
+                              "#B "
+                              ":size " (number-to-string (* (- (window-height 
window) 1)
+                                                            #x10)) "#B"
+                              ";} ?! E_no_ios;")))))
+
+(add-hook 'window-size-change-functions
+          (lambda (window)
+            (let (buffer (window-buffer window))
+              (when (equal (buffer-name buffer) "*poke-vu*")
+                (poke-vu-refresh)))))
+
+;;;; poke-repl
+
+(defconst poke-repl-prompt "#!poke!# ")
+(defvar poke-repl-process nil)
+(defvar poke-repl-seq 0)
+
+(define-derived-mode poke-repl-mode comint-mode "poke"
+  "poke-repl mode."
+  (setq comint-prompt-regexp (concat "^" (regexp-quote poke-repl-prompt)))
+  (setq comint-input-sender 'poke-repl-input-sender)
+  (setq poke-repl-process
+        (condition-case nil
+            (start-process "poke-repl-process" (current-buffer) "hexl")
+          (file-error (start-process "poke-repl-process" (current-buffer) 
"cat"))))
+  (set-process-query-on-exit-flag poke-repl-process nil)
+  (set-marker
+   (process-mark poke-repl-process) (point))
+  (comint-output-filter poke-repl-process poke-repl-prompt))
+
+(defun poke-repl-end-of-iteration ()
+  (with-current-buffer "*poke-repl*"
+    (let ((buffer-read-only nil))
+      (save-excursion
+        (re-search-backward
+         (regexp-quote (concat "#" (number-to-string poke-repl-seq)))
+         nil t)
+        (delete-region (point) (line-end-position))
+        (if (> (length poke-out-eval) 0)
+            (insert poke-out-eval)
+          (unless (equal (point) (point-max))
+            (delete-char 1))))
+      (setq poke-repl-seq (1+ poke-repl-seq)))))
+
+(defun poke-repl-input-sender (proc input)
+  (unless (string-blank-p input)
+    (let ((id (number-to-string poke-repl-seq))
+          (buffer-read-only nil)
+          (lb (- (line-beginning-position) 5)))
+      (comint-output-filter poke-repl-process (format "#%s\n" id))
+      (if (string-match "^[ \t]*\\(var\\|type\\|unit\\|fun\\) " input)
+          (poke-code-send (concat input ";"))
+        (poke-cmd-send (concat input ";")))))
+  (poke-vu-refresh)
+  (comint-output-filter poke-repl-process poke-repl-prompt))
+
+(defun poke-repl ()
+  (interactive)
+  (poke-out)
+  (poke-cmd)
+  (poke-code)
+  (setq poke-repl-seq 0)
+  (let ((buf (get-buffer-create "*poke-repl*")))
+    (with-current-buffer  buf
+      (insert "Welcome to GNU poke.\n")
+      (poke-repl-mode))))
+
+;;;; Main interface
+
+(defun poke-open-file (filename)
+  (interactive "fFile to open: ")
+  ;; XXX: quote filename if needed
+  (poke-code-send
+   (concat "{ set_ios (open (\"" filename "\")); } ?! E_generic_io")))
+
+(defun poke-load-file (filename)
+  (interactive "fPickle to load: ")
+  (poke-code-send (concat "load \"" filename "\";")))
+
+(defun poke ()
+  (interactive)
+  (when (not (process-live-p poke-poked-process))
+    (poke-poked)
+    (sit-for 0.2))
+  (when (not (process-live-p poke-repl-process))
+    (poke-repl))
+  (switch-to-buffer "*poke-out*")
+  (switch-to-buffer-other-window "*poke-repl*"))
+
+(defun poke-exit ()
+  (interactive)
+  ;; Note that killing the buffers will also kill the
+  ;; associated processes if they are running.
+  (let ((out-buffer (get-buffer "*poke-out*"))
+        (cmd-buffer (get-buffer "*poke-cmd*"))
+        (code-buffer (get-buffer "*poke-code*"))
+        (vu-buffer (get-buffer "*poke-vu*"))
+        (repl-buffer (get-buffer "*poke-repl*"))
+        (poked-buffer (get-buffer "*poked*")))
+    (when out-buffer
+      (kill-buffer "*poke-out*")
+      (setq poke-out-process nil))
+    (when cmd-buffer
+      (kill-buffer "*poke-cmd*")
+      (setq poke-cmd-process nil))
+    (when code-buffer
+      (kill-buffer "*poke-code*")
+      (setq poke-code-process nil))
+    (when vu-buffer
+      (kill-buffer "*poke-vu*")
+      (setq poke-vu-process nil))
+    (when repl-buffer
+      (kill-buffer "*poke-repl*")
+      (setq poke-repl-process nil))
+    (when poked-buffer
+      (kill-buffer "*poked*")
+      (setq poke-poked-process nil))))
+
+;;; poke.el ends here

Reply via email to