1 ; Copyright 2022 Frank Duncan (frank@consxy.com) under AGPL3. See distributed LICENSE.txt.
8 ((lexed-ast (let ((ast (clnl-lexer:lex str)))
9 (format t "Via lexing, AST for~%~S~% became~%~S~%~%" str ast) ast))
10 (parsed-ast (let ((ast (clnl-parser:parse lexed-ast)))
11 (format t "Via parsing, AST for~%~S~% became~%~S~%~%" lexed-ast ast) ast))
12 (transpiled-ast (let ((ast (clnl-transpiler:transpile parsed-ast)))
13 (format t "Via transpiling, AST for~%~S~% became~%~S~%" parsed-ast ast) ast)))
14 (eval transpiled-ast)))
16 (defun p (result) result)
18 (defun run (&optional file)
19 "RUN &optional FILE => RESULT
23 FILE: nlogo file with which to initialize
24 RESULT: undefined, the system terminates at the end of the loop
28 RUN starts up the CLNL system."
32 (defvar *callback* nil)
34 (defun boot (&optional file headless-mode)
35 "BOOT &optional FILE HEADLESS-MODE => RESULT
39 FILE: nlogo file with which to initialize state
40 HEADLESS-MODE: a boolean, defaults to nil
45 BOOT does exactly that, boots the clnl system in a clean state. The seed
46 is set so that multiple runs will evaluate to the same.
48 When FILE is not provided, a default model is used.
50 When HEADLESS-MODE is set to nil, the opengl interface is initialized.
51 Otherwise, the model will run headlessly, with no view."
54 (model->single-form-lisp
55 (if file (with-open-file (str file) (clnl-model:read-from-nlogo str)) (clnl-model:default-model))
56 :initialize-interface (not headless-mode)
57 :netlogo-callback (lambda (f) (setf *callback* f))))
58 (*package* *model-package*))
59 (eval netlogoed-lisp)))
61 (defvar *commands-mutex* (sb-thread:make-mutex))
63 (defun run-commands (cmds)
64 "RUN-COMMANDS CMDS => RESULT
68 CMDS: A string that may have one more NetLogo commands
73 RUN-COMMANDS will take NetLogo commands, put them through the various
74 stages need to turn them into Common Lisp code, and run it."
76 ; This mutex is a necessary because we haven't yet moved to a job thread
77 (sb-thread:with-mutex (*commands-mutex*)
78 (clnl-nvm:with-stop-handler
79 (funcall *callback* cmds))))
81 (defun run-reporter (reporter)
82 "RUN-REPORTER REPORTER => RESULT
86 REPORTER: A string that should have only one reporter
87 RESULT: The value reported by the NVM
91 RUN-REPORTER will take a NetLogo REPORTER, put it through the various
92 stages need to turn them into Common Lisp code, run it, and return the RESULT."
93 (eval (clnl-transpiler:transpile (clnl-parser:parse (clnl-lexer:lex reporter)))))
95 ; Because prims are used both at generation time and later at runtime, certain things in
96 ; them must be escaped a little bit more, such as wrapping the whole thing in a list
97 ; primitive. This way, the output of these things looks like halfway decent lisp,
98 ; and everything works nicely. We don't want any <FUNC #> showing up or anything
99 (defun munge-prim (prim)
101 ((copied (copy-list prim)))
102 (when (getf copied :args) (setf (getf copied :args) `(quote ,(getf copied :args))))
105 (defun netlogo-callback-body (prims)
107 (clnl-transpiler:transpile
109 (clnl-lexer:lex ,(intern "NETLOGO-CODE" *model-package*))
110 (list ,@(mapcar #'munge-prim prims)))
111 (list ,@(mapcar #'munge-prim prims)))))
113 (defun create-world-call (model globals code-ast)
114 `(clnl-nvm:create-world
115 :dims ',(clnl-model:world-dimensions model)
119 `(list ,(car pair) (lambda () ,(intern (string-upcase (car pair)) *model-package*))))
121 :turtles-own-vars ',(clnl-code-parser:turtles-own-vars code-ast)
122 :patches-own-vars ',(clnl-code-parser:patches-own-vars code-ast)
123 :breeds ',(clnl-code-parser:breeds code-ast)))
125 (defun create-proc-body (proc prims)
126 `(,(intern (string-upcase (car proc)) *model-package*) ()
127 (clnl-nvm:with-stop-handler
128 ,@(cdr ; remove the progn, cuz it looks nicer
129 (clnl-transpiler:transpile (cadr proc)
132 (if (getf prim :macro) ; The reason we do this is because with macros, we want to evaluate them in
133 ; this scope while preserving them for the generational purposes below
134 (append (list :macro (eval (getf prim :macro))) prim)
138 (defun nlogo->lisp (str pkg-symb boot-fn &key (seed 15) initialize-interface netlogo-callback-fn)
140 ((model (clnl-model:read-from-nlogo str))
145 (multiple-value-bind (found external) (find-symbol (symbol-name proc-symb) :cl)
146 (when (and found (eql :external external)) proc-symb)))
148 (clnl-code-parser:procedures
149 (clnl-code-parser:parse
150 (clnl-lexer:lex (clnl-model:code model))
151 (clnl-model:widget-globals model))))))))
154 (defpackage ,pkg-symb (:use :common-lisp) (:shadow ,@shadow-symbs))
155 (,(intern "IN-PACKAGE" :cl) ,pkg-symb) ; intern because of style check
157 `(defpackage ,,pkg-symb (:use :common-lisp) (:shadow ,,@shadow-symbs))
159 ((clnl:*model-package* (find-package ,pkg-symb)))
160 (clnl:model->multi-form-lisp
162 (intern (symbol-name ',boot-fn) ,pkg-symb)
164 :initialize-interface ,initialize-interface
165 :netlogo-callback-fn ,netlogo-callback-fn)))))))
167 (setf (documentation 'nlogo->lisp 'function)
168 "NLOGO->LISP STR PKG-SYMB BOOT-FN &key SEED INITIALIZE-INTERFACE NETLOGO-CALLBACK-FN => FORMS
170 ARGUMENTS AND VALUES:
172 STR: A stream holding an nlogo file
173 PKG-SYMB: A symbol for the generated package
174 BOOT-FN: A function name
175 SEED: An integer, defaults to 15
176 INITIALIZE-INTERFACE: A boolean
177 NETLOGO-CALLBACK-FN: a symbol
178 FORMS: A list of common lisp form
182 NLOGO->LISP takes a stream STR and returns a multi form lisp program,
183 that when executed, sets up the model. See MODEL->MULTI-FORM-LISP for
186 NLOGO->LISP does extra work of setting up the package to be named by
187 PKG-SYMB in order to correctly shadow common lisp functions.
189 It will also change the current package to the one created for the model
194 (with-open-file (str \"Wolf Sheep Predation.nlogo\") (nlogo->lisp str :wolfsheep 'boot)) => (forms)")
196 (defun model->single-form-lisp (model &key (seed 15) initialize-interface netlogo-callback)
199 (clnl-code-parser:parse (clnl-lexer:lex (clnl-model:code model)) (clnl-model:widget-globals model))
203 (clnl-code-parser:globals code-ast)
204 (clnl-model:widget-globals model))))
207 (lambda (pair) `(defparameter ,(intern (string-upcase (car pair)) *model-package*) ,(cadr pair)))
211 (lambda (proc) (create-proc-body proc prims))
212 (clnl-code-parser:procedures code-ast))
213 (clnl-random:set-seed ,seed)
214 (clnl-model:set-current-interface ',(clnl-model:interface model))
215 ,@(when netlogo-callback
216 `((clnl-model:set-callback
217 (lambda (,(intern "NETLOGO-CODE" *model-package*)) ,(netlogo-callback-body prims)))))
218 ,(create-world-call model globals code-ast)
219 ,@(when netlogo-callback
220 `((funcall ,netlogo-callback
221 (lambda (,(intern "NETLOGO-CODE" *model-package*))
222 ,(netlogo-callback-body prims)))))
223 ,@(when initialize-interface
224 `((clnl-interface:initialize
225 :dims ',(clnl-model:world-dimensions model)
226 :view ',(clnl-model:view model)
227 :buttons ',(clnl-model:buttons model)
228 :textboxes ',(clnl-model:textboxes model)
229 :sliders ',(clnl-model:sliders model)
230 :switches ',(clnl-model:switches model)))))))))
232 (setf (documentation 'model->single-form-lisp 'function)
233 "MODEL->SINGLE-FORM-LISP MODEL &key SEED INITIALIZE-INTERFACE NETLOGO-CALLBACK => FORM
235 ARGUMENTS AND VALUES:
238 SEED: An integer, defaults to 15
239 INITIALIZE-INTERFACE: A boolean
240 NETLOGO-CALLBACK: A function of one argument, or a symbol
241 FORM: A common lisp form
245 MODEL->SINGLE-FORM-LISP takes a model and returns a lisp program as a single form,
246 that when executed runs the model. The SEED passed in is used to start the
249 INITIALIZE-INTERFACE, when non nil, leads to initialization code for the
250 opengl interface being included.
252 NETLOGO-CALLBACK is a function that when called with a single argument,
253 a function that when called with netlogo code, will compile and run that
254 code in the environment of the model.
256 Of note, all globals defined either in the model code or via the widgets
257 are declared special in order to remain in the lexical environment for EVAL.")
259 (defun model->multi-form-lisp (model boot-fn &key (seed 15) initialize-interface netlogo-callback-fn)
262 (clnl-code-parser:parse (clnl-lexer:lex (clnl-model:code model)) (clnl-model:widget-globals model))
266 (clnl-model:widget-globals model)
267 (clnl-code-parser:globals code-ast))))
268 `((in-package ,(intern (package-name *model-package*) :keyword))
271 `(defvar ,(intern (string-upcase (car pair)) *model-package*) ,(cadr pair)))
274 (lambda (proc) `(defun ,@(create-proc-body proc prims)))
275 (clnl-code-parser:procedures code-ast))
277 (clnl-random:set-seed ,seed)
278 (clnl-model:set-current-interface ',(clnl-model:interface model))
279 (clnl-model:set-callback (symbol-function ',netlogo-callback-fn))
280 ,(create-world-call model globals code-ast)
281 ,@(when initialize-interface
282 `((clnl-interface:initialize
283 :dims ',(clnl-model:world-dimensions model)
284 :view ',(clnl-model:view model)
285 :buttons ',(clnl-model:buttons model)
286 :textboxes ',(clnl-model:textboxes model)
287 :sliders ',(clnl-model:sliders model)
288 :switches ',(clnl-model:switches model)))))
289 ,@(when netlogo-callback-fn
290 `((defun ,netlogo-callback-fn (,(intern "NETLOGO-CODE" *model-package*))
291 ,(netlogo-callback-body prims))))))))
293 (setf (documentation 'model->multi-form-lisp 'function)
294 "MODEL->MULTI-FORM-LISP MODEL BOOT-FN &key SEED INITIALIZE-INTERFACE NETLOGO-CALLBACK-FN => FORMS
296 ARGUMENTS AND VALUES:
299 BOOT-FN: A function name
300 SEED: An integer, defaults to 15
301 INITIALIZE-INTERFACE: A boolean
302 NETLOGO-CALLBACK-FN: a symbol
303 FORMS: A list of common lisp form
307 MODEL->MULTI-FORM-LISP takes a model and returns a multi form lisp program,
308 that when executed, sets up the model. Procedures map to defuns, globals
309 to defvars, etc. This can be output to load up quickly later. A function
310 named by BOOT-FN will be set for booting the program.
312 The SEED passed in is used to start the clnl-random RNG.
314 INITIALIZE-INTERFACE, when non nil, leads to initialization code for the
315 opengl interface being included.
317 NETLOGO-CALLBACK-FN is a symbol that will be defined as a function
318 to be called to execute code in the running netlogo instance.")