{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE ScopedTypeVariables #-} module Jikka.Common.Parse.OffsideRule ( insertIndents, ) where import Jikka.Common.Error import Jikka.Common.Location splitToLines :: forall a. (a -> Bool) -> [WithLoc a] -> [[WithLoc a]] splitToLines isNewline = go [] where go :: [WithLoc a] -> [WithLoc a] -> [[WithLoc a]] go [] [] = [] go acc [] = [reverse acc] go acc (token : tokens) | isNewline (value token) = reverse (token : acc) : go [] tokens | otherwise = go (token : acc) tokens insertIndents' :: forall m a. (MonadError Error m, Show a) => a -> a -> [[WithLoc a]] -> m [WithLoc a] insertIndents' indent dedent = go [1] where go :: [Int] -> [[WithLoc a]] -> m [WithLoc a] go stk tokens = case (stk, tokens) of ([1], []) -> return [] (_ : stk, []) -> (WithLoc (Loc 0 1 0) dedent :) <$> go stk [] (_, [] : _) -> throwInternalError "a line must be non-empty" (_, (token : _) : _) | column (loc token) < 0 -> throwInternalError $ "column must be 1-based for insertIndents': " ++ show token ([], _) -> throwInternalError "too many dedents" (x : stk, line@(token : _) : tokens') -> case compare x (column (loc token)) of LT -> (withLoc (loc token) indent :) . (line ++) <$> go (column (loc token) : x : stk) tokens' EQ -> (line ++) <$> go (x : stk) tokens' GT -> case stk of [] -> throwInternalError "too many dedents" (x' : _) | x' < column (loc token) -> throwLexicalErrorAt (loc token) $ "unindent does not match any outer indentation level: " ++ show token | otherwise -> (withLoc (loc token) dedent :) <$> go stk (line : tokens') withLoc :: Loc -> a -> WithLoc a withLoc (Loc y x _) a = WithLoc (Loc y x 0) a -- | `insertIndents` inserts @INDENT@ and @DEDENT@ tokens with Python's way (). The `column` of `Loc` must be 1-based. This doen't use physical `line` of `Loc` because logical lines are used for indentation. insertIndents :: forall m a. (MonadError Error m, Show a) => a -> a -> (a -> Bool) -> [WithLoc a] -> m [WithLoc a] insertIndents indent dedent isNewline tokens = wrapError' "Jikka.Common.Parse.OffsideRule failed" $ do let lines = splitToLines isNewline tokens insertIndents' indent dedent lines