-
Notifications
You must be signed in to change notification settings - Fork 19
/
Copy pathgnu-apl-interactive.el
359 lines (318 loc) · 16.4 KB
/
gnu-apl-interactive.el
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
;;; gnu-apl-interactive.el --- Interaction support for GNU APL -*- lexical-binding: t -*-
;; Copyright (C) 2013-2015 Elias Mårtenson
;;; Code:
(require 'cl-lib)
(require 'gnu-apl-util)
(require 'gnu-apl-network)
(require 'comint)
(require 'xref)
(declare-function gnu-apl--load-help "gnu-apl-mode" (&optional string))
(declare-function gnu-apl--choose-variable "gnu-apl-editor"
(prompt &optional type default-value))
(declare-function gnu-apl--get-function "gnu-apl-editor"
(function-definition))
(declare-function gnu-apl--name-at-point "gnu-apl-show-keyboard" ())
(declare-function gnu-apl--init-mode-common "gnu-apl-mode" ())
(declare-function gnu-apl--parse-function-header "gnu-apl-mode" (string))
(defvar gnu-apl--symbol-doc) ;gnu-apl-refdocs-bsd-license.el
(defvar gnu-apl--symbols) ;gnu-apl-symbols.el
(defvar gnu-apl-auto-function-editor-popup) ;gnu-apl-mode.el
(defvar gnu-apl-use-new-native-library) ;gnu-apl-mode.el
(defvar gnu-apl-mode-syntax-table) ;gnu-apl-mode.el
(defvar gnu-apl-input-display-type nil
"Current input display type.")
(defvar gnu-apl-preoutput-filter-state nil
"Current state of the (pre)output filter.")
(defvar gnu-apl--connection nil
"Process object for the current connection.")
(defvar gnu-apl-current-session nil
"The buffer that holds the currently active GNU APL session.
The value is nil if there is no active session.")
(defvar gnu-apl-libemacs-location "libemacs"
"The location of the native code library from the interpreter.
This shouldn't normally need to be changed except when doing
development of the native code.")
(defun gnu-apl--make-key-command-sym (n)
(intern (concat "insert-sym-apl-" n)))
(defun gnu-apl--make-base-mode-map (prefix)
(let ((map (make-sparse-keymap)))
(dolist (command gnu-apl--symbols)
(let ((key-sequence (caddr command)))
(dolist (s (if (listp key-sequence) key-sequence (list key-sequence)))
(define-key map (gnu-apl--kbd (concat prefix s)) (gnu-apl--make-key-command-sym (car command))))))
(define-key map (kbd (concat prefix "SPC")) 'gnu-apl-insert-spc)
(define-key map (kbd "C-c C-k") 'gnu-apl-show-keyboard)
(define-key map (kbd "C-c C-h") 'gnu-apl-show-help-for-symbol)
(define-key map (kbd "C-c C-a") 'gnu-apl-apropos-symbol)
(define-key map (kbd "C-M-a") 'gnu-apl-beginning-of-defun)
(define-key map (kbd "C-M-e") 'gnu-apl-end-of-defun)
(define-key map (kbd "M-.") 'gnu-apl-find-function-at-point)
(define-key map (kbd "C-c C-.") 'gnu-apl-trace)
(define-key map (kbd "C-c C-i") 'gnu-apl-finnapl-list)
(define-key map [menu-bar gnu-apl] (cons "APL" (make-sparse-keymap "APL")))
(define-key map [menu-bar gnu-apl toggle-keyboard] '("Toggle keyboard" . gnu-apl-show-keyboard))
(define-key map [menu-bar gnu-apl show-help-for-symbol] '("Documentation for symbol" . gnu-apl-show-help-for-symbol))
(define-key map [menu-bar gnu-apl apropos-symbol] '("Search symbols" . gnu-apl-apropos-symbol))
(define-key map [menu-bar gnu-apl find-symbol-at-point] '("Find symbol at point" . gnu-apl-find-function-at-point))
(define-key map [menu-bar gnu-apl trace] '("Trace variable" . gnu-apl-trace))
(define-key map [menu-bar gnu-apl finnapl-list] '("FinnAPL idioms list" . gnu-apl-finnapl-list))
map))
(defun gnu-apl-interactive-send-string (string &optional file line)
"Send STRING to the current active interpreter.
If given, FILE and LINE indicates the file and location where the
code was read from."
(let ((content (split-string string "\n")))
(gnu-apl--send-network-command (concat "sendcontent"
(if (and file line)
(format ":%s:%d" file line)
"")))
(gnu-apl--send-block content)
(let ((reply (gnu-apl--read-network-reply-block)))
(if (string= (car reply) "content sent")
(progn
(gnu-apl--send (gnu-apl--get-interactive-session) "⊣⍬\n")
(message "Content sent to APL interpreter"))
(error "Error sending content to APL interpreter")))))
(defun gnu-apl--get-interactive-session-with-nocheck ()
(when gnu-apl-current-session
(let ((proc-status (comint-check-proc gnu-apl-current-session)))
(when (eq (car proc-status) 'run)
gnu-apl-current-session))))
(defun gnu-apl--get-interactive-session ()
(let ((session (gnu-apl--get-interactive-session-with-nocheck)))
(unless session
(user-error "No active GNU APL session"))
session))
(defvar *gnu-apl-native-lib* "EMACS_NATIVE")
(defvar *gnu-apl-ignore-start* "IGNORE-START")
(defvar *gnu-apl-ignore-end* "IGNORE-END")
(defvar *gnu-apl-network-start* "NATIVE-STARTUP-START")
(defvar *gnu-apl-network-end* "NATIVE-STARTUP-END")
(defun gnu-apl--send (proc string)
"Filter for any commands that are sent to comint."
(let* ((trimmed (gnu-apl--trim-spaces string)))
(cond ((and gnu-apl-auto-function-editor-popup
(cl-plusp (length trimmed))
(string= (cl-subseq trimmed 0 1) "∇"))
;; The command is a function definition command
(unless (gnu-apl--parse-function-header (cl-subseq trimmed 1))
(user-error "Error when parsing function definition command"))
(unwind-protect
(gnu-apl--get-function (gnu-apl--trim-spaces (cl-subseq string 1)))
(let ((buffer (process-buffer proc)))
(with-current-buffer buffer
(let ((inhibit-read-only t))
(save-excursion
(save-restriction
(widen)
(goto-char (process-mark proc))
(insert " ")
(set-marker (process-mark proc) (point))))))))
nil)
(t
;; Default, simply pass the input to the process
(comint-simple-send proc string)))))
(defun gnu-apl--set-face-for-parsed-text (start end mode string)
(cl-case mode
(cerr (add-text-properties start end '(font-lock-face gnu-apl-error) string))
(uerr (add-text-properties start end '(font-lock-face gnu-apl-user-status-text) string))))
(defun gnu-apl--parse-text (string)
(let ((tags nil))
(let ((result (with-output-to-string
(cl-loop with current-mode = gnu-apl-input-display-type
with pos = 0
for i from 0 below (length string)
for char = (aref string i)
for newmode = (cl-case char
(#xf00c0 'cin)
(#xf00c1 'cout)
(#xf00c2 'cerr)
(#xf00c3 'uerr)
(t nil))
if (and newmode (not (eq current-mode newmode)))
do (progn
(push (list pos newmode) tags)
(setq current-mode newmode))
unless newmode
do (progn
(princ (char-to-string char))
(cl-incf pos))))))
(let ((prevmode gnu-apl-input-display-type)
(prevpos 0))
(cl-loop for v in (reverse tags)
for newpos = (car v)
unless (= prevpos newpos)
do (gnu-apl--set-face-for-parsed-text prevpos newpos prevmode result)
do (progn
(setq prevpos newpos)
(setq prevmode (cadr v))))
(unless (= prevpos (length result))
(gnu-apl--set-face-for-parsed-text prevpos (length result) prevmode result))
(setq gnu-apl-input-display-type prevmode)
result))))
(defun gnu-apl--erase-and-set-function (name _content)
(gnu-apl-interactive-send-string (concat "'" *gnu-apl-ignore-start* "'"))
(gnu-apl-interactive-send-string (concat ")ERASE " name))
(gnu-apl-interactive-send-string (concat "'" *gnu-apl-ignore-end* "'")))
(defun gnu-apl--output-disconnected-message (output-fn)
(funcall output-fn "The GNU APL environment has been started, but the Emacs mode was
unable to connect to the backend. Because of this, some
functionality will not be available, such as the external
function editor.
"))
(defun gnu-apl--preoutput-filter (line)
(let ((result "")
(first t))
(cl-labels ((add-to-result (s)
(if first
(setq first nil)
(setq result (concat result "\n")))
(setq result (concat result s)))
(do-connect (mode addr command)
(gnu-apl--connect mode addr)
(message "Connected to APL interpreter")
;; starting from remote protocol version 1.6 the HELP command is
;; available to retrieve all symbols help
(when (version< "1.5" *gnu-apl-remote-protocol*)
(setf gnu-apl--symbol-doc (gnu-apl--load-help)))
(add-to-result command)))
(dolist (plain (split-string line "\n"))
(let ((command (gnu-apl--parse-text plain)))
(cl-ecase gnu-apl-preoutput-filter-state
;; Default parse state
(normal
(cond ((string-match (regexp-quote *gnu-apl-ignore-start*) command)
(setq gnu-apl-preoutput-filter-state 'ignore))
((and (not gnu-apl-use-new-native-library) ; Backwards compatibility
(string-match (regexp-quote *gnu-apl-network-start*) command))
(setq gnu-apl-preoutput-filter-state 'native))
((and gnu-apl-use-new-native-library
(not (process-live-p gnu-apl--connection))
(string-match (concat "Network listener started.*"
"mode:\\([a-z]+\\) "
"addr:\\([a-zA-Z0-9_/]+\\)")
command))
(let ((mode (match-string 1 command))
(addr (match-string 2 command)))
(do-connect mode addr command)))
(t
(add-to-result command))))
;; Ignoring output
(ignore
(cond ((string-match (regexp-quote *gnu-apl-ignore-end*) command)
(setq gnu-apl-preoutput-filter-state 'normal))
(t
nil)))
;; Initialising native code
(native
(cond ((string-match (regexp-quote *gnu-apl-network-end*) command)
(unless (process-live-p gnu-apl--connection)
(gnu-apl--output-disconnected-message #'add-to-result))
(setq gnu-apl-preoutput-filter-state 'normal))
((string-match (concat "Network listener started.*"
"mode:\\([a-z]+\\) "
"addr:\\([a-zA-Z0-9_/]+\\)")
command)
(let ((mode (match-string 1 command))
(addr (match-string 2 command)))
(do-connect mode addr command)))
(t
(add-to-result command))))))))
result))
(defvar gnu-apl-interactive-mode-map)
(defvar gnu-apl-interactive-mode-map-prefix "s-")
(defun gnu-apl--make-interactive-mode-map ()
(let ((map (gnu-apl--make-base-mode-map gnu-apl-interactive-mode-map-prefix)))
(define-key map (kbd "TAB") 'completion-at-point)
(define-key map (kbd "C-c C-f") 'gnu-apl-edit-function)
(define-key map (kbd "C-c C-v") 'gnu-apl-edit-variable)
(define-key map (kbd "C-c C-m") 'gnu-apl-plot-line)
(define-key map [menu-bar gnu-apl edit-function] '("Edit function" . gnu-apl-edit-function))
(define-key map [menu-bar gnu-apl edit-matrix] '("Edit variable" . gnu-apl-edit-variable))
(define-key map [menu-bar gnu-apl plot-line] '("Plot line graph of variable content" . gnu-apl-plot-line))
map))
(defun gnu-apl--set-interactive-mode-map-prefix (symbol new)
"Recreate the prefix and the keymap."
(set-default symbol new)
(setq gnu-apl-interactive-mode-map (gnu-apl--make-interactive-mode-map)))
(defcustom gnu-apl-interactive-mode-map-prefix "s-"
"The keymap prefix for `gnu-apl-interactive-mode-map'.
It is used both to store the new value using ‘set-create’ and to
update ‘gnu-apl-interactive-mode-map’ using
‘gnu-apl--make-interactive-mode-map’. Kill and re-start your
interactive APL buffers to reflect the change."
:type 'string
:group 'gnu-apl
:set 'gnu-apl--set-interactive-mode-map-prefix)
(defvar gnu-apl-interactive-mode-map (gnu-apl--make-interactive-mode-map)
"The keymap for ‘gnu-apl-interactive-mode'.")
(define-derived-mode gnu-apl-interactive-mode comint-mode "GNU-APL/Comint"
"Major mode for interacting with GNU APL."
:syntax-table gnu-apl-mode-syntax-table
:group 'gnu-apl
(use-local-map gnu-apl-interactive-mode-map)
(gnu-apl--init-mode-common)
(setq comint-prompt-regexp "^\\( \\)\\|\\(\\[[0-9]+\\] \\)")
(setq-local gnu-apl-preoutput-filter-state 'normal)
(setq-local gnu-apl-input-display-type 'cout)
(setq-local comint-input-sender 'gnu-apl--send)
(setq-local gnu-apl-trace-symbols nil)
(add-hook 'comint-preoutput-filter-functions 'gnu-apl--preoutput-filter nil t)
(setq font-lock-defaults '(nil t)))
(defun gnu-apl-open-customise ()
"Open the customisation editor for the gnu-apl customisation group."
(interactive)
(customize-group 'gnu-apl t))
(defun gnu-apl--insert-tips ()
(insert "This is the gnu-apl-mode interactive buffer.\n\n"
"To toggle keyboard help, call M-x gnu-apl-show-keyboard (C-c C-k by default).\n"
"APL symbols are bound to the standard keys with the Super key. You can also\n"
"activate the APL-Z ")
(insert-button "input method"
'action 'toggle-input-method
'follow-link t)
(insert " (M-x toggle-input-method or C-\\) which\n"
"allows you to input APL symbols by prefixing the key with a \".\" (period).\n\n"
"There are several ")
(insert-button "customisation"
'action #'(lambda (_event) (customize-group 'gnu-apl t))
'follow-link t)
(insert " options that can be set.\n"
"Click the link or run M-x customize-group RET gnu-apl to set up.\n\n"
"To disable this message, set gnu-apl-show-tips-on-start to nil.\n\n"))
(defun gnu-apl-find-function-at-point ()
"Jump to the definition of the function at point."
(interactive)
(let ((name (gnu-apl--name-at-point)))
(let ((resolved-name (if (and name (string-match "[a-zA-Z_∆⍙][a-zA-Z0-9_∆⍙¯]*" name))
name
(gnu-apl--choose-variable "Function" :function))))
(gnu-apl--send-network-command (concat "functiontag:" resolved-name))
(let ((result (gnu-apl--read-network-reply-block)))
(if (not (string= (car result) "tag"))
(message "No function definition found")
(let ((reference (cadr result)))
(cond ((string-match "^\\(.*\\):\\([0-9]+\\)$" reference)
(let ((file (match-string 1 reference))
(line-num (string-to-number (match-string 2 reference))))
(xref-push-marker-stack)
(let ((buffer (find-buffer-visiting file)))
(if buffer
(let ((window (get-buffer-window buffer)))
(if window
(select-window window)
(switch-to-buffer buffer)))
(find-file-existing file)))
(gnu-apl--move-to-line line-num)))
((string-match "^⎕FX$" reference)
(message "%s: No source information" resolved-name))
(t
(error "Unexpected tag format: %S" reference)))))))))
(defun gnu-apl-switch-to-interactive ()
"Switch to the GNU APL interaction buffer if it has been started."
(interactive)
(let ((buffer (gnu-apl--get-interactive-session)))
(pop-to-buffer buffer)
(goto-char (point-max))))
(provide 'gnu-apl-interactive)
;;; gnu-apl-interactive.el ends here