Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
use super::funejson::Value; ← the fune runtime: the JSON value the test vectors use; fune build keeps it only where a signature takes one
use super::money_amount::{money, money_to_value}; ← from money.amount ^1.0.0 · built alongside by fune
/// The whitespace every language trims alike: Rust's trim() also takes \x85,
/// which JavaScript's trim() keeps, and that takes \u{feff}, which this keeps.
fn is_space(c: char) -> bool {
matches!(
c,
' ' | '\t' | '\n' | '\r' | '\u{0b}' | '\u{0c}' | '\u{a0}' | '\u{1680}' | '\u{2000}'..='\u{200a}' | '\u{2028}' | '\u{2029}' | '\u{202f}' | '\u{205f}' | '\u{3000}'
)
}
fn all_digits(s: &str) -> bool {
!s.is_empty() && s.bytes().all(|b| b.is_ascii_digit())
}
/// A 1-6 digit number with no leading zero (or "0" itself when `zero_ok`).
fn code_number(s: &str, zero_ok: bool) -> Option<i64> {
if !all_digits(s) || s.len() > 6 {
return None;
}
if s.len() > 1 && s.starts_with('0') {
return None;
}
let n: i64 = s.parse().ok()?;
if n == 0 && !zero_ok {
return None;
}
Some(n)
}
/// Take a UK PAYE tax code apart into its region, kind, allowance and basis.
///
/// The annual allowance is number x £10 + £9, not number x £10: a code covers
/// a £10 band of allowances and HMRC's routines give the employee the top of
/// it, so 1257L is worth £12,579 a year to the payroll.
///
/// # Panics
/// Panics on a code HMRC does not issue.
pub fn parse_tax_code(code: &str) -> TaxCode {
let original = code;
let upper = code.trim_matches(is_space).to_uppercase();
if upper.is_empty() {
panic!("unrecognised tax code \"{}\"", original);
}
// The basis marker may be written with or without a space.
let mut cumulative = true;
let mut text: &str = &upper;
for marker in ["W1", "M1", "X"] {
if let Some(rest) = upper.strip_suffix(marker) {
let rest = rest.trim_end_matches(is_space);
if !rest.is_empty() {
text = rest;
cumulative = false;
}
break;
}
}
let (region, body) = if let Some(rest) = text.strip_prefix('S') {
("scotland", rest)
} else if let Some(rest) = text.strip_prefix('C') {
("wales", rest)
} else {
("rest-of-uk", text)
};
let kind: &str;
let mut number = 0i64;
let mut suffix: Option<String> = None;
let mut allowance_pounds = 0i64;
let last = body.chars().last();
if body == "NT" {
if region != "rest-of-uk" {
panic!("there is no Scottish or Welsh NT code: \"{}\"", original);
}
kind = "no-tax";
} else if body == "BR" {
kind = "basic-rate";
} else if body.len() == 2 && body.starts_with('D') && all_digits(&body[1..]) {
kind = "d-rate";
number = body[1..].parse().unwrap();
} else if let Some(n) = body.strip_prefix('K').and_then(|rest| code_number(rest, false)) {
kind = "negative-allowance";
number = n;
allowance_pounds = -(n * 10 + 9);
} else if let Some(n) = match last {
Some('L') | Some('M') | Some('N') | Some('T') => code_number(&body[..body.len() - 1], true),
_ => None,
} {
let letter = last.unwrap();
if n == 0 && letter != 'T' {
panic!(
"unrecognised tax code \"{}\": a zero allowance is only issued as 0T",
original
);
}
kind = "allowance";
number = n;
suffix = Some(letter.to_string());
allowance_pounds = if n == 0 { 0 } else { n * 10 + 9 };
} else {
panic!("unrecognised tax code \"{}\"", original);
}
let prefix = match region {
"scotland" => "S",
"wales" => "C",
_ => "",
};
TaxCode {
code: format!("{}{}", prefix, body),
region: region.to_string(),
kind: kind.to_string(),
number,
suffix,
annual_allowance: money(allowance_pounds * 100, "GBP"),
cumulative,
}
}
pub fn tax_code_to_value(t: &TaxCode) -> Value {
Value::obj(vec![
("code", Value::str(&t.code)),
("region", Value::str(&t.region)),
("kind", Value::str(&t.kind)),
("number", Value::Int(t.number)),
(
"suffix",
match &t.suffix {
Some(s) => Value::str(s),
None => Value::Null,
},
),
("annualAllowance", money_to_value(&t.annual_allowance)),
("cumulative", Value::Bool(t.cumulative)),
])
}
pub fn fune_vector(args: &[Value]) -> Value {
tax_code_to_value(&parse_tax_code(args[0].as_str()))
}