perf: Improved dec_octet parsing

Removed the list folding method and reverted to a standard
try_parser/parse_this_then method as used in the rest of the parser
This commit is contained in:
2025-09-14 21:28:30 +01:00
parent 2ee6741308
commit 6131aa01e7
4 changed files with 114 additions and 34 deletions

View File

@@ -11,3 +11,4 @@
## v2.0.1 ## v2.0.1
- Improved parsing performance slightly and reduced memory usage up to 50% - Improved parsing performance slightly and reduced memory usage up to 50%
- Significantly improved IPV4 parsing performance

View File

@@ -421,39 +421,73 @@ fn parse_ipv4address(str: String) {
Ok(#(oct1 <> "." <> oct2 <> "." <> oct3 <> "." <> oct4, rest)) Ok(#(oct1 <> "." <> oct2 <> "." <> oct3 <> "." <> oct4, rest))
} }
const octet_matches = [
["2", "5", "012345"],
["2", "01234", "0123456789"],
["1", "0123456789", "0123456789"],
["123456789", "0123456789"],
["0123456789"],
]
// dec-octet = DIGIT ; 0-9 // dec-octet = DIGIT ; 0-9
// / %x31-39 DIGIT ; 10-99 // / %x31-39 DIGIT ; 10-99
// / "1" 2DIGIT ; 100-199 // / "1" 2DIGIT ; 100-199
// / "2" %x30-34 DIGIT ; 200-249 // / "2" %x30-34 DIGIT ; 200-249
// / "25" %x30-35 ; 250-255 // / "25" %x30-35 ; 250-255
fn parse_dec_octet(str: String) -> Result(#(String, String), Nil) { pub fn parse_dec_octet(str: String) -> Result(#(String, String), Nil) {
list.fold_until(octet_matches, Error(Nil), fn(_, chars) { try_parsers(
case [
list.fold_until(chars, #("", str), fn(acc, charset) { parse_this_then(_, [
let #(octet, str) = acc fn(str) {
case string.pop_grapheme(str) { case str {
Error(_) -> Stop(#("", "")) "2" as l <> rest -> Ok(#(l, rest))
Ok(#(char, rest)) -> { _ -> Error(Nil)
case string.contains(charset, char) {
True -> Continue(#(octet <> char, rest))
False -> Stop(#("", ""))
} }
},
fn(str) {
case str {
"5" as l <> rest -> Ok(#(l, rest))
_ -> Error(Nil)
} }
},
fn(str) {
case str {
"0" as l <> rest
| "1" as l <> rest
| "2" as l <> rest
| "3" as l <> rest
| "4" as l <> rest
| "5" as l <> rest -> Ok(#(l, rest))
_ -> Error(Nil)
} }
}) },
{ ]),
#("", _) -> Continue(Error(Nil)) parse_this_then(_, [
#(octet, rest) -> Stop(Ok(#(octet, rest))) fn(str) {
case str {
"2" as l <> rest -> Ok(#(l, rest))
_ -> Error(Nil)
} }
}) },
fn(str) {
case str {
"0" as l <> rest
| "1" as l <> rest
| "2" as l <> rest
| "3" as l <> rest
| "4" as l <> rest -> Ok(#(l, rest))
_ -> Error(Nil)
}
},
parse_digit,
]),
parse_this_then(_, [
fn(str) {
case str {
"1" as l <> rest -> Ok(#(l, rest))
_ -> Error(Nil)
}
},
parse_digit,
parse_digit,
]),
parse_this_then(_, [parse_digit_nz, parse_digit]),
parse_digit,
],
str,
)
} }
// reg-name = *( unreserved / pct-encoded / sub-delims ) // reg-name = *( unreserved / pct-encoded / sub-delims )
@@ -727,6 +761,21 @@ fn parse_digit(str: String) -> Result(#(String, String), Nil) {
} }
} }
fn parse_digit_nz(str: String) -> Result(#(String, String), Nil) {
case str {
"1" as l <> rest
| "2" as l <> rest
| "3" as l <> rest
| "4" as l <> rest
| "5" as l <> rest
| "6" as l <> rest
| "7" as l <> rest
| "8" as l <> rest
| "9" as l <> rest -> Ok(#(l, rest))
_ -> Error(Nil)
}
}
fn parse_digits(str: String, digits: String) { fn parse_digits(str: String, digits: String) {
case parse_digit(str) { case parse_digit(str) {
Ok(#(d, rest)) -> { Ok(#(d, rest)) -> {

View File

@@ -154,15 +154,23 @@ pub fn parse_this_then(
to_parse str: String, to_parse str: String,
with parsers: List(fn(String) -> Result(#(String, String), Nil)), with parsers: List(fn(String) -> Result(#(String, String), Nil)),
) -> Result(#(String, String), Nil) { ) -> Result(#(String, String), Nil) {
list.fold_until(parsers, Ok(#("", str)), fn(acc, parser) { do_parse_this_then(str, "", parsers)
let assert Ok(#(res, str)) = acc }
case parser(str) {
Ok(#(res2, rest)) -> { fn do_parse_this_then(
Continue(Ok(#(res <> res2, rest))) to_parse str: String,
from initial: String,
with parsers: List(fn(String) -> Result(#(String, String), Nil)),
) -> Result(#(String, String), Nil) {
case parsers {
[] -> Ok(#(initial, str))
[head, ..tail] -> {
case head(str) {
Ok(#(res, rest)) -> do_parse_this_then(rest, initial <> res, tail)
Error(_) -> Error(Nil)
}
} }
Error(Nil) -> Stop(Error(Nil))
} }
})
} }
pub fn parse_multiple( pub fn parse_multiple(

View File

@@ -12,6 +12,28 @@ pub fn main() {
parse_benchmark() parse_benchmark()
// reg_name_benchmark() // reg_name_benchmark()
// ip_benchmark()
}
@target(erlang)
pub fn ip_benchmark() {
benchmark.run(
[
benchmark.Function("ip_benchmark", fn(data) {
fn() {
let _ = parser.parse_dec_octet(data)
Nil
}
}),
],
[
benchmark.Data("173", "173"),
benchmark.Data("5", "5"),
benchmark.Data("200", "200"),
benchmark.Data("255", "255"),
benchmark.Data("fail", "2b"),
],
)
} }
@target(erlang) @target(erlang)