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 π to rotate.
127 numerator: isize,
128 /// Denominator of the fraction of π 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 π to rotate.
296 angle_numerator: isize,
297 /// Denominator of fraction of π 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 π to rotate.
383 angle_numerator: isize,
384 /// Denominator of fraction of π 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}