Skip to content

Commit 4751b15

Browse files
committed
sskt-specific tokenization
1 parent 68ae711 commit 4751b15

2 files changed

Lines changed: 367 additions & 11 deletions

File tree

botok/utils/corpus_normalization.py

Lines changed: 136 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import unicodedata
33
from enum import Enum
44
from .unicode_normalization import normalize_unicode
5+
from .standard_tibetan import is_standard_tibetan, split_into_stacks
56

67
# Normalize all line breaks to '\n'
78
_LINEBREAKS_RE = re.compile(r"\r\n?|\u0085|\u2028|\u2029")
@@ -135,12 +136,121 @@ def normalize_corpus(
135136
_NON_TIBETAN_RE = re.compile(r"[^\u0F00-\u0FFF D]")
136137
_PUNCT_OR_SPACE_RE = re.compile(rf"[{_PUNCT} ]+")
137138
_LETTER_SPACE_RE = re.compile(rf"([{_LETTER}]) ")
138-
139-
140-
def normalize_for_perplexity(text: str) -> str:
139+
_LETTER_SPACE_REPL = r"\1" + "\u0F0B " # backreference + literal tsheg + space
140+
# space_after_tshegs: any punct-containing run → single shad surrounded by spaces
141+
_PUNCT_RUN_RE = re.compile(rf"[ ]*[{_PUNCT}][{_PUNCT} ]*")
142+
_MULTI_SPACE_RE = re.compile(r" {2,}")
143+
# Split on tsheg or space while capturing the delimiter
144+
_TSHEG_OR_SPACE_RE = re.compile(r"(\u0F0B| )")
145+
# space_after_tshegs: insert a space after any tsheg not already followed by one
146+
_TSHEG_NO_SPACE_RE = re.compile(r"\u0F0B(?! )")
147+
148+
149+
def _process_sskt(text: str, space_sskt: bool, fold_sskt: bool) -> str:
150+
"""Process non-standard (Sanskrit) syllables at the tsheg-delimited level.
151+
152+
Tshegs (U+0F0B) are the syllable separators within a sentence; spaces
153+
mark sentence/clause boundaries. The function walks each tsheg-delimited
154+
piece and, for non-standard syllables:
155+
156+
* ``space_sskt``: expands the syllable into its constituent stacks, each
157+
followed by a tsheg, with spaces between stacks.
158+
* ``fold_sskt``: accumulates consecutive non-standard syllables and
159+
replaces the whole run with the placeholder ``S``.
160+
"""
161+
parts = _TSHEG_OR_SPACE_RE.split(text)
162+
# parts alternates: [content₀, delim₀, content₁, delim₁, …, contentₙ]
163+
out: list[str] = []
164+
in_sskt_run = False
165+
166+
def flush_sskt() -> None:
167+
nonlocal in_sskt_run
168+
if in_sskt_run:
169+
out.append(" S")
170+
in_sskt_run = False
171+
172+
n = len(parts)
173+
for i in range(0, n, 2):
174+
content = parts[i]
175+
delim = parts[i + 1] if i + 1 < n else ""
176+
177+
if not content:
178+
if delim == " " and fold_sskt:
179+
flush_sskt()
180+
if delim:
181+
out.append(delim)
182+
continue
183+
184+
has_tibetan = any(0x0F40 <= ord(c) <= 0x0FBC for c in content)
185+
186+
if not has_tibetan:
187+
if fold_sskt:
188+
flush_sskt()
189+
out.append(content)
190+
if delim:
191+
out.append(delim)
192+
continue
193+
194+
std = is_standard_tibetan(content)
195+
196+
if std:
197+
if fold_sskt and in_sskt_run:
198+
flush_sskt()
199+
out.append(" ")
200+
out.append(content)
201+
if delim:
202+
out.append(delim)
203+
else:
204+
if fold_sskt:
205+
in_sskt_run = True
206+
if delim == " ":
207+
flush_sskt()
208+
out.append(delim)
209+
elif space_sskt:
210+
stacks = split_into_stacks(content)
211+
out.append(" ".join(s + "\u0F0B" for s in stacks))
212+
if delim == " ":
213+
out.append(delim)
214+
# tsheg delimiter absorbed as the tsheg of the last stack
215+
else:
216+
out.append(content)
217+
if delim:
218+
out.append(delim)
219+
220+
if fold_sskt:
221+
flush_sskt()
222+
223+
result = "".join(out)
224+
return _MULTI_SPACE_RE.sub(" ", result)
225+
226+
227+
def normalize_for_perplexity(
228+
text: str,
229+
space_after_tshegs: bool = False,
230+
space_sskt: bool = False,
231+
fold_sskt: bool = False,
232+
) -> str:
141233
"""
142234
Normalize Tibetan text for perplexity calculation.
143235
236+
Parameters
237+
----------
238+
text:
239+
Input text.
240+
space_after_tshegs:
241+
If ``True``, punctuation sequences are replaced by a shad (``།``)
242+
surrounded by spaces instead of a plain space, making sentence
243+
boundaries explicit. Each syllable then ends with ``་ `` (tsheg +
244+
space).
245+
space_sskt:
246+
If ``True``, non-standard (Sanskrit) syllables are split into their
247+
constituent stacks, each receiving its own tsheg. Recommended
248+
together with ``space_after_tshegs``.
249+
fold_sskt:
250+
If ``True``, consecutive runs of non-standard (Sanskrit) syllables
251+
are collapsed to the single placeholder token ``S``. Takes
252+
precedence over ``space_sskt`` when both are set.
253+
144254
Steps applied after ``normalize_corpus``:
145255
1. Replace NYIS TSHEG (U+0FD2) with TSHEG (U+0F0B); fold runs of
146256
consecutive TSHEGs to one.
@@ -157,9 +267,12 @@ def normalize_for_perplexity(text: str) -> str:
157267
with commas) with the placeholder ``D``.
158268
8. Strip any character outside the Tibetan Unicode block (U+0F00-
159269
U+0FFF), keeping spaces and the ``D`` placeholder.
160-
9. Collapse any run of punctuation (U+0F0D-U+0F14) and/or spaces
161-
to a single space.
162-
10. Ensure every letter before a space is followed by a TSHEG:
270+
9. Collapse punctuation / space runs:
271+
- default: to a single space.
272+
- space_after_tshegs: punct-containing runs → `` ། ``
273+
(shad surrounded by spaces); remaining space runs collapsed.
274+
9b. (space_sskt / fold_sskt) Process non-standard syllable tokens.
275+
10. Ensure every syllable-final letter before a space carries a TSHEG:
163276
letter + space → letter + U+0F0B + space.
164277
11. Strip leading/trailing whitespace.
165278
"""
@@ -169,8 +282,8 @@ def normalize_for_perplexity(text: str) -> str:
169282
text = text.replace("\u0FD2", "\u0F0B")
170283
text = _MULTI_TSHEG_RE.sub("\u0F0B", text)
171284

172-
# 2) Remove honorific particles flourish
173-
text = text.translate({ord("\u0F35"): None, ord("\u0F37"): None})
285+
# 2) Remove honorific particles and TSA-PHRU flourish
286+
text = text.translate({ord("\u0F35"): None, ord("\u0F37"): None, ord("\u0F39"): None})
174287

175288
# 3) Normalize nasalization marks to RJES SU NGA RO (U+0F7E)
176289
text = text.replace("\u0F82", "\u0F7E").replace("\u0F83", "\u0F7E")
@@ -190,11 +303,23 @@ def normalize_for_perplexity(text: str) -> str:
190303
# 8) Strip characters outside the Tibetan block (keep D placeholder and spaces)
191304
text = _NON_TIBETAN_RE.sub(" ", text)
192305

193-
# 9) Collapse punctuation / space runs to a single space
194-
text = _PUNCT_OR_SPACE_RE.sub(" ", text)
306+
# 9) Collapse punctuation / space runs
307+
if space_after_tshegs:
308+
text = _PUNCT_RUN_RE.sub(" \u0F0D ", text)
309+
text = _MULTI_SPACE_RE.sub(" ", text)
310+
else:
311+
text = _PUNCT_OR_SPACE_RE.sub(" ", text)
312+
313+
# 9b) Sanskrit syllable handling
314+
if space_sskt or fold_sskt:
315+
text = _process_sskt(text, space_sskt, fold_sskt)
316+
317+
# 9c) In space_after_tshegs mode, guarantee a space follows every tsheg
318+
if space_after_tshegs:
319+
text = _TSHEG_NO_SPACE_RE.sub("\u0F0B ", text)
195320

196321
# 10) Ensure syllable-final letters carry a TSHEG before any space
197-
text = _LETTER_SPACE_RE.sub(r"\1\u0F0B ", text)
322+
text = _LETTER_SPACE_RE.sub(_LETTER_SPACE_REPL, text)
198323

199324
return text.strip()
200325

0 commit comments

Comments
 (0)