forked from santifa/pasp-mode
-
Notifications
You must be signed in to change notification settings - Fork 2
/
clingo-mode.el
414 lines (333 loc) · 14.4 KB
/
clingo-mode.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
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
;;; clingo-mode.el --- A major mode for editing Answer Set Programs -*- lexical-binding: t -*-
;; Copyright (c) 2020 by Ivan Uemlianin
;; Copyright (c) 2017 by Henrik Jürges
;; Maintainer: Ivan Uemlianin <[email protected]>
;; Author: Ivan Uemlianin <[email protected]>
;; Henrik Jürges <[email protected]>
;; URL: https://github.com/llaisdy/clingo-mode
;; Version: 0.4.1
;; X-Bogus-Bureaucratic-Cruft: see also <https://github.com/santifa/pasp-mode>
;; Package-requires: ((emacs "24.3"))
;; Keywords: asp, clingo, Answer Set Programs, Potassco, Major mode, languages
;; 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 of the License, 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. If not, see <https://www.gnu.org/licenses/>.
;;; Commentary:
;; A major mode for editing Answer Set Programs, formally Potassco
;; Answer Set Programs (https://potassco.org/).
;;
;; Answer Set Programs are mainly used to solve complex combinatorial
;; problems by impressive search methods.
;; The modeling language follows a declarative approach with a minimal
;; amount of fixed syntax constructs.
;;; Install
;; Open the file with Emacs and run "M-x eval-buffer"
;; Open an ASP file and run "M-x clingo-mode"
;; To manually load this file within your Emacs config
;; add this file to your load path and place
;; (require 'clingo-mode)
;; in your init file.
;; See "M-x customize-mode clingo-mode" for information
;; about mode configuration.
;;; Features
;; - Syntax highlighting (predicates can be toggled)
;; - Commenting of blocks and standard
;; - Run ASP program from within Emacs and get the compilation output
;; - Auto-load mode when a *.lp file is opened
;;; Keybindings
;; "C-c C-e" - Call clingo with current buffer and an instance file
;; "C-c C-b" - Call clingo with current buffer
;; "C-c C-r" - Call clingo with current region
;; "C-c C-c" - Comment region
;; "C-c C-u" - Uncomment region
;; Remark
;; I'm not an elisp expert, this is a very basic major mode.
;; It is intended to get my hands dirty with elisp but also
;; to be a help full tool.
;; This mode should provide a basic environment for further
;; integration of Answer Set Programs into Emacs.
;; Ideas, issues and pull requests are highly welcome!
;;; Todo:
;; - TOP: make clingo-generate-echo safe
;; - Smart indentation based on nesting depth
;; - Refactoring of predicates/variables (complete buffer and #program parts)
;; - Color compilation output
;; - Smart rearrangement of compilation output (predicates separated, table...)
;; - yas-snippet for rules; constraints; soft constraints; generation?
;; - sync as much as possible with vim-syntax-clingo
;;; Code:
(require 'compile)
;;; Customization
(defgroup clingo nil
"Major mode for editing Answer Set Programs."
:group 'languages
:prefix "clingo-")
(defconst clingo-mode-version "0.4.0" "Version of `clingo-mode'.")
(defcustom clingo-indentation 2
"Level of indentation."
:type 'integer
:group 'clingo-mode)
(defcustom clingo-path (executable-find "clingo")
"Path to clingo binary used for execution."
:type 'string
:group 'clingo-mode)
(defcustom clingo-options '()
"Command line options passed to clingo."
:type '(repeat string)
:group 'clingo-mode
:safe #'list-of-strings-p)
(defcustom clingo-pretty-symbols-p t
"Use Unicode characters where appropriate."
:type 'boolean
:group 'clingo-mode)
;;; Pretty Symbols
(defvar clingo-mode-hook
(lambda ()
(when clingo-pretty-symbols-p
(push '(":-" . ?⊢) prettify-symbols-alist)
(push '(">=" . ?≥) prettify-symbols-alist)
(push '("<=" . ?≤) prettify-symbols-alist)
(push '("!=" . ?≠) prettify-symbols-alist)
(push '("not" . ?¬) prettify-symbols-alist))))
;;; Syntax table
(defvar clingo-mode-syntax-table
(let ((table (make-syntax-table)))
(modify-syntax-entry ?' "w" table)
(modify-syntax-entry ?% "<" table)
(modify-syntax-entry ?\n ">" table)
(modify-syntax-entry ?, "_ p" table)
table)
"Syntax table for `clingo-mode`.")
;;; Syntax highlighting faces
(defface clingo-atom-face
'((t (:inherit font-lock-keyword-face :weight normal)))
"Face for ASP atoms (starting with lower case).")
(defface clingo-construct-face
'((default (:inherit font-lock-builtin-face :height 1.1)))
"Face for ASP base constructs.")
(defface clingo-statistics-face
'((default (:inherit compilation-info :weight bold)))
"Face for Clingo statistics.")
;; Syntax highlighting
(defvar clingo--constructs
'("\\.\\|:-\\|:\\|;\\|:~\\|,\\|(\\|)\\|{\\|}\\|[\\|]\\|not " . 'clingo-construct-face)
"ASP constructs.")
(defconst clingo--constant
'("#[[:word:]]+" . font-lock-builtin-face)
"ASP constants.")
(defconst clingo--variable
'("[^[:word:]]\\(_*\\([[:upper:]][[:word:]_']*\\)?\\)" . (1 font-lock-variable-name-face))
"ASP variable.")
(defconst clingo--variable2
'("\\_<\\(_*[[:upper:]][[:word:]_']*\\)\\_>" . (1 font-lock-variable-name-face))
"ASP variable 2.")
(defconst clingo--atom
'("_*[[:lower:]][[:word:]_']*" . 'clingo-atom-face)
"ASP atoms.")
(defvar clingo-font-lock-keywords
(list clingo--constructs
clingo--constant
clingo--variable2
clingo--variable
clingo--atom)
"Font lock keywords in `clingo-mode'.")
;;; Compilation
(defvar clingo-error-regexp
"^\\(<?[a-zA-Z\.0-9_/\\-]+>?\\):\\([0-9]+\\):\\([0-9-]+\\)"
"Regular expression to match clingo errors.")
(defvar clingo-compilation-keywords
`(("\\(Answer\\): \\([0-9]+\\)\n"
(1 font-lock-function-name-face) (2 compilation-line-face)
(,(car clingo--atom) nil nil
(0 'clingo-atom-face)))
("\\(Answer\\): \\([0-9]+\\)\n"
(,(car clingo--constructs) nil nil
(0 'clingo-construct-face)))
("^ *\\<\\([[:alnum:] _/.+-]+\\)\\> +:"
(1 'clingo-statistics-face))))
(defvar clingo-error-regexp-alist
`((,clingo-error-regexp 1 2 3))
"Alist that specifies how to match Clingo errors as per
`compilation-error-regexp-alist'.")
(defconst clingo-exit-codes
;; Exit codes as per Clasp::Cli::ExitCode enumeration
'((0 . "nothing to be done")
(1 . "interrupted")
(10 . "satisfiable")
(11 . "satisfiable, interrupted")
(20 . "unsatisfiable")
(30 . "satisfiable, all models found")
(33 . "memory error")
(65 . "internal error")
(128 . "syntax or command line error")))
(defun clingo-exit-status-success-p (exit-status)
"Return `t' if EXIT_STATUS conforms to a successful run of Clingo."
(< exit-status 32))
(defun clingo-compilation-filter ()
"Filter Clingo output.
Currently, this function merely deletes ANSI terminal escape codes."
(ansi-color-apply-on-region compilation-filter-start (point-max))
(save-excursion
(while (re-search-forward "^[\\[[0-9]+[a-z]" nil t)
(replace-match ""))))
(defvar-local clingo--cached-exit-message nil
"Cached exit message for use in `clingo-compilation-finish'.")
(defvar-local clingo--cached-process-status nil
"Cached process status for use in `clingo-compilation-finish'.")
(defun clingo-exit-message-function (process-status exit-status msg)
"Return an exit message appropriate for EXIT_STATUS."
(setq clingo--cached-process-status process-status)
(setq clingo--cached-exit-message
(cons (or (cdr-safe (assoc exit-status clingo-exit-codes))
"unknown status code")
exit-status)))
(defun clingo-compilation-finish (buffer message)
"Hook run when a clingo process is finished in BUFFER."
(let* ((process-status clingo--cached-process-status)
(status clingo--cached-exit-message)
(exit-status (cdr status)))
(setq mode-line-process
(list
(let ((out-string (format ":%s [%s]" process-status (cdr status)))
(msg (format "%s %s" mode-name
(replace-regexp-in-string "\n?$" ""
(car status)))))
(propertize out-string
'help-echo msg
'face (if (clingo-exit-status-success-p exit-status)
'compilation-mode-line-exit
'compilation-mode-line-fail)))
compilation-mode-line-errors))
(force-mode-line-update)))
(define-compilation-mode clingo-compilation-mode "ASP"
"Major mode for running ASP files."
(set (make-local-variable 'compilation-error-regexp-alist)
clingo-error-regexp-alist)
(set (make-local-variable 'font-lock-multiline) t)
(set (make-local-variable 'compilation-exit-message-function)
#'clingo-exit-message-function)
(add-hook 'compilation-filter-hook #'clingo-compilation-filter nil t)
(add-hook 'compilation-finish-functions #'clingo-compilation-finish nil t))
(font-lock-add-keywords 'clingo-compilation-mode clingo-compilation-keywords)
(defun clingo-generate-command (encoding options &optional instance)
"Generate Clingo call with some ASP input file.
Argument ENCODING The current buffer which holds the problem encoding.
Argument OPTIONS Options (possibly empty string) sent to clingo.
Optional argument INSTANCE The problem instance which is solved by the encoding.
If no instance it is assumed to be also in the encoding file."
(let ((options (if options options ""))
(files (if instance
(list encoding instance)
(list encoding))))
(concat clingo-path " " options " "
(mapconcat #'identity files " "))))
(defun clingo-run-clingo (encoding options &optional instance)
"Run Clingo with some ASP input files.
Be aware: Partial ASP code may lead to abnormal exits
while the result is sufficient.
Argument ENCODING The current buffer which holds the problem encoding.
Argument OPTIONS Options (possibly empty string) sent to clingo.
Optional argument INSTANCE The problem instance which is solved by the encoding.
If no instance it is assumed to be also in the encoding file."
(when (get-buffer "*clingo output*")
(kill-buffer "*clingo output*"))
(let ((test-command-to-run (clingo-generate-command encoding options instance))
(compilation-buffer-name-function (lambda (_) "" "*clingo output*")))
(compile test-command-to-run 'clingo-compilation-mode)))
(defun clingo-generate-echo (region options &optional instance)
"Generate Clingo call with region echoed to it.
Argument REGION The selected region which holds the problem encoding.
Argument OPTIONS Options (possibly empty string) sent to clingo.
Optional argument INSTANCE The problem instance which is solved by the encoding.
If no instance it is assumed to be also in the encoding file."
(if 'instance
(concat "echo \"" region "\" | " clingo-path " " options " " instance)
(concat "echo \"" region "\" | " clingo-path " " options)))
;; (defun clingo-echo-clingo (region-begin region-end options &optional instance)
(defun clingo-echo-clingo (region options &optional instance)
"Run Clingo on selected region (prompts for options).
Argument REGION The selected region as a string, which has the problem encoding.
Argument OPTIONS Options (possibly empty string) sent to clingo.
Optional argument INSTANCE The problem instance which is solved by the encoding.
If no instance it is assumed to be also in the encoding file."
(when (get-buffer "*clingo output*")
(kill-buffer "*clingo output*"))
(let ((test-command-to-run (clingo-generate-echo region options instance))
(compilation-buffer-name-function (lambda (_) "" "*clingo output*")))
(compile test-command-to-run 'clingo-compilation-mode)))
;; save the last user input
(defvar clingo-last-instance "")
(defvar clingo-last-options "")
(defun get-options ()
"Get options string in minibuffer."
(read-string (format "Options [%s]: " clingo-last-options) nil nil clingo-last-options))
;;;###autoload
(defun clingo-run-region (region-beginning region-end options)
"Run Clingo on selected region (prompts for options).
Argument REGION-BEGINNING point marking beginning of region.
Argument REGION-END point marking end of region.
Argument OPTIONS Options (possibly empty string) sent to clingo."
(interactive
(let ((string (get-options))
(list (region-beginning) (region-end) string)))
(setq-local clingo-last-options options)
(clingo-echo-clingo
(buffer-substring-no-properties region-beginning region-end)
options))
;;;###autoload
(defun clingo-run-buffer (options)
"Run clingo with the current buffer as input; prompts for OPTIONS."
(interactive
(list (get-options)))
(setq-local clingo-last-options options)
(clingo-run-clingo (buffer-file-name) options))
;;;###autoload
(defun clingo-run (options instance)
"Run clingo with the current buffer and some user provided INSTANCE as input; prompts for OPTIONS."
(interactive
(list
(get-options)
(read-file-name
(format "Instance [%s]:" (file-name-nondirectory clingo-last-instance))
nil clingo-last-instance)))
(setq-local clingo-last-options options)
(setq-local clingo-last-instance instance)
(clingo-run-clingo (buffer-file-name) options instance))
;;; Utility functions
(defun clingo--reload-mode ()
"Reload the CLINGO major mode."
(unload-feature 'clingo-mode)
(require 'clingo-mode)
(clingo-mode))
;;; File ending
;;;###autoload
(add-to-list 'auto-mode-alist '("\\.lp\\'" . clingo-mode))
;;; Define clingo mode
;;; Keymap
(defvar
clingo-mode-map
(let ((km (make-sparse-keymap)))
(define-key km (kbd "C-c C-c") 'comment-region)
(define-key km (kbd "C-c C-u") 'uncomment-region)
(define-key km (kbd "C-c C-b") 'clingo-run-buffer)
(define-key km (kbd "C-c C-r") 'clingo-run-region)
(define-key km (kbd "C-c C-e") 'clingo-run)
km))
;;;###autoload
(define-derived-mode clingo-mode prolog-mode "Clingo"
"A major mode for editing Answer Set Programs."
(setq font-lock-defaults '(clingo-font-lock-keywords))
;; define the syntax for un/comment region and dwim
(setq-local comment-start "%")
(setq-local comment-end "")
(setq-local tab-width clingo-indentation))
;; add mode to feature list
(provide 'clingo-mode)
;;; clingo-mode.el ends here