Parser - Add :token argument type
[clnl] / src / main / parse.lisp
1 (in-package #:clnl-parser)
2
3 ; Ok, after thinking about this a little, the parser is completely contextual
4 ; based on what has come before.  We can't do a contextless parsing, like we
5 ; could in other languages, due to amiguity about reporters vs reporter tasks
6 ;
7 ; So, for instance, we could have:
8 ;   x + y => (+ x y)
9 ;   x + y => (x (task +) y)
10 ; So the definition of "+" is completely dependent on the nature of x
11 ;
12 ; The goal of this parser should be to turn in the amiguous lexed ast representing
13 ; NetLogo into an unambigious S-expression, and nothing more, so things like
14 ; expectation of commands being the first symbol is not be necessary until later
15 ;
16 ; In general, the parser will:
17 ;  * Parse the structure of the lexed output first
18 ;  * Parse the structure of the individual expressions (finding ('s and ['s and doing the right thing)
19 ;  * Coalate things into an unambigious expressions
20 ;  * Then we're done, let someone else make it evaluatable
21 ;    - We don't really care if things are commands or reporters right now
22
23 (defparameter *prims* nil)
24 ; prims that are created when compiling the netlogo file
25 ; usually via procedures or top level things like breed declarations
26 (defparameter *dynamic-prims* nil)
27
28 (defun prim-name (prim) (getf prim :name))
29 (defun prim-precedence (prim) (getf prim :precedence))
30 (defun prim-args (prim) (getf prim :args))
31 (defun prim-structure-prim (prim) (getf prim :structure-prim))
32 (defun prim-is-infix (prim) (getf prim :infix))
33
34 (defun find-prim (symb)
35  (or
36   (find symb *prims* :key #'prim-name)
37   (find symb *dynamic-prims* :key #'prim-name)))
38
39 ; Make this only as complicated as it needs to be, letting it grow
40 ; as we take on more and more of the language
41 (defun parse (lexed-ast &optional dynamic-prims)
42  "PARSE LEXED-AST &optional DYNAMIC-PRIMS => AST
43
44   DYNAMIC-PRIMS: DYNAMIC-PRIM*
45   DYNAMIC-PRIM: (:name NAME :args ARGS :infix INFIX :precedence PRECEDENCE)
46   ARGS: ARG*
47
48 ARGUMENTS AND VALUES:
49
50   LEXED-AST: An ambigious ast
51   AST: An unambigious ast that can be transpiled
52   NAME: A symbol in the keyword package
53   INFIX: Boolean denoting whether the prim is infix, defaulting to NIL
54   PRECEDENCE: A number, usually 10 for reporters, and 0 for commands
55   ARG: A list of symbols denoting the type of argument
56
57 DESCRIPTION:
58
59   PARSE takes a ambigious LEXED-AST and converts it to an unambigious one.
60
61   DYNAMIC-PRIMS that are passed in are used to avoid compilation errors on
62   things not statically defined by the NetLogo language, be they user defined
63   procedures or generated primitives from breed declarations.  NAME and PRECEDENCE
64   are required for all dynamic prims.
65
66   PRECEDENCE is a number used to calculate the order of operations.  Higher numbers
67   have more precedence than lower ones.  Generally all commands should have the
68   lowest precedence, and all reporters should have 10 as the precedence.
69
70   The possible values for ARG are :agentset, :boolean, :number, :command-block,
71   or t for wildcard.
72
73   The need for a parser between the lexer and the transpiler is because NetLogo
74   needs two passes to turn into something that can be used.  This is the only entry
75   point into this module, and should probably remain that way.
76
77   There's also a lot of error checking that the LEXED-AST even makes sense, even
78   though the lexer obviously thought it did.
79
80   Examples are too numerous and varied, but by inserting an output between
81   the lexer and this code, a good idea of what goes on can be gotten."
82  (when (find nil dynamic-prims :key #'prim-name)
83   (error "All passed in prims must have a name: ~S" (find nil dynamic-prims :key #'prim-name)))
84  (when (find nil dynamic-prims :key #'prim-precedence)
85   (error "All passed in prims must have a precedence: ~S" (find nil dynamic-prims :key #'prim-precedence)))
86  (let
87   ; could have defined this using the special variable, but didn't to make the
88   ; function definition simpler, as well as the documentation.
89   ((*dynamic-prims* dynamic-prims))
90   (remove-parened-forms (parse-internal lexed-ast))))
91
92 ; This is needed to clean up where we had to note parenthesis wrapped
93 ; things for the purpose of precedence
94 (defun remove-parened-forms (parsed-ast)
95  (cond
96   ((not parsed-ast) nil)
97   ((and (listp parsed-ast) (eql :parened (car parsed-ast))) (remove-parened-forms (cadr parsed-ast)))
98   ((listp parsed-ast) (mapcar #'remove-parened-forms parsed-ast))
99   (t parsed-ast)))
100
101 (defun parse-internal (lexed-ast &key prev-item prev-remaining-arg remaining-args)
102  (let
103   ((prim (and lexed-ast (symbolp (car lexed-ast)) (find-prim (car lexed-ast)))))
104   (cond
105    ((and remaining-args (eql (car remaining-args) :done-with-args))
106     (append (when prev-item (list (help-arg prev-item prev-remaining-arg))) lexed-ast))
107    ((and prim (prim-is-infix prim))
108     (parse-prim prim lexed-ast prev-item prev-remaining-arg remaining-args)) ; Special casing infix prims is cleaner
109    (t
110     (append
111      (when prev-item (list (help-arg prev-item prev-remaining-arg)))
112      (cond
113       ((not lexed-ast) nil)
114       ((stringp (car lexed-ast))
115        (parse-internal (cdr lexed-ast)
116         :prev-item (car lexed-ast)
117         :prev-remaining-arg (car remaining-args)
118         :remaining-args (cdr remaining-args)))
119       ((numberp (car lexed-ast))
120        (parse-internal (cdr lexed-ast)
121         :prev-item (coerce (car lexed-ast) 'double-float)
122         :prev-remaining-arg (car remaining-args)
123         :remaining-args (cdr remaining-args)))
124       ((and remaining-args
125         (or
126          (eql :token (car remaining-args))
127          (and
128           (listp (car remaining-args))
129           (find :token (car remaining-args))
130           (symbolp (car lexed-ast)))))
131        (parse-internal (cdr lexed-ast)
132         :prev-item (car lexed-ast)
133         :prev-remaining-arg (car remaining-args)
134         :remaining-args (cdr remaining-args)))
135       ((eql (intern "(" :keyword) (car lexed-ast)) (parse-parened-expr (cdr lexed-ast) remaining-args))
136       ((eql (intern ")" :keyword) (car lexed-ast)) (error "Closing parens has no opening parens"))
137       ((eql :let (car lexed-ast)) (parse-let (cdr lexed-ast) remaining-args))
138       ((eql :[ (car lexed-ast)) (parse-block (cdr lexed-ast) remaining-args))
139       (prim
140        (when (prim-structure-prim prim)
141         (error "This doesn't make sense here"))
142        (parse-prim prim lexed-ast nil prev-remaining-arg remaining-args))
143       (t (error "Couldn't parse ~S" lexed-ast))))))))
144
145 (defun parse-let (lexed-ast remaining-args)
146  (when (not (keywordp (car lexed-ast))) (error "Needed a keyword for let"))
147  (let*
148   ((half-parsed-remainder (parse-internal (cdr lexed-ast) :remaining-args (list t :done-with-args))))
149   (let
150    ((*dynamic-prims* (cons (list :name (car lexed-ast) :precedence 20) *dynamic-prims*)))
151    (parse-internal
152     (cdr half-parsed-remainder)
153     :remaining-args (cdr remaining-args)
154     :prev-remaining-arg (car remaining-args)
155     :prev-item (list :let (car lexed-ast) (cadr (car half-parsed-remainder)))))))
156
157 (defun reconfigure-due-to-precedence (prev-item prim following-args)
158  (flet
159   ((calculate-precedence (x)
160     (or
161      (and
162       (listp x)
163       (< 1 (length prev-item))
164       (keywordp (car x))
165       (find-prim (car x))
166       (prim-precedence (find-prim (car x))))
167      20)))
168   (cond
169    ((<= (prim-precedence prim) (calculate-precedence prev-item))
170     (cons
171      (prim-name prim)
172      (cons
173       (second (help-arg prev-item (car (prim-args prim))))
174       following-args)))
175    (t (append
176        (butlast prev-item)
177        (list
178         (reconfigure-due-to-precedence
179          (car (last prev-item))
180          prim
181          following-args)))))))
182
183 (defun parse-prim (prim lexed-ast prev-item prev-remaining-arg remaining-args)
184  (when (not (prim-precedence prim))
185   (error "Prim must have a precedence! ~A" prim))
186  (when (and (prim-is-infix prim) (eql :token (car (prim-args prim))))
187   (error "Can't have a prim that wants a token in the first position while being infix: ~A" prim))
188  (when
189   (and
190    (< (prim-precedence prim) 20)
191    (find-if (lambda (arg) (or (eql :token arg) (and (listp arg) (find :token arg)))) (prim-args prim)))
192   (error "Can't have a prim that wants a token and has a precedence of less than 20: ~A" prim))
193  (let*
194   ((args (if (prim-is-infix prim) (cdr (prim-args prim)) (prim-args prim)))
195    (half-parsed-remainder (parse-internal (cdr lexed-ast) :remaining-args (append args (list :done-with-args))))
196    (breakpoint (or
197                 (position-if (lambda (form) (or (not (listp form)) (not (eql :arg (car form))))) half-parsed-remainder)
198                 (length half-parsed-remainder)))
199    (already-parsed-limbo-forms
200     (subseq half-parsed-remainder breakpoint (min (length args) (length half-parsed-remainder))))
201    (middle-forms
202     (cons
203      (if
204       (prim-is-infix prim)
205       (reconfigure-due-to-precedence prev-item prim (mapcar #'cadr (subseq half-parsed-remainder 0 breakpoint)))
206       (cons
207        (prim-name prim)
208        (mapcar #'cadr (subseq half-parsed-remainder 0 breakpoint))))
209      already-parsed-limbo-forms)))
210   (append
211    (butlast middle-forms)
212    (parse-internal
213     (nthcdr (length args) half-parsed-remainder)
214     :remaining-args (if (prim-is-infix prim) remaining-args (cdr remaining-args))
215     :prev-remaining-arg (if (prim-is-infix prim) prev-remaining-arg (car remaining-args))
216     :prev-item (car (last middle-forms))))))
217
218 (defun help-arg (arg arg-type)
219  (cond
220   ((not arg-type) arg)
221   ((eql arg-type :token) (list :arg (list :token arg)))
222   ((and (listp arg-type) (find :token arg-type) (symbolp arg)) (list :arg (list :token arg)))
223   ((eql arg-type :command-block)
224    (if (not (and (consp arg) (eql 'block (car arg))))
225     (error "Required a block, but found a ~A" arg)
226     (list :arg (cons :command-block (cdr arg)))))
227   ((eql arg-type :reporter-block)
228    (if (not (and (consp arg) (eql 'block (car arg))))
229     (error "Required a block, but found a ~A" arg)
230     (list :arg (cons :reporter-block (cdr arg)))))
231   ((or
232     (eql arg-type :list)
233     (and (listp arg-type) (find :list arg-type)))
234    (list
235     :arg
236     (if (and (consp arg) (eql 'block (car arg)))
237      (cons :list-literal (cdr arg))
238      arg)))
239   ((and
240     (listp arg-type)
241     (find :command-block arg-type)
242     (consp arg)
243     (eql 'block (car arg)))
244    (list :arg (cons :command-block (cdr arg))))
245   ((and (listp arg-type) (find :optional arg-type)) arg)
246   (t (list :arg arg))))
247
248 (defun parse-block (tokens remaining-args)
249  (multiple-value-bind (in-block after-block) (find-closing-bracket tokens)
250   (parse-internal after-block
251    :prev-item (cons 'block (parse-internal in-block))
252    :prev-remaining-arg (car remaining-args)
253    :remaining-args (cdr remaining-args))))
254
255 (defun find-closing-bracket (tokens &optional (depth 0))
256  (cond
257   ((not tokens) (error "Failed to find a matching closing bracket"))
258   ((and (eql :] (car tokens)) (= depth 0)) (values nil (cdr tokens)))
259   (t (multiple-value-bind
260       (in-block after-block)
261       (find-closing-bracket (cdr tokens) (case (car tokens) (:[ (1+ depth)) (:] (1- depth)) (t depth)))
262       (values (cons (car tokens) in-block) after-block)))))
263
264 (defun parse-parened-expr (tokens remaining-args)
265  (multiple-value-bind (in-block after-block) (find-closing-paren tokens)
266   (parse-internal after-block
267    :prev-item
268    (let
269     ((parsed-in-block (parse-internal in-block)))
270     (when (/= 1 (length parsed-in-block)) (error "Expected ) here"))
271     (list :parened (car parsed-in-block)))
272    :prev-remaining-arg (car remaining-args)
273    :remaining-args (cdr remaining-args))))
274
275 (defun find-closing-paren (tokens &optional (depth 0))
276  (cond
277   ((not tokens) (error "Failed to find a matching closing bracket"))
278   ((and (eql (intern ")" :keyword) (car tokens)) (= depth 0)) (values nil (cdr tokens)))
279   (t (multiple-value-bind
280       (in-block after-block)
281       (find-closing-paren
282        (cdr tokens)
283        (cond
284         ((eql (intern "(" :keyword) (car tokens)) (1+ depth))
285         ((eql (intern ")" :keyword) (car tokens)) (1- depth)) (t depth)))
286       (values (cons (car tokens) in-block) after-block)))))
287
288 (defmacro defprim (name args precedence &rest options)
289  `(push
290    (list :name ,name :args ',args :infix ,(find :infix options) :precedence ,precedence)
291    *prims*))
292
293 (defmacro defstructureprim (name)
294  `(push
295    (list :name ,name :structure-prim t)
296    *prims*))
297
298 ; This list of prims will get combined with the mapping to actual code later
299 ; Current list of argument types we accept:
300 ; - :number
301 ; - :agentset
302 ; - :command-block
303 ; - :boolean
304 ; - :token (suspends evaluation)
305 ; - t - any type
306 ;
307 ; After the arguments, :infix denotes that it's an :infix operator
308 ;  - Note: Later we should move it to have a list of optional attributes of the primitive
309 (defprim := (t t) 5 :infix)
310 (defprim :!= (t t) 5 :infix)
311 (defprim :- (:number :number) 7 :infix)
312 (defprim :* (:number :number) 8 :infix)
313 (defprim :+ (:number :number) 7 :infix)
314 (defprim :/ (:number :number) 8 :infix)
315 (defprim :< (:number :number) 6 :infix)
316 (defprim :<= (:number :number) 6 :infix)
317 (defprim :any? (:agentset) 10)
318 (defprim :ask (:agentset :command-block) 0)
319 (defprim :ca () 0)
320 (defprim :clear-all () 0)
321 (defprim :crt (:number (:command-block :optional)) 0)
322 (defprim :create-turtles (:number (:command-block :optional)) 0)
323 (defprim :color () 10)
324 (defprim :count (:agentset) 10)
325 (defprim :die () 0)
326 (defprim :display () 0)
327 (defprim :with (:agentset :reporter-block) 12 :infix)
328 (defprim :fd (:number) 0)
329 (defprim :hatch (:number (:command-block :optional)) 0)
330 (defprim :let (t t) 0) ; while this has special processing, we need a prim for meta information
331 (defprim :if (:boolean :command-block) 0)
332 (defprim :if-else (:boolean :command-block :command-block) 0)
333 (defprim :ifelse (:boolean :command-block :command-block) 0)
334 (defprim :label () 10)
335 (defprim :label-color () 10)
336 (defprim :not (:boolean) 10)
337 (defprim :nobody () 10)
338 (defprim :one-of ((:agentset :list)) 10)
339 (defprim :of (:reporter-block :agentset) 11 :infix)
340 (defprim :patches () 10)
341 (defprim :pcolor () 10)
342 (defprim :random (:number) 10)
343 (defprim :random-float (:number) 10)
344 (defprim :random-xcor () 10)
345 (defprim :random-ycor () 10)
346 (defprim :round (t) 10)
347 (defprim :reset-ticks () 0)
348 (defprim :lt (:number) 0)
349 (defprim :rt (:number) 0)
350 (defprim :set (t t) 0)
351 (defprim :set-default-shape (t t) 0)
352 (defprim :setxy (:number :number) 0)
353 (defprim :show (t) 0)
354 (defprim :size () 10)
355 (defprim :stop () 0)
356 (defprim :tick () 0)
357 (defprim :ticks () 10)
358 (defprim :turtles () 10)
359 (defprim :turtles-here () 10)
360 (defprim :who () 10)
361
362 ; colors
363 (defprim :black () 10)
364 (defprim :blue () 10)
365 (defprim :brown () 10)
366 (defprim :green () 10)
367 (defprim :white () 10)
368
369 (defstructureprim :globals)
370 (defstructureprim :breed)
371 (defstructureprim :turtles-own)
372 (defstructureprim :patches-own)
373 (defstructureprim :to)
374 (defstructureprim :to-report)