Code - globals
[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
33 (defun find-prim (symb) (find symb *prims* :key #'prim-name))
34
35 ; Make this only as complicated as it needs to be, letting it grow
36 ; as we take on more and more of the language
37 (defun parse (lexed-ast &optional dynamic-prims)
38  "PARSE LEXED-AST &optional DYNAMIC-PRIMS => AST
39
40   DYNAMIC-PRIMS: DYNAMIC-PRIM*
41
42 ARGUMENTS AND VALUES:
43
44   LEXED-AST: An ambigious ast
45   AST: An unambigious ast that can be transpiled
46   DYNAMIC-PRIM: A prim not statically defined
47
48 DESCRIPTION:
49
50   PARSE takes a ambigious LEXED-AST and converts it to an unambigious one.
51
52   DYNAMIC-PRIMS that are passed in are used to avoid compilation errors on
53   things not statically defined by the NetLogo language, be they user defined
54   procedures or generated primitives from breed declarations.
55
56   The need for a parser between the lexer and the transpiler is because NetLogo
57   needs two passes to turn into something that can be used.  This is the only entry
58   point into this module, and should probably remain that way.
59
60   There's also a lot of error checking that the LEXED-AST even makes sense, even
61   though the lexer obviously thought it did.
62
63   Examples are too numerous and varied, but by inserting an output between
64   the lexer and this code, a good idea of what goes on can be gotten."
65  (let
66   ; could have defined this using the special variable, but didn't to make the
67   ; function definition simpler, as well as the documentation.
68   ((*dynamic-prims* dynamic-prims))
69   (parse-internal lexed-ast)))
70
71 (defun parse-internal (lexed-ast)
72  (cond
73   ((not lexed-ast) nil)
74   ((numberp (car lexed-ast)) (cons (coerce (car lexed-ast) 'double-float) (parse-internal (cdr lexed-ast))))
75   ((eql :[ (car lexed-ast)) (parse-block (cdr lexed-ast)))
76   ((and (symbolp (car lexed-ast)) (find-prim (car lexed-ast)))
77    (let
78     ((prim (find-prim (car lexed-ast))))
79     (when (prim-structure-prim prim)
80      (error "This doesn't make sense here"))
81     (parse-prim prim lexed-ast)))
82   (t (error "Couldn't parse ~S" lexed-ast))))
83
84 (defun parse-prim (prim lexed-ast)
85  (let
86   ((num-args (prim-num-args prim))
87    (parsed-remainder (parse-internal (cdr lexed-ast))))
88   (cons
89    (cons
90     (prim-name prim)
91     (mapcar
92      #'help-arg
93      (prim-args prim)
94      (butlast parsed-remainder (- (length parsed-remainder) num-args))))
95    (nthcdr num-args parsed-remainder))))
96
97 (defun help-arg (arg-type arg)
98  (case arg-type
99   (:command-block
100    (if (not (and (consp arg) (eql 'block (car arg))))
101     (error "Required a block, but found a ~A" arg)
102     (cons :command-block (cdr arg))))
103   (:list
104    (if (and (consp arg) (eql 'block (car arg)))
105     (cons :list-literal (cdr arg))
106     arg))
107   (t arg)))
108
109 (defun parse-block (tokens)
110  (multiple-value-bind (in-block after-block) (find-closing-bracket tokens)
111   (cons
112    (cons
113     'block
114     (parse-internal in-block))
115    (parse-internal after-block))))
116
117 (defun find-closing-bracket (tokens &optional (depth 0))
118  (cond
119   ((not tokens) (error "Failed to find a matching closing bracket"))
120   ((and (eql :] (car tokens)) (= depth 0)) (values nil (cdr tokens)))
121   (t (multiple-value-bind
122       (in-block after-block)
123       (find-closing-bracket (cdr tokens) (case (car tokens) (:[ (1+ depth)) (:] (1- depth)) (t depth)))
124       (values (cons (car tokens) in-block) after-block)))))
125
126 (defmacro defprim (name args)
127  `(push
128    (list :name ,name :args ',args)
129    *prims*))
130
131 (defmacro defstructureprim (name)
132  `(push
133    (list :name ,name :structure-prim t)
134    *prims*))
135
136 ; This list of prims will get combined with the mapping to actual code later
137 ; Current list of argument types we accept:
138 ; - :number
139 ; - :agentset
140 ; - :command-block
141 ; - t - any type
142 (defprim :ask (:agentset :command-block))
143 (defprim :crt (:number))
144 (defprim :fd (:number))
145 (defprim :random-float (:number))
146 (defprim :show (t))
147 (defprim :turtles ())
148
149 (defstructureprim :globals)
150 (defstructureprim :breed)
151 (defstructureprim :turtles-own)
152 (defstructureprim :patches-own)
153 (defstructureprim :to)
154 (defstructureprim :to-report)