22import unicodedata
33from enum import Enum
44from .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