Skip to content

Commit 834b463

Browse files
Merge pull request #63 from ClojureCivitas/grammar-of-physics
add grammar of physics rigid body
2 parents 2e1265c + 89d3657 commit 834b463

3 files changed

Lines changed: 771 additions & 0 deletions

File tree

Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
^:kindly/hide-code
2+
^{:clay {:title "A Grammar of Physics"
3+
:quarto {:type :post
4+
:author [:timothypratley]
5+
:date "2025-09-07"
6+
:description "Build physics simulations from words with Instaparse, sans compilation."
7+
:category :math
8+
:tags [:physics]
9+
:keywords [:instaparse :parsing :grammar]}}}
10+
(ns instaparse.grammar-of-physics.rigid-body
11+
(:require [scicloj.kindly.v4.kind :as kind]))
12+
13+
;; As Clojurists, we naturally think in terms of data structures and transformations.
14+
;; Yet we often overlook one of our most powerful tools: the ability to create custom languages through grammars.
15+
;; Today we'll explore how the amazing [Instaparse](https://github.com/Engelberg/instaparse)
16+
;; can be used to define a grammar of physics, and how a custom evaluator
17+
;; can bring that grammar to life as a physics simulation.
18+
19+
;; ## The Experiment
20+
21+
^:kindly/hide-code
22+
(kind/hiccup
23+
[:div
24+
[:div.text-muted.small.mb-2 "Click and drag the objects to move them"]
25+
[:div#matter-canvas-container {:style {:width "100%"}}]
26+
[:div.d-flex.justify-content-between.align-items-center.mb-3
27+
[:div.btn-group.btn-group-sm
28+
[:button.btn.btn-danger {:onClick "explode()"} "explode"]
29+
[:button.btn.btn-outline-primary {:onClick "rotate()"} "rotate"]]
30+
[:div.btn-group.btn-group-sm
31+
[:button.btn.btn-outline-secondary {:onClick "loadExample(0)"} "load1"]
32+
[:button.btn.btn-outline-secondary {:onClick "loadExample(1)"} "load2"]
33+
[:button.btn.btn-outline-secondary {:onClick "loadExample(2)"} "load3"]
34+
[:button.btn.btn-outline-secondary {:onClick "loadExample(3)"} "load4"]
35+
[:button.btn.btn-outline-secondary {:onClick "loadExample(4)"} "load5"]]]
36+
[:div.mb-3
37+
[:textarea#program.form-control.font-monospace.mb-2.bg-light
38+
{:rows "12"
39+
:style {:font-family "monospace"
40+
:font-size "0.85rem"}}]
41+
[:div.d-flex.justify-content-end.mb-2
42+
[:button.btn.btn-success {:onClick "evalProgram()"} "evaluate"]]
43+
[:pre#parse-error.alert.alert-danger.mt-2
44+
{:role "alert"
45+
:style {:font-family "monospace"
46+
:font-size "0.85rem"}}]]])
47+
48+
;; ::: {.callout-tip}
49+
;; Modify the scene and click "evaluate" to see the changes.
50+
;; Try replacing `--` connections with `%%` springs.
51+
;; :::
52+
53+
;; What you see above is a rigid body simulation.
54+
;; The scene was generated from a text description using
55+
;; a Domain-Specific Language (DSL) for physics.
56+
57+
;; ## The Hypothesis
58+
59+
;; Look familiar?
60+
;; If you have worked with graph visualization tools like Graphviz,
61+
;; you will recognize the declarative style.
62+
;; But instead of describing visual relationships,
63+
;; we are describing *physical* ones: bodies, constraints, and composites.
64+
65+
;; The beauty lies in the syntax: `ball -- a` creates a rigid connection,
66+
;; `a ~~ b` forms a rope, and `b %% floor` adds a spring.
67+
;; Functions like `domino` let us abstract common patterns.
68+
;; Attributes in brackets fine-tune physical properties like mass and stiffness.
69+
70+
;; Some affordances for layout are made with the `grid` function,
71+
;; but a lot more could be done here to automate positioning.
72+
73+
;; ## The Theory
74+
75+
;; Here is my formal grammar of rigid body physics for simulations.
76+
77+
^:kindly/hide-code
78+
(kind/hiccup
79+
[:div.mb-3
80+
[:textarea#grammar.form-control.font-monospace.bg-light
81+
{:rows "18"
82+
:style {:font-family "monospace"
83+
:font-size "0.85rem"}}]
84+
[:div.btn-group.btn-group-sm.mt-2
85+
[:button.btn.btn-primary {:onClick "instaparse()"} "Instaparse"]
86+
[:button.btn.btn-outline-secondary {:onClick "resetGrammar()"} "reset"]]
87+
[:div#grammar-error.alert.alert-danger.mt-2
88+
{:role "alert"
89+
:style {:font-family "monospace"
90+
:font-size "0.85rem"}}]])
91+
92+
;; This EBNF (Extended Backus-Naur Form) specification defines our language's constructs,
93+
;; including bodies with various shapes, constraints, functions, calls, numbers, and symbols.
94+
95+
;; ::: {.callout-tip}
96+
;; Try changing the grammar, clicking Instaparse, and see what happens.
97+
;; :::
98+
99+
;; ## The Application
100+
101+
;; Instaparse takes the grammar and makes a parser.
102+
;; When we parse a program the result is an Abstract Syntax Tree (AST).
103+
104+
^:kindly/hide-code
105+
(kind/hiccup
106+
[:div.mb-3
107+
[:h4.h5.text-muted.mb-3 "Abstract Syntax Tree"]
108+
[:pre#ast.border.rounded.p-3.bg-light
109+
{:style {:min-height "200px"
110+
:max-height "500px"
111+
:overflow "auto"
112+
:font-family "monospace"
113+
:font-size "0.85rem"}}]])
114+
115+
;; The AST is the structure of our physics program.
116+
;; Every concept is organized into a tree that preserves syntax and semantics, as data.
117+
118+
;; ## The Equation
119+
120+
;; The AST is a data representation of our program, not a data representation of the simulation itself.
121+
;; To bring it to life, we need to evaluate the program.
122+
;; The full implementation is in [rigid_body.cljs](rigid_body.cljs), but for now,
123+
;; let's examine a small part of it.
124+
125+
;; ```clojure
126+
;; :call (let [[fn-name & args] xs
127+
;; fn-val (or (get-in env [:labels fn-name])
128+
;; (get core fn-name)
129+
;; (throw (ex-info (str "Function not found: " fn-name)
130+
;; {:id ::function-not-found
131+
;; :fn-name fn-name
132+
;; :env env})))
133+
;; [env arg-vals] (eval-vector env args)
134+
;; [fn-env result] (apply fn-val env arg-vals)
135+
;; env (merge env (select-keys fn-env (vals entity-plural)))]
136+
;; [env result])
137+
;; ```
138+
139+
;; This is the rule for handling `:call` nodes in the AST.
140+
;; The body of the call node contains a function name and arguments.
141+
;; We look up the function in the environment.
142+
;; Arguments are evaluated, the function is applied, and the environment modified.
143+
144+
;; Our evaluator walks the AST, interprets each node according to our physics semantics,
145+
;; and adds bodies, constraints, and composites into an environment.
146+
;; That environment is then traversed to hydrate a Matter.js rigid body simulation.
147+
148+
;; What I love most about the evaluator and hydration is that all the tedious
149+
;; `Matter.World.add(Matter.Bodies.create(...))` nonsense disappears.
150+
;; When all you have is a code API,
151+
;; boilerplate clutters up your simulation to the point that it becomes difficult to reason about.
152+
;; The textual descriptions, in contrast, only describe the domain.
153+
154+
;; Here we start to see how programs beat data representations; **compression through implication**.
155+
;; Our statement `domino: (x, y => rectangle x y 10 50)` labels a function that implies object creation.
156+
;; When we write `ball -- a`, it implies lookup by label and constraint creation.
157+
;; Programs let us work at the level of *intent*.
158+
159+
;; An alternative solution is to create a "world builder" that writes the simulation as data.
160+
;; Indeed, there is fertile middle ground here.
161+
;; Our program produces a data representation from the AST before hydrating the world,
162+
;; so this approach can work in conjunction with a world builder.
163+
;; The grammar feels more powerful and compositional for initial creation,
164+
;; whereas a world builder excels at making adjustments in a more tactile way.
165+
166+
;; ## Breaking the Laws of Compilation
167+
168+
;; This Instaparse demo runs in your browser,
169+
;; but [rigid_body.cljs](rigid_body.cljs) has not been compiled to JavaScript.
170+
;; Isn't that impossible?
171+
;; Libraries like Instaparse require compilation!
172+
173+
;; [Scittle](https://github.com/babashka/scittle) brings the Small Clojure Interpreter (SCI) to the browser as a script.
174+
;; Scittle interprets ClojureScript without compilation.
175+
;; [Scittle Kitchen](https://timothypratley.github.io/scittle-kitchen/)
176+
;; is a collection of pre-prepared ClojureScript libraries as scittle plugins,
177+
;; including Instaparse.
178+
;; Currently, there are 15 unofficial plugins, including asami, clara-rules, emmy, and geom.
179+
180+
;; ## The Laboratory
181+
182+
;; I include the plugins and [rigid_body.cljs](rigid_body.cljs) in this page as scripts:
183+
184+
(kind/hiccup
185+
[:div
186+
[:script {:src "https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.20.0/matter.min.js"}]
187+
[:script {:src "https://cdn.jsdelivr.net/npm/scittle-kitchen/dist/dev/scittle.js"}]
188+
[:script {:src "https://cdn.jsdelivr.net/npm/scittle-kitchen/dist/dev/scittle.pprint.js"}]
189+
[:script {:src "https://cdn.jsdelivr.net/npm/scittle-kitchen/dist/dev/scittle.instaparse.js"}]
190+
[:script {:src "https://cdn.jsdelivr.net/npm/scittle-kitchen/dist/dev/scittle.cljs-devtools.js"}]
191+
[:script {:type "application/x-scittle"
192+
:src "rigid_body.cljs"}]])
193+
194+
;; This loads Matter.js for the physics simulation,
195+
;; Scittle for ClojureScript evaluation,
196+
;; the Instaparse plugin, development tools, and our custom evaluator.
197+
198+
;; When I save this namespace, Clay reloads the page for me.
199+
;; When I save [rigid_body.cljs](rigid_body.cljs), Clay hot-loads just the code for me.
200+
;; This is a convenient way to mess around with an idea,
201+
;; and produce a working artifact that I can share with people.
202+
203+
;; ## The Universe of Possibilities
204+
205+
;; The Grammar of Graphics influenced data visualization by providing a systematic way to describe visual mappings.
206+
;; Libraries like ggplot2 and Vega became powerful precisely because they gave users a *language* to express their intent.
207+
;; Can we apply this same principle in physics?
208+
209+
;; ::: {.callout-note}
210+
;; In the lab, we build setups to explore principles, test boundaries, and learn by doing.
211+
;; By constructing a grammar and evaluator, we create a playground for ideas.
212+
;; :::
213+
214+
;; A well-thought-out set of grammars for physical simulation setup would enable modeling and experimentation.
215+
;; Consider how experimental physics setups are naturally described as connected components:
216+
;; `laser -> mirror -> beam-splitter -> detector` could define an interferometer setup.
217+
;; The value is in being able to quickly and precisely describe an experiment,
218+
;; making it easy to communicate, share, and reproduce.
219+
;; Imagine chemical reaction networks where
220+
;; `H2 + O2 => H2O [catalyst=Pt, temp=400K]` defines stoichiometry and conditions,
221+
;; or ecosystem models where `grass -> rabbit -> fox` creates food webs with population dynamics.
222+
223+
;; It's possible to build up libraries of functional compositions of the components in such a grammar.
224+
;; With these you could quickly prototype setups by combining and adjusting.
225+
;; This would make it easier to share experimental designs as text,
226+
;; or generate documentation and visualizations.
227+
228+
;; ## The Result
229+
230+
;; The intersection of grammars and physics is worth exploring.
231+
;; Tools like [Scittle](https://github.com/babashka/scittle),
232+
;; [Scittle Kitchen](https://timothypratley.github.io/scittle-kitchen/),
233+
;; and [Clay](https://github.com/scicloj/clay)
234+
;; make it fun to try ideas with minimal setup, from the comfort of my favorite editor.
235+
236+
;; We explored three ideas:
237+
238+
;; **1. Grammar**:
239+
;; By creating a grammar tailored to physics simulation,
240+
;; we make concepts accessible through syntax,
241+
;; and a means of combination.
242+
243+
;; **2. Interpretation**:
244+
;; Our evaluator transforms the parsed AST into physics simulations,
245+
;; applies functions, and makes Matter.js API calls.
246+
247+
;; **3. Uncompiled**:
248+
;; Instaparse enables redefinition of the grammar on the fly,
249+
;; and Scittle-kitchen allows me to use it without compilation.
250+
251+
;; Scittle Kitchen plugins include many other interesting ClojureScript libraries.
252+
;; If grammars aren't your cup of tea, I'm sure you'll find something else of interest.
253+
254+
;; Until next time,
255+
;; keep parsing the possibilities.
256+
257+
;; ![Lasers, splitters, mirrors](rigid_body.png)

0 commit comments

Comments
 (0)