summaryrefslogtreecommitdiff
path: root/obwyn.lisp
blob: db2f7787ace16ba55851e66926fdca207f81aaef (plain)
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
(defpackage #:obwyn
  (:use #:cl)
  (:local-nicknames
   (#:a #:alexandria-2)))

(in-package :obwyn)

(defun values-binding-p (form)
  #+testiere
  (:tests
   (:is (values-binding-p 'a))
   (:is (values-binding-p '(a b)))
   (:is (values-binding-p '()))
   (:is (not (values-binding-p :x)))
   (:is (not (values-binding-p '(a b 4))))
   (:is (not (values-binding-p '(a :b 4)))))
  (or (and (symbolp form)
           (not (keywordp form)))
      (and (listp form)
           (every (a:conjoin #'symbolp (complement #'keywordp)) form))))

(defun destructuring-bind-list-p (form)
  #+testiere
  (:tests
   (:is (destructuring-bind-list-p '()))
   (:is (destructuring-bind-list-p '(a b)))
   (:is (destructuring-bind-list-p '(a &rest b)))
   (:is (destructuring-bind-list-p '(a &rest b &key (x 10) y)))
   (:is (destructuring-bind-list-p '(&optional a b)))
   (:is (not (destructuring-bind-list-p 'x)))
   (:is (not (destructuring-bind-list-p '(&rest x &optional y))))
   (:is (not (destructuring-bind-list-p '(&rest 10 &optional y)))))
  (and (listp form)
       (ignore-errors
        (multiple-value-bind (required optional rest kwargs allow-other aux keyp)
            (alexandria:parse-ordinary-lambda-list form)
          (declare (ignorable required optional rest kwargs keyp))
          (not (or allow-other aux))))))

(defun expand-clauses (clauses)
  (loop :with expanded := nil
        :for (tag bindings form) :in (nreverse clauses)
        :do
           (setf expanded 
                 (ecase tag
                   (:simple
                    (cond ((null expanded) form)
                          ((eq 'cl:progn (first expanded))
                           `(progn ,form ,@(rest expanded)))
                          (t
                           `(progn ,form ,expanded))))
                   (:multiple-value-bind
                         `(multiple-value-bind
                                ,(if (listp bindings) bindings (list bindings))
                              ,form
                            ,@(if (and (listp expanded) (eq 'cl:progn (first expanded)))
                                  (rest expanded)
                                  (list expanded))))
                   (:destructuring-bind
                       `(destructuring-bind ,bindings ,form
                          ,@(if (and (listp expanded) (eq 'cl:progn (first expanded))) 
                                (rest expanded)
                                (list expanded))))))
        :finally (return
                   (if (and (listp expanded) (eq 'cl:progn (first expanded)))
                       `(block nil ,@(rest expanded))
                       `(block nil ,expanded)))))


(defun literals-equal (a b)
  (and (symbolp a) (symbolp b) (string-equal a b)))

(argot:deflanguage >>> (:literals= #'literals-equal)
  (<start>
   :match (:seq (:* <clause>) (:eof))
   :then (expand-clauses (first <start>)))
  
  (<clause>
   :match (:or <binding-clause>
               <destructuring-clause>
               <simple-clause>)) 
  
  (<binding-clause>
   :match (:seq <values-bind> (:= :<-) (:item))
   :then (destructuring-bind (bindings _ form) <binding-clause>
           (declare (ignore _))
           (list :multiple-value-bind bindings form)))

  (<values-bind>
   :match (:item)
   :if values-binding-p
   :note "Either a symbol or a list suitable for passing to MULTIPLE-VALUE-BIND")

  (<destructuring-clause>
   :match (:seq <destructuring-list> (:= :<~) (:item))
   :then (destructuring-bind (bindings _ form) <destructuring-clause>
           (declare (ignore _))
           (list :destructuring-bind bindings form)))

  (<destructuring-list>
   :match (:item)
   :if destructuring-bind-list-p
   :note "An list that might be passed as the first argument to DESTRUCTURING-BIND.")  
  
  (<simple-clause>
   :match (:@ simple (:item))
   :if (not (member simple '(:<~ :<-)))
   :then (list :simple nil simple)))