Add docstrings.
[cl-opossum.git] / pegutils.lisp
blobb1be478aa2b16a3809860d389afd9f345fcb1f61
1 ;;; -*- Mode: Lisp; Syntax: ANSI-Common-Lisp; Base: 10; -*-
2 ;;;
3 ;;; pegutils.lisp --- Utility functions for implementing PEG parsers
5 ;; Copyright (C) 2008 Utz-Uwe Haus <lisp@uuhaus.de>
6 ;; $Id$
7 ;;
8 ;; This code is free software; you can redistribute it and/or modify
9 ;; it under the terms of the version 2.1 of the GNU Lesser General
10 ;; Public License as published by the Free Software Foundation, as
11 ;; clarified by the lisp prequel found in LICENSE.
13 ;; This code is distributed in the hope that it will be useful, but
14 ;; without any warranty; without even the implied warranty of
15 ;; merchantability or fitness for a particular purpose. See the GNU
16 ;; Lesser General Public License for more details.
18 ;; Version 2.1 of the GNU Lesser General Public License is in the file
19 ;; LICENSE that was distributed with this file. If it is not
20 ;; present, you can access it from
21 ;; http://www.gnu.org/copyleft/lgpl.txt (until superseded by a
22 ;; newer version) or write to the Free Software Foundation, Inc., 59
23 ;; Temple Place, Suite 330, Boston, MA 02111-1307 USA
25 ;; Commentary:
27 ;; Some code here is inspired by the metapeg library of John Leuner
29 ;;; Code:
32 (eval-when (:load-toplevel :compile-toplevel :execute)
33 (declaim (optimize (speed 0) (safety 3) (debug 3))))
35 (in-package #:opossum)
37 (defparameter *trace* nil "If non-nil, do extensive tracing of the parser functions.")
40 (defclass context ()
41 (;; these slots are copied when cloning a context for recursion
42 (input :accessor input :initarg :input :initform nil
43 :type 'string
44 :documentation "The input string being parsed.")
45 (dst-package :accessor dst-package :initarg :dst-package :initform nil
46 :type 'package
47 :documentation "The package into which symbols generated during the parse are interned.")
48 ;; these slots are shared by all cloned copies of a context -- use only STORE-ACTION to guarantee consistency
49 (actions :accessor actions :initarg :actions :initform (make-list 1 :initial-element NIL)
50 :type 'list
51 :documentation "The list of actions accumulated during the parse.")
52 (action-counter :accessor action-counter :initarg :action-counter :initform '(0)
53 :type '(cons (integer 0) null)
54 :documentation "The counter of actions.")
55 ;; these slots are what make a context unique
56 (parent :accessor parent :initarg :parent :initform nil
57 :documentation "Parent context of this context.")
58 (rule :accessor rule :initarg :rule :initform nil
59 :documentation "Rule name in this context.")
60 (children :accessor children :initform nil)
61 (value :accessor value :initarg :value :initform nil
62 :documentation "Accumulated value after successful matching of rule in this context.")
63 (start-index :accessor start-index :initarg :start-index :initform nil
64 :documentation "Position in INPUT where this context starts.")
65 (end-index :accessor end-index :initarg :end-index :initform nil
66 :documentation "Position in INPUT where this context's match ends.")
67 (depth :accessor depth :initarg :depth :initform 0
68 :documentation "How deep in the tree is this context?"))
69 (:documentation "A parser context object."))
71 (defmethod print-object ((obj context) stream)
72 (print-unreadable-object (obj stream :type T :identity NIL)
73 (format stream "rule ~A (~S) value ~S (~D:~D)"
74 (rule obj) (children obj) (value obj) (start-index obj) (end-index obj))))
76 (defmethod store-action ((ctx context) action)
77 "Store ACTION in context CTX."
78 (let ((a (actions ctx)))
79 (rplacd a (cons (car a) (cdr a)))
80 (rplaca a action)))
82 (defvar *context* nil "The current parser context.")
84 (defun clone-ctx (ctx rule)
85 "Create clone context of CTX for rule RULE."
86 (make-instance 'context
87 :input (input ctx)
88 :dst-package (dst-package ctx)
89 :actions (actions ctx)
90 :action-counter (action-counter ctx)
91 :parent ctx
92 :rule rule
93 :start-index (end-index ctx)
94 :depth (1+ (depth ctx))))
96 (defun ctx-failed-p (ctx)
97 "Check whether CTX failed to match."
98 (null (end-index ctx)))
100 (defun succeed (ctx value start-index end-index)
101 "Mark CTX as successful: set VALUE and matched region START-INDEX:END-INDEX."
102 (setf (value ctx) value)
103 (setf (start-index ctx) start-index)
104 (setf (end-index ctx) end-index)
105 ;; (when *trace*
106 ;; (format *trace-output* "Matched: ~A (~D:~D)~%"
107 ;; (rule ctx) (start-index ctx) (end-index ctx)))
108 ctx)
110 (defun fail ()
111 "Return a failure context generate from *CONTEXT*."
112 (let ((ctx (make-instance 'context
113 :input (input *context*)
114 :rule ':fail
115 :value (rule *context*)
116 :start-index (start-index *context*)
117 :end-index (end-index *context*)
118 ;; probably some of these copies can be saved
119 :dst-package (dst-package *context*)
120 :actions (actions *context*)
121 :action-counter (action-counter *context*)
122 :depth (1+ (depth *context*)))))
123 ;; (when *trace*
124 ;; (format *trace-output* "(failed: ~A ~A ~A)~%"
125 ;; (value ctx) (start-index ctx) (end-index ctx)))
126 ctx))
130 (defun make-name (rule-string)
131 "Create a symbol suitable for naming the parser function for rule RULE-STRING."
132 (intern (concatenate 'string "parse-" rule-string)
133 (dst-package *context*)))
135 (defun make-action-name (&key ctx)
136 "Return a symbol suitable to name the next action in the current *CONTEXT*."
137 (incf (car (action-counter *context*)))
138 (let ((aname (if ctx
139 (format nil "opossum-action-~D-srcpos-~D-~D"
140 (car (action-counter *context*))
141 (start-index ctx)
142 (end-index ctx))
143 (format nil "opossum-action-~D"
144 (car (action-counter *context*)))) ))
145 (intern aname (dst-package *context*))))
147 (defun char-list-to-string (char-list)
148 (coerce char-list 'string))
150 (defmacro build-parser-function (name parser)
151 "Return a function of 1 argument, the offset in *CONTEXT*, parsing using the given PARSER."
152 `(lambda (offset)
153 ,(format nil "Parse a ~A at the given OFFSET." name)
154 (let ((indent (depth *context*)))
155 (when *trace* (format *trace-output* "~vTTrying to parse a ~A at pos ~D~%" indent ,name offset))
156 (let* ((*context* (clone-ctx *context* ,name))
157 (result (funcall ,parser offset)))
158 (unless result
159 (error "Parser function ~A did not return a value" ,parser))
160 (if (ctx-failed-p result)
161 (progn
162 (when *trace* (format *trace-output* "~vT... no ~A at pos ~D~%" indent ,name offset))
163 (fail))
164 (progn
165 (when *trace* (format *trace-output* "~vT... found ~A at ~D:~D~%"
166 indent
167 ,name (start-index result) (end-index result)))
168 (succeed *context* (value result) (start-index result) (end-index result))))))))
172 (defun match-string (string)
173 "Return a function of 1 argument, the offset in *CONTEXT*, that tries to match STRING at that position."
174 #'(lambda (offset)
175 (let ((input (input *context*))
176 (len (length string)))
177 (if (and (>= (length input) (+ offset len))
178 (string= string input :start2 offset :end2 (+ offset len)))
179 (succeed (clone-ctx *context* 'opossum-string) string offset (+ offset (length string)))
180 (fail)))))
182 (defun match-char (char-list)
183 #'(lambda (offset)
184 (let ((input (input *context*)))
185 ;; (when *trace*
186 ;; (format *trace-output* "match-char: looking for one of `~{~A~}'~%" char-list))
187 (if (and (> (length input) offset)
188 (member (char input offset)
189 char-list :test #'char=))
190 (succeed (clone-ctx *context* 'opossum-char) (char input offset) offset (+ offset 1))
191 (fail)))))
193 (defun match-octal-char-code (i1 i2 i3)
194 "Compare the character given by i3 + 8*i2 + 64*i1 to the next input character."
195 (let ((c (+ i3 (* 8 i2) (* 64 i1))))
196 #'(lambda (offset)
197 (let ((input (input *context*)))
198 ;; (when *trace*
199 ;; (format *trace-output* "match-octal-char-code: looking for ~D~%" c))
200 (if (and (> (length input) offset)
201 (= (char-int (char input offset)) c))
202 (succeed (clone-ctx *context* 'opossum-char) (char input offset) offset (+ offset 1))
203 (fail))))))
205 (defun match-char-range (lower-char upper-char)
206 "Match characters in the range between LOWER-CHAR and UPPER-CHAR (inclusive) as decided by CL:CHAR-CODE."
207 #'(lambda (offset)
208 (let ((input (input *context*)))
209 ;; (when *trace*
210 ;; (format *trace-output* "match-char-range: looking for ~A-~A~%" lower-char upper-char))
211 (if (and (> (length input) offset)
212 (let ((x (char-code (char input offset))))
213 (and (>= x (char-code lower-char))
214 (<= x (char-code upper-char)))))
215 (succeed (clone-ctx *context* 'opossum-char-range) (char input offset) offset (+ offset 1))
216 (fail)))))
218 (defun match-any-char (&optional ignored)
219 (declare (ignore ignored))
220 #'(lambda (offset)
221 "Match any character at OFFSET, fail only on EOF."
222 (let ((input (input *context*)))
223 ;; (when *trace*
224 ;; (format *trace-output* "match-any-char~%"))
225 (if (< (1+ offset) (length input))
226 (succeed (clone-ctx *context* 'opossum-anychar) (char input offset) offset (+ offset 1))
227 (fail)))))
229 (defun match-char-class (char-class)
230 "Regexp matching of next input character against [CHAR-CLASS] using cl-ppcre:scan."
231 (declare (type string char-class))
232 ;; FIXME: could use a pre-computed scanner
233 (let ((cc (format nil "[~A]" char-class)))
234 #'(lambda (offset)
235 "Match next character at OFFSET against the characters in CHAR-CLASS."
236 (let ((input (input *context*)))
237 ;; (when *trace*
238 ;; (format *trace-output* "match-char-class on ~A~%"))
239 (if (and (< (1+ offset) (length input))
240 (let ((c (char input offset)))
241 (cl-ppcre:scan cc (make-string 1 :initial-element c))))
242 (succeed (clone-ctx *context* 'opossum-charclass)
243 (char input offset) offset (+ offset 1))
244 (fail))))))
246 (defun fix-escape-sequences (char-list)
247 "Iterate over the list CHAR-LIST, glueing adjacent #\\ #\n and #\\ #\t chars into
248 #\Newline and #\Tab."
249 (cond
250 ((null char-list) char-list)
251 ((null (cdr char-list)) char-list)
253 (loop :with drop := nil
254 :for (c1 c2) :on char-list
255 :if drop
256 :do (setf drop nil)
257 :else
258 :collect (if (char= c1 #\\)
259 (case c2
260 ((#\n) (setf drop T) #\Newline)
261 ((#\t) (setf drop T) #\Tab)
262 ((#\r) (setf drop T) #\Linefeed)
263 (otherwise c1))
265 :end))))
268 ;; parsing combinator functions cribbed from libmetapeg
270 (defun either (&rest parsers)
271 "Produce a function that tries each of the functions in PARSERS sequentially until one succeeds and
272 returns the result of that function, or a failure context if none succeeded."
273 #'(lambda (offset)
274 (let ((*context* (clone-ctx *context* 'opossum-either)))
275 ;; (when *trace*
276 ;; (format *trace-output* "either: ~A ~A~%" *context* parsers))
277 (loop :for p :in parsers
278 :as result = (funcall p offset)
279 :when (not (ctx-failed-p result))
280 :return (succeed *context* (value result) offset (end-index result))
281 :finally (return (fail))))))
284 (defun optional (parser)
285 #'(lambda (offset)
286 (let ((*context* (clone-ctx *context* 'opossum-optional)))
287 ;; (when *trace*
288 ;; (format *trace-output* "optional: ~A ~A~%" *context* parser))
289 (let ((result (funcall parser offset)))
290 (if (ctx-failed-p result)
291 (succeed *context* 'optional offset offset)
292 (succeed *context* (value result) offset (end-index result)))))))
294 (defun follow (parser)
295 #'(lambda (offset)
296 (let ((*context* (clone-ctx *context* 'opossum-follow)))
297 ;; (when *trace*
298 ;; (format *trace-output* "follow: ~A ~A~%" *context* parser))
299 (let ((result (funcall parser offset)))
300 (if (ctx-failed-p result)
301 (fail)
302 (succeed *context* (value result)
303 ;; don't consume input
304 offset offset))))))
306 (defun many (parser)
307 #'(lambda (offset)
308 (let ((*context* (clone-ctx *context* 'opossum-many))
309 (start-offset offset)
310 children)
311 ;; (when *trace*
312 ;; (format *trace-output* "many: ~A ~A~%" *context* parser))
313 (loop :as result := (funcall parser offset)
314 :while (not (ctx-failed-p result))
315 :do (progn (push (value result) children)
316 (setf offset (end-index result)))
317 :finally (return (succeed *context* (nreverse children) start-offset offset))))))
320 (defun many1 (parser)
321 #'(lambda (offset)
322 (let* ((*context* (clone-ctx *context* 'opossum-many1))
323 (result (funcall parser offset)))
324 ;; (when *trace*
325 ;; (format *trace-output* "many1: ~A ~A~%" *context* parser))
326 (if (not (ctx-failed-p result))
327 (let ((result2 (funcall (many parser) (end-index result))))
328 (if (end-index result2)
329 (succeed *context* (cons (value result) (value result2)) offset (end-index result2))
330 (succeed *context* (value result) offset (end-index result))))
331 (fail)))))
334 (defun seq (&rest parsers)
335 #'(lambda (offset)
336 (assert (> (length parsers) 0))
337 (let ((*context* (clone-ctx *context* 'opossum-seq))
338 (start-offset offset)
339 child-values
340 child-nodes)
341 ;; (when *trace*
342 ;; (format *trace-output* "seq: ~A ~A~%" *context* parsers))
343 ;; run the parsers
344 (loop :for p :in parsers
345 ;; :do (when *trace* (format *trace-output* " (seq ~A) trying ~A~%" *context* p))
346 :do (cond
347 ((consp p)
348 (push (succeed (clone-ctx *context* 'action) nil offset offset) child-nodes)
349 (push p child-values)
350 (setf (children *context*) (reverse child-nodes)))
352 (let ((result (funcall p offset)))
353 (if (end-index result)
354 (progn
355 (push result child-nodes)
356 (push (value result) child-values)
357 (setf offset (end-index result))
358 (setf (children *context*) (reverse child-nodes)))
359 (return (fail))))))
360 :finally (return (succeed *context* (reverse child-values) start-offset offset))))))
362 (defun negate (parser)
363 #'(lambda (offset)
364 "Return a successful context at OFFSET if PARSER succeeds, without advancing input position."
365 (let ((*context* (clone-ctx *context* 'opossum-negate)))
366 (let ((result (funcall parser offset)))
367 (if (ctx-failed-p result)
368 (succeed *context* 'negate offset offset)
369 (fail))))))
373 (defun read-stream (stream)
374 "Read STREAM and return a string of all its contents."
375 (let ((s ""))
376 (loop :as line := (read-line stream nil nil)
377 :while line
378 :do (setq s (concatenate 'string s line)))
381 (defun read-file (file)
382 "Read FILE and return a string of all its contents."
383 (with-open-file (f file :direction :input)
384 (let ((len (file-length f)))
385 (if len
386 (let ((s (make-string len)))
387 (read-sequence s f)
389 (read-stream f)))))
391 (defun make-default-dst-package (grammarfile)
392 (make-package (gensym "opossum-parser")
393 :documentation (format T "Opossum parser for grammar ~A" grammarfile)))
395 (defun get-iso-time ()
396 "Return a string in ISO format for the current time"
397 (multiple-value-bind (second minute hour date month year day daylight-p zone)
398 (get-decoded-time)
399 (declare (ignore day))
400 (format nil "~4D-~2,'0D-~2,'0D-~2,'0D:~2,'0D:~2,'0D (UCT~@D)"
401 year month date
402 hour minute second
403 (if daylight-p (1+ (- zone)) (- zone)))))
405 (defun cleanup-action-code (code)
406 "Remove trailing newlines so that the string CODE can be printed nicely."
407 (subseq code 0
408 (when (char= #\Newline (char code (1- (length code))))
409 (1+ (position #\Newline code :from-end T :test #'char/=)))))
411 (defmacro checking-parse (grammarfile parse-file-fun)
412 (let ((res (gensym "resultctx")))
413 `(let ((,res (funcall ,parse-file-fun ,grammarfile *package*)))
414 (cond
415 ((ctx-failed-p ,res)
416 (format *error-output* "Failed to parse PEG grammar ~A~%" ,grammarfile)
417 (error "Parsing ~A failed: ~A" ,grammarfile ,res))
418 ((< (end-index ,res) (length (input ,res)))
419 (format *error-output* "Parsed only ~D characters of grammar ~A~%" (end-index ,res) ,grammarfile)
420 ,res)
421 (T ,res)))))
423 (defun generate-parser-file (grammarfile dst-package dst-file &key start-rule (parse-file-fun #'opossum:parse-file))
424 "Create lisp code in DST-FILE that can be loaded to yield functions to parse using GRAMMARFILE in DST-PACKAGE.
425 DST-PACKAGE will contain the 3 functions PARSE-STRING, PARSE-FILE and PARSE-STREAM as exported entrypoints."
426 (let* ((*package* dst-package)
427 (result (checking-parse grammarfile parse-file-fun))
428 ;; FIXME: check for complete parse
429 (*context* result) ;; routines in pegutils.lisp expect *context* to be bound properly
430 (dpkg (intern (package-name dst-package) :keyword)))
431 (let ((forms (transform (value result)))
432 (actions (actions result)))
433 (with-open-file (s dst-file :direction :output :if-exists :supersede)
434 (let ((*print-readably* T)
435 (*print-pretty* T)
436 (*print-circle* NIL))
437 (format s ";; This is a Common Lisp peg parser automatically generated by OPOSSUM -*- mode:lisp -*-~%")
438 (format s ";; generated from ~A on ~A~%" grammarfile (get-iso-time))
439 (prin1 `(eval-when (:load-toplevel :compile-toplevel :execute)
440 (declaim (optimize (speed 0) (safety 3) (debug 3))))
442 (terpri s)
443 (prin1 `(defpackage ,dpkg
444 (:use :cl :opossum)
445 (:export :parse-string :parse-file :parse-stream :*trace*))
447 (terpri s)
448 (prin1 `(in-package ,dpkg) s) (terpri s)
449 ;; First form is taken to be the start rule
450 (let ((entryrule (or (and start-rule (make-name start-rule))
451 (and forms (cadr (first forms))))))
452 (if (not entryrule)
453 (format *error-output* "Cannot find entry rule for parser")
454 (progn
455 (when *trace*
456 (format *trace-output* "Inserting definitions for parser entry points through ~A~%"
457 entryrule))
458 (terpri s)
459 (prin1 `(defun parse-string (,(intern :s dst-package) dst-package)
460 ,(format nil "Parse S using grammar ~A starting at ~A" grammarfile entryrule)
461 (let ((*context* (make-instance 'opossum:context
462 :dst-package dst-package
463 :input ,(intern :s dst-package))))
464 (funcall (,entryrule) 0)))
466 (terpri s)
467 (prin1 `(defun parse-file (,(intern :f dst-package) dst-package)
468 ,(format nil "Parse file F using grammar ~A starting at ~A" grammarfile entryrule)
469 (parse-string (opossum:read-file ,(intern :f dst-package)) dst-package))
471 (terpri s)
472 (prin1 `(defun parse-stream (,(intern :stream dst-package) dst-package)
473 ,(format nil "Parse stream F using grammar ~A starting at ~A" grammarfile entryrule)
474 (parse-string (opossum:read-stream ,(intern :stream dst-package)) dst-package))
476 (fresh-line s))))
477 (loop :for aform :in forms
478 :do (when *trace* (format *trace-output* "Inserting form ~A~%" aform))
479 :do (terpri s)
480 :do (prin1 aform s)
481 :do (fresh-line s))
482 (terpri s)
483 (prin1
484 `(defparameter ,(intern "*trace*" dst-package) nil
485 "When non-nil, the generated parser function log to cl:*trace-output*.")
487 (terpri s)
489 (loop :for (sym code) :in actions
490 :when sym ;; the final action is named NIL because we push a
491 ;; NIL ahead of us in store-actions
492 :do (when *trace* (format *trace-output* "Inserting defun for ~A~%" sym))
493 :and :do (format s "~%(defun ~S (data)~% (declare (ignorable data) (type list data))~% ~A)~%"
494 sym (cleanup-action-code code))))))))
496 (defun generate-parser-package (grammarfile &key (dst-package (make-package (gensym "opossum-parser-")))
497 start-rule (parse-file-fun #'opossum:parse-file))
498 "Create functions to parse using GRAMMARFILE in DST-PACKAGE, starting ar rule named HEAD.
499 DST-PACKAGE will contain the 3 functions PARSE-STRING, PARSE-FILE and PARSE-STREAM as exported entrypoints."
500 (let* ((*package* dst-package)
501 (result (checking-parse grammarfile parse-file-fun))
502 ;; FIXME: check for complete parse
503 (*context* result)) ;; routines in pegutils.lisp expect *context* to be bound properly)
504 (let ((forms (transform (value result)))
505 (actions (actions result)))
506 (format *trace-output* "Injecting parser functions into ~A~%" dst-package)
507 (break "~A, ~A" forms actions)
508 (use-package '(:cl :opossum) dst-package)
509 (let ((entryrule (or (and start-rule (make-name start-rule))
510 (and forms (cadr (first forms))))))
511 (if (not entryrule)
512 (format *error-output* "Cannot find entry rule for parser")
513 (progn
514 (when *trace*
515 (format *trace-output* "Inserting definitions for parser entry points through ~A~%"
516 entryrule))
517 (intern (compile 'parse-string
518 `(lambda (,(intern :s dst-package))
519 ,(format nil "Parse S using grammar ~A starting at ~A" grammarfile entryrule)
520 (let ((*context* (make-instance 'opossum:context
521 :dst-package ,dst-package
522 :input ,(intern :s dst-package))))
523 (funcall (,entryrule) 0))))
524 dst-package)
525 (intern (compile 'parse-file
526 `(lambda (,(intern :f dst-package))
527 ,(format nil "Parse file F using grammar ~A starting at ~A" grammarfile entryrule)
528 (parse-string (opossum:read-file ,(intern :f dst-package)))))
529 dst-package)
531 (intern (compile 'parse-stream
532 `(lambda (,(intern :stream dst-package))
533 ,(format nil "Parse stream F using grammar ~A starting at ~A" grammarfile entryrule)
534 (parse-string (opossum:read-stream ,(intern :stream dst-package)))))
535 dst-package))))
536 (intern '*trace* dst-package)
537 (setf (documentation '*trace* 'cl:variable)
538 "When non-nil, the generated parser function log to cl:*trace-output*.")
539 (export '(:parse-string :parse-file :parse-stream :*trace*) dst-package)
541 (loop :for aform :in forms
542 :do (when *trace*
543 (format *trace-output* "Injecting form ~A~%" aform))
544 :do (destructuring-bind (defun-sym name args &rest body)
545 aform
546 (declare (ignore defun-sym))
547 (intern (compile name `(lambda ,args ,@body)) dst-package)))
548 (loop :for (sym code) :in actions
549 :when sym
550 :do (when *trace* (format *trace-output* "Injecting definition for ~A~%" sym))
551 :and :do (intern (compile sym `(lambda (data) (declare (ignorable data)) ,code)) dst-package)))))
555 (defun transform (tree &optional (depth 0))
556 (if (and tree
557 (consp tree))
558 (if (eq (first tree) ':action)
559 (progn
560 (when *trace*
561 (format *trace-output* "~AFound action ~A~%" (make-string depth :initial-element #\Space) tree))
562 tree)
563 (let ((data (mapcar #'(lambda (tr) (transform tr (1+ depth)))
564 tree)))
565 (loop :for el :in data
566 :when (and (listp el)
567 (eq (first el) ':action)
568 (symbolp (third el)))
569 :do (let ((*package* (dst-package *context*))
570 (action (third el)))
571 (when *trace*
572 (format *trace-output* "~&Applying action ~A to ~A~%" action data))
573 (handler-case
574 (return-from transform
575 (funcall (symbol-function action) data))
576 (undefined-function (x)
577 (progn
578 (format *error-output* "missing definition for ~A: ~A~%" action x)
579 ; (break "~A in ~A" action *package*)
580 tree)))))
581 data))
582 tree))