1use crate::error::{ParseError, Result};
4use crate::preset::*;
5
6pub fn parse_milk_preset(input: &str) -> Result<MilkPreset> {
8 let mut preset = MilkPreset::default();
9 let mut lines = input.lines().enumerate();
10
11 for (_line_num, line) in lines.by_ref() {
13 let line = line.trim();
14 if line.is_empty() {
15 continue;
16 }
17
18 if line.starts_with("MILKDROP_PRESET_VERSION=") {
19 preset.version = parse_version_line(line)?;
20 } else if line.starts_with("PSVERSION_WARP=") {
21 preset.ps_version_warp = parse_psversion_line(line)?;
22 } else if line.starts_with("PSVERSION_COMP=") {
23 preset.ps_version_comp = parse_psversion_line(line)?;
24 } else if line.starts_with("[preset") {
25 break;
27 }
28 }
29
30 for (_line_num, line) in lines {
32 let line = line.trim();
33 if line.is_empty() {
34 continue;
35 }
36
37 if line.starts_with("per_frame_init_") {
39 if let Some(equation) = parse_equation_line(line) {
40 preset.per_frame_init_equations.push(equation);
41 }
42 }
43 else if line.starts_with("per_frame_") {
45 if let Some(equation) = parse_equation_line(line) {
46 preset.per_frame_equations.push(equation);
47 }
48 }
49 else if line.starts_with("per_pixel_") {
51 if let Some(equation) = parse_equation_line(line) {
52 preset.per_pixel_equations.push(equation);
53 }
54 }
55 else if line.starts_with("warp_") {
57 let shader_line = parse_shader_line(line);
58 if let Some(ref mut shader) = preset.warp_shader {
59 shader.push_str(&shader_line);
60 shader.push('\n');
61 } else {
62 preset.warp_shader = Some(shader_line + "\n");
63 }
64 }
65 else if line.starts_with("comp_") {
67 let shader_line = parse_shader_line(line);
68 if let Some(ref mut shader) = preset.comp_shader {
69 shader.push_str(&shader_line);
70 shader.push('\n');
71 } else {
72 preset.comp_shader = Some(shader_line + "\n");
73 }
74 }
75 else if line.starts_with("wavecode_") {
77 parse_wavecode_line(line, &mut preset.waves)?;
78 }
79 else if line.starts_with("shapecode_") {
81 parse_shapecode_line(line, &mut preset.shapes)?;
82 }
83 else if is_indexed_block(line, "wave_") {
85 parse_wave_equation_line(line, &mut preset.waves)?;
86 }
87 else if is_indexed_block(line, "shape_") {
89 parse_shape_equation_line(line, &mut preset.shapes)?;
90 }
91 else if let Some((key, value)) = line.split_once('=') {
93 parse_parameter(key.trim(), value.trim(), &mut preset.parameters)?;
94 }
95 }
96
97 Ok(preset)
98}
99
100fn parse_version_line(line: &str) -> Result<u32> {
102 line.split('=')
103 .nth(1)
104 .and_then(|v| v.trim().parse().ok())
105 .ok_or_else(|| ParseError::InvalidVersion(line.to_string()))
106}
107
108fn parse_psversion_line(line: &str) -> Result<u32> {
110 line.split('=')
111 .nth(1)
112 .and_then(|v| v.trim().parse().ok())
113 .ok_or_else(|| ParseError::ParseFailed(format!("Invalid PSVERSION: {}", line)))
114}
115
116fn parse_equation_line(line: &str) -> Option<String> {
118 line.split_once('=')
119 .map(|(_, equation)| equation.trim().to_string())
120}
121
122fn parse_shader_line(line: &str) -> String {
124 line.split_once('=')
125 .map(|(_, code)| {
126 code.trim().trim_start_matches('`').to_string()
128 })
129 .unwrap_or_default()
130}
131
132fn parse_parameter(key: &str, value: &str, params: &mut PresetParameters) -> Result<()> {
134 let parse_f32 = |v: &str| -> Result<f32> {
136 v.parse().map_err(|_| ParseError::InvalidParameter {
137 name: key.to_string(),
138 value: v.to_string(),
139 reason: "Expected float".to_string(),
140 })
141 };
142
143 let parse_i32 = |v: &str| -> Result<i32> {
145 v.parse().map_err(|_| ParseError::InvalidParameter {
146 name: key.to_string(),
147 value: v.to_string(),
148 reason: "Expected integer".to_string(),
149 })
150 };
151
152 let parse_bool = |v: &str| -> Result<bool> {
154 match v {
155 "0" => Ok(false),
156 "1" => Ok(true),
157 _ => Err(ParseError::InvalidParameter {
158 name: key.to_string(),
159 value: v.to_string(),
160 reason: "Expected 0 or 1".to_string(),
161 }),
162 }
163 };
164
165 match key {
166 "fRating" => params.f_rating = parse_f32(value)?,
168 "fGammaAdj" => params.f_gamma_adj = parse_f32(value)?,
169 "fDecay" => params.f_decay = parse_f32(value)?,
170 "fVideoEchoZoom" => params.f_video_echo_zoom = parse_f32(value)?,
171 "fVideoEchoAlpha" => params.f_video_echo_alpha = parse_f32(value)?,
172 "fWaveAlpha" => params.f_wave_alpha = parse_f32(value)?,
173 "fWaveScale" => params.f_wave_scale = parse_f32(value)?,
174 "fWaveSmoothing" => params.f_wave_smoothing = parse_f32(value)?,
175 "fWaveParam" => params.f_wave_param = parse_f32(value)?,
176 "fModWaveAlphaStart" => params.f_mod_wave_alpha_start = parse_f32(value)?,
177 "fModWaveAlphaEnd" => params.f_mod_wave_alpha_end = parse_f32(value)?,
178 "fWarpAnimSpeed" => params.f_warp_anim_speed = parse_f32(value)?,
179 "fWarpScale" => params.f_warp_scale = parse_f32(value)?,
180 "fZoomExponent" => params.f_zoom_exponent = parse_f32(value)?,
181 "fShader" => params.f_shader = parse_f32(value)?,
182
183 "zoom" => params.zoom = parse_f32(value)?,
185 "rot" => params.rot = parse_f32(value)?,
186 "cx" => params.cx = parse_f32(value)?,
187 "cy" => params.cy = parse_f32(value)?,
188 "dx" => params.dx = parse_f32(value)?,
189 "dy" => params.dy = parse_f32(value)?,
190 "warp" => params.warp = parse_f32(value)?,
191 "sx" => params.sx = parse_f32(value)?,
192 "sy" => params.sy = parse_f32(value)?,
193
194 "wave_r" => params.wave_r = parse_f32(value)?,
196 "wave_g" => params.wave_g = parse_f32(value)?,
197 "wave_b" => params.wave_b = parse_f32(value)?,
198 "wave_x" => params.wave_x = parse_f32(value)?,
199 "wave_y" => params.wave_y = parse_f32(value)?,
200
201 "ob_size" => params.ob_size = parse_f32(value)?,
203 "ob_r" => params.ob_r = parse_f32(value)?,
204 "ob_g" => params.ob_g = parse_f32(value)?,
205 "ob_b" => params.ob_b = parse_f32(value)?,
206 "ob_a" => params.ob_a = parse_f32(value)?,
207 "ib_size" => params.ib_size = parse_f32(value)?,
208 "ib_r" => params.ib_r = parse_f32(value)?,
209 "ib_g" => params.ib_g = parse_f32(value)?,
210 "ib_b" => params.ib_b = parse_f32(value)?,
211 "ib_a" => params.ib_a = parse_f32(value)?,
212
213 "nMotionVectorsX" => params.n_motion_vectors_x = parse_f32(value)?,
215 "nMotionVectorsY" => params.n_motion_vectors_y = parse_f32(value)?,
216 "mv_dx" => params.mv_dx = parse_f32(value)?,
217 "mv_dy" => params.mv_dy = parse_f32(value)?,
218 "mv_l" => params.mv_l = parse_f32(value)?,
219 "mv_r" => params.mv_r = parse_f32(value)?,
220 "mv_g" => params.mv_g = parse_f32(value)?,
221 "mv_b" => params.mv_b = parse_f32(value)?,
222 "mv_a" => params.mv_a = parse_f32(value)?,
223
224 "b1n" => params.b1n = parse_f32(value)?,
226 "b2n" => params.b2n = parse_f32(value)?,
227 "b3n" => params.b3n = parse_f32(value)?,
228 "b1x" => params.b1x = parse_f32(value)?,
229 "b2x" => params.b2x = parse_f32(value)?,
230 "b3x" => params.b3x = parse_f32(value)?,
231 "b1ed" => params.b1ed = parse_f32(value)?,
232
233 "nVideoEchoOrientation" => params.n_video_echo_orientation = parse_i32(value)?,
235 "nWaveMode" => params.n_wave_mode = parse_i32(value)?,
236
237 "bAdditiveWaves" => params.b_additive_waves = parse_bool(value)?,
239 "bWaveDots" => params.b_wave_dots = parse_bool(value)?,
240 "bWaveThick" => params.b_wave_thick = parse_bool(value)?,
241 "bModWaveAlphaByVolume" => params.b_mod_wave_alpha_by_volume = parse_bool(value)?,
242 "bMaximizeWaveColor" => params.b_maximize_wave_color = parse_bool(value)?,
243 "bTexWrap" => params.b_tex_wrap = parse_bool(value)?,
244 "bDarkenCenter" => params.b_darken_center = parse_bool(value)?,
245 "bRedBlueStereo" => params.b_red_blue_stereo = parse_bool(value)?,
246 "bBrighten" => params.b_brighten = parse_bool(value)?,
247 "bDarken" => params.b_darken = parse_bool(value)?,
248 "bSolarize" => params.b_solarize = parse_bool(value)?,
249 "bInvert" => params.b_invert = parse_bool(value)?,
250
251 _ => {
253 params.extra.insert(key.to_string(), value.to_string());
254 }
255 }
256
257 Ok(())
258}
259
260fn default_wave_code(index: usize) -> WaveCode {
263 WaveCode {
264 index,
265 enabled: false,
266 samples: 512,
267 sep: 0,
268 b_spectrum: false,
269 b_use_dots: false,
270 b_draw_thick: false,
271 b_additive: false,
272 scaling: 1.0,
273 smoothing: 0.5,
274 r: 1.0,
275 g: 1.0,
276 b: 1.0,
277 a: 1.0,
278 per_frame_equations: Vec::new(),
279 per_point_equations: Vec::new(),
280 per_frame_init_equations: Vec::new(),
281 }
282}
283
284fn default_shape_code(index: usize) -> ShapeCode {
286 ShapeCode {
287 index,
288 enabled: false,
289 sides: 4,
290 additive: false,
291 thick_outline: false,
292 textured: false,
293 num_inst: 1,
294 x: 0.5,
295 y: 0.5,
296 rad: 0.1,
297 ang: 0.0,
298 tex_ang: 0.0,
299 tex_zoom: 1.0,
300 r: 1.0,
301 g: 1.0,
302 b: 1.0,
303 a: 1.0,
304 r2: 0.0,
305 g2: 0.0,
306 b2: 0.0,
307 a2: 0.0,
308 border_r: 1.0,
309 border_g: 1.0,
310 border_b: 1.0,
311 border_a: 0.0,
312 per_frame_equations: Vec::new(),
313 per_frame_init_equations: Vec::new(),
314 }
315}
316
317fn is_indexed_block(line: &str, prefix: &str) -> bool {
321 let Some(rest) = line.strip_prefix(prefix) else {
322 return false;
323 };
324 let bytes = rest.as_bytes();
325 let mut i = 0;
326 while i < bytes.len() && bytes[i].is_ascii_digit() {
327 i += 1;
328 }
329 i > 0 && i < bytes.len() && bytes[i] == b'_'
330}
331
332fn parse_wave_equation_line(line: &str, waves: &mut Vec<WaveCode>) -> Result<()> {
337 let Some((key, value)) = line.split_once('=') else {
338 return Ok(());
339 };
340 let rest = key.strip_prefix("wave_").unwrap_or("");
341 let Some((index_str, kind)) = rest.split_once('_') else {
342 return Ok(());
343 };
344 let Ok(index) = index_str.parse::<usize>() else {
345 return Ok(());
346 };
347 while waves.len() <= index {
348 waves.push(default_wave_code(waves.len()));
349 }
350 let wave = &mut waves[index];
351 let equation = value.trim().to_string();
352 if equation.is_empty() {
353 return Ok(());
354 }
355 if kind.starts_with("per_frame") {
356 wave.per_frame_equations.push(equation);
357 } else if kind.starts_with("per_point") {
358 wave.per_point_equations.push(equation);
359 } else if kind.starts_with("init") {
360 wave.per_frame_init_equations.push(equation);
361 }
362 Ok(())
363}
364
365fn parse_shape_equation_line(line: &str, shapes: &mut Vec<ShapeCode>) -> Result<()> {
369 let Some((key, value)) = line.split_once('=') else {
370 return Ok(());
371 };
372 let rest = key.strip_prefix("shape_").unwrap_or("");
373 let Some((index_str, kind)) = rest.split_once('_') else {
374 return Ok(());
375 };
376 let Ok(index) = index_str.parse::<usize>() else {
377 return Ok(());
378 };
379 while shapes.len() <= index {
380 shapes.push(default_shape_code(shapes.len()));
381 }
382 let shape = &mut shapes[index];
383 let equation = value.trim().to_string();
384 if equation.is_empty() {
385 return Ok(());
386 }
387 if kind.starts_with("per_frame") {
388 shape.per_frame_equations.push(equation);
389 } else if kind.starts_with("init") {
390 shape.per_frame_init_equations.push(equation);
391 }
392 Ok(())
393}
394
395fn parse_wavecode_line(line: &str, waves: &mut Vec<WaveCode>) -> Result<()> {
397 let parts: Vec<&str> = line.split('_').collect();
400 if parts.len() < 3 {
401 return Ok(()); }
403
404 let index: usize = parts[1].parse().unwrap_or(0);
405 let param_and_value = line.split_once('=');
406
407 if let Some((param_full, value)) = param_and_value {
408 let param = param_full.split('_').skip(2).collect::<Vec<_>>().join("_");
409
410 while waves.len() <= index {
412 waves.push(default_wave_code(waves.len()));
413 }
414
415 let wave = &mut waves[index];
417 match param.as_str() {
418 "enabled" => wave.enabled = value == "1",
419 "samples" => wave.samples = value.parse().unwrap_or(512),
420 "sep" => wave.sep = value.parse().unwrap_or(0),
421 "bSpectrum" => wave.b_spectrum = value == "1",
422 "bUseDots" => wave.b_use_dots = value == "1",
423 "bDrawThick" => wave.b_draw_thick = value == "1",
424 "bAdditive" => wave.b_additive = value == "1",
425 "scaling" => wave.scaling = value.parse().unwrap_or(1.0),
426 "smoothing" => wave.smoothing = value.parse().unwrap_or(0.5),
427 "r" => wave.r = value.parse().unwrap_or(1.0),
428 "g" => wave.g = value.parse().unwrap_or(1.0),
429 "b" => wave.b = value.parse().unwrap_or(1.0),
430 "a" => wave.a = value.parse().unwrap_or(1.0),
431 _ => {} }
433 }
434
435 Ok(())
436}
437
438fn parse_shapecode_line(line: &str, shapes: &mut Vec<ShapeCode>) -> Result<()> {
440 let parts: Vec<&str> = line.split('_').collect();
443 if parts.len() < 3 {
444 return Ok(()); }
446
447 let index: usize = parts[1].parse().unwrap_or(0);
448 let param_and_value = line.split_once('=');
449
450 if let Some((param_full, value)) = param_and_value {
451 let param = param_full.split('_').skip(2).collect::<Vec<_>>().join("_");
452
453 while shapes.len() <= index {
455 shapes.push(default_shape_code(shapes.len()));
456 }
457
458 let shape = &mut shapes[index];
460 match param.as_str() {
461 "enabled" => shape.enabled = value == "1",
462 "sides" => shape.sides = value.parse().unwrap_or(4),
463 "additive" => shape.additive = value == "1",
464 "thickOutline" => shape.thick_outline = value == "1",
465 "textured" => shape.textured = value == "1",
466 "num_inst" | "num inst" => shape.num_inst = value.parse().unwrap_or(1),
467 "x" => shape.x = value.parse().unwrap_or(0.5),
468 "y" => shape.y = value.parse().unwrap_or(0.5),
469 "rad" => shape.rad = value.parse().unwrap_or(0.1),
470 "ang" => shape.ang = value.parse().unwrap_or(0.0),
471 "tex_ang" | "tex ang" => shape.tex_ang = value.parse().unwrap_or(0.0),
472 "tex_zoom" | "tex zoom" => shape.tex_zoom = value.parse().unwrap_or(1.0),
473 "r" => shape.r = value.parse().unwrap_or(1.0),
474 "g" => shape.g = value.parse().unwrap_or(1.0),
475 "b" => shape.b = value.parse().unwrap_or(1.0),
476 "a" => shape.a = value.parse().unwrap_or(1.0),
477 "r2" => shape.r2 = value.parse().unwrap_or(0.0),
478 "g2" => shape.g2 = value.parse().unwrap_or(0.0),
479 "b2" => shape.b2 = value.parse().unwrap_or(0.0),
480 "a2" => shape.a2 = value.parse().unwrap_or(0.0),
481 "border_r" | "border r" => shape.border_r = value.parse().unwrap_or(1.0),
482 "border_g" | "border g" => shape.border_g = value.parse().unwrap_or(1.0),
483 "border_b" | "border b" => shape.border_b = value.parse().unwrap_or(1.0),
484 "border_a" | "border a" => shape.border_a = value.parse().unwrap_or(0.0),
485 _ => {} }
487 }
488
489 Ok(())
490}
491
492#[cfg(test)]
493mod tests {
494 use super::*;
495
496 #[test]
497 fn test_parse_version() {
498 let line = "MILKDROP_PRESET_VERSION=201";
499 assert_eq!(parse_version_line(line).unwrap(), 201);
500 }
501
502 #[test]
503 fn test_parse_equation() {
504 let line = "per_frame_1=wave_r = 0.5;";
505 assert_eq!(parse_equation_line(line), Some("wave_r = 0.5;".to_string()));
506 }
507
508 #[test]
509 fn test_parse_shader() {
510 let line = "warp_1=`shader_body";
511 assert_eq!(parse_shader_line(line), "shader_body");
512 }
513
514 #[test]
515 fn test_is_indexed_block() {
516 assert!(is_indexed_block("wave_0_per_frame1=...", "wave_"));
517 assert!(is_indexed_block("wave_3_init1=...", "wave_"));
518 assert!(is_indexed_block("shape_2_per_frame4=...", "shape_"));
519 assert!(!is_indexed_block("wave_r=1.0", "wave_"));
521 assert!(!is_indexed_block("wave_g=0.5", "wave_"));
522 assert!(!is_indexed_block("wave_x=0.5", "wave_"));
523 assert!(!is_indexed_block("warp_1=`code`", "wave_"));
524 }
525
526 #[test]
527 fn test_parse_wave_equation_line() {
528 let mut waves: Vec<WaveCode> = Vec::new();
529 parse_wave_equation_line("wave_0_init1=t1 = 0.5;", &mut waves).unwrap();
530 parse_wave_equation_line("wave_0_per_frame1=t1 = t1 + 0.1;", &mut waves).unwrap();
531 parse_wave_equation_line("wave_0_per_point1=x = sample; y = value1;", &mut waves).unwrap();
532 parse_wave_equation_line("wave_0_per_point2=r = 1.0; g = 0.5;", &mut waves).unwrap();
533 assert_eq!(waves.len(), 1);
534 assert_eq!(waves[0].per_frame_init_equations, vec!["t1 = 0.5;"]);
535 assert_eq!(waves[0].per_frame_equations, vec!["t1 = t1 + 0.1;"]);
536 assert_eq!(
537 waves[0].per_point_equations,
538 vec!["x = sample; y = value1;", "r = 1.0; g = 0.5;"]
539 );
540 }
541
542 #[test]
543 fn test_parse_wave_equation_extends_vec_to_index() {
544 let mut waves: Vec<WaveCode> = Vec::new();
545 parse_wave_equation_line("wave_3_init1=t1 = 0.5;", &mut waves).unwrap();
546 assert_eq!(waves.len(), 4);
547 assert_eq!(waves[3].index, 3);
548 assert_eq!(waves[0].per_frame_init_equations.len(), 0);
549 }
550
551 #[test]
552 fn test_parse_shape_equation_line() {
553 let mut shapes: Vec<ShapeCode> = Vec::new();
554 parse_shape_equation_line("shape_0_init1=q1 = 0;", &mut shapes).unwrap();
555 parse_shape_equation_line("shape_0_per_frame1=x = 0.5;", &mut shapes).unwrap();
556 assert_eq!(shapes.len(), 1);
557 assert_eq!(shapes[0].per_frame_init_equations, vec!["q1 = 0;"]);
558 assert_eq!(shapes[0].per_frame_equations, vec!["x = 0.5;"]);
559 }
560
561 #[test]
562 fn test_per_frame_init_routed_to_init_vec() {
563 let src = "MILKDROP_PRESET_VERSION=201\n[preset00]\n\
564 per_frame_init_1=t1 = 0;\n\
565 per_frame_1=t1 = t1 + 1;\n";
566 let preset = parse_milk_preset(src).unwrap();
567 assert_eq!(preset.per_frame_init_equations, vec!["t1 = 0;"]);
568 assert_eq!(preset.per_frame_equations, vec!["t1 = t1 + 1;"]);
569 }
570
571 #[test]
572 fn test_full_preset_with_custom_wave() {
573 let src = "MILKDROP_PRESET_VERSION=201\n[preset00]\n\
574 wavecode_0_enabled=1\n\
575 wavecode_0_samples=64\n\
576 wavecode_0_bSpectrum=1\n\
577 wave_0_init1=q1 = 0;\n\
578 wave_0_per_frame1=q1 = q1 + 0.01;\n\
579 wave_0_per_point1=x = sample; y = value1 * 0.5;\n";
580 let preset = parse_milk_preset(src).unwrap();
581 assert_eq!(preset.waves.len(), 1);
582 let w = &preset.waves[0];
583 assert!(w.enabled);
584 assert_eq!(w.samples, 64);
585 assert!(w.b_spectrum);
586 assert_eq!(w.per_frame_init_equations, vec!["q1 = 0;"]);
587 assert_eq!(w.per_frame_equations, vec!["q1 = q1 + 0.01;"]);
588 assert_eq!(w.per_point_equations, vec!["x = sample; y = value1 * 0.5;"]);
589 }
590
591 #[test]
592 fn test_full_preset_with_custom_shape() {
593 let src = "MILKDROP_PRESET_VERSION=201\n[preset00]\n\
594 shapecode_0_enabled=1\n\
595 shapecode_0_sides=6\n\
596 shapecode_0_num_inst=8\n\
597 shapecode_0_textured=1\n\
598 shapecode_0_thickOutline=1\n\
599 shapecode_0_additive=1\n\
600 shapecode_0_x=0.4\n\
601 shapecode_0_y=0.6\n\
602 shapecode_0_rad=0.15\n\
603 shapecode_0_r=0.8\n\
604 shapecode_0_g=0.2\n\
605 shapecode_0_b=0.5\n\
606 shapecode_0_a=0.9\n\
607 shapecode_0_r2=0.0\n\
608 shapecode_0_g2=0.6\n\
609 shapecode_0_b2=1.0\n\
610 shapecode_0_a2=0.4\n\
611 shapecode_0_border_r=1.0\n\
612 shapecode_0_border_g=1.0\n\
613 shapecode_0_border_b=1.0\n\
614 shapecode_0_border_a=0.5\n\
615 shape_0_init1=t1 = 0;\n\
616 shape_0_init2=t2 = 42;\n\
617 shape_0_per_frame1=ang = ang + 0.05 * instance;\n\
618 shape_0_per_frame2=rad = rad + 0.001;\n";
619 let preset = parse_milk_preset(src).unwrap();
620 assert_eq!(preset.shapes.len(), 1);
621 let s = &preset.shapes[0];
622 assert!(s.enabled);
623 assert_eq!(s.sides, 6);
624 assert_eq!(s.num_inst, 8);
625 assert!(s.textured);
626 assert!(s.thick_outline);
627 assert!(s.additive);
628 assert!((s.x - 0.4).abs() < 1e-5);
629 assert!((s.y - 0.6).abs() < 1e-5);
630 assert!((s.r2 - 0.0).abs() < 1e-5);
631 assert!((s.g2 - 0.6).abs() < 1e-5);
632 assert!((s.border_a - 0.5).abs() < 1e-5);
633 assert_eq!(s.per_frame_init_equations, vec!["t1 = 0;", "t2 = 42;"]);
634 assert_eq!(
635 s.per_frame_equations,
636 vec!["ang = ang + 0.05 * instance;", "rad = rad + 0.001;"]
637 );
638 }
639}