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