File: formals.lisp

package info (click to toggle)
acl2 8.3dfsg-2
  • links: PTS
  • area: main
  • in suites: bullseye
  • size: 309,408 kB
  • sloc: lisp: 3,311,842; javascript: 22,569; cpp: 9,029; ansic: 7,872; perl: 6,501; xml: 3,838; java: 3,738; makefile: 3,383; ruby: 2,633; sh: 2,489; ml: 763; python: 741; yacc: 721; awk: 260; csh: 186; php: 171; lex: 154; tcl: 49; asm: 23; haskell: 17
file content (461 lines) | stat: -rw-r--r-- 17,991 bytes parent folder | download
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
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
; Standard Utilities Library
; Copyright (C) 2008-2014 Centaur Technology
;
; Contact:
;   Centaur Technology Formal Verification Group
;   7600-C N. Capital of Texas Highway, Suite 300, Austin, TX 78731, USA.
;   http://www.centtech.com/
;
; License: (An MIT/X11-style license)
;
;   Permission is hereby granted, free of charge, to any person obtaining a
;   copy of this software and associated documentation files (the "Software"),
;   to deal in the Software without restriction, including without limitation
;   the rights to use, copy, modify, merge, publish, distribute, sublicense,
;   and/or sell copies of the Software, and to permit persons to whom the
;   Software is furnished to do so, subject to the following conditions:
;
;   The above copyright notice and this permission notice shall be included in
;   all copies or substantial portions of the Software.
;
;   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
;   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
;   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
;   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
;   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
;   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
;   DEALINGS IN THE SOFTWARE.
;
; Original author: Jared Davis <jared@centtech.com>

(in-package "STD")
(include-book "look-up")
(include-book "da-base")
(program)

(defxdoc extended-formals
  :parents (std/util define)
  :short "Extended syntax for function arguments that allows for built-in
guards, documentation, and macro-like keyword/optional arguments."

  :long "<p>Macros like @(see define) accept an extended formals syntax.  This
syntax properly extends the normal syntax for a function's arguments used by
@(see defun), so you can still use a plain list of variable names if you like.
But there are also some more interesting extensions.</p>

<p>Basic examples of some extended formals in a @('define'):</p>

@({
 (define split-up-string
   ((x stringp \"The string to split\")
    (separators (and (consp separators)
                     (character-listp separators))
     \"Letters to split on -- <b>dropped from the result!</b>\")
    &key
    (limit (or (not limit)
               (natp limit))
     \"Where to stop, @('nil') means @('(length x)')\"))
   ...)
})

<p>The general syntax for extended formals is:</p>

@({
  Formals ::= (Formal*                 ; ordinary formals
               [&optional OptFormal*]  ; optional positional formals
               [&key OptFormal*]       ; optional keyword formals
               )

  OptFormal ::= Formal          ; optional formal w/NIL default
              | (Formal 'val)   ; optional formal w/custom default

  Formal  ::= (varname Item*)

  Item    ::= xdoc       ; documentation string
            | guard      ; guard specifier
            | :key val   ; other additional options
})


<h4>@('&key') and @('&optional') arguments</h4>

<p>You can use @('&key') and @('&optional') as in @(see macro-args).  (Other
lambda-list keywords like @('&rest') aren't supported.)  When macros like
@('define') see these keywords, then instead of just producing a @('defun') we
will generate:</p>

<ul>
<li>A function, @('name-fn'),</li>
<li>A wrapper macro, @('name'),</li>
<li>A <see topic='@(url acl2::macro-aliases-table)'>macro alias</see> associating
@('name-fn') with @('name')</li>
</ul>

<p>The default values for these parameters work just like in ordinary macros.
The explicit quote serves to separate these from Items.</p>


<h4>Inline Documentation</h4>

<p>You can optionally describe your formals with some @(see xdoc)
documentation.  This description will be embedded within some generated
@('<dl>/<dt>/<dd>') tags that describe your function's interface.  You can
freely use @(see xdoc::markup) and @(see xdoc::preprocessor) directives.
Typically your descriptions should be short and should not include
document-structuring tags like @('<p>'), @('<ul>'), @('<h3>'), and so
forth.</p>


<h4>Inline Guards</h4>

<p>As a convenient shorthand, the @('guard') may be a single non-keyword
symbol, e.g., in @('split-up-string') above, the guard for @('x') is
@('(stringp x)').  To be more precise:</p>

<ul>
 <li>@('t') and @('nil') are treated literally, and</li>
 <li>Any other symbol @('g') is treated as short for @('(g var)')</li>
</ul>

<p>For more complex guards, you can also write down arbitrary terms, e.g.,
above @('separators') must be a non-empty character list.  We put a few
sensible restrictions here.  We require that a guard term must be at least a
cons to distinguish it from documentation, symbol guards, and keywords.  We
also require that it is not simply a quoted constant.  This ensures that guards
can be distinguished from default values in macro arguments.  For example,
compare:</p>

@({
     &key (x 'atom)     ;; x has no guard, default value 'atom
 vs. &key (x atom)      ;; x has guard (atom x), default value nil
 vs. &key ((x atom) '3) ;; x has guard (atom x), default value 3
})


<h4>Keyword/Value Options</h4>

<p>To make the formals syntax more flexible, other keyword/value options can be
embedded within the formal.</p>

<p>The valid keywords and their interpretations can vary from macro to macro.
For instance, @(see define) doesn't accept any keyword/value options, but @(see
defaggregate) fields have a @(':rule-classes') option.</p>



<h4>Additional Features</h4>

<p>Some other features of extended formals are not evident in their syntax.</p>

<p>We generally expect macros that take extended formals to automatically
recognize @(see acl2::stobj)s and insert appropriate @('(declare (xargs :stobjs
...))') forms.</p>

<p>Future work (not yet implemented): certain guards like @('stringp') and
@('(unsigned-byte-p 32 x)'), are recognized as @(see acl2::type-spec)s and
result in @(see type) declarations for the Lisp compiler.  This may
occasionally improve efficiency.</p>")

; Internal representation for extended formals

(def-primitive-aggregate formal
  (name    ; a legal-variablep, name of this formal
   guard   ; always a term, t for formals with no guard
   doc     ; always a string, "" for formals with no documentation
   opts    ; alist binding keywords to values
   )
  :tag :formal)

(defun formallist-p (x)
  (declare (xargs :guard t))
  (if (atom x)
      t
    (and (formal-p (car x))
         (formallist-p (cdr x)))))

(defun formallist->names (x)
  (declare (xargs :guard (formallist-p x)))
  (if (atom x)
      nil
    (cons (formal->name (car x))
          (formallist->names (cdr x)))))

(defun formallist->guards (x)
  (declare (xargs :guard (formallist-p x)))
  (if (atom x)
      nil
    (cons (formal->guard (car x))
          (formallist->guards (cdr x)))))

(defun formallist-collect-stobjs (x world)
  ;; Filter formals, returning only those formals that are stobjs
  (declare (xargs :guard (and (formallist-p x)
                              (plist-worldp world))))
  (cond ((atom x)
         nil)
        ((var-is-stobj-p (formal->name (car x)) world)
         (cons (car x) (formallist-collect-stobjs (cdr x) world)))
        (t
         (formallist-collect-stobjs (cdr x) world))))



; User-level syntax for extended formals.
;  (we're going to parse the user-level syntax into formal-p structures.)

(defun parse-formal-name
  ;; basically just recognizes valid formals
  (ctx      ; context for error messages
   varname  ; what the user has given as the variable name
   )
  (declare (xargs :guard t))
  (b* ((__function__ 'parse-formal-name)
       ((when (legal-variablep varname))
        varname)
       (fake-arglist (list varname))
       ((when (acl2::arglistp fake-arglist))
        (raise "~x0: formal ~x1: mysterious problem that Jared thinks should ~
                be impossible, please tell him that he is wrong." ctx varname)
        'default-valid-legal-variablep)
       ((mv & reason)
        (acl2::find-first-bad-arg fake-arglist)))
    (raise (concatenate 'string "~x0: formal ~x1 is invalid; it " reason)
           ctx varname)
    'default-valid-legal-variablep))

(defun check-formal-guard
  (ctx      ; context for error messages
   varname  ; name of this formal (for better error reporting)
   item     ; the symbolp the user wrote as a guard
   world)
  (b* ((__function__ 'check-formal-guard)
       (macro-args (getprop item 'acl2::macro-args :bad 'acl2::current-acl2-world world))
       ((unless (eq macro-args :bad))
        ;; The shorthand guard is a macro.  Can't really check anything.
        nil)
       ;; Not a macro.  It had better be a unary function.
       (formals (getprop item 'acl2::formals :bad 'acl2::current-acl2-world world))
       ((when (eq formals :bad))
        (raise "Error in ~x0: the guard for ~x1 is ~x2, but there is no ~
                function or macro named ~x2." ctx varname item))
       ((when (tuplep 1 formals))
        ;; Okay, seems fine.
        nil))
    (raise "Error in ~x0: the guard for ~x1 should take a single argument, ~
            but ~x2 takes ~x3 arguments."
           ctx varname item (len formals))))

(defun parse-formal-item
  ;; parses guard/doc item inside an extended formal
  ;;   (doesn't deal with keyword/value opts)
  (ctx      ; context for error messages
   varname  ; name of this formal
   item     ; the actual thing we're parsing
   guards   ; accumulator for guards (for this formal only)
   docs     ; accumulator for docs (for this formal only)
   world
   )
  "Returns (mv guards docs)"
  (declare (xargs :guard (legal-variablep varname)))
  (b* ((__function__ 'parse-formal-item)
       ((when (booleanp item))
        (mv (cons item guards) docs))
       ((when (symbolp item))
        (check-formal-guard ctx varname item world)
        (mv (cons `(,item ,varname) guards) docs))
       ((when (and (consp item)
                   (not (eq (car item) 'quote))))
        (mv (cons item guards) docs))
       ((when (stringp item))
        (mv guards (cons item docs))))
    (raise "~x0: formal ~x1: expected guard specifiers or documentation ~
            strings, but found ~x2."
           ctx varname item)
    (mv guards docs)))

(defun parse-formal-items (ctx varname items guards docs world)
  "Returns (mv guards docs)"
  (declare (xargs :guard (legal-variablep varname)))
  (b* ((__function__ 'parse-formal-items)
       ((when (not items))
        (mv guards docs))
       ((when (atom items))
        (raise "~x0: formal ~x1: extended formal items should be ~
                nil-terminated; found ~x2 as the final cdr."
               ctx varname items)
        (mv guards docs))
       ((mv guards docs)
        (parse-formal-item ctx varname (car items) guards docs world)))
    (parse-formal-items ctx varname (cdr items) guards docs world)))

(defun parse-formal
  (ctx        ; context for error messages
   formal     ; thing the user wrote for this formal
   legal-kwds ; what keywords are allowed in the item list
   world
   )
  "Returns a formal-p"
  (declare (xargs :guard t))
  (b* ((__function__ 'parse-formal)
       ((when (atom formal))
        (b* ((varname (parse-formal-name ctx formal)))
          (make-formal :name varname
                       :guard t
                       :doc ""
                       :opts nil)))
       (varname (parse-formal-name ctx (car formal)))
       (items   (cdr formal))
       ((mv opts items)  (extract-keywords (cons ctx varname) legal-kwds items nil))
       ((mv guards docs) (parse-formal-items ctx varname items nil nil world))
       (guard (cond ((atom guards) 't)
                    ((atom (cdr guards)) (car guards))
                    (t (raise "~x0: formal ~x1: expected a single guard term, ~
                               but found ~&2." ctx varname guards))))
       (doc   (cond ((atom docs) "")
                    ((atom (cdr docs)) (car docs))
                    (t (progn$
                        (raise "~x0: formal ~x1: expected a single xdoc ~
                                string, but found ~&2." ctx varname docs)
                        "")))))
    (make-formal :name varname
                 :guard guard
                 :doc doc
                 :opts opts)))

(defun parse-formals (ctx formals legal-kwds world)
  ;; Assumes lambda-list keywords have already been removed from formals.
  (declare (xargs :guard t))
  (b* ((__function__ 'parse-formals)
       ((when (not formals))
        nil)
       ((when (atom formals))
        (raise "~x0: expected formals to be nil-terminated, but found ~x1 as ~
                the final cdr." ctx formals)))
    (cons (parse-formal ctx (car formals) legal-kwds world)
          (parse-formals ctx (cdr formals) legal-kwds world))))



; Support for macro formals

(defun has-macro-args (x)
  (declare (xargs :guard t))
  (if (atom x)
      nil
    (or (acl2::lambda-keywordp (car x))
        (has-macro-args (cdr x)))))

(defun remove-macro-args
  (ctx     ; context for error messages
   formals ; list of unparsed formals
   seenp   ; have we seen &key or &optional yet?
   )
  ;; Generates a new list of (unparsed) formals, with the macro arguments
  ;; removed.  This is going to be the list of formals for the -fn.  We are
  ;; going to remove "&key" and "&optional" from formals, and also fix up
  ;; any keyword/optional formals that have default values.
  ;;
  ;; When an argument has a default value it looks like
  ;;   (foo 'default).
  ;; The quote here distinguishes it from an extended formal that has a guard,
  ;; e.g., (foo 'atom) has a default value of 'atom and no guard, whereas
  ;; (foo atom) is just a plain extended formal with no default value (well, a
  ;; default value of nil) and a guard of (atom foo).
  (declare (xargs :guard t))
  (b* ((__function__ 'remove-macro-args)
       ((when (atom formals))
        formals)
       ((cons arg1 rest) formals)
       ((when (or (eq arg1 '&key)
                  (eq arg1 '&optional)))
        (remove-macro-args ctx rest t))
       ((when (acl2::lambda-keywordp arg1))
        (raise "~x0: only &key and &optional macro-style arguments are ~
                allowed, but found ~x1." ctx arg1))
       ((unless seenp)
        ;; Haven't yet seen &key/&optional, so don't change any args yet.
        (cons arg1 (remove-macro-args ctx rest seenp)))

       ;; Saw &keyword/&optional already and this isn't another keyword.  So,
       ;; this is a real keyword/optional argument.  We need to remove any
       ;; default value.
       ((when (and (consp arg1)
                   (true-listp arg1)
                   (equal (len arg1) 2)
                   (consp (second arg1))
                   (eq (car (second arg1)) 'quote)))
        ;; Arg1 matches (foo 'value), so it has a default value to remove.
        ;; Change it to drop the default value.
        (cons (first arg1) (remove-macro-args ctx rest seenp))))

    ;; Else, it doesn't match (foo 'value), so leave it alone.
    (cons arg1 (remove-macro-args ctx rest seenp))))


(defun formals-for-macro
  ;; Remove extended formal stuff (guards, documentation) and just get down to
  ;; a list of variable names and &key/&optional stuff, which is suitable for
  ;; the formals for the wrapper macro.
  (ctx      ; context for error messages
   formals  ; list of unparsed formals
   seenp    ; have we seen any macro arguments yet
   )
  (declare (xargs :guard t))
  (b* ((__function__ 'formals-for-macro)
       ((when (atom formals))
        formals)
       ((cons arg1 rest) formals)
       ((when (or (eq arg1 '&key)
                  (eq arg1 '&optional)))
        (cons arg1 (formals-for-macro ctx rest t)))
       ((when (acl2::lambda-keywordp arg1))
        (raise "~x0: only &key and &optional macro-style arguments are ~
                allowed, but found ~x1." ctx arg1))
       ((unless seenp)
        ;; Haven't yet seen &key/&optional, so the only args should be ordinary
        ;; symbols and extended formals.  Keep just the name.  If it's a
        ;; extended formal then it has the form (name ...).
        (cons (if (atom arg1) arg1 (car arg1))
              (formals-for-macro ctx rest seenp)))

       ;; Saw &keyword/&optional already and this isn't another keyword.  So,
       ;; this is a real keyword/optional argument.  If there's a default value
       ;; we need to keep it.
       ((when (and (consp arg1)
                   (true-listp arg1)
                   (equal (len arg1) 2)
                   (consp (second arg1))
                   (eq (car (second arg1)) 'quote)))
        ;; Arg1 matches (extended-formal 'value).  We need to keep just
        ;; (name 'value)
        (let* ((eformal     (first arg1))
               (default-val (second arg1)) ;; already quoted
               (name        (if (atom eformal) eformal (car eformal))))
          (cons (list name default-val)
                (formals-for-macro ctx rest seenp)))))

    ;; Else, it doesn't match (foo 'value), so just do the name extraction like
    ;; normal.
    (cons (if (atom arg1) arg1 (car arg1))
          (formals-for-macro ctx rest seenp))))


(defun remove-macro-default-values (args)
  ;; Args might be, e.g., (a b c (d '3) (e '4))
  ;; We just want to remove the values from any args with default values.
  (declare (xargs :guard t))
  (cond ((atom args)
         nil)
        ((atom (car args))
         (cons (car args) (remove-macro-default-values (cdr args))))
        (t
         (cons (caar args) (remove-macro-default-values (cdr args))))))

(defun make-wrapper-macro (fnname fnname-fn formals)
  (declare (xargs :guard (and (symbolp fnname)
                              (symbolp fnname-fn))))
  (b* ((macro-formals  (formals-for-macro fnname formals nil))
       (fn-arg-names   (remove-macro-default-values
                        (set-difference-equal macro-formals '(&key &optional)))))
    `(defmacro ,fnname ,macro-formals
       (list ',fnname-fn . ,fn-arg-names))))