Convert consecutive FSF copyright years to ranges.
[bpt/emacs.git] / lisp / progmodes / ebnf-abn.el
CommitLineData
da8f925e
VJL
1;;; ebnf-abn.el --- parser for ABNF (Augmented BNF)
2
73b0cd50 3;; Copyright (C) 2001-2011
d7a0267c 4;; Free Software Foundation, Inc.
da8f925e
VJL
5
6;; Author: Vinicius Jose Latorre <viniciusjl@ig.com.br>
7;; Maintainer: Vinicius Jose Latorre <viniciusjl@ig.com.br>
da8f925e 8;; Keywords: wp, ebnf, PostScript
3ced5caa 9;; Version: 1.2
bd78fa1d 10;; Package: ebnf2ps
da8f925e
VJL
11
12;; This file is part of GNU Emacs.
13
b1fc2b50 14;; GNU Emacs is free software: you can redistribute it and/or modify
da8f925e 15;; it under the terms of the GNU General Public License as published by
b1fc2b50
GM
16;; the Free Software Foundation, either version 3 of the License, or
17;; (at your option) any later version.
da8f925e
VJL
18
19;; GNU Emacs is distributed in the hope that it will be useful,
20;; but WITHOUT ANY WARRANTY; without even the implied warranty of
21;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22;; GNU General Public License for more details.
23
24;; You should have received a copy of the GNU General Public License
b1fc2b50 25;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
da8f925e
VJL
26
27;;; Commentary:
28
29;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
30;;
31;;
32;; This is part of ebnf2ps package.
33;;
34;; This package defines a parser for ABNF (Augmented BNF).
35;;
36;; See ebnf2ps.el for documentation.
37;;
38;;
39;; ABNF Syntax
40;; -----------
41;;
42;; See the URL:
7fd08a0a
VJL
43;; `http://www.ietf.org/rfc/rfc2234.txt'
44;; or
da8f925e
VJL
45;; `http://www.faqs.org/rfcs/rfc2234.html'
46;; or
47;; `http://www.rnp.br/ietf/rfc/rfc2234.txt'
48;; ("Augmented BNF for Syntax Specifications: ABNF").
49;;
50;;
51;; rulelist = 1*( rule / (*c-wsp c-nl) )
52;;
53;; rule = rulename defined-as elements c-nl
54;; ; continues if next line starts with white space
55;;
56;; rulename = ALPHA *(ALPHA / DIGIT / "-")
57;;
58;; defined-as = *c-wsp ("=" / "=/") *c-wsp
59;; ; basic rules definition and incremental
60;; ; alternatives
61;;
62;; elements = alternation *c-wsp
63;;
64;; c-wsp = WSP / (c-nl WSP)
65;;
66;; c-nl = comment / CRLF
67;; ; comment or newline
68;;
69;; comment = ";" *(WSP / VCHAR) CRLF
70;;
71;; alternation = concatenation
72;; *(*c-wsp "/" *c-wsp concatenation)
73;;
74;; concatenation = repetition *(1*c-wsp repetition)
75;;
76;; repetition = [repeat] element
77;;
78;; repeat = 1*DIGIT / (*DIGIT "*" *DIGIT)
79;;
80;; element = rulename / group / option /
81;; char-val / num-val / prose-val
82;;
83;; group = "(" *c-wsp alternation *c-wsp ")"
84;;
85;; option = "[" *c-wsp alternation *c-wsp "]"
86;;
87;; char-val = DQUOTE *(%x20-21 / %x23-7E) DQUOTE
88;; ; quoted string of SP and VCHAR without DQUOTE
89;;
90;; num-val = "%" (bin-val / dec-val / hex-val)
91;;
92;; bin-val = "b" 1*BIT
93;; [ 1*("." 1*BIT) / ("-" 1*BIT) ]
94;; ; series of concatenated bit values
95;; ; or single ONEOF range
96;;
97;; dec-val = "d" 1*DIGIT
98;; [ 1*("." 1*DIGIT) / ("-" 1*DIGIT) ]
99;;
100;; hex-val = "x" 1*HEXDIG
101;; [ 1*("." 1*HEXDIG) / ("-" 1*HEXDIG) ]
102;;
103;; prose-val = "<" *(%x20-3D / %x3F-7E) ">"
104;; ; bracketed string of SP and VCHAR without
105;; ; angles
106;; ; prose description, to be used as last resort
107;;
108;; ; Core rules -- the coding depends on the system, here is used 7-bit ASCII
109;;
110;; ALPHA = %x41-5A / %x61-7A
111;; ; A-Z / a-z
112;;
113;; BIT = "0" / "1"
114;;
115;; CHAR = %x01-7F
116;; ; any 7-bit US-ASCII character, excluding NUL
117;;
118;; CR = %x0D
119;; ; carriage return
120;;
121;; CRLF = CR LF
122;; ; Internet standard newline
123;;
124;; CTL = %x00-1F / %x7F
125;; ; controls
126;;
127;; DIGIT = %x30-39
128;; ; 0-9
129;;
130;; DQUOTE = %x22
131;; ; " (Double Quote)
132;;
133;; HEXDIG = DIGIT / "A" / "B" / "C" / "D" / "E" / "F"
134;;
135;; HTAB = %x09
136;; ; horizontal tab
137;;
138;; LF = %x0A
139;; ; linefeed
140;;
141;; LWSP = *(WSP / CRLF WSP)
142;; ; linear white space (past newline)
143;;
144;; OCTET = %x00-FF
145;; ; 8 bits of data
146;;
147;; SP = %x20
148;; ; space
149;;
150;; VCHAR = %x21-7E
151;; ; visible (printing) characters
152;;
153;; WSP = SP / HTAB
154;; ; white space
155;;
156;;
157;; NOTES:
158;;
159;; 1. Rules name and terminal strings are case INSENSITIVE.
160;; So, the following rule names are all equals:
161;; Rule-name, rule-Name, rule-name, RULE-NAME
162;; Also, the following strings are equals:
163;; "abc", "ABC", "aBc", "Abc", "aBC", etc.
164;;
165;; 2. To have a case SENSITIVE string, use the character notation.
166;; For example, to specify the lowercase string "abc", use:
167;; %d97.98.99
168;;
169;; 3. There are no implicit spaces between elements, for example, the
170;; following rules:
171;;
172;; foo = %x61 ; a
173;;
174;; bar = %x62 ; b
175;;
176;; mumble = foo bar foo
177;;
178;; Are equivalent to the following rule:
179;;
180;; mumble = %x61.62.61
181;;
182;; If spaces are needed, it should be explicit specified, like:
183;;
184;; spaces = 1*(%x20 / %x09) ; one or more spaces or tabs
185;;
186;; mumble = foo spaces bar spaces foo
187;;
188;; 4. Lines starting with space or tab are considered a continuation line.
189;; For example, the rule:
190;;
191;; rule = foo
192;; bar
193;;
194;; Is equivalent to:
195;;
196;; rule = foo bar
197;;
198;;
199;; Differences Between ABNF And ebnf2ps ABNF
200;; -----------------------------------------
201;;
202;; Besides the characters that ABNF accepts, ebnf2ps ABNF accepts also the
203;; underscore (_) for rule name and european 8-bit accentuated characters (from
204;; \240 to \377) for rule name, string and comment.
205;;
206;;
207;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
208
209;;; Code:
210
211
212(require 'ebnf-otz)
213
214
215(defvar ebnf-abn-lex nil
216 "Value returned by `ebnf-abn-lex' function.")
217
218\f
219;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
220;; Syntactic analyzer
221
222
223;;; rulelist = 1*( rule / (*c-wsp c-nl) )
224
225(defun ebnf-abn-parser (start)
226 "ABNF parser."
227 (let ((total (+ (- ebnf-limit start) 1))
228 (bias (1- start))
229 (origin (point))
230 rule-list token rule)
231 (goto-char start)
232 (setq token (ebnf-abn-lex))
233 (and (eq token 'end-of-input)
234 (error "Invalid ABNF file format"))
9d59cbb0
VJL
235 (and (eq token 'end-of-rule)
236 (setq token (ebnf-abn-lex)))
da8f925e
VJL
237 (while (not (eq token 'end-of-input))
238 (ebnf-message-float
239 "Parsing...%s%%"
240 (/ (* (- (point) bias) 100.0) total))
241 (setq token (ebnf-abn-rule token)
242 rule (cdr token)
243 token (car token))
244 (or (ebnf-add-empty-rule-list rule)
245 (setq rule-list (cons rule rule-list))))
246 (goto-char origin)
247 rule-list))
248
249
250;;; rule = rulename defined-as elements c-nl
251;;; ; continues if next line starts with white space
252;;;
253;;; rulename = ALPHA *(ALPHA / DIGIT / "-")
254;;;
255;;; defined-as = *c-wsp ("=" / "=/") *c-wsp
256;;; ; basic rules definition and incremental
257;;; ; alternatives
258;;;
259;;; elements = alternation *c-wsp
260;;;
261;;; c-wsp = WSP / (c-nl WSP)
262;;;
263;;; c-nl = comment / CRLF
264;;; ; comment or newline
265;;;
266;;; comment = ";" *(WSP / VCHAR) CRLF
267
268
269(defun ebnf-abn-rule (token)
270 (let ((name ebnf-abn-lex)
271 (action ebnf-action)
272 elements)
273 (setq ebnf-action nil)
274 (or (eq token 'non-terminal)
275 (error "Invalid rule name"))
276 (setq token (ebnf-abn-lex))
277 (or (memq token '(equal incremental-alternative))
278 (error "Invalid rule: missing `=' or `=/'"))
279 (and (eq token 'incremental-alternative)
280 (setq name (concat name " =/")))
281 (setq elements (ebnf-abn-alternation))
282 (or (memq (car elements) '(end-of-rule end-of-input))
283 (error "Invalid rule: there is no end of rule"))
284 (setq elements (cdr elements))
285 (ebnf-eps-add-production name)
286 (cons (ebnf-abn-lex)
287 (ebnf-make-production name elements action))))
288
289
290;;; alternation = concatenation
291;;; *(*c-wsp "/" *c-wsp concatenation)
292
293
294(defun ebnf-abn-alternation ()
295 (let (body concatenation)
296 (while (eq (car (setq concatenation
297 (ebnf-abn-concatenation (ebnf-abn-lex))))
298 'alternative)
299 (setq body (cons (cdr concatenation) body)))
300 (ebnf-token-alternative body concatenation)))
301
302
303;;; concatenation = repetition *(1*c-wsp repetition)
304
305
306(defun ebnf-abn-concatenation (token)
307 (let ((term (ebnf-abn-repetition token))
308 seq)
309 (or (setq token (car term)
310 term (cdr term))
311 (error "Empty element"))
312 (setq seq (cons term seq))
313 (while (setq term (ebnf-abn-repetition token)
314 token (car term)
315 term (cdr term))
316 (setq seq (cons term seq)))
317 (cons token
6411a60a 318 (ebnf-token-sequence seq))))
da8f925e
VJL
319
320
321;;; repetition = [repeat] element
322;;;
323;;; repeat = 1*DIGIT / (*DIGIT "*" *DIGIT)
324
325
326(defun ebnf-abn-repetition (token)
327 (let (lower upper)
328 ;; INTEGER [ "*" [ INTEGER ] ]
329 (when (eq token 'integer)
330 (setq lower ebnf-abn-lex
331 token (ebnf-abn-lex))
332 (or (eq token 'repeat)
333 (setq upper lower)))
334 ;; "*" [ INTEGER ]
335 (when (eq token 'repeat)
336 ;; only * ==> lower & upper are empty string
337 (or lower
338 (setq lower ""
339 upper ""))
340 (when (eq (setq token (ebnf-abn-lex)) 'integer)
341 (setq upper ebnf-abn-lex
342 token (ebnf-abn-lex))))
343 (let ((element (ebnf-abn-element token)))
344 (cond
345 ;; there is a repetition
346 (lower
347 (or element
348 (error "Missing element repetition"))
349 (setq token (ebnf-abn-lex))
350 (cond
351 ;; one or more
352 ((and (string= lower "1") (null upper))
353 (cons token (ebnf-make-one-or-more element)))
354 ;; zero or more
355 ((or (and (string= lower "0") (null upper))
356 (and (string= lower "") (string= upper "")))
357 (cons token (ebnf-make-zero-or-more element)))
358 ;; real repetition
359 (t
360 (ebnf-token-repeat lower (cons token element) upper))))
361 ;; there is an element
362 (element
363 (cons (ebnf-abn-lex) element))
364 ;; something that caller has to deal
365 (t
366 (cons token nil))))))
367
368
369;;; element = rulename / group / option /
370;;; char-val / num-val / prose-val
371;;;
372;;; group = "(" *c-wsp alternation *c-wsp ")"
373;;;
374;;; option = "[" *c-wsp alternation *c-wsp "]"
375;;;
376;;; char-val = DQUOTE *(%x20-21 / %x23-7E) DQUOTE
377;;; ; quoted string of SP and VCHAR without DQUOTE
378;;;
379;;; num-val = "%" (bin-val / dec-val / hex-val)
380;;;
381;;; bin-val = "b" 1*BIT
382;;; [ 1*("." 1*BIT) / ("-" 1*BIT) ]
383;;; ; series of concatenated bit values
384;;; ; or single ONEOF range
385;;;
386;;; dec-val = "d" 1*DIGIT
387;;; [ 1*("." 1*DIGIT) / ("-" 1*DIGIT) ]
388;;;
389;;; hex-val = "x" 1*HEXDIG
390;;; [ 1*("." 1*HEXDIG) / ("-" 1*HEXDIG) ]
391;;;
392;;; prose-val = "<" *(%x20-3D / %x3F-7E) ">"
393;;; ; bracketed string of SP and VCHAR without
394;;; ; angles
395;;; ; prose description, to be used as last resort
396
397
398(defun ebnf-abn-element (token)
399 (cond
400 ;; terminal
401 ((eq token 'terminal)
402 (ebnf-make-terminal ebnf-abn-lex))
403 ;; non-terminal
404 ((eq token 'non-terminal)
405 (ebnf-make-non-terminal ebnf-abn-lex))
406 ;; group
407 ((eq token 'begin-group)
408 (let ((body (ebnf-abn-alternation)))
409 (or (eq (car body) 'end-group)
410 (error "Missing `)'"))
411 (cdr body)))
412 ;; optional
413 ((eq token 'begin-optional)
414 (let ((body (ebnf-abn-alternation)))
415 (or (eq (car body) 'end-optional)
416 (error "Missing `]'"))
417 (ebnf-token-optional (cdr body))))
418 ;; no element
419 (t
420 nil)
421 ))
422
423\f
424;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
425;; Lexical analyzer
426
427
428(defconst ebnf-abn-token-table (make-vector 256 'error)
429 "Vector used to map characters to a lexical token.")
430
431
432(defun ebnf-abn-initialize ()
433 "Initialize EBNF token table."
434 ;; control character & control 8-bit character are set to `error'
435 (let ((char ?\060))
436 ;; digits: 0-9
437 (while (< char ?\072)
438 (aset ebnf-abn-token-table char 'integer)
439 (setq char (1+ char)))
440 ;; printable character: A-Z
441 (setq char ?\101)
442 (while (< char ?\133)
443 (aset ebnf-abn-token-table char 'non-terminal)
444 (setq char (1+ char)))
445 ;; printable character: a-z
446 (setq char ?\141)
447 (while (< char ?\173)
448 (aset ebnf-abn-token-table char 'non-terminal)
449 (setq char (1+ char)))
450 ;; European 8-bit accentuated characters:
451 (setq char ?\240)
452 (while (< char ?\400)
453 (aset ebnf-abn-token-table char 'non-terminal)
454 (setq char (1+ char)))
455 ;; Override end of line characters:
456 (aset ebnf-abn-token-table ?\n 'end-of-rule) ; [NL] linefeed
457 (aset ebnf-abn-token-table ?\r 'end-of-rule) ; [CR] carriage return
458 ;; Override space characters:
459 (aset ebnf-abn-token-table ?\013 'space) ; [VT] vertical tab
460 (aset ebnf-abn-token-table ?\t 'space) ; [HT] horizontal tab
461 (aset ebnf-abn-token-table ?\ 'space) ; [SP] space
462 ;; Override form feed character:
463 (aset ebnf-abn-token-table ?\f 'form-feed) ; [FF] form feed
464 ;; Override other lexical characters:
465 (aset ebnf-abn-token-table ?< 'non-terminal)
466 (aset ebnf-abn-token-table ?% 'terminal)
467 (aset ebnf-abn-token-table ?\" 'terminal)
468 (aset ebnf-abn-token-table ?\( 'begin-group)
469 (aset ebnf-abn-token-table ?\) 'end-group)
470 (aset ebnf-abn-token-table ?* 'repeat)
471 (aset ebnf-abn-token-table ?= 'equal)
472 (aset ebnf-abn-token-table ?\[ 'begin-optional)
473 (aset ebnf-abn-token-table ?\] 'end-optional)
474 (aset ebnf-abn-token-table ?/ 'alternative)
475 ;; Override comment character:
476 (aset ebnf-abn-token-table ?\; 'comment)))
477
478
479;; replace the range "\240-\377" (see `ebnf-range-regexp').
480(defconst ebnf-abn-non-terminal-chars
481 (ebnf-range-regexp "-_0-9A-Za-z" ?\240 ?\377))
482(defconst ebnf-abn-non-terminal-letter-chars
483 (ebnf-range-regexp "A-Za-z" ?\240 ?\377))
484
485
486(defun ebnf-abn-lex ()
13a93e66 487 "Lexical analyzer for ABNF.
da8f925e
VJL
488
489Return a lexical token.
490
491See documentation for variable `ebnf-abn-lex'."
492 (if (>= (point) ebnf-limit)
493 'end-of-input
494 (let (token)
495 ;; skip spaces and comments
496 (while (if (> (following-char) 255)
497 (progn
498 (setq token 'error)
499 nil)
500 (setq token (aref ebnf-abn-token-table (following-char)))
501 (cond
502 ((eq token 'space)
503 (skip-chars-forward " \013\t" ebnf-limit)
504 (< (point) ebnf-limit))
505 ((eq token 'comment)
506 (ebnf-abn-skip-comment))
507 ((eq token 'form-feed)
508 (forward-char)
509 (setq ebnf-action 'form-feed))
510 ((eq token 'end-of-rule)
511 (ebnf-abn-skip-end-of-rule))
512 (t nil)
513 )))
514 (cond
515 ;; end of input
516 ((>= (point) ebnf-limit)
517 'end-of-input)
518 ;; error
519 ((eq token 'error)
eac9c0ef 520 (error "Invalid character"))
da8f925e
VJL
521 ;; end of rule
522 ((eq token 'end-of-rule)
523 'end-of-rule)
524 ;; integer
525 ((eq token 'integer)
526 (setq ebnf-abn-lex (ebnf-buffer-substring "0-9"))
527 'integer)
528 ;; terminal: "string" or %[bdx]NNN((.NNN)+|-NNN)?
529 ((eq token 'terminal)
530 (setq ebnf-abn-lex
531 (if (= (following-char) ?\")
532 (ebnf-abn-string)
533 (ebnf-abn-character)))
534 'terminal)
535 ;; non-terminal: NAME or <NAME>
536 ((eq token 'non-terminal)
537 (let ((prose-p (= (following-char) ?<)))
538 (when prose-p
539 (forward-char)
540 (or (looking-at ebnf-abn-non-terminal-letter-chars)
541 (error "Invalid prose value")))
542 (setq ebnf-abn-lex
543 (ebnf-buffer-substring ebnf-abn-non-terminal-chars))
544 (when prose-p
545 (or (= (following-char) ?>)
546 (error "Invalid prose value"))
547 (setq ebnf-abn-lex (concat "<" ebnf-abn-lex ">"))))
548 'non-terminal)
549 ;; equal: =, =/
550 ((eq token 'equal)
551 (forward-char)
552 (if (/= (following-char) ?/)
553 'equal
554 (forward-char)
555 'incremental-alternative))
556 ;; miscellaneous: (, ), [, ], /, *
557 (t
558 (forward-char)
559 token)
560 ))))
561
562
563(defun ebnf-abn-skip-end-of-rule ()
564 (let (eor-p)
565 (while (progn
566 ;; end of rule ==> 2 or more consecutive end of lines
567 (setq eor-p (or (> (skip-chars-forward "\r\n" ebnf-limit) 1)
568 eor-p))
569 ;; skip spaces
570 (skip-chars-forward " \013\t" ebnf-limit)
571 ;; skip comments
572 (and (= (following-char) ?\;)
573 (ebnf-abn-skip-comment))))
574 (not eor-p)))
575
576
577;; replace the range "\177-\237" (see `ebnf-range-regexp').
578(defconst ebnf-abn-comment-chars
579 (ebnf-range-regexp "^\n\000-\010\016-\037" ?\177 ?\237))
580
581
582(defun ebnf-abn-skip-comment ()
583 (forward-char)
584 (cond
585 ;; open EPS file
586 ((and ebnf-eps-executing (= (following-char) ?\[))
587 (ebnf-eps-add-context (ebnf-abn-eps-filename)))
588 ;; close EPS file
589 ((and ebnf-eps-executing (= (following-char) ?\]))
590 (ebnf-eps-remove-context (ebnf-abn-eps-filename)))
3ced5caa
VJL
591 ;; EPS header
592 ((and ebnf-eps-executing (= (following-char) ?H))
593 (ebnf-eps-header-comment (ebnf-abn-eps-filename)))
594 ;; EPS footer
595 ((and ebnf-eps-executing (= (following-char) ?F))
596 (ebnf-eps-footer-comment (ebnf-abn-eps-filename)))
da8f925e
VJL
597 ;; any other action in comment
598 (t
599 (setq ebnf-action (aref ebnf-comment-table (following-char)))
600 (skip-chars-forward ebnf-abn-comment-chars ebnf-limit))
601 )
602 ;; check for a valid end of comment
603 (cond ((>= (point) ebnf-limit)
604 nil)
605 ((= (following-char) ?\n)
606 t)
607 (t
eac9c0ef 608 (error "Invalid character"))
da8f925e
VJL
609 ))
610
611
612(defun ebnf-abn-eps-filename ()
613 (forward-char)
614 (ebnf-buffer-substring ebnf-abn-comment-chars))
615
616
617;; replace the range "\240-\377" (see `ebnf-range-regexp').
618(defconst ebnf-abn-string-chars
619 (ebnf-range-regexp " -!#-~" ?\240 ?\377))
620
621
622(defun ebnf-abn-string ()
623 (buffer-substring-no-properties
624 (progn
625 (forward-char)
626 (point))
627 (progn
628 (skip-chars-forward ebnf-abn-string-chars ebnf-limit)
629 (or (= (following-char) ?\")
630 (error "Missing `\"'"))
631 (prog1
632 (point)
633 (forward-char)))))
634
635
636(defun ebnf-abn-character ()
637 ;; %[bdx]NNN((-NNN)|(.NNN)+)?
638 (buffer-substring-no-properties
639 (point)
640 (progn
641 (forward-char)
642 (let* ((char (following-char))
643 (chars (cond ((or (= char ?B) (= char ?b)) "01")
644 ((or (= char ?D) (= char ?d)) "0-9")
645 ((or (= char ?X) (= char ?x)) "0-9A-Fa-f")
646 (t (error "Invalid terminal value")))))
647 (forward-char)
648 (or (> (skip-chars-forward chars ebnf-limit) 0)
649 (error "Invalid terminal value"))
650 (if (= (following-char) ?-)
651 (progn
652 (forward-char)
653 (or (> (skip-chars-forward chars ebnf-limit) 0)
654 (error "Invalid terminal value range")))
655 (while (= (following-char) ?.)
656 (forward-char)
657 (or (> (skip-chars-forward chars ebnf-limit) 0)
658 (error "Invalid terminal value")))))
659 (point))))
660
661\f
662;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
663
664
665(provide 'ebnf-abn)
666
da8f925e 667;;; ebnf-abn.el ends here