Commit | Line | Data |
---|---|---|
55b522b2 | 1 | ;;; semantic/util.el --- Utilities for use with semantic tag tables |
a91e32bc CY |
2 | |
3 | ;;; Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2007, | |
4 | ;;; 2008, 2009 Free Software Foundation, Inc. | |
5 | ||
6 | ;; Author: Eric M. Ludlam <zappo@gnu.org> | |
7 | ;; Keywords: syntax | |
8 | ||
9 | ;; This file is part of GNU Emacs. | |
10 | ||
11 | ;; GNU Emacs is free software: you can redistribute it and/or modify | |
12 | ;; it under the terms of the GNU General Public License as published by | |
13 | ;; the Free Software Foundation, either version 3 of the License, or | |
14 | ;; (at your option) any later version. | |
15 | ||
16 | ;; GNU Emacs is distributed in the hope that it will be useful, | |
17 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | |
18 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
19 | ;; GNU General Public License for more details. | |
20 | ||
21 | ;; You should have received a copy of the GNU General Public License | |
22 | ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. | |
23 | ||
24 | ;;; Commentary: | |
25 | ;; | |
26 | ;; Semantic utility API for use with semantic tag tables. | |
27 | ;; | |
28 | ||
29 | (require 'assoc) | |
30 | (require 'semantic) | |
996bc9bf CY |
31 | |
32 | (declare-function data-debug-insert-stuff-list "data-debug") | |
33 | (declare-function data-debug-insert-thing "data-debug") | |
34 | (declare-function semanticdb-file-stream "semantic/db") | |
35 | (declare-function semanticdb-abstract-table-child-p "semantic/db") | |
36 | (declare-function semanticdb-refresh-table "semantic/db") | |
37 | (declare-function semanticdb-get-tags "semantic/db") | |
38 | (declare-function semanticdb-find-results-p "semantic/db-find") | |
39 | ||
40 | ;; For semantic-find-tags-by-class, semantic--find-tags-by-function, | |
41 | ;; and semantic-brute-find-tag-standard: | |
42 | (eval-when-compile (require 'semantic/find)) | |
a91e32bc CY |
43 | |
44 | ;;; Code: | |
45 | ||
46 | (defvar semantic-type-relation-separator-character '(".") | |
47 | "Character strings used to separate a parent/child relationship. | |
48 | This list of strings are used for displaying or finding separators | |
49 | in variable field dereferencing. The first character will be used for | |
50 | display. In C, a type field is separated like this: \"type.field\" | |
51 | thus, the character is a \".\". In C, and additional value of \"->\" | |
52 | would be in the list, so that \"type->field\" could be found.") | |
53 | (make-variable-buffer-local 'semantic-type-relation-separator-character) | |
54 | ||
55 | (defvar semantic-equivalent-major-modes nil | |
56 | "List of major modes which are considered equivalent. | |
57 | Equivalent modes share a parser, and a set of override methods. | |
58 | A value of nil means that the current major mode is the only one.") | |
59 | (make-variable-buffer-local 'semantic-equivalent-major-modes) | |
60 | ||
61 | ;; These semanticdb calls will throw warnings in the byte compiler. | |
62 | ;; Doing the right thing to make them available at compile time | |
63 | ;; really messes up the compilation sequence. | |
64 | (defun semantic-file-tag-table (file) | |
65 | "Return a tag table for FILE. | |
66 | If it is loaded, return the stream after making sure it's ok. | |
67 | If FILE is not loaded, check to see if `semanticdb' feature exists, | |
68 | and use it to get tags from files not in memory. | |
69 | If FILE is not loaded, and semanticdb is not available, find the file | |
70 | and parse it." | |
1eac105a CY |
71 | (save-match-data |
72 | (if (find-buffer-visiting file) | |
73 | (save-excursion | |
74 | (set-buffer (find-buffer-visiting file)) | |
75 | (semantic-fetch-tags)) | |
76 | ;; File not loaded | |
77 | (if (and (require 'semantic/db-mode) | |
78 | (semanticdb-minor-mode-p)) | |
79 | ;; semanticdb is around, use it. | |
80 | (semanticdb-file-stream file) | |
81 | ;; Get the stream ourselves. | |
82 | (save-excursion | |
83 | (set-buffer (find-file-noselect file)) | |
84 | (semantic-fetch-tags)))))) | |
a91e32bc CY |
85 | |
86 | (semantic-alias-obsolete 'semantic-file-token-stream | |
87 | 'semantic-file-tag-table) | |
88 | ||
89 | (defun semantic-something-to-tag-table (something) | |
90 | "Convert SOMETHING into a semantic tag table. | |
91 | Something can be a tag with a valid BUFFER property, a tag table, a | |
92 | buffer, or a filename. If SOMETHING is nil return nil." | |
93 | (cond | |
94 | ;; A list of tags | |
95 | ((and (listp something) | |
96 | (semantic-tag-p (car something))) | |
97 | something) | |
98 | ;; A buffer | |
99 | ((bufferp something) | |
100 | (save-excursion | |
101 | (set-buffer something) | |
102 | (semantic-fetch-tags))) | |
103 | ;; A Tag: Get that tag's buffer | |
104 | ((and (semantic-tag-with-position-p something) | |
105 | (semantic-tag-in-buffer-p something)) | |
106 | (save-excursion | |
107 | (set-buffer (semantic-tag-buffer something)) | |
108 | (semantic-fetch-tags))) | |
109 | ;; Tag with a file name in it | |
110 | ((and (semantic-tag-p something) | |
111 | (semantic-tag-file-name something) | |
112 | (file-exists-p (semantic-tag-file-name something))) | |
113 | (semantic-file-tag-table | |
114 | (semantic-tag-file-name something))) | |
115 | ;; A file name | |
116 | ((and (stringp something) | |
117 | (file-exists-p something)) | |
118 | (semantic-file-tag-table something)) | |
119 | ;; A Semanticdb table | |
996bc9bf | 120 | ((and (featurep 'semantic/db) |
a91e32bc CY |
121 | (semanticdb-minor-mode-p) |
122 | (semanticdb-abstract-table-child-p something)) | |
123 | (semanticdb-refresh-table something) | |
124 | (semanticdb-get-tags something)) | |
125 | ;; Semanticdb find-results | |
996bc9bf | 126 | ((and (featurep 'semantic/db) |
a91e32bc | 127 | (semanticdb-minor-mode-p) |
996bc9bf | 128 | (require 'semantic/db-find) |
a91e32bc CY |
129 | (semanticdb-find-results-p something)) |
130 | (semanticdb-strip-find-results something)) | |
131 | ;; NOTE: This commented out since if a search result returns | |
132 | ;; empty, that empty would turn into everything on the next search. | |
133 | ;; Use the current buffer for nil | |
134 | ;; ((null something) | |
135 | ;; (semantic-fetch-tags)) | |
136 | ;; don't know what it is | |
137 | (t nil))) | |
138 | ||
139 | (semantic-alias-obsolete 'semantic-something-to-stream | |
140 | 'semantic-something-to-tag-table) | |
141 | ||
142 | ;;; Recursive searching through dependency trees | |
143 | ;; | |
144 | ;; This will depend on the general searching APIS defined above. | |
145 | ;; but will add full recursion through the dependencies list per | |
146 | ;; stream. | |
147 | (defun semantic-recursive-find-nonterminal-by-name (name buffer) | |
148 | "Recursively find the first occurrence of NAME. | |
149 | Start search with BUFFER. Recurse through all dependencies till found. | |
150 | The return item is of the form (BUFFER TOKEN) where BUFFER is the buffer | |
151 | in which TOKEN (the token found to match NAME) was found. | |
152 | ||
153 | THIS ISN'T USED IN SEMANTIC. DELETE ME SOON." | |
154 | (save-excursion | |
155 | (set-buffer buffer) | |
156 | (let* ((stream (semantic-fetch-tags)) | |
157 | (includelist (or (semantic-find-tags-by-class 'include stream) | |
158 | "empty.silly.thing")) | |
159 | (found (semantic-find-first-tag-by-name name stream)) | |
160 | (unfound nil)) | |
161 | (while (and (not found) includelist) | |
162 | (let ((fn (semantic-dependency-tag-file (car includelist)))) | |
163 | (if (and fn (not (member fn unfound))) | |
164 | (save-excursion | |
1eac105a CY |
165 | (save-match-data |
166 | (set-buffer (find-file-noselect fn))) | |
a91e32bc CY |
167 | (message "Scanning %s" (buffer-file-name)) |
168 | (setq stream (semantic-fetch-tags)) | |
169 | (setq found (semantic-find-first-tag-by-name name stream)) | |
170 | (if found | |
171 | (setq found (cons (current-buffer) (list found))) | |
172 | (setq includelist | |
173 | (append includelist | |
174 | (semantic-find-tags-by-class | |
175 | 'include stream)))) | |
176 | (setq unfound (cons fn unfound))))) | |
177 | (setq includelist (cdr includelist))) | |
178 | found))) | |
179 | (make-obsolete 'semantic-recursive-find-nonterminal-by-name | |
180 | "Do not use this function.") | |
181 | ||
182 | ;;; Completion APIs | |
183 | ;; | |
184 | ;; These functions provide minibuffer reading/completion for lists of | |
185 | ;; nonterminals. | |
186 | (defvar semantic-read-symbol-history nil | |
187 | "History for a symbol read.") | |
188 | ||
189 | (defun semantic-read-symbol (prompt &optional default stream filter) | |
190 | "Read a symbol name from the user for the current buffer. | |
191 | PROMPT is the prompt to use. | |
192 | Optional arguments: | |
193 | DEFAULT is the default choice. If no default is given, one is read | |
194 | from under point. | |
195 | STREAM is the list of tokens to complete from. | |
196 | FILTER is provides a filter on the types of things to complete. | |
197 | FILTER must be a function to call on each element." | |
198 | (if (not default) (setq default (thing-at-point 'symbol))) | |
199 | (if (not stream) (setq stream (semantic-fetch-tags))) | |
200 | (setq stream | |
201 | (if filter | |
202 | (semantic--find-tags-by-function filter stream) | |
203 | (semantic-brute-find-tag-standard stream))) | |
204 | (if (and default (string-match ":" prompt)) | |
205 | (setq prompt | |
206 | (concat (substring prompt 0 (match-end 0)) | |
207 | " (default: " default ") "))) | |
208 | (completing-read prompt stream nil t "" | |
209 | 'semantic-read-symbol-history | |
210 | default)) | |
211 | ||
212 | (defun semantic-read-variable (prompt &optional default stream) | |
213 | "Read a variable name from the user for the current buffer. | |
214 | PROMPT is the prompt to use. | |
215 | Optional arguments: | |
216 | DEFAULT is the default choice. If no default is given, one is read | |
217 | from under point. | |
218 | STREAM is the list of tokens to complete from." | |
219 | (semantic-read-symbol | |
220 | prompt default | |
221 | (or (semantic-find-tags-by-class | |
222 | 'variable (or stream (current-buffer))) | |
223 | (error "No local variables")))) | |
224 | ||
225 | (defun semantic-read-function (prompt &optional default stream) | |
226 | "Read a function name from the user for the current buffer. | |
227 | PROMPT is the prompt to use. | |
228 | Optional arguments: | |
229 | DEFAULT is the default choice. If no default is given, one is read | |
230 | from under point. | |
231 | STREAM is the list of tags to complete from." | |
232 | (semantic-read-symbol | |
233 | prompt default | |
234 | (or (semantic-find-tags-by-class | |
235 | 'function (or stream (current-buffer))) | |
236 | (error "No local functions")))) | |
237 | ||
238 | (defun semantic-read-type (prompt &optional default stream) | |
239 | "Read a type name from the user for the current buffer. | |
240 | PROMPT is the prompt to use. | |
241 | Optional arguments: | |
242 | DEFAULT is the default choice. If no default is given, one is read | |
243 | from under point. | |
244 | STREAM is the list of tags to complete from." | |
245 | (semantic-read-symbol | |
246 | prompt default | |
247 | (or (semantic-find-tags-by-class | |
248 | 'type (or stream (current-buffer))) | |
249 | (error "No local types")))) | |
250 | ||
251 | \f | |
252 | ;;; Interactive Functions for | |
253 | ;; | |
254 | (defun semantic-describe-tag (&optional tag) | |
255 | "Describe TAG in the minibuffer. | |
256 | If TAG is nil, describe the tag under the cursor." | |
257 | (interactive) | |
258 | (if (not tag) (setq tag (semantic-current-tag))) | |
259 | (semantic-fetch-tags) | |
260 | (if tag (message (semantic-format-tag-summarize tag)))) | |
261 | ||
262 | \f | |
263 | ;;; Putting keys on tags. | |
264 | ;; | |
265 | (defun semantic-add-label (label value &optional tag) | |
266 | "Add a LABEL with VALUE on TAG. | |
267 | If TAG is not specified, use the tag at point." | |
268 | (interactive "sLabel: \nXValue (eval): ") | |
269 | (if (not tag) | |
270 | (progn | |
271 | (semantic-fetch-tags) | |
272 | (setq tag (semantic-current-tag)))) | |
273 | (semantic--tag-put-property tag (intern label) value) | |
274 | (message "Added label %s with value %S" label value)) | |
275 | ||
276 | (defun semantic-show-label (label &optional tag) | |
277 | "Show the value of LABEL on TAG. | |
278 | If TAG is not specified, use the tag at point." | |
279 | (interactive "sLabel: ") | |
280 | (if (not tag) | |
281 | (progn | |
282 | (semantic-fetch-tags) | |
283 | (setq tag (semantic-current-tag)))) | |
284 | (message "%s: %S" label (semantic--tag-get-property tag (intern label)))) | |
285 | ||
286 | \f | |
287 | ;;; Hacks | |
288 | ;; | |
289 | ;; Some hacks to help me test these functions | |
290 | (defun semantic-describe-buffer-var-helper (varsym buffer) | |
291 | "Display to standard out the value of VARSYM in BUFFER." | |
292 | (require 'data-debug) | |
293 | (let ((value (save-excursion | |
294 | (set-buffer buffer) | |
295 | (symbol-value varsym)))) | |
296 | (cond | |
297 | ((and (consp value) | |
298 | (< (length value) 10)) | |
299 | ;; Draw the list of things in the list. | |
300 | (princ (format " %s: #<list of %d items>\n" | |
301 | varsym (length value))) | |
302 | (data-debug-insert-stuff-list | |
303 | value " " ) | |
304 | ) | |
305 | (t | |
306 | ;; Else do a one-liner. | |
307 | (data-debug-insert-thing | |
308 | value " " (concat " " (symbol-name varsym) ": ")) | |
309 | )))) | |
310 | ||
311 | (defun semantic-describe-buffer () | |
312 | "Describe the semantic environment for the current buffer." | |
313 | (interactive) | |
314 | (let ((buff (current-buffer)) | |
315 | ) | |
316 | ||
317 | (with-output-to-temp-buffer (help-buffer) | |
318 | (help-setup-xref (list #'semantic-describe-buffer) (interactive-p)) | |
319 | (with-current-buffer standard-output | |
320 | (princ "Semantic Configuration in ") | |
321 | (princ (buffer-name buff)) | |
322 | (princ "\n\n") | |
323 | ||
324 | (princ "Buffer specific configuration items:\n") | |
325 | (let ((vars '(major-mode | |
326 | semantic-case-fold | |
327 | semantic-expand-nonterminal | |
328 | semantic-parser-name | |
329 | semantic-parse-tree-state | |
330 | semantic-lex-analyzer | |
331 | semantic-lex-reset-hooks | |
332 | ))) | |
333 | (dolist (V vars) | |
334 | (semantic-describe-buffer-var-helper V buff))) | |
335 | ||
336 | (princ "\nGeneral configuration items:\n") | |
337 | (let ((vars '(semantic-inhibit-functions | |
29e1a603 CY |
338 | semantic-init-hook |
339 | semantic-init-db-hook | |
a91e32bc CY |
340 | semantic-unmatched-syntax-hook |
341 | semantic--before-fetch-tags-hook | |
342 | semantic-after-toplevel-bovinate-hook | |
343 | semantic-after-toplevel-cache-change-hook | |
344 | semantic-before-toplevel-cache-flush-hook | |
345 | semantic-dump-parse | |
346 | ||
347 | ))) | |
348 | (dolist (V vars) | |
349 | (semantic-describe-buffer-var-helper V buff))) | |
350 | ||
351 | (princ "\n\n") | |
352 | (mode-local-describe-bindings-2 buff) | |
353 | ))) | |
354 | ) | |
355 | ||
356 | (defun semantic-current-tag-interactive (p) | |
357 | "Display the current token. | |
358 | Argument P is the point to search from in the current buffer." | |
359 | (interactive "d") | |
996bc9bf | 360 | (require 'semantic/find) |
a91e32bc CY |
361 | (let ((tok (semantic-brute-find-innermost-tag-by-position |
362 | p (current-buffer)))) | |
363 | (message (mapconcat 'semantic-abbreviate-nonterminal tok ",")) | |
364 | (car tok)) | |
365 | ) | |
366 | ||
367 | (defun semantic-hack-search () | |
368 | "Display info about something under the cursor using generic methods." | |
369 | (interactive) | |
996bc9bf | 370 | (require 'semantic/find) |
a91e32bc CY |
371 | (let ( |
372 | ;(name (thing-at-point 'symbol)) | |
373 | (strm (cdr (semantic-fetch-tags))) | |
374 | (res nil)) | |
375 | ; (if name | |
376 | (setq res | |
377 | ; (semantic-find-nonterminal-by-name name strm) | |
378 | ; (semantic-find-nonterminal-by-type name strm) | |
379 | ; (semantic-recursive-find-nonterminal-by-name name (current-buffer)) | |
380 | (semantic-brute-find-tag-by-position (point) strm) | |
381 | ||
382 | ) | |
383 | ; ) | |
384 | (if res | |
385 | (progn | |
386 | (pop-to-buffer "*SEMANTIC HACK RESULTS*") | |
387 | (require 'pp) | |
388 | (erase-buffer) | |
389 | (insert (pp-to-string res) "\n") | |
390 | (goto-char (point-min)) | |
391 | (shrink-window-if-larger-than-buffer)) | |
392 | (message "nil")))) | |
393 | ||
394 | (defun semantic-assert-valid-token (tok) | |
395 | "Assert that TOK is a valid token." | |
396 | (if (semantic-tag-p tok) | |
397 | (if (semantic-tag-with-position-p tok) | |
398 | (let ((o (semantic-tag-overlay tok))) | |
399 | (if (and (semantic-overlay-p o) | |
400 | (not (semantic-overlay-live-p o))) | |
401 | (let ((debug-on-error t)) | |
402 | (error "Tag %s is invalid!" (semantic-tag-name tok))) | |
403 | ;; else, tag is OK. | |
404 | )) | |
405 | ;; Positionless tags are also ok. | |
406 | ) | |
407 | (let ((debug-on-error t)) | |
408 | (error "Not a semantic tag: %S" tok)))) | |
409 | ||
410 | (defun semantic-sanity-check (&optional cache over notfirst) | |
411 | "Perform a sanity check on the current buffer. | |
412 | The buffer's set of overlays, and those overlays found via the cache | |
413 | are verified against each other. | |
414 | CACHE, and OVER are the semantic cache, and the overlay list. | |
415 | NOTFIRST indicates that this was not the first call in the recursive use." | |
416 | (interactive) | |
417 | (if (and (not cache) (not over) (not notfirst)) | |
418 | (setq cache semantic--buffer-cache | |
419 | over (semantic-overlays-in (point-min) (point-max)))) | |
420 | (while cache | |
421 | (let ((chil (semantic-tag-components-with-overlays (car cache)))) | |
422 | (if (not (memq (semantic-tag-overlay (car cache)) over)) | |
423 | (message "Tag %s not in buffer overlay list." | |
424 | (semantic-format-tag-concise-prototype (car cache)))) | |
425 | (setq over (delq (semantic-tag-overlay (car cache)) over)) | |
426 | (setq over (semantic-sanity-check chil over t)) | |
427 | (setq cache (cdr cache)))) | |
428 | (if (not notfirst) | |
429 | ;; Strip out all overlays which aren't semantic overlays | |
430 | (let ((o nil)) | |
431 | (while over | |
432 | (when (and (semantic-overlay-get (car over) 'semantic) | |
433 | (not (eq (semantic-overlay-get (car over) 'semantic) | |
434 | 'unmatched))) | |
435 | (setq o (cons (car over) o))) | |
436 | (setq over (cdr over))) | |
437 | (message "Remaining overlays: %S" o))) | |
438 | over) | |
439 | ||
af7b5a91 CY |
440 | ;;; Interactive commands (from Senator). |
441 | ||
442 | ;; The Senator library from upstream CEDET is not included in the | |
443 | ;; built-in version of Emacs. The plan is to fold it into the | |
444 | ;; different parts of CEDET and Emacs, so that it works | |
445 | ;; "transparently". Here are some interactive commands based on | |
446 | ;; Senator. | |
447 | ||
29e1a603 CY |
448 | ;; Symbol completion |
449 | ||
af7b5a91 CY |
450 | (defun semantic-find-tag-for-completion (prefix) |
451 | "Find all tags with name starting with PREFIX. | |
452 | This uses `semanticdb' when available." | |
453 | (let (result ctxt) | |
29e1a603 | 454 | ;; Try the Semantic analyzer |
af7b5a91 CY |
455 | (condition-case nil |
456 | (and (featurep 'semantic/analyze) | |
457 | (setq ctxt (semantic-analyze-current-context)) | |
458 | (setq result (semantic-analyze-possible-completions ctxt))) | |
459 | (error nil)) | |
460 | (or result | |
461 | ;; If the analyzer fails, then go into boring completion. | |
462 | (if (and (featurep 'semantic/db) (semanticdb-minor-mode-p)) | |
463 | (semanticdb-fast-strip-find-results | |
464 | (semanticdb-deep-find-tags-for-completion prefix)) | |
465 | (semantic-deep-find-tags-for-completion prefix (current-buffer)))))) | |
466 | ||
467 | (defun semantic-complete-symbol (&optional predicate) | |
468 | "Complete the symbol under point, using Semantic facilities. | |
469 | When called from a program, optional arg PREDICATE is a predicate | |
470 | determining which symbols are considered." | |
471 | (interactive) | |
472 | (let* ((start (car (nth 2 (semantic-ctxt-current-symbol-and-bounds | |
473 | (point))))) | |
474 | (pattern (regexp-quote (buffer-substring start (point)))) | |
475 | collection completion) | |
476 | (when start | |
477 | (if (and semantic--completion-cache | |
478 | (eq (nth 0 semantic--completion-cache) (current-buffer)) | |
479 | (= (nth 1 semantic--completion-cache) start) | |
480 | (save-excursion | |
481 | (goto-char start) | |
482 | (looking-at (nth 3 semantic--completion-cache)))) | |
483 | ;; Use cached value. | |
484 | (setq collection (nthcdr 4 semantic--completion-cache)) | |
485 | ;; Perform new query. | |
486 | (setq collection (semantic-find-tag-for-completion pattern)) | |
487 | (setq semantic--completion-cache | |
488 | (append (list (current-buffer) start 0 pattern) | |
489 | collection)))) | |
490 | (if (null collection) | |
491 | (let ((str (if pattern (format " for \"%s\"" pattern) ""))) | |
492 | (if (window-minibuffer-p (selected-window)) | |
493 | (minibuffer-message (format " [No completions%s]" str)) | |
494 | (message "Can't find completion%s" str))) | |
495 | (setq completion (try-completion pattern collection predicate)) | |
496 | (if (string= pattern completion) | |
497 | (let ((list (all-completions pattern collection predicate))) | |
498 | (setq list (sort list 'string<)) | |
499 | (if (> (length list) 1) | |
500 | (with-output-to-temp-buffer "*Completions*" | |
501 | (display-completion-list list pattern)) | |
502 | ;; Bury any out-of-date completions buffer. | |
503 | (let ((win (get-buffer-window "*Completions*" 0))) | |
504 | (if win (with-selected-window win (bury-buffer)))))) | |
505 | ;; Exact match | |
506 | (delete-region start (point)) | |
507 | (insert completion) | |
508 | ;; Bury any out-of-date completions buffer. | |
509 | (let ((win (get-buffer-window "*Completions*" 0))) | |
510 | (if win (with-selected-window win (bury-buffer)))))))) | |
511 | ||
a91e32bc CY |
512 | (provide 'semantic/util) |
513 | ||
514 | ;;; Minor modes | |
515 | ;; | |
516 | (require 'semantic/util-modes) | |
517 | ||
55b522b2 | 518 | ;;; semantic/util.el ends here |