66module Nixfmt.Lexer (lexeme , pushTrivia , takeTrivia , whole ) where
77
88import Control.Monad.State.Strict (MonadState , evalStateT , get , modify , put )
9- import Data.Char (isSpace )
9+ import Data.Char (isAlphaNum , isSpace )
10+ import Data.Functor (($>) )
1011import Data.List (dropWhileEnd )
1112import Data.Maybe (fromMaybe )
1213import Data.Text as Text (
1314 Text ,
15+ all ,
1416 isPrefixOf ,
1517 length ,
1618 lines ,
@@ -43,6 +45,7 @@ import Text.Megaparsec (
4345 chunk ,
4446 getSourcePos ,
4547 hidden ,
48+ lookAhead ,
4649 many ,
4750 manyTill ,
4851 notFollowedBy ,
@@ -59,6 +62,8 @@ data ParseTrivium
5962 PTLineComment Text Pos
6063 | -- Track whether it is a doc comment
6164 PTBlockComment Bool [Text ]
65+ | -- | Language annotation like /* lua */ (single line, non-doc)
66+ PTLanguageAnnotation Text
6267 deriving (Show )
6368
6469preLexeme :: Parser a -> Parser a
@@ -127,6 +132,30 @@ blockComment = try $ preLexeme $ do
127132 commonIndentationLength :: Int -> [Text ] -> Int
128133 commonIndentationLength = foldr (min . Text. length . Text. takeWhile (== ' ' ))
129134
135+ languageAnnotation :: Parser ParseTrivium
136+ languageAnnotation = try $ do
137+ -- Parse a block comment and extract its content
138+ PTBlockComment False [content] <- blockComment
139+ isStringDelimiterNext <- lookAhead isNextStringDelimiter
140+
141+ if isStringDelimiterNext && isValidLanguageIdentifier content
142+ then return (PTLanguageAnnotation (strip content))
143+ else fail " Not a language annotation"
144+ where
145+ -- Check if a text is a valid language identifier for language annotations
146+ isValidLanguageIdentifier txt =
147+ let stripped = strip txt
148+ in not (Text. null stripped)
149+ && Text. length stripped <= 30 -- TODO: make configurable or remove limit
150+ && Text. all (\ c -> isAlphaNum c || c `elem` [' -' , ' +' , ' .' , ' _' , ' $' , ' {' , ' }' ]) stripped
151+
152+ -- Parser to peek at the next token to see if it's a string delimiter (" or '')
153+ isNextStringDelimiter = do
154+ _ <- manyP isSpace -- Skip any remaining whitespace
155+ (chunk " \" " $> True )
156+ <|> (chunk " ''" $> True )
157+ <|> pure False
158+
130159-- This should be called with zero or one elements, as per `span isTrailing`
131160convertTrailing :: [ParseTrivium ] -> Maybe TrailingComment
132161convertTrailing = toMaybe . join . map toText
@@ -148,6 +177,7 @@ convertLeading =
148177 PTBlockComment _ [] -> []
149178 PTBlockComment False [c] -> [LineComment $ " " <> strip c]
150179 PTBlockComment isDoc cs -> [BlockComment isDoc cs]
180+ PTLanguageAnnotation c -> [LanguageAnnotation c]
151181 )
152182
153183isTrailing :: ParseTrivium -> Bool
@@ -169,7 +199,7 @@ convertTrivia pts nextCol =
169199 _ -> (convertTrailing trailing, convertLeading leading)
170200
171201trivia :: Parser [ParseTrivium ]
172- trivia = many $ hidden $ lineComment <|> blockComment <|> newlines
202+ trivia = many $ hidden $ languageAnnotation <|> lineComment <|> blockComment <|> newlines
173203
174204-- The following primitives to interact with the state monad that stores trivia
175205-- are designed to prevent trivia from being dropped or duplicated by accident.
0 commit comments