diff --git a/pendragon.project b/pendragon.project index d40cd1c..8128bf6 100644 --- a/pendragon.project +++ b/pendragon.project @@ -1 +1 @@ -{"categories":[{"name":"todo","content":[{"name":"verifie si bloc pas vide","description":"// Hello there","id":2},{"name":"scope variable","description":"// Hello there","id":6},{"name":"while","description":"implémente un while","id":2},{"name":"else","description":"// Hello there","id":7},{"name":"break","description":"// Hello there","id":8},{"name":"continue","description":"// Hello there","id":9},{"name":"compilation","description":"// Hello there","id":3}]},{"name":"in progress","content":[]},{"name":"done","content":[{"name":"comparaison","description":"// Hello there","id":2},{"name":"booleen type","description":"// Hello there","id":4},{"name":"soixante-dix","description":"// Hello there","id":1},{"name":"parenthese comparaison","description":"// Hello there","id":1},{"name":"test comparaison","description":"// Hello there","id":4},{"name":"error ligne compilation","description":"// Hello there","id":2},{"name":"commentaires","description":"// Hello there","id":3},{"name":"compile time verification","description":"odre des termes rpn","id":1},{"name":"test multiple space in texte","description":"// Hello there","id":3},{"name":"test puis in texte","description":"// Hello there","id":2},{"name":"enchainement puis","description":"// Hello there","id":5},{"name":"guillemet mal ferme","description":"// Hello there","id":4},{"name":"display element","description":"for better print","id":6},{"name":"puis à la ligne, alinéa","description":"// Hello there","id":1},{"name":"teste double tiret","description":"// Hello there","id":2},{"name":"teste puis seul","description":"tout seul, seul devant, seul fin","id":3},{"name":"erreur calcul bool/nombre","description":"affiche element precedent lorsque mauvais enchainement","id":1},{"name":"multiple compilation error","description":"// Hello there","id":1},{"name":"tests mod","description":"// Hello there","id":3},{"name":"if","description":"implémente if avec des guillemets de délimitation","id":1}]},{"name":"bug","content":[]},{"name":"to test","content":[{"name":"test variable","description":"// Hello there","id":2},{"name":"teste if","description":"// Hello there","id":3}]},{"name":"bonus","content":[{"name":"stop cheating with \"-\"","description":"// Hello there","id":5},{"name":"affiche ligne et position erreur","description":"// Hello there","id":1},{"name":"pour numero de ligne execution","description":"sauvegarde numero de ligne dans la commande\n\ncommande.ligne(12)","id":3},{"name":"standardizer erreur","description":"regarder les texte répétés","id":1}]},{"name":"+","content":[]}]} \ No newline at end of file +{"categories":[{"name":"todo","content":[{"name":"verifie si bloc pas vide","description":"// Hello there","id":2},{"name":"scope variable","description":"// Hello there","id":6},{"name":"while","description":"implémente un while","id":2},{"name":"else","description":"// Hello there","id":7},{"name":"break","description":"// Hello there","id":8},{"name":"continue","description":"// Hello there","id":9},{"name":"compilation","description":"// Hello there","id":3},{"name":"separe interpretation","description":"// Hello there","id":1},{"name":"programme not in pendragon self ?","description":"// Hello there","id":2}]},{"name":"in progress","content":[]},{"name":"done","content":[{"name":"comparaison","description":"// Hello there","id":2},{"name":"booleen type","description":"// Hello there","id":4},{"name":"soixante-dix","description":"// Hello there","id":1},{"name":"parenthese comparaison","description":"// Hello there","id":1},{"name":"test comparaison","description":"// Hello there","id":4},{"name":"error ligne compilation","description":"// Hello there","id":2},{"name":"commentaires","description":"// Hello there","id":3},{"name":"compile time verification","description":"odre des termes rpn","id":1},{"name":"test multiple space in texte","description":"// Hello there","id":3},{"name":"test puis in texte","description":"// Hello there","id":2},{"name":"enchainement puis","description":"// Hello there","id":5},{"name":"guillemet mal ferme","description":"// Hello there","id":4},{"name":"display element","description":"for better print","id":6},{"name":"puis à la ligne, alinéa","description":"// Hello there","id":1},{"name":"teste double tiret","description":"// Hello there","id":2},{"name":"teste puis seul","description":"tout seul, seul devant, seul fin","id":3},{"name":"erreur calcul bool/nombre","description":"affiche element precedent lorsque mauvais enchainement","id":1},{"name":"multiple compilation error","description":"// Hello there","id":1},{"name":"tests mod","description":"// Hello there","id":3},{"name":"if","description":"implémente if avec des guillemets de délimitation","id":1}]},{"name":"bug","content":[]},{"name":"to test","content":[{"name":"test variable","description":"// Hello there","id":2},{"name":"teste if","description":"// Hello there","id":3}]},{"name":"bonus","content":[{"name":"stop cheating with \"-\"","description":"// Hello there","id":5},{"name":"affiche ligne et position erreur","description":"// Hello there","id":1},{"name":"pour numero de ligne execution","description":"sauvegarde numero de ligne dans la commande\n\ncommande.ligne(12)","id":3},{"name":"standardizer erreur","description":"regarder les texte répétés","id":1}]},{"name":"+","content":[]}]} \ No newline at end of file diff --git a/src/pendragon/debug.rs b/src/debug/display.rs similarity index 96% rename from src/pendragon/debug.rs rename to src/debug/display.rs index cf2a94f..8309d5e 100644 --- a/src/pendragon/debug.rs +++ b/src/debug/display.rs @@ -1,6 +1,7 @@ use std::fmt; use std::time::Duration; -use super::*; +use crate::pendragon::structure::*; +use crate::sophie; pub const TEXTE_ROUGE: &str = "\x1b[31m"; pub const TEXTE_VERT: &str = "\x1b[32m"; @@ -130,9 +131,9 @@ impl fmt::Display for Commande { impl fmt::Display for Element { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {//' match self { - Self::Entier(nombre) => write!(f, "{}", nombre::nombre_comme_texte(*nombre)), + Self::Entier(nombre) => write!(f, "{}", sophie::nombre::nombre_comme_texte(*nombre)), Self::Texte(texte) => write!(f, "\"{}\"", texte), - Self::Booleen(booleen) => write!(f, "{}", booleen::booleen_comme_texte(*booleen)), + Self::Booleen(booleen) => write!(f, "{}", sophie::booleen::booleen_comme_texte(*booleen)), Self::Variable(nom, type_variable) => write!(f, "{}:{}", nom, type_variable.nom()), Self::Operateur(operateur) => write!(f, "{}", operateur), Self::Comparaison(comparaison) => write!(f, "{}.", comparaison), diff --git a/src/debug/mod.rs b/src/debug/mod.rs new file mode 100644 index 0000000..c21c30f --- /dev/null +++ b/src/debug/mod.rs @@ -0,0 +1,5 @@ +pub mod display; + +#[cfg(test)] +mod test; + diff --git a/src/debug/test.rs b/src/debug/test.rs new file mode 100644 index 0000000..16ce5d1 --- /dev/null +++ b/src/debug/test.rs @@ -0,0 +1,318 @@ +use std::collections::HashMap; +use crate::sophie; +use crate::pendragon; +use crate::display::*; +use crate::structure::*; + +#[test] +fn calcul_texte() { + let pendragon = pendragon::Pendragon::nouveau(); + let a = 2345678; + let b = 987654; + + let possible_expression = pendragon.elements_texte(&format!("\"hello\" puis {} fois {} puis \"there\" puis vrai ou faux puis trois puis deux puis alinéa puis retour à la ligne", + sophie::nombre::nombre_comme_texte(a), + sophie::nombre::nombre_comme_texte(b))); + match possible_expression { + Ok(expression) => { + match sophie::texte::calcule_texte(expression, &HashMap::new()) { + Ok(texte) => { + let vrai_texte = format!("hello{}therevraitroisdeux\t\n", sophie::nombre::nombre_comme_texte(a*b)); + assert_eq!(texte, vrai_texte, "Calcul d'expression (texte) donne un mauvais résultat : {}", texte); + } + Err(raison) => { + panic!("Calcul d'expression (texte) échoué, avec l'erreur : {}", raison); + } + } + } + Err(raison) => { + panic!("Détermination d'expression (texte) échouée : {}", raison); + } + } +} + +#[test] +fn conversion_texte() { + let pendragon = pendragon::Pendragon::nouveau(); + let texte = "\"hello aaaa puis AERTY et ou fois six\""; + match pendragon.elements_texte(texte) { + Ok(expression) => { + if expression.len() != 2 { + panic!("L'expression (texte) devrait contenir deux éléments (texte et puis), contient : {:?}", expression); + } + assert_eq!(expression[0], Element::Texte(texte[1..texte.len()-1].into()), "Calcul d'expression (texte) donne un mauvais résultat : {}", texte); + } + Err(raison) => { + panic!("Conversion échouée (texte) : {}", raison); + } + } +} + +#[test] +fn erreur_conversion_texte() { + let pendragon = pendragon::Pendragon::nouveau(); + let textes = vec![ + "trois puis puis un", + "\" test", + "puis", + "un puis", + "puis un", + ]; + for texte in textes { + let Err(raison) = pendragon.elements_texte(texte) else { + panic!("Ne devrait pas réussir à convertir le texte '{}'", texte); + }; + let ErreurPendragon::TexteInvalide(_) = raison else { + panic!("Erreur imprévue pour convertir le texte '{}' : {}", texte, raison); + }; + } +} + + +//---------------------------- + + +#[test] +fn conversion_booleen_texte() { + for b in [true, false].iter() { + let texte = sophie::booleen::booleen_comme_texte(*b); // Convert number to text + match pendragon::booleen::texte_comme_booleen(&texte) { // Convert text back to number + Ok(booleen) => { + assert_eq!(Element::Booleen(*b), booleen, "Booleen inexact : {}, texte : {}", b, texte); + } + Err(raison) => { + panic!("Conversion échouée pour : {}, avec l'erreur : {}", b, raison); + } + } + } +} + +#[test] +fn calcul_booleen() { + let pendragon = pendragon::Pendragon::nouveau(); + let mut configurations = Vec::new(); + for b1 in [true, false] { + for b2 in [true, false] { + for b3 in [true, false] { + for b4 in [true, false] { + for b5 in [true, false] { + configurations.push((b1, b2, b3, b4, b5)); + } + } + } + } + } + for configuration in configurations { + let possible_expression = pendragon.elements_booleen(&format!("{} et non ouvre la parenthèse {} ou non {} ferme la parenthèse ou non {} et {}", + sophie::booleen::booleen_comme_texte(configuration.0), + sophie::booleen::booleen_comme_texte(configuration.1), + sophie::booleen::booleen_comme_texte(configuration.2), + sophie::booleen::booleen_comme_texte(configuration.3), + sophie::booleen::booleen_comme_texte(configuration.4))); + match possible_expression { + Ok(expression) => { + match sophie::booleen::calcule_booleen(expression, &HashMap::new()) { + Ok(booleen) => { + let resultat = configuration.0 && !(configuration.1 || !configuration.2) || !configuration.3 && configuration.4; + assert_eq!(booleen, resultat, "Calcul d'expression (booleen) donne un mauvais résultat : {}", booleen); + } + Err(raison) => { + panic!("Calcul d'expression (booleen) échoué, avec l'erreur : {}", raison); + } + } + } + Err(raison) => { + panic!("Détermination d'expression (booleen) échouée : {}", raison); + } + } + } +} + +#[test] +fn comparaison_booleen() { + let pendragon = pendragon::Pendragon::nouveau(); + for (a, b) in [(1, 4), (2, 2), (3, 1), (0, 3)] { + let possible_expressions = vec![ + pendragon.elements_booleen(&format!("non six plus {} est supérieur à deux fois {}", sophie::nombre::nombre_comme_texte(a), sophie::nombre::nombre_comme_texte(b))), + pendragon.elements_booleen(&format!("six plus {} est inférieur à deux fois {}", sophie::nombre::nombre_comme_texte(a), sophie::nombre::nombre_comme_texte(b))), + pendragon.elements_booleen(&format!("six plus {} est supérieur ou égal à deux fois {}", sophie::nombre::nombre_comme_texte(a), sophie::nombre::nombre_comme_texte(b))), + pendragon.elements_booleen(&format!("non six plus {} est inférieur ou égal à deux fois {}", sophie::nombre::nombre_comme_texte(a), sophie::nombre::nombre_comme_texte(b))), + pendragon.elements_booleen(&format!("non \"deux\" est égal à \"{}\"", sophie::nombre::nombre_comme_texte(a))), + pendragon.elements_booleen(&format!("\"trois\" est différent de \"{}\"", sophie::nombre::nombre_comme_texte(a))), + ]; + let bonne_reponses = vec![ + !(6+a > 2*b), + (6+a < 2*b), + (6+a >= 2*b), + !(6+a <= 2*b), + !(a == 2), + (a != 3), + ]; + for index in 0..possible_expressions.len() { + match &possible_expressions[index] { + Ok(expression) => { + match sophie::booleen::calcule_booleen(expression.clone(), &HashMap::new()) { + Ok(booleen) => { + let reponse = bonne_reponses[index]; + assert_eq!(booleen, reponse, "Calcul d'expression (booleen) n°{} ({},{}) donne un mauvais résultat : {}, attendais {}", index, a, b, booleen, reponse); + } + Err(raison) => { + panic!("Calcul d'expression (booleen) échoué, avec l'erreur : {}", raison); + } + } + } + Err(raison) => { + panic!("Détermination d'expression (booleen) échouée : {}", raison); + } + } + } + } +} + +#[test] +fn combinaison_booleen() { + let pendragon = pendragon::Pendragon::nouveau(); + for a in 0..5 { + for b in 0..5 { + for c in 0..5 { + for d in 1..5 { + for e in 0..5 { + let possible_expression = pendragon.elements_booleen(&format!("non ouvre la parenthèse six plus {} ferme la parenthèse est supérieur à deux fois {} et ouvre la parenthèse {} divisé par deux est inférieur à ouvre la parenthèse {} moins un ferme la parenthèse ou non \"deux\" est égal à \"{}\" ferme la parenthèse", + sophie::nombre::nombre_comme_texte(a), + sophie::nombre::nombre_comme_texte(b), + sophie::nombre::nombre_comme_texte(c), + sophie::nombre::nombre_comme_texte(d), + sophie::nombre::nombre_comme_texte(e), + )); + let bonne_reponse = !((6+a) > 2*b) && (c/2 < (d-1) || !(e == 2)); + match possible_expression { + Ok(expression) => { + match sophie::booleen::calcule_booleen(expression.clone(), &HashMap::new()) { + Ok(booleen) => { + assert_eq!(booleen, bonne_reponse, "Calcul d'expression (booleen) ({},{},{},{},{}) donne un mauvais résultat : {}, attendais {}", a, b, c, d, e, booleen, bonne_reponse); + } + Err(raison) => { + panic!("Calcul d'expression (booleen) échoué, avec l'erreur : {}", raison); + } + } + } + Err(raison) => { + panic!("Détermination d'expression (booleen) échouée : {}", raison); + } + } + } + } + } + } + } +} + +#[test] +fn erreur_calcul_booleen() { + let pendragon = pendragon::Pendragon::nouveau(); + let textes_invalide = vec![ + "et faux", + "vrai et et faux", + "vrai ou ou faux", + "vrai et vrai faux", + "vrai et faux vrai", + "vrai et faux ouvre la parenthèse vrai ou faux ferme la parenthèse", + "vrai et ouvre la parenthèse et vrai ou faux ferme la parenthèse", + "vrai et ouvre la parenthèse vrai ou faux et ferme la parenthèse", + "vrai et ouvre la parenthèse vrai ou faux ferme la parenthèse vrai", + ]; + for texte in textes_invalide { + let Err(raison) = pendragon.elements_booleen(texte) else { + panic!("Devrait détecter une erreur pour '{}'", texte); + }; + let ErreurPendragon::OrdreCalculBooleen(_,_,_) = raison else { + panic!("Devrait détecter une erreur de calcul booléen pour '{}', a déclenché : {}", texte, raison); + }; + } +} + + +// --------------------- + + +#[test] +fn conversion_nombres_texte() { + for i in [0, 1, 42, 70, 123, 999, 1031, 1_001_091, 72_036_854_775_807usize, 2345678*987654].iter() { + let texte = sophie::nombre::nombre_comme_texte(*i); // Convert number to text + if texte.contains("--") { + panic!("Il y a deux tirets pour {} : {}", i, texte); + } + match pendragon::nombre::texte_comme_nombre(&texte) { // Convert text back to number + Ok(nombre) => { + assert_eq!(Element::Entier(*i), nombre, "Nombre inexact : {}, texte : {}", i, texte); + } + Err(raison) => { + panic!("Conversion échouée pour : {}, avec l'erreur : {}", i, raison); + } + } + } +} + +#[test] +fn calcul_nombre() { + let pendragon = pendragon::Pendragon::nouveau(); + let a = 2345678; + let b = 987654; + let c = 34523456; + let d = 45678; + let e = 2; + let possible_expression = pendragon.elements_nombre(&format!("{} fois {} plus ouvre la parenthèse {} moins {} ferme la parenthèse divisé par {}", + sophie::nombre::nombre_comme_texte(a), + sophie::nombre::nombre_comme_texte(b), + sophie::nombre::nombre_comme_texte(c), + sophie::nombre::nombre_comme_texte(d), + sophie::nombre::nombre_comme_texte(e))); + match possible_expression { + Ok(expression) => { + match sophie::nombre::calcule_nombre(expression, &HashMap::new()) { + Ok(nombre) => { + assert_eq!(nombre, a*b+(c-d)/e, "Calcul d'expression (entier) donne un mauvais résultat : {}", nombre); + } + Err(raison) => { + panic!("Calcul d'expression (entier) échoué, avec l'erreur : {}", raison); + } + } + } + Err(raison) => { + panic!("Détermination d'expression (entier) échouée : {}", raison); + } + } +} + +#[test] +fn erreur_calcul_nombre() { + let pendragon = pendragon::Pendragon::nouveau(); + let textes_invalide = vec![ + "un un fois un", + "un plus fois un", + "un moins divisé par un", + "un fois un ouvre la parenthèse un plus un ferme la parenthèse", + "un fois ouvre la parenthèse plus un plus un ferme la parenthèse", + "un fois ouvre la parenthèse un plus un fois ferme la parenthèse", + "un fois ouvre la parenthèse un plus un ferme la parenthèse un", + ]; + for texte in textes_invalide { + let Err(raison) = pendragon.elements_nombre(texte) else { + panic!("Devrait détecter une erreur pour '{}'", texte); + }; + let ErreurPendragon::OrdreCalculEntier(_,_,_) = raison else { + panic!("Devrait détecter une erreur de calcul entier pour '{}', a déclenché : {}", texte, raison); + }; + } +} + +#[test] +fn nombre_invalide_et() { + let pendragon = pendragon::Pendragon::nouveau(); + let Err(raison) = pendragon.elements_nombre("et") else { + panic!("Devrait détecter une erreur pour 'et'"); + }; + let ErreurPendragon::NombreInvalide(_) = raison else { + panic!("Devrait détecter une erreur de nombre invalide pour 'et', a déclenché : {}", raison); + }; +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 9e9128b..9245ff8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,6 +4,9 @@ use std::time::Instant; mod pendragon; use pendragon::*; +mod sophie; +mod debug; +use debug::display; fn main() { let arguments: Vec = env::args().collect(); @@ -21,32 +24,32 @@ fn main() { let lecture = fs::read_to_string(chemin_de_fichier); if let Err(raison) = lecture { - eprintln!("{}Fichier illisible :{} {}", debug::TEXTE_ROUGE, raison, debug::TEXTE_NORMAL); + eprintln!("{}Fichier illisible :{} {}", display::TEXTE_ROUGE, raison, display::TEXTE_NORMAL); return } - debug::message_compilation(chemin_de_fichier); + display::message_compilation(chemin_de_fichier); let debut = Instant::now(); if let Err(raison) = pendragon.compile(lecture.unwrap()) { for erreur in raison { eprintln!("\n{}", erreur); } - debug::message_compilation_echec(); + display::message_compilation_echec(); return } - debug::message_compilation_ok(debut.elapsed()); + display::message_compilation_ok(debut.elapsed()); if debug_mode { println!("\n{}\n", pendragon.programme); } - debug::message_execution(chemin_de_fichier); + display::message_execution(chemin_de_fichier); let debut = Instant::now(); if let Err(raison) = pendragon.programme.execute() { eprintln!("\nErreur : {}", raison); - debug::message_execution_echec(); + display::message_execution_echec(); return } - debug::message_execution_ok(debut.elapsed()); + display::message_execution_ok(debut.elapsed()); } diff --git a/src/pendragon/booleen.rs b/src/pendragon/booleen.rs index 3ac1ecd..c4b9446 100644 --- a/src/pendragon/booleen.rs +++ b/src/pendragon/booleen.rs @@ -213,73 +213,6 @@ fn compare_parentheses(strings: &Vec) -> (usize, usize) { (ouvre_count, ferme_count) } -pub fn affiche_booleen(expression: Vec, variables: &HashMap) -> Result { - let booleen = calcule_booleen(expression.clone(), variables)?; - Ok(booleen_comme_texte(booleen)) -} - -pub fn calcule_booleen(expression: Vec, variables: &HashMap) -> Result { - let mut pile: Vec = Vec::new(); - - for element in expression { - if let Element::Booleen(booleen) = element { - pile.push(booleen); - continue; - } - if let Element::Variable(nom, _) = element { - let Some(variable) = variables.get(&nom) else { - return Err(ErreurPendragon::VariableInconnue(nom.into())) - }; - if let Element::Booleen(booleen) = variable { - pile.push(*booleen); - continue - } else { - return Err(ErreurPendragon::MauvaisType(nom.into(), variable.type_element().nom(), "booleen".into())) - } - } - if let Element::Comparaison(comparaison) = element { - pile.push(comparaison.calcule(variables)?); - continue - } - let Element::Operateur(ref operateur) = element else { - return Err(ErreurPendragon::MauvaisArgument(format!("{}, attendais un opérateur", element))) - }; - let Some(booleen_a) = pile.pop() else { - return Err(ErreurPendragon::CalculBooleen("la pile est vide".into())) - }; - match operateur { - Operateur::Non => { - pile.push(!booleen_a); - } - Operateur::Et => { - let Some(booleen_b) = pile.pop() else { - return Err(ErreurPendragon::CalculBooleen("la pile est vide".into())) - }; - pile.push(booleen_a && booleen_b); - } - Operateur::Ou => { - let Some(booleen_b) = pile.pop() else { - return Err(ErreurPendragon::CalculBooleen("la pile est vide".into())) - }; - pile.push(booleen_a || booleen_b); - } - _ => return Err(ErreurPendragon::MauvaisArgument(format!("{}, attendais un opérateur booléen", element))) - } - } - if pile.len() > 1 { - return Err(ErreurPendragon::CalculBooleen("il reste plusieurs éléments dans la pile".into())) - } - Ok(pile[0]) -} - -pub fn booleen_comme_texte(booleen: bool) -> String { - if booleen { - "vrai".into() - } else { - "faux".into() - } -} - pub fn texte_comme_booleen(texte: &str) -> Result { match texte { "vrai" => Ok(Element::Booleen(true)), @@ -300,173 +233,3 @@ pub fn texte_comme_comparaison(texte: &str) -> Result { - assert_eq!(Element::Booleen(*b), booleen, "Booleen inexact : {}, texte : {}", b, texte); - } - Err(raison) => { - panic!("Conversion échouée pour : {}, avec l'erreur : {}", b, raison); - } - } - } - } - - #[test] - fn calcul_booleen() { - let pendragon = Pendragon::nouveau(); - let mut configurations = Vec::new(); - for b1 in [true, false] { - for b2 in [true, false] { - for b3 in [true, false] { - for b4 in [true, false] { - for b5 in [true, false] { - configurations.push((b1, b2, b3, b4, b5)); - } - } - } - } - } - for configuration in configurations { - let possible_expression = pendragon.elements_booleen(&format!("{} et non ouvre la parenthèse {} ou non {} ferme la parenthèse ou non {} et {}", - booleen_comme_texte(configuration.0), - booleen_comme_texte(configuration.1), - booleen_comme_texte(configuration.2), - booleen_comme_texte(configuration.3), - booleen_comme_texte(configuration.4))); - match possible_expression { - Ok(expression) => { - match calcule_booleen(expression, &HashMap::new()) { - Ok(booleen) => { - let resultat = configuration.0 && !(configuration.1 || !configuration.2) || !configuration.3 && configuration.4; - assert_eq!(booleen, resultat, "Calcul d'expression (booleen) donne un mauvais résultat : {}", booleen); - } - Err(raison) => { - panic!("Calcul d'expression (booleen) échoué, avec l'erreur : {}", raison); - } - } - } - Err(raison) => { - panic!("Détermination d'expression (booleen) échouée : {}", raison); - } - } - } - } - - #[test] - fn comparaison_booleen() { - let pendragon = Pendragon::nouveau(); - for (a, b) in [(1, 4), (2, 2), (3, 1), (0, 3)] { - let possible_expressions = vec![ - pendragon.elements_booleen(&format!("non six plus {} est supérieur à deux fois {}", nombre::nombre_comme_texte(a), nombre::nombre_comme_texte(b))), - pendragon.elements_booleen(&format!("six plus {} est inférieur à deux fois {}", nombre::nombre_comme_texte(a), nombre::nombre_comme_texte(b))), - pendragon.elements_booleen(&format!("six plus {} est supérieur ou égal à deux fois {}", nombre::nombre_comme_texte(a), nombre::nombre_comme_texte(b))), - pendragon.elements_booleen(&format!("non six plus {} est inférieur ou égal à deux fois {}", nombre::nombre_comme_texte(a), nombre::nombre_comme_texte(b))), - pendragon.elements_booleen(&format!("non \"deux\" est égal à \"{}\"", nombre::nombre_comme_texte(a))), - pendragon.elements_booleen(&format!("\"trois\" est différent de \"{}\"", nombre::nombre_comme_texte(a))), - ]; - let bonne_reponses = vec![ - !(6+a > 2*b), - (6+a < 2*b), - (6+a >= 2*b), - !(6+a <= 2*b), - !(a == 2), - (a != 3), - ]; - for index in 0..possible_expressions.len() { - match &possible_expressions[index] { - Ok(expression) => { - match calcule_booleen(expression.clone(), &HashMap::new()) { - Ok(booleen) => { - let reponse = bonne_reponses[index]; - assert_eq!(booleen, reponse, "Calcul d'expression (booleen) n°{} ({},{}) donne un mauvais résultat : {}, attendais {}", index, a, b, booleen, reponse); - } - Err(raison) => { - panic!("Calcul d'expression (booleen) échoué, avec l'erreur : {}", raison); - } - } - } - Err(raison) => { - panic!("Détermination d'expression (booleen) échouée : {}", raison); - } - } - } - } - } - - #[test] - fn combinaison_booleen() { - let pendragon = Pendragon::nouveau(); - for a in 0..5 { - for b in 0..5 { - for c in 0..5 { - for d in 1..5 { - for e in 0..5 { - let possible_expression = pendragon.elements_booleen(&format!("non ouvre la parenthèse six plus {} ferme la parenthèse est supérieur à deux fois {} et ouvre la parenthèse {} divisé par deux est inférieur à ouvre la parenthèse {} moins un ferme la parenthèse ou non \"deux\" est égal à \"{}\" ferme la parenthèse", - nombre::nombre_comme_texte(a), - nombre::nombre_comme_texte(b), - nombre::nombre_comme_texte(c), - nombre::nombre_comme_texte(d), - nombre::nombre_comme_texte(e), - )); - let bonne_reponse = !((6+a) > 2*b) && (c/2 < (d-1) || !(e == 2)); - match possible_expression { - Ok(expression) => { - match calcule_booleen(expression.clone(), &HashMap::new()) { - Ok(booleen) => { - assert_eq!(booleen, bonne_reponse, "Calcul d'expression (booleen) ({},{},{},{},{}) donne un mauvais résultat : {}, attendais {}", a, b, c, d, e, booleen, bonne_reponse); - } - Err(raison) => { - panic!("Calcul d'expression (booleen) échoué, avec l'erreur : {}", raison); - } - } - } - Err(raison) => { - panic!("Détermination d'expression (booleen) échouée : {}", raison); - } - } - } - } - } - } - } - } - - #[test] - fn erreur_calcul_booleen() { - let pendragon = Pendragon::nouveau(); - let textes_invalide = vec![ - "et faux", - "vrai et et faux", - "vrai ou ou faux", - "vrai et vrai faux", - "vrai et faux vrai", - "vrai et faux ouvre la parenthèse vrai ou faux ferme la parenthèse", - "vrai et ouvre la parenthèse et vrai ou faux ferme la parenthèse", - "vrai et ouvre la parenthèse vrai ou faux et ferme la parenthèse", - "vrai et ouvre la parenthèse vrai ou faux ferme la parenthèse vrai", - ]; - for texte in textes_invalide { - let Err(raison) = pendragon.elements_booleen(texte) else { - panic!("Devrait détecter une erreur pour '{}'", texte); - }; - let ErreurPendragon::OrdreCalculBooleen(_,_,_) = raison else { - panic!("Devrait détecter une erreur de calcul booléen pour '{}', a déclenché : {}", texte, raison); - }; - } - } -} diff --git a/src/pendragon/mod.rs b/src/pendragon/mod.rs index 48afec5..9501ca8 100644 --- a/src/pendragon/mod.rs +++ b/src/pendragon/mod.rs @@ -1,12 +1,11 @@ -use std::collections::HashMap; +use crate::display::ErreurPendragon; +use crate::display::ErreurCompilation; pub mod nombre; pub mod texte; pub mod booleen; pub mod structure; use structure::*; -pub mod debug; -use debug::*; pub struct Pendragon { pub programme: Programme, @@ -72,7 +71,7 @@ impl Pendragon { } Err(raison) => { erreurs.push(ErreurCompilation::nouvelle(index_ligne, ligne.into(), raison)); - pile_bloc.push(Bloc::nouveau(vec![Element::Booleen(false)])); + pile_bloc.push(Bloc::nouveau(vec![Element::Booleen(false)], false)); } } indentation_niveau += 1; @@ -113,7 +112,7 @@ impl Pendragon { } match parties[0] { - "Si" => Ok(Bloc::nouveau(self.elements_booleen(parties[1])?)), + "Si" => Ok(Bloc::nouveau(self.elements_booleen(parties[1])?, false)), autre => Err(ErreurPendragon::BlocInconnu(autre.into())), } } diff --git a/src/pendragon/nombre.rs b/src/pendragon/nombre.rs index 743dd9c..04ef3ea 100644 --- a/src/pendragon/nombre.rs +++ b/src/pendragon/nombre.rs @@ -1,10 +1,10 @@ use super::*; -const NOMS_UNITES: [&str; 10] = ["", "un", "deux", "trois", "quatre", "cinq", "six", "sept", "huit", "neuf"]; -const NOMS_UNITES_DIX: [&str; 10] = ["dix", "onze", "douze", "treize", "quatorze", "quinze", "seize", "dix-sept", "dix-huit", "dix-neuf"]; -const NOMS_DIZAINES: [&str; 9] = ["", "dix", "vingt", "trente", "quarante", "cinquante", "soixante", "x", "quatre-vingts"]; -const NOMS_SEPARATEURS: [&str; 7] = ["", "mille", "million", "milliard", "billion", "billiard", "trillion"]; -const UNION: &str = "-"; +pub const NOMS_UNITES: [&str; 10] = ["", "un", "deux", "trois", "quatre", "cinq", "six", "sept", "huit", "neuf"]; +pub const NOMS_UNITES_DIX: [&str; 10] = ["dix", "onze", "douze", "treize", "quatorze", "quinze", "seize", "dix-sept", "dix-huit", "dix-neuf"]; +pub const NOMS_DIZAINES: [&str; 9] = ["", "dix", "vingt", "trente", "quarante", "cinquante", "soixante", "x", "quatre-vingts"]; +pub const NOMS_SEPARATEURS: [&str; 7] = ["", "mille", "million", "milliard", "billion", "billiard", "trillion"]; +pub const UNION: &str = "-"; impl Pendragon { pub fn elements_nombre(&self, arguments: &str) -> Result, ErreurPendragon> { @@ -112,143 +112,6 @@ impl Pendragon { } } -pub fn affiche_nombre(expression: Vec, variables: &HashMap) -> Result { - let nombre = calcule_nombre(expression.clone(), variables)?; - Ok(nombre_comme_texte(nombre)) -} - -pub fn calcule_nombre(expression: Vec, variables: &HashMap) -> Result { - let mut pile: Vec = Vec::new(); - - for element in expression { - if let Element::Entier(nombre) = element { - pile.push(nombre); - continue; - } - if let Element::Variable(nom, _) = element { - let Some(variable) = variables.get(&nom) else { - return Err(ErreurPendragon::VariableInconnue(nom.into())) - }; - if let Element::Entier(nombre) = variable { - pile.push(*nombre); - continue - } else { - return Err(ErreurPendragon::MauvaisType(nom.into(), variable.type_element().nom(), "entier".into())) - } - } - let Element::Operateur(ref operateur) = element else { - return Err(ErreurPendragon::MauvaisArgument(format!("{}, attendais un opérateur", element))) - }; - let Some(nombre_a) = pile.pop() else { - return Err(ErreurPendragon::CalculEntier("la pile est vide".into())) - }; - let Some(nombre_b) = pile.pop() else { - return Err(ErreurPendragon::CalculEntier("la pile est vide".into())) - }; - match operateur { - Operateur::Plus => { - pile.push(nombre_b + nombre_a); - } - Operateur::Moins => { - if nombre_b < nombre_a { - return Err(ErreurPendragon::CalculEntier(format!("a essayé de soustraire '{}' à '{}'", nombre::nombre_comme_texte(nombre_a), nombre::nombre_comme_texte(nombre_b)))) - } - pile.push(nombre_b - nombre_a); - } - Operateur::Fois => { - pile.push(nombre_b * nombre_a); - } - Operateur::Divise => { - pile.push(nombre_b / nombre_a); - } - _ => return Err(ErreurPendragon::MauvaisArgument(format!("'{}', attendais un opérateur d'entiers", element))) - } - } - if pile.len() > 1 { - return Err(ErreurPendragon::CalculEntier("la pile n'est pas vide".into())) - } - Ok(pile[0]) -} - -pub fn nombre_comme_texte(nombre: usize) -> String { - if nombre == 0 { - return "zéro".to_string() - } - if nombre >= 10usize.pow(18) { - return "infini".to_string() - } - let mut groupes: Vec = vec![]; - let mut nombre = nombre; - while nombre > 0 { - groupes.insert(0, nombre % 1000); - nombre /= 1000; - } - let mut chaine: String = "".to_string(); - - for index in 0..groupes.len() { - if groupes[index] == 0 { - continue - } - let pluriel: &str = if (groupes.len() - index - 1 > 1) && groupes[index] > 1 { - "s" - } else { - "" - }; - if index < groupes.len() - 1 { - let union = if index > 0 {UNION} else {""}; - let chiffre = if groupes.len() - index - 1 == 1 && groupes[index] == 1 { // un mille - "".to_string() - } else { - petit_nombre_comme_texte(groupes[index]) + UNION - }; - chaine += &format!("{}{}{}{}", - union, - chiffre, - NOMS_SEPARATEURS[groupes.len() - index - 1], - pluriel, - ); - } else { - let union = if index > 0 {UNION} else {""}; - chaine += union; - chaine += &petit_nombre_comme_texte(groupes[index]); - } - } - chaine -} - -fn petit_nombre_comme_texte(nombre: usize) -> String { - let nombre = nombre.clamp(0, 999); - let centaine = nombre / 100; - let dizaine = (nombre % 100) / 10; - let unité = nombre % 10; - - let centaine_texte = if centaine > 1 { - format!("{}{}cent", NOMS_UNITES[centaine], UNION) - } else if centaine > 0 { - "cent".to_string() - } else { - "".to_string() - }; - - let décalage_dizaine = if [1, 7, 9].contains(&dizaine) {1} else {0}; - let dizaine_texte = NOMS_DIZAINES[dizaine - décalage_dizaine]; - let séparation = if unité == 1 && ![0, 1, 8, 9].contains(&dizaine) {UNION.to_string() + "et"} else {"".to_string()}; - let unité_texte = if [1, 7, 9].contains(&dizaine) {NOMS_UNITES_DIX[unité]} else {NOMS_UNITES[unité]}; - - let mut texte_nombre = format!("{}{}{}{}{}{}", centaine_texte, UNION, dizaine_texte, séparation, UNION, unité_texte); - - while texte_nombre.contains("--") { - texte_nombre = texte_nombre.replace("--","-"); - } - if texte_nombre.starts_with("-") { - texte_nombre = texte_nombre[1..texte_nombre.len()].to_string(); - } - if texte_nombre.ends_with("-") { - texte_nombre = texte_nombre[0..texte_nombre.len()-1].to_string(); - } - texte_nombre -} - pub fn texte_comme_nombre(texte: &str) -> Result { if texte == "zéro" { return Ok(Element::Entier(0)) @@ -360,97 +223,3 @@ fn texte_comme_petit_nombre(texte: &str) -> Result { Ok(nombre) } - - - - -// ----------------------------------------------------------------------- - - -#[cfg(test)] -mod test { - use std::collections::HashMap; - use super::*; - - #[test] - fn conversion_nombres_texte() { - for i in [0, 1, 42, 70, 123, 999, 1031, 1_001_091, 72_036_854_775_807usize, 2345678*987654].iter() { - let texte = nombre_comme_texte(*i); // Convert number to text - if texte.contains("--") { - panic!("Il y a deux tirets pour {} : {}", i, texte); - } - match texte_comme_nombre(&texte) { // Convert text back to number - Ok(nombre) => { - assert_eq!(Element::Entier(*i), nombre, "Nombre inexact : {}, texte : {}", i, texte); - } - Err(raison) => { - panic!("Conversion échouée pour : {}, avec l'erreur : {}", i, raison); - } - } - } - } - - #[test] - fn calcul_nombre() { - let pendragon = Pendragon::nouveau(); - let a = 2345678; - let b = 987654; - let c = 34523456; - let d = 45678; - let e = 2; - let possible_expression = pendragon.elements_nombre(&format!("{} fois {} plus ouvre la parenthèse {} moins {} ferme la parenthèse divisé par {}", - nombre_comme_texte(a), - nombre_comme_texte(b), - nombre_comme_texte(c), - nombre_comme_texte(d), - nombre_comme_texte(e))); - match possible_expression { - Ok(expression) => { - match calcule_nombre(expression, &HashMap::new()) { - Ok(nombre) => { - assert_eq!(nombre, a*b+(c-d)/e, "Calcul d'expression (entier) donne un mauvais résultat : {}", nombre); - } - Err(raison) => { - panic!("Calcul d'expression (entier) échoué, avec l'erreur : {}", raison); - } - } - } - Err(raison) => { - panic!("Détermination d'expression (entier) échouée : {}", raison); - } - } - } - - #[test] - fn erreur_calcul_nombre() { - let pendragon = Pendragon::nouveau(); - let textes_invalide = vec![ - "un un fois un", - "un plus fois un", - "un moins divisé par un", - "un fois un ouvre la parenthèse un plus un ferme la parenthèse", - "un fois ouvre la parenthèse plus un plus un ferme la parenthèse", - "un fois ouvre la parenthèse un plus un fois ferme la parenthèse", - "un fois ouvre la parenthèse un plus un ferme la parenthèse un", - ]; - for texte in textes_invalide { - let Err(raison) = pendragon.elements_nombre(texte) else { - panic!("Devrait détecter une erreur pour '{}'", texte); - }; - let ErreurPendragon::OrdreCalculEntier(_,_,_) = raison else { - panic!("Devrait détecter une erreur de calcul entier pour '{}', a déclenché : {}", texte, raison); - }; - } - } - - #[test] - fn nombre_invalide_et() { - let pendragon = Pendragon::nouveau(); - let Err(raison) = pendragon.elements_nombre("et") else { - panic!("Devrait détecter une erreur pour 'et'"); - }; - let ErreurPendragon::NombreInvalide(_) = raison else { - panic!("Devrait détecter une erreur de nombre invalide pour 'et', a déclenché : {}", raison); - }; - } -} diff --git a/src/pendragon/structure.rs b/src/pendragon/structure.rs index 640bfd0..aee1799 100644 --- a/src/pendragon/structure.rs +++ b/src/pendragon/structure.rs @@ -1,4 +1,3 @@ -use std::io; use std::collections::HashMap; use super::*; @@ -55,29 +54,22 @@ impl Programme { } Ok(()) } - - pub fn execute(&self) -> Result<(), ErreurPendragon> { - let mut variables_globales: HashMap = HashMap::new(); - for phrase in &self.contenu { - match phrase { - Phrase::Commande(commande) => commande.execute(&mut variables_globales)?, - Phrase::Bloc(bloc) => bloc.execute(&mut variables_globales)?, - } - } - Ok(()) - } } pub struct Bloc { pub condition: Vec, + pub repete: bool, pub contenu: Vec, + pub contenu_sinon: Vec, } impl Bloc { - pub fn nouveau(condition: Vec) -> Self { + pub fn nouveau(condition: Vec, repete: bool) -> Self { Self { condition, + repete, contenu: vec![], + contenu_sinon: vec![], } } @@ -85,21 +77,16 @@ impl Bloc { self.contenu.push(Phrase::Commande(commande)); } + pub fn ajoute_commande_sinon(&mut self, commande: Commande) { + self.contenu_sinon.push(Phrase::Commande(commande)); + } + pub fn ajoute_bloc(&mut self, bloc: Bloc) { self.contenu.push(Phrase::Bloc(bloc)); } - pub fn execute(&self, variables: &mut HashMap) -> Result<(), ErreurPendragon> { - if !booleen::calcule_booleen(self.condition.clone(), variables)? { - return Ok(()); - } - for phrase in &self.contenu { - match phrase { - Phrase::Commande(commande) => commande.execute(variables)?, - Phrase::Bloc(bloc) => bloc.execute(variables)?, - } - } - Ok(()) + pub fn ajoute_bloc_sinon(&mut self, bloc: Bloc) { + self.contenu_sinon.push(Phrase::Bloc(bloc)); } } @@ -115,32 +102,6 @@ pub enum Commande { Affiche(Vec), } -impl Commande { - fn execute(&self, variables: &mut HashMap) -> Result<(), ErreurPendragon> { - match self { - Commande::Definis(nom, type_element) => { - variables.insert(nom.to_string(), type_element.comme_element()); - } - Commande::Demande(nom) => { - let valeur = variables[nom].type_element().demande_valeur(&nom)?; - variables.insert(nom.to_string(), valeur); - } - Commande::Modifie(nom, expression) => { - let valeur = match variables[nom].type_element() { - TypeElement::Entier => Element::Entier(nombre::calcule_nombre(expression.clone(), variables)?), - TypeElement::Texte => Element::Texte(texte::calcule_texte(expression.clone(), variables)?), - TypeElement::Booleen => Element::Booleen(booleen::calcule_booleen(expression.clone(), variables)?), - }; - variables.insert(nom.to_string(), valeur); - } - Commande::Affiche(expression) => { - println!("{}", texte::calcule_texte(expression.to_vec(), variables)?); - } - } - Ok(()) - } -} - #[derive(PartialEq, Debug, Clone)] pub enum TypeElement { Entier, @@ -181,22 +142,6 @@ impl TypeElement { Self::Booleen => booleen::texte_comme_booleen(texte), } } - - pub fn demande_valeur(&self, nom: &str) -> Result { - loop { - println!("Quelle valeur pour {} ({}) ?", nom, self.nom()); - - let mut reponse = String::new(); - if let Err(raison) = io::stdin().read_line(&mut reponse) { - return Err(ErreurPendragon::Lecture(format!("{}", raison))); - } - - match self.texte_comme_element(reponse.trim()) { - Ok(element) => return Ok(element), - Err(raison) => eprintln!("Erreur : {}", raison), - } - } - } } #[derive(PartialEq, Debug, Clone)] @@ -219,28 +164,6 @@ impl Element { Self::Operateur(operateur) => operateur.type_element(), } } - - pub fn compare(&self, element: Element, comparaison: TypeComparaison) -> Result { - if let TypeComparaison::Egal = comparaison { - return Ok(*self == element) - } - if let TypeComparaison::Different = comparaison { - return Ok(*self != element) - } - let Self::Entier(nombre_a) = self else { - return Err(ErreurPendragon::ComparaisonInvalide(format!("comparaison numérique avec {}", self))) - }; - let Self::Entier(nombre_b) = element else { - return Err(ErreurPendragon::ComparaisonInvalide(format!("comparaison numérique avec {}", element))) - }; - match comparaison { - TypeComparaison::SuperieurEgal => Ok(*nombre_a >= nombre_b), - TypeComparaison::InferieurEgal => Ok(*nombre_a <= nombre_b), - TypeComparaison::Superieur => Ok(*nombre_a > nombre_b), - TypeComparaison::Inferieur => Ok(*nombre_a < nombre_b), - _ => Err(ErreurPendragon::ComparaisonInvalide("problème de logique".into())), - } - } } #[derive(Clone, Debug, PartialEq)] @@ -301,33 +224,6 @@ impl Comparaison { } return Err(ErreurPendragon::ComparaisonInvalide(format!("voulait comparer {} avec {}", element.type_element().nom(), type_comparaison))) } - - pub fn calcule(&self, variables: &HashMap) -> Result { - let Some(ref comparaison) = self.type_comparaison else { - return Err(ErreurPendragon::ComparaisonInvalide("la comparaison n'a pas de type".into())) - }; - let Some(element) = self.membre_a.first() else { - return Err(ErreurPendragon::ComparaisonInvalide("il n'y a pas de premier membre".into())) - }; - let (membre_a, membre_b) = match element.type_element() { - TypeElement::Entier => { - let membre_a = Element::Entier(nombre::calcule_nombre(self.membre_a.clone(), variables)?); - let membre_b = Element::Entier(nombre::calcule_nombre(self.membre_b.clone(), variables)?); - (membre_a, membre_b) - } - TypeElement::Texte => { - let membre_a = Element::Texte(texte::calcule_texte(self.membre_a.clone(), variables)?); - let membre_b = Element::Texte(texte::calcule_texte(self.membre_b.clone(), variables)?); - (membre_a, membre_b) - } - TypeElement::Booleen => { - let membre_a = Element::Booleen(booleen::calcule_booleen(self.membre_a.clone(), variables)?); - let membre_b = Element::Booleen(booleen::calcule_booleen(self.membre_b.clone(), variables)?); - (membre_a, membre_b) - } - }; - Ok(membre_a.compare(membre_b, comparaison.clone())?) - } } #[derive(Clone, Debug, PartialEq)] diff --git a/src/pendragon/texte.rs b/src/pendragon/texte.rs index ebc1288..f1efa04 100644 --- a/src/pendragon/texte.rs +++ b/src/pendragon/texte.rs @@ -79,125 +79,4 @@ impl Pendragon { }; Err(raison) } -} - -pub fn calcule_texte(expression: Vec, variables: &HashMap) -> Result { - let mut pile: Vec = Vec::new(); - let mut texte: String = String::new(); - - for element in expression { - let Element::Operateur(ref operateur) = element else { - pile.push(element); - continue; - }; - let Operateur::Puis = operateur else { - pile.push(element); - continue; - }; - let Some(element_pile) = pile.last() else { - continue; - }; - if let TypeElement::Booleen = element_pile.type_element() { - texte += &booleen::affiche_booleen(pile.clone(), variables)?; - pile = Vec::new(); - continue; - } - if let TypeElement::Entier = element_pile.type_element() { - texte += &nombre::affiche_nombre(pile.clone(), variables)?; - pile = Vec::new(); - continue; - } - match element_pile { - Element::Texte(contenu) => texte += contenu, - Element::Variable(nom, type_element) => { - let Some(variable) = variables.get(nom) else { - return Err(ErreurPendragon::VariableInconnue(nom.into())) - }; - let Element::Texte(contenu) = variable else { - return Err(ErreurPendragon::MauvaisType(nom.into(), type_element.nom(), "texte".into())) - }; - texte += &contenu; - } - autre => { - return Err(ErreurPendragon::MauvaisArgument(format!("{}", autre))) - } - } - pile = Vec::new(); - } - Ok(texte) -} - - - -// ----------------------------------------------------------------------- - - -#[cfg(test)] -mod test { - use std::collections::HashMap; - use super::*; - - #[test] - fn calcul_texte() { - let pendragon = Pendragon::nouveau(); - let a = 2345678; - let b = 987654; - - let possible_expression = pendragon.elements_texte(&format!("\"hello\" puis {} fois {} puis \"there\" puis vrai ou faux puis trois puis deux puis alinéa puis retour à la ligne", - nombre::nombre_comme_texte(a), - nombre::nombre_comme_texte(b))); - match possible_expression { - Ok(expression) => { - match calcule_texte(expression, &HashMap::new()) { - Ok(texte) => { - let vrai_texte = format!("hello{}therevraitroisdeux\t\n", nombre::nombre_comme_texte(a*b)); - assert_eq!(texte, vrai_texte, "Calcul d'expression (texte) donne un mauvais résultat : {}", texte); - } - Err(raison) => { - panic!("Calcul d'expression (texte) échoué, avec l'erreur : {}", raison); - } - } - } - Err(raison) => { - panic!("Détermination d'expression (texte) échouée : {}", raison); - } - } - } - - #[test] - fn conversion_texte() { - let pendragon = Pendragon::nouveau(); - let texte = "\"hello aaaa puis AERTY et ou fois six\""; - match pendragon.elements_texte(texte) { - Ok(expression) => { - if expression.len() != 2 { - panic!("L'expression (texte) devrait contenir deux éléments (texte et puis), contient : {:?}", expression); - } - assert_eq!(expression[0], Element::Texte(texte[1..texte.len()-1].into()), "Calcul d'expression (texte) donne un mauvais résultat : {}", texte); - } - Err(raison) => { - panic!("Conversion échouée (texte) : {}", raison); - } - } - } - - #[test] - fn erreur_conversion_texte() { - let pendragon = Pendragon::nouveau(); - let textes = vec![ - "trois puis puis un", - "\" test", - "puis", - "un puis", - "puis un", - ]; - for texte in textes { - let Err(raison) = pendragon.elements_texte(texte) else { - panic!("Ne devrait pas réussir à convertir le texte '{}'", texte); - }; - let ErreurPendragon::TexteInvalide(_) = raison else { - panic!("Erreur imprévue pour convertir le texte '{}' : {}", texte, raison); - }; - } - } } \ No newline at end of file diff --git a/src/sophie/booleen.rs b/src/sophie/booleen.rs new file mode 100644 index 0000000..3cda8b9 --- /dev/null +++ b/src/sophie/booleen.rs @@ -0,0 +1,68 @@ +use super::*; + +pub fn affiche_booleen(expression: Vec, variables: &HashMap) -> Result { + let booleen = calcule_booleen(expression.clone(), variables)?; + Ok(booleen_comme_texte(booleen)) +} + +pub fn calcule_booleen(expression: Vec, variables: &HashMap) -> Result { + let mut pile: Vec = Vec::new(); + + for element in expression { + if let Element::Booleen(booleen) = element { + pile.push(booleen); + continue; + } + if let Element::Variable(nom, _) = element { + let Some(variable) = variables.get(&nom) else { + return Err(ErreurPendragon::VariableInconnue(nom.into())) + }; + if let Element::Booleen(booleen) = variable { + pile.push(*booleen); + continue + } else { + return Err(ErreurPendragon::MauvaisType(nom.into(), variable.type_element().nom(), "booleen".into())) + } + } + if let Element::Comparaison(comparaison) = element { + pile.push(comparaison.calcule(variables)?); + continue + } + let Element::Operateur(ref operateur) = element else { + return Err(ErreurPendragon::MauvaisArgument(format!("{}, attendais un opérateur", element))) + }; + let Some(booleen_a) = pile.pop() else { + return Err(ErreurPendragon::CalculBooleen("la pile est vide".into())) + }; + match operateur { + Operateur::Non => { + pile.push(!booleen_a); + } + Operateur::Et => { + let Some(booleen_b) = pile.pop() else { + return Err(ErreurPendragon::CalculBooleen("la pile est vide".into())) + }; + pile.push(booleen_a && booleen_b); + } + Operateur::Ou => { + let Some(booleen_b) = pile.pop() else { + return Err(ErreurPendragon::CalculBooleen("la pile est vide".into())) + }; + pile.push(booleen_a || booleen_b); + } + _ => return Err(ErreurPendragon::MauvaisArgument(format!("{}, attendais un opérateur booléen", element))) + } + } + if pile.len() > 1 { + return Err(ErreurPendragon::CalculBooleen("il reste plusieurs éléments dans la pile".into())) + } + Ok(pile[0]) +} + +pub fn booleen_comme_texte(booleen: bool) -> String { + if booleen { + "vrai".into() + } else { + "faux".into() + } +} \ No newline at end of file diff --git a/src/sophie/mod.rs b/src/sophie/mod.rs new file mode 100644 index 0000000..e14f1cb --- /dev/null +++ b/src/sophie/mod.rs @@ -0,0 +1,148 @@ +use std::io; +use std::collections::HashMap; +use crate::pendragon::structure::*; +use crate::display::ErreurPendragon; + +pub mod booleen; +pub mod texte; +pub mod nombre; + +impl Programme { + pub fn execute(&self) -> Result<(), ErreurPendragon> { + let mut variables_globales: HashMap = HashMap::new(); + for phrase in &self.contenu { + match phrase { + Phrase::Commande(commande) => commande.execute(&mut variables_globales)?, + Phrase::Bloc(bloc) => bloc.execute(&mut variables_globales)?, + } + } + Ok(()) + } +} + +impl Bloc { + pub fn execute(&self, variables: &mut HashMap) -> Result<(), ErreurPendragon> { + if booleen::calcule_booleen(self.condition.clone(), variables)? { + for phrase in &self.contenu { + match phrase { + Phrase::Commande(commande) => commande.execute(variables)?, + Phrase::Bloc(bloc) => bloc.execute(variables)?, + } + } + while booleen::calcule_booleen(self.condition.clone(), variables)? && self.repete { + for phrase in &self.contenu { + match phrase { + Phrase::Commande(commande) => commande.execute(variables)?, + Phrase::Bloc(bloc) => bloc.execute(variables)?, + } + } + } + } else if self.contenu_sinon.len() > 0 { + for phrase in &self.contenu_sinon { + match phrase { + Phrase::Commande(commande) => commande.execute(variables)?, + Phrase::Bloc(bloc) => bloc.execute(variables)?, + } + } + } + Ok(()) + } +} + +impl Commande { + fn execute(&self, variables: &mut HashMap) -> Result<(), ErreurPendragon> { + match self { + Commande::Definis(nom, type_element) => { + variables.insert(nom.to_string(), type_element.comme_element()); + } + Commande::Demande(nom) => { + let valeur = variables[nom].type_element().demande_valeur(&nom)?; + variables.insert(nom.to_string(), valeur); + } + Commande::Modifie(nom, expression) => { + let valeur = match variables[nom].type_element() { + TypeElement::Entier => Element::Entier(nombre::calcule_nombre(expression.clone(), variables)?), + TypeElement::Texte => Element::Texte(texte::calcule_texte(expression.clone(), variables)?), + TypeElement::Booleen => Element::Booleen(booleen::calcule_booleen(expression.clone(), variables)?), + }; + variables.insert(nom.to_string(), valeur); + } + Commande::Affiche(expression) => { + println!("{}", texte::calcule_texte(expression.to_vec(), variables)?); + } + } + Ok(()) + } +} + +impl TypeElement { + pub fn demande_valeur(&self, nom: &str) -> Result { + loop { + println!("Quelle valeur pour {} ({}) ?", nom, self.nom()); + + let mut reponse = String::new(); + if let Err(raison) = io::stdin().read_line(&mut reponse) { + return Err(ErreurPendragon::Lecture(format!("{}", raison))); + } + + match self.texte_comme_element(reponse.trim()) { + Ok(element) => return Ok(element), + Err(raison) => eprintln!("Erreur : {}", raison), + } + } + } +} + + +impl Element { + pub fn compare(&self, element: Element, comparaison: TypeComparaison) -> Result { + if let TypeComparaison::Egal = comparaison { + return Ok(*self == element) + } + if let TypeComparaison::Different = comparaison { + return Ok(*self != element) + } + let Self::Entier(nombre_a) = self else { + return Err(ErreurPendragon::ComparaisonInvalide(format!("comparaison numérique avec {}", self))) + }; + let Self::Entier(nombre_b) = element else { + return Err(ErreurPendragon::ComparaisonInvalide(format!("comparaison numérique avec {}", element))) + }; + match comparaison { + TypeComparaison::SuperieurEgal => Ok(*nombre_a >= nombre_b), + TypeComparaison::InferieurEgal => Ok(*nombre_a <= nombre_b), + TypeComparaison::Superieur => Ok(*nombre_a > nombre_b), + TypeComparaison::Inferieur => Ok(*nombre_a < nombre_b), + _ => Err(ErreurPendragon::ComparaisonInvalide("problème de logique".into())), + } + } +} + +impl Comparaison { + pub fn calcule(&self, variables: &HashMap) -> Result { + let Some(ref comparaison) = self.type_comparaison else { + return Err(ErreurPendragon::ComparaisonInvalide("la comparaison n'a pas de type".into())) + }; + let Some(element) = self.membre_a.first() else { + return Err(ErreurPendragon::ComparaisonInvalide("il n'y a pas de premier membre".into())) + }; + let (membre_a, membre_b) = match element.type_element() { + TypeElement::Entier => { + let membre_a = Element::Entier(nombre::calcule_nombre(self.membre_a.clone(), variables)?); + let membre_b = Element::Entier(nombre::calcule_nombre(self.membre_b.clone(), variables)?); + (membre_a, membre_b) + } + TypeElement::Texte => { + let membre_a = Element::Texte(texte::calcule_texte(self.membre_a.clone(), variables)?); + let membre_b = Element::Texte(texte::calcule_texte(self.membre_b.clone(), variables)?); + (membre_a, membre_b) + } + TypeElement::Booleen => { + let membre_a = Element::Booleen(booleen::calcule_booleen(self.membre_a.clone(), variables)?); + let membre_b = Element::Booleen(booleen::calcule_booleen(self.membre_b.clone(), variables)?); + (membre_a, membre_b) + } + }; + Ok(membre_a.compare(membre_b, comparaison.clone())?) + } +} \ No newline at end of file diff --git a/src/sophie/nombre.rs b/src/sophie/nombre.rs new file mode 100644 index 0000000..f642fad --- /dev/null +++ b/src/sophie/nombre.rs @@ -0,0 +1,139 @@ +use crate::nombre::*; +use super::*; + +pub fn affiche_nombre(expression: Vec, variables: &HashMap) -> Result { + let nombre = calcule_nombre(expression.clone(), variables)?; + Ok(nombre_comme_texte(nombre)) +} + +pub fn calcule_nombre(expression: Vec, variables: &HashMap) -> Result { + let mut pile: Vec = Vec::new(); + + for element in expression { + if let Element::Entier(nombre) = element { + pile.push(nombre); + continue; + } + if let Element::Variable(nom, _) = element { + let Some(variable) = variables.get(&nom) else { + return Err(ErreurPendragon::VariableInconnue(nom.into())) + }; + if let Element::Entier(nombre) = variable { + pile.push(*nombre); + continue + } else { + return Err(ErreurPendragon::MauvaisType(nom.into(), variable.type_element().nom(), "entier".into())) + } + } + let Element::Operateur(ref operateur) = element else { + return Err(ErreurPendragon::MauvaisArgument(format!("{}, attendais un opérateur", element))) + }; + let Some(nombre_a) = pile.pop() else { + return Err(ErreurPendragon::CalculEntier("la pile est vide".into())) + }; + let Some(nombre_b) = pile.pop() else { + return Err(ErreurPendragon::CalculEntier("la pile est vide".into())) + }; + match operateur { + Operateur::Plus => { + pile.push(nombre_b + nombre_a); + } + Operateur::Moins => { + if nombre_b < nombre_a { + return Err(ErreurPendragon::CalculEntier(format!("a essayé de soustraire '{}' à '{}'", nombre_comme_texte(nombre_a), nombre_comme_texte(nombre_b)))) + } + pile.push(nombre_b - nombre_a); + } + Operateur::Fois => { + pile.push(nombre_b * nombre_a); + } + Operateur::Divise => { + pile.push(nombre_b / nombre_a); + } + _ => return Err(ErreurPendragon::MauvaisArgument(format!("'{}', attendais un opérateur d'entiers", element))) + } + } + if pile.len() > 1 { + return Err(ErreurPendragon::CalculEntier("la pile n'est pas vide".into())) + } + Ok(pile[0]) +} + +pub fn nombre_comme_texte(nombre: usize) -> String { + if nombre == 0 { + return "zéro".to_string() + } + if nombre >= 10usize.pow(18) { + return "infini".to_string() + } + let mut groupes: Vec = vec![]; + let mut nombre = nombre; + while nombre > 0 { + groupes.insert(0, nombre % 1000); + nombre /= 1000; + } + let mut chaine: String = "".to_string(); + + for index in 0..groupes.len() { + if groupes[index] == 0 { + continue + } + let pluriel: &str = if (groupes.len() - index - 1 > 1) && groupes[index] > 1 { + "s" + } else { + "" + }; + if index < groupes.len() - 1 { + let union = if index > 0 {UNION} else {""}; + let chiffre = if groupes.len() - index - 1 == 1 && groupes[index] == 1 { // un mille + "".to_string() + } else { + petit_nombre_comme_texte(groupes[index]) + UNION + }; + chaine += &format!("{}{}{}{}", + union, + chiffre, + NOMS_SEPARATEURS[groupes.len() - index - 1], + pluriel, + ); + } else { + let union = if index > 0 {UNION} else {""}; + chaine += union; + chaine += &petit_nombre_comme_texte(groupes[index]); + } + } + chaine +} + +fn petit_nombre_comme_texte(nombre: usize) -> String { + let nombre = nombre.clamp(0, 999); + let centaine = nombre / 100; + let dizaine = (nombre % 100) / 10; + let unité = nombre % 10; + + let centaine_texte = if centaine > 1 { + format!("{}{}cent", NOMS_UNITES[centaine], UNION) + } else if centaine > 0 { + "cent".to_string() + } else { + "".to_string() + }; + + let décalage_dizaine = if [1, 7, 9].contains(&dizaine) {1} else {0}; + let dizaine_texte = NOMS_DIZAINES[dizaine - décalage_dizaine]; + let séparation = if unité == 1 && ![0, 1, 8, 9].contains(&dizaine) {UNION.to_string() + "et"} else {"".to_string()}; + let unité_texte = if [1, 7, 9].contains(&dizaine) {NOMS_UNITES_DIX[unité]} else {NOMS_UNITES[unité]}; + + let mut texte_nombre = format!("{}{}{}{}{}{}", centaine_texte, UNION, dizaine_texte, séparation, UNION, unité_texte); + + while texte_nombre.contains("--") { + texte_nombre = texte_nombre.replace("--","-"); + } + if texte_nombre.starts_with("-") { + texte_nombre = texte_nombre[1..texte_nombre.len()].to_string(); + } + if texte_nombre.ends_with("-") { + texte_nombre = texte_nombre[0..texte_nombre.len()-1].to_string(); + } + texte_nombre +} \ No newline at end of file diff --git a/src/sophie/texte.rs b/src/sophie/texte.rs new file mode 100644 index 0000000..29fb243 --- /dev/null +++ b/src/sophie/texte.rs @@ -0,0 +1,47 @@ +use super::*; + +pub fn calcule_texte(expression: Vec, variables: &HashMap) -> Result { + let mut pile: Vec = Vec::new(); + let mut texte: String = String::new(); + + for element in expression { + let Element::Operateur(ref operateur) = element else { + pile.push(element); + continue; + }; + let Operateur::Puis = operateur else { + pile.push(element); + continue; + }; + let Some(element_pile) = pile.last() else { + continue; + }; + if let TypeElement::Booleen = element_pile.type_element() { + texte += &booleen::affiche_booleen(pile.clone(), variables)?; + pile = Vec::new(); + continue; + } + if let TypeElement::Entier = element_pile.type_element() { + texte += &nombre::affiche_nombre(pile.clone(), variables)?; + pile = Vec::new(); + continue; + } + match element_pile { + Element::Texte(contenu) => texte += contenu, + Element::Variable(nom, type_element) => { + let Some(variable) = variables.get(nom) else { + return Err(ErreurPendragon::VariableInconnue(nom.into())) + }; + let Element::Texte(contenu) = variable else { + return Err(ErreurPendragon::MauvaisType(nom.into(), type_element.nom(), "texte".into())) + }; + texte += &contenu; + } + autre => { + return Err(ErreurPendragon::MauvaisArgument(format!("{}", autre))) + } + } + pile = Vec::new(); + } + Ok(texte) +} \ No newline at end of file