summaryrefslogtreecommitdiff
path: root/parser.lisp
blob: 04d3f2bb26ccb2d9ce753969931a14011fb421f8 (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
(in-package #:parser)

(defun run (p input)
  (let ((r (funcall p input)))
    (if (parsing-p r)
      (parsing-tree r)
      (input::generate-report (failure-place r) (failure-message r)))))

(defstruct parsing
  tree
  left)

(defstruct failure
  place
  message)

(defstruct (normal-failure (:include failure)))

(defstruct (critical-failure (:include failure)))

(defun new (tree)
  (lambda (input)
    (make-parsing :tree tree :left input)))

(defun bind (p f)
  (lambda (input)
    (let ((r (funcall p input)))
      (if (parsing-p r)
        (funcall (funcall f (parsing-tree r)) (parsing-left r))
        r))))

(defun fail (&optional (message "Unknown error."))
  (lambda (input)
    (make-critical-failure :place input :message message)))

(defun either (first-parser &rest other-parsers)
  (lambda (input)
    (labels ((either-rec (body)
                         (if (cdr body)
                           (let ((r (funcall (car body) input)))
                             (if (normal-failure-p r)
                               (either-rec (cdr body))
                               r))
                           (funcall (car body) input))))
      (either-rec (cons first-parser other-parsers)))))

(defun unit (&optional (predicate #'characterp))
  (lambda (input)
    (if (input::has-data? input)
      (let ((c (input::element input)))
        (if (funcall predicate c)
          (make-parsing :tree c :left (input::advance input))
          (make-normal-failure :place input :message "Predicate not satisfied.")))
      (make-normal-failure :place input :message "Reached end of input."))))

(defmacro comp (bindings &body body)
  (if (null bindings)
    `(new (progn ,@body))
    (let ((v (first (car bindings)))
          (p (second (car bindings))))
      (if (eq v '_)
        `(bind ,p (lambda () (comp ,(cdr bindings) ,@body)))
        `(bind ,p (lambda (,v) (comp ,(cdr bindings) ,@body)))))))

(defparameter nothing
  (new nil))

(defun zero-or-one (p)
  (either p nothing))

(defun zero-or-more (p)
  (either (comp ((x p)
                 (xs (zero-or-more p)))
            (cons x xs))
          nothing))

(defun one-or-more (p)
  (comp ((x p)
         (xs (zero-or-more p)))
    (cons x xs)))