{-# LANGUAGE TemplateHaskell #-}
module TreeSitter.Language where
import Data.Char
import Data.Function ((&))
import Data.Ix (Ix)
import Data.Traversable (for)
import Data.List.Split
import Data.Word
import Foreign.C.String
import Foreign.Ptr
import Language.Haskell.TH
import Language.Haskell.TH.Syntax
import System.Directory
import System.FilePath.Posix
newtype Language = Language ()
deriving (Show, Eq)
type TSSymbol = Word16
data SymbolType = Regular | Anonymous | Auxiliary
deriving (Enum, Eq, Ord, Show)
foreign import ccall unsafe "ts_language_symbol_count" ts_language_symbol_count :: Ptr Language -> Word32
foreign import ccall unsafe "ts_language_symbol_name" ts_language_symbol_name :: Ptr Language -> TSSymbol -> CString
foreign import ccall unsafe "ts_language_symbol_type" ts_language_symbol_type :: Ptr Language -> TSSymbol -> Int
class (Bounded s, Enum s, Ix s, Ord s, Show s) => Symbol s where
symbolType :: s -> SymbolType
mkSymbolDatatype :: Name -> Ptr Language -> Q [Dec]
mkSymbolDatatype name language = do
symbols <- (++ [(Regular, "ParseError")]) <$> runIO (languageSymbols language)
let namedSymbols = renameDups [] $ uncurry symbolToName <$> symbols
Module _ modName <- thisModule
pure
[ DataD [] name [] Nothing (flip NormalC [] . mkName . snd <$> namedSymbols) [ DerivClause Nothing [ ConT ''Show, ConT ''Enum, ConT ''Eq, ConT ''Ord, ConT ''Bounded, ConT ''Ix ] ]
, InstanceD Nothing [] (AppT (ConT ''Symbol) (ConT name)) [ FunD 'symbolType (uncurry (clause modName) <$> namedSymbols) ] ]
where clause modName symbolType str = Clause [ ConP (Name (OccName str) (NameQ modName)) [] ] (NormalB (ConE (promote symbolType))) []
promote Regular = 'Regular
promote Anonymous = 'Anonymous
promote Auxiliary = 'Auxiliary
renameDups done [] = reverse done
renameDups done ((ty, name):queue) = if elem name (snd <$> done)
then renameDups done ((ty, name ++ "'") : queue)
else renameDups ((ty, name) : done) queue
addDependentFileRelative :: FilePath -> Q [Dec]
addDependentFileRelative relativeFile = do
currentFilename <- loc_filename <$> location
pwd <- runIO getCurrentDirectory
let invocationRelativePath = takeDirectory (pwd </> currentFilename) </> relativeFile
addDependentFile invocationRelativePath
return []
languageSymbols :: Ptr Language -> IO [(SymbolType, String)]
languageSymbols language = for [0..fromIntegral (pred count)] $ \ symbol -> do
name <- peekCString (ts_language_symbol_name language symbol)
pure (toEnum (ts_language_symbol_type language symbol), name)
where count = ts_language_symbol_count language
symbolToName :: SymbolType -> String -> (SymbolType, String)
symbolToName ty name
= prefixHidden name
& toWords
& filter (not . all (== '_'))
& map (>>= toDescription)
& (>>= initUpper)
& (prefix ++)
& (,) ty
where toWords = split (condense (whenElt (not . isAlpha)))
prefixHidden s@('_':_) = "Hidden" ++ s
prefixHidden s = s
initUpper (c:cs) = toUpper c : cs
initUpper "" = ""
toDescription '{' = "LBrace"
toDescription '}' = "RBrace"
toDescription '(' = "LParen"
toDescription ')' = "RParen"
toDescription '.' = "Dot"
toDescription ':' = "Colon"
toDescription ',' = "Comma"
toDescription '|' = "Pipe"
toDescription ';' = "Semicolon"
toDescription '*' = "Star"
toDescription '&' = "Ampersand"
toDescription '=' = "Equal"
toDescription '<' = "LAngle"
toDescription '>' = "RAngle"
toDescription '[' = "LBracket"
toDescription ']' = "RBracket"
toDescription '+' = "Plus"
toDescription '-' = "Minus"
toDescription '/' = "Slash"
toDescription '\\' = "Backslash"
toDescription '^' = "Caret"
toDescription '!' = "Bang"
toDescription '%' = "Percent"
toDescription '@' = "At"
toDescription '~' = "Tilde"
toDescription '?' = "Question"
toDescription '`' = "Backtick"
toDescription '#' = "Hash"
toDescription '$' = "Dollar"
toDescription '"' = "DQuote"
toDescription '\'' = "SQuote"
toDescription '\t' = "Tab"
toDescription '\n' = "LF"
toDescription '\r' = "CR"
toDescription c = [c]
prefix = case ty of
Regular -> ""
Anonymous -> "Anon"
Auxiliary -> "Aux"