chaffjs 0.36.0 → 0.37.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (257) hide show
  1. package/dist/detectors/acronym-expansion.d.ts.map +1 -1
  2. package/dist/detectors/acronym-expansion.js +10 -3
  3. package/dist/detectors/acronym-expansion.js.map +1 -1
  4. package/dist/detectors/acronym-link.d.ts +5 -0
  5. package/dist/detectors/acronym-link.d.ts.map +1 -1
  6. package/dist/detectors/acronym-link.js +33 -0
  7. package/dist/detectors/acronym-link.js.map +1 -1
  8. package/dist/detectors/acronym-mention.d.ts +5 -0
  9. package/dist/detectors/acronym-mention.d.ts.map +1 -0
  10. package/dist/detectors/acronym-mention.js +34 -0
  11. package/dist/detectors/acronym-mention.js.map +1 -0
  12. package/dist/detectors/acronym-name.js +2 -2
  13. package/dist/detectors/acronym-name.js.map +1 -1
  14. package/dist/detectors/addressee-title-edge.d.ts +21 -0
  15. package/dist/detectors/addressee-title-edge.d.ts.map +1 -0
  16. package/dist/detectors/addressee-title-edge.js +22 -0
  17. package/dist/detectors/addressee-title-edge.js.map +1 -0
  18. package/dist/detectors/capitalised-term.d.ts +4 -0
  19. package/dist/detectors/capitalised-term.d.ts.map +1 -1
  20. package/dist/detectors/capitalised-term.js +18 -1
  21. package/dist/detectors/capitalised-term.js.map +1 -1
  22. package/dist/detectors/caps-caption.d.ts +14 -4
  23. package/dist/detectors/caps-caption.d.ts.map +1 -1
  24. package/dist/detectors/caps-caption.js +36 -9
  25. package/dist/detectors/caps-caption.js.map +1 -1
  26. package/dist/detectors/char-shape.d.ts.map +1 -1
  27. package/dist/detectors/char-shape.js +37 -14
  28. package/dist/detectors/char-shape.js.map +1 -1
  29. package/dist/detectors/citation-sentence.d.ts +8 -0
  30. package/dist/detectors/citation-sentence.d.ts.map +1 -0
  31. package/dist/detectors/citation-sentence.js +40 -0
  32. package/dist/detectors/citation-sentence.js.map +1 -0
  33. package/dist/detectors/contraction-mention.d.ts +3 -0
  34. package/dist/detectors/contraction-mention.d.ts.map +1 -0
  35. package/dist/detectors/contraction-mention.js +22 -0
  36. package/dist/detectors/contraction-mention.js.map +1 -0
  37. package/dist/detectors/dash-break.d.ts +7 -0
  38. package/dist/detectors/dash-break.d.ts.map +1 -0
  39. package/dist/detectors/dash-break.js +30 -0
  40. package/dist/detectors/dash-break.js.map +1 -0
  41. package/dist/detectors/date-any-year.d.ts.map +1 -1
  42. package/dist/detectors/date-any-year.js +5 -3
  43. package/dist/detectors/date-any-year.js.map +1 -1
  44. package/dist/detectors/definition-use.d.ts.map +1 -1
  45. package/dist/detectors/definition-use.js +9 -4
  46. package/dist/detectors/definition-use.js.map +1 -1
  47. package/dist/detectors/document-shape.d.ts +5 -0
  48. package/dist/detectors/document-shape.d.ts.map +1 -1
  49. package/dist/detectors/document-shape.js +15 -1
  50. package/dist/detectors/document-shape.js.map +1 -1
  51. package/dist/detectors/duplicate-heading.d.ts +1 -0
  52. package/dist/detectors/duplicate-heading.d.ts.map +1 -1
  53. package/dist/detectors/duplicate-heading.js +25 -3
  54. package/dist/detectors/duplicate-heading.js.map +1 -1
  55. package/dist/detectors/email-letter.d.ts +9 -2
  56. package/dist/detectors/email-letter.d.ts.map +1 -1
  57. package/dist/detectors/email-letter.js +39 -8
  58. package/dist/detectors/email-letter.js.map +1 -1
  59. package/dist/detectors/fullwidth-alnum.d.ts.map +1 -1
  60. package/dist/detectors/fullwidth-alnum.js +7 -1
  61. package/dist/detectors/fullwidth-alnum.js.map +1 -1
  62. package/dist/detectors/gram-openers.d.ts +4 -3
  63. package/dist/detectors/gram-openers.d.ts.map +1 -1
  64. package/dist/detectors/gram-openers.js +26 -2
  65. package/dist/detectors/gram-openers.js.map +1 -1
  66. package/dist/detectors/heading-record-rhythm.d.ts +9 -0
  67. package/dist/detectors/heading-record-rhythm.d.ts.map +1 -0
  68. package/dist/detectors/heading-record-rhythm.js +57 -0
  69. package/dist/detectors/heading-record-rhythm.js.map +1 -0
  70. package/dist/detectors/kutoten-consistency.d.ts.map +1 -1
  71. package/dist/detectors/kutoten-consistency.js +7 -0
  72. package/dist/detectors/kutoten-consistency.js.map +1 -1
  73. package/dist/detectors/lexicon.d.ts.map +1 -1
  74. package/dist/detectors/lexicon.js +5 -2
  75. package/dist/detectors/lexicon.js.map +1 -1
  76. package/dist/detectors/mark-shape.d.ts +3 -1
  77. package/dist/detectors/mark-shape.d.ts.map +1 -1
  78. package/dist/detectors/mark-shape.js +20 -5
  79. package/dist/detectors/mark-shape.js.map +1 -1
  80. package/dist/detectors/name-variant.d.ts.map +1 -1
  81. package/dist/detectors/name-variant.js +6 -0
  82. package/dist/detectors/name-variant.js.map +1 -1
  83. package/dist/detectors/organisation-run.d.ts +0 -6
  84. package/dist/detectors/organisation-run.d.ts.map +1 -1
  85. package/dist/detectors/organisation-run.js +4 -1
  86. package/dist/detectors/organisation-run.js.map +1 -1
  87. package/dist/detectors/orthographic-variant.d.ts.map +1 -1
  88. package/dist/detectors/orthographic-variant.js +33 -4
  89. package/dist/detectors/orthographic-variant.js.map +1 -1
  90. package/dist/detectors/orthography.d.ts.map +1 -1
  91. package/dist/detectors/orthography.js +4 -2
  92. package/dist/detectors/orthography.js.map +1 -1
  93. package/dist/detectors/oxford-comma.d.ts.map +1 -1
  94. package/dist/detectors/oxford-comma.js +33 -3
  95. package/dist/detectors/oxford-comma.js.map +1 -1
  96. package/dist/detectors/range-notation.d.ts +2 -2
  97. package/dist/detectors/range-notation.d.ts.map +1 -1
  98. package/dist/detectors/range-notation.js +18 -7
  99. package/dist/detectors/range-notation.js.map +1 -1
  100. package/dist/detectors/requirement-smell.d.ts.map +1 -1
  101. package/dist/detectors/requirement-smell.js +10 -6
  102. package/dist/detectors/requirement-smell.js.map +1 -1
  103. package/dist/detectors/sentence-counts.d.ts +10 -2
  104. package/dist/detectors/sentence-counts.d.ts.map +1 -1
  105. package/dist/detectors/sentence-counts.js +34 -4
  106. package/dist/detectors/sentence-counts.js.map +1 -1
  107. package/dist/detectors/sentence-length.d.ts.map +1 -1
  108. package/dist/detectors/sentence-length.js +13 -4
  109. package/dist/detectors/sentence-length.js.map +1 -1
  110. package/dist/detectors/signals.d.ts.map +1 -1
  111. package/dist/detectors/signals.js +15 -3
  112. package/dist/detectors/signals.js.map +1 -1
  113. package/dist/detectors/superlative-name.d.ts +5 -0
  114. package/dist/detectors/superlative-name.d.ts.map +1 -1
  115. package/dist/detectors/superlative-name.js +9 -0
  116. package/dist/detectors/superlative-name.js.map +1 -1
  117. package/dist/detectors/token-shape.d.ts.map +1 -1
  118. package/dist/detectors/token-shape.js +3 -1
  119. package/dist/detectors/token-shape.js.map +1 -1
  120. package/dist/detectors/undefined-term.d.ts.map +1 -1
  121. package/dist/detectors/undefined-term.js +5 -0
  122. package/dist/detectors/undefined-term.js.map +1 -1
  123. package/dist/detectors/unknown-word.d.ts +4 -1
  124. package/dist/detectors/unknown-word.d.ts.map +1 -1
  125. package/dist/detectors/unknown-word.js +1 -1
  126. package/dist/detectors/unknown-word.js.map +1 -1
  127. package/dist/html/html-site-chrome.d.ts.map +1 -1
  128. package/dist/html/html-site-chrome.js +35 -12
  129. package/dist/html/html-site-chrome.js.map +1 -1
  130. package/dist/latin-word-boundary.d.ts +5 -0
  131. package/dist/latin-word-boundary.d.ts.map +1 -1
  132. package/dist/latin-word-boundary.js +17 -0
  133. package/dist/latin-word-boundary.js.map +1 -1
  134. package/dist/leading-label.d.ts +4 -0
  135. package/dist/leading-label.d.ts.map +1 -0
  136. package/dist/leading-label.js +14 -0
  137. package/dist/leading-label.js.map +1 -0
  138. package/dist/link-shape.d.ts +3 -2
  139. package/dist/link-shape.d.ts.map +1 -1
  140. package/dist/link-shape.js +12 -3
  141. package/dist/link-shape.js.map +1 -1
  142. package/dist/name-misspelt-usual.d.ts +5 -0
  143. package/dist/name-misspelt-usual.d.ts.map +1 -0
  144. package/dist/name-misspelt-usual.js +19 -0
  145. package/dist/name-misspelt-usual.js.map +1 -0
  146. package/dist/notice-header.d.ts +19 -0
  147. package/dist/notice-header.d.ts.map +1 -0
  148. package/dist/notice-header.js +31 -0
  149. package/dist/notice-header.js.map +1 -0
  150. package/dist/sentence-load.d.ts +2 -1
  151. package/dist/sentence-load.d.ts.map +1 -1
  152. package/dist/sentence-load.js +11 -3
  153. package/dist/sentence-load.js.map +1 -1
  154. package/dist/structure/attached-parts.d.ts +2 -1
  155. package/dist/structure/attached-parts.d.ts.map +1 -1
  156. package/dist/structure/attached-parts.js +22 -9
  157. package/dist/structure/attached-parts.js.map +1 -1
  158. package/dist/structure/definition-use.d.ts +5 -2
  159. package/dist/structure/definition-use.d.ts.map +1 -1
  160. package/dist/structure/definition-use.js +24 -7
  161. package/dist/structure/definition-use.js.map +1 -1
  162. package/dist/structure/gerund-phrase.d.ts +8 -0
  163. package/dist/structure/gerund-phrase.d.ts.map +1 -0
  164. package/dist/structure/gerund-phrase.js +30 -0
  165. package/dist/structure/gerund-phrase.js.map +1 -0
  166. package/dist/structure/universal.d.ts +7 -3
  167. package/dist/structure/universal.d.ts.map +1 -1
  168. package/dist/structure/universal.js +17 -5
  169. package/dist/structure/universal.js.map +1 -1
  170. package/dist/title-line.d.ts +2 -0
  171. package/dist/title-line.d.ts.map +1 -1
  172. package/dist/title-line.js +2 -1
  173. package/dist/title-line.js.map +1 -1
  174. package/dist/topic-clause.d.ts +7 -0
  175. package/dist/topic-clause.d.ts.map +1 -0
  176. package/dist/topic-clause.js +87 -0
  177. package/dist/topic-clause.js.map +1 -0
  178. package/dist/uncounted-particles.d.ts +18 -0
  179. package/dist/uncounted-particles.d.ts.map +1 -0
  180. package/dist/uncounted-particles.js +47 -0
  181. package/dist/uncounted-particles.js.map +1 -0
  182. package/package.json +3 -3
  183. package/rules/adversative-ga-repeat.yaml +1 -0
  184. package/rules/all-caps-shouting.yaml +2 -2
  185. package/rules/bare-url-mix.yaml +2 -2
  186. package/rules/concrete-evidence-density.yaml +1 -0
  187. package/rules/contraction-consistency.yaml +2 -2
  188. package/rules/duplicate-definition.yaml +2 -2
  189. package/rules/duplicate-heading.yaml +2 -2
  190. package/rules/fullwidth-alnum-consistency.yaml +2 -2
  191. package/rules/kutoten-consistency.yaml +2 -2
  192. package/rules/latin-spacing.yaml +2 -2
  193. package/rules/max-kanji-continuous.yaml +1 -1
  194. package/rules/max-sentence-length.yaml +2 -2
  195. package/rules/ngram-repetition.yaml +2 -2
  196. package/rules/orthographic-variant.yaml +2 -2
  197. package/rules/oxford-comma-consistency.yaml +2 -2
  198. package/rules/preamble-length.yaml +3 -2
  199. package/rules/range-notation-consistency.yaml +2 -2
  200. package/rules/requirement-smell.yaml +2 -2
  201. package/rules/title-length.yaml +2 -2
  202. package/rules/topic-predicate-distance.yaml +2 -2
  203. package/rules/undefined-acronym.yaml +3 -2
  204. package/rules/undefined-term.yaml +2 -2
  205. package/rules/unqualified-superlative.yaml +1 -1
  206. package/rules/unused-definition.yaml +2 -2
  207. package/rules/use-before-definition.yaml +2 -2
  208. package/src/detectors/acronym-expansion.ts +10 -3
  209. package/src/detectors/acronym-link.ts +38 -0
  210. package/src/detectors/acronym-mention.ts +44 -0
  211. package/src/detectors/acronym-name.ts +2 -2
  212. package/src/detectors/addressee-title-edge.ts +39 -0
  213. package/src/detectors/capitalised-term.ts +26 -2
  214. package/src/detectors/caps-caption.ts +43 -9
  215. package/src/detectors/char-shape.ts +55 -17
  216. package/src/detectors/citation-sentence.ts +44 -0
  217. package/src/detectors/contraction-mention.ts +24 -0
  218. package/src/detectors/dash-break.ts +45 -0
  219. package/src/detectors/date-any-year.ts +7 -3
  220. package/src/detectors/definition-use.ts +12 -8
  221. package/src/detectors/document-shape.ts +20 -1
  222. package/src/detectors/duplicate-heading.ts +26 -3
  223. package/src/detectors/email-letter.ts +50 -7
  224. package/src/detectors/fullwidth-alnum.ts +9 -1
  225. package/src/detectors/gram-openers.ts +36 -7
  226. package/src/detectors/heading-record-rhythm.ts +57 -0
  227. package/src/detectors/kutoten-consistency.ts +9 -0
  228. package/src/detectors/lexicon.ts +6 -2
  229. package/src/detectors/mark-shape.ts +30 -5
  230. package/src/detectors/name-variant.ts +6 -0
  231. package/src/detectors/organisation-run.ts +6 -1
  232. package/src/detectors/orthographic-variant.ts +45 -6
  233. package/src/detectors/orthography.ts +6 -2
  234. package/src/detectors/oxford-comma.ts +35 -3
  235. package/src/detectors/range-notation.ts +26 -6
  236. package/src/detectors/requirement-smell.ts +12 -8
  237. package/src/detectors/sentence-counts.ts +40 -4
  238. package/src/detectors/sentence-length.ts +17 -4
  239. package/src/detectors/signals.ts +18 -3
  240. package/src/detectors/superlative-name.ts +10 -0
  241. package/src/detectors/token-shape.ts +3 -1
  242. package/src/detectors/undefined-term.ts +8 -0
  243. package/src/detectors/unknown-word.ts +1 -1
  244. package/src/html/html-site-chrome.ts +70 -13
  245. package/src/latin-word-boundary.ts +18 -0
  246. package/src/leading-label.ts +15 -0
  247. package/src/link-shape.ts +15 -3
  248. package/src/name-misspelt-usual.ts +25 -0
  249. package/src/notice-header.ts +57 -0
  250. package/src/sentence-load.ts +14 -3
  251. package/src/structure/attached-parts.ts +24 -9
  252. package/src/structure/definition-use.ts +33 -7
  253. package/src/structure/gerund-phrase.ts +31 -0
  254. package/src/structure/universal.ts +22 -7
  255. package/src/title-line.ts +2 -1
  256. package/src/topic-clause.ts +109 -0
  257. package/src/uncounted-particles.ts +72 -0
@@ -0,0 +1,109 @@
1
+ // Where a sentence's topic (〜は) gets its own predicate when that is not the end of the sentence: the topic is said to be
2
+ // something and the sentence goes on with another clause (この道行きは安易なものではありませんが、…). Pure: tokens in, an index out.
3
+ import type { Token } from "./plugin.ts";
4
+
5
+ const COMMA = new Set(["、", ","]);
6
+ const NOMINAL = new Set(["NOUN", "PRON", "PROPN"]);
7
+ const PREDICATE = new Set(["VERB", "AUX", "ADJ", "SCONJ"]);
8
+ /** The nouns a restrictive であって leads to: 失業者であって、支援の必要がある者. */
9
+ const RESTRICTED_HEADS = new Set(["者", "もの", "物", "方"]);
10
+ /** Verbs that report what others say or think (〜と言われている). とされる is a rule's wording, not a report. */
11
+ const REPORTING = new Set(["言う", "思う", "考える", "見る", "伝える"]);
12
+
13
+ /**
14
+ * The copula that says what the topic is: で (ものである; read as a particle in 事業ではなく) and です. な before a noun is not a
15
+ * predicate.
16
+ */
17
+ const isCopula = (token: Token | undefined): boolean =>
18
+ token?.surface === "で" ? token.pos === "AUX" || token.pos === "ADP" : token?.pos === "AUX" && token.lemma === "です";
19
+
20
+ /** The words a copula is finished with before its conjunction: あり, ござい, ます, ませ, ん, the は of ではありません. */
21
+ const COPULA_TAIL = new Set(["ある", "ござる", "ます", "ん", "ぬ", "は"]);
22
+
23
+ const isCopulaTail = (token: Token): boolean => COPULA_TAIL.has(token.lemma ?? token.surface) || COPULA_TAIL.has(token.surface);
24
+
25
+ /** Whether the words just before end are a copula and its tail (ではありません, でございます, である). */
26
+ /** The first word at or before at that is not a copula's tail. Walks back only over the tail, so each call is short. */
27
+ const beforeTail = (tokens: readonly Token[], at: number): number => {
28
+ const token = tokens[at];
29
+ return token !== undefined && isCopulaTail(token) ? beforeTail(tokens, at - 1) : at;
30
+ };
31
+
32
+ const copulaBefore = (tokens: readonly Token[], end: number): boolean => isCopula(tokens[beforeTail(tokens, end - 1)]);
33
+
34
+ const isComma = (token: Token | undefined): boolean => COMMA.has(token?.surface ?? "");
35
+
36
+ /** A noun of its own, not a nominaliser (こと, の, ところ) that only turns the clause before it into a noun. */
37
+ const isOwnNoun = (token: Token | undefined): boolean => NOMINAL.has(token?.pos ?? "") && token?.features?.["NounType"] !== "Dependent";
38
+
39
+ const isParticle = (token: Token, surface: string): boolean => token.surface === surface && token.pos === "ADP";
40
+
41
+ /**
42
+ * The last place a subject of its own stands before the sentence's last word: a new topic (法律はありませんが) anywhere, or a
43
+ * noun with が that the final predicate takes (no comma after it: 動きが見られ始めている). A noun with が inside a condition
44
+ * (債務者が請求した場合、) is not one. -1 when there is none. A clause has a subject of its own after it when this is past it.
45
+ */
46
+ const lastOwnSubject = (tokens: readonly Token[], last: number): number => {
47
+ const lastComma = tokens.findLastIndex((token, at) => at < last && isComma(token));
48
+ return tokens.findLastIndex(
49
+ (token, at) => at < last && isOwnNoun(tokens[at - 1]) && (isParticle(token, "は") || (isParticle(token, "が") && at > lastComma)),
50
+ );
51
+ };
52
+
53
+ /** The words of the clause after a comma at from, up to the next clause stopped by a comma, or the sentence's last word. */
54
+ const nextClause = (tokens: readonly Token[], from: number, last: number): readonly Token[] => {
55
+ const stop = tokens.findIndex((token, at) => at > from && at < last && isComma(token) && PREDICATE.has(tokens[at - 1]?.pos ?? ""));
56
+ return tokens.slice(from + 1, stop === -1 ? last : stop);
57
+ };
58
+
59
+ /** であって that narrows a class to a head noun after it (失業者であって、支援の必要がある者の…): the topic's predicate is still to come. */
60
+ const narrowsToHead = (tokens: readonly Token[], comma: number, last: number): boolean =>
61
+ nextClause(tokens, comma, last).some((token, at, clause) => token.pos === "NOUN" && RESTRICTED_HEADS.has(token.surface) && clause[at + 1]?.surface !== "は");
62
+
63
+ /** A copula with が or て and a comma (〜ではありませんが、, 〜でございますが、, 〜であって、 that does not narrow to a head noun). */
64
+ const closesCopula = (tokens: readonly Token[], at: number, last: number): boolean => {
65
+ const token = tokens[at];
66
+ if (token?.pos !== "SCONJ" || (token.surface !== "が" && token.surface !== "て")) return false;
67
+ if (!isComma(tokens[at + 1]) || !copulaBefore(tokens, at)) return false;
68
+ return token.surface === "が" || !narrowsToHead(tokens, at + 1, last);
69
+ };
70
+
71
+ /** A quotation the sentence reports in the passive (〜だと言われているが), whose topic is inside the quotation. */
72
+ const closesReport = (tokens: readonly Token[], at: number): boolean => {
73
+ const token = tokens[at];
74
+ return token?.surface === "と" && token.pos === "ADP" && REPORTING.has(tokens[at + 1]?.lemma ?? "") && tokens[at + 2]?.features?.["Voice"] === "Pass";
75
+ };
76
+
77
+ /** A clause the topic's predicate closes for certain. */
78
+ const closesSurely = (tokens: readonly Token[], at: number, last: number): boolean => closesCopula(tokens, at, last) || closesReport(tokens, at);
79
+
80
+ /** 〜にあるところ、 / 〜したところ、: a clause the nominaliser ところ closes after a predicate. */
81
+ const closesWithTokoro = (tokens: readonly Token[], at: number): boolean =>
82
+ tokens[at]?.surface === "ところ" && tokens[at]?.features?.["NounType"] === "Dependent" && PREDICATE.has(tokens[at - 1]?.pos ?? "");
83
+
84
+ /** 〜であるならば、: a copula's conditional. */
85
+ const closesWithCondition = (tokens: readonly Token[], at: number): boolean =>
86
+ tokens[at]?.surface === "ば" && tokens[at]?.pos === "SCONJ" && tokens[at - 1]?.surface === "なら" && tokens[at - 1]?.lemma === "だ";
87
+
88
+ /**
89
+ * A clause that may close the topic's predicate, when a new subject follows, each stopped by a comma: a copula after ない or
90
+ * ため (〜ではなく、, 〜であるため、), a copula's condition (〜であるならば、), ところ (〜にあるところ、), and a continuing verb
91
+ * (〜を続けており、).
92
+ */
93
+ const closesIfNewSubject = (tokens: readonly Token[], at: number): boolean => {
94
+ const token = tokens[at];
95
+ if (!isComma(tokens[at + 1])) return false;
96
+ if (token?.surface === "ため" || token?.lemma === "ない") return copulaBefore(tokens, at);
97
+ if (closesWithTokoro(tokens, at) || closesWithCondition(tokens, at)) return true;
98
+ return token?.pos === "VERB" && token.lemma === "おる" && token.features?.["Bound"] === "Yes";
99
+ };
100
+
101
+ /**
102
+ * The index of the word that ends the topic's own clause, between the topic and the sentence's last word, or undefined when the
103
+ * topic's predicate is the one the sentence ends on.
104
+ */
105
+ export const topicClauseEnd = (tokens: readonly Token[], topic: number, last: number): number | undefined => {
106
+ const subject = lastOwnSubject(tokens, last);
107
+ const end = tokens.findIndex((_, at) => at > topic && at < last && (closesSurely(tokens, at, last) || (closesIfNewSubject(tokens, at) && subject > at)));
108
+ return end === -1 ? undefined : end;
109
+ };
@@ -0,0 +1,72 @@
1
+ import type { Lexicon, Token } from "./plugin.ts";
2
+
3
+ /** The words that decide which particle-looking tokens are not links in a chain of particles. */
4
+ export type UncountedWords = {
5
+ /** The particles a chain is made of (の). */
6
+ readonly particles: readonly string[];
7
+ /** Words written as a number: every character one of these, or a decimal digit. */
8
+ readonly numeralChars: ReadonlySet<string>;
9
+ /** Words that number a provision and may be followed by a branch number (第70条の2: 条). */
10
+ readonly counters: readonly string[];
11
+ };
12
+
13
+ const DECIMAL_DIGITS = /^\p{Nd}+$/u;
14
+
15
+ export const isNumeral = (surface: string, numeralChars: ReadonlySet<string>): boolean =>
16
+ DECIMAL_DIGITS.test(surface) || (surface !== "" && [...surface].every((char) => numeralChars.has(char)));
17
+
18
+ const numeralAt = (tokens: readonly Token[], at: number, words: UncountedWords): boolean => {
19
+ const token = tokens[at];
20
+ return token !== undefined && isNumeral(token.surface, words.numeralChars);
21
+ };
22
+
23
+ /** 第70条, 第3項: a counter right after a number. */
24
+ const numberedCounterAt = (tokens: readonly Token[], at: number, words: UncountedWords): boolean => {
25
+ const token = tokens[at];
26
+ return token !== undefined && words.counters.includes(token.surface) && numeralAt(tokens, at - 1, words);
27
+ };
28
+
29
+ const NOMINAL = new Set(["NOUN", "PROPN"]);
30
+ const LETTER = /\p{L}/u;
31
+
32
+ /** Where the run of number tokens that starts at `at` ends (三十 may be two tokens). */
33
+ const numeralRunEnd = (tokens: readonly Token[], at: number, words: UncountedWords): number => {
34
+ const end = tokens.findIndex((token, index) => index >= at && !isNumeral(token.surface, words.numeralChars));
35
+ return end === -1 ? tokens.length : end;
36
+ };
37
+
38
+ /**
39
+ * A word after the number makes it a quantity (資料2-1の4ページ: page 4 of it), not a branch number. A word that opens
40
+ * the next number (第 of 第70条の2第1項) is not one.
41
+ */
42
+ const isWordAt = (tokens: readonly Token[], at: number, words: UncountedWords): boolean => {
43
+ const token = tokens[at];
44
+ return token !== undefined && NOMINAL.has(token.pos) && LETTER.test(token.surface) && !numeralAt(tokens, at + 1, words);
45
+ };
46
+
47
+ /** 第70条の2, 70の2-8の2, 第2の2: the particle joins a branch number to the number before it, so it is part of the number. */
48
+ const isNumberingConnector = (tokens: readonly Token[], at: number, words: UncountedWords): boolean =>
49
+ numeralAt(tokens, at + 1, words) &&
50
+ !isWordAt(tokens, numeralRunEnd(tokens, at + 1, words), words) &&
51
+ (numeralAt(tokens, at - 1, words) || numberedCounterAt(tokens, at - 1, words));
52
+
53
+ /** The particle tokens of a sentence that join the parts of a number (第70条の2) and are not links of a chain. */
54
+ export const uncountedParticles = (tokens: readonly Token[], words: UncountedWords): ReadonlySet<Token> =>
55
+ new Set(tokens.filter((token, at) => words.particles.includes(token.surface) && isNumberingConnector(tokens, at, words)));
56
+
57
+ const COUNTER_LEXICON = "numbering-counter";
58
+ const NUMBER_LEXICON = "number-word";
59
+ const NUMERAL_GROUPS: readonly (string | undefined)[] = ["digit", "place"];
60
+
61
+ /** The words for `uncountedParticles`, read from the document's lexicons. */
62
+ export const uncountedWordsOf = (lexicons: Readonly<Record<string, Lexicon>>, particles: readonly string[]): UncountedWords => ({
63
+ particles,
64
+ numeralChars: new Set((lexicons[NUMBER_LEXICON] ?? []).filter((entry) => NUMERAL_GROUPS.includes(entry.group)).map((entry) => entry.pattern)),
65
+ counters: (lexicons[COUNTER_LEXICON] ?? []).map((entry) => entry.pattern),
66
+ });
67
+
68
+ /** The tokens of a sentence without the particles that join the parts of a number. */
69
+ export const countedTokens = (tokens: readonly Token[], words: UncountedWords): readonly Token[] => {
70
+ const uncounted = uncountedParticles(tokens, words);
71
+ return uncounted.size === 0 ? tokens : tokens.filter((token) => !uncounted.has(token));
72
+ };