Просмотр исходного кода

Add and test punycode encoding.

Simon Sapin 12 лет назад
Родитель
Сommit
7781e38d9b
1 измененных файлов с 93 добавлено и 10 удалено
  1. 93 10
      punycode.rs

+ 93 - 10
punycode.rs

@@ -10,7 +10,6 @@
 use std::u32;
 use std::u32;
 use std::char;
 use std::char;
 use std::ascii::Ascii;
 use std::ascii::Ascii;
-use std::ascii::AsciiStr;
 
 
 
 
 static BASE: u32 = 36;
 static BASE: u32 = 36;
@@ -37,7 +36,7 @@ fn adapt(mut delta: u32, num_points: u32, first_time: bool) -> u32 {
 
 
 
 
 pub fn decode(input: &[Ascii]) -> Option<~[char]> {
 pub fn decode(input: &[Ascii]) -> Option<~[char]> {
-    let (mut output, input) = match input.to_str_ascii().rfind(DELIMITER) {
+    let (mut output, input) = match input.rposition_elem(&DELIMITER.to_ascii()) {
         None => (~[], input),
         None => (~[], input),
         Some(position) => (
         Some(position) => (
             input.slice_to(position).map(|a| a.to_char()),
             input.slice_to(position).map(|a| a.to_char()),
@@ -64,7 +63,7 @@ pub fn decode(input: &[Ascii]) -> Option<~[char]> {
                 _ => return None
                 _ => return None
             } as u32;
             } as u32;
             if digit > (u32::max_value - i) / weight {
             if digit > (u32::max_value - i) / weight {
-                return None  // Malformed input would cause integer overflow
+                return None  // Overflow
             }
             }
             i += digit * weight;
             i += digit * weight;
             let t = if k <= bias { T_MIN }
             let t = if k <= bias { T_MIN }
@@ -74,7 +73,7 @@ pub fn decode(input: &[Ascii]) -> Option<~[char]> {
                 break
                 break
             }
             }
             if weight > u32::max_value / (BASE - t) {
             if weight > u32::max_value / (BASE - t) {
-                return None  // Malformed input would cause integer overflow
+                return None  // Overflow
             }
             }
             weight *= BASE - t;
             weight *= BASE - t;
             k += BASE;
             k += BASE;
@@ -86,7 +85,7 @@ pub fn decode(input: &[Ascii]) -> Option<~[char]> {
         let length = output.len() as u32;
         let length = output.len() as u32;
         bias = adapt(i - previous_i, length + 1, previous_i == 0);
         bias = adapt(i - previous_i, length + 1, previous_i == 0);
         if i / (length + 1) > u32::max_value - n {
         if i / (length + 1) > u32::max_value - n {
-            return None  // Malformed input would cause integer overflow
+            return None  // Overflow
         }
         }
         n += i / (length + 1);
         n += i / (length + 1);
         i %= length + 1;
         i %= length + 1;
@@ -101,18 +100,102 @@ pub fn decode(input: &[Ascii]) -> Option<~[char]> {
 }
 }
 
 
 
 
+pub fn encode(input: &[char]) -> Option<~[Ascii]> {
+    let mut output = ~[];
+    for &c in input.iter() {
+        if c.is_ascii() {
+            output.push(unsafe { c.to_ascii_nocheck() })
+        }
+    }
+    let b = output.len() as u32;
+    if b > 0 {
+        output.push('-'.to_ascii())
+    }
+    let mut n = INITIAL_N;
+    let mut delta = 0;
+    let mut bias = INITIAL_BIAS;
+    let mut h = b;
+    let input_length = input.len() as u32;
+    while h < input_length {
+        let m = input.iter().map(|&c| c as u32).filter(|&c| c >= n).min().unwrap();
+        if m - n > (u32::max_value - delta) / (h + 1) {
+            return None  // Overflow
+        }
+        delta += (m - n) * (h + 1);
+        n = m;
+        for &c in input.iter() {
+            let c = c as u32;
+            if c < n {
+                delta += 1;
+                if delta == 0 {
+                    return None  // Overflow
+                }
+            }
+            if c == n {
+                let mut q = delta;
+                let mut k = BASE;
+                loop {
+                    let t = if k <= bias { T_MIN }
+                            else if k >= bias + T_MAX { T_MAX }
+                            else { k - bias };
+                    if q < t {
+                        break
+                    }
+                    let value = t + ((q - t) % (BASE - t));
+                    output.push(value_to_digit(value));
+                    q = (q - t) / (BASE - t);
+                    k += BASE;
+                }
+                output.push(value_to_digit(q));
+                bias = adapt(delta, h + 1, h == b);
+                delta = 0;
+                h += 1;
+            }
+        }
+        delta += 1;
+        n += 1;
+    }
+    Some(output)
+}
+
+
+fn value_to_digit(value: u32) -> Ascii {
+    let code_point = match value {
+        0 .. 25 => value + 0x61,  // a..z
+        26 .. 35 => value - 26 + 0x30,  // 0..9
+        _ => fail!()
+    };
+    unsafe { (code_point as u8).to_ascii_nocheck() }
+}
+
+
 #[cfg(test)]
 #[cfg(test)]
 mod tests {
 mod tests {
-    use super::decode;
+    use super::{decode, encode};
     use std::ascii::AsciiCast;
     use std::ascii::AsciiCast;
     use std::str::from_chars;
     use std::str::from_chars;
     use extra::json::{from_str, List, Object, String};
     use extra::json::{from_str, List, Object, String};
 
 
     fn one_test(description: &str, decoded: &str, encoded: &str) {
     fn one_test(description: &str, decoded: &str, encoded: &str) {
-        let result = decode(encoded.to_ascii()).map(|s| from_chars(s));
-        assert!(result == Some(decoded.to_owned()),
-                format!("Decoding {:?} failed:\n  {:?}\n!= {:?}\n{}",
-                        encoded, result.unwrap_or(~"<Failed>"), decoded, description));
+        match decode(encoded.to_ascii()) {
+            None => fail!("Decoding {:?} failed.", encoded),
+            Some(result) => {
+                let result = from_chars(result);
+                assert!(result.as_slice() == decoded,
+                        format!("Incorrect decoding of {:?}:\n   {:?}\n!= {:?}\n{}",
+                                encoded, result.as_slice(), decoded, description))
+            }
+        }
+
+        match encode(decoded.iter().to_owned_vec()) {
+            None => fail!("Encoding {:?} failed.", decoded),
+            Some(result) => {
+                let result = result.to_str_ascii();
+                assert!(result.as_slice() == encoded,
+                        format!("Incorrect encoding of {:?}:\n   {:?}\n!= {:?}\n{}",
+                                decoded, result.as_slice(), encoded, description))
+            }
+        }
     }
     }
 
 
     fn get_string<'a>(map: &'a ~Object, key: &~str) -> &'a str {
     fn get_string<'a>(map: &'a ~Object, key: &~str) -> &'a str {