]> code.delx.au - gnu-emacs-elpa/blob - packages/ada-mode/gnat-core.el
c6e597fe5a45c3fc7ea24146cc0772df725fec0c
[gnu-emacs-elpa] / packages / ada-mode / gnat-core.el
1 ;; Support for running GNAT tools, which support multiple programming
2 ;; languages.
3 ;;
4 ;; GNAT is provided by AdaCore; see http://libre.adacore.com/
5 ;;
6 ;;; Copyright (C) 2012, 2013 Free Software Foundation, Inc.
7 ;;
8 ;; Author: Stephen Leake <stephen_leake@member.fsf.org>
9 ;; Maintainer: Stephen Leake <stephen_leake@member.fsf.org>
10 ;;
11 ;; This file is part of GNU Emacs.
12 ;;
13 ;; GNU Emacs is free software: you can redistribute it and/or modify
14 ;; it under the terms of the GNU General Public License as published by
15 ;; the Free Software Foundation, either version 3 of the License, or
16 ;; (at your option) any later version.
17 ;;
18 ;; GNU Emacs is distributed in the hope that it will be useful,
19 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
20 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 ;; GNU General Public License for more details.
22 ;;
23 ;; You should have received a copy of the GNU General Public License
24 ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
25
26 ;; We use cl-delete-if, defined in cl-seq.el. cl-seq.el has no
27 ;; 'provide'. autoload for cl-delete-if is defined in cl-loaddefs.el,
28 ;; which is not pre-loaded. cl-lib does (load "cl-loaddefs.el"), so
29 ;; that seems to be the thing to do
30 (require 'cl-lib)
31
32 ;;;;; code
33
34 ;;;; project file handling
35
36 (defun gnat-prj-add-prj-dir (dir project)
37 "Add DIR to 'prj_dir and to GPR_PROJECT_PATH in 'proc_env. Return new project."
38 (let ((prj-dir (plist-get project 'prj_dir)))
39
40 (cond
41 ((listp prj-dir)
42 (add-to-list 'prj-dir dir))
43
44 (prj-dir
45 (setq prj-dir (list dir)))
46
47 (t nil))
48
49 (setq project (plist-put project 'prj_dir prj-dir))
50
51 (let ((process-environment (plist-get project 'proc_env)))
52 (setenv "GPR_PROJECT_PATH"
53 (mapconcat 'identity
54 (plist-get project 'prj_dir)
55 (plist-get project 'path_sep)))
56
57 (setq project (plist-put project 'proc_env process-environment))
58 )
59
60 project))
61
62 (defun gnat-prj-parse-emacs-one (name value project)
63 "Handle gnat-specific Emacs Ada project file settings.
64 Return new PROJECT if NAME recognized, nil otherwise.
65 See also `gnat-parse-emacs-final'."
66 (let ((process-environment (plist-get project 'proc_env))); for substitute-in-file-name
67 (cond
68 ((or
69 ;; we allow either name here for backward compatibility
70 (string= name "gpr_project_path")
71 (string= name "ada_project_path"))
72 ;; We maintain two project values for this;
73 ;; 'prj_dir - a list of directories, for gpr-ff-special-with
74 ;; GPR_PROJECT_PATH in 'proc_env, for gnat-run
75 (gnat-prj-add-prj-dir (expand-file-name (substitute-in-file-name value)) project))
76
77 ((string= (match-string 1) "gpr_file")
78 ;; The file is parsed in `gnat-parse-emacs-prj-file-final', so
79 ;; it can add to user-specified src_dir.
80 (setq project
81 (plist-put project
82 'gpr_file
83 (expand-file-name (substitute-in-file-name value))))
84 project)
85 )))
86
87 (defun gnat-prj-parse-emacs-final (project)
88 "Final processing of gnat-specific Emacs Ada project file settings."
89 (when (buffer-live-p (get-buffer (gnat-run-buffer-name)))
90 (kill-buffer (gnat-run-buffer-name))); things may have changed, force re-create
91
92 (if (ada-prj-get 'gpr_file project)
93 (set 'project (gnat-parse-gpr (ada-prj-get 'gpr_file project) project))
94
95 ;; add the compiler libraries to src_dir
96 (setq project (gnat-get-paths project))
97 )
98
99 project)
100
101 (defun gnat-get-paths (project)
102 "Add project and/or compiler source, object paths to PROJECT src_dir and/or prc_dir."
103 (with-current-buffer (gnat-run-buffer)
104 ;; gnat list -v -P can return status 0 or 4; always lists compiler dirs
105 (let ((src-dirs (ada-prj-get 'src_dir project))
106 (prj-dirs (ada-prj-get 'prj_dir project)))
107
108 (gnat-run-gnat "list" (list "-v") '(0 4))
109
110 (goto-char (point-min))
111
112 (condition-case nil
113 (progn
114 ;; Source path
115 (search-forward "Source Search Path:")
116 (forward-line 1)
117 (while (not (looking-at "^$")) ; terminate on blank line
118 (back-to-indentation) ; skip whitespace forward
119 (if (looking-at "<Current_Directory>")
120 (add-to-list 'src-dirs (directory-file-name default-directory))
121 (add-to-list 'src-dirs
122 (expand-file-name ; canonicalize path part
123 (directory-file-name
124 (buffer-substring-no-properties (point) (point-at-eol))))))
125 (forward-line 1))
126
127 ;; Project path
128 ;;
129 ;; These are also added to src_dir, so compilation errors
130 ;; reported in project files are found.
131 (search-forward "Project Search Path:")
132 (forward-line 1)
133 (while (not (looking-at "^$"))
134 (back-to-indentation)
135 (if (looking-at "<Current_Directory>")
136 (add-to-list 'prj-dirs ".")
137 (add-to-list 'prj-dirs
138 (expand-file-name
139 (buffer-substring-no-properties (point) (point-at-eol))))
140 (add-to-list 'src-dirs
141 (expand-file-name
142 (buffer-substring-no-properties (point) (point-at-eol)))))
143 (forward-line 1))
144
145 )
146 ('error
147 (pop-to-buffer (current-buffer))
148 ;; search-forward failed
149 (error "parse gpr failed")
150 ))
151
152 (setq project (plist-put project 'src_dir (reverse src-dirs)))
153 (mapc (lambda (dir) (gnat-prj-add-prj-dir dir project))
154 (reverse prj-dirs))
155 ))
156 project)
157
158 (defun gnat-parse-gpr (gpr-file project)
159 "Append to src_dir and prj_dir in PROJECT by parsing GPR-FILE.
160 Return new value of PROJECT.
161 GPR-FILE must be full path to file, normalized.
162 src_dir will include compiler runtime."
163 ;; this can take a long time; let the user know what's up
164 (message "Parsing %s ..." gpr-file)
165
166 (if (ada-prj-get 'gpr_file project)
167 ;; gpr-file defined in Emacs Ada mode project file
168 (when (not (equal gpr-file (ada-prj-get 'gpr_file project)))
169 (error "Ada project file %s defines a different GNAT project file than %s"
170 ada-prj-current-file
171 gpr-file))
172
173 ;; gpr-file is top level Ada mode project file
174 (setq project (plist-put project 'gpr_file gpr-file))
175 )
176
177 (setq project (gnat-get-paths project))
178
179 (message "Parsing %s ... done" gpr-file)
180 project)
181
182 ;;;; command line tool interface
183
184 (defun gnat-run-buffer-name (&optional prefix)
185 (concat (or prefix " *gnat-run-")
186 (or (ada-prj-get 'gpr_file)
187 ada-prj-current-file)
188 "*"))
189
190 (defun gnat-run-buffer (&optional buffer-name-prefix)
191 "Return a buffer suitable for running gnat command line tools for the current project."
192 (ada-require-project-file)
193 (let* ((name (gnat-run-buffer-name buffer-name-prefix))
194 (buffer (get-buffer name)))
195 (if buffer
196 buffer
197 (setq buffer (get-buffer-create name))
198 (with-current-buffer buffer
199 (setq default-directory
200 (file-name-directory
201 (or (ada-prj-get 'gpr_file)
202 ada-prj-current-file)))
203 )
204 buffer)))
205
206 (defun gnat-run (exec command &optional err-msg expected-status)
207 "Run a gnat command line tool, as \"EXEC COMMAND\".
208 EXEC must be an executable found on `exec-path'. COMMAND must be a list of strings.
209 ERR-MSG must be nil or a string.
210 EXPECTED-STATUS must be nil or a list of integers.
211 Return process status.
212 Assumes current buffer is (gnat-run-buffer)"
213 (set 'buffer-read-only nil)
214 (erase-buffer)
215
216 (setq command (cl-delete-if 'null command))
217
218 (let ((process-environment (ada-prj-get 'proc_env)) ;; for GPR_PROJECT_PATH
219 status)
220
221 (insert (format "GPR_PROJECT_PATH=%s\n%s " (getenv "GPR_PROJECT_PATH") exec)); for debugging
222 (mapc (lambda (str) (insert (concat str " "))) command); for debugging
223 (newline)
224
225 (setq status (apply 'call-process exec nil t nil command))
226 (cond
227 ((memq status (or expected-status '(0))); success
228 nil)
229
230 (t ; failure
231 (pop-to-buffer (current-buffer))
232 (if err-msg
233 (error "%s %s failed; %s" exec (car command) err-msg)
234 (error "%s %s failed" exec (car command))
235 ))
236 )))
237
238 (defun gnat-run-gnat (command &optional switches-args expected-status)
239 "Run the \"gnat\" command line tool, as \"gnat COMMAND -P<prj> SWITCHES-ARGS\".
240 COMMAND must be a string, SWITCHES-ARGS a list of strings.
241 EXPECTED-STATUS must be nil or a list of integers.
242 Return process status.
243 Assumes current buffer is (gnat-run-buffer)"
244 (let* ((project-file-switch
245 (when (ada-prj-get 'gpr_file)
246 (concat "-P" (file-name-nondirectory (ada-prj-get 'gpr_file)))))
247 (cmd (append (list command) (list project-file-switch) switches-args)))
248
249 (gnat-run "gnat" cmd nil expected-status)
250 ))
251
252 (defun gnat-run-no-prj (command &optional dir)
253 "Run the gnat command line tool, as \"gnat COMMAND\", with DIR as current directory.
254 Return process status. Assumes current buffer
255 is (gnat-run-buffer)"
256 (set 'buffer-read-only nil)
257 (erase-buffer)
258
259 (setq command (cl-delete-if 'null command))
260 (mapc (lambda (str) (insert (concat str " "))) command)
261 (newline)
262
263 (let ((default-directory (or dir default-directory))
264 status)
265
266 (setq status (apply 'call-process "gnat" nil t nil command))
267 (cond
268 ((= status 0); success
269 nil)
270
271 (t ; failure
272 (pop-to-buffer (current-buffer))
273 (error "gnat %s failed" (car command)))
274 )))
275
276 ;;;; gnatprep utils
277
278 (defun gnatprep-indent ()
279 "If point is on a gnatprep keyword, return indentation column
280 for it. Otherwise return nil. Intended to be added to
281 `wisi-indent-calculate-functions' or other indentation function
282 list."
283 ;; gnatprep keywords are:
284 ;;
285 ;; #if identifier [then]
286 ;; #elsif identifier [then]
287 ;; #else
288 ;; #end if;
289 ;;
290 ;; they are all indented at column 0.
291 (when (equal (char-after) ?\#) 0))
292
293 (defun gnatprep-syntax-propertize (start end)
294 (goto-char start)
295 (while (re-search-forward
296 "^[ \t]*\\(#\\(?:if\\|else\\|elsif\\|end\\)\\)"; gnatprep keywords.
297 end t)
298 (cond
299 ((match-beginning 1)
300 (put-text-property
301 (match-beginning 1) (match-end 1) 'syntax-table '(11 . ?\n)))
302 )
303 ))
304
305 ;;;; support for ada-gnat-xref and ada-gnatinspect
306 (defun ada-gnat-file-name-from-ada-name (ada-name)
307 "For `ada-file-name-from-ada-name'."
308 (let ((result nil))
309
310 (while (string-match "\\." ada-name)
311 (setq ada-name (replace-match "-" t t ada-name)))
312
313 (setq ada-name (downcase ada-name))
314
315 (with-current-buffer (gnat-run-buffer)
316 (gnat-run-no-prj
317 (list
318 "krunch"
319 ada-name
320 ;; "0" means only krunch GNAT library names
321 "0"))
322
323 (goto-char (point-min))
324 (forward-line 1); skip cmd
325 (setq result (buffer-substring-no-properties (line-beginning-position) (line-end-position)))
326 )
327 result))
328
329 (defconst ada-gnat-predefined-package-alist
330 '(("a-textio" . "Ada.Text_IO")
331 ("a-chahan" . "Ada.Characters.Handling")
332 ("a-comlin" . "Ada.Command_Line")
333 ("a-except" . "Ada.Exceptions")
334 ("a-numeri" . "Ada.Numerics")
335 ("a-string" . "Ada.Strings")
336 ("a-strmap" . "Ada.Strings.Maps")
337 ("a-strunb" . "Ada.Strings.Unbounded")
338 ("g-socket" . "GNAT.Sockets")
339 ("interfac" . "Interfaces")
340 ("i-c" . "Interfaces.C")
341 ("i-cstrin" . "Interfaces.C.Strings")
342 ("s-stoele" . "System.Storage_Elements")
343 ("unchconv" . "Unchecked_Conversion") ; Ada 83 name
344 )
345 "Alist (filename . package name) of GNAT file names for predefined Ada packages.")
346
347 (defun ada-gnat-ada-name-from-file-name (file-name)
348 "For `ada-ada-name-from-file-name'."
349 (let* (status
350 (ada-name (file-name-sans-extension (file-name-nondirectory file-name)))
351 (predefined (cdr (assoc ada-name ada-gnat-predefined-package-alist))))
352
353 (if predefined
354 predefined
355 (while (string-match "-" ada-name)
356 (setq ada-name (replace-match "." t t ada-name)))
357 ada-name)))
358
359 (defun ada-gnat-make-package-body (body-file-name)
360 "For `ada-make-package-body'."
361 ;; WORKAROUND: gnat stub 7.1w does not accept aggregate project files,
362 ;; and doesn't use the gnatstub package if it is in a 'with'd
363 ;; project file; see AdaCore ticket LC30-001. On the other hand we
364 ;; need a project file to specify the source dirs so the tree file
365 ;; can be generated. So we use gnat-run-no-prj, and the user
366 ;; must specify the proper project file in gnat_stub_opts.
367 ;;
368 ;; gnatstub always creates the body in the current directory (in the
369 ;; process where gnatstub is running); the -o parameter may not
370 ;; contain path info. So we pass a directory to gnat-run-no-prj.
371 (let ((start-buffer (current-buffer))
372 (start-file (buffer-file-name))
373 ;; can also specify gnat stub options/switches in .gpr file, in package 'gnatstub'.
374 (opts (when (ada-prj-get 'gnat_stub_opts)
375 (split-string (ada-prj-get 'gnat_stub_opts))))
376 (switches (when (ada-prj-get 'gnat_stub_switches)
377 (split-string (ada-prj-get 'gnat_stub_switches))))
378 )
379
380 ;; Make sure all relevant files are saved to disk. This also saves
381 ;; the bogus body buffer created by ff-find-the-other-file, so we
382 ;; need -f gnat stub option. We won't get here if there is an
383 ;; existing body file.
384 (save-some-buffers t)
385 (add-to-list 'opts "-f")
386 (with-current-buffer (gnat-run-buffer)
387 (gnat-run-no-prj
388 (append (list "stub") opts (list start-file "-cargs") switches)
389 (file-name-directory body-file-name))
390
391 (find-file body-file-name)
392 (indent-region (point-min) (point-max))
393 (save-buffer)
394 (set-buffer start-buffer)
395 )
396 nil))
397
398 (provide 'gnat-core)
399
400 ;; end of file