2-- Module : Streamly.Internal.Unicode.Char
3-- Copyright : (c) 2018 Composewell Technologies
5-- License : BSD-3-Clause
6-- Maintainer : streamly@composewell.com
7-- Stability : experimental
10module Streamly.Internal.Unicode.Char
15 -- * Unicode aware operations
23 -- * Unicode normalization
24 , NormalizationMode(..)
31import Data.Char (isAsciiUpper, isAsciiLower, chr, ord)
32import Data.Typeable (Typeable)
34import Unicode.Char (DecomposeMode(..))
35import Streamly.Internal.Data.Stream.IsStream.Type
36 (IsStream, fromStreamD, toStreamD)
37import Streamly.Internal.Data.Stream.StreamD (Stream(..), Step (..))
39import qualified Unicode.Char as Char
41-------------------------------------------------------------------------------
42-- Unicode aware operations on strings
43-------------------------------------------------------------------------------
45-- | Select alphabetic characters in the ascii character set.
49{-# INLINE isAsciiAlpha #-}
50isAsciiAlpha :: Char -> Bool
51isAsciiAlpha c = isAsciiUpper c || isAsciiLower c
53-------------------------------------------------------------------------------
54-- Unicode aware operations on strings
55-------------------------------------------------------------------------------
60toCaseFold :: IsStream t => Char -> t m Char
65toLower :: IsStream t => Char -> t m Char
70toUpper :: IsStream t => Char -> t m Char
75toTitle :: IsStream t => Char -> t m Char
79-------------------------------------------------------------------------------
80-- Unicode normalization
81-------------------------------------------------------------------------------
84 = NFD -- ^ Canonical decomposition.
85 | NFKD -- ^ Compatibility decomposition.
86 | NFC -- ^ Canonical decomposition followed by canonical composition.
87 | NFKC -- ^ Compatibility decomposition followed by canonical composition.
88 deriving (Eq, Show, Enum, Typeable)
90-------------------------------------------------------------------------------
91-- Normalization combinators
92-------------------------------------------------------------------------------
96{-# INLINE insertIntoReBuf #-}
97insertIntoReBuf :: Char -> ReBuf -> ReBuf
98insertIntoReBuf c [] = [c]
99insertIntoReBuf c xxs@(x:xs)
100 | Char.combiningClass c < Char.combiningClass x = c : xxs
101 | otherwise = x : insertIntoReBuf c xs
103-- {-# ANN type DecomposeState Fuse #-}
104data DecomposeState st
105 = YieldCharList [Char] (DecomposeState st)
106 | ReadInputChar ReBuf st
108 | IsDecomposable [Char] ReBuf st
111{-# INLINE_NORMAL decomposeD #-}
113 Monad m => Bool -> DecomposeMode -> Stream m Char -> Stream m Char
114decomposeD decomposeHangul mode (Stream step state) =
115 Stream sstep (ReadInputChar [] state)
119 {-# INLINE_LATE sstep #-}
120 -- XXX Does this cause any problem?
121 sstep _ (YieldCharList [] ns) = return $ Skip ns
122 sstep _ (YieldCharList (ch:chs) ns) =
123 return $ Yield ch (YieldCharList chs ns)
124 sstep gst (ReadInputChar rebuf st) = do
130 | Char.isHangul ch ->
132 then YieldCharList rebuf (IsHangul ch st1)
135 (ReadInputChar [] st1)
136 | Char.isDecomposable mode ch ->
137 IsDecomposable (Char.decompose mode ch) rebuf st1
139 if Char.isCombining ch
140 then ReadInputChar (insertIntoReBuf ch rebuf) st1
143 (ReadInputChar [] st1)
144 Skip st1 -> ReadInputChar rebuf st1
145 Stop -> YieldCharList rebuf DecomposeStop
146 sstep _ (IsHangul ch st) =
149 $ let (l, v, t) = Char.decomposeHangul ch
150 in if t == chr Char.jamoTFirst
151 then YieldCharList [l, v] (ReadInputChar [] st)
152 else YieldCharList [l, v, t] (ReadInputChar [] st)
153 sstep _ (IsDecomposable [] rebuf st) =
154 return $ Skip $ ReadInputChar rebuf st
155 sstep _ (IsDecomposable (ch:chs) rebuf st)
156 | Char.isDecomposable mode ch =
158 $ Skip $ IsDecomposable (Char.decompose mode ch ++ chs) rebuf st
162 $ if Char.isCombining ch
163 then IsDecomposable chs (insertIntoReBuf ch rebuf) st
164 else YieldCharList (rebuf ++ [ch]) (IsDecomposable chs [] st)
165 sstep _ DecomposeStop = return Stop
167-- Hold an L to wait for V, hold an LV to wait for T.
169 = Jamo !Char -- Jamo L, V or T
170 | Hangul !Char -- Hangul Syllable LV or LVT
173{-# INLINE fromJamoBuf #-}
174fromJamoBuf :: JamoBuf -> Char
175fromJamoBuf (Jamo ch) = ch
176fromJamoBuf (Hangul ch) = ch
177fromJamoBuf (HangulLV ch) = ch
179-- {-# ANN type ComposeState Fuse #-}
181 = YieldChar Char (ComposeState st)
182 | YieldList [Char] (ComposeState st)
184 | ComposeReg Int [Char] st
185 | ComposeJamo JamoBuf st
188-- Assumes every character except hangul characters are fully decomposed and the
189-- combining characters are reordered. Hangul characters may or may not be
191{-# INLINE_EARLY partialComposeD #-}
192partialComposeD :: Monad m => Stream m Char -> Stream m Char
193partialComposeD (Stream step state) = Stream step' (ComposeNone state)
197 {-# INLINE_NORMAL step' #-}
198 step' _ ComposeStop = return Stop
199 step' _ (YieldChar ch ns) = return $ Yield ch ns
200 step' _ (YieldList [] ns) = return $ Skip ns
201 step' _ (YieldList (x:xs) ns) = return $ Yield x $ YieldList xs ns
202 step' gst (ComposeNone st) = do
206 Yield x st1 -> Skip $ composeNone x st1
207 Skip st1 -> Skip $ ComposeNone st1
209 step' gst (ComposeJamo jbuf st) = do
213 Yield x st1 -> Skip $ composeJamo jbuf x st1
214 Skip st1 -> Skip $ ComposeJamo jbuf st1
215 Stop -> Skip $ YieldChar (fromJamoBuf jbuf) ComposeStop
216 step' gst (ComposeReg i rbuf st) = do
220 Yield x st1 -> Skip $ composeReg i rbuf x st1
221 Skip st1 -> Skip $ ComposeReg i rbuf st1
222 Stop -> Skip $ YieldList rbuf ComposeStop
224 {-# INLINE initHangul #-}
225 initHangul c st = ComposeJamo (Hangul c) st
227 {-# INLINE initJamo #-}
228 initJamo c st = ComposeJamo (Jamo c) st
230 {-# INLINE initReg #-}
231 initReg !c st = ComposeReg 0 [c] st
233 {-# INLINE composeNone #-}
235 | Char.isHangul ch = initHangul ch st
236 | Char.isJamo ch = initJamo ch st
237 | otherwise = initReg ch st
239 {-# INLINE composeCharHangul #-}
240 composeCharHangul jbuf ch st =
241 YieldChar (fromJamoBuf jbuf) $ ComposeJamo (Hangul ch) st
243 {-# INLINE composeCharJamo #-}
244 composeCharJamo jbuf ch st
245 | ich <= Char.jamoLLast =
246 YieldChar (fromJamoBuf jbuf) $ ComposeJamo (Jamo ch) st
247 | ich < Char.jamoVFirst = flushAndWrite jbuf ch st
248 | ich <= Char.jamoVLast =
251 case Char.jamoLIndex c of
253 let vi = ich - Char.jamoVFirst
254 lvi = li * Char.jamoNCount + vi * Char.jamoTCount
255 lv = chr (Char.hangulFirst + lvi)
256 in ComposeJamo (HangulLV lv) st
257 Nothing -> writeTwo c ch st
258 Hangul c -> writeTwo c ch st
259 HangulLV c -> writeTwo c ch st
260 | ich <= Char.jamoTFirst = flushAndWrite jbuf ch st
262 let ti = ich - Char.jamoTFirst
264 Jamo c -> writeTwo c ch st
266 | Char.isHangulLV c -> writeLVT c ti st
267 | otherwise -> writeTwo c ch st
268 HangulLV c -> writeLVT c ti st
272 flushAndWrite jb c s = YieldList [fromJamoBuf jb, c] $ ComposeNone s
275 let lvt = chr $ ord lv + ti
276 in YieldChar lvt $ ComposeNone s
278 writeTwo c1 c2 s = YieldList [c1, c2] $ ComposeNone s
282 {-# INLINE composeJamo #-}
283 composeJamo jbuf ch st
284 | Char.isJamo ch = composeCharJamo jbuf ch st
285 | Char.isHangul ch = composeCharHangul jbuf ch st
286 | otherwise = YieldChar (fromJamoBuf jbuf) (ComposeReg 0 [ch] st)
288 -- i ~ Char.combiningClass (last rbuf)
289 {-# INLINE composeCharCombining #-}
290 composeCharCombining i rbuf ch st =
292 then case Char.compose str ch of
293 Nothing -> ComposeReg cch (rbuf ++ [ch]) st
294 Just x -> ComposeReg i (x : tail rbuf) st
295 else ComposeReg i (rbuf ++ [ch]) st
300 cch = Char.combiningClass ch
302 {-# INLINE composeReg #-}
303 composeReg i rbuf !ch !st
304 | Char.isHangul ch = YieldList rbuf $ initHangul ch st
305 | Char.isJamo ch = YieldList rbuf $ initJamo ch st
306 | Char.isCombining ch = composeCharCombining i rbuf ch st
308 , Char.isCombiningStarter ch
309 , Just x <- Char.composeStarters s ch = ComposeReg 0 [x] st
310 | otherwise = YieldList rbuf $ ComposeReg 0 [ch] st
312normalizeD :: Monad m => NormalizationMode -> Stream m Char -> Stream m Char
313normalizeD NFD = decomposeD True Canonical
314normalizeD NFKD = decomposeD True Kompat
315normalizeD NFC = partialComposeD . decomposeD False Canonical
316normalizeD NFKC = partialComposeD . decomposeD False Kompat
318normalize :: (IsStream t, Monad m) => NormalizationMode -> t m Char -> t m Char
319normalize mode = fromStreamD . normalizeD mode . toStreamD
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list