forked from Ramarren/cl-parser-combinators
-
Notifications
You must be signed in to change notification settings - Fork 1
/
basic.lisp
141 lines (120 loc) · 5.83 KB
/
basic.lisp
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
(in-package :parser-combinators)
;;; operate on list of tokens
(defclass parser-possibility ()
((tree :accessor tree-of :initarg :tree :initform nil)
(suffix :accessor suffix-of :initarg :suffix :initform nil)))
;;; lazy results
;;; continuation is a thunk returning parser-possibility or nil
(defclass parse-result-store ()
((storage :accessor storage-of :initarg :storage :initform (make-array 3 :initial-element nil))
(counter :accessor counter-of :initarg :counter :initform 0)
(continuation :accessor continuation-of :initarg :continuation :initform (constantly nil))))
(defclass parse-result ()
((store :accessor store-of :initarg :store :initform nil)
(current :accessor current-of :initarg :current :initform -1)))
(defgeneric nth-result (n parse-result-store)
(:method (n (parse-result-store null))
(declare (ignore n parse-result-store))
nil)
(:method (n (parse-result parse-result))
(nth-result n (store-of parse-result)))
(:method (n (parse-result-store parse-result-store))
(with-accessors ((storage storage-of)
(counter counter-of)
(continuation continuation-of))
parse-result-store
(if (< n counter)
(svref storage n)
(when continuation
(iter (for i from counter to n)
(for next-result = (funcall continuation))
(when (= i (length storage))
(let ((old-storage storage))
(setf storage (make-array (* 2 (length storage)) :initial-element nil))
(setf (subseq storage 0 i) old-storage)))
(setf (svref storage i) next-result)
(unless next-result
(setf continuation nil))
(while next-result)
(finally (setf counter i)
(return next-result))))))))
(defun make-parse-result (continuation)
(make-instance 'parse-result :store
(make-instance 'parse-result-store :continuation continuation)))
(defun current-result (parse-result)
(when (= (current-of parse-result) -1)
(next-result parse-result))
(nth-result (current-of parse-result) (store-of parse-result)))
(defun next-result (parse-result)
(incf (current-of parse-result))
(current-result parse-result))
(defun gather-results (parse-result)
(let ((current-result (current-result parse-result))
(continuation-results
(iter (for result next (next-result parse-result))
(while result)
(collect result))))
(when current-result
(cons current-result continuation-results))))
(defun copy-parse-result (parse-result)
(make-instance 'parse-result :store (store-of parse-result)))
;;; here parser spec is list of (pattern optional-guard comprehension)
;;; using do-like notation, <- is special
;;; list of either monads: (monad parameters), name bindings (<- name monad)
;;; simple, no let
(eval-when (:compile-toplevel :load-toplevel :execute)
(defun do-notation (monad-sequence bind ignore-gensym)
(destructuring-bind (monad . rest) monad-sequence
(cond ((endp rest)
monad)
((and (listp monad)
(eql (car monad) '<-))
(destructuring-bind (<- name monad) monad
(declare (ignore <-))
`(,bind ,monad
#'(lambda (,name)
,(do-notation rest bind ignore-gensym)))))
(t
`(,bind ,monad
#'(lambda (,ignore-gensym)
(declare (ignore ,ignore-gensym))
,(do-notation rest bind ignore-gensym))))))))
(defmacro mdo (&body spec)
"Combinator: use do-like notation to sequentially link parsers. (<- name parser) allows capturing of return values."
(with-unique-names (ignore-gensym)
(do-notation spec 'bind ignore-gensym)))
(defparameter *curtail-table* (make-hash-table))
(defparameter *memo-table* (make-hash-table))
(defun parse-string (parser string)
"Parse a string, return a PARSE-RESULT object. All returned values may share structure."
(let ((*memo-table* (make-hash-table))
(*curtail-table* (make-hash-table))
(context (make-context string)))
(values (make-parse-result (funcall parser context))
(front-of context))))
(defun parse-string* (parser string &key (complete nil))
"Parse a string and return the first result, whether the parse was incomplete, whether it was
successfull, and the context front as multiple values. The context front is an object containg the
context latest in the input and a list of lists of parser tags which were current at that point,
which allows approximate error reporting. It will be nil if the parse is successful and complete.
If COMPLETE is T, return the first parse to consume the input
completely. If COMPLETE is :FIRST return the first result only when it the whole input was consumed,
or immediately return nil."
(multiple-value-bind (parse-result front) (parse-string (ensure-parser parser) string)
(ecase complete
((nil :first)
(let ((result
(current-result parse-result)))
(cond ((or (null result)
(and (eql complete :first)
(not (end-context-p (suffix-of result)))))
(values nil nil nil front))
((not (end-context-p (suffix-of result)))
(values (tree-of result) (suffix-of result) t front))
(t (values (tree-of result) nil t nil)))))
(t (iter (with results = parse-result)
(for result = (next-result results))
(while result)
(when (end-context-p (suffix-of result))
(return (values (tree-of result) nil t nil)))
(finally (return (values nil nil nil front))))))))