postprocess :: [Pos Token] -> Either ParseError [Pos Token]
postprocess input = go input
    where
    translateDots (s@(_,x):(pos,Dot):xs) | isSpacy x = s : (pos, InfixDot) : translateDots xs
        where isSpacy = (`elem` [Space, Newline, Indent, Dedent])
    translateDots (x:xs) = x : translateDots xs
    translateDots [] = []
    go [] = Right stripSpaces
    go (x:[]) = Right stripSpaces
    go ((_,Space):xs) = go xs
    go ((posX, x):rest@((posY, y):_)) = case (needsSpacey x, isSpacey y) of
        (Just True, True) -> go rest
        (Just True, False) -> if isException x y
                                then go rest
                                else Left $ addErrorMessage (Expect "whitespace") $ newErrorMessage (SysUnExpect $ show y) posY
        (Just False, True) -> if isException x y
                                then go rest
                                else Left $ addErrorMessage (UnExpect "whitespace") $ newErrorMessage (SysUnExpect $ show y) posY
        (Just False, False) -> go rest
        (Nothing, _) -> go rest
    isSpacey x = x `elem` [Space, Indent, Newline, Dedent, CloseParen, CloseBrack, CloseBrace, Comma, Ellipsis]
    needsSpacey (Name _) = Just True
    needsSpacey (Label _) = Just True
    needsSpacey (Lit _) = Just True
    needsSpacey At = Just False
    needsSpacey _ = Nothing
    isException (Name _) Dot = True
    isException _ _ = False
    stripSpaces = filter ((/= Space) . snd) (translateDots input)


------ Core Combinators ------
postprocess :: [Pos Token] -> Either ParseError [Pos Token]
postprocess input = go input
    where
    translateDots (s@(_,x):(pos,Dot):xs) | isSpacy x = s : (pos, InfixDot) : translateDots xs
        where isSpacy = (`elem` [Space, Newline, Indent, Dedent])
    translateDots (x:xs) = x : translateDots xs
    translateDots [] = []
    go [] = Right stripSpaces
    go (x:[]) = Right stripSpaces
    go ((_,Space):xs) = go xs
    go ((posX, x):rest@((posY, y):_)) = case (needsSpacey x, isSpacey y) of
        (Just True, True) -> go rest
        (Just True, False) -> if isException x y
                                then go rest
                                else Left $ addErrorMessage (Expect "whitespace") $ newErrorMessage (SysUnExpect $ show y) posY
        (Just False, True) -> if isException x y
                                then go rest
                                else Left $ addErrorMessage (UnExpect "whitespace") $ newErrorMessage (SysUnExpect $ show y) posY
        (Just False, False) -> go rest
        (Nothing, _) -> go rest
    isSpacey x = x `elem` [Space, Indent, Newline, Dedent, CloseParen, CloseBrack, CloseBrace, Comma, Ellipsis]
    needsSpacey (Name _) = Just True
    needsSpacey (Label _) = Just True
    needsSpacey (Lit _) = Just True
    needsSpacey At = Just False
    needsSpacey _ = Nothing
    isException (Name _) Dot = True
    isException _ _ = False
    stripSpaces = filter ((/= Space) . snd) (translateDots input)


------ Core Combinators ------
postprocess input = go input
    where
    translateDots (s@(_,x):(pos,Dot):xs) | isSpacy x = s : (pos, InfixDot) : translateDots xs
        where isSpacy = (`elem` [Space, Newline, Indent, Dedent])
    translateDots (x:xs) = x : translateDots xs
    translateDots [] = []
    go [] = Right stripSpaces
    go (x:[]) = Right stripSpaces
    go ((_,Space):xs) = go xs
    go ((posX, x):rest@((posY, y):_)) = case (needsSpacey x, isSpacey y) of
        (Just True, True) -> go rest
        (Just True, False) -> if isException x y
                                then go rest
                                else Left $ addErrorMessage (Expect "whitespace") $ newErrorMessage (SysUnExpect $ show y) posY
        (Just False, True) -> if isException x y
                                then go rest
                                else Left $ addErrorMessage (UnExpect "whitespace") $ newErrorMessage (SysUnExpect $ show y) posY
        (Just False, False) -> go rest
        (Nothing, _) -> go rest
    isSpacey x = x `elem` [Space, Indent, Newline, Dedent, CloseParen, CloseBrack, CloseBrace, Comma, Ellipsis]
    needsSpacey (Name _) = Just True
    needsSpacey (Label _) = Just True
    needsSpacey (Lit _) = Just True
    needsSpacey At = Just False
    needsSpacey _ = Nothing
    isException (Name _) Dot = True
    isException _ _ = False
    stripSpaces = filter ((/= Space) . snd) (translateDots input)


------ Core Combinators ------