1use nom::{
18 IResult, Parser,
19 branch::alt,
20 bytes::complete::{tag, take_until, take_while, take_while_m_n, take_while1},
21 character::complete::{char as c_char, multispace1},
22 combinator::{cut, map, opt, verify},
23 error::{Error, ErrorKind},
24 multi::many0,
25 number::complete::double,
26 sequence::{delimited, preceded, terminated},
27};
28
29use crate::error::ShapeError;
30use crate::expr::{Expr, MAX_EXPR_NODES, parse_expr};
31use crate::ops::{
32 AttachCase, AttachSelector, Axis, CarveCase, CarveSelector, CompFaceCase, CompTarget,
33 FaceSelector, FitCandidate, OffsetCase, OffsetSelector, RoofCase, RoofFaceSelector, RoofSpec,
34 RoofType, RuleCall, RuleVariant, ShapeOp, SplitEntry, SplitSize, SplitSlot, VariantSelector,
35};
36use crate::scope::Vec3;
37
38const MAX_SPLIT_SLOTS: usize = 256;
40const MAX_COMP_CASES: usize = 32;
41const MAX_OPS: usize = 1024;
42const MAX_IDENTIFIER_LEN: usize = 64;
43pub const MAX_VARIANTS: usize = 64;
47pub const MAX_RULE_ARGS: usize = 16;
49
50pub(crate) fn space_or_comment<'a, E: nom::error::ParseError<&'a str>>(
53 input: &'a str,
54) -> IResult<&'a str, (), E> {
55 let comment = alt((
56 preceded(tag("/*"), terminated(take_until("*/"), tag("*/"))),
57 preceded(tag("//"), take_while(|c: char| c != '\n' && c != '\r')),
60 ));
61 let mut p = many0(alt((map(multispace1, |_| ()), map(comment, |_| ()))));
62 p.parse(input).map(|(i, _)| (i, ()))
63}
64
65fn ws<'a, F, O, E: nom::error::ParseError<&'a str>>(
66 inner: F,
67) -> impl Parser<&'a str, Output = O, Error = E>
68where
69 F: Parser<&'a str, Output = O, Error = E>,
70{
71 delimited(space_or_comment, inner, space_or_comment)
72}
73
74fn finite_float(input: &str) -> IResult<&str, f64> {
77 verify(double, |x: &f64| x.is_finite()).parse(input)
78}
79
80fn is_ident_start(c: char) -> bool {
81 c.is_alphabetic() || c == '_'
82}
83
84fn is_ident_char(c: char) -> bool {
85 c.is_alphanumeric() || c == '_'
86}
87
88pub(crate) fn identifier(input: &str) -> IResult<&str, &str> {
89 let original = input;
90 let (input, s) = take_while1(is_ident_char).parse(input)?;
91 if !s.chars().next().map(is_ident_start).unwrap_or(false) {
92 return Err(nom::Err::Error(Error::new(original, ErrorKind::Alpha)));
95 }
96 if s.len() > MAX_IDENTIFIER_LEN {
97 return Err(nom::Err::Failure(Error::new(input, ErrorKind::TooLarge)));
98 }
99 Ok((input, s))
100}
101
102fn rule_name(input: &str) -> IResult<&str, String> {
108 alt((
109 map(
110 delimited(
111 c_char('"'),
112 take_while_m_n(0, MAX_IDENTIFIER_LEN, |c: char| c != '"'),
113 c_char('"'),
114 ),
115 |s: &str| s.to_string(),
116 ),
117 map(ws(identifier), |s: &str| s.to_string()),
118 ))
119 .parse(input)
120}
121
122fn keyword<'a>(
127 kw: &'static str,
128) -> impl FnMut(&'a str) -> IResult<&'a str, &'a str, Error<&'a str>> {
129 move |input: &'a str| {
130 let (rest, m) = tag::<_, _, Error<&str>>(kw).parse(input)?;
131 if rest.chars().next().is_some_and(is_ident_char) {
132 return Err(nom::Err::Error(Error::new(input, ErrorKind::Tag)));
133 }
134 Ok((rest, m))
135 }
136}
137
138fn arg_expr(input: &str) -> IResult<&str, Expr> {
141 let (rest, e) = parse_expr(input)?;
142 if e.node_count() > MAX_EXPR_NODES {
143 return Err(nom::Err::Failure(Error::new(input, ErrorKind::TooLarge)));
144 }
145 Ok((rest, e))
146}
147
148fn parse_rule_call(input: &str) -> IResult<&str, RuleCall> {
154 let (input, name) = rule_name(input)?;
155 let Ok((mut rem, _)) = ws::<_, _, Error<&str>>(c_char('(')).parse(input) else {
157 return Ok((input, RuleCall::new(name)));
158 };
159 let mut args = Vec::new();
160 if let Ok((after, _)) = ws::<_, _, Error<&str>>(c_char(')')).parse(rem) {
161 return Ok((after, RuleCall::with_args(name, args)));
162 }
163 loop {
164 let (after_arg, arg) = arg_expr(rem)?;
165 args.push(arg);
166 if args.len() > MAX_RULE_ARGS {
167 return Err(nom::Err::Failure(Error::new(
168 after_arg,
169 ErrorKind::TooLarge,
170 )));
171 }
172 if let Ok((after, _)) = ws::<_, _, Error<&str>>(c_char(',')).parse(after_arg) {
173 rem = after;
174 continue;
175 }
176 let (after, _) = cut(ws(c_char(')'))).parse(after_arg)?;
177 rem = after;
178 break;
179 }
180 Ok((rem, RuleCall::with_args(name, args)))
181}
182
183fn parse_axis(input: &str) -> IResult<&str, Axis> {
186 alt((
187 map(tag("X"), |_| Axis::X),
188 map(tag("Y"), |_| Axis::Y),
189 map(tag("Z"), |_| Axis::Z),
190 ))
191 .parse(input)
192}
193
194fn parse_expr3(input: &str) -> IResult<&str, [Expr; 3]> {
198 let (input, _) = ws(c_char('(')).parse(input)?;
199 let (input, x) = arg_expr(input)?;
200 let (input, _) = ws(c_char(',')).parse(input)?;
201 let (input, y) = arg_expr(input)?;
202 let (input, _) = ws(c_char(',')).parse(input)?;
203 let (input, z) = arg_expr(input)?;
204 let (input, _) = ws(c_char(')')).parse(input)?;
205 Ok((input, [x, y, z]))
206}
207
208fn parse_expr4(input: &str) -> IResult<&str, [Expr; 4]> {
212 let (input, _) = ws(c_char('(')).parse(input)?;
213 let (input, w) = arg_expr(input)?;
214 let (input, _) = ws(c_char(',')).parse(input)?;
215 let (input, x) = arg_expr(input)?;
216 let (input, _) = ws(c_char(',')).parse(input)?;
217 let (input, y) = arg_expr(input)?;
218 let (input, _) = ws(c_char(',')).parse(input)?;
219 let (input, z) = arg_expr(input)?;
220 let (input, _) = ws(c_char(')')).parse(input)?;
221 if let (Some(wv), Some(xv), Some(yv), Some(zv)) =
222 (w.as_lit(), x.as_lit(), y.as_lit(), z.as_lit())
223 {
224 let len_sq = xv * xv + yv * yv + zv * zv + wv * wv;
225 if !len_sq.is_finite() || len_sq < 1e-12 {
226 return Err(nom::Err::Failure(Error::new(input, ErrorKind::Verify)));
227 }
228 }
229 Ok((input, [w, x, y, z]))
230}
231
232fn parse_split_size(input: &str) -> IResult<&str, SplitSize> {
237 if let Ok((rest, _)) = c_char::<_, Error<&str>>('~').parse(input) {
238 let (rest, e) = arg_expr(rest)?;
239 return Ok((rest, SplitSize::Floating(e)));
240 }
241 if let Ok((rest, _)) = c_char::<_, Error<&str>>('\'').parse(input) {
242 let (rest, e) = arg_expr(rest)?;
243 return Ok((rest, SplitSize::Relative(e)));
244 }
245 let (rest, e) = arg_expr(input)?;
246 Ok((rest, SplitSize::Absolute(e)))
247}
248
249fn parse_split_slot(input: &str) -> IResult<&str, SplitSlot> {
251 let (input, size) = ws(parse_split_size).parse(input)?;
252 let (input, _) = ws(c_char(':')).parse(input)?;
253 let (input, rule) = ws(parse_rule_call).parse(input)?;
254 Ok((input, SplitSlot { size, rule }))
255}
256
257fn parse_comp_face_case(input: &str) -> IResult<&str, CompFaceCase> {
260 let (input, sel_str) = ws(identifier).parse(input)?;
261 let selector = FaceSelector::parse(sel_str)
262 .ok_or_else(|| nom::Err::Failure(Error::new(input, ErrorKind::Tag)))?;
263 let (input, _) = ws(c_char(':')).parse(input)?;
264 let (input, rule) = ws(parse_rule_call).parse(input)?;
265 Ok((input, CompFaceCase { selector, rule }))
266}
267
268fn parse_extrude(input: &str) -> IResult<&str, ShapeOp> {
271 let (input, _) = keyword("Extrude")(input)?;
272 let (input, h) = cut(delimited(ws(c_char('(')), arg_expr, ws(c_char(')')))).parse(input)?;
273 if let Some(v) = h.as_lit()
276 && v <= 0.0
277 {
278 return Err(nom::Err::Failure(Error::new(input, ErrorKind::Verify)));
279 }
280 Ok((input, ShapeOp::Extrude(h)))
281}
282
283fn parse_taper(input: &str) -> IResult<&str, ShapeOp> {
284 let (input, _) = keyword("Taper")(input)?;
285 let (input, amount) =
286 cut(delimited(ws(c_char('(')), arg_expr, ws(c_char(')')))).parse(input)?;
287 if let Some(v) = amount.as_lit()
288 && !(0.0..=1.0).contains(&v)
289 {
290 return Err(nom::Err::Failure(Error::new(input, ErrorKind::Verify)));
291 }
292 Ok((input, ShapeOp::Taper(amount)))
293}
294
295fn parse_rotate(input: &str) -> IResult<&str, ShapeOp> {
296 let (input, _) = keyword("Rotate")(input)?;
297 let (input, q) = cut(ws(parse_expr4)).parse(input)?;
298 Ok((input, ShapeOp::Rotate(q)))
299}
300
301fn parse_translate(input: &str) -> IResult<&str, ShapeOp> {
302 let (input, _) = keyword("Translate")(input)?;
303 let (input, v) = cut(ws(parse_expr3)).parse(input)?;
304 Ok((input, ShapeOp::Translate(v)))
305}
306
307fn parse_scale(input: &str) -> IResult<&str, ShapeOp> {
308 let (input, _) = keyword("Scale")(input)?;
309 let (input, v) = cut(ws(parse_expr3)).parse(input)?;
310 for c in &v {
311 if let Some(lit) = c.as_lit()
312 && lit <= 0.0
313 {
314 return Err(nom::Err::Failure(Error::new(input, ErrorKind::Verify)));
315 }
316 }
317 Ok((input, ShapeOp::Scale(v)))
318}
319
320fn parse_split_snap(input: &str) -> IResult<&str, Option<crate::ops::SnapBinding>> {
324 let Ok((after_comma, _)) = ws::<_, _, Error<&str>>(c_char(',')).parse(input) else {
326 return Ok((input, None));
327 };
328 let Ok((after_eq, _)) = (
329 ws::<_, _, Error<&str>>(tag("snap")),
330 ws::<_, _, Error<&str>>(c_char('=')),
331 )
332 .parse(after_comma)
333 else {
334 return Ok((input, None));
335 };
336 let (after_label, label) = cut(ws(rule_name)).parse(after_eq)?;
337 let mut remaining = after_label;
339 let mut tolerance: Option<f64> = None;
340 if let Ok((after_c, _)) = ws::<_, _, Error<&str>>(c_char(',')).parse(remaining)
341 && let Ok((after_t_eq, _)) = (
342 ws::<_, _, Error<&str>>(tag("tol")),
343 ws::<_, _, Error<&str>>(c_char('=')),
344 )
345 .parse(after_c)
346 {
347 let (after_v, v) = cut(ws(finite_float)).parse(after_t_eq)?;
348 if v < 0.0 {
349 return Err(nom::Err::Failure(Error::new(after_v, ErrorKind::Verify)));
350 }
351 tolerance = Some(v);
352 remaining = after_v;
353 }
354 Ok((
355 remaining,
356 Some(crate::ops::SnapBinding { label, tolerance }),
357 ))
358}
359
360fn parse_split_group(input: &str) -> IResult<&str, Vec<SplitSlot>> {
362 let (input, _) = ws(c_char('{')).parse(input)?;
363 let (mut remaining, first) = cut(ws(parse_split_slot)).parse(input)?;
364 let mut slots = vec![first];
365 loop {
366 if slots.len() >= MAX_SPLIT_SLOTS {
367 return Err(nom::Err::Failure(Error::new(
368 remaining,
369 ErrorKind::TooLarge,
370 )));
371 }
372 let Ok((after_sep, _)) = ws::<_, _, Error<&str>>(c_char('|')).parse(remaining) else {
373 break;
374 };
375 let (after_item, slot) = cut(ws(parse_split_slot)).parse(after_sep)?;
376 slots.push(slot);
377 remaining = after_item;
378 }
379 let (remaining, _) = cut(ws(c_char('}'))).parse(remaining)?;
380 let (remaining, _) = cut(ws(c_char('*'))).parse(remaining)?;
381 Ok((remaining, slots))
382}
383
384fn parse_split_entry(input: &str) -> IResult<&str, SplitEntry> {
386 if input.trim_start().starts_with('{') {
387 let (rest, group) = parse_split_group(input)?;
388 return Ok((rest, SplitEntry::Group(group)));
389 }
390 let (rest, slot) = parse_split_slot(input)?;
391 Ok((rest, SplitEntry::Slot(slot)))
392}
393
394fn parse_split(input: &str) -> IResult<&str, ShapeOp> {
395 let (input, _) = keyword("Split")(input)?;
396 let (input, _) = cut(ws(c_char('('))).parse(input)?;
398 let (input, axis) = cut(ws(parse_axis)).parse(input)?;
399 let (input, snap) = parse_split_snap(input)?;
400 let (input, _) = cut(ws(c_char(')'))).parse(input)?;
401 let (input, _) = cut(ws(c_char('{'))).parse(input)?;
402 let (mut remaining, first) = cut(ws(parse_split_entry)).parse(input)?;
404 let mut entries = vec![first];
405 loop {
406 if entries.len() >= MAX_SPLIT_SLOTS {
407 return Err(nom::Err::Failure(Error::new(
408 remaining,
409 ErrorKind::TooLarge,
410 )));
411 }
412 let Ok((after_sep, _)) = ws::<_, _, Error<&str>>(c_char('|')).parse(remaining) else {
413 break;
414 };
415 match ws(parse_split_entry).parse(after_sep) {
416 Ok((after_item, entry)) => {
417 entries.push(entry);
418 remaining = after_item;
419 }
420 Err(nom::Err::Failure(e)) => return Err(nom::Err::Failure(e)),
421 Err(_) => {
422 remaining = after_sep; break;
424 }
425 }
426 }
427 let (remaining, _) = ws(c_char('}')).parse(remaining)?;
428 if entries
430 .iter()
431 .filter(|e| matches!(e, SplitEntry::Group(_)))
432 .count()
433 > 1
434 {
435 return Err(nom::Err::Failure(Error::new(remaining, ErrorKind::Verify)));
436 }
437 Ok((
438 remaining,
439 ShapeOp::Split {
440 axis,
441 entries,
442 snap,
443 },
444 ))
445}
446
447fn parse_split_area(input: &str) -> IResult<&str, ShapeOp> {
449 let (input, _) = keyword("SplitArea")(input)?;
450 let (input, _) = cut(ws(c_char('('))).parse(input)?;
451 let (input, axis) = cut(ws(parse_axis)).parse(input)?;
452 if axis == Axis::Y {
453 return Err(nom::Err::Failure(Error::new(input, ErrorKind::Verify)));
454 }
455 let (input, _) = cut(ws(c_char(')'))).parse(input)?;
456 let (input, _) = cut(ws(c_char('{'))).parse(input)?;
457 let (mut remaining, first) = cut(ws(parse_split_slot)).parse(input)?;
458 let mut slots = vec![first];
459 loop {
460 if slots.len() >= MAX_SPLIT_SLOTS {
461 return Err(nom::Err::Failure(Error::new(
462 remaining,
463 ErrorKind::TooLarge,
464 )));
465 }
466 let Ok((after_sep, _)) = ws::<_, _, Error<&str>>(c_char('|')).parse(remaining) else {
467 break;
468 };
469 let (after_item, slot) = cut(ws(parse_split_slot)).parse(after_sep)?;
470 slots.push(slot);
471 remaining = after_item;
472 }
473 let (remaining, _) = ws(c_char('}')).parse(remaining)?;
474 Ok((remaining, ShapeOp::SplitArea { axis, slots }))
475}
476
477fn parse_fit(input: &str) -> IResult<&str, ShapeOp> {
480 let (input, _) = keyword("Fit")(input)?;
481 let (input, _) = cut(ws(c_char('('))).parse(input)?;
482 let (input, axis) = cut(ws(parse_axis)).parse(input)?;
483 let (input, _) = cut(ws(c_char(')'))).parse(input)?;
484 let (input, _) = cut(ws(c_char('{'))).parse(input)?;
485 let mut candidates = Vec::new();
486 let mut remaining = input;
487 loop {
488 if candidates.len() >= MAX_SPLIT_SLOTS {
489 return Err(nom::Err::Failure(Error::new(
490 remaining,
491 ErrorKind::TooLarge,
492 )));
493 }
494 let (after_min, min_size) = cut(ws(arg_expr)).parse(remaining)?;
495 let (after_colon, _) = cut(ws(c_char(':'))).parse(after_min)?;
496 let (after_rule, rule) = cut(ws(parse_rule_call)).parse(after_colon)?;
497 candidates.push(FitCandidate { min_size, rule });
498 if let Ok((after_sep, _)) = ws::<_, _, Error<&str>>(c_char('|')).parse(after_rule) {
499 remaining = after_sep;
500 continue;
501 }
502 remaining = after_rule;
503 break;
504 }
505 let (remaining, _) = cut(ws(c_char('}'))).parse(remaining)?;
506 Ok((remaining, ShapeOp::Fit { axis, candidates }))
507}
508
509fn parse_reg_snap(input: &str) -> IResult<&str, ShapeOp> {
511 let (input, _) = keyword("RegSnap")(input)?;
512 let (input, _) = cut(ws(c_char('('))).parse(input)?;
513 let (input, label) = cut(ws(rule_name)).parse(input)?;
514 let (input, _) = cut(ws(c_char(')'))).parse(input)?;
515 Ok((input, ShapeOp::RegSnap(label)))
516}
517
518fn parse_occlusion_label(input: &str) -> IResult<&str, Option<String>> {
520 let Ok((rest, _)) = ws::<_, _, Error<&str>>(c_char('(')).parse(input) else {
521 return Ok((input, None));
522 };
523 let (rest, label) = cut(ws(rule_name)).parse(rest)?;
524 let (rest, _) = cut(ws(c_char(')'))).parse(rest)?;
525 Ok((rest, Some(label)))
526}
527
528fn parse_occlusion_body(input: &str) -> IResult<&str, (RuleCall, Option<String>)> {
530 let (input, label) = parse_occlusion_label(input)?;
531 let (input, _) = cut(ws(c_char('{'))).parse(input)?;
532 let (input, rule) = cut(ws(parse_rule_call)).parse(input)?;
533 let (input, _) = cut(ws(c_char('}'))).parse(input)?;
534 Ok((input, (rule, label)))
535}
536
537fn parse_if_clear(input: &str) -> IResult<&str, ShapeOp> {
539 let (input, _) = keyword("IfClear")(input)?;
540 let (input, (rule, label)) = parse_occlusion_body(input)?;
541 Ok((input, ShapeOp::IfClear { rule, label }))
542}
543
544fn parse_if_occluded(input: &str) -> IResult<&str, ShapeOp> {
546 let (input, _) = keyword("IfOccluded")(input)?;
547 let (input, (rule, label)) = parse_occlusion_body(input)?;
548 Ok((input, ShapeOp::IfOccluded { rule, label }))
549}
550
551fn parse_if_inside(input: &str) -> IResult<&str, ShapeOp> {
553 let (input, _) = keyword("IfInside")(input)?;
554 let (input, (rule, label)) = parse_occlusion_body(input)?;
555 Ok((input, ShapeOp::IfInside { rule, label }))
556}
557
558fn parse_if_touches(input: &str) -> IResult<&str, ShapeOp> {
560 let (input, _) = keyword("IfTouches")(input)?;
561 let (input, (rule, label)) = parse_occlusion_body(input)?;
562 Ok((input, ShapeOp::IfTouches { rule, label }))
563}
564
565fn parse_label(input: &str) -> IResult<&str, ShapeOp> {
567 let (input, _) = keyword("Label")(input)?;
568 let (input, _) = cut(ws(c_char('('))).parse(input)?;
569 let (input, label) = cut(ws(rule_name)).parse(input)?;
570 let (input, _) = cut(ws(c_char(')'))).parse(input)?;
571 Ok((input, ShapeOp::Label(label)))
572}
573
574fn parse_pick(input: &str) -> IResult<&str, ShapeOp> {
576 let (input, _) = keyword("Pick")(input)?;
577 let (input, _) = cut(ws(c_char('('))).parse(input)?;
578 let (input, key) = cut(ws(rule_name)).parse(input)?;
579 let (input, _) = cut(ws(c_char(')'))).parse(input)?;
580 let (input, _) = cut(ws(c_char('{'))).parse(input)?;
581 let mut choices = Vec::new();
582 let mut remaining = input;
583 loop {
584 if choices.len() >= MAX_VARIANTS {
585 return Err(nom::Err::Failure(Error::new(
586 remaining,
587 ErrorKind::TooLarge,
588 )));
589 }
590 let (after_w, w) = cut(ws(parse_weight_prefix)).parse(remaining)?;
591 if !w.is_finite() || w < 0.0 {
592 return Err(nom::Err::Failure(Error::new(after_w, ErrorKind::Verify)));
593 }
594 let (after_rule, rule) = cut(ws(parse_rule_call)).parse(after_w)?;
595 choices.push((w / 100.0, rule));
596 if let Ok((after_sep, _)) = ws::<_, _, Error<&str>>(c_char('|')).parse(after_rule) {
597 remaining = after_sep;
598 continue;
599 }
600 remaining = after_rule;
601 break;
602 }
603 let (remaining, _) = cut(ws(c_char('}'))).parse(remaining)?;
604 Ok((remaining, ShapeOp::Pick { key, choices }))
605}
606
607fn parse_scatter(input: &str) -> IResult<&str, ShapeOp> {
609 let (input, _) = keyword("Scatter")(input)?;
610 let (input, _) = cut(ws(c_char('('))).parse(input)?;
611 let (input, kind) = cut(ws(identifier)).parse(input)?;
612 let volume = match kind {
613 "Top" | "top" => false,
614 "Volume" | "volume" => true,
615 _ => return Err(nom::Err::Failure(Error::new(input, ErrorKind::Tag))),
616 };
617 let (input, _) = cut(ws(c_char(','))).parse(input)?;
618 let (input, count) = cut(arg_expr).parse(input)?;
619 let (input, _) = cut(ws(c_char(')'))).parse(input)?;
620 let (input, _) = cut(ws(c_char('{'))).parse(input)?;
621 let (input, rule) = cut(ws(parse_rule_call)).parse(input)?;
622 let (input, _) = cut(ws(c_char('}'))).parse(input)?;
623 Ok((
624 input,
625 ShapeOp::Scatter {
626 volume,
627 count,
628 rule,
629 },
630 ))
631}
632
633const MAX_REPEAT_TILE_SIZES: usize = 256;
636
637fn check_tile_lit<'a>(e: &Expr, at: &'a str) -> Result<(), nom::Err<Error<&'a str>>> {
640 if let Some(v) = e.as_lit()
641 && v <= 0.0
642 {
643 return Err(nom::Err::Failure(Error::new(at, ErrorKind::Verify)));
644 }
645 Ok(())
646}
647
648fn parse_repeat_tile_sizes(input: &str) -> IResult<&str, Vec<Expr>> {
652 if let Ok((rest, _)) = ws::<_, _, Error<&str>>(c_char('[')).parse(input) {
654 let (rest, first) = cut(arg_expr).parse(rest)?;
655 check_tile_lit(&first, rest)?;
656 let mut sizes = vec![first];
657 let mut remaining = rest;
658 loop {
659 if sizes.len() >= MAX_REPEAT_TILE_SIZES {
660 return Err(nom::Err::Failure(Error::new(
661 remaining,
662 ErrorKind::TooLarge,
663 )));
664 }
665 let Ok((after_comma, _)) = ws::<_, _, Error<&str>>(c_char(',')).parse(remaining) else {
666 break;
667 };
668 let (after_num, v) = cut(arg_expr).parse(after_comma)?;
669 check_tile_lit(&v, after_num)?;
670 sizes.push(v);
671 remaining = after_num;
672 }
673 let (remaining, _) = cut(ws(c_char(']'))).parse(remaining)?;
674 return Ok((remaining, sizes));
675 }
676 let (rest, v) = arg_expr(input)?;
678 check_tile_lit(&v, rest)?;
679 Ok((rest, vec![v]))
680}
681
682fn parse_repeat(input: &str) -> IResult<&str, ShapeOp> {
683 let (input, _) = keyword("Repeat")(input)?;
684 let (input, _) = cut(ws(c_char('('))).parse(input)?;
686 let (input, axis) = cut(ws(parse_axis)).parse(input)?;
687 let (input, _) = cut(ws(c_char(','))).parse(input)?;
688 let (input, tile_sizes) = cut(parse_repeat_tile_sizes).parse(input)?;
689 let (input, _) = cut(ws(c_char(')'))).parse(input)?;
690 let (input, _) = cut(ws(c_char('{'))).parse(input)?;
691 let (input, rule) = cut(ws(parse_rule_call)).parse(input)?;
692 let (input, _) = cut(ws(c_char('}'))).parse(input)?;
693 Ok((
694 input,
695 ShapeOp::Repeat {
696 axis,
697 tile_sizes,
698 rule,
699 },
700 ))
701}
702
703fn parse_comp_edge_case(input: &str) -> IResult<&str, crate::ops::CompEdgeCase> {
704 let (input, sel_str) = ws(identifier).parse(input)?;
705 let selector = crate::ops::EdgeSelector::parse(sel_str)
706 .ok_or_else(|| nom::Err::Failure(Error::new(input, ErrorKind::Tag)))?;
707 let (input, _) = ws(c_char(':')).parse(input)?;
708 let (input, rule) = ws(parse_rule_call).parse(input)?;
709 Ok((input, crate::ops::CompEdgeCase { selector, rule }))
710}
711
712fn parse_comp(input: &str) -> IResult<&str, ShapeOp> {
713 let (input, _) = keyword("Comp")(input)?;
714 let (input, _) = cut(ws(c_char('('))).parse(input)?;
716 let (input, kind) = cut(ws(identifier)).parse(input)?;
717 let edges = match kind {
718 "Faces" | "faces" => false,
719 "Edges" | "edges" => true,
720 _ => return Err(nom::Err::Failure(Error::new(input, ErrorKind::Tag))),
721 };
722 let (input, _) = cut(ws(c_char(')'))).parse(input)?;
723 let (input, _) = cut(ws(c_char('{'))).parse(input)?;
724 if edges {
725 let (mut remaining, first) = cut(ws(parse_comp_edge_case)).parse(input)?;
726 let mut cases = vec![first];
727 loop {
728 if cases.len() >= MAX_COMP_CASES {
729 return Err(nom::Err::Failure(Error::new(
730 remaining,
731 ErrorKind::TooLarge,
732 )));
733 }
734 let Ok((after_sep, _)) = ws::<_, _, Error<&str>>(c_char('|')).parse(remaining) else {
735 break;
736 };
737 match ws(parse_comp_edge_case).parse(after_sep) {
738 Ok((after_item, case)) => {
739 cases.push(case);
740 remaining = after_item;
741 }
742 Err(nom::Err::Failure(e)) => return Err(nom::Err::Failure(e)),
743 Err(_) => {
744 remaining = after_sep;
745 break;
746 }
747 }
748 }
749 let (remaining, _) = ws(c_char('}')).parse(remaining)?;
750 return Ok((remaining, ShapeOp::Comp(CompTarget::Edges(cases))));
751 }
752 let (mut remaining, first) = cut(ws(parse_comp_face_case)).parse(input)?;
754 let mut cases = vec![first];
755 loop {
756 if cases.len() >= MAX_COMP_CASES {
757 return Err(nom::Err::Failure(Error::new(
758 remaining,
759 ErrorKind::TooLarge,
760 )));
761 }
762 let Ok((after_sep, _)) = ws::<_, _, Error<&str>>(c_char('|')).parse(remaining) else {
763 break;
764 };
765 match ws(parse_comp_face_case).parse(after_sep) {
766 Ok((after_item, case)) => {
767 cases.push(case);
768 remaining = after_item;
769 }
770 Err(nom::Err::Failure(e)) => return Err(nom::Err::Failure(e)),
771 Err(_) => {
772 remaining = after_sep; break;
774 }
775 }
776 }
777 let (remaining, _) = ws(c_char('}')).parse(remaining)?;
778 Ok((remaining, ShapeOp::Comp(CompTarget::Faces(cases))))
779}
780
781fn parse_instance(input: &str) -> IResult<&str, ShapeOp> {
782 let (input, _) = tag::<_, _, Error<&str>>("I").parse(input)?;
783 if input.chars().next().is_some_and(is_ident_char) {
785 return Err(nom::Err::Error(Error::new(input, ErrorKind::Tag)));
786 }
787 let (input, mesh_id) =
788 cut(delimited(ws(c_char('(')), ws(rule_name), ws(c_char(')')))).parse(input)?;
789 Ok((input, ShapeOp::I(mesh_id)))
790}
791
792const MAX_POLYGON_VERTICES: usize = 256;
794
795fn parse_vec2(input: &str) -> IResult<&str, glam::DVec2> {
799 let (input, _) = ws(c_char('(')).parse(input)?;
800 let (input, x) = ws(finite_float).parse(input)?;
801 let (input, _) = ws(c_char(',')).parse(input)?;
802 let (input, y) = ws(finite_float).parse(input)?;
803 let (input, _) = ws(c_char(')')).parse(input)?;
804 Ok((input, glam::DVec2::new(x, y)))
805}
806
807fn parse_polygon(input: &str) -> IResult<&str, ShapeOp> {
808 let (input, _) = keyword("Polygon")(input)?;
809 let (input, _) = cut(ws(c_char('('))).parse(input)?;
810 let (input, first) = cut(ws(parse_vec2)).parse(input)?;
811 let mut verts = vec![first];
812 let mut remaining = input;
813 loop {
814 if verts.len() >= MAX_POLYGON_VERTICES {
815 return Err(nom::Err::Failure(Error::new(
816 remaining,
817 ErrorKind::TooLarge,
818 )));
819 }
820 let Ok((after_comma, _)) = ws::<_, _, Error<&str>>(c_char(',')).parse(remaining) else {
821 break;
822 };
823 let (after_pt, pt) = cut(ws(parse_vec2)).parse(after_comma)?;
824 verts.push(pt);
825 remaining = after_pt;
826 }
827 let (remaining, _) = cut(ws(c_char(')'))).parse(remaining)?;
828 if verts.len() < 3 {
829 return Err(nom::Err::Failure(Error::new(remaining, ErrorKind::Verify)));
830 }
831 Ok((remaining, ShapeOp::Polygon(verts)))
832}
833
834fn parse_mat(input: &str) -> IResult<&str, ShapeOp> {
837 let (input, _) = keyword("Mat")(input)?;
838 let (input, _) = cut(ws(c_char('('))).parse(input)?;
839 let (input, mat_id) = cut(ws(rule_name)).parse(input)?;
840 let (input, density) = opt(preceded(ws(c_char(',')), ws(finite_float))).parse(input)?;
841 let (input, _) = cut(ws(c_char(')'))).parse(input)?;
842 if let Some(d) = density
843 && d <= 0.0
844 {
845 return Err(nom::Err::Failure(Error::new(input, ErrorKind::Verify)));
846 }
847 let material = crate::model::Material {
848 id: mat_id,
849 density,
850 };
851 Ok((input, ShapeOp::Mat(material)))
852}
853
854fn parse_align_target(input: &str) -> IResult<&str, Vec3> {
857 let (input, _) = opt(terminated(tag("World"), c_char('.'))).parse(input)?;
858 let (input, name) = identifier.parse(input)?;
859 let v = match name {
860 "Up" => Vec3::new(0.0, 1.0, 0.0),
861 "Down" => Vec3::new(0.0, -1.0, 0.0),
862 "Right" => Vec3::new(1.0, 0.0, 0.0),
863 "Left" => Vec3::new(-1.0, 0.0, 0.0),
864 "Forward" => Vec3::new(0.0, 0.0, -1.0),
865 "Back" => Vec3::new(0.0, 0.0, 1.0),
866 _ => return Err(nom::Err::Failure(Error::new(input, ErrorKind::Tag))),
867 };
868 Ok((input, v))
869}
870
871fn parse_align(input: &str) -> IResult<&str, ShapeOp> {
873 let (input, _) = keyword("Align")(input)?;
874 let (input, _) = cut(ws(c_char('('))).parse(input)?;
875 let (input, local_axis) = cut(ws(parse_axis)).parse(input)?;
876 let (input, _) = cut(ws(c_char(','))).parse(input)?;
877 let (input, target) = cut(ws(parse_align_target)).parse(input)?;
878 let (input, _) = cut(ws(c_char(')'))).parse(input)?;
879 Ok((input, ShapeOp::Align { local_axis, target }))
880}
881
882fn parse_offset_case(input: &str) -> IResult<&str, OffsetCase> {
883 let (input, sel_str) = ws(identifier).parse(input)?;
884 let selector = OffsetSelector::parse(sel_str)
885 .ok_or_else(|| nom::Err::Failure(Error::new(input, ErrorKind::Tag)))?;
886 let (input, _) = ws(c_char(':')).parse(input)?;
887 let (input, rule) = ws(parse_rule_call).parse(input)?;
888 Ok((input, OffsetCase { selector, rule }))
889}
890
891fn parse_offset(input: &str) -> IResult<&str, ShapeOp> {
893 let (input, _) = keyword("Offset")(input)?;
894 let (input, distance) =
895 cut(delimited(ws(c_char('(')), arg_expr, ws(c_char(')')))).parse(input)?;
896 let (input, _) = cut(ws(c_char('{'))).parse(input)?;
897 let (mut remaining, first) = cut(ws(parse_offset_case)).parse(input)?;
898 let mut cases = vec![first];
899 loop {
900 if cases.len() >= MAX_COMP_CASES {
901 return Err(nom::Err::Failure(Error::new(
902 remaining,
903 ErrorKind::TooLarge,
904 )));
905 }
906 let Ok((after_sep, _)) = ws::<_, _, Error<&str>>(c_char('|')).parse(remaining) else {
907 break;
908 };
909 match ws(parse_offset_case).parse(after_sep) {
910 Ok((after_item, case)) => {
911 cases.push(case);
912 remaining = after_item;
913 }
914 Err(nom::Err::Failure(e)) => return Err(nom::Err::Failure(e)),
915 Err(_) => {
916 remaining = after_sep;
917 break;
918 }
919 }
920 }
921 let (remaining, _) = ws(c_char('}')).parse(remaining)?;
922 Ok((remaining, ShapeOp::Offset { distance, cases }))
923}
924
925fn parse_roof_case(input: &str) -> IResult<&str, RoofCase> {
926 let (input, sel_str) = ws(identifier).parse(input)?;
927 let selector = RoofFaceSelector::parse(sel_str)
928 .ok_or_else(|| nom::Err::Failure(Error::new(input, ErrorKind::Tag)))?;
929 let (input, _) = ws(c_char(':')).parse(input)?;
930 let (input, rule) = ws(parse_rule_call).parse(input)?;
931 Ok((input, RoofCase { selector, rule }))
932}
933
934#[derive(Default)]
936struct RoofNamed {
937 overhang: Option<Expr>,
938 offset: Option<Expr>,
939 tier: Option<Expr>,
940 fascia: Option<Expr>,
941 secondary: Option<Expr>,
942 height: Option<Expr>,
943 ridge: Option<Axis>,
944}
945
946fn parse_roof_named_params(input: &str) -> IResult<&str, RoofNamed> {
950 let mut remaining = input;
951 let mut named = RoofNamed::default();
952
953 loop {
954 let Ok((after_comma, _)) = ws::<_, _, Error<&str>>(c_char(',')).parse(remaining) else {
956 break;
957 };
958 let Ok((after_eq, (key, _))) = (
960 ws::<_, _, Error<&str>>(identifier),
961 ws::<_, _, Error<&str>>(c_char('=')),
962 )
963 .parse(after_comma)
964 else {
965 break;
966 };
967 if key == "ridge" {
968 let (rest, axis) = cut(ws(parse_axis)).parse(after_eq)?;
969 if axis == Axis::Y {
970 return Err(nom::Err::Failure(Error::new(after_eq, ErrorKind::Verify)));
972 }
973 named.ridge = Some(axis);
974 remaining = rest;
975 continue;
976 }
977 let slot = match key {
978 "overhang" => &mut named.overhang,
979 "offset" => &mut named.offset,
980 "tier" => &mut named.tier,
981 "fascia" => &mut named.fascia,
982 "secondary" => &mut named.secondary,
983 "height" => &mut named.height,
984 _ => break,
985 };
986 let (rest, e) = cut(arg_expr).parse(after_eq)?;
987 *slot = Some(e);
988 remaining = rest;
989 }
990 Ok((remaining, named))
991}
992
993fn roof_type_uses_secondary_pitch(rt: RoofType) -> bool {
996 matches!(rt, RoofType::Gambrel | RoofType::Mansard)
997}
998
999fn parse_roof(input: &str) -> IResult<&str, ShapeOp> {
1006 let (input, _) = keyword("Roof")(input)?;
1007 let (input, _) = cut(ws(c_char('('))).parse(input)?;
1008 let (input, type_str) = cut(ws(identifier)).parse(input)?;
1009 let roof_type = RoofType::parse(type_str)
1010 .ok_or_else(|| nom::Err::Failure(Error::new(input, ErrorKind::Tag)))?;
1011
1012 let named_ahead = |s: &str| {
1015 (
1016 ws::<_, _, Error<&str>>(identifier),
1017 ws::<_, _, Error<&str>>(c_char('=')),
1018 )
1019 .parse(s)
1020 .is_ok()
1021 };
1022
1023 let mut pitch: Option<Expr> = None;
1024 let mut second_positional: Option<Expr> = None;
1025 let mut remaining = input;
1026
1027 if let Ok((after_comma, _)) = ws::<_, _, Error<&str>>(c_char(',')).parse(remaining)
1028 && !named_ahead(after_comma)
1029 {
1030 let (rest, p) = cut(arg_expr).parse(after_comma)?;
1031 if let Some(v) = p.as_lit()
1032 && (v <= 0.0 || v >= 90.0)
1033 {
1034 return Err(nom::Err::Failure(Error::new(rest, ErrorKind::Verify)));
1035 }
1036 pitch = Some(p);
1037 remaining = rest;
1038 if let Ok((after_c2, _)) = ws::<_, _, Error<&str>>(c_char(',')).parse(remaining)
1041 && !named_ahead(after_c2)
1042 {
1043 let (rest2, v) = cut(arg_expr).parse(after_c2)?;
1044 second_positional = Some(v);
1045 remaining = rest2;
1046 }
1047 }
1048
1049 let uses_secondary = roof_type_uses_secondary_pitch(roof_type);
1050 let mut secondary_pitch: Option<Expr> = None;
1051 let mut overhang: Option<Expr> = None;
1052 if let Some(v) = second_positional {
1053 if uses_secondary {
1054 secondary_pitch = Some(v);
1055 } else {
1056 if let Some(lit) = v.as_lit()
1057 && lit < 0.0
1058 {
1059 return Err(nom::Err::Failure(Error::new(remaining, ErrorKind::Verify)));
1060 }
1061 overhang = Some(v);
1062 }
1063 }
1064
1065 let (input, named) = parse_roof_named_params(remaining)?;
1067 if let Some(v) = named.overhang {
1068 if let Some(lit) = v.as_lit()
1069 && lit < 0.0
1070 {
1071 return Err(nom::Err::Failure(Error::new(input, ErrorKind::Verify)));
1072 }
1073 overhang = Some(v);
1074 }
1075 if let Some(v) = named.secondary {
1076 secondary_pitch = Some(v);
1077 }
1078
1079 let (input, _) = cut(ws(c_char(')'))).parse(input)?;
1080
1081 let Some(pitch) = pitch.or_else(|| named.height.is_some().then(|| Expr::lit(45.0))) else {
1083 return Err(nom::Err::Failure(Error::new(input, ErrorKind::Verify)));
1084 };
1085
1086 let spec = RoofSpec {
1087 roof_type,
1088 pitch,
1089 height: named.height,
1090 secondary_pitch,
1091 overhang: overhang.unwrap_or_else(|| Expr::lit(0.0)),
1092 ridge_offset: named.offset.unwrap_or_else(|| Expr::lit(0.5)),
1093 fascia_depth: named.fascia.unwrap_or_else(|| Expr::lit(0.0)),
1094 tier_height: named.tier,
1095 ridge_axis: named.ridge,
1096 };
1097
1098 let (input, _) = cut(ws(c_char('{'))).parse(input)?;
1099 let (mut remaining, first) = cut(ws(parse_roof_case)).parse(input)?;
1100 let mut cases = vec![first];
1101 loop {
1102 if cases.len() >= MAX_COMP_CASES {
1103 return Err(nom::Err::Failure(Error::new(
1104 remaining,
1105 ErrorKind::TooLarge,
1106 )));
1107 }
1108 let Ok((after_sep, _)) = ws::<_, _, Error<&str>>(c_char('|')).parse(remaining) else {
1109 break;
1110 };
1111 match ws(parse_roof_case).parse(after_sep) {
1112 Ok((after_item, case)) => {
1113 cases.push(case);
1114 remaining = after_item;
1115 }
1116 Err(nom::Err::Failure(e)) => return Err(nom::Err::Failure(e)),
1117 Err(_) => {
1118 remaining = after_sep;
1119 break;
1120 }
1121 }
1122 }
1123 let (remaining, _) = ws(c_char('}')).parse(remaining)?;
1124 Ok((remaining, ShapeOp::Roof { spec, cases }))
1125}
1126
1127fn parse_attach_case(input: &str) -> IResult<&str, AttachCase> {
1129 let (input, sel_str) = ws(identifier).parse(input)?;
1130 let selector = AttachSelector::parse(sel_str)
1131 .ok_or_else(|| nom::Err::Failure(Error::new(input, ErrorKind::Tag)))?;
1132 let (input, _) = ws(c_char(':')).parse(input)?;
1133 let (input, rule) = ws(parse_rule_call).parse(input)?;
1134 Ok((input, AttachCase { selector, rule }))
1135}
1136
1137fn parse_world_axis(input: &str) -> IResult<&str, Vec3> {
1139 let (input, name) = ws(identifier).parse(input)?;
1140 let v = match name {
1141 "Up" | "up" => Vec3::Y,
1142 "Down" | "down" => Vec3::NEG_Y,
1143 "Right" | "right" => Vec3::X,
1144 "Left" | "left" => Vec3::NEG_X,
1145 "Forward" | "forward" => Vec3::NEG_Z,
1146 "Back" | "back" => Vec3::Z,
1147 _ => return Err(nom::Err::Failure(Error::new(input, ErrorKind::Tag))),
1148 };
1149 Ok((input, v))
1150}
1151
1152fn parse_attach(input: &str) -> IResult<&str, ShapeOp> {
1154 let (input, _) = keyword("Attach")(input)?;
1155 let (input, _) = cut(ws(c_char('('))).parse(input)?;
1156 let (input, world_axis) = cut(ws(parse_world_axis)).parse(input)?;
1157 let (input, _) = cut(ws(c_char(')'))).parse(input)?;
1158 let (input, _) = cut(ws(c_char('{'))).parse(input)?;
1159 let (mut remaining, first) = cut(ws(parse_attach_case)).parse(input)?;
1160 let mut cases = vec![first];
1161 loop {
1162 if cases.len() >= MAX_COMP_CASES {
1163 return Err(nom::Err::Failure(Error::new(
1164 remaining,
1165 ErrorKind::TooLarge,
1166 )));
1167 }
1168 let Ok((after_sep, _)) = ws::<_, _, Error<&str>>(c_char('|')).parse(remaining) else {
1169 break;
1170 };
1171 match ws(parse_attach_case).parse(after_sep) {
1172 Ok((after_item, case)) => {
1173 cases.push(case);
1174 remaining = after_item;
1175 }
1176 Err(nom::Err::Failure(e)) => return Err(nom::Err::Failure(e)),
1177 Err(_) => {
1178 remaining = after_sep;
1179 break;
1180 }
1181 }
1182 }
1183 let (remaining, _) = ws(c_char('}')).parse(remaining)?;
1184 Ok((remaining, ShapeOp::Attach { world_axis, cases }))
1185}
1186
1187fn parse_size(input: &str) -> IResult<&str, ShapeOp> {
1189 let (input, _) = keyword("Size")(input)?;
1190 let (input, v) = cut(ws(parse_expr3)).parse(input)?;
1191 for c in &v {
1192 if let Some(lit) = c.as_lit()
1193 && lit < 0.0
1194 {
1195 return Err(nom::Err::Failure(Error::new(input, ErrorKind::Verify)));
1196 }
1197 }
1198 Ok((input, ShapeOp::Size(v)))
1199}
1200
1201fn parse_center(input: &str) -> IResult<&str, ShapeOp> {
1204 let (input, _) = keyword("Center")(input)?;
1205 let (input, _) = cut(ws(c_char('('))).parse(input)?;
1206 let (input, mask) = cut(ws(identifier)).parse(input)?;
1207 let (mut x, mut y, mut z) = (false, false, false);
1208 for ch in mask.chars() {
1209 match ch {
1210 'X' | 'x' => x = true,
1211 'Y' | 'y' => y = true,
1212 'Z' | 'z' => z = true,
1213 _ => return Err(nom::Err::Failure(Error::new(input, ErrorKind::Verify))),
1214 }
1215 }
1216 if !(x || y || z) {
1217 return Err(nom::Err::Failure(Error::new(input, ErrorKind::Verify)));
1218 }
1219 let (input, _) = cut(ws(c_char(')'))).parse(input)?;
1220 Ok((input, ShapeOp::Center { x, y, z }))
1221}
1222
1223fn parse_mirror(input: &str) -> IResult<&str, ShapeOp> {
1226 let (input, _) = keyword("Mirror")(input)?;
1227 let (input, _) = cut(ws(c_char('('))).parse(input)?;
1228 let (input, axis) = cut(ws(parse_axis)).parse(input)?;
1229 if axis != Axis::X {
1230 return Err(nom::Err::Failure(Error::new(input, ErrorKind::Verify)));
1231 }
1232 let (input, _) = cut(ws(c_char(')'))).parse(input)?;
1233 Ok((input, ShapeOp::Mirror))
1234}
1235
1236fn parse_carve_case(input: &str) -> IResult<&str, CarveCase> {
1237 let (input, sel_str) = ws(identifier).parse(input)?;
1238 let selector = CarveSelector::parse(sel_str)
1239 .ok_or_else(|| nom::Err::Failure(Error::new(input, ErrorKind::Tag)))?;
1240 let (input, _) = ws(c_char(':')).parse(input)?;
1241 let (input, rule) = ws(parse_rule_call).parse(input)?;
1242 Ok((input, CarveCase { selector, rule }))
1243}
1244
1245fn parse_carve_cases(input: &str) -> IResult<&str, Vec<CarveCase>> {
1246 let (input, _) = cut(ws(c_char('{'))).parse(input)?;
1247 let (mut remaining, first) = cut(ws(parse_carve_case)).parse(input)?;
1248 let mut cases = vec![first];
1249 loop {
1250 if cases.len() >= MAX_COMP_CASES {
1251 return Err(nom::Err::Failure(Error::new(
1252 remaining,
1253 ErrorKind::TooLarge,
1254 )));
1255 }
1256 let Ok((after_sep, _)) = ws::<_, _, Error<&str>>(c_char('|')).parse(remaining) else {
1257 break;
1258 };
1259 let (after_item, case) = cut(ws(parse_carve_case)).parse(after_sep)?;
1260 cases.push(case);
1261 remaining = after_item;
1262 }
1263 let (remaining, _) = cut(ws(c_char('}'))).parse(remaining)?;
1264 Ok((remaining, cases))
1265}
1266
1267fn parse_shape_l(input: &str) -> IResult<&str, ShapeOp> {
1269 let (input, _) = keyword("ShapeL")(input)?;
1270 let (input, _) = cut(ws(c_char('('))).parse(input)?;
1271 let (input, front) = cut(arg_expr).parse(input)?;
1272 let (input, _) = cut(ws(c_char(','))).parse(input)?;
1273 let (input, side) = cut(arg_expr).parse(input)?;
1274 let (input, _) = cut(ws(c_char(')'))).parse(input)?;
1275 let (input, cases) = parse_carve_cases(input)?;
1276 Ok((input, ShapeOp::ShapeL { front, side, cases }))
1277}
1278
1279fn parse_shape_u(input: &str) -> IResult<&str, ShapeOp> {
1281 let (input, _) = keyword("ShapeU")(input)?;
1282 let (input, _) = cut(ws(c_char('('))).parse(input)?;
1283 let (input, front) = cut(arg_expr).parse(input)?;
1284 let (input, _) = cut(ws(c_char(','))).parse(input)?;
1285 let (input, left) = cut(arg_expr).parse(input)?;
1286 let (input, _) = cut(ws(c_char(','))).parse(input)?;
1287 let (input, right) = cut(arg_expr).parse(input)?;
1288 let (input, _) = cut(ws(c_char(')'))).parse(input)?;
1289 let (input, cases) = parse_carve_cases(input)?;
1290 Ok((
1291 input,
1292 ShapeOp::ShapeU {
1293 front,
1294 left,
1295 right,
1296 cases,
1297 },
1298 ))
1299}
1300
1301fn parse_rule_ref(input: &str) -> IResult<&str, ShapeOp> {
1302 map(ws(parse_rule_call), ShapeOp::Rule).parse(input)
1303}
1304
1305fn parse_op(input: &str) -> IResult<&str, ShapeOp> {
1308 alt((
1311 alt((
1312 parse_extrude,
1313 parse_taper,
1314 parse_rotate,
1315 parse_translate,
1316 parse_scale,
1317 parse_split_area,
1320 parse_split,
1321 parse_fit,
1322 parse_shape_l,
1323 parse_shape_u,
1324 parse_size,
1325 parse_center,
1326 parse_mirror,
1327 parse_repeat,
1328 parse_comp,
1329 )),
1330 alt((
1331 parse_if_clear,
1336 parse_if_occluded,
1337 parse_if_inside,
1338 parse_if_touches,
1339 parse_label,
1340 parse_scatter,
1341 parse_pick,
1342 parse_instance,
1343 parse_mat,
1344 parse_align,
1345 parse_offset,
1346 parse_roof,
1347 parse_attach,
1348 parse_polygon,
1349 parse_reg_snap,
1350 parse_rule_ref,
1351 )),
1352 ))
1353 .parse(input)
1354}
1355
1356fn is_terminating_op(op: &ShapeOp) -> bool {
1362 matches!(
1363 op,
1364 ShapeOp::I(_)
1365 | ShapeOp::Rule(_)
1366 | ShapeOp::Split { .. }
1367 | ShapeOp::SplitArea { .. }
1368 | ShapeOp::Fit { .. }
1369 | ShapeOp::ShapeL { .. }
1370 | ShapeOp::ShapeU { .. }
1371 | ShapeOp::Comp(_)
1372 | ShapeOp::Repeat { .. }
1373 | ShapeOp::Offset { .. }
1374 | ShapeOp::Roof { .. }
1375 | ShapeOp::Attach { .. }
1376 | ShapeOp::Scatter { .. }
1377 | ShapeOp::Pick { .. }
1378 )
1379}
1380
1381pub fn parse_ops(input: &str) -> Result<Vec<ShapeOp>, ShapeError> {
1391 let mut remaining = input;
1392 let mut ops = Vec::new();
1393
1394 loop {
1395 let (next, _) = space_or_comment::<Error<&str>>(remaining)
1396 .map_err(|e| ShapeError::ParseError(e.to_string()))?;
1397 remaining = next;
1398
1399 if remaining.is_empty() {
1400 break;
1401 }
1402
1403 if ops.len() >= MAX_OPS {
1404 return Err(ShapeError::CapacityOverflow);
1405 }
1406
1407 let (next, op) = parse_op(remaining).map_err(|e| ShapeError::ParseError(e.to_string()))?;
1408 let terminates = is_terminating_op(&op);
1409 ops.push(op);
1410 remaining = next;
1411
1412 if terminates {
1413 let (after_ws, _) = space_or_comment::<Error<&str>>(remaining)
1416 .map_err(|e| ShapeError::ParseError(e.to_string()))?;
1417 if !after_ws.is_empty() {
1418 return Err(ShapeError::ParseError(
1419 "unreachable operations after terminal or branching op".to_string(),
1420 ));
1421 }
1422 break;
1423 }
1424 }
1425
1426 Ok(ops)
1427}
1428
1429fn split_top_level_pipe(s: &str) -> Result<Vec<&str>, ShapeError> {
1442 let mut depth: i32 = 0;
1443 let mut in_string = false;
1444 let mut in_block_comment = false;
1445 let mut in_line_comment = false;
1446 let mut start = 0;
1447 let mut parts = Vec::new();
1448 let mut iter = s.char_indices().peekable();
1452
1453 while let Some((byte_pos, c)) = iter.next() {
1454 if in_line_comment {
1455 if c == '\n' || c == '\r' {
1456 in_line_comment = false;
1457 }
1458 continue;
1459 }
1460
1461 if in_block_comment {
1462 if c == '*' && matches!(iter.peek(), Some((_, '/'))) {
1463 iter.next(); in_block_comment = false;
1465 }
1466 continue;
1467 }
1468
1469 if in_string {
1470 if c == '"' {
1471 in_string = false;
1472 }
1473 continue;
1474 }
1475
1476 if c == '/' {
1478 match iter.peek() {
1479 Some(&(_, '/')) => {
1480 iter.next();
1481 in_line_comment = true;
1482 continue;
1483 }
1484 Some(&(_, '*')) => {
1485 iter.next();
1486 in_block_comment = true;
1487 continue;
1488 }
1489 _ => {}
1490 }
1491 }
1492
1493 match c {
1494 '"' => in_string = true,
1495 '{' => depth += 1,
1496 '}' => depth -= 1,
1497 '|' if depth == 0 => {
1498 if parts.len() + 2 > MAX_VARIANTS {
1502 return Err(ShapeError::CapacityOverflow);
1503 }
1504 parts.push(s[start..byte_pos].trim());
1505 start = byte_pos + c.len_utf8();
1506 }
1507 _ => {}
1508 }
1509 }
1510
1511 parts.push(s[start..].trim());
1512 Ok(parts)
1513}
1514
1515fn parse_weight_prefix(input: &str) -> IResult<&str, f64> {
1516 let (input, w) = ws(double).parse(input)?;
1517 let (input, _) = ws(c_char('%')).parse(input)?;
1518 Ok((input, w))
1519}
1520
1521fn try_parse_weight(input: &str) -> Option<(f64, &str)> {
1523 let trimmed = input.trim_start();
1524 match parse_weight_prefix(trimmed) {
1525 Ok((rest, w)) if w.is_finite() && w >= 0.0 => Some((w / 100.0, rest)),
1526 _ => None,
1527 }
1528}
1529
1530#[derive(Debug, Clone)]
1544pub struct GrammarRule {
1545 pub name: String,
1546 pub params: Vec<String>,
1548 pub variants: Vec<RuleVariant>,
1549}
1550
1551impl GrammarRule {
1552 pub fn ops(&self) -> &[ShapeOp] {
1555 self.variants
1556 .first()
1557 .map(|v| v.ops.as_slice())
1558 .unwrap_or(&[])
1559 }
1560}
1561
1562fn try_parse_when(part: &str) -> Option<Result<(Expr, &str), ShapeError>> {
1564 let trimmed = part.trim_start();
1565 let rest = trimmed.strip_prefix("when")?;
1566 if rest.chars().next().is_some_and(is_ident_char) {
1568 return None;
1569 }
1570 let parse = || -> Result<(Expr, &str), ShapeError> {
1571 let (rest2, _) = ws::<_, _, Error<&str>>(c_char('('))
1572 .parse(rest)
1573 .map_err(|e| ShapeError::ParseError(e.to_string()))?;
1574 let (rest3, cond) = arg_expr(rest2).map_err(|e| ShapeError::ParseError(e.to_string()))?;
1575 let (rest4, _) = ws::<_, _, Error<&str>>(c_char(')'))
1576 .parse(rest3)
1577 .map_err(|e| ShapeError::ParseError(e.to_string()))?;
1578 let (rest5, _) = ws::<_, _, Error<&str>>(c_char(':'))
1579 .parse(rest4)
1580 .map_err(|e| ShapeError::ParseError(e.to_string()))?;
1581 Ok((cond, rest5))
1582 };
1583 Some(parse())
1584}
1585
1586fn try_parse_else(part: &str) -> Option<&str> {
1588 let trimmed = part.trim_start();
1589 let rest = trimmed.strip_prefix("else")?;
1590 if rest.chars().next().is_some_and(is_ident_char) {
1591 return None;
1592 }
1593 let (rest2, _) = ws::<_, _, Error<&str>>(c_char(':')).parse(rest).ok()?;
1594 Some(rest2)
1595}
1596
1597fn parse_param_decl(input: &str) -> Result<(Vec<String>, &str), ShapeError> {
1599 let Ok((rest, _)) = ws::<_, _, Error<&str>>(c_char('(')).parse(input) else {
1600 return Ok((Vec::new(), input));
1601 };
1602 let mut params = Vec::new();
1603 let mut remaining = rest;
1604 if let Ok((after, _)) = ws::<_, _, Error<&str>>(c_char(')')).parse(remaining) {
1605 return Ok((params, after));
1606 }
1607 loop {
1608 let (after, p) = ws(identifier)
1609 .parse(remaining)
1610 .map_err(|e| ShapeError::ParseError(e.to_string()))?;
1611 if params.contains(&p.to_string()) {
1612 return Err(ShapeError::ParseError(format!(
1613 "duplicate rule parameter name: {p}"
1614 )));
1615 }
1616 params.push(p.to_string());
1617 if params.len() > MAX_RULE_ARGS {
1618 return Err(ShapeError::CapacityOverflow);
1619 }
1620 if let Ok((after2, _)) = ws::<_, _, Error<&str>>(c_char(',')).parse(after) {
1621 remaining = after2;
1622 continue;
1623 }
1624 let (after2, _) = ws::<_, _, Error<&str>>(c_char(')'))
1625 .parse(after)
1626 .map_err(|e| ShapeError::ParseError(e.to_string()))?;
1627 return Ok((params, after2));
1628 }
1629}
1630
1631#[derive(Debug, Clone)]
1633pub enum Statement {
1634 Rule(GrammarRule),
1635 Attr {
1637 name: String,
1638 value: f64,
1639 },
1640 Const {
1642 name: String,
1643 value: f64,
1644 },
1645 Style {
1647 name: String,
1648 extends: Option<String>,
1649 overrides: Vec<(String, f64)>,
1650 },
1651}
1652
1653fn const_eval(e: &Expr, at: &str) -> Result<f64, ShapeError> {
1656 fn check(e: &Expr) -> bool {
1657 match e {
1658 Expr::Lit(_) => true,
1659 Expr::Var(_) => false,
1660 Expr::Unary(_, a) => check(a),
1661 Expr::Binary(_, a, b) => check(a) && check(b),
1662 Expr::Call(f, args) => *f != crate::expr::Func::Rand && args.iter().all(check),
1663 }
1664 }
1665 if !check(e) {
1666 return Err(ShapeError::ParseError(format!(
1667 "declaration value must be a constant expression (no variables, no rand): {at:?}"
1668 )));
1669 }
1670 let mut rng = rand_pcg::Pcg64::new(0, 0);
1671 let globals = std::collections::HashMap::new();
1672 let mut ctx = crate::expr::EvalCtx {
1673 scope_size: crate::scope::Vec3::ZERO,
1674 split_i: 0.0,
1675 split_n: 1.0,
1676 depth: 0.0,
1677 params: &[],
1678 globals: &globals,
1679 rng: &mut rng,
1680 };
1681 e.eval(&mut ctx)
1682}
1683
1684fn parse_style_overrides(input: &str) -> Result<Vec<(String, f64)>, ShapeError> {
1686 let mut out = Vec::new();
1687 let mut remaining = input;
1688 loop {
1689 let (rest, name) = ws::<_, _, Error<&str>>(identifier)
1690 .parse(remaining)
1691 .map_err(|e| ShapeError::ParseError(e.to_string()))?;
1692 let (rest, _) = ws::<_, _, Error<&str>>(c_char('='))
1693 .parse(rest)
1694 .map_err(|e| ShapeError::ParseError(e.to_string()))?;
1695 let (rest, value_expr) =
1696 arg_expr(rest).map_err(|e| ShapeError::ParseError(e.to_string()))?;
1697 out.push((name.to_string(), const_eval(&value_expr, name)?));
1698 if out.len() > MAX_SPLIT_SLOTS {
1699 return Err(ShapeError::CapacityOverflow);
1700 }
1701 if let Ok((rest2, _)) = ws::<_, _, Error<&str>>(c_char(',')).parse(rest) {
1702 remaining = rest2;
1703 continue;
1704 }
1705 let (rest2, _) = ws::<_, _, Error<&str>>(c_char('}'))
1706 .parse(rest)
1707 .map_err(|e| ShapeError::ParseError(e.to_string()))?;
1708 let (rest3, _) = space_or_comment::<Error<&str>>(rest2)
1709 .map_err(|e| ShapeError::ParseError(e.to_string()))?;
1710 if !rest3.is_empty() {
1711 return Err(ShapeError::ParseError(format!(
1712 "trailing input after style block: {rest3:?}"
1713 )));
1714 }
1715 return Ok(out);
1716 }
1717}
1718
1719pub fn parse_statement(input: &str) -> Result<Statement, ShapeError> {
1730 let (rest, _) = space_or_comment::<Error<&str>>(input)
1731 .map_err(|e| ShapeError::ParseError(e.to_string()))?;
1732 for (kw, is_attr) in [("attr", true), ("const", false)] {
1734 if let Some(after) = rest.strip_prefix(kw)
1735 && !after.chars().next().is_some_and(is_ident_char)
1736 {
1737 let (after2, name) = ws::<_, _, Error<&str>>(identifier)
1738 .parse(after)
1739 .map_err(|e| ShapeError::ParseError(e.to_string()))?;
1740 let (after3, _) = ws::<_, _, Error<&str>>(c_char('='))
1741 .parse(after2)
1742 .map_err(|e| ShapeError::ParseError(e.to_string()))?;
1743 let (after4, value_expr) =
1744 arg_expr(after3).map_err(|e| ShapeError::ParseError(e.to_string()))?;
1745 let (after5, _) = space_or_comment::<Error<&str>>(after4)
1746 .map_err(|e| ShapeError::ParseError(e.to_string()))?;
1747 if !after5.is_empty() {
1748 return Err(ShapeError::ParseError(format!(
1749 "trailing input after declaration: {after5:?}"
1750 )));
1751 }
1752 let value = const_eval(&value_expr, name)?;
1753 return Ok(if is_attr {
1754 Statement::Attr {
1755 name: name.to_string(),
1756 value,
1757 }
1758 } else {
1759 Statement::Const {
1760 name: name.to_string(),
1761 value,
1762 }
1763 });
1764 }
1765 }
1766 if let Some(after) = rest.strip_prefix("style")
1767 && !after.chars().next().is_some_and(is_ident_char)
1768 {
1769 let (after2, name) = ws::<_, _, Error<&str>>(identifier)
1770 .parse(after)
1771 .map_err(|e| ShapeError::ParseError(e.to_string()))?;
1772 let (after3, extends) =
1773 if let Ok((a, _)) = ws::<_, _, Error<&str>>(tag("extends")).parse(after2) {
1774 let (a2, base) = ws::<_, _, Error<&str>>(identifier)
1775 .parse(a)
1776 .map_err(|e| ShapeError::ParseError(e.to_string()))?;
1777 (a2, Some(base.to_string()))
1778 } else {
1779 (after2, None)
1780 };
1781 let (after4, _) = ws::<_, _, Error<&str>>(c_char('{'))
1782 .parse(after3)
1783 .map_err(|e| ShapeError::ParseError(e.to_string()))?;
1784 let overrides = parse_style_overrides(after4)?;
1785 return Ok(Statement::Style {
1786 name: name.to_string(),
1787 extends,
1788 overrides,
1789 });
1790 }
1791 Ok(Statement::Rule(parse_rule(input)?))
1792}
1793
1794pub fn parse_rule(input: &str) -> Result<GrammarRule, ShapeError> {
1805 let (remaining, _) = space_or_comment::<Error<&str>>(input)
1806 .map_err(|e| ShapeError::ParseError(e.to_string()))?;
1807 let (remaining, name) = ws(identifier)
1808 .parse(remaining)
1809 .map_err(|e| ShapeError::ParseError(e.to_string()))?;
1810 let (params, remaining) = parse_param_decl(remaining)?;
1811 let (remaining, _) = ws(tag::<_, _, Error<&str>>("-->"))
1812 .parse(remaining)
1813 .map_err(|e| ShapeError::ParseError(e.to_string()))?;
1814
1815 let parts = split_top_level_pipe(remaining)?;
1817
1818 let mut variants: Vec<RuleVariant> = Vec::with_capacity(parts.len());
1819 let multi = parts.len() > 1;
1820 for (i, part) in parts.iter().enumerate() {
1821 let is_last = i + 1 == parts.len();
1822 if let Some(res) = try_parse_when(part) {
1823 let (cond, rest) = res?;
1824 variants.push(RuleVariant {
1825 selector: VariantSelector::When(cond),
1826 ops: parse_ops(rest)?,
1827 });
1828 continue;
1829 }
1830 if let Some(rest) = try_parse_else(part) {
1831 if !is_last {
1832 return Err(ShapeError::ParseError(
1833 "`else:` must be the last variant".to_string(),
1834 ));
1835 }
1836 variants.push(RuleVariant {
1837 selector: VariantSelector::Else,
1838 ops: parse_ops(rest)?,
1839 });
1840 continue;
1841 }
1842 match try_parse_weight(part) {
1843 Some((weight, rest)) => variants.push(RuleVariant {
1844 selector: VariantSelector::Weight(weight),
1845 ops: parse_ops(rest)?,
1846 }),
1847 None if multi => {
1848 return Err(ShapeError::ParseError(format!(
1849 "rule alternative needs a 'weight%', 'when(..):', or 'else:' prefix: {part:?}"
1850 )));
1851 }
1852 None => variants.push(RuleVariant {
1853 selector: VariantSelector::Weight(1.0),
1854 ops: parse_ops(part)?,
1855 }),
1856 }
1857 }
1858
1859 let n_weight = variants
1863 .iter()
1864 .filter(|v| matches!(v.selector, VariantSelector::Weight(_)))
1865 .count();
1866 let n_when = variants
1867 .iter()
1868 .filter(|v| matches!(v.selector, VariantSelector::When(_)))
1869 .count();
1870 let has_else = variants
1871 .iter()
1872 .any(|v| matches!(v.selector, VariantSelector::Else));
1873
1874 if n_weight > 0 && n_when > 0 {
1875 return Err(ShapeError::ParseError(
1876 "rule mixes weighted and guarded variants".to_string(),
1877 ));
1878 }
1879 if n_weight > 0 && has_else {
1880 let used: f64 = variants
1881 .iter()
1882 .filter_map(|v| match v.selector {
1883 VariantSelector::Weight(w) => Some(w),
1884 _ => None,
1885 })
1886 .sum();
1887 let remainder = 1.0 - used;
1888 if remainder <= 1e-9 {
1889 return Err(ShapeError::ParseError(format!(
1890 "`else:` has no probability mass left (weights already sum to {:.0}%)",
1891 used * 100.0
1892 )));
1893 }
1894 if let Some(last) = variants.last_mut() {
1895 last.selector = VariantSelector::Weight(remainder);
1896 }
1897 }
1898
1899 Ok(GrammarRule {
1900 name: name.to_string(),
1901 params,
1902 variants,
1903 })
1904}
1905
1906#[cfg(test)]
1907mod tests {
1908 use super::*;
1909 use crate::ops::{Axis, ShapeOp, SplitSize};
1910
1911 #[test]
1912 fn test_parse_extrude() {
1913 let ops = parse_ops("Extrude(10)").unwrap();
1914 assert_eq!(ops.len(), 1);
1915 assert_eq!(ops[0], ShapeOp::Extrude(Expr::lit(10.0)));
1916 }
1917
1918 #[test]
1919 fn test_parse_taper() {
1920 let ops = parse_ops("Taper(0.5)").unwrap();
1921 assert_eq!(ops[0], ShapeOp::Taper(Expr::lit(0.5)));
1922 }
1923
1924 #[test]
1925 fn test_parse_split_y() {
1926 let ops = parse_ops("Split(Y) { ~1.0: Floor | ~1.0: Floor | 2.0: Roof }").unwrap();
1927 assert_eq!(ops.len(), 1);
1928 let ShapeOp::Split { axis, entries, .. } = &ops[0] else {
1929 panic!("expected Split");
1930 };
1931 let slots: Vec<_> = entries.iter().filter_map(|e| e.as_slot()).collect();
1932 assert_eq!(*axis, Axis::Y);
1933 assert_eq!(slots.len(), 3);
1934 assert_eq!(slots[0].size, SplitSize::float(1.0));
1935 assert_eq!(slots[0].rule, "Floor");
1936 assert_eq!(slots[2].size, SplitSize::abs(2.0));
1937 assert_eq!(slots[2].rule, "Roof");
1938 }
1939
1940 #[test]
1941 fn test_parse_split_relative() {
1942 let ops = parse_ops("Split(X) { '0.3: Left | '0.7: Right }").unwrap();
1943 let ShapeOp::Split { entries, .. } = &ops[0] else {
1944 panic!("expected Split");
1945 };
1946 let slots: Vec<_> = entries.iter().filter_map(|e| e.as_slot()).collect();
1947 assert_eq!(slots[0].size, SplitSize::rel(0.3));
1948 assert_eq!(slots[1].size, SplitSize::rel(0.7));
1949 }
1950
1951 #[test]
1952 fn test_parse_comp_faces() {
1953 let ops =
1954 parse_ops("Comp(Faces) { Top: Roof | Side: Facade | Bottom: Foundation }").unwrap();
1955 assert_eq!(ops.len(), 1);
1956 let ShapeOp::Comp(CompTarget::Faces(cases)) = &ops[0] else {
1957 panic!("expected Comp(Faces)");
1958 };
1959 assert_eq!(cases.len(), 3);
1960 assert_eq!(cases[0].selector, FaceSelector::Top);
1961 assert_eq!(cases[0].rule, "Roof");
1962 }
1963
1964 #[test]
1965 fn test_parse_instance() {
1966 let ops = parse_ops(r#"I("Window")"#).unwrap();
1967 assert_eq!(ops[0], ShapeOp::I("Window".to_string()));
1968 }
1969
1970 #[test]
1971 fn test_parse_mat() {
1972 let ops = parse_ops(r#"Mat("Brick")"#).unwrap();
1973 assert_eq!(ops[0], ShapeOp::Mat(crate::model::Material::new("Brick")));
1974 let ops2 = parse_ops("Mat(Stone)").unwrap();
1975 assert_eq!(ops2[0], ShapeOp::Mat(crate::model::Material::new("Stone")));
1976 }
1977
1978 #[test]
1979 fn test_parse_mat_with_density() {
1980 let ops = parse_ops(r#"Mat("Brick", 1800)"#).unwrap();
1981 assert_eq!(
1982 ops[0],
1983 ShapeOp::Mat(crate::model::Material::with_density("Brick", 1800.0))
1984 );
1985 }
1986
1987 #[test]
1988 fn test_parse_mat_zero_density_rejected() {
1989 assert!(parse_ops("Mat(Stone, 0)").is_err());
1990 }
1991
1992 #[test]
1993 fn test_parse_mat_negative_density_rejected() {
1994 assert!(parse_ops("Mat(Stone, -5)").is_err());
1995 }
1996
1997 #[test]
1998 fn test_parse_rule_ref() {
1999 let ops = parse_ops("Floor").unwrap();
2000 assert_eq!(ops[0], ShapeOp::Rule("Floor".into()));
2001 }
2002
2003 #[test]
2004 fn test_parse_multiple_ops() {
2005 let ops =
2006 parse_ops(r#"Extrude(10) Split(Y) { 2.0: Ground | ~1.0: Upper | 3.0: Roof }"#).unwrap();
2007 assert_eq!(ops.len(), 2);
2008 }
2009
2010 #[test]
2011 fn test_parse_grammar_rule_deterministic() {
2012 let rule = parse_rule("Lot --> Extrude(10) Split(Y) { ~1: Floor | 2: Roof }").unwrap();
2013 assert_eq!(rule.name, "Lot");
2014 assert_eq!(rule.variants.len(), 1);
2015 assert!((rule.variants[0].weight().unwrap() - 1.0).abs() < 1e-9);
2016 assert_eq!(rule.variants[0].ops.len(), 2);
2017 }
2018
2019 #[test]
2020 fn test_parse_grammar_rule_stochastic() {
2021 let rule = parse_rule("Facade --> 70% BrickWall | 30% GlassCurtain").unwrap();
2022 assert_eq!(rule.name, "Facade");
2023 assert_eq!(rule.variants.len(), 2);
2024 assert!((rule.variants[0].weight().unwrap() - 0.70).abs() < 1e-9);
2025 assert_eq!(
2026 rule.variants[0].ops,
2027 vec![ShapeOp::Rule("BrickWall".into())]
2028 );
2029 assert!((rule.variants[1].weight().unwrap() - 0.30).abs() < 1e-9);
2030 assert_eq!(
2031 rule.variants[1].ops,
2032 vec![ShapeOp::Rule("GlassCurtain".into())]
2033 );
2034 }
2035
2036 #[test]
2037 fn test_parse_stochastic_with_complex_ops() {
2038 let rule = parse_rule("R --> 50% Split(X) { ~1: A | ~1: B } | 50% I(Solid)").unwrap();
2040 assert_eq!(rule.variants.len(), 2);
2041 assert_eq!(rule.variants[0].ops.len(), 1); assert_eq!(rule.variants[1].ops.len(), 1); }
2044
2045 #[test]
2046 fn test_extrude_zero_rejected() {
2047 assert!(parse_ops("Extrude(0)").is_err());
2048 }
2049
2050 #[test]
2051 fn test_taper_out_of_range_rejected() {
2052 assert!(parse_ops("Taper(1.5)").is_err());
2053 }
2054
2055 #[test]
2056 fn test_comments_ignored() {
2057 let ops = parse_ops("// comment\nExtrude(5) /* block */ Taper(0.2)").unwrap();
2058 assert_eq!(ops.len(), 2);
2059 }
2060
2061 #[test]
2064 fn test_grammar_rule_ops_empty_variants_returns_empty() {
2065 let rule = GrammarRule {
2066 name: "Empty".to_string(),
2067 params: vec![],
2068 variants: vec![],
2069 };
2070 assert_eq!(rule.ops(), &[] as &[ShapeOp]);
2071 }
2072
2073 #[test]
2076 fn test_rotate_overflow_components_rejected() {
2077 assert!(parse_ops("Rotate(1e160, 0, 0, 0)").is_err());
2080 assert!(parse_ops("Rotate(1, 1e200, 0, 0)").is_err());
2081 }
2082
2083 #[test]
2086 fn test_pipe_in_quoted_mesh_id_not_split() {
2087 let rule = parse_rule(r#"Lot --> I("Wall|Door")"#).unwrap();
2089 assert_eq!(rule.variants.len(), 1);
2090 assert_eq!(
2091 rule.variants[0].ops,
2092 vec![ShapeOp::I("Wall|Door".to_string())]
2093 );
2094 }
2095
2096 #[test]
2097 fn test_block_comment_with_brace_does_not_confuse_depth() {
2098 let rule = parse_rule("Facade --> 50% Extrude(10) /* { */ | 50% I(Solid)").unwrap();
2100 assert_eq!(rule.variants.len(), 2);
2101 assert!((rule.variants[0].weight().unwrap() - 0.50).abs() < 1e-9);
2102 assert!((rule.variants[1].weight().unwrap() - 0.50).abs() < 1e-9);
2103 }
2104
2105 #[test]
2108 fn test_ops_after_instance_rejected() {
2109 assert!(parse_ops(r#"I("Wall") Scale(2, 2, 2)"#).is_err());
2110 }
2111
2112 #[test]
2113 fn test_ops_after_rule_ref_rejected() {
2114 assert!(parse_ops("Floor Scale(2, 2, 2)").is_err());
2115 }
2116
2117 #[test]
2118 fn test_ops_after_split_rejected() {
2119 assert!(parse_ops("Split(Y) { ~1: A | ~1: B } Scale(1, 2, 1)").is_err());
2120 }
2121
2122 #[test]
2125 fn test_too_many_variants_rejected() {
2126 let variants: Vec<String> = (0..=MAX_VARIANTS).map(|i| format!("1% I(M{i})")).collect();
2128 let rule_str = format!("R --> {}", variants.join(" | "));
2129 assert!(matches!(
2130 parse_rule(&rule_str),
2131 Err(ShapeError::CapacityOverflow)
2132 ));
2133 }
2134
2135 #[test]
2136 fn test_max_variants_boundary_accepted() {
2137 let variants: Vec<String> = (0..MAX_VARIANTS).map(|i| format!("1% I(M{i})")).collect();
2139 let rule_str = format!("R --> {}", variants.join(" | "));
2140 let rule = parse_rule(&rule_str).unwrap();
2141 assert_eq!(rule.variants.len(), MAX_VARIANTS);
2142 }
2143
2144 #[test]
2147 fn test_translate_wrong_arg_count_rejected() {
2148 assert!(parse_ops("Translate(1.0, 2.0)").is_err());
2152 }
2153
2154 #[test]
2155 fn test_extrude_missing_arg_rejected() {
2156 assert!(parse_ops("Extrude()").is_err());
2157 }
2158
2159 #[test]
2160 fn test_split_missing_brace_rejected() {
2161 assert!(parse_ops("Split(Y)").is_err());
2163 }
2164
2165 #[test]
2168 fn test_single_variant_with_weight_prefix() {
2169 let rule = parse_rule("Lot --> 100% Extrude(10)").unwrap();
2170 assert_eq!(rule.variants.len(), 1);
2171 assert!((rule.variants[0].weight().unwrap() - 1.0).abs() < 1e-9);
2172 assert_eq!(
2173 rule.variants[0].ops,
2174 vec![ShapeOp::Extrude(Expr::lit(10.0))]
2175 );
2176 }
2177
2178 #[test]
2181 fn test_scale_negative_rejected() {
2182 assert!(parse_ops("Scale(-1, 1, 1)").is_err());
2183 }
2184
2185 #[test]
2186 fn test_scale_zero_rejected() {
2187 assert!(parse_ops("Scale(0, 1, 1)").is_err());
2188 }
2189
2190 #[test]
2191 fn test_scale_positive_accepted() {
2192 let ops = parse_ops("Scale(0.5, 2, 1)").unwrap();
2193 assert_eq!(ops.len(), 1);
2194 }
2195
2196 #[test]
2199 fn test_parse_align_basic() {
2200 let ops = parse_ops("Align(Y, Up)").unwrap();
2201 assert_eq!(ops.len(), 1);
2202 let ShapeOp::Align { local_axis, target } = &ops[0] else {
2203 panic!("expected Align");
2204 };
2205 assert_eq!(*local_axis, Axis::Y);
2206 assert!((*target - crate::scope::Vec3::new(0.0, 1.0, 0.0)).length() < 1e-9);
2207 }
2208
2209 #[test]
2210 fn test_parse_align_world_prefix() {
2211 let ops = parse_ops("Align(Z, World.Forward)").unwrap();
2212 let ShapeOp::Align { local_axis, target } = &ops[0] else {
2213 panic!("expected Align");
2214 };
2215 assert_eq!(*local_axis, Axis::Z);
2216 assert!((*target - crate::scope::Vec3::new(0.0, 0.0, -1.0)).length() < 1e-9);
2217 }
2218
2219 #[test]
2220 fn test_parse_align_unknown_target_rejected() {
2221 assert!(parse_ops("Align(Y, Sideways)").is_err());
2222 }
2223
2224 #[test]
2227 fn test_parse_offset_basic() {
2228 let ops = parse_ops("Offset(-0.2) { Inside: Glass | Border: Frame }").unwrap();
2229 assert_eq!(ops.len(), 1);
2230 let ShapeOp::Offset { distance, cases } = &ops[0] else {
2231 panic!("expected Offset");
2232 };
2233 assert!((distance.as_lit().unwrap() - (-0.2)).abs() < 1e-9);
2234 assert_eq!(cases.len(), 2);
2235 assert_eq!(cases[0].selector, OffsetSelector::Inside);
2236 assert_eq!(cases[0].rule, "Glass");
2237 assert_eq!(cases[1].selector, OffsetSelector::Border);
2238 assert_eq!(cases[1].rule, "Frame");
2239 }
2240
2241 #[test]
2242 fn test_parse_offset_is_terminating() {
2243 assert!(parse_ops("Offset(-0.1) { Inside: A } Scale(1, 2, 1)").is_err());
2244 }
2245
2246 #[test]
2249 fn test_parse_roof_gable_no_overhang() {
2250 let ops = parse_ops("Roof(Gable, 30) { Slope: Tiles | GableEnd: Bricks }").unwrap();
2251 assert_eq!(ops.len(), 1);
2252 let ShapeOp::Roof { spec, cases } = &ops[0] else {
2253 panic!("expected Roof");
2254 };
2255 assert_eq!(spec.roof_type, RoofType::Gable);
2256 assert!((spec.pitch.as_lit().unwrap() - 30.0).abs() < 1e-9);
2257 assert!(spec.overhang.as_lit().unwrap().abs() < 1e-9);
2258 assert_eq!(cases.len(), 2);
2259 assert_eq!(cases[0].selector, RoofFaceSelector::Slope);
2260 assert_eq!(cases[1].selector, RoofFaceSelector::GableEnd);
2261 }
2262
2263 #[test]
2264 fn test_parse_roof_hip_with_overhang() {
2265 let ops = parse_ops("Roof(Hip, 45, 0.5) { Slope: Tiles }").unwrap();
2266 let ShapeOp::Roof { spec, .. } = &ops[0] else {
2267 panic!("expected Roof");
2268 };
2269 assert_eq!(spec.roof_type, RoofType::Hip);
2270 assert!((spec.pitch.as_lit().unwrap() - 45.0).abs() < 1e-9);
2271 assert!((spec.overhang.as_lit().unwrap() - 0.5).abs() < 1e-9);
2272 }
2273
2274 #[test]
2275 fn test_parse_roof_angle_out_of_range_rejected() {
2276 assert!(parse_ops("Roof(Gable, 0) { Slope: Tiles }").is_err());
2277 assert!(parse_ops("Roof(Gable, 90) { Slope: Tiles }").is_err());
2278 }
2279
2280 #[test]
2281 fn test_parse_roof_is_terminating() {
2282 assert!(parse_ops("Roof(Shed, 30) { Slope: Tiles } Scale(1, 2, 1)").is_err());
2283 }
2284}