Org Mode tables and fill-in quizzes – Latin verb conjugation drills in Emacs

| emacs, org

I was looking for a Latin verb conjugation drill similar to these ones for and nouns and pronouns. I liked the instant feedback and the ability to quickly get hints. I couldn’t find an online drill I liked, though, so I made my own with Emacs and Org. (Because… why not?)

I wrote some code that would take a table like this:

present – 1st sing. – ago / agere agO
present – 2nd sing. – ago / agere agis
present – 3rd sing. – ago / agere agit
present – 1st plu. – ago / agere agimus
present – 2nd plu. – ago / agere agitis
present – 3rd plu. – ago / agere agunt
imperfect – 1st sing. – ago / agere agEbam
imperfect – 2nd sing. – ago / agere agEbAs
imperfect – 3rd sing. – ago / agere agEbat
imperfect – 1st plu. – ago / agere agEbAmus
imperfect – 2nd plu. – ago / agere agEbAtis
imperfect – 3rd plu. – ago / agere agEbant
future – 1st sing. – ago / agere agam
future – 2nd sing. – ago / agere agEs
future – 3rd sing. – ago / agere agEt
future – 1st plu. – ago / agere agEmus
future – 2nd plu. – ago / agere agEtis
future – 3rd plu. – ago / agere agent

I can call my/make-fill-in-quiz to get a quiz buffer that looks like this. If I get stuck, ? shows me a hint in the echo area.

latin-verb-drills-0

To make it easier, I’ve left case-fold-search set to nil so that I don’t have to match the case (uppercase vowels = macrons), but I can set case-fold-search to t if I want to make sure I’ve got the macrons in the right places.

Here’s the code to display the quiz buffer.

     (require 'widget)
     (defun my/check-widget-value (widget &rest ignore)
       "Provide visual feedback for WIDGET."
       (cond
        ((string= (widget-value widget) "?")
         ;; Asking for hint
         (message "%s" (widget-get widget :correct))
         (widget-value-set widget ""))
        ;; Use string-match to obey case-fold-search 
        ((string-match 
          (concat "^"
                  (regexp-quote (widget-get widget :correct))
                  "$")
          (widget-value widget))
         (message "Correct")
         (goto-char (widget-field-start widget))
         (goto-char (line-end-position))
         (insert "✓")
         (widget-forward 1)
         )))

   (defun my/make-fill-in-quiz (&optional quiz-table)
     "Create an fill-in quiz for the Org table at point.
The Org table's first column should have the questions and the second column 
should have the answers."
     (interactive (list (org-babel-read-table)))
     (with-current-buffer (get-buffer-create "*Quiz*")
       (kill-all-local-variables)
       (let ((inhibit-read-only t))
         (erase-buffer))
       (remove-overlays)
       (mapc (lambda (row)
               (widget-insert (car row))
               (widget-insert "\t")
               (widget-create 'editable-field
                              :size 15
                              :correct (cadr row)
                              :notify 'my/check-widget-value)
               (widget-insert "\n"))    
             quiz-table)
       (widget-create 'push-button
                      :table quiz-table
                      :notify (lambda (widget &rest ignore)
                                (my/make-fill-in-quiz (widget-get widget :table))) 
                      "Reset")
       (use-local-map widget-keymap)
       (widget-setup)
       (goto-char (point-min))
       (widget-forward 1)
       (switch-to-buffer (current-buffer))))

Incidentally, I generated the table above from a larger table of Latin verb conjugations in the appendix of Wheelock’s Latin, specified like this:

#+NAME: present-indicative-active
| laudO    | moneO   | agO    | audiO   | capiO   |
| laudAs   | monEs   | agis   | audIs   | capis   |
| laudat   | monet   | agit   | audit   | capit   |
| laudAmus | monEmus | agimus | audImus | capimus |
| laudAtis | monEtis | agitis | audItis | capitis |
| laudant  | monent  | agunt  | audiunt | capiunt |

#+NAME: imperfect-indicative-active
| laudAbam   | monEbam   | agEbam   | audiEbam   | capiEbam   |
| laudAbas   | monEbas   | agEbAs   | audiEbAs   | capiEbas   |
| laudAbat   | monEbat   | agEbat   | audiEbat   | capiEbat   |
| laudAbAmus | monEbAmus | agEbAmus | audiEbAmus | capiEbAmus |
| laudAbAtis | monEbAtis | agEbAtis | audiEbAtis | capiEbAtis |
| laudAbant  | monEbant  | agEbant  | audiEbant  | capiEbant  |

#+NAME: future-indicative-active
| laudAbO    | monEbO    | agam   | audiam     | capiam     |
| laudAbis   | monEbis   | agEs   | audiEs     | capiEs     |
| laudAbit   | monEbit   | agEt   | audiet     | capiet     |
| laudAbimus | monEbimus | agEmus | audiEmus   | capiEmus   |
| laudAbitis | monEbitis | agEtis | audiEtis   | capiEtis   |
| laudAbunt  | monEbunt  | agent  | audient    | capient    |

with the code:

#+begin_src emacs-lisp :var present=present-indicative-active :var imperfect=imperfect-indicative-active :var future=future-indicative-active
  (defun my/label-latin-with-verbs (table verbs persons tense)
    (apply 'append
           (-zip-with (lambda (row person) 
                        (-zip-with (lambda (word verb)
                                     (list word (format "%s - %s - %s" tense person verb)))
                                   row verbs))
                      table (-cycle persons))))
  (apply 'append 
         (mapcar (lambda (tense)
                   (my/label-latin-with-verbs 
                    (symbol-value tense)
                    '("laudo / laudare" "moneo / monEre" "ago / agere" "audiO / audIre" "capiO / capere")
                    '("1st sing." "2nd sing." "3rd sing." "1st plu." "2nd plu." "3rd plu.")
                    (symbol-name tense)))
                 '(present imperfect future)))

#+end_src

This uses dash.el for the -zip-with and -cycle functions. There’s probably a much better way to process the lists, but I’m still getting the hang of thinking properly functionally… =)

Anyway, I’m sure it will be handy for a number of other quiz-like things. org-drill and org-drill-table will probably come in handy for flashcards, too!

You can comment with Disqus or you can e-mail me at sacha@sachachua.com.