|
1 | 1 | use super::GlobalConfiguration; |
2 | | -use anyhow::{anyhow, bail, ensure, Context, Error, Result}; |
| 2 | +use anyhow::{anyhow, bail, Context, Error, Result}; |
3 | 3 | use itertools::Itertools; |
4 | 4 | use lhapdf::{Pdf, PdfSet}; |
5 | 5 | use pineappl::boc::{ScaleFuncForm, Scales}; |
@@ -433,36 +433,61 @@ pub fn parse_integer_range(range: &str) -> Result<RangeInclusive<usize>> { |
433 | 433 | } |
434 | 434 |
|
435 | 435 | pub fn parse_order(order: &str) -> Result<(u8, u8)> { |
436 | | - let mut alphas = 0; |
437 | | - let mut alpha = 0; |
| 436 | + const FORMAT: &str = "must be one of `asX`, `aY`, `asXaY` or `aYasX` with `X` and `Y` integer"; |
438 | 437 |
|
439 | | - let matches: Vec<_> = order.match_indices('a').collect(); |
| 438 | + // since 'a' is a substring of 'as', the latter must be given first |
| 439 | + let couplings = ["as", "a"]; |
| 440 | + let mut variables = [None, None]; |
| 441 | + let mut parsed; |
440 | 442 |
|
441 | | - ensure!( |
442 | | - matches.len() <= 2, |
443 | | - "unable to parse order; too many couplings in '{}'", |
444 | | - order |
445 | | - ); |
| 443 | + let mut chunk = order; |
446 | 444 |
|
447 | | - for (index, _) in matches { |
448 | | - if &order[index..index + 2] == "as" { |
449 | | - let len = order[index + 2..] |
450 | | - .chars() |
451 | | - .take_while(|c| c.is_numeric()) |
452 | | - .count(); |
453 | | - alphas = str::parse::<u8>(&order[index + 2..index + 2 + len]) |
454 | | - .context(format!("unable to parse order '{order}'"))?; |
455 | | - } else { |
456 | | - let len = order[index + 1..] |
457 | | - .chars() |
458 | | - .take_while(|c| c.is_numeric()) |
459 | | - .count(); |
460 | | - alpha = str::parse::<u8>(&order[index + 1..index + 1 + len]) |
461 | | - .context(format!("unable to parse order '{order}'"))?; |
| 445 | + loop { |
| 446 | + parsed = false; |
| 447 | + |
| 448 | + for (coupling, variable) in couplings.iter().zip(&mut variables) { |
| 449 | + if let Some(stripped) = chunk.strip_prefix(*coupling) { |
| 450 | + // find the index of the next coupling |
| 451 | + let len = couplings |
| 452 | + .iter() |
| 453 | + .filter_map(|c| stripped.find(c)) |
| 454 | + .min() |
| 455 | + .unwrap_or(stripped.len()); |
| 456 | + |
| 457 | + let (exponent, new_chunk) = stripped.split_at(len); |
| 458 | + chunk = new_chunk; |
| 459 | + |
| 460 | + if let Some(var) = variable { |
| 461 | + bail!("exponent of '{coupling}' has already been set to '{var}'"); |
| 462 | + } else if exponent.is_empty() { |
| 463 | + // if there's no exponent, we assume it to be one |
| 464 | + *variable = Some(1); |
| 465 | + } else { |
| 466 | + *variable = Some(exponent.parse().map_err(|err| { |
| 467 | + anyhow!("parsing exponent '{exponent}' of '{coupling}' failed with '{err}'") |
| 468 | + })?); |
| 469 | + } |
| 470 | + |
| 471 | + parsed = true; |
| 472 | + break; |
| 473 | + } |
462 | 474 | } |
| 475 | + |
| 476 | + if !parsed { |
| 477 | + break; |
| 478 | + } |
| 479 | + } |
| 480 | + |
| 481 | + if !chunk.is_empty() { |
| 482 | + bail!("found remainder '{chunk}', but order {FORMAT}"); |
463 | 483 | } |
464 | 484 |
|
465 | | - Ok((alphas, alpha)) |
| 485 | + Ok(match variables { |
| 486 | + [Some(alphas), Some(alpha)] => (alphas, alpha), |
| 487 | + [Some(alphas), None] => (alphas, 0), |
| 488 | + [None, Some(alpha)] => (0, alpha), |
| 489 | + [None, None] => bail!("{FORMAT}"), |
| 490 | + }) |
466 | 491 | } |
467 | 492 |
|
468 | 493 | #[cfg(test)] |
@@ -494,4 +519,46 @@ mod test { |
494 | 519 | } |
495 | 520 | ); |
496 | 521 | } |
| 522 | + |
| 523 | + #[test] |
| 524 | + fn parse_order() { |
| 525 | + // that's OK |
| 526 | + assert_eq!(super::parse_order("as16a28").unwrap(), (16, 28)); |
| 527 | + |
| 528 | + // empty exponents are set to 1 |
| 529 | + assert_eq!(super::parse_order("asa28").unwrap(), (1, 28)); |
| 530 | + assert_eq!(super::parse_order("as16a").unwrap(), (16, 1)); |
| 531 | + assert_eq!(super::parse_order("asa").unwrap(), (1, 1)); |
| 532 | + assert_eq!(super::parse_order("aas").unwrap(), (1, 1)); |
| 533 | + |
| 534 | + // exponent is not an integer |
| 535 | + assert_eq!( |
| 536 | + format!("{}", super::parse_order("asBLA").unwrap_err()), |
| 537 | + "parsing exponent 'BLA' of 'as' failed with 'invalid digit found in string'" |
| 538 | + ); |
| 539 | + |
| 540 | + // 'LO' was previous accepted: https://github.com/NNPDF/pineappl/issues/347 |
| 541 | + assert_eq!( |
| 542 | + format!("{}", super::parse_order("LO").unwrap_err()), |
| 543 | + "found remainder 'LO', but order must be one of `asX`, `aY`, `asXaY` or `aYasX` with `X` and `Y` integer" |
| 544 | + ); |
| 545 | + |
| 546 | + // repeated couplings must raise an error |
| 547 | + assert_eq!( |
| 548 | + format!("{}", super::parse_order("as3as2").unwrap_err()), |
| 549 | + "exponent of 'as' has already been set to '3'" |
| 550 | + ); |
| 551 | + |
| 552 | + // different repeat coupling |
| 553 | + assert_eq!( |
| 554 | + format!("{}", super::parse_order("a3a2as2").unwrap_err()), |
| 555 | + "exponent of 'a' has already been set to '3'" |
| 556 | + ); |
| 557 | + |
| 558 | + // empty is wrong as well |
| 559 | + assert_eq!( |
| 560 | + format!("{}", super::parse_order("").unwrap_err()), |
| 561 | + "must be one of `asX`, `aY`, `asXaY` or `aYasX` with `X` and `Y` integer" |
| 562 | + ); |
| 563 | + } |
497 | 564 | } |
0 commit comments