Repository: fniessink/toisto (language: python)

Issue:
Add present perfect tense
And past perfect. Might be simpler to add both at the same time.


Discussion / hints:

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8fcefd84..11562526 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
 
 ### Added
 
+- Add support for the present perfect tense. Closes [#632](https://github.com/fniessink/toisto/issues/632).
 - When showing examples, also show the meaning of the examples. Closes [#638](https://github.com/fniessink/toisto/issues/638).
 
 ## 0.18.1 - 2024-04-09
diff --git a/docs/software.md b/docs/software.md
index 8bf93a9f..48a78a02 100644
--- a/docs/software.md
+++ b/docs/software.md
@@ -442,7 +442,7 @@ When concepts are verbs, infinitives can be specified as follows:
 
 ### Tenses
 
-When concepts are verbs, the present tense and the past tense can be specified as follows:
+When concepts are verbs, the present tense, past tense, and present perfect tense can be specified as follows:
 
 ```json
 {
@@ -467,6 +467,16 @@ When concepts are verbs, the present tense and the past tense can be specified a
                 "second person": "..."
             },
             "plural": "..."
+        },
+        "present perfect tense": {
+            "singular": {
+                "first person": {
+                    "en": "I have been",
+                    "fi": "minä olen ollut",
+                },
+                "second person": "..."
+            },
+            "plural": "..."
         }
     }
 }
diff --git a/src/concepts/verbs/be.json b/src/concepts/verbs/be.json
index de602c2e..844d7460 100644
--- a/src/concepts/verbs/be.json
+++ b/src/concepts/verbs/be.json
@@ -93,6 +93,48 @@
                             "nl": "zij waren"
                         }
                     }
+                },
+                "present perfect tense": {
+                    "singular": {
+                        "first person": {
+                            "en": "I have been",
+                            "fi": "minä olen ollut",
+                            "nl": "ik ben geweest"
+                        },
+                        "second person": {
+                            "en": "you have been;singular",
+                            "fi": "sinä olet ollut",
+                            "nl": "jij bent geweest"
+                        },
+                        "third person": {
+                            "fi": "hän on ollut",
+                            "female": {
+                                "en": "she has been",
+                                "nl": "zij is geweest"
+                            },
+                            "male": {
+                                "en": "he has been",
+                                "nl": "hij is geweest"
+                            }
+                        }
+                    },
+                    "plural": {
+                        "first person": {
+                            "en": "we have been",
+                            "fi": "me olemme olleet",
+                            "nl": "wij zijn geweest"
+                        },
+                        "second person": {
+                            "en": "you have been;plural",
+                            "fi": "te olette olleet",
+                            "nl": "jullie zijn geweest"
+                        },
+                        "third person": {
+                            "en": "they have been",
+                            "fi": "he ovat olleet",
+                            "nl": "zij zijn geweest"
+                        }
+                    }
                 }
             },
             "negative": {
@@ -221,6 +263,69 @@
                             ]
                         }
                     }
+                },
+                "present perfect tense": {
+                    "singular": {
+                        "first person": {
+                            "en": "I have not been",
+                            "fi": "minä en ole ollut",
+                            "nl": [
+                                "ik ben niet geweest",
+                                "ik ben geen geweest"
+                            ]
+                        },
+                        "second person": {
+                            "en": "you have not been;singular",
+                            "fi": "sinä et ole ollut",
+                            "nl": [
+                                "jij bent niet geweest",
+                                "jij bent geen geweest"
+                            ]
+                        },
+                        "third person": {
+                            "fi": "hän ei ole ollut",
+                            "female": {
+                                "en": "she has not been",
+                                "nl": [
+                                    "zij is niet geweest",
+                                    "zij is geen geweest"
+                                ]
+                            },
+                            "male": {
+                                "en": "he has not been",
+                                "nl": [
+                                    "hij is niet geweest",
+                                    "hij is geen geweest"
+                                ]
+                            }
+                        }
+                    },
+                    "plural": {
+                        "first person": {
+                            "en": "we have not been",
+                            "fi": "me emme ole olleet",
+                            "nl": [
+                                "wij zijn niet geweest",
+                                "wij zijn geen geweest"
+                            ]
+                        },
+                        "second person": {
+                            "en": "you have not been;plural",
+                            "fi": "te ette ole olleet",
+                            "nl": [
+                                "jullie zijn niet geweest",
+                                "jullie zijn geen geweest"
+                            ]
+                        },
+                        "third person": {
+                            "en": "they have not been",
+                            "fi": "he eivät ole olleet",
+                            "nl": [
+                                "zij zijn niet geweest",
+                                "zij zijn geen geweest"
+                            ]
+                        }
+                    }
                 }
             }
         },
@@ -309,6 +414,48 @@
                             "nl": "waren zij?"
                         }
                     }
+                },
+                "present perfect tense": {
+                    "singular": {
+                        "first person": {
+                            "en": "have I been?",
+                            "fi": "olenko minä ollut?",
+                            "nl": "ben ik geweest?"
+                        },
+                        "second person": {
+                            "en": "have you been?;singular",
+                            "fi": "oletko sinä ollut?",
+                            "nl": "ben jij geweest?"
+                        },
+                        "third person": {
+                            "fi": "onko hän ollut?",
+                            "female": {
+                                "en": "has she been?",
+                                "nl": "is zij geweest?"
+                            },
+                            "male": {
+                                "en": "has he been?",
+                                "nl": "is hij geweest?"
+                            }
+                        }
+                    },
+                    "plural": {
+                        "first person": {
+                            "en": "have we been?",
+                            "fi": "olemmeko me olleet?",
+                            "nl": "zijn wij geweest?"
+                        },
+                        "second person": {
+                            "en": "have you been?;plural",
+                            "fi": "oletteko te olleet?",
+                            "nl": "zijn jullie geweest?"
+                        },
+                        "third person": {
+                            "en": "have they been?",
+                            "fi": "ovatko he olleet?",
+                            "nl": "zijn zij geweest?"
+                        }
+                    }
                 }
             },
             "negative": {
@@ -437,6 +584,69 @@
                             ]
                         }
                     }
+                },
+                "present perfect tense": {
+                    "singular": {
+                        "first person": {
+                            "en": "have I not been?",
+                            "fi": "enkö minä ole ollut?",
+                            "nl": [
+                                "ben ik niet geweest?",
+                                "ben ik geen geweest?"
+                            ]
+                        },
+                        "second person": {
+                            "en": "are you not?;singular",
+                            "fi": "etkö sinä ole ollut?",
+                            "nl": [
+                                "ben jij niet geweest?",
+                                "ben jij geen geweest?"
+                            ]
+                        },
+                        "third person": {
+                            "fi": "eikö hän ole ollut?",
+                            "female": {
+                                "en": "has she not been?",
+                                "nl": [
+                                    "is zij niet geweest?",
+                                    "is zij geen geweest?"
+                                ]
+                            },
+                            "male": {
+                                "en": "has he not been?",
+                                "nl": [
+                                    "is hij niet geweest?",
+                                    "is hij geen geweest?"
+                                ]
+                            }
+                        }
+                    },
+                    "plural": {
+                        "first person": {
+                            "en": "have we not been?",
+                            "fi": "emmekö me ole olleet?",
+                            "nl": [
+                                "zijn wij niet geweest?",
+                                "zijn we geen geweest?"
+                            ]
+                        },
+                        "second person": {
+                            "en": "have you not been?;plural",
+                            "fi": "ettekö te ole olleet?",
+                            "nl": [
+                                "zijn jullie niet geweest?",
+                                "zijn jullie geen geweest?"
+                            ]
+                        },
+                        "third person": {
+                            "en": "have they not been?",
+                            "fi": "eivätkö he ole olleet?",
+                            "nl": [
+                                "zijn zij niet geweest?",
+                                "zijn zij geen geweest?"
+                            ]
+                        }
+                    }
                 }
             }
         }
@@ -448,4 +658,4 @@
             "nl": "lonen"
         }
     }
-}
\ No newline at end of file
+}
diff --git a/src/toisto/model/language/grammar.py b/src/toisto/model/language/grammar.py
index 864c5cb2..adb010f3 100644
--- a/src/toisto/model/language/grammar.py
+++ b/src/toisto/model/language/grammar.py
@@ -7,7 +7,7 @@
 GrammaticalPerson = Literal["first person", "second person", "third person"]
 DegreeOfComparison = Literal["positive degree", "comparative degree", "superlative degree"]
 Diminutive = Literal["root", "diminutive"]
-Tense = Literal["present tense", "past tense"]
+Tense = Literal["present tense", "past tense", "present perfect tense"]
 GrammaticalMood = Literal["declarative", "interrogative", "imperative"]
 GrammaticalPolarity = Literal["affirmative", "negative"]
 Number = Literal["cardinal", "ordinal"]
diff --git a/src/toisto/model/quiz/quiz.py b/src/toisto/model/quiz/quiz.py
index 45780a82..363e0343 100644
--- a/src/toisto/model/quiz/quiz.py
+++ b/src/toisto/model/quiz/quiz.py
@@ -33,6 +33,7 @@
     "give infinitive",
     "give present tense",
     "give past tense",
+    "give present perfect tense",
     "make declarative",
     "make interrogative",
     "make imperative",
@@ -60,6 +61,7 @@
     "infinitive": "give infinitive",
     "present tense": "give present tense",
     "past tense": "give past tense",
+    "present perfect tense": "give present perfect tense",
     "declarative": "make declarative",
     "interrogative": "make interrogative",
     "imperative": "make imperative",


Test patch:
diff --git a/tests/toisto/model/quiz/test_quiz.py b/tests/toisto/model/quiz/test_quiz.py
index 6ed983f0..9d482e8b 100644
--- a/tests/toisto/model/quiz/test_quiz.py
+++ b/tests/toisto/model/quiz/test_quiz.py
@@ -98,6 +98,7 @@ def test_instructions(self):
             "Give the [underline]infinitive[/underline] in Finnish",
             "Give the [underline]present tense[/underline] in Finnish",
             "Give the [underline]past tense[/underline] in Finnish",
+            "Give the [underline]present perfect tense[/underline] in Finnish",
             "Give the [underline]declarative[/underline] in Finnish",
             "Give the [underline]interrogative[/underline] in Finnish",
             "Give the [underline]imperative[/underline] in Finnish",
diff --git a/tests/toisto/model/quiz/test_quiz_factory.py b/tests/toisto/model/quiz/test_quiz_factory.py
index ab631b58..fc5fa604 100644
--- a/tests/toisto/model/quiz/test_quiz_factory.py
+++ b/tests/toisto/model/quiz/test_quiz_factory.py
@@ -2,6 +2,7 @@
 
 from toisto.model.language import EN, FI, NL
 from toisto.model.language.concept import Concept
+from toisto.model.language.grammar import Tense
 from toisto.model.language.label import Label
 from toisto.model.quiz.quiz import Quizzes
 from toisto.model.quiz.quiz_factory import create_quizzes, grammatical_quiz_types
@@ -23,21 +24,25 @@ def create_verb_with_person(self) -> Concept:
             },
         )
 
-    def create_verb_with_tense_and_person(self) -> Concept:
+    def create_verb_with_tense_and_person(self, *tense: Tense) -> Concept:
         """Create a verb with grammatical person nested within tense."""
-        return self.create_concept(
-            "to eat",
-            {
-                "present tense": {
-                    "singular": dict(en="I eat", nl="ik eet"),
-                    "plural": dict(en="we eat", nl="wij eten"),
-                },
-                "past tense": {
-                    "singular": dict(en="I ate", nl="ik at"),
-                    "plural": dict(en="we ate", nl="wij aten"),
-                },
-            },
-        )
+        concept_dict: dict[str, object] = {}
+        if "present tense" in tense:
+            concept_dict["present tense"] = {
+                "singular": dict(en="I eat", nl="ik eet"),
+                "plural": dict(en="we eat", nl="wij eten"),
+            }
+        if "past tense" in tense:
+            concept_dict["past tense"] = {
+                "singular": dict(en="I ate", nl="ik at"),
+                "plural": dict(en="we ate", nl="wij aten"),
+            }
+        if "present perfect tense" in tense:
+            concept_dict["present perfect tense"] = {
+                "singular": dict(en="I have eaten", nl="ik heb gegeten"),
+                "plural": dict(en="we have eaten", nl="wij hebben gegeten"),
+            }
+        return self.create_concept("to eat", concept_dict)
 
     def create_verb_with_number_and_person(self) -> Concept:
         """Create a verb with grammatical number nested with grammatical person."""
@@ -926,10 +931,10 @@ def test_tense_nested_with_grammatical_number_nested_and_grammatical_person(self
 class TenseQuizzesTest(QuizFactoryTestCase):
     """Unit tests for concepts with tenses."""
 
-    def test_tense_nested_with_grammatical_person(self):
-        """Test that quizzes can be generated for tense nested with grammatical person."""
+    def test_present_and_past_tense_nested_with_grammatical_person(self):
+        """Test that quizzes can be generated for present and past tense nested with grammatical person."""
         self.language_pair = NL_EN
-        concept = self.create_verb_with_tense_and_person()
+        concept = self.create_verb_with_tense_and_person("present tense", "past tense")
         present, past = concept.constituents
         present_singular, present_plural, past_singular, past_plural = concept.leaf_concepts(NL)
         self.assertSetEqual(
@@ -962,6 +967,62 @@ def test_tense_nested_with_grammatical_person(self):
             create_quizzes(NL_EN, concept),
         )
 
+    def test_present_past_and_perfect_tense_nested_with_grammatical_person(self):
+        """Test that quizzes can be generated for present, past, and perfect tense nested with grammatical person."""
+        self.language_pair = NL_EN
+        concept = self.create_verb_with_tense_and_person("present tense", "past tense", "present perfect tense")
+        present, past, perfect = concept.constituents
+        present_singular, present_plural, past_singular, past_plural, perfect_singular, perfect_plural = (
+            concept.leaf_concepts(NL)
+        )
+        self.assertSetEqual(
+            {
+                self.create_quiz(present_singular, "ik eet", ["I eat"], "read"),
+                self.create_quiz(present_singular, "ik eet", ["ik eet"], "dictate"),
+                self.create_quiz(present_singular, "ik eet", ["I eat"], "interpret"),
+                self.create_quiz(present_singular, "I eat", ["ik eet"], "write"),
+                self.create_quiz(present_plural, "wij eten", ["we eat"], "read"),
+                self.create_quiz(present_plural, "wij eten", ["wij eten"], "dictate"),
+                self.create_quiz(present_plural, "wij eten", ["we eat"], "interpret"),
+                self.create_quiz(present_plural, "we eat", ["wij eten"], "write"),
+                self.create_quiz(present, "ik eet", ["wij eten"], "pluralize"),
+                self.create_quiz(present, "wij eten", ["ik eet"], "singularize"),
+                self.create_quiz(past_singular, "ik at", ["I ate"], "read"),
+                self.create_quiz(past_singular, "ik at", ["ik at"], "dictate"),
+                self.create_quiz(past_singular, "ik at", ["I ate"], "interpret"),
+                self.create_quiz(past_singular, "I ate", ["ik at"], "write"),
+                self.create_quiz(past_plural, "wij aten", ["we ate"], "read"),
+                self.create_quiz(past_plural, "wij aten", ["wij aten"], "dictate"),
+                self.create_quiz(past_plural, "wij aten", ["we ate"], "interpret"),
+                self.create_quiz(past_plural, "we ate", ["wij aten"], "write"),
+                self.create_quiz(past, "ik at", ["wij aten"], "pluralize"),
+                self.create_quiz(past, "wij aten", ["ik at"], "singularize"),
+                self.create_quiz(perfect_singular, "ik heb gegeten", ["I have eaten"], "read"),
+                self.create_quiz(perfect_singular, "ik heb gegeten", ["ik heb gegeten"], "dictate"),
+                self.create_quiz(perfect_singular, "ik heb gegeten", ["I have eaten"], "interpret"),
+                self.create_quiz(perfect_singular, "I have eaten", ["ik heb gegeten"], "write"),
+                self.create_quiz(perfect_plural, "wij hebben gegeten", ["we have eaten"], "read"),
+                self.create_quiz(perfect_plural, "wij hebben gegeten", ["wij hebben gegeten"], "dictate"),
+                self.create_quiz(perfect_plural, "wij hebben gegeten", ["we have eaten"], "interpret"),
+                self.create_quiz(perfect_plural, "we have eaten", ["wij hebben gegeten"], "write"),
+                self.create_quiz(perfect, "ik heb gegeten", ["wij hebben gegeten"], "pluralize"),
+                self.create_quiz(perfect, "wij hebben gegeten", ["ik heb gegeten"], "singularize"),
+                self.create_quiz(concept, "ik eet", ["ik at"], "give past tense"),
+                self.create_quiz(concept, "wij eten", ["wij aten"], "give past tense"),
+                self.create_quiz(concept, "ik at", ["ik eet"], "give present tense"),
+                self.create_quiz(concept, "wij aten", ["wij eten"], "give present tense"),
+                self.create_quiz(concept, "ik heb gegeten", ["ik eet"], "give present tense"),
+                self.create_quiz(concept, "wij hebben gegeten", ["wij eten"], "give present tense"),
+                self.create_quiz(concept, "ik heb gegeten", ["ik at"], "give past tense"),
+                self.create_quiz(concept, "wij hebben gegeten", ["wij aten"], "give past tense"),
+                self.create_quiz(concept, "ik eet", ["ik heb gegeten"], "give present perfect tense"),
+                self.create_quiz(concept, "wij eten", ["wij hebben gegeten"], "give present perfect tense"),
+                self.create_quiz(concept, "ik at", ["ik heb gegeten"], "give present perfect tense"),
+                self.create_quiz(concept, "wij aten", ["wij hebben gegeten"], "give present perfect tense"),
+            },
+            create_quizzes(NL_EN, concept),
+        )
+
     def test_tense_nested_with_grammatical_person_and_infinitive(self):
         """Test that quizzes can be generated for tense nested with grammatical person and infinitive."""
         self.language_pair = NL_EN
@@ -1440,7 +1501,7 @@ def test_verb_with_person(self):
 
     def test_verb_with_tense_and_person(self):
         """Test the grammatical quiz types for a verb with tense and grammatical person."""
-        verb = self.create_verb_with_tense_and_person()
+        verb = self.create_verb_with_tense_and_person("present tense", "past tense")
         present_singular, present_plural, past_singular, past_plural = verb.leaf_concepts(NL)
         for singular, plural in ((present_singular, present_plural), (past_singular, past_plural)):
             self.assertEqual(("pluralize",), grammatical_quiz_types(singular, plural))