Skip to content

Commit

Permalink
refactor: Move off of IndexMap/HashMap
Browse files Browse the repository at this point in the history
This dropped 17KB

Again, performance shouldn't be too bad as the total number of argument
id's passed in by the user shouldn't be huge, with the upper end being
5-15 except for in extreme cases like rustc accepting arguments from
cargo via a file.
  • Loading branch information
epage committed Aug 11, 2022
1 parent d441ebb commit 6e7fd6d
Show file tree
Hide file tree
Showing 8 changed files with 206 additions and 16 deletions.
1 change: 0 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ debug = ["clap_derive/debug", "dep:backtrace"] # Enables debug messages
unstable-doc = ["derive", "cargo", "wrap_help", "env", "unicode", "unstable-replace", "unstable-grouped"] # for docs.rs

# Used in default
std = ["indexmap/std"] # support for no_std in a backwards-compatible way
std = [] # support for no_std in a backwards-compatible way
color = ["dep:atty", "dep:termcolor"]
suggestions = ["dep:strsim"]

Expand All @@ -89,7 +89,6 @@ clap_lex = { path = "./clap_lex", version = "0.2.2" }
bitflags = "1.2"
textwrap = { version = "0.15.0", default-features = false, features = [] }
unicase = { version = "2.6", optional = true }
indexmap = "1.0"
strsim = { version = "0.10", optional = true }
atty = { version = "0.2", optional = true }
termcolor = { version = "1.1.1", optional = true }
Expand Down
4 changes: 2 additions & 2 deletions src/builder/command.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
// Std
use std::collections::HashMap;
use std::env;
use std::ffi::OsString;
use std::fmt;
Expand All @@ -20,6 +19,7 @@ use crate::output::fmt::Stream;
use crate::output::{fmt::Colorizer, Help, HelpWriter, Usage};
use crate::parser::{ArgMatcher, ArgMatches, Parser};
use crate::util::ChildGraph;
use crate::util::FlatMap;
use crate::util::{color::ColorChoice, Id, Key};
use crate::{Error, INTERNAL_ERROR_MSG};

Expand Down Expand Up @@ -93,7 +93,7 @@ pub struct Command<'help> {
g_settings: AppFlags,
args: MKeyMap<'help>,
subcommands: Vec<Command<'help>>,
replacers: HashMap<&'help str, &'help [&'help str]>,
replacers: FlatMap<&'help str, &'help [&'help str]>,
groups: Vec<ArgGroup<'help>>,
current_help_heading: Option<&'help str>,
current_disp_ord: Option<usize>,
Expand Down
12 changes: 6 additions & 6 deletions src/parser/arg_matcher.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
// Std
use std::collections::HashMap;
use std::ffi::OsString;
use std::mem;
use std::ops::Deref;
Expand All @@ -10,6 +9,7 @@ use crate::parser::AnyValue;
use crate::parser::Identifier;
use crate::parser::PendingArg;
use crate::parser::{ArgMatches, MatchedArg, SubCommand, ValueSource};
use crate::util::FlatMap;
use crate::util::Id;
use crate::INTERNAL_ERROR_MSG;

Expand Down Expand Up @@ -46,14 +46,14 @@ impl ArgMatcher {
"ArgMatcher::get_global_values: global_arg_vec={:?}",
global_arg_vec
);
let mut vals_map = HashMap::new();
let mut vals_map = FlatMap::new();
self.fill_in_global_values(global_arg_vec, &mut vals_map);
}

fn fill_in_global_values(
&mut self,
global_arg_vec: &[Id],
vals_map: &mut HashMap<Id, MatchedArg>,
vals_map: &mut FlatMap<Id, MatchedArg>,
) {
for global_arg in global_arg_vec {
if let Some(ma) = self.get(global_arg) {
Expand Down Expand Up @@ -99,18 +99,18 @@ impl ArgMatcher {
}

pub(crate) fn remove(&mut self, arg: &Id) {
self.matches.args.swap_remove(arg);
self.matches.args.remove(arg);
}

pub(crate) fn contains(&self, arg: &Id) -> bool {
self.matches.args.contains_key(arg)
}

pub(crate) fn arg_ids(&self) -> indexmap::map::Keys<Id, MatchedArg> {
pub(crate) fn arg_ids(&self) -> std::slice::Iter<'_, Id> {
self.matches.args.keys()
}

pub(crate) fn entry(&mut self, arg: &Id) -> indexmap::map::Entry<Id, MatchedArg> {
pub(crate) fn entry(&mut self, arg: &Id) -> crate::util::Entry<Id, MatchedArg> {
self.matches.args.entry(arg.clone())
}

Expand Down
6 changes: 2 additions & 4 deletions src/parser/matches/arg_matches.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,13 @@ use std::fmt::Debug;
use std::iter::{Cloned, Flatten, Map};
use std::slice::Iter;

// Third Party
use indexmap::IndexMap;

// Internal
use crate::parser::AnyValue;
use crate::parser::AnyValueId;
use crate::parser::MatchedArg;
use crate::parser::MatchesError;
use crate::parser::ValueSource;
use crate::util::FlatMap;
use crate::util::{Id, Key};
use crate::INTERNAL_ERROR_MSG;

Expand Down Expand Up @@ -68,7 +66,7 @@ pub struct ArgMatches {
pub(crate) valid_args: Vec<Id>,
#[cfg(debug_assertions)]
pub(crate) valid_subcommands: Vec<Id>,
pub(crate) args: IndexMap<Id, MatchedArg>,
pub(crate) args: FlatMap<Id, MatchedArg>,
pub(crate) subcommand: Option<Box<SubCommand>>,
}

Expand Down
3 changes: 2 additions & 1 deletion src/parser/validator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use crate::output::fmt::Stream;
use crate::output::Usage;
use crate::parser::{ArgMatcher, ParseState};
use crate::util::ChildGraph;
use crate::util::FlatMap;
use crate::util::FlatSet;
use crate::util::Id;
use crate::INTERNAL_ERROR_MSG;
Expand Down Expand Up @@ -383,7 +384,7 @@ impl<'help, 'cmd> Validator<'help, 'cmd> {

#[derive(Default, Clone, Debug)]
struct Conflicts {
potential: std::collections::HashMap<Id, Vec<Id>>,
potential: FlatMap<Id, Vec<Id>>,
}

impl Conflicts {
Expand Down
190 changes: 190 additions & 0 deletions src/util/flat_map.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
use std::borrow::Borrow;

/// Flat (Vec) backed map
///
/// This preserves insertion order
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct FlatMap<K, V> {
keys: Vec<K>,
values: Vec<V>,
}

impl<K: PartialEq + Eq, V> FlatMap<K, V> {
pub(crate) fn new() -> Self {
Default::default()
}

pub(crate) fn insert(&mut self, key: K, mut value: V) -> Option<V> {
for (index, existing) in self.keys.iter().enumerate() {
if *existing == key {
std::mem::swap(&mut self.values[index], &mut value);
return Some(value);
}
}

self.keys.push(key);
self.values.push(value);
None
}

pub fn contains_key<Q: ?Sized>(&self, key: &Q) -> bool
where
K: Borrow<Q>,
Q: std::hash::Hash + Eq,
{
for existing in &self.keys {
if existing.borrow() == key {
return true;
}
}
false
}

pub fn remove<Q: ?Sized>(&mut self, key: &Q) -> Option<V>
where
K: Borrow<Q>,
Q: std::hash::Hash + Eq,
{
let index = self
.keys
.iter()
.enumerate()
.find_map(|(i, k)| (k.borrow() == key).then(|| i))?;
self.keys.remove(index);
Some(self.values.remove(index))
}

pub(crate) fn is_empty(&self) -> bool {
self.keys.is_empty()
}

pub fn entry(&mut self, key: K) -> Entry<K, V> {
for (index, existing) in self.keys.iter().enumerate() {
if *existing == key {
return Entry::Occupied(OccupiedEntry { v: self, index });
}
}
Entry::Vacant(VacantEntry { v: self, key })
}

pub fn get<Q: ?Sized>(&self, k: &Q) -> Option<&V>
where
K: Borrow<Q>,
Q: std::hash::Hash + Eq,
{
for (index, existing) in self.keys.iter().enumerate() {
if existing.borrow() == k {
return Some(&self.values[index]);
}
}
None
}

pub fn get_mut<Q: ?Sized>(&mut self, k: &Q) -> Option<&mut V>
where
K: Borrow<Q>,
Q: std::hash::Hash + Eq,
{
for (index, existing) in self.keys.iter().enumerate() {
if existing.borrow() == k {
return Some(&mut self.values[index]);
}
}
None
}

pub fn keys(&self) -> std::slice::Iter<'_, K> {
self.keys.iter()
}

pub fn iter_mut(&mut self) -> IterMut<K, V> {
IterMut {
keys: self.keys.iter_mut(),
values: self.values.iter_mut(),
}
}
}

impl<K: PartialEq + Eq, V> Default for FlatMap<K, V> {
fn default() -> Self {
Self {
keys: Default::default(),
values: Default::default(),
}
}
}

pub enum Entry<'a, K: 'a, V: 'a> {
Vacant(VacantEntry<'a, K, V>),
Occupied(OccupiedEntry<'a, K, V>),
}

impl<'a, K: 'a, V: 'a> Entry<'a, K, V> {
pub fn or_insert(self, default: V) -> &'a mut V {
match self {
Entry::Occupied(entry) => &mut entry.v.values[entry.index],
Entry::Vacant(entry) => {
entry.v.keys.push(entry.key);
entry.v.values.push(default);
entry.v.values.last_mut().unwrap()
}
}
}

pub fn or_insert_with<F: FnOnce() -> V>(self, default: F) -> &'a mut V {
match self {
Entry::Occupied(entry) => &mut entry.v.values[entry.index],
Entry::Vacant(entry) => {
entry.v.keys.push(entry.key);
entry.v.values.push(default());
entry.v.values.last_mut().unwrap()
}
}
}
}

pub struct VacantEntry<'a, K: 'a, V: 'a> {
v: &'a mut FlatMap<K, V>,
key: K,
}

pub struct OccupiedEntry<'a, K: 'a, V: 'a> {
v: &'a mut FlatMap<K, V>,
index: usize,
}

pub struct IterMut<'a, K: 'a, V: 'a> {
keys: std::slice::IterMut<'a, K>,
values: std::slice::IterMut<'a, V>,
}

impl<'a, K, V> Iterator for IterMut<'a, K, V> {
type Item = (&'a K, &'a mut V);

fn next(&mut self) -> Option<(&'a K, &'a mut V)> {
match self.keys.next() {
Some(k) => {
let v = self.values.next().unwrap();
Some((k, v))
}
None => None,
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.keys.size_hint()
}
}

impl<'a, K, V> DoubleEndedIterator for IterMut<'a, K, V> {
fn next_back(&mut self) -> Option<(&'a K, &'a mut V)> {
match self.keys.next_back() {
Some(k) => {
let v = self.values.next_back().unwrap();
Some((k, v))
}
None => None,
}
}
}

impl<'a, K, V> ExactSizeIterator for IterMut<'a, K, V> {}
3 changes: 3 additions & 0 deletions src/util/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#![allow(clippy::single_component_path_imports)]

mod flat_map;
mod flat_set;
mod fnv;
mod graph;
Expand All @@ -8,6 +9,8 @@ mod str_to_bool;

pub use self::fnv::Key;

pub(crate) use self::flat_map::Entry;
pub(crate) use self::flat_map::FlatMap;
pub(crate) use self::flat_set::FlatSet;
pub(crate) use self::str_to_bool::str_to_bool;
pub(crate) use self::str_to_bool::FALSE_LITERALS;
Expand Down

0 comments on commit 6e7fd6d

Please sign in to comment.