159 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			Rust
		
	
	
	
			
		
		
	
	
			159 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			Rust
		
	
	
	
| use super::error::{Error, UnknownLabels};
 | |
| use crate::ast::{CallArg, Span};
 | |
| use itertools::Itertools;
 | |
| use std::collections::{HashMap, HashSet};
 | |
| 
 | |
| #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
 | |
| pub struct FieldMap {
 | |
|     pub arity: usize,
 | |
|     pub fields: HashMap<String, (usize, Span)>,
 | |
|     pub is_function: bool,
 | |
| }
 | |
| 
 | |
| impl FieldMap {
 | |
|     pub fn new(arity: usize, is_function: bool) -> Self {
 | |
|         Self {
 | |
|             arity,
 | |
|             fields: HashMap::new(),
 | |
|             is_function,
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     #[allow(clippy::result_large_err)]
 | |
|     pub fn insert(&mut self, label: String, index: usize, location: &Span) -> Result<(), Error> {
 | |
|         match self.fields.insert(label.clone(), (index, *location)) {
 | |
|             Some((_, location_other)) => {
 | |
|                 if self.is_function {
 | |
|                     Err(Error::DuplicateArgument {
 | |
|                         label,
 | |
|                         location: *location,
 | |
|                         duplicate_location: location_other,
 | |
|                     })
 | |
|                 } else {
 | |
|                     Err(Error::DuplicateField {
 | |
|                         label,
 | |
|                         location: *location,
 | |
|                         duplicate_location: location_other,
 | |
|                     })
 | |
|                 }
 | |
|             }
 | |
|             None => Ok(()),
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     pub fn into_option(self) -> Option<Self> {
 | |
|         if self.fields.is_empty() {
 | |
|             None
 | |
|         } else {
 | |
|             Some(self)
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /// Reorder an argument list so that labelled fields supplied out-of-order are
 | |
|     /// in the correct order.
 | |
|     #[allow(clippy::result_large_err)]
 | |
|     pub fn reorder<A>(&self, args: &mut [CallArg<A>], location: Span) -> Result<(), Error> {
 | |
|         let mut last_labeled_arguments_given: Option<&CallArg<A>> = None;
 | |
|         let mut seen_labels = std::collections::HashSet::new();
 | |
|         let mut unknown_labels = Vec::new();
 | |
| 
 | |
|         if self.arity != args.len() {
 | |
|             return Err(Error::IncorrectFieldsArity {
 | |
|                 labels: self.incorrect_arity_labels(args),
 | |
|                 location,
 | |
|                 expected: self.arity,
 | |
|                 given: args.len(),
 | |
|             });
 | |
|         }
 | |
| 
 | |
|         for arg in args.iter() {
 | |
|             match &arg.label {
 | |
|                 Some(_) => {
 | |
|                     last_labeled_arguments_given = Some(arg);
 | |
|                 }
 | |
| 
 | |
|                 None => {
 | |
|                     if let Some(label) = last_labeled_arguments_given {
 | |
|                         return Err(Error::PositionalArgumentAfterLabeled {
 | |
|                             location: arg.location,
 | |
|                             labeled_arg_location: label.location,
 | |
|                         });
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         let mut i = 0;
 | |
|         while i < args.len() {
 | |
|             let label = &args.get(i).expect("Field indexing to get label").label;
 | |
| 
 | |
|             let (label, &location) = match label {
 | |
|                 // A labelled argument, we may need to reposition it in the array vector
 | |
|                 Some(l) => (
 | |
|                     l,
 | |
|                     &args
 | |
|                         .get(i)
 | |
|                         .expect("Indexing in labelled field reordering")
 | |
|                         .location,
 | |
|                 ),
 | |
| 
 | |
|                 // Not a labelled argument
 | |
|                 None => {
 | |
|                     i += 1;
 | |
|                     continue;
 | |
|                 }
 | |
|             };
 | |
| 
 | |
|             let (position, duplicate_location) = match self.fields.get(label) {
 | |
|                 None => {
 | |
|                     unknown_labels.push(location);
 | |
|                     i += 1;
 | |
|                     continue;
 | |
|                 }
 | |
|                 Some(&p) => p,
 | |
|             };
 | |
| 
 | |
|             // If the argument is already in the right place
 | |
|             if position == i {
 | |
|                 seen_labels.insert(label.clone());
 | |
|                 i += 1;
 | |
|             } else {
 | |
|                 if seen_labels.contains(label) {
 | |
|                     return Err(Error::DuplicateArgument {
 | |
|                         location,
 | |
|                         duplicate_location,
 | |
|                         label: label.to_string(),
 | |
|                     });
 | |
|                 }
 | |
| 
 | |
|                 seen_labels.insert(label.clone());
 | |
| 
 | |
|                 args.swap(position, i);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         if unknown_labels.is_empty() {
 | |
|             Ok(())
 | |
|         } else {
 | |
|             let valid = self.fields.keys().map(|t| t.to_string()).sorted().collect();
 | |
| 
 | |
|             Err(Error::UnknownLabels(vec![UnknownLabels {
 | |
|                 valid,
 | |
|                 unknown: unknown_labels,
 | |
|                 supplied: seen_labels.into_iter().collect(),
 | |
|             }]))
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     pub fn incorrect_arity_labels<A>(&self, args: &[CallArg<A>]) -> Vec<String> {
 | |
|         let given: HashSet<_> = args.iter().filter_map(|arg| arg.label.as_ref()).collect();
 | |
| 
 | |
|         self.fields
 | |
|             .keys()
 | |
|             .filter(|f| !given.contains(f))
 | |
|             .sorted()
 | |
|             .cloned()
 | |
|             .collect()
 | |
|     }
 | |
| }
 |