1 (in-package #:clnl-model)
3 (defvar *separator* "@#$#@#$#@")
19 (defun default-model ()
20 "DEFAULT-MODEL => MODEL
24 MODEL: an object representing the model
28 Returns the default startup model."
31 (make-view :min-pxcor -5 :max-pxcor 5 :min-pycor -5 :max-pycor 5))))
33 (defun read-from-nlogo (str)
34 "READ-FROM-NLOGO STR => MODEL
38 STR: a readable stream
39 MODEL: an object representing the model
43 Takes a stream STR, reads in a nlogo file, parses it, and then
44 returns the model object."
48 ((read-sections (&optional section)
50 ((line (read-line str nil)))
52 (if (string= *separator* line)
53 (cons section (read-sections))
54 (read-sections (append section (list line))))))))
57 :code (clnl-code-parser:parse
58 (clnl-lexer:lex (format nil "~{~A~^~%~}" (nth 0 sections)))
63 (slider (intern (string-upcase (slider-varname widget)) :keyword))
64 (switch (intern (string-upcase (switch-varname widget)) :keyword))))
65 (parse-interface (nth 1 sections)))))
66 :interface (parse-interface (nth 1 sections))
67 :info (nth 2 sections)
68 :turtle-shapes (nth 3 sections)
69 :version (nth 4 sections)
70 :preview-commands (nth 5 sections)
71 :system-dynamics (nth 6 sections)
72 :behavior-space (nth 7 sections)
73 :hub-net-client (nth 8 sections)
74 :link-shapes (nth 9 sections)
75 :model-settings (nth 10 sections)
76 :delta-tick (nth 11 sections))))
80 (defparameter *widget-parsers* nil)
82 (defmacro defwidget-definition (type &rest definitions)
90 (when (find (car def) (list :int :double :inverted-boolean :boolean :choice :string :option)) (second def)))
100 ((line `(nth ,n ,lines)))
102 (:specified `(string= ,(second def) ,line))
103 (:int `(parse-integer ,line :junk-allowed t))
104 (:double `(ignore-errors (coerce (read-from-string ,line) 'double-float)))
105 (:boolean `(or (string= "1" ,line) (string= "0" ,line)))
106 (:inverted-boolean `(or (string= "0" ,line) (string= "1" ,line)))
107 (:choice `(find ,line ',(mapcar #'car (third def)) :test #'string=)))))
109 (loop for i to (length definitions) collect i)))))
111 (,(read-from-string (format nil "make-~A" type))
116 ((line `(nth ,n ,lines))
119 (:int `(parse-integer ,line))
120 (:double `(coerce (read-from-string ,line) 'double-float))
121 (:boolean `(string= "1" ,line))
122 (:inverted-boolean `(string= "0" ,line))
123 (:choice `(cadr (find ,line ',(third def) :key #'car :test #'string=)))
124 (:option `(when (string/= ,line ,(third def)) ,line))
126 (when val-getter (list (intern (symbol-name (cadr def)) :keyword) val-getter))))
128 (loop for i to (length definitions) collect i))))))
131 (defwidget-definition view
132 (:specified "GRAPHICS-WINDOW")
146 (:boolean wrapping-allowed-in-x)
147 (:boolean wrapping-allowed-in-y)
153 (:choice update-mode (("0" :continuous) ("1" :tick-based)))
155 (:boolean show-tick-counter)
156 (:string tick-counter-label)
157 (:double frame-rate 30))
159 (defwidget-definition slider
160 (:specified "SLIDER")
172 (:option units "NIL")
173 (:choice direction (("HORIZONTAL" :horizontal) ("VERTICAL" :vertical))))
175 (defwidget-definition switch
176 (:specified "SWITCH")
183 (:inverted-boolean on)
187 (defun parse-interface (interface-as-strings)
191 ((separate-widgets-as-strings (lines &optional widget-as-strings)
193 (if (string= "" (car lines))
194 (cons widget-as-strings (separate-widgets-as-strings (cdr lines)))
195 (separate-widgets-as-strings (cdr lines) (append widget-as-strings (list (car lines))))))))
196 (separate-widgets-as-strings interface-as-strings))))
200 (lambda (widget-as-strings)
202 ((parser (find-if (lambda (validator) (funcall validator widget-as-strings)) *widget-parsers* :key #'car)))
203 (when parser (funcall (cadr parser) widget-as-strings))))
204 widgets-as-strings))))
206 ;; INFORMATION ABOUT MODEL
208 (defun world-dimensions (model)
209 "WORLD-DIMENSIONS MODEL => DIMS
211 DIMS: (:xmin XMIN :xmax XMAX :ymin YMIN :ymax YMAX)
213 ARGUMENTS AND VALUES:
215 MODEL: A valid model containing a view
216 XMIN: An integer representing the minimum patch coord in X
217 XMAX: An integer representing the maximum patch coord in X
218 YMIN: An integer representing the minimum patch coord in Y
219 YMAX: An integer representing the maximum patch coord in Y
223 Returns the dimensions of MODEL. MODEL must be a valid model
224 as parsed by CLNL, and have a valid view in it."
226 ((view (find-if #'view-p (model-interface model))))
228 :xmin (view-min-pxcor view)
229 :xmax (view-max-pxcor view)
230 :ymin (view-min-pycor view)
231 :ymax (view-max-pycor view))))
233 ; For now, we keep the code hidden in this package
234 (defun globals (model)
235 "GLOBALS MODEL => GLOBALS
239 ARGUMENTS AND VALUES:
242 GLOBAL: A symbol interned in clnl:*model-package*
246 Returns the globals that get declared in the model, from widgets or
247 from code. They are interned in the package set for clnl, so
248 that they can later be used by functions in that package."
252 (intern (string-upcase (car pair)) clnl:*model-package*)
255 (clnl-code-parser:globals (model-code model))
260 (slider (list (slider-varname widget) (slider-default widget)))
261 (switch (list (switch-varname widget) (switch-on widget)))))
262 (model-interface model))))))