summaryrefslogtreecommitdiff
path: root/README.org
blob: 0f1877f0adc3e96c5b6b61134ce88896cd39c41b (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
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

* parzival
  
  The quest of =parzival= is to make it fun and easy to build parsers
  that consume streams and return common lisp values. E.g. A [[https://github.com/cbeo/parzival/blob/master/examples/Tutorial.org][JSON]]
  parser or can be written reasonbly succinctly in parzival.

  Check out the [[https://codeberg.org/hypergoof/parzival/src/branch/master/examples/Tutorial.org][Tutorial]] for a detailed introduction.

  If you use Emacs, load [[./parzival-indent.el]] to get custom indentation
  and keyword highlighting for parzival's special forms.
  
** A Neat / Dumb Example

  What follows is a quick example of using =parzival= to build a parser for
  simple arithmetic expressions. But first, you should be aware of two
  *completely optional* naming conventions that =parzival= adopts.
  
  1. Names beginning and ending in a =<= are *parsers*.
  2. Names that begin with =<<= are *higher-order functions* that accept
     or return parsers.
  
#+begin_src lisp

  (defpackage :parzival-user (:use :cl :parzival))
  (in-package :parzival-user)
  ;; we want to parse + - / or * and result in a function that can be
  ;; used do arithmetic on numbers
  (<<def <op<  
         (<<bind (<<strip (<<any-char "+-*/"))
                 (lambda (op-char)
                   (<<result
                    (case op-char
                      (#\+ #'+)
                      (#\- #'-)
                      (#\* #'*)
                      (t #'/))))))


  ;; parse a two real numbers separated by a valid operator
  ;; result in the operator applied to the numbers
  (<<def <simple-expression<
         (<<let ((arg1 (<<strip <real<))
                 (op <op<)
                 (arg2 (<<strip <real<)))
                (<<result (funcall op arg1 arg2))))


#+end_src

The above is "good enough" to parse simple expressions like "44.32 + 55" or
"88 / 11.11". E.g.

#+begin_src lisp
PARZIVAL-USER> (parse "33 * 2.5" <simple-expression< t)
82.5
T
#<REPLAY-STREAMS:STATIC-TEXT-REPLAY-STREAM {1003B1BBB3}>
PARZIVAL-USER> (parse "331 / 2.5" <simple-expression< t)
132.4
T
#<REPLAY-STREAMS:STATIC-TEXT-REPLAY-STREAM {1003BE0753}>
PARZIVAL-USER> (parse "foozball / 2.5" <simple-expression< t)
NIL
NIL
#<REPLAY-STREAMS:STATIC-TEXT-REPLAY-STREAM {1003C024E3}>
PARZIVAL-USER> T

#+end_src

In the last example the string =foozball= does not represent a real
number, and hence, the parse fails.  You can examine the third return
value to see where in the input stream the parse failed.

** An Extended Silly / Dumber Example

The code for this example is a little long winded, so for the
impatient I present below the grand payoff. 

What we have here is a natural language calculator.  It computes
addition, subtraction, multipication, and division with all the
convenience of typing out numbers without the cumbersome use of
digits!

Here is a REPL session

#+BEGIN_SRC

PARZIVAL-NUMBERS> (natural-language-calc)
Hello! And Welcome To the Super Practical Natural Language Calculator!

Type quit to quit

> one hundred minus eight
EQUALS ninety-two

> twenty-nine plus four hundred seventy-seven
EQUALS five hundred six

> twenty over four
EQUALS five

> twenty-one times sixteen
EQUALS three hundred thirty-six

> four thousand nine hundred fifty-five times two hundred seventeen
EQUALS one million seventy-five thousand two hundred thirty-five

> quit

"OK"

PARZIVAL-NUMBERS> 
#+END_SRC

(ta-dah)

*** The Code


 #+BEGIN_SRC lisp

 (defpackage :parzival-numbers
   (:use :cl :parzival))

 (in-package :parzival-numbers)

 (defun <<map-to (parser value)
   (<<map (lambda (x) value) parser))

 (<<def <ones<
   (<<or (<<map-to (<<string "one") 1)
         (<<map-to (<<string "two") 2)
         (<<map-to (<<string "three") 3)
         (<<map-to (<<string "four") 4)
         (<<map-to (<<string "five") 5)
         (<<map-to (<<string "six") 6)
         (<<map-to (<<string "seven") 7)
         (<<map-to (<<string "eight") 8)
         (<<map-to (<<string "nine") 9)))

 (<<def <teens<
   (<<or (<<map-to (<<string "ten") 10)
         (<<map-to (<<string "eleven") 11)
         (<<map-to (<<string "twelve") 12)
         (<<map-to (<<string "thirteen") 13)
         (<<map-to (<<string "fourteen") 14)
         (<<map-to (<<string "fifteen") 15)
         (<<map-to (<<string "sixteen") 16)
         (<<map-to (<<string "seventeen") 17)
         (<<map-to (<<string "eighteen") 18)
         (<<map-to (<<string "nineteen") 19)))

 (<<def <tens<
   (<<or (<<map-to (<<string "twenty") 20)
         (<<map-to (<<string "thirty") 30)
         (<<map-to (<<string "forty") 40)
         (<<map-to (<<string "fifty") 50)
         (<<map-to (<<string "sixty") 60)
         (<<map-to (<<string "seventy") 70)
         (<<map-to (<<string "eighty") 80)
         (<<map-to (<<string "ninety") 90)))

 (<<def <20-to-99<
   (<<bind <tens<
           (lambda (tens)
             (<<map (lambda (ones) (+ tens ones))
                    (<<and (<<char #\-) <ones<)))))

 (<<def <1-to-99<
   (<<or <20-to-99< <tens< <teens< <ones<))


 (<<def <one-hundreds<
   (<<bind <ones<
           (lambda (num)
             (<<map (lambda (ignore) (* num 100))
                    (<<and (<<+ <space<) (<<string "hundred"))))))

 (<<def <in-hundreds<
   (<<bind <one-hundreds<
           (lambda (hundreds)
             (<<map (lambda (num) (+ hundreds num))
                    (<<and (<<+ <space<) <1-to-99<)))))

 (<<def <all-hundreds<
   (<<plus <in-hundreds< <one-hundreds<))


 (defun <<magnitude-order (name factor)
   (<<bind (<<or <all-hundreds< <1-to-99<)
           (lambda (val)
             (<<map (lambda (ignore) (* val factor))
                    (<<and (<<+ <space<) (<<string name))))))

 (<<def <thousands< (<<magnitude-order "thousand" 1000))

 (<<def <millions< (<<magnitude-order "million" 1000000))

 (<<def <billions< (<<magnitude-order "billion" 1000000000))

 (<<def <trillions< (<<magnitude-order "trillion" 1000000000000))

 (<<def <quadrillions< (<<magnitude-order "quadrillion" 1000000000000000))

 (<<def <number<
   (<<map (lambda (ls) (apply #'+ ls))
          (apply #'parzival::<<list
                 (mapcar (lambda (p) (<<or (<<strip p) (<<result 0)))
                         (list <quadrillions< <trillions< <billions<
                               <millions< <thousands<
                               <all-hundreds< <1-to-99<)))))


 (defun parse-number (str)
   "Just for parsing numbers"
   (parse str <number< t))


 ;; three plus forty-seven thousand plus two hundred million sixty-five

 (<<def <op< (<<strip (<<or (<<string "plus")
                            (<<string "minus")
                            (<<string "times")
                            (<<string "over"))))

 (<<def <calc<
   (<<plus
    (<<bind <number<
            (lambda (number)
              (<<map (lambda (op-calc)
                       (cond ((equal (car op-calc) "plus")
                              (+ number (cdr op-calc)))
                             ((equal (car op-calc) "minus")
                              (- number (cdr op-calc)))
                             ((equal (car op-calc) "times")
                              (* number (cdr op-calc)))
                             ((equal (car op-calc) "over")
                              (round (/ number (cdr op-calc))))))
                     (<<cons <op< #'<calc<))))
    <number<))


 (defun natural-language-calc ()
   (format t "Hello! And Welcome To the Super Practical Natural Language Calculator!~%~%")
   (format t "Type quit to quit~%")
   (format t "> ")
   (loop named goof-calc
         for line = (read-line)
         do
         (if (equal line "quit")
             (return-from goof-calc "OK")
             (let ((parsed (parse (string-downcase line) <calc< t)))
               (if parsed
                   (format t "EQUALS ~R~%> " parsed)
                   (format t "No no no.. all wrong...~%> "))))))


 #+END_SRC