1use crate::Direction;
2use nom::branch::alt;
3use nom::bytes::complete::{tag, tag_no_case, take_while};
4use nom::character::complete::{multispace0, one_of};
5use nom::combinator::{all_consuming, cut, map, opt};
6use nom::error::{context, VerboseError};
7use nom::multi::{fold_many1, many0};
8use nom::sequence::{delimited, tuple};
9use nom::IResult;
10use std::collections::HashMap;
11
12#[derive(Debug, Clone)]
13pub enum Value {
14 Float(f64),
15 Ref(String),
16 Cel(String),
17}
18
19impl Value {
20 fn float(&self) -> f64 {
21 match self {
22 Value::Float(f) => *f,
23 _ => unimplemented!(),
24 }
25 }
26
27 fn rfloat(&self, r: &ResolverContext) -> Result<f64, Err> {
28 match self {
29 Value::Float(f) => Ok(*f),
30 Value::Ref(ident) => match r.definitions.get(ident) {
31 Some(var) => match var {
32 Variable::Number(n) => Ok(*n),
33 _ => Err(Err::BadType(ident.to_string())),
34 },
35 None => Err(Err::UndefinedVariable(ident.to_string())),
36 },
37 Value::Cel(exp) => {
38 use cel_interpreter::*;
39 match r.eval_cel(exp.to_string()) {
40 objects::CelType::UInt(n) => Ok(n as f64),
41 objects::CelType::Int(n) => Ok(n as f64),
42 objects::CelType::Float(n) => Ok(n),
43 _ => Err(Err::BadType(exp.to_string())),
44 }
45 }
46 }
47 }
48}
49
50#[derive(Debug, Clone)]
51enum Variable {
52 Geo(AST),
53 Number(f64),
54}
55
56#[derive(Debug, Clone, Default)]
57struct ResolverContext {
58 pub definitions: HashMap<String, Variable>,
59}
60
61impl ResolverContext {
62 fn handle_assignment(&mut self, var: String, ast: Box<AST>) {
63 match *ast {
64 AST::Cel(exp) => {
65 use cel_interpreter::objects::CelType;
66 match self.eval_cel(exp) {
67 CelType::UInt(n) => {
68 self.definitions.insert(var, Variable::Number(n as f64));
69 }
70 CelType::Int(n) => {
71 self.definitions.insert(var, Variable::Number(n as f64));
72 }
73 CelType::Float(n) => {
74 self.definitions.insert(var, Variable::Number(n));
75 }
76 _ => panic!(),
77 }
78 }
79 _ => {
80 self.definitions.insert(var, Variable::Geo(*ast));
81 }
82 }
83 }
84
85 fn cel_ctx(&self) -> cel_interpreter::context::Context {
86 use cel_interpreter::*;
87 let mut ctx = context::Context::default();
88 for (ident, val) in &self.definitions {
89 match val {
90 Variable::Geo(_) => {}
91 Variable::Number(n) => {
92 ctx.add_variable(ident.clone(), objects::CelType::Float(*n));
93 }
94 }
95 }
96
97 ctx
98 }
99
100 fn eval_cel(&self, exp: String) -> cel_interpreter::objects::CelType {
101 use cel_interpreter::*;
102 match Program::compile(&exp) {
103 Ok(p) => {
104 let ctx = self.cel_ctx();
105 p.execute(&ctx)
106 }
107 Err(e) => panic!("{}", e), }
109 }
110}
111
112#[derive(Debug, Clone)]
113pub enum Err {
114 Parse(String),
115 UndefinedVariable(String),
116 BadType(String),
117}
118
119#[derive(Debug, Clone)]
120pub enum InnerAST {
121 ScrewHole(Value),
122 Smiley,
123 MechanicalSolderPoint(Option<(Value, Value)>),
124}
125
126impl InnerAST {
127 fn into_inner_feature<'a>(
128 self,
129 _ctx: &mut ResolverContext,
130 ) -> Box<dyn super::features::InnerFeature + 'a> {
131 use super::features::{MechanicalSolderPoint, ScrewHole, Smiley};
132
133 match self {
134 InnerAST::ScrewHole(dia) => Box::new(ScrewHole::with_diameter(dia.float())),
135 InnerAST::Smiley => Box::new(Smiley::default()),
136 InnerAST::MechanicalSolderPoint(sz) => Box::new(match sz {
137 Some((x, y)) => MechanicalSolderPoint::with_size((x.float(), y.float())),
138 None => MechanicalSolderPoint::default(),
139 }),
140 }
141 }
142}
143
144#[derive(Debug, Clone)]
145pub enum WrapPosition {
146 Cardinal {
147 side: Direction,
148 offset: Value,
149 align: crate::Align,
150 },
151 Corner {
152 side: Direction,
153 opposite: bool,
154 align: crate::Align,
155 },
156 Angle {
157 angle: Value,
158 offset: Value,
159 },
160}
161
162impl WrapPosition {
163 fn into_positioning(self, r: &ResolverContext) -> Result<crate::features::Positioning, Err> {
164 match self {
165 WrapPosition::Cardinal {
166 side,
167 offset,
168 align,
169 } => Ok(crate::features::Positioning::Cardinal {
170 side,
171 align,
172 centerline_adjustment: offset.rfloat(r)?,
173 }),
174 WrapPosition::Corner {
175 side,
176 opposite,
177 align,
178 } => Ok(crate::features::Positioning::Corner {
179 side,
180 align,
181 opposite,
182 }),
183 WrapPosition::Angle { angle, offset } => Ok(crate::features::Positioning::Angle {
184 degrees: angle.rfloat(r)?,
185 amount: offset.rfloat(r)?,
186 }),
187 }
188 }
189}
190
191#[derive(Debug, Clone)]
192pub enum AST {
193 Assign(String, Box<AST>),
194 VarRef(String),
195 Comment(String),
196 Cel(String),
197 Rect {
198 coords: Option<(Value, Value)>,
199 size: Option<(Value, Value)>,
200 inner: Option<InnerAST>,
201 rounded: Option<Value>,
202 },
203 Circle {
204 coords: Option<(Value, Value)>,
205 radius: Value,
206 inner: Option<InnerAST>,
207 },
208 Triangle {
209 size: (Value, Value),
210 inner: Option<InnerAST>,
211 },
212 RMount {
213 depth: Value,
214 dir: crate::Direction,
215 },
216 Array {
217 dir: crate::Direction,
218 num: usize,
219 inner: Box<AST>,
220 vscore: bool,
221 },
222 ColumnLayout {
223 coords: Option<(Value, Value)>,
224 align: crate::Align,
225 inners: Vec<Box<AST>>,
226 },
227 Wrap {
228 inner: Box<AST>,
229 features: Vec<(WrapPosition, Box<AST>)>,
230 },
231 Tuple {
232 inners: Vec<Box<AST>>,
233 },
234 Negative {
235 inners: Vec<Box<AST>>,
236 },
237 Rotate {
238 rotation: Value,
239 inners: Vec<Box<AST>>,
240 },
241}
242
243impl AST {
244 fn into_feature<'a>(
245 self,
246 ctx: &mut ResolverContext,
247 ) -> Result<Box<dyn super::Feature + 'a>, Err> {
248 use super::features::{Circle, RMount, Rect, Triangle};
249
250 match self {
251 AST::Rect {
252 coords,
253 size,
254 inner,
255 rounded: _,
256 } => Ok(if let Some(inner) = inner {
257 let r = Rect::with_inner(inner.into_inner_feature(ctx));
258 let (w, h) = if let Some((w, h)) = size {
259 (w.rfloat(ctx)?, h.rfloat(ctx)?)
260 } else {
261 (2., 2.)
262 };
263 let r = if let Some((x, y)) = coords {
264 r.dimensions((x.rfloat(ctx)?, y.rfloat(ctx)?).into(), w, h)
265 } else {
266 r.dimensions([0., 0.].into(), w, h)
267 };
268 Box::new(r)
269 } else {
270 Box::new(match (coords, size) {
271 (Some((x, y)), Some((w, h))) => Rect::with_center(
272 (x.rfloat(ctx)?, y.rfloat(ctx)?).into(),
273 w.rfloat(ctx)?,
274 h.rfloat(ctx)?,
275 ),
276 (None, Some((w, h))) => {
277 Rect::with_center([0., 0.].into(), w.rfloat(ctx)?, h.rfloat(ctx)?)
278 }
279 (Some((x, y)), None) => {
280 Rect::with_center((x.rfloat(ctx)?, y.rfloat(ctx)?).into(), 2., 2.)
281 }
282 (None, None) => Rect::with_center([-1f64, -1f64].into(), 2., 2.),
283 })
284 }),
285 AST::Circle {
286 coords,
287 radius,
288 inner,
289 } => Ok(match (inner, coords) {
290 (Some(i), Some((x, y))) => Box::new(Circle::with_inner(
291 i.into_inner_feature(ctx),
292 (x.rfloat(ctx)?, y.rfloat(ctx)?).into(),
293 radius.rfloat(ctx)?,
294 )),
295 (Some(i), None) => Box::new(Circle::wrap_with_radius(
296 i.into_inner_feature(ctx),
297 radius.rfloat(ctx)?,
298 )),
299 (None, Some((x, y))) => Box::new(Circle::new(
300 (x.rfloat(ctx)?, y.rfloat(ctx)?).into(),
301 radius.rfloat(ctx)?,
302 )),
303 (None, None) => Box::new(Circle::with_radius(radius.rfloat(ctx)?)),
304 }),
305 AST::Triangle { size, inner } => Ok(match inner {
306 Some(i) => Box::new(Triangle::with_inner(i.into_inner_feature(ctx)).dimensions(
307 [0., 0.].into(),
308 size.0.rfloat(ctx)?,
309 size.1.rfloat(ctx)?,
310 )),
311 None => Box::new(Triangle::right_angle(
312 size.0.rfloat(ctx)?,
313 size.1.rfloat(ctx)?,
314 )),
315 }),
316 AST::RMount { depth, dir } => {
317 Ok(Box::new(RMount::new(depth.rfloat(ctx)?).direction(dir)))
318 }
319 AST::Array {
320 dir,
321 num,
322 inner,
323 vscore,
324 } => Ok(Box::new(
325 crate::features::repeating::Tile::new(inner.into_feature(ctx)?, dir, num)
326 .v_score(vscore),
327 )),
328 AST::ColumnLayout {
329 align,
330 inners,
331 coords,
332 } => Ok(Box::new({
333 let mut layout = match align {
334 crate::Align::Start => crate::features::Column::align_left(
335 inners
336 .into_iter()
337 .map(|i| i.into_feature(ctx))
338 .collect::<Result<Vec<_>, Err>>()?,
339 ),
340 crate::Align::Center => crate::features::Column::align_center(
341 inners
342 .into_iter()
343 .map(|i| i.into_feature(ctx))
344 .collect::<Result<Vec<_>, Err>>()?,
345 ),
346 crate::Align::End => crate::features::Column::align_right(
347 inners
348 .into_iter()
349 .map(|i| i.into_feature(ctx))
350 .collect::<Result<Vec<_>, Err>>()?,
351 ),
352 };
353 if let Some((x, y)) = coords {
354 use crate::features::Feature;
355 layout.translate([x.rfloat(ctx)?, y.rfloat(ctx)?].into());
356 };
357 layout
358 })),
359 AST::Wrap { inner, features } => {
360 let mut pos = crate::features::AtPos::new(inner.into_feature(ctx)?);
361 for (position, feature) in features {
362 pos.push(feature.into_feature(ctx)?, position.into_positioning(ctx)?);
363 }
364 Ok(Box::new(pos))
365 }
366 AST::Tuple { inners } => {
367 let mut out: Option<Box<dyn super::Feature>> = None;
368 for inner in inners.into_iter() {
369 out = match out {
370 None => Some(inner.into_feature(ctx)?),
371 Some(left) => Some(Box::new({
372 let mut out = crate::features::AtPos::new(left);
373 out.push(
374 inner.into_feature(ctx)?,
375 crate::features::Positioning::Cardinal {
376 side: Direction::Right,
377 align: crate::Align::End,
378 centerline_adjustment: 0.,
379 },
380 );
381 out
382 })),
383 };
384 }
385
386 Ok(out.unwrap())
387 }
388 AST::Negative { inners } => Ok(Box::new(crate::features::Negative::new(
389 inners
390 .into_iter()
391 .map(|f| f.into_feature(ctx))
392 .collect::<Result<Vec<_>, Err>>()?,
393 ))),
394 AST::Rotate { rotation, inners } => Ok(Box::new(crate::features::Rotate::new(
395 rotation.rfloat(ctx)?,
396 inners
397 .into_iter()
398 .map(|f| f.into_feature(ctx))
399 .collect::<Result<Vec<_>, Err>>()?,
400 ))),
401 AST::Assign(_, _) => unreachable!(),
402 AST::Comment(_) => unreachable!(),
403 AST::Cel(_) => unreachable!(),
404 AST::VarRef(ident) => match ctx.definitions.get(&ident) {
405 Some(var) => match var {
406 Variable::Geo(ast) => ast.clone().into_feature(ctx),
407 _ => Err(Err::BadType(ident)),
408 },
409 None => Err(Err::UndefinedVariable(ident)),
410 },
411 }
412 }
413}
414
415fn parse_cel(i: &str) -> IResult<&str, AST, VerboseError<&str>> {
416 let (start, _) = multispace0(i)?;
417 let (i, exp) = context(
418 "cel",
419 delimited(tag("!{"), cut(take_while(|c| c != '}')), tag("}")),
420 )(start)?;
421
422 if let Err(_) = cel_interpreter::Program::compile(exp) {
423 return Err(nom::Err::Error(VerboseError {
424 errors: vec![(
425 start,
426 nom::error::VerboseErrorKind::Nom(nom::error::ErrorKind::Satisfy),
427 )],
428 }));
429 }
430
431 Ok((i, AST::Cel(exp.to_string())))
432}
433
434fn parse_ident(i: &str) -> IResult<&str, String, VerboseError<&str>> {
435 let (i, _) = multispace0(i)?;
436 let (i, s) = context(
437 "ident",
438 take_while(|c| {
439 c == '_' || (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
440 }),
441 )(i)?;
442 Ok((i, s.into()))
443}
444
445fn parse_uint(i: &str) -> IResult<&str, usize, VerboseError<&str>> {
446 let (i, _) = multispace0(i)?;
447 let (i, s) = context("uint", take_while(|c| c == '-' || (c >= '0' && c <= '9')))(i)?;
448 Ok((
449 i,
450 s.parse().map_err(|_e| {
451 nom::Err::Error(VerboseError {
452 errors: vec![(
453 i,
454 nom::error::VerboseErrorKind::Nom(nom::error::ErrorKind::Digit),
455 )],
456 })
457 })?,
458 ))
459}
460
461fn parse_float(i: &str) -> IResult<&str, Value, VerboseError<&str>> {
462 let (i, _) = multispace0(i)?;
463
464 if let Ok((i, _)) = tag::<_, _, VerboseError<&str>>("$")(i) {
466 let (i, ident) = parse_ident(i)?;
467 return Ok((i, Value::Ref(ident)));
468 }
469
470 if let Ok((i, ast)) = parse_cel(i) {
472 match ast {
473 AST::Cel(exp) => {
474 return Ok((i, Value::Cel(exp)));
475 }
476 _ => unreachable!(),
477 }
478 }
479
480 let (i, s) = context(
481 "float",
482 take_while(|c| c == '.' || c == '+' || c == '-' || (c >= '0' && c <= '9')),
483 )(i)?;
484
485 Ok((
486 i,
487 Value::Float(s.parse().map_err(|_e| {
488 nom::Err::Error(VerboseError {
489 errors: vec![(
490 i,
491 nom::error::VerboseErrorKind::Nom(nom::error::ErrorKind::Digit),
492 )],
493 })
494 })?),
495 ))
496}
497
498fn parse_coords(i: &str) -> IResult<&str, (Value, Value), VerboseError<&str>> {
499 let (i, _) = multispace0(i)?;
500 let (i, _) = tag("(")(i)?;
501 let (i, x) = parse_float(i)?;
502 let (i, _) = multispace0(i)?;
503 let (i, _) = tag(",")(i)?;
504 let (i, y) = parse_float(i)?;
505 let (i, _) = multispace0(i)?;
506 let (i, _) = tag(")")(i)?;
507 Ok((i, (x, y)))
508}
509
510fn parse_inner(i: &str) -> IResult<&str, InnerAST, VerboseError<&str>> {
511 let (i, _) = multispace0(i)?;
512
513 let (i, inner) = delimited(
514 tuple((tag("("), multispace0)),
515 alt((
516 map(tuple((tag("h"), parse_float)), |(_, f)| {
517 InnerAST::ScrewHole(f)
518 }),
519 map(tag("h"), |_| InnerAST::ScrewHole(Value::Float(3.1))),
520 map(tag("smiley"), |_| InnerAST::Smiley),
521 parse_inner_msp,
522 )),
523 tuple((multispace0, tag(")"))),
524 )(i)?;
525
526 Ok((i, inner))
527}
528
529fn parse_inner_msp(i: &str) -> IResult<&str, InnerAST, VerboseError<&str>> {
530 let (i, _) = tag_no_case("msp")(i)?;
531 match context("msp details", parse_details)(i) {
532 Ok((i2, deets)) => {
533 let size = if let Some((x, y)) = deets.size {
534 Some((x, y))
535 } else if deets.extra.len() == 2 {
536 Some((deets.extra[0].clone(), deets.extra[1].clone()))
537 } else if deets.extra.len() == 1 {
538 Some((deets.extra[0].clone(), deets.extra[0].clone()))
539 } else {
540 None
541 };
542
543 Ok((i2, InnerAST::MechanicalSolderPoint(size)))
544 }
545 Err(_) => Ok((i, InnerAST::MechanicalSolderPoint(None))),
546 }
547}
548
549enum DetailFragment {
550 Coord(Value, Value),
551 Size(Value, Value),
552 Radius(Value),
553 Rounding(Value),
554 Extra(Value),
555}
556
557#[derive(Debug, Default, Clone)]
558struct Details {
559 coords: Option<(Value, Value)>,
560 size: Option<(Value, Value)>,
561 radius: Option<Value>,
562 extra: Vec<Value>,
563 inner: Option<InnerAST>,
564 rounded: Option<Value>,
565}
566
567impl Details {
568 fn parse_pos(i: &str) -> IResult<&str, DetailFragment, VerboseError<&str>> {
569 let (i, _) = multispace0(i)?;
570 let (i, _t) = tag("@")(i)?;
571 let (i, c) = cut(parse_coords)(i)?;
572 Ok((i, DetailFragment::Coord(c.0, c.1)))
573 }
574 fn parse_extra(i: &str) -> IResult<&str, DetailFragment, VerboseError<&str>> {
575 let (i, f) = parse_float(i)?;
576 Ok((i, DetailFragment::Extra(f)))
577 }
578 fn parse_size(i: &str) -> IResult<&str, DetailFragment, VerboseError<&str>> {
579 let (i, _) = multispace0(i)?;
580 let (i, (_, _, _, _, c)) = tuple((
581 alt((tag_no_case("size"), tag_no_case("s"))),
582 multispace0,
583 tag("="),
584 multispace0,
585 cut(parse_coords),
586 ))(i)?;
587 Ok((i, DetailFragment::Size(c.0, c.1)))
588 }
589 fn parse_radius(i: &str) -> IResult<&str, DetailFragment, VerboseError<&str>> {
590 let (i, _) = multispace0(i)?;
591 let (i, (_, _, _, _, r)) = tuple((
592 alt((tag_no_case("radius"), tag_no_case("r"))),
593 multispace0,
594 tag("="),
595 multispace0,
596 cut(parse_float),
597 ))(i)?;
598 Ok((i, DetailFragment::Radius(r)))
599 }
600 fn parse_rounding(i: &str) -> IResult<&str, DetailFragment, VerboseError<&str>> {
601 let (i, _) = multispace0(i)?;
602 let (i, (_, _, _, _, r)) = tuple((
603 alt((tag_no_case("round"), tag_no_case("r"))),
604 multispace0,
605 tag("="),
606 multispace0,
607 cut(parse_float),
608 ))(i)?;
609 Ok((i, DetailFragment::Rounding(r)))
610 }
611
612 fn with_inner(mut self, inner: Option<InnerAST>) -> Self {
613 self.inner = inner;
614 self
615 }
616}
617
618fn parse_details(i: &str) -> IResult<&str, Details, VerboseError<&str>> {
619 let (i, _) = multispace0(i)?;
620
621 let (i, deets) = delimited(
622 tuple((tag("<"), multispace0)),
623 cut(fold_many1(
624 alt((
625 tuple((
626 context("pos", Details::parse_pos),
627 multispace0,
628 opt(tag(",")),
629 )),
630 tuple((
631 context("size", Details::parse_size),
632 multispace0,
633 opt(tag(",")),
634 )),
635 tuple((
636 context("radius", Details::parse_radius),
637 multispace0,
638 opt(tag(",")),
639 )),
640 tuple((
641 context("rounding", Details::parse_rounding),
642 multispace0,
643 opt(tag(",")),
644 )),
645 tuple((Details::parse_extra, multispace0, opt(tag(",")))),
646 )),
647 Details::default(),
648 |mut acc: Details, (fragment, _, _)| {
649 match fragment {
650 DetailFragment::Coord(x, y) => {
651 acc.coords = Some((x, y));
652 }
653 DetailFragment::Size(x, y) => {
654 acc.size = Some((x, y));
655 }
656 DetailFragment::Radius(r) => {
657 acc.radius = Some(r);
658 }
659 DetailFragment::Rounding(r) => {
660 acc.rounded = Some(r);
661 }
662 DetailFragment::Extra(f) => acc.extra.push(f),
663 }
664 acc
665 },
666 )),
667 tuple((tag(">"), multispace0)),
668 )(i)?;
669
670 let (i, inner) = opt(parse_inner)(i)?;
671 Ok((i, deets.with_inner(inner)))
672}
673
674fn parse_rect(i: &str) -> IResult<&str, AST, VerboseError<&str>> {
675 let (i, _) = multispace0(i)?;
676 let (i, _) = tag_no_case("R")(i)?;
677 let (i, deets) = context("rectangle details", parse_details)(i)?;
678
679 let size = if let Some((x, y)) = deets.size {
680 Some((x, y))
681 } else if deets.extra.len() == 2 {
682 Some((deets.extra[0].clone(), deets.extra[1].clone()))
683 } else if deets.extra.len() == 1 {
684 Some((deets.extra[0].clone(), deets.extra[0].clone()))
685 } else {
686 None
687 };
688
689 Ok((
690 i,
691 AST::Rect {
692 size,
693 coords: deets.coords,
694 inner: deets.inner,
695 rounded: deets.rounded,
696 },
697 ))
698}
699
700fn parse_circle(i: &str) -> IResult<&str, AST, VerboseError<&str>> {
701 let (i, _) = multispace0(i)?;
702 let (i, _) = tag_no_case("C")(i)?;
703 let (i2, deets) = context("circle details", parse_details)(i)?;
704
705 let r = if let Some(r) = deets.radius {
706 r
707 } else if deets.extra.len() == 1 {
708 deets.extra[0].clone()
709 } else {
710 return Err(nom::Err::Failure(nom::error::make_error(
711 i,
712 nom::error::ErrorKind::Satisfy,
713 )));
714 };
715
716 Ok((
717 i2,
718 AST::Circle {
719 coords: deets.coords,
720 radius: r,
721 inner: deets.inner,
722 },
723 ))
724}
725
726fn parse_triangle(i: &str) -> IResult<&str, AST, VerboseError<&str>> {
727 let (i, _) = multispace0(i)?;
728 let (i, _) = tag_no_case("T")(i)?;
729 let (i2, deets) = context("triangle details", cut(parse_details))(i)?;
730
731 let size = if let Some((x, y)) = deets.size {
732 (x, y)
733 } else if deets.extra.len() == 2 {
734 (deets.extra[0].clone(), deets.extra[1].clone())
735 } else if deets.extra.len() == 1 {
736 (deets.extra[0].clone(), deets.extra[0].clone())
737 } else {
738 return Err(nom::Err::Failure(nom::error::make_error(
739 i,
740 nom::error::ErrorKind::Satisfy,
741 )));
742 };
743
744 Ok((
745 i2,
746 AST::Triangle {
747 size,
748 inner: deets.inner,
749 },
750 ))
751}
752
753fn parse_rmount(i: &str) -> IResult<&str, AST, VerboseError<&str>> {
754 let (i, _) = multispace0(i)?;
755 let (i, dir) = alt((
756 tag_no_case("mount_cut_left"),
757 tag_no_case("mount_cut_right"),
758 tag_no_case("mount_cut_down"),
759 tag_no_case("mount_cut"),
760 ))(i)?;
761 let (i, deets) = context("mount details", cut(parse_details))(i)?;
762
763 let depth = if deets.extra.len() == 1 {
764 deets.extra[0].clone()
765 } else {
766 return Err(nom::Err::Failure(nom::error::make_error(
767 i,
768 nom::error::ErrorKind::Satisfy,
769 )));
770 };
771
772 Ok((
773 i,
774 AST::RMount {
775 depth,
776 dir: match dir.to_lowercase().as_str() {
777 "mount_cut_left" => crate::Direction::Left,
778 "mount_cut_right" => crate::Direction::Right,
779 "mount_cut_down" => crate::Direction::Down,
780 _ => crate::Direction::Up,
781 },
782 },
783 ))
784}
785
786fn parse_array(i: &str) -> IResult<&str, AST, VerboseError<&str>> {
787 let (i, _) = multispace0(i)?;
788
789 let (i, params) = context(
790 "array",
791 delimited(
792 tuple((tag("["), multispace0)),
793 cut(tuple((
794 parse_uint,
795 opt(tuple((multispace0, tag(";"), multispace0, one_of("UDRL")))),
796 opt(tuple((
797 multispace0,
798 tag(";"),
799 multispace0,
800 alt((tag_no_case("vscore"), tag_no_case("v-score"))),
801 ))),
802 ))),
803 tuple((tag("]"), multispace0)),
804 ),
805 )(i)?;
806 let (i, geo) = parse_geo(i)?;
807
808 let (num, dir, vscore) = params;
809 let dir = if let Some((_, _, _, s)) = dir {
810 match s {
811 'L' => crate::Direction::Left,
812 'R' => crate::Direction::Right,
813 'U' => crate::Direction::Up,
814 'D' => crate::Direction::Down,
815 _ => {
816 return Err(nom::Err::Failure(nom::error::make_error(
817 i,
818 nom::error::ErrorKind::Satisfy,
819 )));
820 }
821 }
822 } else {
823 crate::Direction::Right
824 };
825
826 Ok((
827 i,
828 AST::Array {
829 dir,
830 num,
831 inner: Box::new(geo),
832 vscore: vscore.is_some(),
833 },
834 ))
835}
836
837fn parse_column_layout(i: &str) -> IResult<&str, AST, VerboseError<&str>> {
838 let (i, _) = multispace0(i)?;
839
840 let (i, (dir, _, pos, _, _, inners)) = context(
841 "column",
842 delimited(
843 tuple((tag_no_case("column"), multispace0)),
844 tuple((
845 alt((
846 tag_no_case("left"),
847 tag_no_case("center"),
848 tag_no_case("right"),
849 )),
850 multispace0,
851 opt(tuple((tag("@"), parse_coords))),
852 multispace0,
853 tag("{"),
854 fold_many1(
855 tuple((parse_geo, multispace0, opt(tag(",")))),
856 Vec::new(),
857 |mut acc, (inner, _, _)| {
858 acc.push(Box::new(inner));
859 acc
860 },
861 ),
862 )),
863 tuple((tag("}"), multispace0)),
864 ),
865 )(i)?;
866
867 Ok((
868 i,
869 AST::ColumnLayout {
870 align: match dir.to_lowercase().as_str() {
871 "left" => crate::Align::Start,
872 "right" => crate::Align::End,
873 _ => crate::Align::Center,
874 },
875 inners: inners,
876 coords: pos.map(|x| x.1),
877 },
878 ))
879}
880
881fn parse_pos_spec(i: &str) -> IResult<&str, WrapPosition, VerboseError<&str>> {
882 let (i, (_, side, offset, _, align, _)) = tuple((
883 multispace0,
884 alt((
885 tag_no_case("left"),
886 tag_no_case("right"),
887 tag_no_case("up"),
888 tag_no_case("down"),
889 tag_no_case("top"),
890 tag_no_case("bottom"),
891 )),
892 opt(parse_float),
893 multispace0,
894 opt(tuple((
895 multispace0,
896 tag_no_case("align"),
897 multispace0,
898 alt((
899 tag_no_case("center"),
900 tag_no_case("exterior"),
901 tag_no_case("interior"),
902 )),
903 multispace0,
904 ))),
905 tag("=>"),
906 ))(i)?;
907
908 Ok((
909 i,
910 WrapPosition::Cardinal {
911 side: match side.to_lowercase().as_str() {
912 "left" => Direction::Left,
913 "right" => Direction::Right,
914 "top" | "up" => Direction::Up,
915 "bottom" | "down" => Direction::Down,
916 _ => unreachable!(),
917 },
918 offset: offset.unwrap_or(Value::Float(0.0)),
919 align: match align {
920 Some((_, _, _, align, _)) => match align.to_lowercase().as_str() {
921 "exterior" => crate::Align::End,
922 "interior" => crate::Align::Start,
923 _ => crate::Align::Center,
924 },
925 _ => crate::Align::Center,
926 },
927 },
928 ))
929}
930
931fn parse_about_spec(i: &str) -> IResult<&str, WrapPosition, VerboseError<&str>> {
932 let (i, (_, angle, _, offset, _, _)) = tuple((
933 tuple((multispace0, tag_no_case("angle("))),
934 parse_float,
935 tuple((multispace0, tag(")"))),
936 opt(parse_float),
937 multispace0,
938 tag("=>"),
939 ))(i)?;
940
941 Ok((
942 i,
943 WrapPosition::Angle {
944 angle,
945 offset: offset.unwrap_or(Value::Float(0.0)),
946 },
947 ))
948}
949
950fn parse_wrap_center_spec(i: &str) -> IResult<&str, WrapPosition, VerboseError<&str>> {
951 let (i, _) = tuple((
952 tuple((multispace0, tag_no_case("center"))),
953 multispace0,
954 tag("=>"),
955 ))(i)?;
956
957 Ok((
958 i,
959 WrapPosition::Angle {
960 angle: Value::Float(0.0),
961 offset: Value::Float(0.0),
962 },
963 ))
964}
965
966fn parse_corner_spec(i: &str) -> IResult<&str, WrapPosition, VerboseError<&str>> {
967 let (i, (_, opp, side, _, align, _)) = tuple((
968 multispace0,
969 alt((tag_no_case("min-"), tag_no_case("max-"))),
970 alt((
971 tag_no_case("left"),
972 tag_no_case("right"),
973 tag_no_case("up"),
974 tag_no_case("down"),
975 tag_no_case("top"),
976 tag_no_case("bottom"),
977 )),
978 multispace0,
979 opt(tuple((
980 multispace0,
981 tag_no_case("align"),
982 multispace0,
983 alt((
984 tag_no_case("center"),
985 tag_no_case("exterior"),
986 tag_no_case("interior"),
987 )),
988 multispace0,
989 ))),
990 tag("=>"),
991 ))(i)?;
992
993 Ok((
994 i,
995 WrapPosition::Corner {
996 side: match side.to_lowercase().as_str() {
997 "left" => Direction::Left,
998 "right" => Direction::Right,
999 "top" | "up" => Direction::Up,
1000 "bottom" | "down" => Direction::Down,
1001 _ => unreachable!(),
1002 },
1003 opposite: match opp.to_lowercase().as_str() {
1004 "min-" => false,
1005 "max-" => true,
1006 _ => unreachable!(),
1007 },
1008 align: match align {
1009 Some((_, _, _, align, _)) => match align.to_lowercase().as_str() {
1010 "exterior" => crate::Align::End,
1011 "interior" => crate::Align::Start,
1012 _ => crate::Align::Center,
1013 },
1014 _ => crate::Align::Center,
1015 },
1016 },
1017 ))
1018}
1019
1020fn parse_wrap(i: &str) -> IResult<&str, AST, VerboseError<&str>> {
1021 let (i, _) = multispace0(i)?;
1022
1023 let (i, (_, _, _, inner, _, _, _, _, _, _)) = context(
1024 "wrap",
1025 tuple((
1026 tag_no_case("wrap"),
1027 multispace0,
1028 tag("("),
1029 parse_geo,
1030 multispace0,
1031 tag(")"),
1032 multispace0,
1033 tag_no_case("with"),
1034 multispace0,
1035 tag("{"),
1036 )),
1037 )(i)?;
1038 let (i, elements) = fold_many1(
1039 context(
1040 "pos spec",
1041 alt((
1042 nom::combinator::map(
1043 tuple((
1044 alt((
1045 parse_pos_spec,
1046 parse_about_spec,
1047 parse_wrap_center_spec,
1048 parse_corner_spec,
1049 )),
1050 multispace0,
1051 parse_geo,
1052 multispace0,
1053 opt(tag(",")),
1054 )),
1055 |s| Some(s),
1056 ),
1057 nom::combinator::map(parse_comment, |_| None),
1058 )),
1059 ),
1060 Vec::new(),
1061 |mut acc, feature| {
1062 if let Some((pos, _, feature, _, _)) = feature {
1063 acc.push((pos, Box::new(feature)));
1064 }
1065 acc
1066 },
1067 )(i)?;
1068 let (i, _) = tuple((multispace0, tag("}"), multispace0))(i)?;
1069
1070 Ok((
1071 i,
1072 AST::Wrap {
1073 inner: Box::new(inner),
1074 features: elements,
1075 },
1076 ))
1077}
1078
1079fn parse_assign(i: &str) -> IResult<&str, AST, VerboseError<&str>> {
1080 let (i, _) = multispace0(i)?;
1081
1082 let (i, (_, _, var, _, _, geo, _, _)) = tuple((
1083 tag("let"),
1084 multispace0,
1085 parse_ident,
1086 multispace0,
1087 tag("="),
1088 parse_geo,
1089 multispace0,
1090 opt(tag(";")),
1091 ))(i)?;
1092
1093 Ok((i, AST::Assign(var, Box::new(geo))))
1094}
1095
1096fn parse_var(i: &str) -> IResult<&str, AST, VerboseError<&str>> {
1097 let (i, _) = multispace0(i)?;
1098 let (i, (_, var)) = tuple((tag("$"), parse_ident))(i)?;
1099 Ok((i, AST::VarRef(var)))
1100}
1101
1102pub fn parse_comment(i: &str) -> IResult<&str, AST, VerboseError<&str>> {
1103 let (i, _) = multispace0(i)?;
1104 let (i, _) = alt((tag("#"), tag("//")))(i)?;
1105 let (i, v) = take_while(|chr| chr != '\n')(i)?;
1106 Ok((i, AST::Comment(v.to_string())))
1107}
1108
1109fn parse_tuple(i: &str) -> IResult<&str, AST, VerboseError<&str>> {
1110 let (i, _) = multispace0(i)?;
1111 let (i, _) = tag("(")(i)?;
1112
1113 let (i, elements) = fold_many1(
1114 tuple((multispace0, parse_geo, multispace0, opt(tag(",")))),
1115 Vec::new(),
1116 |mut acc, (_, feature, _, _)| {
1117 acc.push(Box::new(feature));
1118 acc
1119 },
1120 )(i)?;
1121 let (i, _) = tuple((multispace0, tag(")"), multispace0))(i)?;
1122
1123 Ok((i, AST::Tuple { inners: elements }))
1124}
1125
1126fn parse_negative(i: &str) -> IResult<&str, AST, VerboseError<&str>> {
1127 let (i, _) = multispace0(i)?;
1128
1129 let (i, (_, _, inners)) = context(
1130 "negative",
1131 delimited(
1132 tuple((tag_no_case("negative"), multispace0)),
1133 tuple((
1134 multispace0,
1135 tag("{"),
1136 fold_many1(
1137 tuple((parse_geo, multispace0, opt(tag(",")))),
1138 Vec::new(),
1139 |mut acc, (inner, _, _)| {
1140 acc.push(Box::new(inner));
1141 acc
1142 },
1143 ),
1144 )),
1145 tuple((tag("}"), multispace0)),
1146 ),
1147 )(i)?;
1148
1149 Ok((i, AST::Negative { inners: inners }))
1150}
1151
1152fn parse_rotate(i: &str) -> IResult<&str, AST, VerboseError<&str>> {
1153 let (i, _) = multispace0(i)?;
1154
1155 let (i, (_, _, _, rotation, _, _, _)) = context(
1156 "rotate",
1157 tuple((
1158 tag_no_case("rotate"),
1159 multispace0,
1160 tag("("),
1161 parse_float,
1162 multispace0,
1163 tag(")"),
1164 multispace0,
1165 )),
1166 )(i)?;
1167
1168 let (i, (_, inners)) = context(
1169 "rotate_body",
1170 delimited(
1171 tag("{"),
1172 tuple((
1173 multispace0,
1174 fold_many1(
1175 tuple((parse_geo, multispace0, opt(tag(",")))),
1176 Vec::new(),
1177 |mut acc, (inner, _, _)| {
1178 acc.push(Box::new(inner));
1179 acc
1180 },
1181 ),
1182 )),
1183 tuple((tag("}"), multispace0)),
1184 ),
1185 )(i)?;
1186
1187 Ok((
1188 i,
1189 AST::Rotate {
1190 rotation: rotation,
1191 inners: inners,
1192 },
1193 ))
1194}
1195
1196fn parse_geo(i: &str) -> IResult<&str, AST, VerboseError<&str>> {
1197 alt((
1198 parse_assign,
1199 parse_cel,
1200 parse_array,
1201 parse_rect,
1202 parse_circle,
1203 parse_triangle,
1204 parse_rmount,
1205 parse_wrap,
1206 parse_column_layout,
1207 parse_var,
1208 parse_tuple,
1209 parse_negative,
1210 parse_rotate,
1211 parse_comment,
1212 ))(i)
1213}
1214
1215pub fn build<'a>(i: &str) -> Result<Vec<Box<dyn super::Feature + 'a>>, Err> {
1218 let mut ctx = ResolverContext::default();
1219 let (_, (g, _)) = all_consuming(tuple((many0(parse_geo), multispace0)))(i).map_err(|e| {
1220 Err::Parse(nom::error::convert_error(
1221 i,
1222 match e {
1223 nom::Err::Error(e) | nom::Err::Failure(e) => e,
1224 _ => unreachable!(),
1225 },
1226 ))
1227 })?;
1228
1229 g.into_iter()
1230 .map(|g| match g {
1231 AST::Assign(var, geo) => {
1232 ctx.handle_assignment(var, geo);
1233 None
1234 }
1235 AST::Comment(_) => None,
1236 _ => Some(g.into_feature(&mut ctx)),
1237 })
1238 .filter(|f| f.is_some())
1239 .map(|f| f.unwrap())
1240 .collect()
1241}
1242
1243#[cfg(test)]
1244mod tests {
1245 use super::*;
1246
1247 #[test]
1248 fn test_rect() {
1249 let out = parse_geo("R<@(1,2)>");
1250 assert!(
1251 matches!(out, Ok(("", AST::Rect{ coords: Some((Value::Float(x), Value::Float(y))), size: None, inner: _, rounded: None })) if
1252 x > 0.99 && x < 1.01 && y > 1.99 && y < 2.01
1253 )
1254 );
1255
1256 let out = parse_geo("R<@(1,2), 2, 4>");
1257 assert!(
1258 matches!(out, Ok(("", AST::Rect{ coords: Some((Value::Float(x), Value::Float(y))), size: Some((Value::Float(w), Value::Float(h))), inner: _, rounded: None })) if
1259 x > 0.99 && x < 1.01 && y > 1.99 && y < 2.01 &&
1260 w > 1.99 && w < 2.01 && h > 3.99 && h < 4.01
1261 )
1262 );
1263
1264 let out = parse_geo("R<@(1,2), 4>");
1265 assert!(
1266 matches!(out, Ok(("", AST::Rect{ coords: Some((Value::Float(x), Value::Float(y))), size: Some((Value::Float(w), Value::Float(h))), inner: _, rounded: None })) if
1267 x > 0.99 && x < 1.01 && y > 1.99 && y < 2.01 &&
1268 w > 3.99 && w < 4.01 && h > 3.99 && h < 4.01
1269 )
1270 );
1271
1272 let out = parse_geo("R<4>");
1273 assert!(
1274 matches!(out, Ok(("", AST::Rect{ coords: None, size: Some((Value::Float(w), Value::Float(h))), inner: _, rounded: None })) if
1275 w > 3.99 && w < 4.01 && h > 3.99 && h < 4.01
1276 )
1277 );
1278
1279 let out = parse_geo("R<@(1,2), size = (2,4)>");
1280 assert!(
1281 matches!(out, Ok(("", AST::Rect{ coords: Some((Value::Float(x), Value::Float(y))), size: Some((Value::Float(w), Value::Float(h))), inner: _, rounded: None })) if
1282 x > 0.99 && x < 1.01 && y > 1.99 && y < 2.01 &&
1283 w > 1.99 && w < 2.01 && h > 3.99 && h < 4.01
1284 )
1285 );
1286
1287 let out = parse_geo(" R<6>(h)");
1288 assert!(
1289 matches!(out, Ok(("", AST::Rect{ coords: None, size: Some((Value::Float(w), Value::Float(h))), inner: Some(InnerAST::ScrewHole(Value::Float(dia))), rounded: None })) if
1290 w > 5.99 && w < 6.01 && h > 5.99 && h < 6.01 &&
1291 dia < 3.11 && dia > 3.09
1292 )
1293 );
1294
1295 let out = parse_geo(" R<6, round = 2>(h)");
1296 assert!(
1297 matches!(out, Ok(("", AST::Rect{ coords: None, size: Some((Value::Float(w), Value::Float(h))), inner: Some(InnerAST::ScrewHole(Value::Float(dia))), rounded: Some(_) })) if
1298 w > 5.99 && w < 6.01 && h > 5.99 && h < 6.01 &&
1299 dia < 3.11 && dia > 3.09
1300 )
1301 );
1302 }
1303
1304 #[test]
1305 fn test_circle() {
1306 let out = parse_geo("C < @ ( 2 , 1 ), 4.5>");
1307 assert!(
1308 matches!(out, Ok(("", AST::Circle{ coords: Some((Value::Float(x), Value::Float(y))), radius: r, inner: _ })) if
1309 y > 0.99 && y < 1.01 && x > 1.99 && x < 2.01 &&
1310 r.float() > 4.49 && r.float() < 4.51
1311 )
1312 );
1313
1314 let out = parse_geo("C<@(2, 1), 3.5>");
1315 assert!(
1316 matches!(out, Ok(("", AST::Circle{ coords: Some((Value::Float(x), Value::Float(y))), radius: r, inner: _ })) if
1317 y > 0.99 && y < 1.01 && x > 1.99 && x < 2.01 &&
1318 r.float() > 3.49 && r.float() < 3.51
1319 )
1320 );
1321 let out = parse_geo("C<3.5>");
1322 assert!(
1323 matches!(out, Ok(("", AST::Circle{ coords: None, radius: r, inner: _ })) if
1324 r.float() > 3.49 && r.float() < 3.51
1325 )
1326 );
1327
1328 let out = parse_geo("C<@(2, 1), R=3.5>");
1329 assert!(
1330 matches!(out, Ok(("", AST::Circle{ coords: Some((Value::Float(x), Value::Float(y))), radius: r, inner: _ })) if
1331 y > 0.99 && y < 1.01 && x> 1.99 && x < 2.01 &&
1332 r.float() > 3.49 && r.float() < 3.51
1333 )
1334 );
1335
1336 let out = parse_geo("C<3.5> ( h9 )");
1337 assert!(
1338 matches!(out, Ok(("", AST::Circle{ coords: None, radius: r, inner: Some(InnerAST::ScrewHole(Value::Float(dia))) })) if
1339 r.float() > 3.49 && r.float() < 3.51 && dia > 8.999 && dia < 9.001
1340 )
1341 );
1342 }
1343
1344 #[test]
1345 fn test_msp() {
1346 let out = parse_geo("C<5>(msp)");
1347 assert!(matches!(
1348 out,
1349 Ok((
1350 "",
1351 AST::Circle {
1352 inner: Some(InnerAST::MechanicalSolderPoint(None)),
1353 ..
1354 },
1355 ))
1356 ));
1357
1358 let out = parse_geo("C<5>(msp<2,1>)");
1359 assert!(matches!(
1360 out,
1361 Ok((
1362 "",
1363 AST::Circle { inner: Some(InnerAST::MechanicalSolderPoint(Some((Value::Float(w), Value::Float(h))))), .. },
1364 )) if w > 1.99 && w < 2.01 && h > 0.99 && h < 1.01
1365 ));
1366 }
1367
1368 #[test]
1369 fn test_triangle() {
1370 let out = parse_geo("T<2,1>");
1371 assert!(
1372 matches!(out, Ok(("", AST::Triangle{ size: (Value::Float(x), Value::Float(y)), inner: _ })) if
1373 y > 0.99 && y < 1.01 && x > 1.99 && x < 2.01
1374 )
1375 );
1376 }
1377
1378 #[test]
1379 fn test_r_mount() {
1380 let out = parse_geo("mount_cut<12>");
1381 assert!(matches!(out, Ok(("", AST::RMount{ depth, dir })) if
1382 depth.float() > 11.99 && depth.float() < 12.01 && dir == crate::Direction::Up
1383 ));
1384 }
1385
1386 #[test]
1387 fn test_array() {
1388 let out = parse_geo("[5]C<4.5>");
1389 assert!(
1390 matches!(out, Ok(("", AST::Array{ num: 5, inner: b, dir: crate::Direction::Right, vscore: false})) if
1391 matches!(&*b, AST::Circle{ radius, .. } if radius.float() > 4.4 && radius.float() < 4.6)
1392 )
1393 );
1394
1395 let out = parse_geo("[5; D; v-score]C<4.5>");
1396 assert!(
1397 matches!(out, Ok(("", AST::Array{ num: 5, inner: b, dir: crate::Direction::Down, vscore: true})) if
1398 matches!(&*b, AST::Circle{ radius, .. } if radius.float() > 4.4 && radius.float() < 4.6)
1399 )
1400 );
1401 }
1402
1403 #[test]
1404 fn test_column_layout() {
1405 let out = parse_geo("column left { R<5> }");
1406 assert!(matches!(
1407 out,
1408 Ok((
1409 "",
1410 AST::ColumnLayout {
1411 align: crate::Align::Start,
1412 inners: i,
1413 coords: None,
1414 },
1415 ))
1416 if i.len() == 1
1417 ));
1418
1419 let out = parse_geo("column RiGHt { C<5> R<1> }");
1420 assert!(matches!(
1421 out,
1422 Ok((
1423 "",
1424 AST::ColumnLayout {
1425 align: crate::Align::End,
1426 inners: i,
1427 coords: None,
1428 },
1429 ))
1430 if i.len() == 2
1431 ));
1432
1433 let out = parse_geo("column center { R<1> }");
1434 assert!(matches!(
1436 out,
1437 Ok((
1438 "",
1439 AST::ColumnLayout {
1440 align: crate::Align::Center,
1441 inners: i,
1442 coords: None,
1443 },
1444 ))
1445 if i.len() == 1
1446 ));
1447
1448 let out = parse_geo("column center { (R<1>) }");
1449 assert!(matches!(
1451 out,
1452 Ok((
1453 "",
1454 AST::ColumnLayout {
1455 align: crate::Align::Center,
1456 inners: i,
1457 coords: None,
1458 },
1459 ))
1460 if i.len() == 1 && matches!(*i[0], AST::Tuple{ .. })
1461 ));
1462
1463 let out = parse_geo("column center @(1, 2) { R<1> }");
1464 eprintln!("{:?}", out);
1465 assert!(matches!(
1466 out,
1467 Ok((
1468 "",
1469 AST::ColumnLayout {
1470 align: crate::Align::Center,
1471 inners: i,
1472 coords: Some((Value::Float(x), Value::Float(y))),
1473 },
1474 ))
1475 if i.len() == 1 && x > 0.99 && x < 1.01 && y > 1.99 && y < 2.01
1476 ));
1477 }
1478
1479 #[test]
1480 fn test_wrap() {
1481 let out = parse_geo(
1482 "wrap ($inner) with { left-0.5 => C<2>(h), right align exterior => C<2>(h4) }",
1483 );
1484 assert!(matches!(out, Ok(("", AST::Wrap { inner, features })) if
1486 matches!(*inner, AST::VarRef(ref var) if var == "inner") && features.len() == 2 &&
1487 matches!(features[1].0, WrapPosition::Cardinal{ align: crate::Align::End, .. })
1488 ));
1489
1490 let out = parse_geo(
1491 "wrap(column center {[12] R<5>(h)}) with {left-0.5 => C<2>(h), right+0.5 => C<2>(h)}",
1492 );
1493 assert!(matches!(out, Ok(("", AST::Wrap { inner, features })) if
1495 matches!(*inner, AST::ColumnLayout{ .. }) && features.len() == 2 &&
1496 matches!(features[0].0, WrapPosition::Cardinal{ side: Direction::Left, offset: Value::Float(o1), .. } if
1497 o1 < -0.4 && o1 > -0.6) &&
1498 matches!(features[1].0, WrapPosition::Cardinal{ side: Direction::Right, offset: Value::Float(o2), .. } if
1499 o2 > 0.4 && o2 < 0.6)
1500 ));
1501
1502 let out = parse_geo(
1503 "wrap ($inner) with {\n left => (C<2>(h), C<2>),\n # test comment\n right => (C<2>(h), C<2>),\n}",
1504 );
1505 assert!(matches!(out, Ok(("", AST::Wrap { inner, features })) if
1507 matches!(*inner, AST::VarRef(ref var) if var == "inner") && features.len() == 2 &&
1508 matches!(features[0].0, WrapPosition::Cardinal{ align: crate::Align::Center, .. })
1509 ));
1510
1511 let out =
1512 parse_geo("wrap ($inner) with {\n angle(90) => C<2>,\n angle(-90) 25 => C<2>,\n}");
1513 assert!(matches!(out, Ok(("", AST::Wrap { inner, features })) if
1515 matches!(*inner, AST::VarRef(ref var) if var == "inner") && features.len() == 2 &&
1516 matches!(features[0].0, WrapPosition::Angle{ angle: Value::Float(a1), .. } if
1517 a1 < 91. && a1 > 89.) &&
1518 matches!(features[1].0, WrapPosition::Angle{ offset: Value::Float(o2), .. } if
1519 o2 > 24. && o2 < 26.)
1520 ));
1521
1522 let out = parse_geo("wrap ($inner) with {\n center => C<2>,\n}");
1523 assert!(matches!(out, Ok(("", AST::Wrap { inner, features })) if
1524 matches!(*inner, AST::VarRef(ref var) if var == "inner") && features.len() == 1 &&
1525 matches!(features[0].0, WrapPosition::Angle{ angle: Value::Float(a1), .. } if
1526 a1 < 0.1 && a1 > -0.1)
1527 ));
1528
1529 let out = parse_geo("wrap ($inner) with {\n min-left align exterior => C<2>,\n}");
1530 assert!(matches!(out, Ok(("", AST::Wrap { inner, features })) if
1532 matches!(*inner, AST::VarRef(ref var) if var == "inner") && features.len() == 1 &&
1533 matches!(features[0].0, WrapPosition::Corner{ side: Direction::Left, align: crate::Align::End, opposite: false})
1534 ));
1535 }
1536
1537 #[test]
1538 fn test_tuple() {
1539 let out = parse_geo("(C<2>(h))");
1540 eprintln!("{:?}", out);
1541 assert!(
1542 matches!(out, Ok(("", AST::Tuple{ inners })) if inners.len() == 1 &&
1543 matches!(&*inners[0], AST::Circle{ coords: None, radius: r, inner: Some(_) } if
1544 r.float() > 1.99 && r.float() < 2.01
1545 )
1546 )
1547 );
1548
1549 let out = parse_geo("(C<2>(h), R<4>)");
1550 assert!(
1551 matches!(out, Ok(("", AST::Tuple{ inners })) if inners.len() == 2 &&
1552 matches!(*inners[1], AST::Rect{ coords: None, size: Some((Value::Float(w), Value::Float(h))), inner: _, rounded: None } if
1553 w > 3.99 && w < 4.01 && h > 3.99 && h < 4.01
1554 )
1555 )
1556 );
1557
1558 let out = parse_geo("(C<2>(h), (C<2>(h), R<4>))");
1559 assert!(
1561 matches!(out, Ok(("", AST::Tuple{ inners })) if inners.len() == 2 &&
1562 matches!(&*inners[1], AST::Tuple{ inners } if inners.len() == 2 &&
1563 matches!(*inners[1], AST::Rect{ coords: None, size: Some((Value::Float(w), Value::Float(h))), inner: _, rounded: None } if
1564 w > 3.99 && w < 4.01 && h > 3.99 && h < 4.01
1565 )
1566 )
1567 )
1568 );
1569 }
1570
1571 #[test]
1572 fn test_negative() {
1573 let out = parse_geo("negative{C<2>}");
1574 assert!(
1576 matches!(out, Ok(("", AST::Negative{ inners })) if inners.len() == 1 &&
1577 matches!(&*inners[0], AST::Circle{ coords: None, radius: r, inner: None } if
1578 r.float() > 1.99 && r.float() < 2.01
1579 )
1580 )
1581 );
1582
1583 let out = parse_geo("negative {\n C<2>,\n R<4>\n\n}");
1584 assert!(
1586 matches!(out, Ok(("", AST::Negative{ inners })) if inners.len() == 2 &&
1587 matches!(&*inners[0], AST::Circle{ coords: None, radius: r, inner: None } if
1588 r.float() > 1.99 && r.float() < 2.01
1589 ) &&
1590 matches!(*inners[1], AST::Rect{ coords: None, size: Some((Value::Float(w), Value::Float(h))), inner: _, rounded: None } if
1591 w > 3.99 && w < 4.01 && h > 3.99 && h < 4.01
1592 )
1593 )
1594 );
1595 }
1596
1597 #[test]
1598 fn test_var() {
1599 let out = parse_geo("let bleh = C<25>");
1600 assert!(matches!(out, Ok(("", AST::Assign(var, circ))) if
1602 var == "bleh".to_string() && matches!(*circ, AST::Circle{ .. })));
1603
1604 let out = parse_geo("$bleh");
1605 assert!(matches!(out, Ok(("", AST::VarRef(var))) if var == "bleh".to_string()));
1607
1608 let out = build(
1609 "let rect = column center {
1610 [12] R<7.5>(h)
1611 [11] R<7.5>(h)
1612 [12] R<7.5>(h)
1613 }$rect",
1614 );
1615 assert!(matches!(out, Ok(features) if features.len() == 1));
1617 }
1618
1619 #[test]
1620 fn test_err_msgs() {
1621 let out = build("C<a>");
1622 assert!(matches!(out, Err(Err::Parse(_))));
1623 let out = build("T<a>");
1624 assert!(matches!(out, Err(Err::Parse(_))));
1625
1626 let out = build("R<@(a)>");
1627 assert!(matches!(out, Err(Err::Parse(_))));
1633
1634 let out = build("(aBC)");
1635 assert!(matches!(out, Err(Err::Parse(_))));
1636
1637 let out = build("let bleh = !{aa$%dsfsd + 44}");
1638 assert!(matches!(out, Err(Err::Parse(_))));
1639 }
1640
1641 #[test]
1642 fn test_cel() {
1643 let out = parse_geo("let bleh = !{44}");
1644 assert!(matches!(out, Ok(("", AST::Assign(var, exp))) if
1645 var == "bleh".to_string() && matches!(*exp, AST::Cel(_))));
1646
1647 let out = build("let bleh = !{1 + 1}");
1648 assert!(matches!(out, Ok(_)));
1649 let out = build("let bleh = !{1 + 1}\nlet ye = !{bleh + 2}");
1650 assert!(matches!(out, Ok(_)));
1651
1652 let out = build("let v2 = !{1 * 1}\nR<$v2>");
1653 assert!(matches!(out, Ok(v) if v.len() == 1));
1654
1655 let out = build("R<$missing>");
1656 assert!(matches!(out, Err(Err::UndefinedVariable(_))));
1658
1659 let out = build("let bleh = !{22};\nR<!{bleh + 1}>");
1660 assert!(matches!(out, Ok(v) if v.len() == 1));
1661
1662 let out = build("let bleh = !{5};\nwrap (R<5>) with { left $bleh => R<2>, }");
1663 assert!(matches!(out, Ok(v) if v.len() == 1));
1665 }
1666
1667 #[test]
1668 fn test_comment() {
1669 let out = parse_geo("# yooooooo");
1670 assert!(matches!(out, Ok(("", AST::Comment(msg))) if
1671 msg == " yooooooo"
1672 ));
1673
1674 let out = parse_geo("// yeeeeeeeee");
1675 assert!(matches!(out, Ok(("", AST::Comment(msg))) if
1676 msg == " yeeeeeeeee"
1677 ));
1678 }
1679
1680 #[test]
1681 fn test_rotate() {
1682 let out = parse_geo("rotate(45.0){C<2>}");
1683 eprintln!("{:?}", out);
1684 assert!(
1685 matches!(out, Ok(("", AST::Rotate{ rotation, inners })) if inners.len() == 1 &&
1686 matches!(&*inners[0], AST::Circle{ coords: None, radius: r, inner: None } if
1687 r.float() > 1.99 && r.float() < 2.01
1688 ) &&
1689 matches!(rotation.float(), f if f < 45.01 && f > 44.99)
1690 )
1691 );
1692 }
1693}