diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,12 @@
+## Changes in 3.4.0.1
+
+ * Address new `x-partial` warning of GHC 9.8.
+ * Alex 3.4.0.1 needs GHC 8.0 or higher to build.
+   The code it generates is the same as 3.4.0.0, so it will likely work for older GHCs.
+ * Tested with GHC 8.0 - 9.8.1.
+
+_Andreas Abel, 2023-10-29_
+
 ## Changes in 3.4.0.0
 
  * New wrappers to lex strict `Text`:
diff --git a/alex.cabal b/alex.cabal
--- a/alex.cabal
+++ b/alex.cabal
@@ -1,6 +1,6 @@
 cabal-version: >= 1.10
 name: alex
-version: 3.4.0.0
+version: 3.4.0.1
 -- don't forget updating changelog.md!
 license: BSD3
 license-file: LICENSE
@@ -22,8 +22,9 @@
 build-type: Simple
 
 tested-with:
-        GHC == 9.6.2
-        GHC == 9.4.5
+        GHC == 9.8.1
+        GHC == 9.6.3
+        GHC == 9.4.7
         GHC == 9.2.8
         GHC == 9.0.2
         GHC == 8.10.7
@@ -32,11 +33,6 @@
         GHC == 8.4.4
         GHC == 8.2.2
         GHC == 8.0.2
-        GHC == 7.10.3
-        GHC == 7.8.4
-        GHC == 7.6.3
-        GHC == 7.4.2
-        GHC == 7.0.4
 
 data-dir: data/
 
@@ -104,39 +100,46 @@
   hs-source-dirs: src
   main-is: Main.hs
 
-  build-depends: base >= 2.1 && < 5
-               , array
-               , containers
-               , directory
-
-  default-language: Haskell98
-  default-extensions: CPP
-  other-extensions: MagicHash
+  build-depends:
+      base >= 4.9 && < 5
+        -- Data.List.NonEmpty enters `base` at 4.9
+    , array
+    , containers
+    , directory
 
-  ghc-options: -Wall -rtsopts
+  default-language:
+    Haskell2010
+  default-extensions:
+    PatternSynonyms
+    ScopedTypeVariables
+    TupleSections
+  other-extensions:
+    CPP
+    FlexibleContexts
+    MagicHash
+    NondecreasingIndentation
+    OverloadedLists
+  ghc-options: -Wall -Wcompat -rtsopts
 
   other-modules:
-        AbsSyn
-        CharSet
-        DFA
-        DFAMin
-        DFS
-        Info
-        Map
-        NFA
-        Output
-        Paths_alex
-        Parser
-        ParseMonad
-        Scan
-        Set
-        Sort
-        Util
-        UTF8
-        Data.Ranged
-        Data.Ranged.Boundaries
-        Data.Ranged.RangedSet
-        Data.Ranged.Ranges
+    AbsSyn
+    CharSet
+    DFA
+    DFAMin
+    DFS
+    Info
+    NFA
+    Output
+    Paths_alex
+    Parser
+    ParseMonad
+    Scan
+    Util
+    UTF8
+    Data.Ranged
+    Data.Ranged.Boundaries
+    Data.Ranged.RangedSet
+    Data.Ranged.Ranges
 
 test-suite tests
   type: exitcode-stdio-1.0
@@ -144,6 +147,6 @@
   -- This line is important as it ensures that the local `exe:alex` component declared above is built before the test-suite component is invoked, as well as making sure that `alex` is made available on $PATH and `$alex_datadir` is set accordingly before invoking `test.hs`
   build-tools: alex
 
-  default-language: Haskell98
+  default-language: Haskell2010
 
   build-depends: base, process
diff --git a/src/AbsSyn.hs b/src/AbsSyn.hs
--- a/src/AbsSyn.hs
+++ b/src/AbsSyn.hs
@@ -23,16 +23,15 @@
   StrType(..)
   ) where
 
-import CharSet ( CharSet, Encoding )
-import Map ( Map )
-import qualified Map hiding ( Map )
-import Data.IntMap (IntMap)
-import Sort ( nub' )
-import Util ( str, nl )
-
-import Data.Maybe ( fromJust )
+import CharSet     ( CharSet, Encoding )
+import Data.Maybe  ( fromJust )
+import Data.Map    ( Map )
+import Data.IntMap ( IntMap )
+import Util        ( str, nl )
+import qualified Data.Map as Map
+import qualified Data.Set as Set
 
-infixl 4 :|
+infixl 4 :||
 infixl 5 :%%
 
 -- -----------------------------------------------------------------------------
@@ -200,7 +199,7 @@
 -- `RExp' provides an abstract syntax for regular expressions.  `Eps' will
 -- match empty strings; `Ch p' matches strings containing a single character
 -- `c' if `p c' is true; `re1 :%% re2' matches a string if `re1' matches one of
--- its prefixes and `re2' matches the rest; `re1 :| re2' matches a string if
+-- its prefixes and `re2' matches the rest; `re1 :|| re2' matches a string if
 -- `re1' or `re2' matches it; `Star re', `Plus re' and `Ques re' can be
 -- expressed in terms of the other operators.  See the definitions of `ARexp'
 -- for a formal definition of the semantics of these operators.
@@ -209,7 +208,7 @@
   = Eps            -- ^ Empty.
   | Ch CharSet     -- ^ Singleton.
   | RExp :%% RExp  -- ^ Sequence.
-  | RExp :| RExp   -- ^ Alternative.
+  | RExp :|| RExp   -- ^ Alternative.
   | Star RExp      -- ^ Zero or more repetitions.
   | Plus RExp      -- ^ One  or more repetitions.
   | Ques RExp      -- ^ Zero or one  repetitions.
@@ -218,7 +217,7 @@
   showsPrec _ Eps = showString "()"
   showsPrec _ (Ch _) = showString "[..]"
   showsPrec _ (l :%% r)  = shows l . shows r
-  showsPrec _ (l :| r)  = shows l . ('|':) . shows r
+  showsPrec _ (l :|| r)  = shows l . ('|':) . shows r
   showsPrec _ (Star r) = shows r . ('*':)
   showsPrec _ (Plus r) = shows r . ('+':)
   showsPrec _ (Ques r) = shows r . ('?':)
@@ -228,7 +227,7 @@
 nullable Eps       = True
 nullable Ch{}      = False
 nullable (l :%% r) = nullable l && nullable r
-nullable (l :|  r) = nullable l || nullable r
+nullable (l :||  r) = nullable l || nullable r
 nullable Star{}    = True
 nullable (Plus r)  = nullable r
 nullable Ques{}    = True
@@ -264,7 +263,7 @@
 arexp Eps = eps_ar
 arexp (Ch p) = ch_ar p
 arexp (re :%% re') = arexp re `seq_ar` arexp re'
-arexp (re :| re') = arexp re `bar_ar` arexp re'
+arexp (re :|| re') = arexp re `bar_ar` arexp re'
 arexp (Star re) = star_ar (arexp re)
 arexp (Plus re) = plus_ar (arexp re)
 arexp (Ques re) = ques_ar (arexp re)
@@ -330,10 +329,14 @@
 
         code_map = Map.fromList name_code_pairs
 
-        name_code_pairs = zip (nub' (<=) nms) [1..]
+        name_code_pairs = zip nms [1..]
 
-        nms = [nm | RECtx{reCtxStartCodes = scs} <- scannerTokens scan,
-                    (nm,_) <- scs, nm /= "0"]
+        nms = Set.toAscList . Set.fromList $
+                [ nm
+                | RECtx{ reCtxStartCodes = scs } <- scannerTokens scan
+                , (nm, _) <- scs
+                , nm /= "0"
+                ]
 
 
 -- Grab the code fragments for the token actions, and replace them
diff --git a/src/CharSet.hs b/src/CharSet.hs
--- a/src/CharSet.hs
+++ b/src/CharSet.hs
@@ -10,6 +10,8 @@
 --
 -- ----------------------------------------------------------------------------}
 
+{-# LANGUAGE OverloadedLists #-}
+
 module CharSet (
   setSingleton,
 
@@ -36,19 +38,30 @@
   byteSetElem
   ) where
 
-import Data.Array
-import Data.Ranged
-import Data.Word
-import Data.Maybe (catMaybes)
-import Data.Char (chr,ord)
-import UTF8
+import           Data.Array         ( Array, array )
+import           Data.Char          ( chr, ord )
+import           Data.Maybe         ( catMaybes )
+import           Data.Word          ( Word8 )
+import           Data.List.NonEmpty ( pattern (:|), (<|) )
+import qualified Data.List.NonEmpty as List1
 
+import           UTF8               ( List1, encode )
+import           Data.Ranged
+  ( Boundary( BoundaryAbove, BoundaryAboveAll, BoundaryBelow, BoundaryBelowAll )
+  , DiscreteOrdered, Range( Range ), RSet
+  , makeRangedSet
+  , rSetDifference, rSetEmpty, rSetHas, rSetNegation, rSetRanges, rSetUnion, rSingleton
+  )
+
+-- import Data.Semigroup (sconcat)
+-- import qualified Data.Foldable      as Fold
+
 type Byte = Word8
 -- Implementation as functions
 type CharSet = RSet Char
 type ByteSet = RSet Byte
 -- type Utf8Set = RSet [Byte]
-type Utf8Range = Span [Byte]
+type Utf8Range = Span (List1 Byte)
 
 data Encoding = Latin1 | UTF8
               deriving (Eq, Show)
@@ -83,16 +96,19 @@
 charSetRange :: Char -> Char -> CharSet
 charSetRange c1 c2 = makeRangedSet [Range (BoundaryBelow c1) (BoundaryAbove c2)]
 
+{-# INLINE bytes #-}
+bytes :: [Byte]
+bytes = [minBound..maxBound]
+
 byteSetToArray :: ByteSet -> Array Byte Bool
-byteSetToArray set = array (fst (head ass), fst (last ass)) ass
-  where ass = [(c,rSetHas set c) | c <- [0..0xff]]
+byteSetToArray set = array (minBound, maxBound) [(c, rSetHas set c) | c <- bytes]
 
 byteSetElems :: ByteSet -> [Byte]
-byteSetElems set = [c | c <- [0 .. 0xff], rSetHas set c]
+byteSetElems set = filter (rSetHas set) bytes
 
 charToRanges :: Encoding -> CharSet -> [Utf8Range]
 charToRanges Latin1 =
-    map (fmap ((: []).fromIntegral.ord)) -- Span [Byte]
+    map (fmap ((:| []) . fromIntegral . ord)) -- Span [Byte]
   . catMaybes
   . fmap (charRangeToCharSpan False)
   . rSetRanges
@@ -105,20 +121,20 @@
   . rSetRanges
 
 -- | Turns a range of characters expressed as a pair of UTF-8 byte sequences into a set of ranges, in which each range of the resulting set is between pairs of sequences of the same length
-toUtfRange :: Span [Byte] -> [Span [Byte]]
-toUtfRange (Span x y) = fix x y
+toUtfRange :: Span (List1 Byte) -> [Span (List1 Byte)]
+toUtfRange (Span x y) = List1.toList $ fix x y
 
-fix :: [Byte] -> [Byte] -> [Span [Byte]]
+fix :: List1 Byte -> List1 Byte -> List1 (Span (List1 Byte))
 fix x y
     | length x == length y = [Span x y]
-    | length x == 1 = Span x [0x7F] : fix [0xC2,0x80] y
-    | length x == 2 = Span x [0xDF,0xBF] : fix [0xE0,0x80,0x80] y
-    | length x == 3 = Span x [0xEF,0xBF,0xBF] : fix [0xF0,0x80,0x80,0x80] y
+    | length x == 1 = Span x [0x7F] <| fix [0xC2,0x80] y
+    | length x == 2 = Span x [0xDF,0xBF] <| fix [0xE0,0x80,0x80] y
+    | length x == 3 = Span x [0xEF,0xBF,0xBF] <| fix [0xF0,0x80,0x80,0x80] y
     | otherwise = error "fix: incorrect input given"
 
 
-byteRangeToBytePair :: Span [Byte] -> ([Byte],[Byte])
-byteRangeToBytePair (Span x y) = (x,y)
+byteRangeToBytePair :: Span a -> (a, a)
+byteRangeToBytePair (Span x y) = (x, y)
 
 data Span a = Span a a -- lower bound inclusive, higher bound exclusive
                        -- (SDM: upper bound inclusive, surely?)
@@ -143,8 +159,8 @@
             BoundaryAboveAll | uni -> chr 0x10ffff
                              | otherwise -> chr 0xff
 
-byteRanges :: Encoding -> CharSet -> [([Byte],[Byte])]
-byteRanges enc =  fmap byteRangeToBytePair . charToRanges enc
+byteRanges :: Encoding -> CharSet -> [(List1 Byte, List1 Byte)]
+byteRanges enc = fmap byteRangeToBytePair . charToRanges enc
 
 byteSetRange :: Byte -> Byte -> ByteSet
 byteSetRange c1 c2 = makeRangedSet [Range (BoundaryBelow c1) (BoundaryAbove c2)]
@@ -164,4 +180,3 @@
           quoteH (BoundaryBelow a) = "c < " ++ show a
           quoteH (BoundaryAboveAll) = "True"
           quoteH (BoundaryBelowAll) = "False"
-
diff --git a/src/DFA.hs b/src/DFA.hs
--- a/src/DFA.hs
+++ b/src/DFA.hs
@@ -13,18 +13,21 @@
 --
 -- ----------------------------------------------------------------------------}
 
-module DFA(scanner2dfa) where
+module DFA (scanner2dfa) where
 
-import AbsSyn
-import qualified Map
+import Data.Array    ( (!) )
+import Data.Function ( on )
+import Data.Maybe    ( fromJust )
+
 import qualified Data.IntMap as IntMap
+import qualified Data.IntSet as IntSet
+import qualified Data.Map    as Map
+import qualified Data.List   as List
+
+import AbsSyn
 import NFA
-import Sort ( msort, nub' )
 import CharSet
 
-import Data.Array ( (!) )
-import Data.Maybe ( fromJust )
-
 {-                        Defined in the Scan Module
 
 -- (This section should logically belong to the DFA module but it has been
@@ -128,23 +131,20 @@
                    | Acc _ _ _ (RightContextRExp s) <- accs ]
 
         outs :: [(ByteSet,SNum)]
-        outs =  [ out | s <- ss, out <- nst_outs (nfa!s) ]
+        outs =  [ out | s <- ss, out <- nst_outs (nfa ! s) ]
 
-        accs = sort_accs [acc| s<-ss, acc<-nst_accs (nfa!s)]
+        accs = sort_accs [ acc | s <- ss, acc <- nst_accs (nfa ! s) ]
 
 -- `sort_accs' sorts a list of accept values into descending order of priority,
 -- eliminating any elements that follow an unconditional accept value.
 
-sort_accs:: [Accept a] -> [Accept a]
-sort_accs accs = foldr chk [] (msort le accs)
+sort_accs :: [Accept a] -> [Accept a]
+sort_accs accs = foldr chk [] $ List.sortBy (compare `on` accPrio) accs
         where
         chk acc@(Acc _ _ Nothing NoRightContext) _   = [acc]
         chk acc                                  rst = acc:rst
 
-        le (Acc{accPrio = n}) (Acc{accPrio=n'}) = n<=n'
 
-
-
 {------------------------------------------------------------------------------
                           State Sets and Partial DFAs
 ------------------------------------------------------------------------------}
@@ -160,19 +160,17 @@
 
 type StateSet = [SNum]
 
-new_pdfa:: Int -> NFA -> DFA StateSet a
+new_pdfa :: Int -> NFA -> DFA StateSet a
 new_pdfa starts nfa
- = DFA { dfa_start_states = start_ss,
-         dfa_states = Map.empty
+ = DFA { dfa_start_states = [ List.sort $ nst_cl $ nfa ! n | n <- [0 .. starts - 1] ]
+       , dfa_states       = Map.empty
        }
- where
-        start_ss = [ msort (<=) (nst_cl(nfa!n)) | n <- [0..(starts-1)]]
 
  -- starts is the number of start states
 
 -- constructs the epsilon-closure of a set of NFA states
-mk_ss:: NFA -> [SNum] -> StateSet
-mk_ss nfa l = nub' (<=) [s'| s<-l, s'<-nst_cl(nfa!s)]
+mk_ss :: NFA -> [SNum] -> StateSet
+mk_ss nfa l = IntSet.toAscList $ IntSet.fromList [ s' | s <- l, s' <- nst_cl (nfa ! s) ]
 
 add_pdfa:: StateSet -> State StateSet a -> DFA StateSet a -> DFA StateSet a
 add_pdfa ss pst (DFA st mp) = DFA st (Map.insert ss pst mp)
@@ -205,44 +203,3 @@
                                 RightContextRExp s ->
                                   RightContextRExp (lookup' (mk_ss nfa [s]))
                                 other -> other
-
-{-
-
--- `mk_st' constructs a state node from the list of accept values and a list of
--- transitions.  The transitions list all the valid transitions out of the
--- node; all invalid transitions should be represented in the array by state
--- -1.  `mk_st' has to work out whether the accept states contain an
--- unconditional entry, in which case the first field of `St' should be true,
--- and which default state to use in constructing the array (the array may span
--- a sub-range of the character set, the state number given the third argument
--- of `St' being taken as the default if an input character lies outside the
--- range).  The default values is chosen to minimise the bounds of the array
--- and so there are two candidates: the value that 0 maps to (in which case
--- some initial segment of the array may be omitted) or the value that 255 maps
--- to (in which case a final segment of the array may be omitted), hence the
--- calculation of `(df,bds)'.
---
--- Note that empty arrays are avoided as they can cause severe problems for
--- some popular Haskell compilers.
-
-mk_st:: [Accept Code] -> [(Char,Int)] -> State Code
-mk_st accs as =
-        if null as
-           then St accs (-1) (listArray ('0','0') [-1])
-           else St accs df (listArray bds [arr!c| c<-range bds])
-        where
-        bds = if sz==0 then ('0','0') else bds0
-
-        (sz,df,bds0) | sz1 < sz2 = (sz1,df1,bds1)
-                     | otherwise = (sz2,df2,bds2)
-
-        (sz1,df1,bds1) = mk_bds(arr!chr 0)
-        (sz2,df2,bds2) = mk_bds(arr!chr 255)
-
-        mk_bds df = (t-b, df, (chr b, chr (255-t)))
-                where
-                b = length (takeWhile id [arr!c==df| c<-['\0'..'\xff']])
-                t = length (takeWhile id [arr!c==df| c<-['\xff','\xfe'..'\0']])
-
-        arr = listArray ('\0','\xff') (take 256 (repeat (-1))) // as
--}
diff --git a/src/DFAMin.hs b/src/DFAMin.hs
--- a/src/DFAMin.hs
+++ b/src/DFAMin.hs
@@ -1,21 +1,23 @@
 {-# OPTIONS_GHC -fno-warn-name-shadowing #-}
 
-{-# LANGUAGE PatternGuards       #-}
-{-# LANGUAGE ScopedTypeVariables #-}
-{-# LANGUAGE TupleSections       #-}
+{-# LANGUAGE CPP #-}
 
 module DFAMin (minimizeDFA) where
 
 import AbsSyn
 
-import Data.Map (Map)
-import qualified Data.Map as Map
-import Data.IntSet (IntSet)
-import qualified Data.IntSet as IS
-import Data.IntMap (IntMap)
-import qualified Data.IntMap as IM
-import qualified Data.List as List
+import Data.IntMap ( IntMap )
+import Data.IntSet ( IntSet )
+import Data.Map    ( Map )
+#if !MIN_VERSION_containers(0,6,0)
+import Data.Maybe  ( mapMaybe )
+#endif
 
+import qualified Data.Map    as Map
+import qualified Data.IntSet as IntSet
+import qualified Data.IntMap as IntMap
+import qualified Data.List   as List
+
 -- % Hopcroft's Algorithm for DFA minimization (cut/pasted from Wikipedia):
 -- % X refines Y into Y1 and Y2 means
 -- %  Y1 := Y ∩ X
@@ -82,7 +84,7 @@
       number :: Int -> [EquivalenceClass] -> [(Int, EquivalenceClass)]
       number _ [] = []
       number n (ss:sss) =
-        case filter (`IS.member` ss) starts of
+        case filter (`IntSet.member` ss) starts of
           []      -> (n,ss) : number (n+1) sss
           starts' -> map (,ss) starts' ++ number n sss
           -- if one of the states of the minimized DFA corresponds
@@ -91,12 +93,12 @@
 
       states :: [(Int, State Int a)]
       states = [
-                let old_states = map (lookup statemap) (IS.toList equiv)
-                    accs = map fix_acc (state_acc (head old_states))
+                let old_states = map (lookup statemap) (IntSet.toList equiv)
+                    accs = map fix_acc (state_acc (headWithDefault undefined old_states))
                            -- accepts should all be the same
-                    out  = IM.fromList [ (b, get_new old)
+                    out  = IntMap.fromList [ (b, get_new old)
                                            | State _ out <- old_states,
-                                             (b,old) <- IM.toList out ]
+                                             (b,old) <- IntMap.toList out ]
                 in (n, State accs out)
                | (n, equiv) <- numbered_states
                ]
@@ -116,7 +118,7 @@
 
       old_to_new :: Map Int Int
       old_to_new = Map.fromList [ (s,n) | (n,ss) <- numbered_states,
-                                          s <- IS.toList ss ]
+                                          s <- IntSet.toList ss ]
 
 type EquivalenceClass = IntSet
 
@@ -129,7 +131,7 @@
        where acc (State as _) = not (List.null as)
 
     nonaccepting_states :: EquivalenceClass
-    nonaccepting_states = IS.fromList (Map.keys nonaccepting)
+    nonaccepting_states = IntSet.fromList (Map.keys nonaccepting)
 
     -- group the accepting states into equivalence classes
     accept_map :: Map [Accept a] [Int]
@@ -139,11 +141,11 @@
              (Map.toList accepting)
 
     accept_groups :: [EquivalenceClass]
-    accept_groups = map IS.fromList (Map.elems accept_map)
+    accept_groups = map IntSet.fromList (Map.elems accept_map)
 
     init_r, init_q :: [EquivalenceClass]
     init_r  -- Issue #71: each EquivalenceClass needs to be a non-empty set
-      | IS.null nonaccepting_states = []
+      | IntSet.null nonaccepting_states = []
       | otherwise                   = [nonaccepting_states]
     init_q = accept_groups
 
@@ -155,10 +157,10 @@
     -- since a transition function might not be an injective.
     -- This is a cache of the information needed to compute xs below
     bigmap :: IntMap (IntMap EquivalenceClass)
-    bigmap = IM.fromListWith (IM.unionWith IS.union)
-                [ (i, IM.singleton to (IS.singleton from))
+    bigmap = IntMap.fromListWith (IntMap.unionWith IntSet.union)
+                [ (i, IntMap.singleton to (IntSet.singleton from))
                 | (from, state) <- Map.toList statemap,
-                  (i,to) <- IM.toList (state_out state) ]
+                  (i,to) <- IntMap.toList (state_out state) ]
 
     -- The outer loop: recurse on each set in R and Q
     go :: [EquivalenceClass] -> [EquivalenceClass] -> [EquivalenceClass]
@@ -168,18 +170,18 @@
         preimage :: IntMap EquivalenceClass -- inversed transition function
                  -> EquivalenceClass        -- subset of codomain of original transition function
                  -> EquivalenceClass        -- preimage of given subset
-#if MIN_VERSION_containers(0, 6, 0)
-        preimage invMap a = IS.unions (IM.restrictKeys invMap a)
+#if MIN_VERSION_containers(0,6,0)
+        preimage invMap = IntSet.unions . IntMap.restrictKeys invMap
 #else
-        preimage invMap a = IS.unions [IM.findWithDefault IS.empty s invMap | s <- IS.toList a]
+        preimage invMap = IntSet.unions . mapMaybe (`IntMap.lookup` invMap) . IntSet.toList
 #endif
 
         xs :: [EquivalenceClass]
         xs =
           [ x
-          | invMap <- IM.elems bigmap
+          | invMap <- IntMap.elems bigmap
           , let x = preimage invMap a
-          , not (IS.null x)
+          , not (IntSet.null x)
           ]
 
         refineWith
@@ -187,12 +189,12 @@
           -> EquivalenceClass -- input equivalence class
           -> Maybe (EquivalenceClass, EquivalenceClass) -- refined equivalence class
         refineWith x y =
-          if IS.null y1 || IS.null y2
+          if IntSet.null y1 || IntSet.null y2
             then Nothing
             else Just (y1, y2)
           where
-            y1 = IS.intersection y x
-            y2 = IS.difference   y x
+            y1 = IntSet.intersection y x
+            y2 = IntSet.difference   y x
 
         go0 (r,q) x = go1 r [] []
           where
@@ -201,11 +203,16 @@
             go1 (y:r) r' q' = case refineWith x y of
               Nothing                       -> go1 r (y:r') q'
               Just (y1, y2)
-                | IS.size y1 <= IS.size y2  -> go1 r (y2:r') (y1:q')
-                | otherwise                 -> go1 r (y1:r') (y2:q')
+                | IntSet.size y1 <= IntSet.size y2 -> go1 r (y2:r') (y1:q')
+                | otherwise                        -> go1 r (y1:r') (y2:q')
 
             -- iterates over Q
             go2 []    q' = q'
             go2 (y:q) q' = case refineWith x y of
               Nothing       -> go2 q (y:q')
               Just (y1, y2) -> go2 q (y1:y2:q')
+
+-- To pacify GHC 9.8's warning about 'head'
+headWithDefault :: a -> [a] -> a
+headWithDefault a []    = a
+headWithDefault _ (a:_) = a
diff --git a/src/DFS.hs b/src/DFS.hs
--- a/src/DFS.hs
+++ b/src/DFS.hs
@@ -23,10 +23,9 @@
 
 module DFS where
 
-import Set ( Set )
-import qualified Set hiding ( Set )
-
 import Data.Array ( (!), accumArray, listArray )
+import Data.Set   ( Set )
+import qualified Data.Set as Set
 
 -- The result of a depth-first search of a graph is a list of trees,
 -- `GForest'.  `post_order' provides a post-order traversal of a forest.
diff --git a/src/Data/Ranged/RangedSet.hs b/src/Data/Ranged/RangedSet.hs
--- a/src/Data/Ranged/RangedSet.hs
+++ b/src/Data/Ranged/RangedSet.hs
@@ -1,3 +1,5 @@
+{-# LANGUAGE CPP #-}
+
 module Data.Ranged.RangedSet (
    -- ** Ranged Set Type
    RSet,
@@ -27,13 +29,11 @@
 
 import Data.Ranged.Boundaries
 import Data.Ranged.Ranges
-#if MIN_VERSION_base(4,9,0) && !MIN_VERSION_base(4,11,0)
+#if !MIN_VERSION_base(4,11,0)
 import Data.Semigroup
-#elif !MIN_VERSION_base(4,9,0)
-import Data.Monoid
 #endif
 
-import Data.List hiding (and, null)
+import qualified Data.List as List
 
 infixl 7 -/\-
 infixl 6 -\/-, -!-
@@ -44,35 +44,25 @@
 newtype DiscreteOrdered v => RSet v = RSet {rSetRanges :: [Range v]}
    deriving (Eq, Show, Ord)
 
-#if MIN_VERSION_base(4,9,0)
 instance DiscreteOrdered a => Semigroup (RSet a) where
     (<>) = rSetUnion
-#endif
 
 instance DiscreteOrdered a => Monoid (RSet a) where
-#if MIN_VERSION_base(4,9,0)
+    mempty  = rSetEmpty
     mappend = (<>)
-#else
-    mappend = rSetUnion
-#endif
-    mempty = rSetEmpty
 
 -- | Determine if the ranges in the list are both in order and non-overlapping.
 -- If so then they are suitable input for the unsafeRangedSet function.
 validRangeList :: DiscreteOrdered v => [Range v] -> Bool
-
-validRangeList [] = True
-validRangeList [Range lower upper] = lower <= upper
-validRangeList rs = and $ zipWith okAdjacent rs (tail rs)
-   where
-      okAdjacent (Range lower1 upper1) (Range lower2 upper2) =
-         lower1 <= upper1 && upper1 <= lower2 && lower2 <= upper2
+validRangeList rs = and $
+  all (\ (Range lower upper) -> lower <= upper) rs :
+  zipWith (\ (Range _ upper1) (Range lower2 _) -> upper1 <= lower2) rs (drop 1 rs)
 
 
 -- | Rearrange and merge the ranges in the list so that they are in order and
 -- non-overlapping.
 normaliseRangeList :: DiscreteOrdered v => [Range v] -> [Range v]
-normaliseRangeList = normalise . sort . filter (not . rangeIsEmpty)
+normaliseRangeList = normalise . List.sort . filter (not . rangeIsEmpty)
 
 
 -- Private routine: normalise a range list that is known to be already sorted.
diff --git a/src/Info.hs b/src/Info.hs
--- a/src/Info.hs
+++ b/src/Info.hs
@@ -11,7 +11,7 @@
 module Info (infoDFA) where
 
 import AbsSyn
-import qualified Map
+import qualified Data.Map as Map
 import qualified Data.IntMap as IntMap
 import Util
 
diff --git a/src/Main.hs b/src/Main.hs
--- a/src/Main.hs
+++ b/src/Main.hs
@@ -1,4 +1,5 @@
-{-# LANGUAGE CPP #-}
+{-# LANGUAGE NondecreasingIndentation #-}
+
 -- -----------------------------------------------------------------------------
 --
 -- Main.hs, part of Alex
@@ -15,56 +16,39 @@
 import DFAMin
 import NFA
 import Info
-import Map ( Map )
-import qualified Map hiding ( Map )
 import Output
-import ParseMonad ( runP, Warning(..) )
+import ParseMonad            ( runP, Warning(..) )
 import Parser
 import Scan
-import Util ( hline )
-import Paths_alex ( version, getDataDir )
+import Util                  ( hline )
+import Paths_alex            ( version, getDataDir )
 
-#if __GLASGOW_HASKELL__ < 610
-import Control.Exception as Exception ( block, unblock, catch, throw )
-#endif
-#if __GLASGOW_HASKELL__ >= 610
-import Control.Exception ( bracketOnError )
-#endif
-import Control.Monad ( when, liftM )
-import Data.Char ( chr )
-import Data.List ( isSuffixOf, nub )
-import Data.Version ( showVersion )
+import Control.Exception     ( bracketOnError )
+import Control.Monad         ( when, liftM )
+import Data.Char             ( chr )
+import Data.List             ( isSuffixOf, nub )
+import Data.Map              ( Map )
+import Data.Version          ( showVersion )
 import System.Console.GetOpt ( getOpt, usageInfo, ArgOrder(..), OptDescr(..), ArgDescr(..) )
-import System.Directory ( removeFile )
-import System.Environment ( getProgName, getArgs )
-import System.Exit ( ExitCode(..), exitWith )
-import System.IO ( stderr, Handle, IOMode(..), openFile, hClose, hPutStr, hPutStrLn )
-#if __GLASGOW_HASKELL__ >= 612
-import System.IO ( hGetContents, hSetEncoding, utf8 )
-#endif
+import System.Directory      ( removeFile )
+import System.Environment    ( getProgName, getArgs )
+import System.Exit           ( ExitCode(..), exitWith )
+import System.IO             ( stderr, Handle, IOMode(..), openFile, hClose, hPutStr, hPutStrLn
+                             , hGetContents, hSetEncoding, utf8 )
+import qualified Data.Map    as Map
 
 -- We need to force every file we open to be read in
 -- as UTF8
 alexReadFile :: FilePath -> IO String
-#if __GLASGOW_HASKELL__ >= 612
-alexReadFile file = do
-  h <- alexOpenFile file ReadMode
-  hGetContents h
-#else
-alexReadFile = readFile
-#endif
+alexReadFile file = hGetContents =<< alexOpenFile file ReadMode
 
 -- We need to force every file we write to be written
 -- to as UTF8
 alexOpenFile :: FilePath -> IOMode -> IO Handle
-#if __GLASGOW_HASKELL__ >= 612
 alexOpenFile file mode = do
   h <- openFile file mode
   hSetEncoding h utf8
   return h
-#else
-alexOpenFile = openFile
-#endif
 
 -- `main' decodes the command line arguments and calls `alex'.
 
@@ -521,19 +505,3 @@
 
 dieAlex :: String -> IO a
 dieAlex s = getProgramName >>= \prog -> die (prog ++ ": " ++ s)
-
-#if __GLASGOW_HASKELL__ < 610
-bracketOnError
-        :: IO a         -- ^ computation to run first (\"acquire resource\")
-        -> (a -> IO b)  -- ^ computation to run last (\"release resource\")
-        -> (a -> IO c)  -- ^ computation to run in-between
-        -> IO c         -- returns the value from the in-between computation
-bracketOnError before after thing =
-  block (do
-    a <- before
-    r <- Exception.catch
-           (unblock (thing a))
-           (\e -> do { after a; throw e })
-    return r
- )
-#endif
diff --git a/src/Map.hs b/src/Map.hs
deleted file mode 100644
--- a/src/Map.hs
+++ /dev/null
@@ -1,68 +0,0 @@
-{-# LANGUAGE CPP #-}
-module Map (
-   Map,
-   member, lookup, findWithDefault,
-   empty,
-   insert, insertWith,
-   delete,
-   union, unionWith, unions,
-   mapWithKey,
-   elems,
-   fromList, fromListWith,
-   toAscList
-) where
-
-#if __GLASGOW_HASKELL__ >= 603
-import Data.Map
-import Prelude ()
-#else
-import Data.FiniteMap
-import Prelude hiding ( lookup )
-
-type Map k a = FiniteMap k a
-
-member :: Ord k => k -> Map k a -> Bool
-member = elemFM
-
-lookup :: Ord k => k -> Map k a -> Maybe a
-lookup = flip lookupFM
-
-findWithDefault :: Ord k => a -> k -> Map k a -> a
-findWithDefault a k m = lookupWithDefaultFM m a k
-
-empty :: Map k a
-empty = emptyFM
-
-insert :: Ord k => k -> a -> Map k a -> Map k a
-insert k a m = addToFM m k a
-
-insertWith :: Ord k => (a -> a -> a) -> k -> a -> Map k a -> Map k a
-insertWith c k a m = addToFM_C c m k a
-
-delete :: Ord k => k -> Map k a -> Map k a
-delete = flip delFromFM
-
-union :: Ord k => Map k a -> Map k a -> Map k a
-union = flip plusFM
-
-unionWith :: Ord k => (a -> a -> a) -> Map k a -> Map k a -> Map k a
-unionWith c l r = plusFM_C c r l
-
-unions :: Ord k => [Map k a] -> Map k a
-unions = foldl (flip plusFM) emptyFM
-
-mapWithKey :: (k -> a -> b) -> Map k a -> Map k b
-mapWithKey = mapFM
-
-elems :: Map k a -> [a]
-elems = eltsFM
-
-fromList :: Ord k => [(k,a)] -> Map k a
-fromList = listToFM
-
-fromListWith :: Ord k => (a -> a -> a) -> [(k,a)] -> Map k a
-fromListWith c = addListToFM_C (flip c) emptyFM
-
-toAscList :: Map k a -> [(k,a)]
-toAscList = fmToList
-#endif
diff --git a/src/NFA.hs b/src/NFA.hs
--- a/src/NFA.hs
+++ b/src/NFA.hs
@@ -16,18 +16,17 @@
 
 module NFA where
 
+import Control.Monad ( forM_, zipWithM, zipWithM_, when, liftM, ap )
+import Data.Array    ( Array, (!), array, listArray, assocs, bounds )
+import Data.Map      ( Map )
+import qualified Data.Map as Map
+import qualified Data.List.NonEmpty as List1
+
 import AbsSyn
 import CharSet
-import DFS ( t_close, out )
-import Map ( Map )
-import qualified Map hiding ( Map )
+import DFS  ( t_close, out )
 import Util ( str, space )
 
-#if __GLASGOW_HASKELL__ < 710
-import Control.Applicative ( Applicative(..) )
-#endif
-import Control.Monad ( forM_, zipWithM, zipWithM_, when, liftM, ap )
-import Data.Array ( Array, (!), array, listArray, assocs, bounds )
 
 -- Each state of a nondeterministic automaton contains a list of `Accept'
 -- values, a list of epsilon transitions (an epsilon transition represents a
@@ -132,7 +131,7 @@
   s <- newState
   rexp2nfa b s re1
   rexp2nfa s e re2
-rexp2nfa b e (re1 :| re2) = do
+rexp2nfa b e (re1 :|| re2) = do
   rexp2nfa b e re1
   rexp2nfa b e re2
 rexp2nfa b e (Star re) = do
@@ -226,10 +225,8 @@
 charEdge from charset to = do
   -- trace ("charEdge: " ++ (show $ charset) ++ " => " ++ show (byteRanges charset)) $
   e <- getEncoding
-  forM_ (byteRanges e charset) $ \(xs,ys) -> do
-    bytesEdge from xs ys to
-
-
+  forM_ (byteRanges e charset) $ \ (xs, ys) -> do
+    bytesEdge from (List1.toList xs) (List1.toList ys) to
 
 byteEdge :: SNum -> ByteSet -> SNum -> NFAM ()
 byteEdge from charset to = N $ \s n _ -> (s, addEdge n, ())
@@ -260,7 +257,6 @@
            Map.insert state (NSt [new_acc] [] []) n
        Just (NSt acc eps trans) ->
            Map.insert state (NSt (new_acc:acc) eps trans) n
-
 
 rctxt_accept :: Accept Code
 rctxt_accept = Acc 0 Nothing Nothing NoRightContext
diff --git a/src/Output.hs b/src/Output.hs
--- a/src/Output.hs
+++ b/src/Output.hs
@@ -13,7 +13,7 @@
 import AbsSyn
 import CharSet
 import Util
-import qualified Map
+import qualified Data.Map as Map
 import qualified Data.IntMap as IntMap
 
 import Control.Monad.ST ( ST, runST )
@@ -24,7 +24,8 @@
 import Data.Maybe (isJust)
 import Data.Bits
 import Data.Char ( ord, chr )
-import Data.List ( maximumBy, sortBy, groupBy, mapAccumR )
+import Data.List ( maximumBy, sortBy, mapAccumR )
+import qualified Data.List.NonEmpty as List1
 
 -- -----------------------------------------------------------------------------
 -- Printing the output
@@ -399,10 +400,10 @@
         best_default :: [(Int,SNum)] -> SNum
         best_default prod_list
            | null sorted = -1
-           | otherwise   = snd (head (maximumBy lengths eq))
+           | otherwise   = snd (List1.head (maximumBy lengths eq))
            where sorted  = sortBy compareSnds prod_list
                  compareSnds (_,a) (_,b) = compare a b
-                 eq = groupBy (\(_,a) (_,b) -> a == b) sorted
+                 eq = List1.groupBy (\(_,a) (_,b) -> a == b) sorted
                  lengths  a b = length a `compare` length b
 
         -- remove all the default productions from the DFA
@@ -493,18 +494,19 @@
                -> [(Int, Int)]
                -> ST s Int
 findFreeOffset off check off_arr state = do
-    -- offset 0 isn't allowed
+
+  -- offset 0 isn't allowed
   if off == 0 then try_next else do
 
     -- don't use an offset we've used before
-  b <- readArray off_arr off
-  if b /= 0 then try_next else do
+    b <- readArray off_arr off
+    if b /= 0 then try_next else do
 
-    -- check whether the actions for this state fit in the table
-  ok <- fits off state check
-  if ok then return off else try_next
- where
-        try_next = findFreeOffset (off+1) check off_arr state
+      -- check whether the actions for this state fit in the table
+      ok <- fits off state check
+      if ok then return off else try_next
+  where
+    try_next = findFreeOffset (off+1) check off_arr state
 
 -- This is an inner loop, so we use some strictness hacks, and avoid
 -- array bounds checks (unsafeRead instead of readArray) to speed
diff --git a/src/ParseMonad.hs b/src/ParseMonad.hs
--- a/src/ParseMonad.hs
+++ b/src/ParseMonad.hs
@@ -14,17 +14,16 @@
         setStartCode, getStartCode, getInput, setInput,
  ) where
 
-import AbsSyn hiding ( StartCode )
-import CharSet ( CharSet )
-import Map ( Map )
-import qualified Map hiding ( Map )
+import Control.Monad       ( liftM, ap, when )
+import Data.Map            ( Map )
+import Data.List.NonEmpty  ( pattern (:|) )
+import Data.Word           ( Word8 )
+import qualified Data.Map as Map
+
+import AbsSyn   hiding ( StartCode )
+import CharSet  ( CharSet )
 import UTF8
 
-#if __GLASGOW_HASKELL__ < 710
-import Control.Applicative ( Applicative(..) )
-#endif
-import Control.Monad ( liftM, ap, when )
-import Data.Word (Word8)
 -- -----------------------------------------------------------------------------
 -- The input type
 --import Codec.Binary.UTF8.Light as UTF8
@@ -40,18 +39,20 @@
 alexInputPrevChar (_,c,_,_) = c
 
 
-alexGetChar :: AlexInput -> Maybe (Char,AlexInput)
-alexGetChar (_,_,[],[]) = Nothing
-alexGetChar (p,_,[],(c:s))  = let p' = alexMove p c in p' `seq`
-                                Just (c, (p', c, [], s))
-alexGetChar (_, _ ,_ : _, _) = undefined -- hide compiler warning
+alexGetChar :: AlexInput -> Maybe (Char, AlexInput)
+alexGetChar (_, _, [], [])  = Nothing
+alexGetChar (p, _, [], c:s) = p' `seq` Just (c, (p', c, [], s))
+  where
+    p' = alexMove p c
+alexGetChar (_, _ , _:_, _) = undefined -- hide compiler warning
 
-alexGetByte :: AlexInput -> Maybe (Byte,AlexInput)
-alexGetByte (p,c,(b:bs),s) = Just (b,(p,c,bs,s))
-alexGetByte (_,_,[],[]) = Nothing
-alexGetByte (p,_,[],(c:s))  = let p' = alexMove p c
-                                  (b:bs) = UTF8.encode c
-                              in p' `seq`  Just (b, (p', c, bs, s))
+alexGetByte :: AlexInput -> Maybe (Byte, AlexInput)
+alexGetByte (p, c, b:bs, s)   = Just (b, (p, c, bs, s))
+alexGetByte (_, _, [],   [])  = Nothing
+alexGetByte (p, _, [],   c:s) = p' `seq`  Just (b, (p', c, bs, s))
+  where
+    p' = alexMove p c
+    b :| bs = UTF8.encode c
 
 -- -----------------------------------------------------------------------------
 -- Token positions
diff --git a/src/Parser.hs b/src/Parser.hs
--- a/src/Parser.hs
+++ b/src/Parser.hs
@@ -724,7 +724,7 @@
 	 =  case happyOut24 happy_x_1 of { (HappyWrap24 happy_var_1) -> 
 	case happyOut23 happy_x_3 of { (HappyWrap23 happy_var_3) -> 
 	happyIn23
-		 (happy_var_1 :| happy_var_3
+		 (happy_var_1 :|| happy_var_3
 	)}}
 
 happyReduce_41 :: () => Happy_GHC_Exts.Int# -> Token -> Happy_GHC_Exts.Int# -> Happy_IntList -> HappyStk (HappyAbsSyn ) -> P (HappyAbsSyn )
diff --git a/src/Parser.y.boot b/src/Parser.y.boot
--- a/src/Parser.y.boot
+++ b/src/Parser.y.boot
@@ -159,7 +159,7 @@
 	| {- empty -}	{ NoRightContext }
 
 rexp	:: { RExp }
-	: alt '|' rexp 			{ $1 :| $3 }
+	: alt '|' rexp 			{ $1 :|| $3 }
 	| alt		 		{ $1 }
 
 alt	:: { RExp }
diff --git a/src/Scan.hs b/src/Scan.hs
--- a/src/Scan.hs
+++ b/src/Scan.hs
@@ -2,11 +2,18 @@
 {-# LANGUAGE CPP #-}
 {-# LANGUAGE MagicHash #-}
 {-# LINE 13 "src/Scan.x" #-}
+{-# LANGUAGE CPP #-}
+
+-- Switch off partiality warning about 'head' and 'tail'
+#if __GLASGOW_HASKELL__ >= 908
+{-# OPTIONS_GHC -Wno-x-partial #-}
+#endif
+
 module Scan (lexer, AlexPosn(..), Token(..), Tkn(..), tokPosn, multiplicity) where
 
 import Data.Char
 import ParseMonad
---import Debug.Trace
+-- import Debug.Trace
 #if __GLASGOW_HASKELL__ >= 603
 #include "ghcconfig.h"
 #elif defined(__GLASGOW_HASKELL__)
@@ -546,7 +553,7 @@
         -- match when checking the right context, just
         -- the first match will do.
 #endif
-{-# LINE 84 "src/Scan.x" #-}
+{-# LINE 91 "src/Scan.x" #-}
 -- -----------------------------------------------------------------------------
 -- Token type
 
@@ -587,7 +594,7 @@
 string    (p,_,str) ln = return $ T p (StringT (extract ln str))
 bind      (p,_,str) _  = return $ T p (BindT (takeWhile isIdChar str))
 escape    (p,_,str) _  = return $ T p (CharT (esc str))
-decch     (p,_,str) ln = return $ T p (CharT (do_ech 10 ln (take (ln-1) (tail str))))
+decch     (p,_,str) ln = return $ T p (CharT (do_ech 10 ln (take (ln-1) (drop 1 str))))
 hexch     (p,_,str) ln = return $ T p (CharT (do_ech 16 ln (take (ln-2) (drop 2 str))))
 octch     (p,_,str) ln = return $ T p (CharT (do_ech 8  ln (take (ln-2) (drop 2 str))))
 char      (p,_,str) _  = return $ T p (CharT (head str))
@@ -607,7 +614,7 @@
 isIdChar c = isAlphaNum c || c `elem` "_'"
 
 extract :: Int -> String -> String
-extract ln str = take (ln-2) (tail str)
+extract ln str = take (ln-2) (drop 1 str)
 
 do_ech :: Int -> Int -> String -> Char
 do_ech radix _ln str = chr (parseInt radix str)
@@ -640,7 +647,7 @@
 -- implementing a large chunk of the Haskell lexical syntax).
 
 code :: Action
-code (p,_,_inp) _ = do
+code (p, _, _inp) _ = do
   currentInput <- getInput
   go currentInput 1 ""
   where
@@ -686,10 +693,10 @@
 
 lexError :: String -> P a
 lexError s = do
-  (_,_,_,input) <- getInput
-  failP (s ++ (if (not (null input))
-                  then " at " ++ show (head input)
-                  else " at end of file"))
+  (_, _, _, input) <- getInput
+  failP $ s ++ " at " ++ case input of
+    c:_ -> show c
+    []  -> "end of file"
 
 lexer :: (Token -> P a) -> P a
 lexer cont = lexToken >>= cont
diff --git a/src/Scan.x.boot b/src/Scan.x.boot
--- a/src/Scan.x.boot
+++ b/src/Scan.x.boot
@@ -11,11 +11,18 @@
 -------------------------------------------------------------------------------
 
 {
+{-# LANGUAGE CPP #-}
+
+-- Switch off partiality warning about 'head' and 'tail'
+#if __GLASGOW_HASKELL__ >= 908
+{-# OPTIONS_GHC -Wno-x-partial #-}
+#endif
+
 module Scan (lexer, AlexPosn(..), Token(..), Tkn(..), tokPosn, multiplicity) where
 
 import Data.Char
 import ParseMonad
---import Debug.Trace
+-- import Debug.Trace
 }
 
 $digit    = 0-9
@@ -123,7 +130,7 @@
 string    (p,_,str) ln = return $ T p (StringT (extract ln str))
 bind      (p,_,str) _  = return $ T p (BindT (takeWhile isIdChar str))
 escape    (p,_,str) _  = return $ T p (CharT (esc str))
-decch     (p,_,str) ln = return $ T p (CharT (do_ech 10 ln (take (ln-1) (tail str))))
+decch     (p,_,str) ln = return $ T p (CharT (do_ech 10 ln (take (ln-1) (drop 1 str))))
 hexch     (p,_,str) ln = return $ T p (CharT (do_ech 16 ln (take (ln-2) (drop 2 str))))
 octch     (p,_,str) ln = return $ T p (CharT (do_ech 8  ln (take (ln-2) (drop 2 str))))
 char      (p,_,str) _  = return $ T p (CharT (head str))
@@ -143,7 +150,7 @@
 isIdChar c = isAlphaNum c || c `elem` "_'"
 
 extract :: Int -> String -> String
-extract ln str = take (ln-2) (tail str)
+extract ln str = take (ln-2) (drop 1 str)
 
 do_ech :: Int -> Int -> String -> Char
 do_ech radix _ln str = chr (parseInt radix str)
@@ -176,7 +183,7 @@
 -- implementing a large chunk of the Haskell lexical syntax).
 
 code :: Action
-code (p,_,_inp) _ = do
+code (p, _, _inp) _ = do
   currentInput <- getInput
   go currentInput 1 ""
   where
@@ -222,10 +229,10 @@
 
 lexError :: String -> P a
 lexError s = do
-  (_,_,_,input) <- getInput
-  failP (s ++ (if (not (null input))
-                  then " at " ++ show (head input)
-                  else " at end of file"))
+  (_, _, _, input) <- getInput
+  failP $ s ++ " at " ++ case input of
+    c:_ -> show c
+    []  -> "end of file"
 
 lexer :: (Token -> P a) -> P a
 lexer cont = lexToken >>= cont
diff --git a/src/Set.hs b/src/Set.hs
deleted file mode 100644
--- a/src/Set.hs
+++ /dev/null
@@ -1,15 +0,0 @@
-{-# LANGUAGE CPP #-}
-module Set ( Set, member, empty, insert ) where
-
-import Data.Set
-
-#if defined(__GLASGOW_HASKELL__) && __GLASGOW_HASKELL__ < 603
-member :: Ord a => a -> Set a -> Bool
-member = elementOf
-
-empty  :: Set a
-empty = emptySet
-
-insert :: Ord a => a -> Set a -> Set a
-insert = flip addToSet
-#endif
diff --git a/src/Sort.hs b/src/Sort.hs
deleted file mode 100644
--- a/src/Sort.hs
+++ /dev/null
@@ -1,58 +0,0 @@
-{------------------------------------------------------------------------------
-                                 SORTING LISTS
-
-This module provides a properly parameterised merge sort function, complete
-with associated functions. It is based on a Bob Buckley's (Bob Buckley
-18-AUG-95) coding of Knuth's natural merge sort (see Vol. 2).  It seems to be
-fast in the average case; it makes use of natural runs in the data becomming
-linear on ordered data; and it completes in worst time O(n.log(n)).  It is
-divinely elegant.
-
-`nub'' is an n.log(n) version of `nub' and `group_sort' sorts a list into
-strictly ascending order, using a combining function in its arguments to
-amalgamate duplicates.
-
-Chris Dornan, 14-Aug-93, 17-Nov-94, 29-Dec-95
-------------------------------------------------------------------------------}
-
-module Sort where
-
--- Hide (<=) so that we don't get name shadowing warnings for it
-import Prelude hiding ((<=))
-
-msort :: (a->a->Bool) -> [a] -> [a]
-msort _    [] = []                    -- (foldb f []) is undefined
-msort (<=) xs = foldb (mrg (<=)) (runs (<=) xs)
-
-runs :: (a->a->Bool) -> [a] -> [[a]]
-runs (<=) xs0 = foldr op [] xs0
-      where
-        op z xss@(xs@(x:_):xss') | z<=x      = (z:xs):xss'
-                                 | otherwise = [z]:xss
-        op z xss                             = [z]:xss
-
-foldb :: (a->a->a) -> [a] -> a
-foldb _ [x] = x
-foldb f xs0 = foldb f (fold xs0)
-      where
-        fold (x1:x2:xs) = f x1 x2 : fold xs
-        fold xs         = xs
-
-mrg:: (a->a->Bool) -> [a] -> [a] -> [a]
-mrg _    [] l = l
-mrg _    l@(_:_) [] = l
-mrg (<=) l1@(h1:t1) l2@(h2:t2) =
-        if h1<=h2
-           then h1:mrg (<=) t1 l2
-           else h2:mrg (<=) l1 t2
-
-
-nub':: (a->a->Bool) -> [a] -> [a]
-nub' (<=) l = group_sort (<=) const l
-
-
-group_sort:: (a->a->Bool) -> (a->[a]->b) -> [a] -> [b]
-group_sort le cmb l = s_m (msort le l)
-        where
-        s_m [] = []
-        s_m (h:t) = cmb h (takeWhile (`le` h) t):s_m (dropWhile (`le` h) t)
diff --git a/src/UTF8.hs b/src/UTF8.hs
--- a/src/UTF8.hs
+++ b/src/UTF8.hs
@@ -1,9 +1,15 @@
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE OverloadedLists  #-}
+
 module UTF8 where
 
-import Data.Word
-import Data.Bits
-import Data.Char
+import Data.Word ( Word8 )
+import Data.Bits ( (.&.), shiftR )
+import Data.Char ( ord )
 
+import qualified Data.List.NonEmpty as List1
+type List1 = List1.NonEmpty
+
 {-
 -- Could also be imported:
 
@@ -15,8 +21,8 @@
 -}
 
 -- | Encode a Haskell String to a list of Word8 values, in UTF8 format.
-encode :: Char -> [Word8]
-encode = map fromIntegral . go . ord
+encode :: Char -> List1 Word8
+encode = fmap fromIntegral . go . ord
  where
   go oc
    | oc <= 0x7f       = [oc]
diff --git a/tests/Makefile b/tests/Makefile
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -32,14 +32,14 @@
 GHC_VERSION_WORDS=$(subst ., ,$(GHC_VERSION))
 GHC_MAJOR_VERSION=$(word 1,$(GHC_VERSION_WORDS))
 GHC_MINOR_VERSION=$(word 2,$(GHC_VERSION_WORDS))
+
 # Text dependency comes with GHC from 8.4 onwards
 GHC_SHIPS_WITH_TEXT:=$(shell if [ $(GHC_MAJOR_VERSION) -gt 8 -o $(GHC_MAJOR_VERSION) -ge 8 -a $(GHC_MINOR_VERSION) -ge 4 ]; then echo "yes"; else echo "no"; fi)
-# -fwarn-incomplete-uni-patterns only from 7.4
-WARNS_FOR_GHC_GTEQ_7_4=-fwarn-incomplete-uni-patterns
-WARNS_FOR_GHC_LT_7_4=-fno-warn-lazy-unlifted-bindings
-WARNS_DEP_GHC_GTEQ_7_4:=$(shell if [ $(GHC_MAJOR_VERSION) -gt 7 -o $(GHC_MAJOR_VERSION) -ge 7 -a $(GHC_MINOR_VERSION) -ge 4 ]; then echo "$(WARNS_FOR_GHC_GTEQ_7_4)"; else echo "$(WARNS_FOR_GHC_LT_7_4)"; fi)
 
-HC_OPTS=-Wall $(WARNS_DEP_GHC_GTEQ_7_4) -fno-warn-missing-signatures -fno-warn-unused-imports -fno-warn-tabs -Werror
+# Turn off x-partial warning (new in GHC 9.8)
+WARNS_DEP_GHC_GTEQ_9_8:=$(shell if [ $(GHC_MAJOR_VERSION) -gt 9 -o $(GHC_MAJOR_VERSION) -ge 9 -a $(GHC_MINOR_VERSION) -ge 8 ]; then echo "-Wno-x-partial"; fi)
+
+HC_OPTS=-Wall $(WARNS_DEP_GHC_GTEQ_9_8) -fwarn-incomplete-uni-patterns -fno-warn-missing-signatures -fno-warn-unused-imports -fno-warn-tabs -Werror
 
 .PRECIOUS: %.n.hs %.g.hs %.o %.exe %.bin
 
