aboutsummaryrefslogtreecommitdiff
path: root/lib/citation/book.ml
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-08-20 22:11:17 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-08-20 22:11:17 +0200
commit1988d350242b47aa52aa07904c495e7e2c0eba82 (patch)
tree463254012c555da51aa976dbe350415070b80a63 /lib/citation/book.ml
parent2d8942e08edcbe5430f97270bdb1233e287600ee (diff)
downloadcolitur-1988d350242b47aa52aa07904c495e7e2c0eba82.tar.gz
colitur-1988d350242b47aa52aa07904c495e7e2c0eba82.zip
fix: audit findings — parser strictness, name ambiguity, and errors
Found by auditing the shipped program rather than the diff. The parser accepted OCaml integer-literal syntax, so "Luke 1_1:5" read as chapter ELEVEN and "+5" as 5 -- a typo silently becoming a different chapter, reachable through any user overlay. Numbers are now plain digits and positive, and a descending range is rejected: 1:20-10 is always a transcription error. No shipped citation changed. FOUR PAIRS OF DIFFERENT BOOKS SHARED A FULL TITLE. 1 and 2 Corinthians both rendered "Epistola ad Corinthios", as did Thessalonians, Timothy and Peter -- 108 citations in 2027 alone that a reader cannot resolve to a book. This is the Kings defect fixed earlier and not generalised. The titles now carry their volume numeral, marked CONSTRUCTED, and a test asserts no two books share a name -- while allowing the case where two ids ARE the same book under different numbering, which a tradition relates. Spec section 8.5 is now delivered rather than merely recorded. Shipped styles did not re-parse their own output: 32 of 52 Latin abbreviations and 49 of 52 full titles failed, so a citation copied from colitur's own output into an overlay was passed through untouched and printed in the wrong language, silently. Every shipped name is registered as a spelling and split_book learned multi-word titles by longest-token match. Now 0 of 52 fail beyond the same-book aliases. Overlay errors were written for a compiler author: they named an OCaml source file the reader does not have and buried the useful token. The existing five-path rewriter is replaced by a generic one, applied to every load path rather than one, so "rank: is not one of the allowed values (at Class9)" replaces the raw Of_sexp_error dump. Also: the new-overlay scaffold documented citations and layer without showing them, and its comment implied the wrong nesting -- the single easiest thing to get wrong; error messages echoed whole file lines, copying an unrelated file's contents into stderr when a flag pointed at one; and config --show validated partway down its table, exiting 2 after writing five rows to stdout.
Diffstat (limited to 'lib/citation/book.ml')
-rw-r--r--lib/citation/book.ml116
1 files changed, 67 insertions, 49 deletions
diff --git a/lib/citation/book.ml b/lib/citation/book.ml
index 2a2b9c0..986a898 100644
--- a/lib/citation/book.ml
+++ b/lib/citation/book.ml
@@ -12,57 +12,57 @@ let to_string t = t
the lectionary and was the source of every token missed in the first
pass. *)
let table =
- [ ("genesis", [ "Gen" ]);
- ("exodus", [ "Ex"; "Exod" ]);
- ("leviticus", [ "Lev" ]);
- ("numbers", [ "Num" ]);
- ("kings_3", [ "3 Kings"; "3 Kgs." ]);
- ("kings_4", [ "4 Kings" ]);
- ("esdras_2", [ "2 Esd." ]);
- ("tobit", [ "Tob" ]);
- ("judith", [ "Judith" ]);
- ("esther", [ "Esther" ]);
- ("proverbs", [ "Prov" ]);
- ("song_of_songs", [ "Song" ]);
- ("wisdom", [ "Wis"; "Wis." ]);
- ("ecclesiasticus", [ "Ecclus"; "Sir"; "Eccli" ]);
- ("isaiah", [ "Isa"; "Isa." ]);
- ("jeremiah", [ "Jer" ]);
- ("ezekiel", [ "Ezech"; "Ezek" ]);
- ("daniel", [ "Dan" ]);
- ("osee", [ "Osee" ]);
- ("joel", [ "Joel" ]);
- ("jonas", [ "Jonas" ]);
- ("malachi", [ "Mal" ]);
- ("matthew", [ "Matt"; "Matt." ]);
- ("mark", [ "Mark" ]);
- ("luke", [ "Luke" ]);
- ("john", [ "John" ]);
- ("acts", [ "Acts" ]);
- ("romans", [ "Rom" ]);
- ("corinthians_1", [ "1 Cor"; "1 Cor." ]);
- ("corinthians_2", [ "2 Cor"; "2 Cor." ]);
- ("galatians", [ "Gal" ]);
- ("ephesians", [ "Eph"; "Eph." ]);
- ("philippians", [ "Phil" ]);
- ("colossians", [ "Col"; "Col." ]);
- ("thessalonians_1", [ "1 Thess"; "1 Thess." ]);
- ("thessalonians_2", [ "2 Thess" ]);
- ("timothy_1", [ "1 Tim." ]);
- ("timothy_2", [ "2 Tim"; "2 Tim." ]);
- ("titus", [ "Titus" ]);
- ("hebrews", [ "Heb" ]);
- ("james", [ "Jas"; "James" ]);
- ("peter_1", [ "1 Pet"; "1 Pet." ]);
- ("peter_2", [ "2 Pet." ]);
- ("john_1", [ "1 John" ]);
+ [ ("genesis", [ "Gen"; "Liber Genesis"; "Genesis" ]);
+ ("exodus", [ "Ex"; "Exod"; "Liber Exodi"; "Exodus" ]);
+ ("leviticus", [ "Lev"; "Levit"; "Liber Levitici"; "Leviticus" ]);
+ ("numbers", [ "Num"; "Liber Numeri"; "Numbers" ]);
+ ("kings_3", [ "3 Kings"; "3 Kgs."; "3 Reg"; "Liber Regum III" ]);
+ ("kings_4", [ "4 Kings"; "4 Reg"; "Liber Regum IV"; "4 Kgs." ]);
+ ("esdras_2", [ "2 Esd."; "2 Esdr"; "Liber Esdrae"; "2 Esdras" ]);
+ ("tobit", [ "Tob"; "Liber Tobiae"; "Tobias" ]);
+ ("judith", [ "Judith"; "Iudith"; "Liber Iudith"; "Jth" ]);
+ ("esther", [ "Esther"; "Esth"; "Liber Esther" ]);
+ ("proverbs", [ "Prov"; "Proverbs" ]);
+ ("song_of_songs", [ "Song"; "Cant."; "Canticle of Canticles" ]);
+ ("wisdom", [ "Wis"; "Wis."; "Sap"; "Liber Sapientiae"; "Wisdom" ]);
+ ("ecclesiasticus", [ "Ecclus"; "Sir"; "Eccli"; "Ecclesiasticus" ]);
+ ("isaiah", [ "Isa"; "Isa."; "Isai"; "Isaias Propheta"; "Isaias" ]);
+ ("jeremiah", [ "Jer"; "Ier"; "Ieremias Propheta"; "Jeremias" ]);
+ ("ezekiel", [ "Ezech"; "Ezek"; "Ezechiel Propheta"; "Ezechiel" ]);
+ ("daniel", [ "Dan"; "Daniel Propheta"; "Daniel" ]);
+ ("osee", [ "Osee"; "Osee Propheta" ]);
+ ("joel", [ "Joel"; "Ioel"; "Ioel Propheta" ]);
+ ("jonas", [ "Jonas"; "Ionae"; "Ionas Propheta" ]);
+ ("malachi", [ "Mal"; "Malach"; "Malachias Propheta"; "Malachias" ]);
+ ("matthew", [ "Matt"; "Matt."; "Matth"; "Evangelium secundum Matthaeum"; "Matthew" ]);
+ ("mark", [ "Mark"; "Marc"; "Evangelium secundum Marcum" ]);
+ ("luke", [ "Luke"; "Luc"; "Evangelium secundum Lucam" ]);
+ ("john", [ "John"; "Ioann"; "Evangelium secundum Ioannem" ]);
+ ("acts", [ "Acts"; "Act"; "Actus Apostolorum"; "Acts of the Apostles" ]);
+ ("romans", [ "Rom"; "Epistola ad Romanos"; "Romans" ]);
+ ("corinthians_1", [ "1 Cor"; "1 Cor."; "Epistola I ad Corinthios"; "1 Corinthians" ]);
+ ("corinthians_2", [ "2 Cor"; "2 Cor."; "Epistola II ad Corinthios"; "2 Corinthians" ]);
+ ("galatians", [ "Gal"; "Epistola ad Galatas"; "Galatians" ]);
+ ("ephesians", [ "Eph"; "Eph."; "Ephes"; "Epistola ad Ephesios"; "Ephesians" ]);
+ ("philippians", [ "Phil"; "Epistola ad Philippenses"; "Philippians" ]);
+ ("colossians", [ "Col"; "Col."; "Epistola ad Colossenses"; "Colossians" ]);
+ ("thessalonians_1", [ "1 Thess"; "1 Thess."; "Epistola I ad Thessalonicenses"; "1 Thessalonians" ]);
+ ("thessalonians_2", [ "2 Thess"; "Epistola II ad Thessalonicenses"; "2 Thessalonians" ]);
+ ("timothy_1", [ "1 Tim."; "1 Tim"; "Epistola I ad Timotheum"; "1 Timothy" ]);
+ ("timothy_2", [ "2 Tim"; "2 Tim."; "Epistola II ad Timotheum"; "2 Timothy" ]);
+ ("titus", [ "Titus"; "Tit"; "Epistola ad Titum" ]);
+ ("hebrews", [ "Heb"; "Hebr"; "Epistola ad Hebraeos"; "Hebrews" ]);
+ ("james", [ "Jas"; "James"; "Iac"; "Epistola beati Iacobi Apostoli" ]);
+ ("peter_1", [ "1 Pet"; "1 Pet."; "1 Petri"; "Epistola I beati Petri Apostoli"; "1 Peter" ]);
+ ("peter_2", [ "2 Pet."; "2 Petri"; "Epistola II beati Petri Apostoli"; "2 Pet"; "2 Peter" ]);
+ ("john_1", [ "1 John"; "1 Ioann"; "Epistola beati Ioannis Apostoli" ]);
(* "Apoc" is the Vulgate spelling and "Rev" its modern equivalent, but
BOTH sit inside Vulgate-tradition data, so both resolve to the same
Vulgate id here -- see book.mli's note by [apocalypse] never being an
[of_token] result under that name. Do not add "revelation" as a
spelling: it exists only as a tradition target (below), and giving it
an [of_token] entry would let one book carry two different ids. *)
- ("apocalypse", [ "Apoc"; "Rev" ]) ]
+ ("apocalypse", [ "Apoc"; "Rev"; "Liber Apocalypsis"; "Apocalypse" ]) ]
(* Targets a tradition can map ONTO that the Vulgate data never cites
directly. Present so [tradition_of_fields] can validate both sides.
@@ -70,16 +70,34 @@ let table =
"Sir" and "Rev" already resolve to the Vulgate ids [ecclesiasticus] and
[apocalypse] above, so a modern-numbering tradition maps ONTO these
targets rather than data ever citing them directly. *)
-let tradition_targets =
- [ "kings_1"; "kings_2"; "nehemiah"; "sirach"; "hosea"; "jonah"; "revelation" ]
+
+(* The modern-numbering targets. They have table rows so that colitur's OWN
+ rendered output re-parses: with --sigla-tradition modern the book prints
+ as "1 Reg" or "1 Kings", and a user copying that back into an overlay must
+ get it read correctly. The shipped data never cites them directly, which
+ is why they are listed apart. *)
+let tradition_target_table =
+ [
+ ("kings_1", [ "1 Reg"; "Liber Regum I"; "1 Kgs"; "1 Kings" ]);
+ ("kings_2", [ "2 Reg"; "Liber Regum II"; "2 Kgs"; "2 Kings" ]);
+ ("nehemiah", [ "Neh"; "Liber Nehemiae"; "Nehemiah" ]);
+ ("sirach", [ "Liber Ecclesiastici"; "Sirach" ]);
+ ("hosea", [ "Os"; "Hos"; "Hosea" ]);
+ ("jonah", [ "Ion"; "Jon"; "Jonah" ]);
+ ("revelation", [ "Apocalypsis"; "Revelation" ]);
+ ]
+
+let tradition_targets = List.map fst tradition_target_table
let all = List.map fst table @ tradition_targets
let tokens =
- List.concat_map (fun (id, sp) -> List.map (fun s -> (s, id)) sp) table
+ List.concat_map
+ (fun (id, sp) -> List.map (fun s -> (s, id)) sp)
+ (table @ tradition_target_table)
let default_spelling id =
- match List.assoc_opt id table with
+ match List.assoc_opt id (table @ tradition_target_table) with
| Some (first :: _) -> first
| Some [] | None -> id