use std::env; use std::fs; use std::fmt; use std::collections::HashMap; mod nombres; enum ErreurSophie { CommandeInconnue(String), PhraseVide, ManqueArgument, OrthographeNombre(String), MauvaisArgument(String), DesequilibreParenthese, VariableInconnue(String), MauvaisType(String), } #[derive(PartialEq, Debug)] enum Variable { Entier(usize), Texte(String), } impl fmt::Display for ErreurSophie { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::CommandeInconnue(commande) => write!(f, "La commande \"{}\" est inconnue.", commande), Self::PhraseVide => write!(f, "La phrase est vide."), Self::ManqueArgument => write!(f, "Il manque un argument."), Self::OrthographeNombre(nombre) => write!(f, "Le nombre \"{}\" est mal orthographié.", nombre), Self::MauvaisArgument(message) => write!(f, "La commande a reçu un mauvais argument, {}.", message), Self::DesequilibreParenthese => write!(f, "Les parenthèses sont déséquilibrés."), Self::VariableInconnue(nom) => write!(f, "La variable \"{}\" est inconnue.", nom), Self::MauvaisType(attendu) => write!(f, "La variable est du mauvais type, {}.", attendu), } } } struct Sophie { variables: HashMap, } impl Sophie { fn new() -> Self { Self { variables: HashMap::new(), } } fn execute(&mut self, contenu: String) { let contenu_propre = contenu.replace("\n", ""); let mut texte: Vec<&str> = contenu_propre.split('.').collect(); texte.pop(); // remove empty phrase after last dot for (index_phrase, phrase) in texte.iter().enumerate() { match self.execute_phrase(phrase) { Ok(_) => {}, Err(raison) => { eprintln!("Erreur phrase {} : {}", index_phrase + 1, raison); return } } } } fn execute_phrase(&mut self, phrase: &str) -> Result<(), ErreurSophie> { let phrase = phrase.trim(); let parties: Vec<&str> = phrase.splitn(2, ' ').collect(); if parties.is_empty() { return Err(ErreurSophie::PhraseVide) } if parties.len() == 1 { return Err(ErreurSophie::ManqueArgument) } match parties[0] { "Définie" => { self.definie(parties[1])?; } "Modifie" => { self.modifie(parties[1])?; }, "Affiche" => { self.affiche(parties[1])?; }, "Demande" => { self.demande(parties[1])?; } autre_commande => { return Err(ErreurSophie::CommandeInconnue(autre_commande.to_string())) } }; Ok(()) } fn definie(&mut self, arguments: &str) -> Result<(), ErreurSophie> { let (variable_nom, variable_type) = nom_de_variable(arguments, "comme")?; let contenu = match variable_type.as_str() { "entier" => Variable::Entier(0), "texte" => Variable::Texte("".to_string()), _ => return Err(ErreurSophie::MauvaisArgument("type de variable inconnu".into())), }; self.variables.insert(variable_nom, contenu); Ok(()) } fn modifie(&mut self, arguments: &str) -> Result<(), ErreurSophie> { let (variable_nom, contenu) = nom_de_variable(arguments, "avec")?; if !self.variables.contains_key(&variable_nom) { return Err(ErreurSophie::VariableInconnue(variable_nom)) } let valeur = self.operation(&contenu)?; self.variables.insert(variable_nom, Variable::Entier(valeur)); Ok(()) } fn affiche(&self, arguments: &str) -> Result<(), ErreurSophie> { let liste_arguments: Vec<&str> = arguments.split(',').collect(); let mut texte = "".to_string(); for argument in liste_arguments { let argument: &str = argument.trim(); if argument.starts_with('"') { if argument.ends_with('"') { texte += &argument[1..argument.len()-1]; } } else { let resultat = self.operation(argument)?; texte += &nombres::nombre_comme_texte(resultat); } } println!("{}", texte); Ok(()) } fn demande(&self, arguments: &str) -> Result<(), ErreurSophie> { println!("- demande : {}", arguments); Ok(()) } } fn nom_de_variable(arguments: &str, separateur: &str) -> Result<(String, String), ErreurSophie> { let parties: Vec<&str> = arguments.splitn(2, separateur).collect(); let nom_variable: String = parties[0].trim().to_string(); if parties.len() == 1 { return Err(ErreurSophie::ManqueArgument) } let Some(first_char) = nom_variable.chars().next() else { return Err(ErreurSophie::MauvaisArgument("il n'y a pas de variable".to_string())) }; if !first_char.is_uppercase() { return Err(ErreurSophie::MauvaisArgument("il manque une majuscule à la variable".to_string())) } Ok((nom_variable, parties[1].trim().to_string())) } fn main() { let arguments: Vec = env::args().collect(); if arguments.len() < 2 { eprintln!("Utilisation : sophie "); return } let chemin_de_fichier = &arguments[1]; let mut sophie = Sophie::new(); match fs::read_to_string(chemin_de_fichier) { Ok(contenu) => { sophie.execute(contenu); } Err(raison) => { eprintln!("Fichier illisible : {}", raison); } } } // ------------------------------------------------------------------------- #[cfg(test)] mod tests { use super::*; #[test] fn teste_conversion_nombres_texte() { for i in [0, 1, 42, 123, 999, 1031, 1_001_091, 72_036_854_775_807usize].iter() { let texte = nombres::nombre_comme_texte(*i); // Convert number to text match nombres::texte_comme_nombre(&texte) { // Convert text back to number Ok(nombre) => { assert_eq!(*i, nombre, "Nombre inexact : {}, texte : {}", i, texte); } Err(raison) => { panic!("Conversion échouée pour : {}, avec l'erreur : {}", i, raison); } } } } #[test] fn teste_somme() { for (a, b) in [(0, 0), (5, 7), (1467,45678), (1001, 0), (72_036_854_775_807usize, 14_036_567_775_807usize)] { let texte_a = nombres::nombre_comme_texte(a); let texte_b = nombres::nombre_comme_texte(b); let sophie = Sophie::new(); let resultat = sophie.operation(&format!("{} plus {}", texte_a, texte_b)); match resultat { // Convert text back to number Ok(nombre) => { assert_eq!(a+b, nombre, "Résultat inexact pour {}+{} : {}", a, b, nombre); } Err(raison) => { panic!("Conversion échouée pour : ({},{}), avec l'erreur : {}", a, b, raison); } } } } #[test] fn teste_definition_variable() { let mut sophie = Sophie::new(); let resultat = sophie.execute_phrase("Définie Variable comme entier"); match resultat { Ok(_) => { assert_eq!(sophie.variables["Variable"], Variable::Entier(0), "Variable mal définie"); } Err(raison) => { panic!("Définition de variable échouée : {}", raison); } } } #[test] fn teste_modification_variable() { let mut sophie = Sophie::new(); if let Err(raison) = sophie.execute_phrase("Définie Variable comme entier") { panic!("Définition de variable échouée : {}", raison); } let a = 2345678; let resultat = sophie.execute_phrase(&format!("Modifie Variable avec {} ", nombres::nombre_comme_texte(a))); match resultat { Ok(_) => { assert_eq!(sophie.variables["Variable"], Variable::Entier(a), "Variable mal modifiée"); } Err(raison) => { panic!("Modification de variable échouée : {}", raison); } } } #[test] fn teste_operation_variable() { let mut sophie = Sophie::new(); if let Err(raison) = sophie.execute_phrase("Définie Variable comme entier") { panic!("Définition de variable échouée : {}", raison); } let a = 2345678; if let Err(raison) = sophie.execute_phrase(&format!("Modifie Variable avec {} ", nombres::nombre_comme_texte(a))) { panic!("Modification de variable échouée : {}", raison); } let b = 987654; let resultat = sophie.operation(&format!("Variable plus {}", nombres::nombre_comme_texte(b))); match resultat { Ok(nombre) => { assert_eq!(nombre, a+b, "Echec de la somme d'un entier et d'une variable, attendais {}, a reçu {}", a+b, nombre); } Err(raison) => { panic!("Opération de variable échouée : {}", raison); } } } #[test] fn teste_maths() { let sophie = Sophie::new(); let a = 2345678; let b = 987654; let c = 34523456; let d = 45678; let e = 2; let resultat = sophie.operation(&format!("{} fois {} plus ouvre la parenthèse {} moins {} ferme la parenthèse divisé par {}", nombres::nombre_comme_texte(a), nombres::nombre_comme_texte(b), nombres::nombre_comme_texte(c), nombres::nombre_comme_texte(d), nombres::nombre_comme_texte(e) )); match resultat { Ok(nombre) => { assert_eq!(nombre, a*b+(c-d)/e, "Echec de l'opération mathématique, got {}", nombre); } Err(raison) => { panic!("Execution échouée pour multiplication, avec l'erreur : {}", raison); } } } }