1use alloc::boxed::Box;
10use alloc::string::{String, ToString};
11use alloc::vec::Vec;
12
13use crate::behavior::program::{CExpr, CNode, COp, Program, VarTable};
14use crate::behavior::value::{Arith, Cmp, Val};
15use crate::components::{Behavior, BehaviorExpr, BehaviorNode, BehaviorSource};
16use crate::ecs::{ComponentTag, TracePath, TraceStep};
17
18struct Names<'a> {
20 locals: &'a [String],
21 queries: &'a [String],
22 bindings: Vec<String>,
23 peak: usize,
25}
26
27impl Names<'_> {
28 fn bind(&mut self, name: &str) -> u16 {
29 self.bindings.push(name.to_string());
30 self.peak = self.peak.max(self.bindings.len());
31 (self.bindings.len() - 1) as u16
32 }
33
34 fn binding(&self, name: &str) -> Option<u16> {
35 self.bindings
36 .iter()
37 .rposition(|n| n == name)
38 .map(|i| i as u16)
39 }
40
41 fn local(&self, name: &str) -> Option<u16> {
42 self.locals.iter().position(|n| n == name).map(|i| i as u16)
43 }
44
45 fn query(&self, name: &str) -> Option<u16> {
46 self.queries
47 .iter()
48 .position(|n| n == name)
49 .map(|i| i as u16)
50 }
51}
52
53fn surviving_tag(name: &str) -> Option<u8> {
59 ComponentTag::parse(name)
60 .and_then(ComponentTag::surviving_tag)
61 .map(|t| t as u8)
62}
63
64pub fn compile(def: Behavior, vars: &mut VarTable) -> Program {
67 let scope: Vec<u8> = def.scope.iter().filter_map(|c| surviving_tag(c)).collect();
68 let local_names: Vec<String> = def.locals.iter().map(|l| l.name.clone()).collect();
69 let local_inits: Vec<Val> = def
70 .locals
71 .iter()
72 .map(|l| Val::from_literal(&l.value))
73 .collect();
74 let query_names: Vec<String> = def.queries.iter().map(|q| q.name.clone()).collect();
75 let queries: Vec<Vec<u8>> = def
76 .queries
77 .iter()
78 .map(|q| q.has.iter().filter_map(|c| surviving_tag(c)).collect())
79 .collect();
80
81 if let BehaviorSource::Variable(name) = &def.on {
84 vars.intern(name);
85 }
86
87 let mut names = Names {
88 locals: &local_names,
89 queries: &query_names,
90 bindings: Vec::new(),
91 peak: 0,
92 };
93 let mut paths = Vec::new();
94 let body = compile_nodes(
95 &def.body,
96 &mut names,
97 vars,
98 &[TraceStep::Field("do")],
99 &mut paths,
100 );
101 let bindings = names.peak;
102
103 Program {
104 def,
105 scope,
106 local_inits,
107 queries,
108 body,
109 paths,
110 bindings,
111 }
112}
113
114fn branch(path: &[TraceStep], verb: &'static str, list: &'static str) -> Vec<TraceStep> {
118 let mut base = path.to_vec();
119 base.push(TraceStep::Field(verb));
120 base.push(TraceStep::Field(list));
121 base
122}
123
124fn compile_nodes(
125 nodes: &[BehaviorNode],
126 names: &mut Names<'_>,
127 vars: &mut VarTable,
128 base: &[TraceStep],
129 paths: &mut Vec<TracePath>,
130) -> Vec<CNode> {
131 let depth = names.bindings.len();
132 let out = nodes
133 .iter()
134 .enumerate()
135 .map(|(i, n)| {
136 let mut path = base.to_vec();
137 path.push(TraceStep::Index(i as u32));
138 let id = paths.len() as u32;
141 paths.push(path.clone());
142 let op = compile_node(n, names, vars, &path, paths);
143 CNode { id, op }
144 })
145 .collect();
146 names.bindings.truncate(depth);
147 out
148}
149
150fn compile_node(
151 node: &BehaviorNode,
152 names: &mut Names<'_>,
153 vars: &mut VarTable,
154 path: &[TraceStep],
155 paths: &mut Vec<TracePath>,
156) -> COp {
157 match node {
158 BehaviorNode::If {
159 cond,
160 then,
161 otherwise,
162 } => COp::If {
163 cond: compile_expr(cond, names, vars),
164 then: compile_nodes(then, names, vars, &branch(path, "if", "then"), paths),
165 otherwise: compile_nodes(otherwise, names, vars, &branch(path, "if", "else"), paths),
166 },
167 BehaviorNode::ForEach { query, bind, body } => {
168 let Some(query) = names.query(query) else {
169 return COp::Never;
170 };
171 let depth = names.bindings.len();
172 let bind = names.bind(bind);
173 let body = compile_nodes(body, names, vars, &branch(path, "for_each", "do"), paths);
174 names.bindings.truncate(depth);
175 COp::ForEach { query, bind, body }
176 }
177 BehaviorNode::Let { name, value } => {
178 let value = compile_expr(value, names, vars);
179 COp::Let {
180 bind: names.bind(name),
181 value,
182 }
183 }
184 BehaviorNode::Set { var, value, add } => COp::SetVar {
185 slot: vars.intern(var),
186 value: compile_expr(value, names, vars),
187 add: *add,
188 },
189 BehaviorNode::SetLocal { local, value, add } => match names.local(local) {
190 Some(slot) => COp::SetLocal {
191 slot,
192 value: compile_expr(value, names, vars),
193 add: *add,
194 },
195 None => COp::Never,
196 },
197 BehaviorNode::SetTransform {
198 entity,
199 position,
200 rotation_deg,
201 scale,
202 } => COp::SetTransform {
203 entity: compile_expr(entity, names, vars),
204 position: position.as_ref().map(|e| compile_expr(e, names, vars)),
205 rotation_deg: rotation_deg.as_ref().map(|e| compile_expr(e, names, vars)),
206 scale: scale.as_ref().map(|e| compile_expr(e, names, vars)),
207 },
208 BehaviorNode::Spawn {
209 template,
210 position,
211 rotation_deg,
212 scale,
213 lifetime,
214 bind,
215 } => match template {
216 Some(template) => COp::Spawn {
217 template: *template,
218 position: *position,
219 rotation_deg: *rotation_deg,
220 scale: if *scale == [0.0; 3] { [1.0; 3] } else { *scale },
223 lifetime: *lifetime,
224 bind: bind.as_ref().map(|b| names.bind(b)),
225 },
226 None => COp::Never,
227 },
228 BehaviorNode::Despawn { target } => COp::Despawn(compile_expr(target, names, vars)),
229 BehaviorNode::Reparent { child, parent } => COp::Reparent {
230 child: compile_expr(child, names, vars),
231 parent: parent.as_ref().map(|e| compile_expr(e, names, vars)),
232 },
233 BehaviorNode::Show { target } => COp::Visible(compile_expr(target, names, vars), true),
234 BehaviorNode::Hide { target } => COp::Visible(compile_expr(target, names, vars), false),
235 BehaviorNode::Sound { clip, kind, volume } => match clip {
236 Some(clip) => COp::Sound {
237 clip: *clip,
238 kind: *kind,
239 volume: *volume,
240 },
241 None => COp::Never,
242 },
243 BehaviorNode::Scene { scene, transition } => match scene {
244 Some(scene) => COp::Scene {
245 scene: *scene,
246 transition: transition.clone(),
247 },
248 None => COp::Never,
249 },
250 BehaviorNode::Screen { screen } => match screen {
251 Some(screen) => COp::Screen(*screen),
252 None => COp::Never,
253 },
254 BehaviorNode::Story(playback) => COp::Story(*playback),
255 BehaviorNode::Save => COp::Save,
256 }
257}
258
259fn compile_expr(expr: &BehaviorExpr, names: &mut Names<'_>, vars: &mut VarTable) -> CExpr {
260 let binary =
261 |a: &BehaviorExpr, b: &BehaviorExpr, names: &mut Names<'_>, vars: &mut VarTable| {
262 (
263 Box::new(compile_expr(a, names, vars)),
264 Box::new(compile_expr(b, names, vars)),
265 )
266 };
267 match expr {
268 BehaviorExpr::Bool(b) => CExpr::Lit(Val::Bool(*b)),
269 BehaviorExpr::Int(i) => CExpr::Lit(Val::Int(*i)),
270 BehaviorExpr::Float(f) => CExpr::Lit(Val::Float(*f)),
271 BehaviorExpr::Vec3(v) => CExpr::Lit(Val::Vec3(*v)),
272 BehaviorExpr::Var(name) => CExpr::Var(vars.intern(name)),
273 BehaviorExpr::Local(name) => names.local(name).map_or(CExpr::Never, CExpr::Local),
274 BehaviorExpr::Bind(name) => names.binding(name).map_or(CExpr::Never, CExpr::Bind),
275 BehaviorExpr::Named(id) => id.map_or(CExpr::Never, CExpr::Named),
276 BehaviorExpr::SelfEntity => CExpr::SelfEntity,
277 BehaviorExpr::Dt => CExpr::Dt,
278 BehaviorExpr::Elapsed => CExpr::Elapsed,
279 BehaviorExpr::Position(e) => CExpr::Position(Box::new(compile_expr(e, names, vars))),
280 BehaviorExpr::Alive(e) => CExpr::Alive(Box::new(compile_expr(e, names, vars))),
281 BehaviorExpr::Normalize(e) => CExpr::Normalize(Box::new(compile_expr(e, names, vars))),
282 BehaviorExpr::Not(e) => CExpr::Not(Box::new(compile_expr(e, names, vars))),
283 BehaviorExpr::Distance(a, b) => {
284 let (a, b) = binary(a, b, names, vars);
285 CExpr::Distance(a, b)
286 }
287 BehaviorExpr::First(q) => names.query(q).map_or(CExpr::Never, CExpr::First),
288 BehaviorExpr::Count(q) => names.query(q).map_or(CExpr::Never, CExpr::Count),
289 BehaviorExpr::Add(a, b) => {
290 let (a, b) = binary(a, b, names, vars);
291 CExpr::Arith(Arith::Add, a, b)
292 }
293 BehaviorExpr::Sub(a, b) => {
294 let (a, b) = binary(a, b, names, vars);
295 CExpr::Arith(Arith::Sub, a, b)
296 }
297 BehaviorExpr::Mul(a, b) => {
298 let (a, b) = binary(a, b, names, vars);
299 CExpr::Arith(Arith::Mul, a, b)
300 }
301 BehaviorExpr::Div(a, b) => {
302 let (a, b) = binary(a, b, names, vars);
303 CExpr::Arith(Arith::Div, a, b)
304 }
305 BehaviorExpr::Eq(a, b) => {
306 let (a, b) = binary(a, b, names, vars);
307 CExpr::Compare(Cmp::Eq, a, b)
308 }
309 BehaviorExpr::Ne(a, b) => {
310 let (a, b) = binary(a, b, names, vars);
311 CExpr::Compare(Cmp::Ne, a, b)
312 }
313 BehaviorExpr::Lt(a, b) => {
314 let (a, b) = binary(a, b, names, vars);
315 CExpr::Compare(Cmp::Lt, a, b)
316 }
317 BehaviorExpr::Le(a, b) => {
318 let (a, b) = binary(a, b, names, vars);
319 CExpr::Compare(Cmp::Le, a, b)
320 }
321 BehaviorExpr::Gt(a, b) => {
322 let (a, b) = binary(a, b, names, vars);
323 CExpr::Compare(Cmp::Gt, a, b)
324 }
325 BehaviorExpr::Ge(a, b) => {
326 let (a, b) = binary(a, b, names, vars);
327 CExpr::Compare(Cmp::Ge, a, b)
328 }
329 BehaviorExpr::All(items) => {
330 CExpr::All(items.iter().map(|e| compile_expr(e, names, vars)).collect())
331 }
332 BehaviorExpr::Any(items) => {
333 CExpr::Any(items.iter().map(|e| compile_expr(e, names, vars)).collect())
334 }
335 }
336}
337
338#[cfg(test)]
339mod tests {
340 use super::*;
341 use crate::components::{BehaviorLiteral, BehaviorLocal, BehaviorQuery, CueKind};
342 use crate::ecs::AudioClipHandle;
343 use crate::ecs::asset_id::AssetId;
344 use alloc::vec;
345
346 fn behavior(body: Vec<BehaviorNode>) -> Behavior {
349 Behavior {
350 locals: vec![BehaviorLocal {
351 name: String::from("hp"),
352 value: BehaviorLiteral::Int(3),
353 }],
354 queries: vec![BehaviorQuery {
355 name: String::from("props"),
356 has: vec![String::from("Prop")],
357 }],
358 body,
359 ..Behavior::default()
360 }
361 }
362
363 fn compiled(body: Vec<BehaviorNode>) -> Program {
364 compile(behavior(body), &mut VarTable::default())
365 }
366
367 fn op(node: BehaviorNode) -> COp {
369 let mut body = compiled(vec![node]).body;
370 assert_eq!(body.len(), 1, "expected exactly one compiled node");
371 body.remove(0).op
372 }
373
374 fn expr(e: BehaviorExpr) -> CExpr {
375 match op(BehaviorNode::Despawn { target: e }) {
376 COp::Despawn(e) => e,
377 other => panic!("expected a despawn, got {other:?}"),
378 }
379 }
380
381 fn int(i: i32) -> Box<BehaviorExpr> {
382 Box::new(BehaviorExpr::Int(i))
383 }
384
385 #[test]
389 fn an_unresolvable_name_compiles_to_a_node_that_does_nothing() {
390 assert!(matches!(
391 op(BehaviorNode::ForEach {
392 query: String::from("undeclared"),
393 bind: String::from("it"),
394 body: Vec::new(),
395 }),
396 COp::Never
397 ));
398 assert!(matches!(
399 op(BehaviorNode::SetLocal {
400 local: String::from("undeclared"),
401 value: BehaviorExpr::Int(1),
402 add: false,
403 }),
404 COp::Never
405 ));
406 assert!(matches!(
407 op(BehaviorNode::Spawn {
408 template: None,
409 position: [0.0; 3],
410 rotation_deg: [0.0; 3],
411 scale: [1.0; 3],
412 lifetime: 0.0,
413 bind: None,
414 }),
415 COp::Never
416 ));
417 assert!(matches!(
418 op(BehaviorNode::Sound {
419 clip: None,
420 kind: CueKind::Sound,
421 volume: 1.0,
422 }),
423 COp::Never
424 ));
425 assert!(matches!(
426 op(BehaviorNode::Scene {
427 scene: None,
428 transition: String::from("Cut"),
429 }),
430 COp::Never
431 ));
432 assert!(matches!(
433 op(BehaviorNode::Screen { screen: None }),
434 COp::Never
435 ));
436 }
437
438 #[test]
439 fn a_resolvable_name_compiles_to_the_node_it_denotes() {
440 assert!(matches!(
441 op(BehaviorNode::Sound {
442 clip: Some(AudioClipHandle(2)),
443 kind: CueKind::Music,
444 volume: 0.5,
445 }),
446 COp::Sound {
447 clip: AudioClipHandle(2),
448 kind: CueKind::Music,
449 volume: 0.5,
450 }
451 ));
452 assert!(matches!(
453 op(BehaviorNode::Scene {
454 scene: Some(AssetId(4)),
455 transition: String::from("FadeBlack"),
456 }),
457 COp::Scene {
458 scene: AssetId(4),
459 ..
460 }
461 ));
462 assert!(matches!(
463 op(BehaviorNode::Screen {
464 screen: Some(AssetId(5)),
465 }),
466 COp::Screen(AssetId(5))
467 ));
468 }
469
470 #[test]
473 fn a_zero_scaled_spawn_compiles_to_unit_scale() {
474 let spawn = |scale| {
475 op(BehaviorNode::Spawn {
476 template: Some(AssetId(1)),
477 position: [1.0, 2.0, 3.0],
478 rotation_deg: [0.0; 3],
479 scale,
480 lifetime: 2.0,
481 bind: Some(String::from("copy")),
482 })
483 };
484 let COp::Spawn { scale, bind, .. } = spawn([0.0; 3]) else {
485 panic!("expected a spawn");
486 };
487 assert_eq!(scale, [1.0; 3]);
488 assert_eq!(bind, Some(0));
489
490 let COp::Spawn { scale, .. } = spawn([2.0; 3]) else {
491 panic!("expected a spawn");
492 };
493 assert_eq!(scale, [2.0; 3]);
494 }
495
496 #[test]
497 fn show_and_hide_compile_to_the_same_node_with_opposite_visibility() {
498 let target = || BehaviorExpr::Named(Some(AssetId(1)));
499 assert!(matches!(
500 op(BehaviorNode::Show { target: target() }),
501 COp::Visible(_, true)
502 ));
503 assert!(matches!(
504 op(BehaviorNode::Hide { target: target() }),
505 COp::Visible(_, false)
506 ));
507 }
508
509 #[test]
510 fn a_reparent_compiles_both_its_parent_forms() {
511 assert!(matches!(
512 op(BehaviorNode::Reparent {
513 child: BehaviorExpr::SelfEntity,
514 parent: Some(BehaviorExpr::Named(Some(AssetId(1)))),
515 }),
516 COp::Reparent {
517 parent: Some(_),
518 ..
519 }
520 ));
521 assert!(matches!(
522 op(BehaviorNode::Reparent {
523 child: BehaviorExpr::SelfEntity,
524 parent: None,
525 }),
526 COp::Reparent { parent: None, .. }
527 ));
528 }
529
530 #[test]
531 fn a_local_and_the_tick_readings_compile_to_their_slots() {
532 assert!(matches!(
533 expr(BehaviorExpr::Local(String::from("hp"))),
534 CExpr::Local(0)
535 ));
536 assert!(matches!(
537 expr(BehaviorExpr::Local(String::from("undeclared"))),
538 CExpr::Never
539 ));
540 assert!(matches!(expr(BehaviorExpr::Dt), CExpr::Dt));
541 assert!(matches!(expr(BehaviorExpr::Elapsed), CExpr::Elapsed));
542 }
543
544 #[test]
545 fn the_unary_expressions_compile_to_their_operators() {
546 assert!(matches!(
547 expr(BehaviorExpr::Normalize(int(1))),
548 CExpr::Normalize(_)
549 ));
550 assert!(matches!(expr(BehaviorExpr::Not(int(1))), CExpr::Not(_)));
551 }
552
553 #[test]
554 fn every_arithmetic_operator_compiles_to_its_own_kind() {
555 let arith = |e| match expr(e) {
556 CExpr::Arith(op, _, _) => op,
557 other => panic!("expected arithmetic, got {other:?}"),
558 };
559 assert!(matches!(
560 arith(BehaviorExpr::Add(int(1), int(2))),
561 Arith::Add
562 ));
563 assert!(matches!(
564 arith(BehaviorExpr::Sub(int(1), int(2))),
565 Arith::Sub
566 ));
567 assert!(matches!(
568 arith(BehaviorExpr::Mul(int(1), int(2))),
569 Arith::Mul
570 ));
571 assert!(matches!(
572 arith(BehaviorExpr::Div(int(1), int(2))),
573 Arith::Div
574 ));
575 }
576
577 #[test]
578 fn every_comparison_operator_compiles_to_its_own_kind() {
579 let cmp = |e| match expr(e) {
580 CExpr::Compare(op, _, _) => op,
581 other => panic!("expected a comparison, got {other:?}"),
582 };
583 assert!(matches!(cmp(BehaviorExpr::Eq(int(1), int(2))), Cmp::Eq));
584 assert!(matches!(cmp(BehaviorExpr::Ne(int(1), int(2))), Cmp::Ne));
585 assert!(matches!(cmp(BehaviorExpr::Lt(int(1), int(2))), Cmp::Lt));
586 assert!(matches!(cmp(BehaviorExpr::Le(int(1), int(2))), Cmp::Le));
587 assert!(matches!(cmp(BehaviorExpr::Gt(int(1), int(2))), Cmp::Gt));
588 assert!(matches!(cmp(BehaviorExpr::Ge(int(1), int(2))), Cmp::Ge));
589 }
590
591 #[test]
592 fn the_variadic_expressions_compile_each_operand() {
593 let items = || vec![BehaviorExpr::Bool(true), BehaviorExpr::Dt];
594 let CExpr::All(all) = expr(BehaviorExpr::All(items())) else {
595 panic!("expected an all");
596 };
597 assert_eq!(all.len(), 2);
598 let CExpr::Any(any) = expr(BehaviorExpr::Any(items())) else {
599 panic!("expected an any");
600 };
601 assert_eq!(any.len(), 2);
602 }
603}