From 99629ebe5f1818f064d78d5ce4e29abbee0f7b26 Mon Sep 17 00:00:00 2001 From: Zachary S Date: Tue, 5 Apr 2022 16:12:54 -0500 Subject: [PATCH 1/2] Pre-allocate capacity for maps in json! macro. Attempt to address issue #810. This implementation works by expanding the contents of the map twice, first to produce a capacity value, then actually inserting the elements. Because it expands the contents of the map twice, when encountering invalid syntax, it may print error messages twice, which may be unwanted. --- src/macros.rs | 104 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 103 insertions(+), 1 deletion(-) diff --git a/src/macros.rs b/src/macros.rs index 5287998b4..a255384cf 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -234,6 +234,107 @@ macro_rules! json_internal { json_internal!(@object $object ($($key)* $tt) ($($rest)*) ($($rest)*)); }; + + ////////////////////////////////////////////////////////////////////////// + // TT muncher for counting the elements inside of an object {...}. + // Each entry is replaced with `1 + ` (or `1` if it is the last element). + // 0 is inserted if the object has a trailing comma. + // + // Must be invoked as: json_internal!(@object_capacity () ($($tt)*) ($($tt)*)) + // + // We require two copies of the input tokens so that we can match on one + // copy and trigger errors on the other copy. + ////////////////////////////////////////////////////////////////////////// + + // Done. + (@object_capacity () () ()) => {0}; + + // Current entry followed by trailing comma. + (@object_capacity entry , $($rest:tt)*) => { + 1 + json_internal!(@object_capacity () ($($rest)*) ($($rest)*)) + }; + + // Current entry followed by unexpected token. The actual parsing macro will print the error message. + (@object_capacity entry $unexpected:tt $($rest:tt)*) => { + 0 + }; + + // Insert the last entry without trailing comma. + (@object_capacity entry) => { + 1 + }; + + // Next value is `null`. + (@object_capacity ($($key:tt)+) (: null $($rest:tt)*) $copy:tt) => { + json_internal!(@object_capacity entry $($rest)*) + }; + + // Next value is `true`. + (@object_capacity ($($key:tt)+) (: true $($rest:tt)*) $copy:tt) => { + json_internal!(@object_capacity entry $($rest)*) + }; + + // Next value is `false`. + (@object_capacity ($($key:tt)+) (: false $($rest:tt)*) $copy:tt) => { + json_internal!(@object_capacity entry $($rest)*) + }; + + // Next value is an array. + (@object_capacity ($($key:tt)+) (: [$($array:tt)*] $($rest:tt)*) $copy:tt) => { + json_internal!(@object_capacity entry $($rest)*) + }; + + // Next value is a map. + (@object_capacity ($($key:tt)+) (: {$($map:tt)*} $($rest:tt)*) $copy:tt) => { + json_internal!(@object_capacity entry $($rest)*) + }; + + // Next value is an expression followed by comma. + (@object_capacity ($($key:tt)+) (: $value:expr , $($rest:tt)*) $copy:tt) => { + json_internal!(@object_capacity entry , $($rest)*) + }; + + // Last value is an expression with no trailing comma. + (@object_capacity ($($key:tt)+) (: $value:expr) $copy:tt) => { + json_internal!(@object_capacity entry) + }; + + // Missing value for last entry. The actual parsing macro will print the error message. + (@object_capacity ($($key:tt)+) (:) $copy:tt) => { + 0 + }; + + // Missing colon and value for last entry. The actual parsing macro will print the error message. + (@object_capacity ($($key:tt)+) () $copy:tt) => { + 0 + }; + + // Misplaced colon. The actual parsing macro will print the error message. + (@object_capacity () (: $($rest:tt)*) ($colon:tt $($copy:tt)*)) => { + 0 + }; + + // Found a comma inside a key. The actual parsing macro will print the error message. + (@object_capacity ($($key:tt)*) (, $($rest:tt)*) ($comma:tt $($copy:tt)*)) => { + 0 + }; + + // Key is fully parenthesized. This is not necessary for counting capacity + // since we don't evaluate $key anyway, so just use the munching below. + // (@object_capacity () (($key:expr) : $($rest:tt)*) $copy:tt) => { + // json_internal!(@object_capacity ($key) (: $($rest)*) (: $($rest)*)) + // }; + + // Refuse to absorb colon token into key expression. + (@object_capacity ($($key:tt)*) (: $($unexpected:tt)+) $copy:tt) => { + json_expect_expr_comma!($($unexpected)+) + }; + + // Munch a token into the current key. + (@object_capacity ($($key:tt)*) ($tt:tt $($rest:tt)*) $copy:tt) => { + json_internal!(@object_capacity ($($key)* $tt) ($($rest)*) ($($rest)*)) + }; + ////////////////////////////////////////////////////////////////////////// // The main implementation. // @@ -266,7 +367,8 @@ macro_rules! json_internal { ({ $($tt:tt)+ }) => { $crate::Value::Object({ - let mut object = $crate::Map::new(); + let capacity = json_internal!(@object_capacity () ($($tt)+) ($($tt)+)); + let mut object = $crate::Map::with_capacity(capacity); json_internal!(@object object () ($($tt)+) ($($tt)+)); object }) From e9764820f5bb0fc26290991b65dd24388b9d851e Mon Sep 17 00:00:00 2001 From: Zachary S Date: Thu, 7 Apr 2022 02:28:08 -0500 Subject: [PATCH 2/2] Change map-capacity-counting part of json_internal! macro to not report errors. Since these errors are already reported by the part of the macro that actually parses the map, it is unnecessary to report them twice, so the capacity-counting part can just return 0 if it encounters an error. --- src/macros.rs | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/macros.rs b/src/macros.rs index a255384cf..06f9af7d5 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -254,7 +254,9 @@ macro_rules! json_internal { 1 + json_internal!(@object_capacity () ($($rest)*) ($($rest)*)) }; - // Current entry followed by unexpected token. The actual parsing macro will print the error message. + // Current entry followed by unexpected token. The part that parses the values + // will trigger a reasonable error message; here, we just return 0 + // so that there is not a duplicated error message. (@object_capacity entry $unexpected:tt $($rest:tt)*) => { 0 }; @@ -299,22 +301,30 @@ macro_rules! json_internal { json_internal!(@object_capacity entry) }; - // Missing value for last entry. The actual parsing macro will print the error message. + // Missing value for last entry. The part that parses the values + // will trigger a reasonable error message; here, we just return 0 + // so that there is not a duplicated error message. (@object_capacity ($($key:tt)+) (:) $copy:tt) => { 0 }; - // Missing colon and value for last entry. The actual parsing macro will print the error message. + // Missing colon and value for last entry. The part that parses the values + // will trigger a reasonable error message; here, we just return 0 + // so that there is not a duplicated error message. (@object_capacity ($($key:tt)+) () $copy:tt) => { 0 }; - // Misplaced colon. The actual parsing macro will print the error message. + // Misplaced colon. The part that parses the values + // will trigger a reasonable error message; here, we just return 0 + // so that there is not a duplicated error message. (@object_capacity () (: $($rest:tt)*) ($colon:tt $($copy:tt)*)) => { 0 }; - // Found a comma inside a key. The actual parsing macro will print the error message. + // Found a comma inside a key. The part that parses the values + // will trigger a reasonable error message; here, we just return 0 + // so that there is not a duplicated error message. (@object_capacity ($($key:tt)*) (, $($rest:tt)*) ($comma:tt $($copy:tt)*)) => { 0 }; @@ -326,8 +336,10 @@ macro_rules! json_internal { // }; // Refuse to absorb colon token into key expression. + // The part that parses the values will trigger a reasonable error message; + // here, we just return 0 so that there is not a duplicated error message. (@object_capacity ($($key:tt)*) (: $($unexpected:tt)+) $copy:tt) => { - json_expect_expr_comma!($($unexpected)+) + 0 }; // Munch a token into the current key.