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

Add UrlParser to set optional parameter through method chaining.

Simon Sapin 12 лет назад
Родитель
Сommit
0320fb55c6
3 измененных файлов с 153 добавлено и 120 удалено
  1. 89 102
      src/parser.rs
  2. 3 3
      src/tests.rs
  3. 61 15
      src/url.rs

+ 89 - 102
src/parser.rs

@@ -11,10 +11,9 @@ use std::ascii::StrAsciiExt;
 use std::str::CharRange;
 use std::str::CharRange;
 
 
 use encoding;
 use encoding;
-use encoding::EncodingRef;
 
 
 use super::{
 use super::{
-    ParseResult, ErrorHandler, Url, RelativeSchemeData, OtherSchemeData, Host, Domain,
+    ParseResult, UrlParser, Url, RelativeSchemeData, OtherSchemeData, Host, Domain,
     utf8_percent_encode, percent_encode};
     utf8_percent_encode, percent_encode};
 use encode_sets::{SIMPLE_ENCODE_SET, DEFAULT_ENCODE_SET, USERINFO_ENCODE_SET, QUERY_ENCODE_SET};
 use encode_sets::{SIMPLE_ENCODE_SET, DEFAULT_ENCODE_SET, USERINFO_ENCODE_SET, QUERY_ENCODE_SET};
 
 
@@ -25,6 +24,12 @@ macro_rules! is_match(
     );
     );
 )
 )
 
 
+macro_rules! parse_error(
+    ($parser: expr, $message: expr) => (
+        try!(($parser.parse_error)($message))
+    );
+)
+
 #[deriving(PartialEq, Eq)]
 #[deriving(PartialEq, Eq)]
 pub enum Context {
 pub enum Context {
     UrlParserContext,
     UrlParserContext,
@@ -38,18 +43,15 @@ pub enum SchemeType {
 }
 }
 
 
 
 
-pub fn parse_url(input: &str, base_url: Option<&Url>, encoding_override: Option<EncodingRef>,
-                 parse_error: ErrorHandler)
-                 -> ParseResult<Url> {
+pub fn parse_url(input: &str, parser: &UrlParser) -> ParseResult<Url> {
     let input = input.trim_chars(&[' ', '\t', '\n', '\r', '\x0C']);
     let input = input.trim_chars(&[' ', '\t', '\n', '\r', '\x0C']);
     let (scheme, remaining) = match parse_scheme(input, UrlParserContext) {
     let (scheme, remaining) = match parse_scheme(input, UrlParserContext) {
         Some((scheme, remaining)) => (scheme, remaining),
         Some((scheme, remaining)) => (scheme, remaining),
         // No-scheme state
         // No-scheme state
-        None => return match base_url {
+        None => return match parser.base_url {
             Some(&Url { ref scheme, scheme_data: RelativeSchemeData(ref base),
             Some(&Url { ref scheme, scheme_data: RelativeSchemeData(ref base),
                         ref query, .. }) => {
                         ref query, .. }) => {
-                parse_relative_url(input, scheme.clone(), base, query,
-                                   encoding_override, parse_error)
+                parse_relative_url(input, scheme.clone(), base, query, parser)
             },
             },
             Some(_) => Err("Relative URL with a non-relative base"),
             Some(_) => Err("Relative URL with a non-relative base"),
             None => Err("Relative URL without a base"),
             None => Err("Relative URL without a base"),
@@ -57,35 +59,34 @@ pub fn parse_url(input: &str, base_url: Option<&Url>, encoding_override: Option<
     };
     };
     if scheme.as_slice() == "file" {
     if scheme.as_slice() == "file" {
         // Relative state?
         // Relative state?
-        match base_url {
+        match parser.base_url {
             Some(&Url { scheme: ref base_scheme, scheme_data: RelativeSchemeData(ref base),
             Some(&Url { scheme: ref base_scheme, scheme_data: RelativeSchemeData(ref base),
                         ref query, .. })
                         ref query, .. })
             if scheme == *base_scheme => {
             if scheme == *base_scheme => {
-                parse_relative_url(remaining, scheme, base, query, encoding_override, parse_error)
+                parse_relative_url(remaining, scheme, base, query, parser)
             },
             },
             // FIXME: Should not have to use a made-up base URL.
             // FIXME: Should not have to use a made-up base URL.
             _ => parse_relative_url(remaining, scheme, &RelativeSchemeData {
             _ => parse_relative_url(remaining, scheme, &RelativeSchemeData {
                 username: String::new(), password: None, host: Domain(String::new()),
                 username: String::new(), password: None, host: Domain(String::new()),
                 port: String::new(), path: Vec::new()
                 port: String::new(), path: Vec::new()
-            }, &None, encoding_override, parse_error)
+            }, &None, parser)
         }
         }
     } else if is_relative_scheme(scheme.as_slice()) {
     } else if is_relative_scheme(scheme.as_slice()) {
-        match base_url {
+        match parser.base_url {
             Some(&Url { scheme: ref base_scheme, scheme_data: RelativeSchemeData(ref base),
             Some(&Url { scheme: ref base_scheme, scheme_data: RelativeSchemeData(ref base),
                         ref query, .. })
                         ref query, .. })
             if scheme == *base_scheme && !remaining.starts_with("//") => {
             if scheme == *base_scheme && !remaining.starts_with("//") => {
-                try!(parse_error("Relative URL with a scheme"));
-                parse_relative_url(remaining, scheme, base, query, encoding_override, parse_error)
+                parse_error!(parser, "Relative URL with a scheme");
+                parse_relative_url(remaining, scheme, base, query, parser)
             },
             },
-            _ => parse_absolute_url(scheme, remaining, encoding_override, parse_error),
+            _ => parse_absolute_url(scheme, remaining, parser),
         }
         }
     } else {
     } else {
         // Scheme data state
         // Scheme data state
-        let (scheme_data, remaining) = try!(parse_scheme_data(remaining, parse_error));
-        let (query, fragment) = try!(parse_query_and_fragment(
-            remaining, encoding_override, parse_error));
+        let (scheme_data, remaining) = try!(parse_scheme_data(remaining, parser));
+        let (query, fragment) = try!(parse_query_and_fragment(remaining, parser));
         Ok(Url { scheme: scheme, scheme_data: OtherSchemeData(scheme_data),
         Ok(Url { scheme: scheme, scheme_data: OtherSchemeData(scheme_data),
-                 query: query, fragment: fragment, encoding_override: encoding_override })
+                 query: query, fragment: fragment, encoding_override: parser.encoding_override })
     }
     }
 }
 }
 
 
@@ -112,41 +113,37 @@ pub fn parse_scheme<'a>(input: &'a str, context: Context) -> Option<(String, &'a
 }
 }
 
 
 
 
-fn parse_absolute_url<'a>(scheme: String, input: &'a str, encoding_override: Option<EncodingRef>,
-                          parse_error: ErrorHandler)
-                          -> ParseResult<Url> {
+fn parse_absolute_url<'a>(scheme: String, input: &'a str, parser: &UrlParser) -> ParseResult<Url> {
     // Authority first slash state
     // Authority first slash state
-    let remaining = try!(skip_slashes(input, parse_error));
+    let remaining = try!(skip_slashes(input, parser));
     // Authority state
     // Authority state
-    let (username, password, remaining) = try!(parse_userinfo(remaining, parse_error));
+    let (username, password, remaining) = try!(parse_userinfo(remaining, parser));
     // Host state
     // Host state
-    let (host, port, remaining) = try!(parse_host(remaining, scheme.as_slice(), parse_error));
+    let (host, port, remaining) = try!(parse_host(remaining, scheme.as_slice(), parser));
     let (path, remaining) = try!(parse_path_start(
     let (path, remaining) = try!(parse_path_start(
-        remaining, UrlParserContext, NonFileScheme, parse_error));
+        remaining, UrlParserContext, NonFileScheme, parser));
     let scheme_data = RelativeSchemeData(RelativeSchemeData {
     let scheme_data = RelativeSchemeData(RelativeSchemeData {
         username: username, password: password, host: host, port: port, path: path });
         username: username, password: password, host: host, port: port, path: path });
-    let (query, fragment) = try!(parse_query_and_fragment(
-        remaining, encoding_override, parse_error));
+    let (query, fragment) = try!(parse_query_and_fragment(remaining, parser));
     Ok(Url { scheme: scheme, scheme_data: scheme_data, query: query, fragment: fragment,
     Ok(Url { scheme: scheme, scheme_data: scheme_data, query: query, fragment: fragment,
-             encoding_override: encoding_override })
+             encoding_override: parser.encoding_override })
 }
 }
 
 
 
 
 fn parse_relative_url<'a>(input: &'a str, scheme: String, base: &RelativeSchemeData,
 fn parse_relative_url<'a>(input: &'a str, scheme: String, base: &RelativeSchemeData,
-                          base_query: &Option<String>, encoding_override: Option<EncodingRef>,
-                          parse_error: ErrorHandler)
+                          base_query: &Option<String>, parser: &UrlParser)
                           -> ParseResult<Url> {
                           -> ParseResult<Url> {
     if input.is_empty() {
     if input.is_empty() {
         return Ok(Url { scheme: scheme, scheme_data: RelativeSchemeData(base.clone()),
         return Ok(Url { scheme: scheme, scheme_data: RelativeSchemeData(base.clone()),
                         query: base_query.clone(), fragment: None,
                         query: base_query.clone(), fragment: None,
-                        encoding_override: encoding_override })
+                        encoding_override: parser.encoding_override })
     }
     }
     let scheme_type = if scheme.as_slice() == "file" { FileScheme } else { NonFileScheme };
     let scheme_type = if scheme.as_slice() == "file" { FileScheme } else { NonFileScheme };
     match input.char_at(0) {
     match input.char_at(0) {
         '/' | '\\' => {
         '/' | '\\' => {
             // Relative slash state
             // Relative slash state
             if input.len() > 1 && is_match!(input.char_at(1), '/' | '\\') {
             if input.len() > 1 && is_match!(input.char_at(1), '/' | '\\') {
-                if input.char_at(1) == '\\' { try!(parse_error("backslash")) }
+                if input.char_at(1) == '\\' { parse_error!(parser, "backslash") }
                 if scheme_type == FileScheme {
                 if scheme_type == FileScheme {
                     // File host state
                     // File host state
                     let remaining = input.slice_from(2);
                     let remaining = input.slice_from(2);
@@ -160,28 +157,25 @@ fn parse_relative_url<'a>(input: &'a str, scheme: String, base: &RelativeSchemeD
                         // Windows drive letter quirk
                         // Windows drive letter quirk
                         (Domain(String::new()), remaining)
                         (Domain(String::new()), remaining)
                     } else {
                     } else {
-                        try!(parse_file_host(remaining, parse_error))
+                        try!(parse_file_host(remaining, parser))
                     };
                     };
                     let (path, remaining) = try!(parse_path_start(
                     let (path, remaining) = try!(parse_path_start(
-                        remaining, UrlParserContext,
-                        scheme_type, parse_error));
+                        remaining, UrlParserContext, scheme_type, parser));
                     let scheme_data = RelativeSchemeData(RelativeSchemeData {
                     let scheme_data = RelativeSchemeData(RelativeSchemeData {
                         username: String::new(), password: None,
                         username: String::new(), password: None,
                         host: host, port: String::new(), path: path
                         host: host, port: String::new(), path: path
                     });
                     });
-                    let (query, fragment) = try!(parse_query_and_fragment(
-                        remaining, encoding_override, parse_error));
+                    let (query, fragment) = try!(parse_query_and_fragment(remaining, parser));
                     Ok(Url { scheme: scheme, scheme_data: scheme_data,
                     Ok(Url { scheme: scheme, scheme_data: scheme_data,
                              query: query, fragment: fragment,
                              query: query, fragment: fragment,
-                             encoding_override: encoding_override })
+                             encoding_override: parser.encoding_override })
                 } else {
                 } else {
-                    parse_absolute_url(scheme, input, encoding_override, parse_error)
+                    parse_absolute_url(scheme, input, parser)
                 }
                 }
             } else {
             } else {
                 // Relative path state
                 // Relative path state
                 let (path, remaining) = try!(parse_path(
                 let (path, remaining) = try!(parse_path(
-                    [], input.slice_from(1), UrlParserContext,
-                    scheme_type, parse_error));
+                    [], input.slice_from(1), UrlParserContext, scheme_type, parser));
                 let scheme_data = RelativeSchemeData(if scheme_type == FileScheme {
                 let scheme_data = RelativeSchemeData(if scheme_type == FileScheme {
                     RelativeSchemeData {
                     RelativeSchemeData {
                         username: String::new(), password: None, host:
                         username: String::new(), password: None, host:
@@ -197,25 +191,23 @@ fn parse_relative_url<'a>(input: &'a str, scheme: String, base: &RelativeSchemeD
                     }
                     }
                 });
                 });
                 let (query, fragment) = try!(
                 let (query, fragment) = try!(
-                    parse_query_and_fragment(
-                        remaining, encoding_override, parse_error));
+                    parse_query_and_fragment(remaining, parser));
                 Ok(Url { scheme: scheme, scheme_data: scheme_data,
                 Ok(Url { scheme: scheme, scheme_data: scheme_data,
                          query: query, fragment: fragment,
                          query: query, fragment: fragment,
-                         encoding_override: encoding_override })
+                         encoding_override: parser.encoding_override })
             }
             }
         },
         },
         '?' => {
         '?' => {
-            let (query, fragment) = try!(parse_query_and_fragment(
-                input, encoding_override, parse_error));
+            let (query, fragment) = try!(parse_query_and_fragment(input, parser));
             Ok(Url { scheme: scheme, scheme_data: RelativeSchemeData(base.clone()),
             Ok(Url { scheme: scheme, scheme_data: RelativeSchemeData(base.clone()),
                      query: query, fragment: fragment,
                      query: query, fragment: fragment,
-                     encoding_override: encoding_override })
+                     encoding_override: parser.encoding_override })
         },
         },
         '#' => {
         '#' => {
-            let fragment = Some(try!(parse_fragment(input.slice_from(1), parse_error)));
+            let fragment = Some(try!(parse_fragment(input.slice_from(1), parser)));
             Ok(Url { scheme: scheme, scheme_data: RelativeSchemeData(base.clone()),
             Ok(Url { scheme: scheme, scheme_data: RelativeSchemeData(base.clone()),
                      query: base_query.clone(), fragment: fragment,
                      query: base_query.clone(), fragment: fragment,
-                     encoding_override: encoding_override })
+                     encoding_override: parser.encoding_override })
         }
         }
         _ => {
         _ => {
             let (scheme_data, remaining) = if scheme_type == FileScheme
             let (scheme_data, remaining) = if scheme_type == FileScheme
@@ -227,8 +219,7 @@ fn parse_relative_url<'a>(input: &'a str, scheme: String, base: &RelativeSchemeD
             {
             {
                 // Windows drive letter quirk
                 // Windows drive letter quirk
                 let (path, remaining) = try!(parse_path(
                 let (path, remaining) = try!(parse_path(
-                    [], input, UrlParserContext,
-                    scheme_type, parse_error));
+                    [], input, UrlParserContext, scheme_type, parser));
                  (RelativeSchemeData(RelativeSchemeData {
                  (RelativeSchemeData(RelativeSchemeData {
                     username: String::new(), password: None,
                     username: String::new(), password: None,
                     host: Domain(String::new()),
                     host: Domain(String::new()),
@@ -239,8 +230,7 @@ fn parse_relative_url<'a>(input: &'a str, scheme: String, base: &RelativeSchemeD
                 let base_path = base.path.slice_to(base.path.len() - 1);
                 let base_path = base.path.slice_to(base.path.len() - 1);
                 // Relative path state
                 // Relative path state
                 let (path, remaining) = try!(parse_path(
                 let (path, remaining) = try!(parse_path(
-                    base_path, input, UrlParserContext,
-                    scheme_type, parse_error));
+                    base_path, input, UrlParserContext, scheme_type, parser));
                 (RelativeSchemeData(RelativeSchemeData {
                 (RelativeSchemeData(RelativeSchemeData {
                     username: base.username.clone(),
                     username: base.username.clone(),
                     password: base.password.clone(),
                     password: base.password.clone(),
@@ -249,32 +239,31 @@ fn parse_relative_url<'a>(input: &'a str, scheme: String, base: &RelativeSchemeD
                     path: path
                     path: path
                 }), remaining)
                 }), remaining)
             };
             };
-            let (query, fragment) = try!(parse_query_and_fragment(
-                remaining, encoding_override, parse_error));
+            let (query, fragment) = try!(parse_query_and_fragment(remaining, parser));
             Ok(Url { scheme: scheme, scheme_data: scheme_data,
             Ok(Url { scheme: scheme, scheme_data: scheme_data,
                      query: query, fragment: fragment,
                      query: query, fragment: fragment,
-                     encoding_override: encoding_override })
+                     encoding_override: parser.encoding_override })
         }
         }
     }
     }
 }
 }
 
 
 
 
-fn skip_slashes<'a>(input: &'a str, parse_error: ErrorHandler) -> ParseResult<&'a str> {
+fn skip_slashes<'a>(input: &'a str, parser: &UrlParser) -> ParseResult<&'a str> {
     let first_non_slash = input.find(|c| !is_match!(c, '/' | '\\')).unwrap_or(input.len());
     let first_non_slash = input.find(|c| !is_match!(c, '/' | '\\')).unwrap_or(input.len());
     if input.slice_to(first_non_slash) != "//" {
     if input.slice_to(first_non_slash) != "//" {
-        try!(parse_error("Expected two slashes"));
+        parse_error!(parser, "Expected two slashes");
     }
     }
     Ok(input.slice_from(first_non_slash))
     Ok(input.slice_from(first_non_slash))
 }
 }
 
 
 
 
-fn parse_userinfo<'a>(input: &'a str, parse_error: ErrorHandler)
+fn parse_userinfo<'a>(input: &'a str, parser: &UrlParser)
                       -> ParseResult<(String, Option<String>, &'a str)> {
                       -> ParseResult<(String, Option<String>, &'a str)> {
     let mut last_at = None;
     let mut last_at = None;
     for (i, c) in input.char_indices() {
     for (i, c) in input.char_indices() {
         match c {
         match c {
             '@' => {
             '@' => {
-                if last_at.is_some() { try!(parse_error("@ in userinfo")) }
+                if last_at.is_some() { parse_error!(parser, "@ in userinfo") }
                 last_at = Some(i)
                 last_at = Some(i)
             },
             },
             '/' | '\\' | '?' | '#' => break,
             '/' | '\\' | '?' | '#' => break,
@@ -291,12 +280,12 @@ fn parse_userinfo<'a>(input: &'a str, parse_error: ErrorHandler)
     for (i, c, next_i) in input.char_ranges() {
     for (i, c, next_i) in input.char_ranges() {
         match c {
         match c {
             ':' => {
             ':' => {
-                password = Some(try!(parse_password(input.slice_from(i + 1), parse_error)));
+                password = Some(try!(parse_password(input.slice_from(i + 1), parser)));
                 break
                 break
             },
             },
-            '\t' | '\n' | '\r' => try!(parse_error("Invalid character")),
+            '\t' | '\n' | '\r' => parse_error!(parser, "Invalid character"),
             _ => {
             _ => {
-                try!(check_url_code_point(input, i, c, parse_error));
+                try!(check_url_code_point(input, i, c, parser));
                 // The spec says to use the default encode set,
                 // The spec says to use the default encode set,
                 // but also replaces '@' by '%40' in an earlier step.
                 // but also replaces '@' by '%40' in an earlier step.
                 utf8_percent_encode(input.slice(i, next_i),
                 utf8_percent_encode(input.slice(i, next_i),
@@ -308,13 +297,13 @@ fn parse_userinfo<'a>(input: &'a str, parse_error: ErrorHandler)
 }
 }
 
 
 
 
-fn parse_password(input: &str, parse_error: ErrorHandler) -> ParseResult<String> {
+fn parse_password(input: &str, parser: &UrlParser) -> ParseResult<String> {
     let mut password = String::new();
     let mut password = String::new();
     for (i, c, next_i) in input.char_ranges() {
     for (i, c, next_i) in input.char_ranges() {
         match c {
         match c {
-            '\t' | '\n' | '\r' => try!(parse_error("Invalid character")),
+            '\t' | '\n' | '\r' => parse_error!(parser, "Invalid character"),
             _ => {
             _ => {
-                try!(check_url_code_point(input, i, c, parse_error));
+                try!(check_url_code_point(input, i, c, parser));
                 // The spec says to use the default encode set,
                 // The spec says to use the default encode set,
                 // but also replaces '@' by '%40' in an earlier step.
                 // but also replaces '@' by '%40' in an earlier step.
                 utf8_percent_encode(input.slice(i, next_i),
                 utf8_percent_encode(input.slice(i, next_i),
@@ -326,11 +315,11 @@ fn parse_password(input: &str, parse_error: ErrorHandler) -> ParseResult<String>
 }
 }
 
 
 
 
-pub fn parse_host<'a>(input: &'a str, scheme: &str, parse_error: ErrorHandler)
+pub fn parse_host<'a>(input: &'a str, scheme: &str, parser: &UrlParser)
                           -> ParseResult<(Host, String, &'a str)> {
                           -> ParseResult<(Host, String, &'a str)> {
-    let (host, remaining) = try!(parse_hostname(input, parse_error));
+    let (host, remaining) = try!(parse_hostname(input, parser));
     let (port, remaining) = if remaining.starts_with(":") {
     let (port, remaining) = if remaining.starts_with(":") {
-        try!(parse_port(remaining.slice_from(1), scheme, parse_error))
+        try!(parse_port(remaining.slice_from(1), scheme, parser))
     } else {
     } else {
         (String::new(), remaining)
         (String::new(), remaining)
     };
     };
@@ -338,7 +327,7 @@ pub fn parse_host<'a>(input: &'a str, scheme: &str, parse_error: ErrorHandler)
 }
 }
 
 
 
 
-pub fn parse_hostname<'a>(input: &'a str, parse_error: ErrorHandler)
+pub fn parse_hostname<'a>(input: &'a str, parser: &UrlParser)
                       -> ParseResult<(Host, &'a str)> {
                       -> ParseResult<(Host, &'a str)> {
     let mut inside_square_brackets = false;
     let mut inside_square_brackets = false;
     let mut host_input = String::new();
     let mut host_input = String::new();
@@ -353,7 +342,7 @@ pub fn parse_hostname<'a>(input: &'a str, parse_error: ErrorHandler)
                 end = i;
                 end = i;
                 break
                 break
             },
             },
-            '\t' | '\n' | '\r' => try!(parse_error("Invalid character")),
+            '\t' | '\n' | '\r' => parse_error!(parser, "Invalid character"),
             c => {
             c => {
                 match c {
                 match c {
                     '[' => inside_square_brackets = true,
                     '[' => inside_square_brackets = true,
@@ -369,7 +358,7 @@ pub fn parse_hostname<'a>(input: &'a str, parse_error: ErrorHandler)
 }
 }
 
 
 
 
-pub fn parse_port<'a>(input: &'a str, scheme: &str, parse_error: ErrorHandler)
+pub fn parse_port<'a>(input: &'a str, scheme: &str, parser: &UrlParser)
                   -> ParseResult<(String, &'a str)> {
                   -> ParseResult<(String, &'a str)> {
     let mut port = String::new();
     let mut port = String::new();
     let mut has_initial_zero = false;
     let mut has_initial_zero = false;
@@ -388,7 +377,7 @@ pub fn parse_port<'a>(input: &'a str, scheme: &str, parse_error: ErrorHandler)
                 end = i;
                 end = i;
                 break
                 break
             },
             },
-            '\t' | '\n' | '\r' => try!(parse_error("Invalid character")),
+            '\t' | '\n' | '\r' => parse_error!(parser, "Invalid character"),
             _ => return Err("Invalid port number")
             _ => return Err("Invalid port number")
         }
         }
     }
     }
@@ -405,7 +394,7 @@ pub fn parse_port<'a>(input: &'a str, scheme: &str, parse_error: ErrorHandler)
 }
 }
 
 
 
 
-fn parse_file_host<'a>(input: &'a str, parse_error: ErrorHandler) -> ParseResult<(Host, &'a str)> {
+fn parse_file_host<'a>(input: &'a str, parser: &UrlParser) -> ParseResult<(Host, &'a str)> {
     let mut host_input = String::new();
     let mut host_input = String::new();
     let mut end = input.len();
     let mut end = input.len();
     for (i, c) in input.char_indices() {
     for (i, c) in input.char_indices() {
@@ -414,7 +403,7 @@ fn parse_file_host<'a>(input: &'a str, parse_error: ErrorHandler) -> ParseResult
                 end = i;
                 end = i;
                 break
                 break
             },
             },
-            '\t' | '\n' | '\r' => try!(parse_error("Invalid character")),
+            '\t' | '\n' | '\r' => parse_error!(parser, "Invalid character"),
             _ => host_input.push_char(c)
             _ => host_input.push_char(c)
         }
         }
     }
     }
@@ -428,7 +417,7 @@ fn parse_file_host<'a>(input: &'a str, parse_error: ErrorHandler) -> ParseResult
 
 
 
 
 pub fn parse_path_start<'a>(input: &'a str, context: Context, scheme_type: SchemeType,
 pub fn parse_path_start<'a>(input: &'a str, context: Context, scheme_type: SchemeType,
-                            parse_error: ErrorHandler)
+                            parser: &UrlParser)
                             -> ParseResult<(Vec<String>, &'a str)> {
                             -> ParseResult<(Vec<String>, &'a str)> {
     let mut i = 0;
     let mut i = 0;
     // Relative path start state
     // Relative path start state
@@ -436,18 +425,18 @@ pub fn parse_path_start<'a>(input: &'a str, context: Context, scheme_type: Schem
         match input.char_at(0) {
         match input.char_at(0) {
             '/' => i = 1,
             '/' => i = 1,
             '\\' => {
             '\\' => {
-                try!(parse_error("Backslash"));
+                parse_error!(parser, "Backslash");
                 i = 1;
                 i = 1;
             },
             },
             _ => ()
             _ => ()
         }
         }
     }
     }
-    parse_path([], input.slice_from(i), context, scheme_type, parse_error)
+    parse_path([], input.slice_from(i), context, scheme_type, parser)
 }
 }
 
 
 
 
 fn parse_path<'a>(base_path: &[String], input: &'a str, context: Context,
 fn parse_path<'a>(base_path: &[String], input: &'a str, context: Context,
-                  scheme_type: SchemeType, parse_error: ErrorHandler)
+                  scheme_type: SchemeType, parser: &UrlParser)
                   -> ParseResult<(Vec<String>, &'a str)> {
                   -> ParseResult<(Vec<String>, &'a str)> {
     // Relative path state
     // Relative path state
     let mut path = base_path.to_owned();
     let mut path = base_path.to_owned();
@@ -465,7 +454,7 @@ fn parse_path<'a>(base_path: &[String], input: &'a str, context: Context,
                     break
                     break
                 },
                 },
                 '\\' => {
                 '\\' => {
-                    try!(parse_error("Backslash"));
+                    parse_error!(parser, "Backslash");
                     ends_with_slash = true;
                     ends_with_slash = true;
                     end = i;
                     end = i;
                     break
                     break
@@ -474,9 +463,9 @@ fn parse_path<'a>(base_path: &[String], input: &'a str, context: Context,
                     end = i;
                     end = i;
                     break
                     break
                 },
                 },
-                '\t' | '\n' | '\r' => try!(parse_error("Invalid character")),
+                '\t' | '\n' | '\r' => parse_error!(parser, "Invalid character"),
                 _ => {
                 _ => {
-                    try!(check_url_code_point(input, i, c, parse_error));
+                    try!(check_url_code_point(input, i, c, parser));
                     utf8_percent_encode(input.slice(i, next_i),
                     utf8_percent_encode(input.slice(i, next_i),
                                         DEFAULT_ENCODE_SET, &mut path_part);
                                         DEFAULT_ENCODE_SET, &mut path_part);
                 }
                 }
@@ -517,7 +506,7 @@ fn parse_path<'a>(base_path: &[String], input: &'a str, context: Context,
 }
 }
 
 
 
 
-fn parse_scheme_data<'a>(input: &'a str, parse_error: ErrorHandler)
+fn parse_scheme_data<'a>(input: &'a str, parser: &UrlParser)
                          -> ParseResult<(String, &'a str)> {
                          -> ParseResult<(String, &'a str)> {
     let mut scheme_data = String::new();
     let mut scheme_data = String::new();
     let mut end = input.len();
     let mut end = input.len();
@@ -527,9 +516,9 @@ fn parse_scheme_data<'a>(input: &'a str, parse_error: ErrorHandler)
                 end = i;
                 end = i;
                 break
                 break
             },
             },
-            '\t' | '\n' | '\r' => try!(parse_error("Invalid character")),
+            '\t' | '\n' | '\r' => parse_error!(parser, "Invalid character"),
             _ => {
             _ => {
-                try!(check_url_code_point(input, i, c, parse_error));
+                try!(check_url_code_point(input, i, c, parser));
                 utf8_percent_encode(input.slice(i, next_i),
                 utf8_percent_encode(input.slice(i, next_i),
                                     SIMPLE_ENCODE_SET, &mut scheme_data);
                                     SIMPLE_ENCODE_SET, &mut scheme_data);
             }
             }
@@ -539,19 +528,18 @@ fn parse_scheme_data<'a>(input: &'a str, parse_error: ErrorHandler)
 }
 }
 
 
 
 
-fn parse_query_and_fragment(input: &str, encoding_override: Option<EncodingRef>,
-                            parse_error: ErrorHandler)
+fn parse_query_and_fragment(input: &str, parser: &UrlParser)
                             -> ParseResult<(Option<String>, Option<String>)> {
                             -> ParseResult<(Option<String>, Option<String>)> {
     if input.is_empty() {
     if input.is_empty() {
         return Ok((None, None))
         return Ok((None, None))
     }
     }
     match input.char_at(0) {
     match input.char_at(0) {
-        '#' => Ok((None, Some(try!(parse_fragment(input.slice_from(1), parse_error))))),
+        '#' => Ok((None, Some(try!(parse_fragment(input.slice_from(1), parser))))),
         '?' => {
         '?' => {
             let (query, remaining) = try!(parse_query(
             let (query, remaining) = try!(parse_query(
-                input.slice_from(1), encoding_override, UrlParserContext, parse_error));
+                input.slice_from(1), UrlParserContext, parser));
             let fragment = match remaining {
             let fragment = match remaining {
-                Some(remaining) => Some(try!(parse_fragment(remaining, parse_error))),
+                Some(remaining) => Some(try!(parse_fragment(remaining, parser))),
                 None => None
                 None => None
             };
             };
             Ok((Some(query), fragment))
             Ok((Some(query), fragment))
@@ -562,8 +550,7 @@ fn parse_query_and_fragment(input: &str, encoding_override: Option<EncodingRef>,
 }
 }
 
 
 
 
-pub fn parse_query<'a>(input: &'a str, encoding_override: Option<EncodingRef>, context: Context,
-                   parse_error: ErrorHandler)
+pub fn parse_query<'a>(input: &'a str, context: Context, parser: &UrlParser)
                    -> ParseResult<(String, Option<&'a str>)> {
                    -> ParseResult<(String, Option<&'a str>)> {
     let mut query = String::new();
     let mut query = String::new();
     let mut remaining = None;
     let mut remaining = None;
@@ -573,15 +560,15 @@ pub fn parse_query<'a>(input: &'a str, encoding_override: Option<EncodingRef>, c
                 remaining = Some(input.slice_from(i + 1));
                 remaining = Some(input.slice_from(i + 1));
                 break
                 break
             },
             },
-            '\t' | '\n' | '\r' => try!(parse_error("Invalid character")),
+            '\t' | '\n' | '\r' => parse_error!(parser, "Invalid character"),
             _ => {
             _ => {
-                try!(check_url_code_point(input, i, c, parse_error));
+                try!(check_url_code_point(input, i, c, parser));
                 query.push_char(c);
                 query.push_char(c);
             }
             }
         }
         }
     }
     }
     let encoded;
     let encoded;
-    let query_bytes = match encoding_override {
+    let query_bytes = match parser.encoding_override {
         Some(encoding) => {
         Some(encoding) => {
             encoded = encoding.encode(query.as_slice(), encoding::EncodeReplace).unwrap();
             encoded = encoding.encode(query.as_slice(), encoding::EncodeReplace).unwrap();
             encoded.as_slice()
             encoded.as_slice()
@@ -594,13 +581,13 @@ pub fn parse_query<'a>(input: &'a str, encoding_override: Option<EncodingRef>, c
 }
 }
 
 
 
 
-pub fn parse_fragment<'a>(input: &'a str, parse_error: ErrorHandler) -> ParseResult<String> {
+pub fn parse_fragment<'a>(input: &'a str, parser: &UrlParser) -> ParseResult<String> {
     let mut fragment = String::new();
     let mut fragment = String::new();
     for (i, c, next_i) in input.char_ranges() {
     for (i, c, next_i) in input.char_ranges() {
         match c {
         match c {
-            '\t' | '\n' | '\r' => try!(parse_error("Invalid character")),
+            '\t' | '\n' | '\r' => parse_error!(parser, "Invalid character"),
             _ => {
             _ => {
-                try!(check_url_code_point(input, i, c, parse_error));
+                try!(check_url_code_point(input, i, c, parser));
                 utf8_percent_encode(input.slice(i, next_i),
                 utf8_percent_encode(input.slice(i, next_i),
                                     SIMPLE_ENCODE_SET, &mut fragment);
                                     SIMPLE_ENCODE_SET, &mut fragment);
             }
             }
@@ -701,14 +688,14 @@ impl<'a> Iterator<(uint, char, uint)> for CharRanges<'a> {
 }
 }
 
 
 #[inline]
 #[inline]
-fn check_url_code_point(input: &str, i: uint, c: char, parse_error: ErrorHandler)
+fn check_url_code_point(input: &str, i: uint, c: char, parser: &UrlParser)
                         -> ParseResult<()> {
                         -> ParseResult<()> {
     if c == '%' {
     if c == '%' {
         if !starts_with_2_hex(input.slice_from(i + 1)) {
         if !starts_with_2_hex(input.slice_from(i + 1)) {
-            try!(parse_error("Invalid percent-encoded sequence"));
+            parse_error!(parser, "Invalid percent-encoded sequence");
         }
         }
     } else if !is_url_code_point(c) {
     } else if !is_url_code_point(c) {
-        try!(parse_error("Non-URL code point"));
+        parse_error!(parser, "Non-URL code point");
     }
     }
     Ok(())
     Ok(())
 }
 }

+ 3 - 3
src/tests.rs

@@ -9,7 +9,7 @@
 
 
 use std::char;
 use std::char;
 use std::u32;
 use std::u32;
-use super::{Url, RelativeSchemeData, OtherSchemeData};
+use super::{UrlParser, Url, RelativeSchemeData, OtherSchemeData};
 
 
 
 
 #[test]
 #[test]
@@ -28,11 +28,11 @@ fn test_url_parsing() {
             fragment: expected_fragment,
             fragment: expected_fragment,
             expected_failure: expected_failure,
             expected_failure: expected_failure,
         } = test;
         } = test;
-        let base = match Url::parse(base.as_slice(), None) {
+        let base = match Url::parse(base.as_slice()) {
             Ok(base) => base,
             Ok(base) => base,
             Err(message) => fail!("Error parsing base {}: {}", base, message)
             Err(message) => fail!("Error parsing base {}: {}", base, message)
         };
         };
-        let url = Url::parse(input.as_slice(), Some(&base));
+        let url = UrlParser::new().base_url(&base).parse(input.as_slice());
         if expected_scheme.is_none() {
         if expected_scheme.is_none() {
             if url.is_ok() && !expected_failure {
             if url.is_ok() && !expected_failure {
                 fail!("Expected a parse error for URL {}", input);
                 fail!("Expected a parse error for URL {}", input);

+ 61 - 15
src/url.rs

@@ -111,11 +111,47 @@ impl<S: hash::Writer> hash::Hash<S> for Url {
     }
     }
 }
 }
 
 
-macro_rules! is_match(
-    ($value:expr, $($pattern:pat)|+) => (
-        match $value { $($pattern)|+ => true, _ => false }
-    );
-)
+
+pub struct UrlParser<'a> {
+    base_url: Option<&'a Url>,
+    encoding_override: Option<EncodingRef>,
+    parse_error: ErrorHandler,
+}
+
+
+impl<'a> UrlParser<'a> {
+    #[inline]
+    pub fn new() -> UrlParser<'a> {
+        UrlParser {
+            base_url: None,
+            encoding_override: None,
+            parse_error: silent_handler,
+        }
+    }
+
+    #[inline]
+    pub fn base_url<'b>(&'b mut self, value: &'a Url) -> &'b mut UrlParser<'a> {
+        self.base_url = Some(value);
+        self
+    }
+
+    #[inline]
+    pub fn encoding_override<'b>(&'b mut self, value: EncodingRef) -> &'b mut UrlParser<'a> {
+        self.encoding_override = Some(value);
+        self
+    }
+
+    #[inline]
+    pub fn parse_error_handler<'b>(&'b mut self, value: ErrorHandler) -> &'b mut UrlParser<'a> {
+        self.parse_error = value;
+        self
+    }
+
+    #[inline]
+    pub fn parse(&self, input: &str) -> ParseResult<Url> {
+        parser::parse_url(input, self)
+    }
+}
 
 
 
 
 pub type ParseResult<T> = Result<T, &'static str>;
 pub type ParseResult<T> = Result<T, &'static str>;
@@ -131,10 +167,9 @@ fn silent_handler(_reason: &'static str) -> ParseResult<()> {
 
 
 
 
 impl Url {
 impl Url {
-    pub fn parse(input: &str, base_url: Option<&Url>)
-                 -> ParseResult<Url> {
-        let encoding_override = None;
-        parser::parse_url(input, base_url, encoding_override, silent_handler)
+    #[inline]
+    pub fn parse(input: &str) -> ParseResult<Url> {
+        UrlParser::new().parse(input)
     }
     }
 
 
     pub fn serialize(&self) -> String {
     pub fn serialize(&self) -> String {
@@ -194,6 +229,7 @@ impl Url {
         result
         result
     }
     }
 
 
+    #[inline]
     pub fn non_relative_scheme_data<'a>(&'a self) -> Option<&'a str> {
     pub fn non_relative_scheme_data<'a>(&'a self) -> Option<&'a str> {
         match self.scheme_data {
         match self.scheme_data {
             RelativeSchemeData(..) => None,
             RelativeSchemeData(..) => None,
@@ -201,6 +237,7 @@ impl Url {
         }
         }
     }
     }
 
 
+    #[inline]
     pub fn relative_scheme_data<'a>(&'a self) -> Option<&'a RelativeSchemeData> {
     pub fn relative_scheme_data<'a>(&'a self) -> Option<&'a RelativeSchemeData> {
         match self.scheme_data {
         match self.scheme_data {
             RelativeSchemeData(ref scheme_data) => Some(scheme_data),
             RelativeSchemeData(ref scheme_data) => Some(scheme_data),
@@ -208,6 +245,7 @@ impl Url {
         }
         }
     }
     }
 
 
+    #[inline]
     pub fn host<'a>(&'a self) -> Option<&'a Host> {
     pub fn host<'a>(&'a self) -> Option<&'a Host> {
         match self.scheme_data {
         match self.scheme_data {
             RelativeSchemeData(ref scheme_data) => Some(&scheme_data.host),
             RelativeSchemeData(ref scheme_data) => Some(&scheme_data.host),
@@ -215,6 +253,7 @@ impl Url {
         }
         }
     }
     }
 
 
+    #[inline]
     pub fn port<'a>(&'a self) -> Option<&'a str> {
     pub fn port<'a>(&'a self) -> Option<&'a str> {
         match self.scheme_data {
         match self.scheme_data {
             RelativeSchemeData(ref scheme_data) => Some(scheme_data.port.as_slice()),
             RelativeSchemeData(ref scheme_data) => Some(scheme_data.port.as_slice()),
@@ -222,6 +261,7 @@ impl Url {
         }
         }
     }
     }
 
 
+    #[inline]
     pub fn path<'a>(&'a self) -> Option<&'a [String]> {
     pub fn path<'a>(&'a self) -> Option<&'a [String]> {
         match self.scheme_data {
         match self.scheme_data {
             RelativeSchemeData(ref scheme_data) => Some(scheme_data.path.as_slice()),
             RelativeSchemeData(ref scheme_data) => Some(scheme_data.path.as_slice()),
@@ -229,6 +269,7 @@ impl Url {
         }
         }
     }
     }
 
 
+    #[inline]
     pub fn serialize_path(&self) -> Option<String> {
     pub fn serialize_path(&self) -> Option<String> {
         match self.scheme_data {
         match self.scheme_data {
             RelativeSchemeData(ref scheme_data) => Some(scheme_data.serialize_path()),
             RelativeSchemeData(ref scheme_data) => Some(scheme_data.serialize_path()),
@@ -310,7 +351,7 @@ impl UrlUtils for Url {
         match self.scheme_data {
         match self.scheme_data {
             RelativeSchemeData(RelativeSchemeData { ref mut host, ref mut port, .. }) => {
             RelativeSchemeData(RelativeSchemeData { ref mut host, ref mut port, .. }) => {
                 let (new_host, new_port, _) = try!(parser::parse_host(
                 let (new_host, new_port, _) = try!(parser::parse_host(
-                    input, self.scheme.as_slice(), silent_handler));
+                    input, self.scheme.as_slice(), &UrlParser::new()));
                 *host = new_host;
                 *host = new_host;
                 *port = new_port;
                 *port = new_port;
                 Ok(())
                 Ok(())
@@ -324,7 +365,7 @@ impl UrlUtils for Url {
         match self.scheme_data {
         match self.scheme_data {
             RelativeSchemeData(RelativeSchemeData { ref mut host, .. }) => {
             RelativeSchemeData(RelativeSchemeData { ref mut host, .. }) => {
                 let (new_host, _) = try!(parser::parse_hostname(
                 let (new_host, _) = try!(parser::parse_hostname(
-                    input, silent_handler));
+                    input, &UrlParser::new()));
                 *host = new_host;
                 *host = new_host;
                 Ok(())
                 Ok(())
             },
             },
@@ -340,7 +381,7 @@ impl UrlUtils for Url {
                     return Err("Can not set port on file: URL.")
                     return Err("Can not set port on file: URL.")
                 }
                 }
                 let (new_port, _) = try!(parser::parse_port(
                 let (new_port, _) = try!(parser::parse_port(
-                    input, self.scheme.as_slice(), silent_handler));
+                    input, self.scheme.as_slice(), &UrlParser::new()));
                 *port = new_port;
                 *port = new_port;
                 Ok(())
                 Ok(())
             },
             },
@@ -356,7 +397,7 @@ impl UrlUtils for Url {
                     input, parser::SetterContext,
                     input, parser::SetterContext,
                     if self.scheme.as_slice() == "file" { parser::FileScheme }
                     if self.scheme.as_slice() == "file" { parser::FileScheme }
                         else { parser::NonFileScheme },
                         else { parser::NonFileScheme },
-                    silent_handler));
+                    &UrlParser::new()));
                 *path = new_path;
                 *path = new_path;
                 Ok(())
                 Ok(())
             },
             },
@@ -374,9 +415,14 @@ impl UrlUtils for Url {
         self.query = if input.is_empty() {
         self.query = if input.is_empty() {
             None
             None
         } else {
         } else {
+            let mut parser = UrlParser::new();
+            let parser = match self.encoding_override {
+                None => &parser,
+                Some(encoding) => &*parser.encoding_override(encoding),
+            };
             let input = if input.starts_with("?") { input.slice_from(1) } else { input };
             let input = if input.starts_with("?") { input.slice_from(1) } else { input };
             let (new_query, _) = try!(parser::parse_query(
             let (new_query, _) = try!(parser::parse_query(
-                input, self.encoding_override, parser::SetterContext, silent_handler));
+                input, parser::SetterContext, parser));
             Some(new_query)
             Some(new_query)
         };
         };
         Ok(())
         Ok(())
@@ -391,7 +437,7 @@ impl UrlUtils for Url {
             None
             None
         } else {
         } else {
             let input = if input.starts_with("#") { input.slice_from(1) } else { input };
             let input = if input.starts_with("#") { input.slice_from(1) } else { input };
-            Some(try!(parser::parse_fragment(input, silent_handler)))
+            Some(try!(parser::parse_fragment(input, &UrlParser::new())))
         };
         };
         Ok(())
         Ok(())
     }
     }