1use crate::interpreter::value::*;
5use crate::interpreter::BuiltinContext;
6use minir_macros::interpreter_builtin;
7
8use super::color::RColor;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum LineType {
15 Blank,
16 Solid,
17 Dashed,
18 Dotted,
19 DotDash,
20 LongDash,
21 TwoDash,
22}
23
24impl LineType {
25 pub fn from_r_value(val: &RValue) -> Result<Self, RError> {
26 match val {
27 RValue::Vector(rv) => match &rv.inner {
28 Vector::Character(c) => {
29 let s = c.first().and_then(|o| o.as_deref()).unwrap_or("solid");
30 Self::from_str(s)
31 }
32 Vector::Integer(i) => {
33 let n = i.first_opt().unwrap_or(1);
34 Self::from_int(n)
35 }
36 Vector::Double(d) => {
37 let n = d.first_opt().map(|v| v as i64).unwrap_or(1);
38 Self::from_int(n)
39 }
40 _ => Err(RError::new(
41 RErrorKind::Other,
42 "invalid line type specification",
43 )),
44 },
45 _ => Err(RError::new(
46 RErrorKind::Other,
47 "invalid line type specification",
48 )),
49 }
50 }
51
52 fn from_str(s: &str) -> Result<Self, RError> {
53 match s.to_lowercase().as_str() {
54 "blank" | "0" => Ok(LineType::Blank),
55 "solid" | "1" => Ok(LineType::Solid),
56 "dashed" | "2" => Ok(LineType::Dashed),
57 "dotted" | "3" => Ok(LineType::Dotted),
58 "dotdash" | "4" => Ok(LineType::DotDash),
59 "longdash" | "5" => Ok(LineType::LongDash),
60 "twodash" | "6" => Ok(LineType::TwoDash),
61 _ => Err(RError::new(
62 RErrorKind::Other,
63 format!("invalid line type '{s}'"),
64 )),
65 }
66 }
67
68 fn from_int(n: i64) -> Result<Self, RError> {
69 match n {
70 0 => Ok(LineType::Blank),
71 1 => Ok(LineType::Solid),
72 2 => Ok(LineType::Dashed),
73 3 => Ok(LineType::Dotted),
74 4 => Ok(LineType::DotDash),
75 5 => Ok(LineType::LongDash),
76 6 => Ok(LineType::TwoDash),
77 _ => Err(RError::new(
78 RErrorKind::Other,
79 format!("invalid line type integer {n}: must be 0–6"),
80 )),
81 }
82 }
83
84 #[allow(dead_code)] fn to_int(self) -> i64 {
86 match self {
87 LineType::Blank => 0,
88 LineType::Solid => 1,
89 LineType::Dashed => 2,
90 LineType::Dotted => 3,
91 LineType::DotDash => 4,
92 LineType::LongDash => 5,
93 LineType::TwoDash => 6,
94 }
95 }
96
97 fn to_str(self) -> &'static str {
98 match self {
99 LineType::Blank => "blank",
100 LineType::Solid => "solid",
101 LineType::Dashed => "dashed",
102 LineType::Dotted => "dotted",
103 LineType::DotDash => "dotdash",
104 LineType::LongDash => "longdash",
105 LineType::TwoDash => "twodash",
106 }
107 }
108}
109
110#[derive(Debug, Clone, Copy, PartialEq, Eq)]
112pub enum FontFace {
113 Plain,
114 Bold,
115 Italic,
116 BoldItalic,
117 Symbol,
118}
119
120impl FontFace {
121 fn from_int(n: i64) -> Result<Self, RError> {
122 match n {
123 1 => Ok(FontFace::Plain),
124 2 => Ok(FontFace::Bold),
125 3 => Ok(FontFace::Italic),
126 4 => Ok(FontFace::BoldItalic),
127 5 => Ok(FontFace::Symbol),
128 _ => Err(RError::new(
129 RErrorKind::Other,
130 format!("invalid font face {n}: must be 1–5"),
131 )),
132 }
133 }
134
135 fn to_int(self) -> i64 {
136 match self {
137 FontFace::Plain => 1,
138 FontFace::Bold => 2,
139 FontFace::Italic => 3,
140 FontFace::BoldItalic => 4,
141 FontFace::Symbol => 5,
142 }
143 }
144}
145
146#[derive(Debug, Clone)]
152pub struct ParState {
153 pub col: RColor,
155 pub bg: RColor,
157 pub lwd: f64,
159 pub lty: LineType,
161 pub pch: i64,
163 pub cex: f64,
165 pub ps: f64,
167 pub font: FontFace,
169 pub family: String,
171 pub mar: [f64; 4],
173 pub mfrow: [i64; 2],
175 pub mfcol: [i64; 2],
177 pub usr: [f64; 4],
179 pub las: i64,
181 pub xaxs: String,
183 pub yaxs: String,
185 pub new: bool,
187}
188
189impl Default for ParState {
190 fn default() -> Self {
191 ParState {
192 col: RColor::BLACK,
193 bg: RColor::WHITE,
194 lwd: 1.0,
195 lty: LineType::Solid,
196 pch: 1,
197 cex: 1.0,
198 ps: 12.0,
199 font: FontFace::Plain,
200 family: "sans".to_string(),
201 mar: [5.1, 4.1, 4.1, 2.1],
202 mfrow: [1, 1],
203 mfcol: [1, 1],
204 usr: [0.0, 1.0, 0.0, 1.0],
205 las: 0,
206 xaxs: "r".to_string(),
207 yaxs: "r".to_string(),
208 new: false,
209 }
210 }
211}
212
213impl ParState {
214 pub fn get(&self, name: &str) -> Option<RValue> {
216 match name {
217 "col" => Some(RValue::vec(Vector::Character(
218 vec![Some(self.col.to_hex())].into(),
219 ))),
220 "bg" => Some(RValue::vec(Vector::Character(
221 vec![Some(self.bg.to_hex())].into(),
222 ))),
223 "lwd" => Some(RValue::vec(Vector::Double(vec![Some(self.lwd)].into()))),
224 "lty" => Some(RValue::vec(Vector::Character(
225 vec![Some(self.lty.to_str().to_string())].into(),
226 ))),
227 "pch" => Some(RValue::vec(Vector::Integer(vec![Some(self.pch)].into()))),
228 "cex" => Some(RValue::vec(Vector::Double(vec![Some(self.cex)].into()))),
229 "ps" => Some(RValue::vec(Vector::Double(vec![Some(self.ps)].into()))),
230 "font" => Some(RValue::vec(Vector::Integer(
231 vec![Some(self.font.to_int())].into(),
232 ))),
233 "family" => Some(RValue::vec(Vector::Character(
234 vec![Some(self.family.clone())].into(),
235 ))),
236 "mar" => Some(RValue::vec(Vector::Double(
237 self.mar.iter().map(|&v| Some(v)).collect::<Vec<_>>().into(),
238 ))),
239 "mfrow" => Some(RValue::vec(Vector::Integer(
240 self.mfrow
241 .iter()
242 .map(|&v| Some(v))
243 .collect::<Vec<_>>()
244 .into(),
245 ))),
246 "mfcol" => Some(RValue::vec(Vector::Integer(
247 self.mfcol
248 .iter()
249 .map(|&v| Some(v))
250 .collect::<Vec<_>>()
251 .into(),
252 ))),
253 "usr" => Some(RValue::vec(Vector::Double(
254 self.usr.iter().map(|&v| Some(v)).collect::<Vec<_>>().into(),
255 ))),
256 "las" => Some(RValue::vec(Vector::Integer(vec![Some(self.las)].into()))),
257 "xaxs" => Some(RValue::vec(Vector::Character(
258 vec![Some(self.xaxs.clone())].into(),
259 ))),
260 "yaxs" => Some(RValue::vec(Vector::Character(
261 vec![Some(self.yaxs.clone())].into(),
262 ))),
263 "new" => Some(RValue::vec(Vector::Logical(vec![Some(self.new)].into()))),
264 _ => None,
265 }
266 }
267
268 pub fn set(&mut self, name: &str, value: &RValue) -> Result<Option<RValue>, RError> {
270 let old = self.get(name);
271 match name {
272 "col" => {
273 self.col = parse_color_param(value, name)?;
274 }
275 "bg" => {
276 self.bg = parse_color_param(value, name)?;
277 }
278 "lwd" => {
279 self.lwd = parse_double_param(value, name)?;
280 }
281 "lty" => {
282 self.lty = LineType::from_r_value(value)?;
283 }
284 "pch" => {
285 self.pch = parse_int_param(value, name)?;
286 }
287 "cex" => {
288 self.cex = parse_double_param(value, name)?;
289 }
290 "ps" => {
291 self.ps = parse_double_param(value, name)?;
292 }
293 "font" => {
294 let n = parse_int_param(value, name)?;
295 self.font = FontFace::from_int(n)?;
296 }
297 "family" => {
298 self.family = parse_string_param(value, name)?;
299 }
300 "mar" => {
301 self.mar = parse_double4_param(value, name)?;
302 }
303 "mfrow" => {
304 self.mfrow = parse_int2_param(value, name)?;
305 }
306 "mfcol" => {
307 self.mfcol = parse_int2_param(value, name)?;
308 }
309 "usr" => {
310 self.usr = parse_double4_param(value, name)?;
311 }
312 "las" => {
313 self.las = parse_int_param(value, name)?;
314 }
315 "xaxs" => {
316 self.xaxs = parse_string_param(value, name)?;
317 }
318 "yaxs" => {
319 self.yaxs = parse_string_param(value, name)?;
320 }
321 "new" => {
322 self.new = parse_logical_param(value, name)?;
323 }
324 _ => {
325 return Err(RError::new(
326 RErrorKind::Other,
327 format!("'{name}' is not a graphical parameter"),
328 ));
329 }
330 }
331 Ok(old)
332 }
333
334 pub fn known_params() -> &'static [&'static str] {
336 &[
337 "bg", "cex", "col", "family", "font", "las", "lty", "lwd", "mar", "mfcol", "mfrow",
338 "new", "pch", "ps", "usr", "xaxs", "yaxs",
339 ]
340 }
341}
342
343fn parse_color_param(value: &RValue, name: &str) -> Result<RColor, RError> {
348 match value {
349 RValue::Vector(rv) => match &rv.inner {
350 Vector::Character(c) => {
351 let s = c.first().and_then(|o| o.as_deref()).ok_or_else(|| {
352 RError::new(
353 RErrorKind::Other,
354 format!("invalid color for parameter '{name}'"),
355 )
356 })?;
357 RColor::from_r_value(s, &[]).map_err(|e| {
358 RError::new(
359 RErrorKind::Other,
360 format!("invalid color for parameter '{name}': {e}"),
361 )
362 })
363 }
364 _ => Err(RError::new(
365 RErrorKind::Other,
366 format!("invalid color for parameter '{name}'"),
367 )),
368 },
369 _ => Err(RError::new(
370 RErrorKind::Other,
371 format!("invalid color for parameter '{name}'"),
372 )),
373 }
374}
375
376fn parse_double_param(value: &RValue, name: &str) -> Result<f64, RError> {
377 value
378 .as_vector()
379 .and_then(|v| v.as_double_scalar())
380 .ok_or_else(|| {
381 RError::new(
382 RErrorKind::Other,
383 format!("invalid value for parameter '{name}'"),
384 )
385 })
386}
387
388fn parse_int_param(value: &RValue, name: &str) -> Result<i64, RError> {
389 value
390 .as_vector()
391 .and_then(|v| v.as_integer_scalar())
392 .ok_or_else(|| {
393 RError::new(
394 RErrorKind::Other,
395 format!("invalid value for parameter '{name}'"),
396 )
397 })
398}
399
400fn parse_string_param(value: &RValue, name: &str) -> Result<String, RError> {
401 value
402 .as_vector()
403 .and_then(|v| v.as_character_scalar())
404 .ok_or_else(|| {
405 RError::new(
406 RErrorKind::Other,
407 format!("invalid value for parameter '{name}'"),
408 )
409 })
410}
411
412fn parse_logical_param(value: &RValue, name: &str) -> Result<bool, RError> {
413 value
414 .as_vector()
415 .and_then(|v| v.as_logical_scalar())
416 .ok_or_else(|| {
417 RError::new(
418 RErrorKind::Other,
419 format!("invalid value for parameter '{name}'"),
420 )
421 })
422}
423
424fn parse_double4_param(value: &RValue, name: &str) -> Result<[f64; 4], RError> {
425 let v = value.as_vector().ok_or_else(|| {
426 RError::new(
427 RErrorKind::Other,
428 format!("parameter '{name}' requires a numeric vector of length 4"),
429 )
430 })?;
431 let doubles = v.to_doubles();
432 if doubles.len() != 4 {
433 return Err(RError::new(
434 RErrorKind::Other,
435 format!(
436 "parameter '{name}' requires a numeric vector of length 4, got {}",
437 doubles.len()
438 ),
439 ));
440 }
441 let mut result = [0.0; 4];
442 for (i, d) in doubles.iter().enumerate() {
443 result[i] = d.ok_or_else(|| {
444 RError::new(RErrorKind::Other, format!("NA value in parameter '{name}'"))
445 })?;
446 }
447 Ok(result)
448}
449
450fn parse_int2_param(value: &RValue, name: &str) -> Result<[i64; 2], RError> {
451 let v = value.as_vector().ok_or_else(|| {
452 RError::new(
453 RErrorKind::Other,
454 format!("parameter '{name}' requires an integer vector of length 2"),
455 )
456 })?;
457 let ints = v.to_integers();
458 if ints.len() != 2 {
459 return Err(RError::new(
460 RErrorKind::Other,
461 format!(
462 "parameter '{name}' requires a vector of length 2, got {}",
463 ints.len()
464 ),
465 ));
466 }
467 let mut result = [0i64; 2];
468 for (i, val) in ints.iter().enumerate() {
469 result[i] = val.ok_or_else(|| {
470 RError::new(RErrorKind::Other, format!("NA value in parameter '{name}'"))
471 })?;
472 }
473 Ok(result)
474}
475
476#[interpreter_builtin(namespace = "graphics")]
489fn interp_par(
490 args: &[RValue],
491 named: &[(String, RValue)],
492 ctx: &BuiltinContext,
493) -> Result<RValue, RError> {
494 let mut par = ctx.interpreter().par_state.borrow_mut();
495
496 if args.is_empty() && named.is_empty() {
498 let mut entries: Vec<(Option<String>, RValue)> = Vec::new();
499 for &name in ParState::known_params() {
500 if let Some(val) = par.get(name) {
501 entries.push((Some(name.to_string()), val));
502 }
503 }
504 return Ok(RValue::List(RList::new(entries)));
505 }
506
507 let mut result_entries: Vec<(Option<String>, RValue)> = Vec::new();
508
509 for arg in args {
511 if let Some(v) = arg.as_vector() {
512 if let Some(name) = v.as_character_scalar() {
513 match par.get(&name) {
514 Some(val) => {
515 result_entries.push((Some(name), val));
516 }
517 None => {
518 return Err(RError::new(
519 RErrorKind::Other,
520 format!("'{name}' is not a graphical parameter"),
521 ));
522 }
523 }
524 }
525 }
526 }
527
528 for (name, value) in named {
530 let old = par.set(name, value)?;
531 if let Some(old_val) = old {
532 result_entries.push((Some(name.clone()), old_val));
533 }
534 }
535
536 if result_entries.is_empty() {
537 Ok(RValue::List(RList::new(vec![])))
538 } else if result_entries.len() == 1 && args.len() == 1 && named.is_empty() {
539 Ok(result_entries
541 .into_iter()
542 .next()
543 .expect("len() == 1 guarantees next()")
544 .1)
545 } else {
546 Ok(RValue::List(RList::new(result_entries)))
547 }
548}
549
550