1 {-# LANGUAGE StrictData #-}
2 {-# OPTIONS_GHC -Wno-orphans #-}
4 module Worksheets.Utils.IPA (
5 module Worksheets.Utils.IPA,
9 import Data.Char qualified as Char
10 import Data.Text qualified as Text
12 import Worksheets.Utils.JSON qualified as JSON
13 import Worksheets.Utils.Prelude
14 import Prelude (error)
16 toIPA_ :: HasCallStack => ReprIPA a => a -> IPA
17 toIPA_ = toIPA >>> fromMaybe (error "toIPA_")
19 instance IsString (Syllable []) where
20 fromString "" = Syllable []
25 instance Semigroup (Syllable []) where
26 WithSuprasegmentalFeature f x <> y = x <> WithSuprasegmentalFeature f y
27 Syllable x <> y = mergeSyl x y
29 mergeSyl s (WithSuprasegmentalFeature f t) =
30 WithSuprasegmentalFeature f (mergeSyl s t)
31 mergeSyl s (Syllable t) = Syllable (s <> t)
32 instance IsString Segment where
36 & either (error . show) id
38 ipaToText :: Syllable [] -> Text
39 ipaToText = toIPA_ >>> unIPA
41 type IPAWord = NonEmpty (Syllable [])
43 = IPAPhonemic (NonEmpty IPAWord)
44 | IPAPhonetic (NonEmpty IPAWord)
45 | IPAPhonError (Text, [IPAException])
48 newtype IPAPhons = IPAPhons {unIPAPhons :: [IPAPhon]}
50 deriving newtype (Semigroup)
51 deriving newtype (Monoid)
53 instance JSON.FromJSON IPAPhons where
55 v & JSON.withText "IPAPhons" \(Text.split (== ',') -> ipaSentences) -> do
56 let sentencesIPA = ipaSentences <&> (Text.dropAround Char.isSpace >>> textToIPA)
57 return $ IPAPhons sentencesIPA
58 textToIPA :: Text -> IPAPhon
60 let (errs, wordsIPAs) = words <&> parseSyllables @[] & partitionEithers
61 in case wordsIPAs & mapMaybe nonEmpty & nonEmpty of
62 Nothing -> IPAPhonError (t, [InvalidIPA "no syllable"])
64 | null errs -> IPAPhonemic wordsIPAsNE
65 | otherwise -> IPAPhonError (t, errs)
68 | Text.isPrefixOf "[" t || Text.isSuffixOf "]" t =
70 & Text.dropAround (\c -> c == '[' || c == ']')
74 & Text.dropAround (\c -> c == '\\' || c == '/')
76 instance JSON.ToJSON IPAPhons where
77 toJSON (IPAPhons ipas) =
78 JSON.toJSON $ ipas <&> ipaphonToText
80 ipaphonToText :: IPAPhon -> Text
82 IPAPhonemic ipaWords -> ipaWords & ipaWordsToText (Just Phonemic)
83 IPAPhonetic ipaWords -> ipaWords & ipaWordsToText (Just Phonetic)
84 IPAPhonError (t, _err) -> t
86 ipaWordsToText :: Maybe Delimiter -> NonEmpty IPAWord -> Text
87 ipaWordsToText delim words =
91 [ word & toList <&> toIPA_ & fromList & sconcat & unIPA
92 | word <- words & toList
96 (start, end) = case delim of
98 Just Phonetic -> ("[", "]")
99 Just Phonemic -> ("\\", "\\")
101 dropSupraSegmentalFeatures :: NonEmpty IPAWord -> NonEmpty IPAWord
102 dropSupraSegmentalFeatures = fmap (fmap (Syllable . syllableToSegments))
104 dropSegmentalFeatures :: Segment -> Segment
105 dropSegmentalFeatures = \case
106 WithSegmentalFeature _f seg -> dropSegmentalFeatures seg
109 isSilent :: Foldable t => Syllable t -> Bool
110 isSilent = syllableToSegments >>> all (== Zero)
112 syllableToSegments :: Syllable t -> t Segment
113 syllableToSegments = \case
114 Syllable segs -> segs
115 WithSuprasegmentalFeature _feat syl -> syllableToSegments syl
117 suprasegmentalFeatures :: Syllable t -> [SuprasegmentalFeature]
118 suprasegmentalFeatures = \case
120 WithSuprasegmentalFeature feat syl -> feat : suprasegmentalFeatures syl
122 setSuprasegmentalFeatures :: [SuprasegmentalFeature] -> Syllable t -> Syllable t
123 setSuprasegmentalFeatures [] = id
124 setSuprasegmentalFeatures [f] = \case
125 s@Syllable{} -> WithSuprasegmentalFeature f s
126 WithSuprasegmentalFeature g syl
127 | f == g -> WithSuprasegmentalFeature f syl
128 | otherwise -> WithSuprasegmentalFeature g $ setSuprasegmentalFeatures [f] syl
129 setSuprasegmentalFeatures (f : fs) =
130 setSuprasegmentalFeatures [f] . (setSuprasegmentalFeatures fs)