Skip to main content

geo_aid_script/
figure.rs

1//! Geo-AID's figure Intermediate Representation and all definitions related to it.
2//! Note that a part of it is also located in `geo-aid-figure`
3
4use std::{fmt::Display, str::FromStr};
5
6use crate::geometry::ValueEnum;
7use crate::math::{EntityKind, IndexMap, Reconstruct, ReconstructCtx, Reindex};
8use geo_aid_figure::math_string::{
9    MathChar, MathIndex, MathSpecial, MathString, ParseErrorKind, SPECIAL_MATH,
10};
11use geo_aid_figure::{Style, VarIndex};
12
13use crate::span;
14
15use super::math::Entity;
16use super::{
17    math,
18    parser::{FromProperty, Parse, PropertyValue},
19    token::{Ident, PointCollectionItem, Span},
20    unroll::most_similar,
21    Error,
22};
23
24/// A drawn point
25#[derive(Debug, Clone)]
26pub struct PointItem {
27    /// Index of the defining expression
28    pub id: VarIndex,
29    /// Label of this point
30    pub label: MathString,
31    /// Whether to display a small circle in its place
32    pub display_dot: bool,
33}
34
35impl Reindex for PointItem {
36    fn reindex(&mut self, map: &IndexMap) {
37        self.id.reindex(map);
38    }
39}
40
41impl Reconstruct for PointItem {
42    fn reconstruct(self, ctx: &mut ReconstructCtx) -> Self {
43        Self {
44            id: self.id.reconstruct(ctx),
45            ..self
46        }
47    }
48}
49
50impl From<PointItem> for Item {
51    fn from(value: PointItem) -> Self {
52        Self::Point(value)
53    }
54}
55
56/// A drawn circle
57#[derive(Debug, Clone)]
58pub struct CircleItem {
59    /// Index of the defining expression
60    pub id: VarIndex,
61    /// Label of this circle
62    pub label: MathString,
63    /// How to draw the circle (brush)
64    pub style: Style,
65}
66
67impl Reindex for CircleItem {
68    fn reindex(&mut self, map: &IndexMap) {
69        self.id.reindex(map);
70    }
71}
72
73impl Reconstruct for CircleItem {
74    fn reconstruct(self, ctx: &mut ReconstructCtx) -> Self {
75        Self {
76            id: self.id.reconstruct(ctx),
77            ..self
78        }
79    }
80}
81
82impl From<CircleItem> for Item {
83    fn from(value: CircleItem) -> Self {
84        Self::Circle(value)
85    }
86}
87
88/// A drawn line
89#[derive(Debug, Clone)]
90pub struct LineItem {
91    /// Index of the defining expression
92    pub id: VarIndex,
93    /// Label of this line
94    pub label: MathString,
95    /// How to draw the line (brush)
96    pub style: Style,
97}
98
99impl Reindex for LineItem {
100    fn reindex(&mut self, map: &IndexMap) {
101        self.id.reindex(map);
102    }
103}
104
105impl Reconstruct for LineItem {
106    fn reconstruct(self, ctx: &mut ReconstructCtx) -> Self {
107        Self {
108            id: self.id.reconstruct(ctx),
109            ..self
110        }
111    }
112}
113
114impl From<LineItem> for Item {
115    fn from(value: LineItem) -> Self {
116        Self::Line(value)
117    }
118}
119
120/// A drawn ray (half-line)
121#[derive(Debug, Clone)]
122pub struct RayItem {
123    /// Index of the expression defining the ray's origin (end point).
124    pub p_id: VarIndex,
125    /// Index of the expression defining the ray's guiding point
126    pub q_id: VarIndex,
127    /// The ray's label
128    pub label: MathString,
129    /// How to draw the ray (brush)
130    pub style: Style,
131}
132
133impl Reindex for RayItem {
134    fn reindex(&mut self, map: &IndexMap) {
135        self.p_id.reindex(map);
136        self.q_id.reindex(map);
137    }
138}
139
140impl Reconstruct for RayItem {
141    fn reconstruct(self, ctx: &mut ReconstructCtx) -> Self {
142        Self {
143            p_id: self.p_id.reconstruct(ctx),
144            q_id: self.q_id.reconstruct(ctx),
145            ..self
146        }
147    }
148}
149
150impl From<RayItem> for Item {
151    fn from(value: RayItem) -> Self {
152        Self::Ray(value)
153    }
154}
155
156/// A drawn segment
157#[derive(Debug, Clone)]
158pub struct SegmentItem {
159    /// Index of the expression defining the first endpoint
160    pub p_id: VarIndex,
161    /// Index of the expression defining the second endpoint
162    pub q_id: VarIndex,
163    /// The segment's label
164    pub label: MathString,
165    /// How to draw the segment (brush)
166    pub style: Style,
167}
168
169impl From<SegmentItem> for Item {
170    fn from(value: SegmentItem) -> Self {
171        Self::Segment(value)
172    }
173}
174
175impl Reindex for SegmentItem {
176    fn reindex(&mut self, map: &IndexMap) {
177        self.p_id.reindex(map);
178        self.q_id.reindex(map);
179    }
180}
181
182impl Reconstruct for SegmentItem {
183    fn reconstruct(self, ctx: &mut ReconstructCtx) -> Self {
184        Self {
185            p_id: self.p_id.reconstruct(ctx),
186            q_id: self.q_id.reconstruct(ctx),
187            ..self
188        }
189    }
190}
191
192/// A type-erased drawn item of the figure
193#[derive(Debug, Clone)]
194pub enum Item {
195    Point(PointItem),
196    Circle(CircleItem),
197    Line(LineItem),
198    Ray(RayItem),
199    Segment(SegmentItem),
200}
201
202impl Reindex for Item {
203    fn reindex(&mut self, map: &IndexMap) {
204        match self {
205            Self::Point(v) => v.reindex(map),
206            Self::Circle(v) => v.reindex(map),
207            Self::Line(v) => v.reindex(map),
208            Self::Ray(v) => v.reindex(map),
209            Self::Segment(v) => v.reindex(map),
210        }
211    }
212}
213
214impl Reconstruct for Item {
215    fn reconstruct(self, ctx: &mut ReconstructCtx) -> Self {
216        match self {
217            Self::Point(v) => Self::Point(v.reconstruct(ctx)),
218            Self::Circle(v) => Self::Circle(v.reconstruct(ctx)),
219            Self::Line(v) => Self::Line(v.reconstruct(ctx)),
220            Self::Ray(v) => Self::Ray(v.reconstruct(ctx)),
221            Self::Segment(v) => Self::Segment(v.reconstruct(ctx)),
222        }
223    }
224}
225
226/// Defines the visual data of the figure.
227#[derive(Debug, Default, Clone)]
228pub struct Figure {
229    /// Entities used by the figure
230    pub entities: Vec<EntityKind>,
231    /// Variables used by the figure
232    pub variables: Vec<math::Expr<()>>,
233    /// Drawn items
234    pub items: Vec<Item>,
235}
236
237/// Generated figure, created by the engine
238#[derive(Debug, Clone, Default)]
239pub struct Generated {
240    /// Entities used by the figure
241    pub entities: Vec<Entity<ValueEnum>>,
242    /// Variables used by the figure
243    pub variables: Vec<math::Expr<ValueEnum>>,
244    /// Drawn items with meta
245    pub items: Vec<Item>,
246}
247
248/// A [`MathString`] with a [`Span`].
249#[derive(Debug, Clone)]
250pub struct SpannedMathString {
251    pub string: MathString,
252    pub span: Span,
253}
254
255impl SpannedMathString {
256    /// Create an empty math string with a span.
257    #[must_use]
258    pub fn new(span: Span) -> Self {
259        Self {
260            string: MathString::new(),
261            span,
262        }
263    }
264
265    /// Return `Some` with `self` if the string should be displayed by default.
266    /// A string should be displayed by default if it consists of one alphabetical
267    /// (either special or literal) and is possibly followed by primes (') and an index
268    /// of any length.
269    ///
270    /// # Panics
271    /// Any panic here is a bug.
272    #[must_use]
273    pub fn displayed_by_default(&self) -> Option<Self> {
274        let mut result = MathString::new();
275
276        // The first set of characters must be either a single character or a special code.
277        let mut letter = String::new();
278
279        let mut chars = self.string.iter().copied().peekable();
280
281        while let Some(MathChar::Ascii(c)) = chars.peek().copied() {
282            chars.next();
283            letter.push(c);
284        }
285
286        if let Some(special) = MathSpecial::parse(&letter) {
287            if special.is_alphabetic() {
288                result.push(MathChar::Special(special));
289            } else {
290                return None;
291            }
292        } else if letter.len() == 1 {
293            result.push(MathChar::Ascii(letter.chars().next().unwrap()));
294        } else {
295            return None;
296        }
297
298        while Some(MathChar::Prime) == chars.peek().copied() {
299            chars.next();
300            result.push(MathChar::Prime);
301        }
302
303        if chars.next() == Some(MathChar::SetIndex(MathIndex::Lower)) {
304            result.push(MathChar::SetIndex(MathIndex::Lower));
305            for c in chars.by_ref() {
306                if c == MathChar::SetIndex(MathIndex::Normal) {
307                    break;
308                }
309
310                result.push(c);
311            }
312            result.push(MathChar::SetIndex(MathIndex::Normal));
313        }
314
315        if chars.next().is_none() {
316            Some(Self {
317                string: result,
318                span: self.span,
319            })
320        } else {
321            None
322        }
323    }
324
325    /// Try to parse the given string as a math string.
326    ///
327    /// # Errors
328    /// Returns an error on parsing errors.
329    pub fn parse(content: &str, content_span: Span) -> Result<Self, Error> {
330        let string = MathString::from_str(content).map_err(|err| {
331            let error_span = span!(
332                content_span.start.line,
333                content_span.start.column + err.span.start,
334                content_span.end.line,
335                content_span.end.column + err.span.end
336            );
337
338            match err.kind {
339                ParseErrorKind::SpecialNotRecognised(special) => {
340                    let suggested = most_similar(&SPECIAL_MATH, &special).map(ToString::to_string);
341
342                    Error::SpecialNotRecognised {
343                        error_span,
344                        code: special,
345                        suggested,
346                    }
347                }
348                ParseErrorKind::NestedIndex => Error::LabelIndexInsideIndex { error_span },
349                ParseErrorKind::UnclosedSpecialTag(special) => Error::UnclosedSpecial {
350                    error_span,
351                    parsed_special: special,
352                },
353            }
354        })?;
355
356        Ok(Self {
357            string,
358            span: content_span,
359        })
360    }
361
362    #[must_use]
363    pub fn is_empty(&self) -> bool {
364        self.string.is_empty()
365    }
366
367    #[must_use]
368    pub fn get_span(&self) -> Span {
369        self.span
370    }
371}
372
373impl FromProperty for SpannedMathString {
374    fn from_property(property: PropertyValue) -> Result<Self, Error> {
375        match property {
376            PropertyValue::Number(n) => Err(Error::StringOrIdentExpected {
377                error_span: n.get_span(),
378            }),
379            PropertyValue::Ident(i) => match i {
380                Ident::Collection(mut c) => {
381                    if c.len() == 1 {
382                        Ok(Self::from(c.collection.swap_remove(0)))
383                    } else {
384                        Err(Error::InvalidIdentMathString { error_span: c.span })
385                    }
386                }
387                Ident::Named(n) => Self::parse(&n.ident, n.span)?
388                    .displayed_by_default()
389                    .ok_or(Error::InvalidIdentMathString { error_span: n.span }),
390            },
391            PropertyValue::String(s) => Self::parse(
392                &s.content,
393                span!(
394                    s.span.start.line,
395                    s.span.start.column + 1,
396                    s.span.end.line,
397                    s.span.end.column - 1
398                ),
399            ),
400            PropertyValue::RawString(s) => Ok(Self {
401                string: MathString::raw(&s.lit.content),
402                span: s.get_span(),
403            }),
404        }
405    }
406}
407
408impl From<PointCollectionItem> for SpannedMathString {
409    fn from(value: PointCollectionItem) -> Self {
410        let mut string = MathString::new();
411        string.push(MathChar::Ascii(value.letter));
412
413        if let Some(index) = value.index {
414            string.extend(index.chars().map(MathChar::Ascii));
415        }
416
417        string.extend([MathChar::Prime].repeat(value.primes.into()));
418
419        Self {
420            string,
421            span: value.span,
422        }
423    }
424}
425
426impl Display for SpannedMathString {
427    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
428        write!(f, "{}", self.string)
429    }
430}