commit c0ee1fd098974aa34230cb73021f2578f0cfe8fc
parent 1a693daa459cf24202142db2763f08d97ddc6e70
Author: Antoine A <>
Date: Fri, 21 Feb 2025 17:58:55 +0100
Improve rs codegen
Diffstat:
2 files changed, 43 insertions(+), 46 deletions(-)
diff --git a/codegen_rs.py b/codegen_rs.py
@@ -68,18 +68,20 @@ pub enum PatternErr {
}
"""
+
def gen_match(name, return_type, match):
global rust
rust += f"pub const fn {name}(self) -> {return_type} {{\nmatch self {{\n"
for r in registry:
- rust += f"{r['code']} => {match(r)},\n"
+ rust += f"{r['code']} => {match(r)},\n"
rust += "}\n}\n\n"
-
+
def gen_range(name):
def fmt_range(r):
(start, end) = r[name + "_range"] or [0, 0]
return f"{start}..{end}"
+
gen_match(f"{name}_id", "core::ops::Range<usize>", fmt_range)
@@ -90,36 +92,9 @@ def gen_pattern(name):
for repetition, char in pattern:
fmt += f"({repetition}, {char.upper()}),"
return fmt + "]"
- gen_match(f"{name}_pattern", "Pattern", fmt_pattern)
+ gen_match(f"{name}_pattern", "Pattern", fmt_pattern)
-def gen_check(name):
- global rust
- rust += f"""pub fn {name}_check(self, iban_ascii: &[u8]) -> Result<(), PatternErr> {{
- // IBAN are made of ASCII digits and uppercase
- debug_assert!(iban_ascii.iter().all(|b| b.is_ascii_uppercase() || b.is_ascii_digit()));
- match self {{
- """
- for r in registry:
- pattern = r[name + "_rules"] or []
- length = r[name + "_len"]
- rust += f"{r['code']} => {{\n"
- rust += f" if iban_ascii.len() != {length} {{"
- rust += f" return Err(PatternErr::Len({length}, iban_ascii.len()))\n"
- rust += " }"
- if len(pattern) != 0:
- cursor = 0
- rust += " else if "
- for repetition, char in pattern:
- if cursor > 0:
- rust += " || "
- rust += f"!{char.upper()}.check(&iban_ascii[{cursor}..{cursor + repetition}])"
- cursor += repetition
- rust += """ {
- return Err(PatternErr::Malformed)
- }"""
- rust += "\n}\n"
- rust += "}\nOk(())\n}\n"
rust += "#[derive(Debug, Clone, Copy, PartialEq, Eq)]\n"
rust += "pub enum Country {\n"
@@ -135,7 +110,7 @@ rust += " _ => None,\n"
rust += " }\n"
rust += "}\n\n"
gen_match("as_str", "&'static str", lambda r: f'"{r["code"]}"')
-gen_match("as_bytes", "[u8; 2]", lambda r: f"[b'{r['code'][0]}', b'{r['code'][1]}']")
+gen_match("as_bytes", "&'static [u8; 2]", lambda r: f'b"{r["code"]}"')
gen_match("iban_len", "usize", lambda r: f"{r['iban_len']}")
rust += "pub const fn bban_len(self) -> usize {\n"
rust += " self.iban_len() - 4\n"
@@ -143,11 +118,8 @@ rust += "}\n\n"
gen_range("bank")
gen_range("branch")
gen_pattern("bban")
-# gen_pattern("bank")
-# gen_pattern("branch")
-gen_check("bban")
-# gen_check("bank")
-# gen_check("branch")
+gen_pattern("bank")
+gen_pattern("branch")
rust += "}\n\n"
rust += """
@@ -173,6 +145,24 @@ pub fn rng_pattern(out: &mut [u8], pattern: Pattern) {
}
}
+/// Valid an IBAN slice against a pattern
+pub fn check_pattern(iban_ascii: &[u8], pattern: Pattern) -> Result<(), PatternErr> {
+ // IBAN are made of ASCII digits and uppercase
+ debug_assert!(iban_ascii.iter().all(|b| b.is_ascii_uppercase() || b.is_ascii_digit()));
+ let pattern_len: u8 = pattern.iter().map(|(len, _)| *len as u8).sum();
+ if iban_ascii.len() != pattern_len as usize {
+ return Err(PatternErr::Len(pattern_len, iban_ascii.len()));
+ }
+ let mut cursor = 0;
+ for (repetition, char) in pattern {
+ if !char.check(&iban_ascii[cursor..cursor + *repetition as usize]) {
+ return Err(PatternErr::Malformed);
+ }
+ cursor += *repetition as usize;
+ }
+ Ok(())
+}
+
"""
rust += "#[cfg(test)]\n"
rust += f"pub const VALID_IBAN: [(&str, Option<&str>); {len(registry)}] = [\n"
diff --git a/parse_registry.py b/parse_registry.py
@@ -22,7 +22,7 @@ def parse_line(prefix):
return parts
-def parse_list_line(prefix):
+def parse_countries(prefix):
line = parse_line(prefix)
return list(map(lambda x: [] if x is None else x.strip('"').split(", "), line))
@@ -38,6 +38,8 @@ def parse_int_line(prefix):
def parse_pattern(encoded):
+ if encoded is None:
+ return (0, [], "")
assert re.match(STRUCTURE_PATTERN, encoded), f"{STRUCTURE_PATTERN} {encoded}"
pattern_len = 0
rules = []
@@ -74,20 +76,20 @@ def parse_range(range):
parse_line("Data element")
country_names = parse_line("Name of country")
country_code = parse_line("IBAN prefix country code (ISO 3166)")
-country_code_include = parse_list_line(
+country_code_include = parse_countries(
"Country code includes other countries/territories"
)
sepa = parse_bool_line("SEPA country")
-sepa_include = parse_list_line("SEPA country also includes")
+sepa_include = parse_countries("SEPA country also includes")
account_example = parse_line("Domestic account number example")
parse_line("BBAN")
bban_patterns = parse_line("BBAN structure")
bban_len = parse_int_line("BBAN length")
bank_range = parse_line("Bank identifier position within the BBAN")
-bban_bank_structure = parse_line("Bank identifier pattern")
+bank_patterns = parse_line("Bank identifier pattern")
branch_range = parse_line("Branch identifier position within the BBAN")
-bban_branch_structure = parse_line("Branch identifier pattern")
+branch_patterns = parse_line("Branch identifier pattern")
bban_bank_example = parse_line("Bank identifier example")
bban_branch_example = parse_line("Branch identifier example")
bban_example = parse_line("BBAN example")
@@ -111,13 +113,13 @@ for i in range(len(country_names)):
elif code == "NO":
bban_patterns[i] = "4!n6!n1!n"
elif code == "AL":
- bban_bank_structure[i] = "3!n"
- bban_branch_structure[i] = "5!n"
+ bank_patterns[i] = "3!n"
+ branch_patterns[i] = "5!n"
elif code == "EG":
- bban_bank_structure[i] += "n"
- bban_branch_structure[i] += "n"
+ bank_patterns[i] += "n"
+ branch_patterns[i] += "n"
elif code == "FI":
- bban_bank_structure[i] = "3!n"
+ bank_patterns[i] = "3!n"
elif code == "BA":
# The BBAN does not match the IBAN. The bank and branch match
# the BBAN. Manually fix all three to correspond to IBAN.
@@ -147,6 +149,9 @@ for i in range(len(country_names)):
(bban_length, bban_rules, bban_regex) = parse_pattern(bban_pattern)
assert bban_len[i] == bban_length == iban_len[i] - 4
+ (_, bank_rules, _) = parse_pattern(bank_patterns[i])
+ (_, branch_rules, _) = parse_pattern(branch_patterns[i])
+
# if bban_bank[i] is not None:
# assert range_len(bban_bank[i]) == structure_len(bban_bank_structure[i])
# if bban_branch[i] is not None:
@@ -166,7 +171,9 @@ for i in range(len(country_names)):
"bban_regex": bban_regex,
"bban_example": bban_example[i],
"bank_range": parse_range(bank_range[i]),
+ "bank_rules": bank_rules,
"branch_range": parse_range(branch_range[i]),
+ "branch_rules": branch_rules,
}
)