l_system_fractals/
parse_files.rs

1// src/parse_files.rs
2//
3// This file is part of l-system-fractals
4//
5// Copyright 2024 Christopher Phan
6//
7// See README.md in repository root directory for copyright/license info
8//
9// SPDX-License-Identifier: MIT OR Apache-2.0
10
11//! Provides deserialization capabilities, for reading input files.
12use std::collections::HashMap;
13use std::f64::consts::PI;
14use std::fs;
15
16use serde::Deserialize;
17
18use crate::errors::LSystemError;
19use crate::num_validity::AlmostEq;
20use crate::paths::PlotParameters;
21use crate::rules::{DrawAction, InitializedDrawRules, RewriteRules};
22
23/// Represents a DrawAction, coming from JSON deserialization.
24///
25/// # Examples
26///
27/// ```
28/// use std::collections::HashMap;
29/// use std::f64::consts::PI;
30///
31/// use l_system_fractals::parse_files::JSONDrawAction;
32/// use l_system_fractals::rules::DrawAction;
33/// use l_system_fractals::num_validity::AlmostEq;
34///
35/// let json_string = r#"{
36///                         "A": {"type": "RotateCW", "numerator": 1, "denominator": 4},
37///                         "B": {"type": "Push", "stack": 0}
38///                     }"#;
39/// let actions: HashMap<char, JSONDrawAction> = serde_json::from_str(&json_string).unwrap();
40///
41/// let action_a: DrawAction = actions.get(&'A')
42///     .unwrap()
43///     .to_owned()
44///     .try_into()
45///     .unwrap();
46/// assert!(
47///     action_a.almost_eq(
48///         &DrawAction::RotateCW(0.25 * PI),
49///         0.001
50///     )
51/// );
52///
53/// let action_b: DrawAction = actions.get(&'B')
54///     .unwrap()
55///     .to_owned()
56///     .try_into()
57///     .unwrap();
58/// assert!(
59///     action_b.almost_eq(
60///         &DrawAction::Push(0),
61///         0.001
62///     )
63/// );
64/// ```
65#[derive(Copy, Clone, Debug, Deserialize, PartialEq)]
66#[serde(tag = "type")]
67pub enum JSONDrawAction {
68    /// Deserializable version of `DrawAction::DrawForward`.
69    ///
70    /// # Example
71    ///
72    /// ```
73    /// use std::collections::HashMap;
74    ///
75    /// use l_system_fractals::parse_files::JSONDrawAction;
76    /// use l_system_fractals::rules::DrawAction;
77    /// use l_system_fractals::num_validity::AlmostEq;
78    ///
79    /// let json_string = r#"{ "type": "DrawForward", "dist": 1.5 }"#;
80    /// let json_df: JSONDrawAction = serde_json::from_str(json_string).unwrap();
81    /// let df: DrawAction = json_df.try_into().unwrap();
82    /// assert!(df.almost_eq(&DrawAction::DrawForward(1.5), 0.001));
83    /// ```
84    DrawForward {
85        /// Distance to draw forward.
86        dist: f64,
87    },
88    /// Deserializable version of `DrawAction::MoveForward`.
89    ///
90    /// # Example
91    ///
92    /// ```
93    /// use std::collections::HashMap;
94    ///
95    /// use l_system_fractals::parse_files::JSONDrawAction;
96    /// use l_system_fractals::rules::DrawAction;
97    /// use l_system_fractals::num_validity::AlmostEq;
98    ///
99    /// let json_string = r#"{ "type": "MoveForward", "dist": 1.5 }"#;
100    /// let json_mf: JSONDrawAction = serde_json::from_str(json_string).unwrap();
101    /// let mf: DrawAction = json_mf.try_into().unwrap();
102    /// assert!(mf.almost_eq(&DrawAction::MoveForward(1.5), 0.001));
103    /// ```
104    MoveForward {
105        /// Distance to move forward.
106        dist: f64,
107    },
108    /// Deserializable version of `DrawAction::RotateCW`.
109    ///
110    /// # Example
111    ///
112    /// ```
113    /// use std::collections::HashMap;
114    /// use std::f64::consts::PI;
115    ///
116    /// use l_system_fractals::parse_files::JSONDrawAction;
117    /// use l_system_fractals::rules::DrawAction;
118    /// use l_system_fractals::num_validity::AlmostEq;
119    ///
120    /// let json_string = r#"{ "type": "RotateCW", "numerator": 1, "denominator": 4 }"#;
121    /// let json_rc: JSONDrawAction = serde_json::from_str(json_string).unwrap();
122    /// let rc: DrawAction = json_rc.try_into().unwrap();
123    /// assert!(rc.almost_eq(&DrawAction::RotateCW(0.25 * PI), 0.001));
124    /// ```
125    RotateCW {
126        /// Numerator of the fraction of &pi; to rotate.
127        numerator: isize,
128        /// Denominator of the fraction of &pi; to rotate.
129        denominator: usize,
130    },
131    /// Deserializable version of `DrawAction::Push`.
132    ///
133    /// # Example
134    ///
135    /// ```
136    /// use std::collections::HashMap;
137    ///
138    /// use l_system_fractals::parse_files::JSONDrawAction;
139    /// use l_system_fractals::rules::DrawAction;
140    ///
141    /// let json_string = r#"{ "type": "Push", "stack": 3 }"#;
142    /// let json_p: JSONDrawAction = serde_json::from_str(json_string).unwrap();
143    /// let p: DrawAction = json_p.try_into().unwrap();
144    /// assert_eq!(p, DrawAction::Push(3));
145    /// ```
146    Push {
147        /// Identifier for the stack to push onto.
148        stack: usize,
149    },
150    /// Deserializable version of `DrawAction::Pop`.
151    ///
152    /// # Example
153    ///
154    /// ```
155    /// use std::collections::HashMap;
156    ///
157    /// use l_system_fractals::parse_files::JSONDrawAction;
158    /// use l_system_fractals::rules::DrawAction;
159    ///
160    /// let json_string = r#"{ "type": "Pop", "stack": 3 }"#;
161    /// let json_p: JSONDrawAction = serde_json::from_str(json_string).unwrap();
162    /// let p: DrawAction = json_p.try_into().unwrap();
163    /// assert_eq!(p, DrawAction::Pop(3));
164    /// ```
165    Pop {
166        /// Identifier for the stack to pop from.
167        stack: usize,
168    },
169    /// Deserializable version of `DrawAction::Null`.
170    ///
171    /// # Example
172    ///
173    /// ```
174    /// use std::collections::HashMap;
175    ///
176    /// use l_system_fractals::parse_files::JSONDrawAction;
177    /// use l_system_fractals::rules::DrawAction;
178    ///
179    /// let json_string = r#"{ "type": "Null"}"#;
180    /// let json_n: JSONDrawAction = serde_json::from_str(json_string).unwrap();
181    /// let n: DrawAction = json_n.try_into().unwrap();
182    /// assert_eq!(n, DrawAction::Null);
183    /// ```
184    Null,
185}
186
187impl AlmostEq for JSONDrawAction {
188    fn almost_eq(&self, other: &Self, epsilon: f64) -> bool {
189        match (self, other) {
190            (Self::DrawForward { dist: x }, Self::DrawForward { dist: y }) => {
191                x.almost_eq(y, epsilon)
192            }
193            (Self::MoveForward { dist: x }, Self::MoveForward { dist: y }) => {
194                x.almost_eq(y, epsilon)
195            }
196            (x, y) => x == y,
197        }
198    }
199}
200
201impl TryFrom<JSONDrawAction> for DrawAction {
202    type Error = LSystemError;
203
204    fn try_from(j: JSONDrawAction) -> Result<Self, Self::Error> {
205        match j {
206            JSONDrawAction::DrawForward { dist } => Ok(Self::DrawForward(dist)),
207            JSONDrawAction::MoveForward { dist } => Ok(Self::MoveForward(dist)),
208            JSONDrawAction::RotateCW {
209                numerator,
210                denominator,
211            } => {
212                if denominator == 0 {
213                    Err(LSystemError::DivideByZero)
214                } else {
215                    Ok(Self::RotateCW(PI * numerator as f64 / denominator as f64))
216                }
217            }
218            JSONDrawAction::Push { stack } => Ok(Self::Push(stack)),
219            JSONDrawAction::Pop { stack } => Ok(Self::Pop(stack)),
220            JSONDrawAction::Null => Ok(Self::Null),
221        }
222    }
223}
224
225/// Represents user input from JSON of the L-System rules, for conversion into `DrawRules`.
226#[derive(Debug, Clone, Deserialize, PartialEq)]
227#[serde(tag = "type")]
228pub enum JSONDrawRules {
229    /// Most basic specification syntax for rules.
230    ///
231    /// The following commands are defined:
232    /// - `+`: rotate clockwise by angle
233    /// - `-`: rotate counterclockwise by angle
234    /// - `[`: push current location/angle pair onto stack `0`
235    /// - `]`: pop location/angle pair off of stack `0`
236    ///
237    /// The angle is specified as `PI * angle_numerator / angle_denominator`.
238    ///
239    /// Every other character represents drawing forward a distance of 1.0.
240    ///
241    /// # Example
242    ///
243    /// ```
244    /// use std::collections::HashMap;
245    /// use std::f64::consts::PI;
246    ///
247    /// use l_system_fractals::parse_files::{JSONDrawAction, JSONDrawRules};
248    /// use l_system_fractals::rules::{
249    ///     DrawAction,
250    ///     DrawRules,
251    ///     InitializedDrawRules,
252    ///     RuleAssignment,
253    ///     RewriteRules
254    /// };
255    /// use l_system_fractals::num_validity::AlmostEq;
256    ///
257    /// // Koch snowflake
258    /// // from Benoit B. Mandelbrot (1983), _The Fractal Geometry of Nautre (Updated and
259    /// // Agumented)_, New York: W.H. Freeman and Company, p. 42
260    ///
261    /// let json_string = r#"{
262    ///     "type": "Simple",
263    ///     "replacement": {"A": "A+A--A+A"},
264    ///     "angle_numerator": 1,
265    ///     "angle_denominator": 3,
266    ///     "start": "+A--A--A"
267    /// }"#;
268    ///
269    /// let json_dr: JSONDrawRules = serde_json::from_str(json_string).unwrap();
270    /// let dr1: InitializedDrawRules = json_dr.try_into().unwrap();
271    ///
272    /// let rwr = RewriteRules(HashMap::from([('A', "A+A--A+A".into())]));
273    /// let assignment = RuleAssignment(
274    ///     HashMap::from([
275    ///         ('A', DrawAction::DrawForward(1.0)),
276    ///         ('+', DrawAction::RotateCW(PI/3.0)),
277    ///         ('-', DrawAction::RotateCW(-PI/3.0)),
278    ///         ('[', DrawAction::Push(0)),
279    ///         (']', DrawAction::Pop(0))
280    ///     ])
281    /// );
282    ///
283    /// let dr2 = InitializedDrawRules {
284    ///     draw_rules: DrawRules {
285    ///         rewrite_rules: rwr,
286    ///         assignment,
287    ///     },
288    ///     start: "+A--A--A".into()
289    /// };
290    /// assert!(dr1.almost_eq(&dr2, 0.001));
291    /// ```
292    Simple {
293        /// Describes the replacement used in each iteration step.
294        replacement: HashMap<char, String>,
295        /// Numerator of fraction of &pi; to rotate.
296        angle_numerator: isize,
297        /// Denominator of fraction of &pi; to rotate.
298        angle_denominator: usize,
299        /// Initial string.
300        start: String,
301    },
302    /// A more advanced specification syntax for rules that provides more flexibility.
303    ///
304    /// The following commands are defined:
305    /// - `+`: rotate clockwise by angle
306    /// - `-`: rotate counterclockwise by angle
307    /// - `[`: push current location/angle pair onto stack `0`
308    /// - `]`: pop location/angle pair off of stack `0`
309    ///
310    /// The angle is specified as `PI * angle_numerator / angle_denominator`.
311    ///
312    /// Other characters are mapped to drawing or moving forward based on
313    /// the fields `draw_step_sizes` and `move_step_sizes`.
314    ///
315    /// Any other character is mapped to `Null`.
316    ///
317    /// # Example
318    ///
319    /// ```
320    /// use std::collections::HashMap;
321    /// use std::f64::consts::PI;
322    ///
323    /// use l_system_fractals::parse_files::{JSONDrawAction, JSONDrawRules};
324    /// use l_system_fractals::rules::{
325    ///     DrawAction,
326    ///     DrawRules,
327    ///     InitializedDrawRules,
328    ///     RuleAssignment,
329    ///     RewriteRules
330    /// };
331    /// use l_system_fractals::num_validity::AlmostEq;
332    ///
333    /// // Islands
334    /// // from Benoit B. Mandelbrot (1983), _The Fractal Geometry of Nautre (Updated and
335    /// // Agumented)_, New York: W.H. Freeman and Company, p. 121
336    ///
337    /// let json_string = r#"{
338    ///     "type": "Advanced",
339    ///     "replacement": { "A": "A+BA-AA-A-AA+B+AA-BA+AA+A+AA-B-AAA", "B": "BBBBBB" },
340    ///     "draw_step_sizes": { "A": 1.0 },
341    ///     "move_step_sizes": { "B": 1.0 },
342    ///     "angle_numerator": 1,
343    ///     "angle_denominator": 2,
344    ///     "start": "A-A-A-A-"
345    /// }"#;
346    ///
347    /// let json_dr: JSONDrawRules = serde_json::from_str(json_string).unwrap();
348    /// let dr1: InitializedDrawRules = json_dr.try_into().unwrap();
349    ///
350    /// let rwr = RewriteRules(HashMap::from([
351    ///     ('A', "A+BA-AA-A-AA+B+AA-BA+AA+A+AA-B-AAA".into()),
352    ///     ('B', "BBBBBB".into())
353    /// ]));
354    /// let assignment = RuleAssignment(
355    ///     HashMap::from([
356    ///         ('A', DrawAction::DrawForward(1.0)),
357    ///         ('B', DrawAction::MoveForward(1.0)),
358    ///         ('+', DrawAction::RotateCW(PI/2.0)),
359    ///         ('-', DrawAction::RotateCW(-PI/2.0)),
360    ///         ('[', DrawAction::Push(0)),
361    ///         (']', DrawAction::Pop(0))
362    ///     ])
363    /// );
364    ///
365    /// let dr2 = InitializedDrawRules {
366    ///     draw_rules: DrawRules {
367    ///         rewrite_rules: rwr,
368    ///         assignment
369    ///     },
370    ///     start: "A-A-A-A-".into()
371    /// };
372    ///
373    /// assert!(dr1.almost_eq(&dr2, 0.001));
374    /// ```
375    Advanced {
376        /// Describes the replacement used in each iteration step.
377        replacement: HashMap<char, String>,
378        /// Used to define which characters will represent drawing forward (and how much).
379        draw_step_sizes: HashMap<char, f64>,
380        /// Used to define which characters will represent moving forward (and how much).
381        move_step_sizes: HashMap<char, f64>,
382        /// Numerator of fraction of &pi; to rotate.
383        angle_numerator: isize,
384        /// Denominator of fraction of &pi; to rotate.
385        angle_denominator: usize,
386        /// Initial string.
387        start: String,
388    },
389    /// A specification syntax that allows for complete flexibility.
390    ///
391    /// # Example
392    ///
393    /// ```
394    /// use std::collections::HashMap;
395    /// use std::f64::consts::PI;
396    ///
397    /// use l_system_fractals::parse_files::{JSONDrawAction, JSONDrawRules};
398    /// use l_system_fractals::rules::{
399    ///     DrawAction,
400    ///     DrawRules,
401    ///     InitializedDrawRules,
402    ///     RuleAssignment,
403    ///     RewriteRules
404    /// };
405    /// use l_system_fractals::num_validity::AlmostEq;
406    ///
407    /// let json_string = r#"{
408    ///     "type": "Full",
409    ///     "replacement": {
410    ///       "A": "A[A{B<AX",
411    ///       "B": "BBBB",
412    ///       "X": "}+A{>+A<]+A["
413    ///     },
414    ///     "assignment": {
415    ///       "A": { "type": "DrawForward", "dist": 1.0 },
416    ///       "B": { "type": "MoveForward", "dist": 1.0 },
417    ///       "X": { "type": "Null" },
418    ///       "[": { "type": "Push", "stack": 0 },
419    ///       "{": { "type": "Push", "stack": 1 },
420    ///       "<": { "type": "Push", "stack": 2 },
421    ///       "]": { "type": "Pop", "stack": 0 },
422    ///       "}": { "type": "Pop", "stack": 1 },
423    ///       ">": { "type": "Pop", "stack": 2 },
424    ///       "+": { "type": "RotateCW", "numerator": 2, "denominator": 9 }
425    ///     },
426    ///     "start": "+A+A+A+A+A+A+A+A+A"
427    /// }"#;
428    ///
429    /// let json_dr: JSONDrawRules = serde_json::from_str(json_string).unwrap();
430    /// let dr1: InitializedDrawRules = json_dr.try_into().unwrap();
431    ///
432    /// let rwr = RewriteRules(HashMap::from([
433    ///       ('A', "A[A{B<AX".into()),
434    ///       ('B', "BBBB".into()),
435    ///       ('X', "}+A{>+A<]+A[".into())
436    /// ]));
437    /// let assignment = RuleAssignment(
438    ///     HashMap::from([
439    ///         ('A', DrawAction::DrawForward(1.0)),
440    ///         ('B', DrawAction::MoveForward(1.0)),
441    ///         ('X', DrawAction::Null),
442    ///         ('+', DrawAction::RotateCW(2.0 * PI/9.0)),
443    ///         ('[', DrawAction::Push(0)),
444    ///         ('{', DrawAction::Push(1)),
445    ///         ('<', DrawAction::Push(2)),
446    ///         (']', DrawAction::Pop(0)),
447    ///         ('}', DrawAction::Pop(1)),
448    ///         ('>', DrawAction::Pop(2)),
449    ///     ])
450    /// );
451    ///
452    /// let dr2 = InitializedDrawRules {
453    ///     draw_rules: DrawRules {
454    ///         rewrite_rules: rwr,
455    ///         assignment
456    ///     },
457    ///     start: "+A+A+A+A+A+A+A+A+A".into()
458    /// };
459    /// assert!(dr1.almost_eq(&dr2, 0.001));
460    /// ```
461    Full {
462        /// Describes the replacement used in each iteration step.
463        replacement: HashMap<char, String>,
464        /// Specification of a `JSONDrawAction` for each character.
465        assignment: HashMap<char, JSONDrawAction>,
466        /// Initial string.
467        start: String,
468    },
469}
470
471impl AlmostEq for JSONDrawRules {
472    fn almost_eq(&self, other: &Self, epsilon: f64) -> bool {
473        match (self, other) {
474            (
475                Self::Advanced {
476                    replacement: s_rpl,
477                    draw_step_sizes: s_dss,
478                    move_step_sizes: s_mss,
479                    angle_numerator: s_an,
480                    angle_denominator: s_ad,
481                    start: s_start,
482                },
483                Self::Advanced {
484                    replacement: o_rpl,
485                    draw_step_sizes: o_dss,
486                    move_step_sizes: o_mss,
487                    angle_numerator: o_an,
488                    angle_denominator: o_ad,
489                    start: o_start,
490                },
491            ) => {
492                s_dss.almost_eq(o_dss, epsilon)
493                    && s_mss.almost_eq(o_mss, epsilon)
494                    && s_rpl == o_rpl
495                    && s_an == o_an
496                    && s_ad == o_ad
497                    && s_start == o_start
498            }
499            (
500                Self::Full {
501                    replacement: s_rpl,
502                    assignment: s_asn,
503                    start: s_start,
504                },
505                Self::Full {
506                    replacement: o_rpl,
507                    assignment: o_asn,
508                    start: o_start,
509                },
510            ) => s_asn.almost_eq(o_asn, epsilon) && s_rpl == o_rpl && s_start == o_start,
511            (a, b) => a == b,
512        }
513    }
514}
515
516impl TryFrom<JSONDrawRules> for InitializedDrawRules {
517    type Error = LSystemError;
518
519    fn try_from(json_input: JSONDrawRules) -> Result<Self, Self::Error> {
520        match json_input {
521            JSONDrawRules::Simple {
522                replacement,
523                angle_numerator,
524                angle_denominator,
525                start,
526            } => Self::new_simple(replacement, angle_numerator, angle_denominator, start),
527            JSONDrawRules::Advanced {
528                replacement,
529                draw_step_sizes,
530                move_step_sizes,
531                angle_numerator,
532                angle_denominator,
533                start,
534            } => Self::new_advanced(
535                replacement,
536                draw_step_sizes,
537                move_step_sizes,
538                angle_numerator,
539                angle_denominator,
540                start,
541            ),
542            JSONDrawRules::Full {
543                replacement,
544                assignment,
545                start,
546            } => {
547                let mut assignment_hm: HashMap<char, DrawAction> = HashMap::new();
548                for (key, val) in assignment.iter() {
549                    let da: DrawAction = (*val).try_into()?;
550                    assignment_hm.insert(*key, da);
551                }
552                Ok(Self::new(replacement, assignment_hm, start))
553            }
554        }
555    }
556}
557
558/// Represents the JSON input for an input file.
559///
560/// This provides the details of the L-system as well as instructions for printing.
561///
562/// The three variants of this enum correspond to the three commands (and are tagged using the
563/// `command` field in the JSON): `PlotOne`, `PlotMany`, and `OutputStrings`.
564#[derive(Clone, Debug, Deserialize, PartialEq)]
565#[serde(tag = "command")]
566pub enum JSONInput {
567    /// Produces a single plot, showing the specified iteration of the fractal.
568    ///
569    /// # Example
570    ///
571    /// ```
572    /// use std::collections::HashMap;
573    ///
574    /// use l_system_fractals::parse_files::{
575    ///     JSONDrawAction, JSONInput, JSONDrawRules
576    /// };
577    /// use l_system_fractals::paths::PlotParameters;
578    /// use l_system_fractals::num_validity::AlmostEq;
579    ///
580    /// let json_string = r#"{
581    ///   "command": "PlotOne",
582    ///   "rules": {
583    ///     "type": "Advanced",
584    ///     "replacement": { "A": "A+BA-AA-A-AA+B+AA-BA+AA+A+AA-B-AAA", "B": "BBBBBB" },
585    ///     "draw_step_sizes": { "A": 1.0 },
586    ///     "move_step_sizes": { "B": 1.0 },
587    ///     "angle_numerator": 1,
588    ///     "angle_denominator": 2,
589    ///     "start": "A-A-A-A-"
590    ///   },
591    ///   "iteration": 3,
592    ///   "plot_param": {
593    ///     "width": 700.0,
594    ///     "height": 700.0,
595    ///     "border": 50.0,
596    ///     "fill": "none",
597    ///     "stroke": "magenta",
598    ///     "stroke_width": 1
599    ///   },
600    ///   "filename": "islands.svg",
601    ///   "comment": "Benoit B. Mandelbrot (1983), _The Fractal Geometry of Nautre (Updated and Agumented)_, New York: W.H. Freeman and Company, p. 121"
602    /// }"#;
603    /// let json_input1: JSONInput = serde_json::from_str(json_string).unwrap();
604    ///
605    /// let rules = JSONDrawRules::Advanced {
606    ///     replacement: HashMap::from([
607    ///             ('A', "A+BA-AA-A-AA+B+AA-BA+AA+A+AA-B-AAA".into()),
608    ///             ('B', "BBBBBB".into())
609    ///         ]),
610    ///     draw_step_sizes: HashMap::from([('A', 1.0)]),
611    ///     move_step_sizes: HashMap::from([('B', 1.0)]),
612    ///     angle_numerator: 1,
613    ///     angle_denominator: 2,
614    ///     start: "A-A-A-A-".into()
615    /// };
616    /// let plot_param = PlotParameters {
617    ///   width: 700.0,
618    ///   height: 700.0,
619    ///   border: 50.0,
620    ///   fill: "none".into(),
621    ///   stroke: "magenta".into(),
622    ///   stroke_width: 1.0,
623    ///   background: None
624    /// };
625    ///
626    /// let json_input2 = JSONInput::PlotOne {
627    ///     rules,
628    ///     iteration: 3,
629    ///     plot_param,
630    ///     filename: "islands.svg".into(),
631    ///     comment: Some("Benoit B. Mandelbrot (1983), _The Fractal Geometry of Nautre".to_string()
632    ///             + " (Updated and Agumented)_, New York: W.H. Freeman and Company, p. 121")
633    /// };
634    /// assert!(json_input1.almost_eq(&json_input2, 0.001));
635    /// ```
636    PlotOne {
637        /// The rules for the L-system
638        rules: JSONDrawRules,
639        /// The iteration to display
640        iteration: usize,
641        /// The parameters for the output SVG file
642        plot_param: PlotParameters,
643        /// The filename to which the SVG will be saved
644        filename: String,
645        /// An optional comment
646        comment: Option<String>,
647    },
648    /// Produces multiple plots, showing the specified iterations of the fractal.
649    ///
650    /// # Example
651    ///
652    /// ```
653    /// use std::collections::HashMap;
654    ///
655    /// use l_system_fractals::parse_files::{
656    ///     JSONDrawAction, JSONInput, JSONDrawRules
657    /// };
658    /// use l_system_fractals::paths::PlotParameters;
659    /// use l_system_fractals::num_validity::AlmostEq;
660    ///
661    /// let json_string = r#"{
662    ///   "command": "PlotMany",
663    ///   "rules": {
664    ///     "type": "Advanced",
665    ///     "replacement": { "A": "A+BA-AA-A-AA+B+AA-BA+AA+A+AA-B-AAA", "B": "BBBBBB" },
666    ///     "draw_step_sizes": { "A": 1.0 },
667    ///     "move_step_sizes": { "B": 1.0 },
668    ///     "angle_numerator": 1,
669    ///     "angle_denominator": 2,
670    ///     "start": "A-A-A-A-"
671    ///   },
672    ///   "iterations": [0, 1, 2, 3],
673    ///   "row_len": 2,
674    ///   "plot_param": {
675    ///     "width": 700.0,
676    ///     "height": 700.0,
677    ///     "border": 50.0,
678    ///     "fill": "none",
679    ///     "stroke": "magenta",
680    ///     "stroke_width": 1
681    ///   },
682    ///   "filename": "islands.svg",
683    ///   "comment": "Benoit B. Mandelbrot (1983), _The Fractal Geometry of Nautre (Updated and Agumented)_, New York: W.H. Freeman and Company, p. 121"
684    /// }"#;
685    /// let json_input1: JSONInput = serde_json::from_str(json_string).unwrap();
686    ///
687    /// let rules = JSONDrawRules::Advanced {
688    ///     replacement: HashMap::from([
689    ///             ('A', "A+BA-AA-A-AA+B+AA-BA+AA+A+AA-B-AAA".into()),
690    ///             ('B', "BBBBBB".into())
691    ///         ]),
692    ///     draw_step_sizes: HashMap::from([('A', 1.0)]),
693    ///     move_step_sizes: HashMap::from([('B', 1.0)]),
694    ///     angle_numerator: 1,
695    ///     angle_denominator: 2,
696    ///     start: "A-A-A-A-".into()
697    /// };
698    /// let plot_param = PlotParameters {
699    ///   width: 700.0,
700    ///   height: 700.0,
701    ///   border: 50.0,
702    ///   fill: "none".into(),
703    ///   stroke: "magenta".into(),
704    ///   stroke_width: 1.0,
705    ///   background: None
706    /// };
707    ///
708    /// let json_input2 = JSONInput::PlotMany {
709    ///     rules,
710    ///     iterations: vec![0, 1, 2, 3],
711    ///     row_len: 2,
712    ///     plot_param,
713    ///     filename: "islands.svg".into(),
714    ///     comment: Some("Benoit B. Mandelbrot (1983), _The Fractal Geometry of Nautre".to_string()
715    ///             + " (Updated and Agumented)_, New York: W.H. Freeman and Company, p. 121")
716    /// };
717    /// assert!(json_input1.almost_eq(&json_input2, 0.001));
718    /// ```
719    PlotMany {
720        /// The rules for the L-system
721        rules: JSONDrawRules,
722        /// The iterations to display
723        iterations: Vec<usize>,
724        /// Number of plots in a row
725        row_len: usize,
726        /// The parameters for the output SVG file
727        plot_param: PlotParameters,
728        /// The filename to which the SVG will be saved
729        filename: String,
730        /// An optional comment
731        comment: Option<String>,
732    },
733    /// Does not produce any plots, but rather just performs the string replacement rules.
734    ///
735    /// # Example
736    ///
737    /// ```
738    /// use std::collections::HashMap;
739    ///
740    /// use l_system_fractals::parse_files::JSONInput;
741    ///
742    /// let json_string = r#"{
743    ///      "command": "OutputStrings",
744    ///      "rules": { "A": "AB", "B": "BAB" },
745    ///      "start": "A",
746    ///      "iteration": 2,
747    ///      "filename": "text_output.txt"
748    /// }"#;
749    ///
750    /// let json_input1: JSONInput = serde_json::from_str(json_string).unwrap();
751    ///
752    /// let json_input2 = JSONInput::OutputStrings {
753    ///     rules: HashMap::from([('A', "AB".into()), ('B', "BAB".into())]),
754    ///     start: "A".into(),
755    ///     iteration: 2,
756    ///     filename: "text_output.txt".into(),
757    ///     comment: None
758    /// };
759    /// ```
760    OutputStrings {
761        /// The replacement rules
762        rules: HashMap<char, String>,
763        /// The initial string
764        start: String,
765        /// The iteration to calculate
766        iteration: usize,
767        /// The name of the file to gernerate with the output
768        filename: String,
769        /// An optional comment
770        comment: Option<String>,
771    },
772}
773
774impl AlmostEq for JSONInput {
775    fn almost_eq(&self, other: &Self, epsilon: f64) -> bool {
776        match (self, other) {
777            (
778                Self::PlotOne {
779                    rules: s_rules,
780                    iteration: s_iter,
781                    plot_param: s_pp,
782                    filename: s_fn,
783                    comment: s_comm,
784                },
785                Self::PlotOne {
786                    rules: o_rules,
787                    iteration: o_iter,
788                    plot_param: o_pp,
789                    filename: o_fn,
790                    comment: o_comm,
791                },
792            ) => {
793                s_rules.almost_eq(o_rules, epsilon)
794                    && s_pp.almost_eq(o_pp, epsilon)
795                    && s_iter == o_iter
796                    && s_fn == o_fn
797                    && s_comm == o_comm
798            }
799            (
800                Self::PlotMany {
801                    rules: s_rules,
802                    iterations: s_iter,
803                    row_len: s_rl,
804                    plot_param: s_pp,
805                    filename: s_fn,
806                    comment: s_comm,
807                },
808                Self::PlotMany {
809                    rules: o_rules,
810                    iterations: o_iter,
811                    row_len: o_rl,
812                    plot_param: o_pp,
813                    filename: o_fn,
814                    comment: o_comm,
815                },
816            ) => {
817                s_rules.almost_eq(o_rules, epsilon)
818                    && s_pp.almost_eq(o_pp, epsilon)
819                    && s_rl == o_rl
820                    && s_iter == o_iter
821                    && s_fn == o_fn
822                    && s_comm == o_comm
823            }
824            (a, b) => a == b,
825        }
826    }
827}
828
829impl JSONInput {
830    /// Runs the command specified by the input.
831    pub fn run(&self) -> Result<String, LSystemError> {
832        match self {
833            Self::OutputStrings {
834                rules,
835                start,
836                iteration,
837                filename,
838                ..
839            } => {
840                let rrules = RewriteRules(rules.clone());
841                let output = rrules.repeat_iterate(start.clone(), *iteration);
842                match fs::write(filename, output) {
843                    Ok(()) => Ok(filename.to_string()),
844                    Err(e) => Err(LSystemError::FileSystemError(Box::new(e))),
845                }
846            }
847            Self::PlotOne {
848                rules,
849                iteration,
850                plot_param,
851                filename,
852                ..
853            } => {
854                let draw_rules: InitializedDrawRules = (rules.clone()).try_into()?;
855                let svg = draw_rules.make_iteration_svg(*iteration, plot_param)?;
856                match fs::write(filename, svg) {
857                    Ok(()) => Ok(filename.to_string()),
858                    Err(e) => Err(LSystemError::FileSystemError(Box::new(e))),
859                }
860            }
861            Self::PlotMany {
862                rules,
863                iterations,
864                row_len,
865                plot_param,
866                filename,
867                ..
868            } => {
869                let draw_rules: InitializedDrawRules = (rules.clone()).try_into()?;
870                let svg =
871                    draw_rules.make_combined_svg(&plot_param, *row_len, iterations.to_vec())?;
872                match fs::write(filename, svg) {
873                    Ok(()) => Ok(filename.to_string()),
874                    Err(e) => Err(LSystemError::FileSystemError(Box::new(e))),
875                }
876            }
877        }
878    }
879}
880
881/// Deserailizes the JSON, then runs the underlying JSONInfo.
882pub fn run_json(json: String) -> Result<String, LSystemError> {
883    match serde_json::from_str::<JSONInput>(&json) {
884        Ok(k) => k.run(),
885        Err(e) => Err(LSystemError::DeserializationError(Box::new(e))),
886    }
887}
888
889/// Reads a JSON file, deserailizes the JSON, then runs the underlying JSONInfo.
890pub fn run_file(filename: String) -> Result<String, LSystemError> {
891    match fs::read_to_string(filename) {
892        Ok(s) => run_json(s),
893        Err(e) => Err(LSystemError::FileSystemError(Box::new(e))),
894    }
895}