packages feed

megaparsec-tests-9.8.2: tests/Text/Megaparsec/UnicodeSpec.hs

module Text.Megaparsec.UnicodeSpec (spec) where

import Control.Monad (forM_)
import Test.Hspec
import qualified Text.Megaparsec.Unicode as Unicode

spec :: Spec
spec = do
  describe "stringLength" $ do
    it "computes correct length in the presence of wide chars" $
      Unicode.stringLength "123 구구 이면" `shouldBe` 13
    it "computes correct length in the presence of zero-width chars" $
      -- A letter, a combining acute accent that occupies no cell of its
      -- own, and a wide ideograph.
      Unicode.stringLength "e\769日" `shouldBe` 3
  describe "charLength" $ do
    it "returns 1 for ordinary chars" $
      expectLengths 1 narrowChars
    it "returns 2 for wide chars" $
      expectLengths 2 wideChars
    it "returns 0 for zero-width chars" $
      expectLengths 0 zeroWidthChars
    it "agrees with isWideChar and isZeroWidthChar for every code point" $
      -- Among other things this pins down the shortcut that both functions
      -- take for the characters below the top of the Latin-1 range.
      let inconsistent ch =
            let n = Unicode.charLength ch
             in n `notElem` [0, 1, 2]
                  || Unicode.isWideChar ch /= (n == 2)
                  || Unicode.isZeroWidthChar ch /= (n == 0)
       in take 5 (filter inconsistent [minBound .. maxBound]) `shouldBe` []
  describe "isWideChar" $ do
    it "returns False for non-wide chars" $
      Unicode.isWideChar 'a' `shouldBe` False
    it "returns True for wide chars" $
      Unicode.isWideChar '구' `shouldBe` True
    it "returns False for wide chars that combine with their neighbour" $
      -- U+302A is Wide by East Asian Width but it is a non-spacing mark, so
      -- it does not occupy two columns of its own.
      Unicode.isWideChar '\12330' `shouldBe` False
  describe "isZeroWidthChar" $ do
    it "returns False for ordinary chars" $
      Unicode.isZeroWidthChar 'a' `shouldBe` False
    it "returns True for control chars" $
      Unicode.isZeroWidthChar '\SOH' `shouldBe` True
    it "returns True for combining marks" $
      Unicode.isZeroWidthChar '\769' `shouldBe` True

-- | Check that every character in the collection has the given length,
-- naming the offending one if it does not.
expectLengths :: Int -> [(String, Char)] -> Expectation
expectLengths n cs =
  forM_ cs $ \(name, ch) ->
    (name, Unicode.charLength ch) `shouldBe` (name, n)

-- | Characters that occupy a single column.
narrowChars :: [(String, Char)]
narrowChars =
  [ ("U+0061 latin small letter a", 'a'),
    ("U+00E9 latin small letter e with acute", '\233'),
    ("U+0416 cyrillic capital letter zhe", '\1046'),
    ("U+05D0 hebrew letter alef", '\1488'),
    ("U+0E01 thai character ko kai", '\3585'),
    ("U+2192 rightwards arrow", '\8594')
  ]

-- | Characters that occupy two columns. The comments give the version of
-- the Unicode standard that assigned them, which is what makes them a
-- reasonable regression test for stale character width data.
wideChars :: [(String, Char)]
wideChars =
  [ ("U+4E00 cjk unified ideograph (1.1)", '\19968'),
    ("U+9FFF cjk unified ideograph (14.0)", '\40959'),
    ("U+4DBF cjk unified ideograph (13.0)", '\19903'),
    ("U+31BF bopomofo letter (14.0)", '\12735'),
    ("U+AC00 hangul syllable ga", '\44032'),
    ("U+FF21 fullwidth latin capital letter a", '\65313'),
    ("U+1F600 grinning face (6.1)", '\128512'),
    ("U+1F6DD playground slide (14.0)", '\128733'),
    ("U+1FAE0 melting face (14.0)", '\129760'),
    ("U+30000 cjk unified ideograph extension g (13.0)", '\196608'),
    ("U+31350 cjk unified ideograph extension h (15.0)", '\201552')
  ]

-- | Characters that occupy no columns at all.
zeroWidthChars :: [(String, Char)]
zeroWidthChars =
  [ ("U+0001 start of heading", '\SOH'),
    ("U+00AD soft hyphen", '\173'),
    ("U+0301 combining acute accent", '\769'),
    ("U+0651 arabic shadda", '\1617'),
    ("U+093C devanagari sign nukta", '\2364'),
    ("U+0BCD tamil sign virama", '\3021'),
    ("U+0E31 thai character mai han akat", '\3633'),
    ("U+1AB0 combining doubled circumflex accent", '\6832'),
    ("U+1DC0 combining dotted grave accent", '\7616'),
    ("U+200D zero width joiner", '\8205'),
    ("U+2060 word joiner", '\8288'),
    ("U+20E3 combining enclosing keycap", '\8419'),
    ("U+FE0F variation selector-16", '\65039'),
    ("U+FEFF zero width no-break space", '\65279'),
    ("U+11FF hangul jongseong ssangnieun", '\4607'),
    ("U+E0101 variation selector-18", '\917761')
  ]