-- | Takes a 'Scanner' with startCodes identified by 'String'
-- and recodes it to one with startCodes @[0 .. n - 1]@,
-- returning the new 'Scanner', the mapping, and @n@.
assignStartCodes :: Scanner String c -> (Scanner Int c, Map String Int, Int)
assignStartCodes scanner = (first startCode scanner, codeMap, numStartCodes)
  where
    startCode name = fromJust $ Map.lookup name codeMap
    next a _ = let a' = a + 1 in a' `seq` (a', a')
    (numStartCodes, codeMap) = 
        second (insert "0" 0) $ 
        Traversable.mapAccumL next 1 $ 
        Map.fromList 
            [ (name, ()) 
            | Rule { ruleStartCodes = names } <- scannerRules scanner
            , name <- names
            , name /= "0" 
            ]
assignStartCodes :: Scanner String c -> (Scanner Int c, Map String Int, Int)
assignStartCodes scanner = (first startCode scanner, codeMap, numStartCodes)
  where
    startCode name = fromJust $ Map.lookup name codeMap
    next a _ = let a' = a + 1 in a' `seq` (a', a')
    (numStartCodes, codeMap) = 
        second (insert "0" 0) $ 
        Traversable.mapAccumL next 1 $ 
        Map.fromList 
            [ (name, ()) 
            | Rule { ruleStartCodes = names } <- scannerRules scanner
            , name <- names
            , name /= "0" 
            ]
assignStartCodes scanner = (first startCode scanner, codeMap, numStartCodes)
  where
    startCode name = fromJust $ Map.lookup name codeMap
    next a _ = let a' = a + 1 in a' `seq` (a', a')
    (numStartCodes, codeMap) = 
        second (insert "0" 0) $ 
        Traversable.mapAccumL next 1 $ 
        Map.fromList 
            [ (name, ()) 
            | Rule { ruleStartCodes = names } <- scannerRules scanner
            , name <- names
            , name /= "0" 
            ]