Improve parser - add wolfsheep prims, infix ability
[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-num-args (prim) (length (getf prim :args)))
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) (find symb *prims* :key #'prim-name))
35
36 ; Make this only as complicated as it needs to be, letting it grow
37 ; as we take on more and more of the language
38 (defun parse (lexed-ast &optional dynamic-prims)
39  "PARSE LEXED-AST &optional DYNAMIC-PRIMS => AST
40
41   DYNAMIC-PRIMS: DYNAMIC-PRIM*
42
43 ARGUMENTS AND VALUES:
44
45   LEXED-AST: An ambigious ast
46   AST: An unambigious ast that can be transpiled
47   DYNAMIC-PRIM: A prim not statically defined
48
49 DESCRIPTION:
50
51   PARSE takes a ambigious LEXED-AST and converts it to an unambigious one.
52
53   DYNAMIC-PRIMS that are passed in are used to avoid compilation errors on
54   things not statically defined by the NetLogo language, be they user defined
55   procedures or generated primitives from breed declarations.
56
57   The need for a parser between the lexer and the transpiler is because NetLogo
58   needs two passes to turn into something that can be used.  This is the only entry
59   point into this module, and should probably remain that way.
60
61   There's also a lot of error checking that the LEXED-AST even makes sense, even
62   though the lexer obviously thought it did.
63
64   Examples are too numerous and varied, but by inserting an output between
65   the lexer and this code, a good idea of what goes on can be gotten."
66  (let
67   ; could have defined this using the special variable, but didn't to make the
68   ; function definition simpler, as well as the documentation.
69   ((*dynamic-prims* dynamic-prims))
70   (parse-internal lexed-ast)))
71
72 (defun parse-internal (lexed-ast &key prev-item arg-countdown)
73  (let
74   ((prim (and lexed-ast (symbolp (car lexed-ast)) (find-prim (car lexed-ast)))))
75   (cond
76    ((and arg-countdown (zerop arg-countdown)) (append (when prev-item (list prev-item)) lexed-ast))
77    ((and prim (prim-is-infix prim))
78     (parse-prim prim lexed-ast prev-item arg-countdown)) ; Special casing infix prims is cleaner
79    (t
80     (append
81      (when prev-item (list prev-item))
82      (cond
83       ((not lexed-ast) nil)
84       ((stringp (car lexed-ast))
85        (parse-internal (cdr lexed-ast)
86         :prev-item (car lexed-ast)
87         :arg-countdown (when arg-countdown (1- arg-countdown))))
88       ((numberp (car lexed-ast))
89        (parse-internal (cdr lexed-ast)
90         :prev-item (coerce (car lexed-ast) 'double-float)
91         :arg-countdown (when arg-countdown (1- arg-countdown))))
92       ((eql (intern "(" (find-package :keyword)) (car lexed-ast)) (parse-parened-expr (cdr lexed-ast) arg-countdown))
93       ((eql (intern ")" (find-package :keyword)) (car lexed-ast)) (error "Closing parens has no opening parens"))
94       ((eql :[ (car lexed-ast)) (parse-block (cdr lexed-ast) arg-countdown))
95       (prim
96        (when (prim-structure-prim prim)
97         (error "This doesn't make sense here"))
98        (parse-prim prim lexed-ast nil arg-countdown))
99       (t (error "Couldn't parse ~S" lexed-ast))))))))
100
101 (defun parse-prim (prim lexed-ast prev-item arg-countdown)
102  (let*
103   ((num-args (- (prim-num-args prim) (if (prim-is-infix prim) 1 0)))
104    (half-parsed-remainder (parse-internal (cdr lexed-ast) :arg-countdown num-args)))
105   (parse-internal
106    (nthcdr num-args half-parsed-remainder)
107    :arg-countdown (when arg-countdown (if (prim-is-infix prim) arg-countdown (1- arg-countdown)))
108    :prev-item
109    (cons
110     (prim-name prim)
111     (mapcar
112      #'help-arg
113      (prim-args prim)
114      (append
115       (when (prim-is-infix prim) (list prev-item))
116       (butlast half-parsed-remainder (- (length half-parsed-remainder) num-args))))))))
117
118 (defun help-arg (arg-type arg)
119  (case arg-type
120   (:command-block
121    (if (not (and (consp arg) (eql 'block (car arg))))
122     (error "Required a block, but found a ~A" arg)
123     (cons :command-block (cdr arg))))
124   (:reporter-block
125    (if (not (and (consp arg) (eql 'block (car arg))))
126     (error "Required a block, but found a ~A" arg)
127     (cons :reporter-block (cdr arg))))
128   (:list
129    (if (and (consp arg) (eql 'block (car arg)))
130     (cons :list-literal (cdr arg))
131     arg))
132   (t arg)))
133
134 (defun parse-block (tokens arg-countdown)
135  (multiple-value-bind (in-block after-block) (find-closing-bracket tokens)
136   (parse-internal after-block
137    :prev-item (cons 'block (parse-internal in-block))
138    :arg-countdown (when arg-countdown (1- arg-countdown)))))
139
140 (defun find-closing-bracket (tokens &optional (depth 0))
141  (cond
142   ((not tokens) (error "Failed to find a matching closing bracket"))
143   ((and (eql :] (car tokens)) (= depth 0)) (values nil (cdr tokens)))
144   (t (multiple-value-bind
145       (in-block after-block)
146       (find-closing-bracket (cdr tokens) (case (car tokens) (:[ (1+ depth)) (:] (1- depth)) (t depth)))
147       (values (cons (car tokens) in-block) after-block)))))
148
149 (defun parse-parened-expr (tokens arg-countdown)
150  (multiple-value-bind (in-block after-block) (find-closing-paren tokens)
151   (parse-internal after-block
152    :prev-item
153    (let
154     ((parsed-in-block (parse-internal in-block)))
155     (when (/= 1 (length parsed-in-block)) (error "Expected ) here"))
156     (car parsed-in-block))
157    :arg-countdown (when arg-countdown (1- arg-countdown)))))
158
159 (defun find-closing-paren (tokens &optional (depth 0))
160  (cond
161   ((not tokens) (error "Failed to find a matching closing bracket"))
162   ((and (eql (intern ")" (find-package :keyword)) (car tokens)) (= depth 0)) (values nil (cdr tokens)))
163   (t (multiple-value-bind
164       (in-block after-block)
165       (find-closing-paren
166        (cdr tokens)
167        (cond
168         ((eql (intern "(" (find-package :keyword)) (car tokens)) (1+ depth))
169         ((eql (intern ")" (find-package :keyword)) (car tokens)) (1- depth)) (t depth)))
170       (values (cons (car tokens) in-block) after-block)))))
171
172 (defmacro defprim (name args &optional infix)
173  `(push
174    (list :name ,name :args ',args :infix ,infix)
175    *prims*))
176
177 (defmacro defstructureprim (name)
178  `(push
179    (list :name ,name :structure-prim t)
180    *prims*))
181
182 ; This list of prims will get combined with the mapping to actual code later
183 ; Current list of argument types we accept:
184 ; - :number
185 ; - :agentset
186 ; - :command-block
187 ; - :boolean
188 ; - t - any type
189 ;
190 ; After the arguments, :infix denotes that it's an :infix operator
191 ;  - Note: Later we should move it to have a list of optional attributes of the primitive
192 (defprim := (t t) :infix)
193 (defprim :!= (t t) :infix)
194 (defprim :- (:number :number) :infix)
195 (defprim :* (:number :number) :infix)
196 (defprim :+ (:number :number) :infix)
197 (defprim :/ (:number :number) :infix)
198 (defprim :< (:number :number) :infix)
199 (defprim :<= (:number :number) :infix)
200 (defprim :any? (:agentset))
201 (defprim :ask (:agentset :command-block))
202 (defprim :clear-all ())
203 (defprim :crt (:number))
204 (defprim :color ())
205 (defprim :count ())
206 (defprim :die ())
207 (defprim :display ())
208 (defprim :with (:reporter-block))
209 (defprim :fd (:number))
210 (defprim :hatch (:number :command-block))
211 (defprim :let (t t))
212 (defprim :if (:boolean :command-block))
213 (defprim :ifelse (:boolean :command-block :command-block))
214 (defprim :label ())
215 (defprim :label-color ())
216 (defprim :not (:boolean))
217 (defprim :nobody ())
218 (defprim :one-of (t))
219 (defprim :patches ())
220 (defprim :pcolor ())
221 (defprim :random (:number))
222 (defprim :random-float (:number))
223 (defprim :random-xcor ())
224 (defprim :random-ycor ())
225 (defprim :round ())
226 (defprim :reset-ticks ())
227 (defprim :lt (:number))
228 (defprim :rt (:number))
229 (defprim :set (t t))
230 (defprim :set-default-shape (t t))
231 (defprim :setxy (:number :number))
232 (defprim :show (t))
233 (defprim :size ())
234 (defprim :stop ())
235 (defprim :tick ())
236 (defprim :turtles ())
237
238 ; colors
239 (defprim :black ())
240 (defprim :blue ())
241 (defprim :brown ())
242 (defprim :green ())
243 (defprim :white ())
244
245 (defstructureprim :globals)
246 (defstructureprim :breed)
247 (defstructureprim :turtles-own)
248 (defstructureprim :patches-own)
249 (defstructureprim :to)
250 (defstructureprim :to-report)
251
252 ; Placeholder prims that should be populated in dynamic prims
253
254 ; Generated by globals/widgets
255 (defprim :grass ())
256 (defprim :initial-number-sheep ())
257 (defprim :initial-number-wolves ())
258 (defprim :sheep-gain-from-food ())
259 (defprim :wolf-gain-from-food ())
260 (defprim :sheep-reproduce ())
261 (defprim :wolf-reproduce ())
262 (defprim :grass? ())
263 (defprim :grass-regrowth-time ())
264 (defprim :show-energy? ())
265
266 ; Generated by procedures
267 (defprim :move ())
268 (defprim :eat-grass ())
269 (defprim :reproduce-sheep ())
270 (defprim :reproduce-wolves ())
271 (defprim :catch-sheep ())
272 (defprim :death ())
273 (defprim :grow-grass ())
274 (defprim :display-labels ())
275
276 ; Generated by *-own
277 (defprim :countdown ())
278 (defprim :energy ())
279
280 ; Generated by a let
281 (defprim :prey ())
282
283 ; Generated by breeds
284 (defprim :sheep ())
285 (defprim :wolves ())
286 (defprim :create-sheep (:number :command-block))   ; look at me not have to do optionals yet
287 (defprim :sheep-here ())
288 (defprim :create-wolves (:number :command-block))