Abbie's Haskell compiler
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

135 lines
4.3 KiB

  1. {-# LANGUAGE ViewPatterns #-}
  2. module Frontend.Parser.Foreign where
  3. import Control.Monad
  4. import qualified Data.Text as T
  5. import Data.List
  6. import Frontend.Lexer.Wrapper
  7. import Frontend.Lexer.Tokens
  8. import Frontend.Syntax
  9. import Text.Show.Pretty
  10. import Frontend.Parser.Posn
  11. import Data.Char (isAlpha)
  12. parseForeignItem :: Token -> Alex FfiImpEnt
  13. parseForeignItem token@(Token (TokString impent) line col) = go 0 impent emptyItem
  14. where
  15. pn = Posn line (col + 1)
  16. go off xs item
  17. | T.pack " " `T.isPrefixOf` xs =
  18. let (run, rest) = T.span (== ' ') xs
  19. in go (off + T.length run) rest item
  20. go off (T.span (/= ' ') -> (x, xs)) item
  21. | x == xs, T.null xs = pure item
  22. | x == T.pack "static" =
  23. if fiStatic item
  24. then tooManyStatics pn off
  25. else go (off + T.length x) xs item{fiStatic = True}
  26. | T.pack ".h" `T.isSuffixOf` x =
  27. case fiHeader item of
  28. Nothing -> go (off + T.length x) xs item{fiHeader = Just x}
  29. Just _ -> tooManyHeaders pn off (T.length x)
  30. | T.singleton '&' `T.isPrefixOf` x, T.length x == 1 =
  31. if fiIsRef item
  32. then tooManyReferences pn off
  33. else go (off + 1) xs item{fiIsRef = True}
  34. | T.singleton '&' `T.isPrefixOf` x, T.length x >= 2 =
  35. if fiIsRef item
  36. then tooManyReferences pn off
  37. else go (off + 1) (T.tail x <> xs) item{fiIsRef = True}
  38. | otherwise =
  39. case fiItemName item of
  40. Nothing -> do
  41. id <- parseCid pn off x
  42. go (off + T.length x) xs item{fiItemName = id}
  43. _ -> tooManyItemNames pn off (T.length x)
  44. parseForeignItem _ = undefined
  45. tooManyHeaders :: Posn -> Int -> Int -> Alex a
  46. tooManyHeaders (Posn l c) off len =
  47. alexThrow $ \fname ->
  48. ParseError
  49. { parseErrorMessage = "this foreign entity has too many header names; only one is allowed."
  50. , parseErrorFilename = fname
  51. , parseErrorInlineDesc = Just "repeated header name"
  52. , parseErrorBegin = Posn l (c + off)
  53. , parseErrorEnd = Posn l (c + off + len)
  54. }
  55. tooManyItemNames :: Posn -> Int -> Int -> Alex a
  56. tooManyItemNames (Posn l c) off len =
  57. alexThrow $ \fname ->
  58. ParseError
  59. { parseErrorMessage = "this foreign entity has too many names; only one is allowed."
  60. , parseErrorFilename = fname
  61. , parseErrorInlineDesc = Just "repeated name"
  62. , parseErrorBegin = Posn l (c + off)
  63. , parseErrorEnd = Posn l (c + off + len)
  64. }
  65. tooManyReferences :: Posn -> Int -> Alex a
  66. tooManyReferences (Posn l c) off =
  67. alexThrow $ \fname ->
  68. ParseError
  69. { parseErrorMessage = "this foreign entity has too many '&'s; only one is allowed."
  70. , parseErrorFilename = fname
  71. , parseErrorInlineDesc = Just "repeated '&'"
  72. , parseErrorBegin = Posn l (c + off)
  73. , parseErrorEnd = Posn l (c + off + 1)
  74. }
  75. tooManyStatics :: Posn -> Int -> Alex a
  76. tooManyStatics (Posn l c) off =
  77. alexThrow $ \fname ->
  78. ParseError
  79. { parseErrorMessage = "this foreign entity has too many 'static's; only one is allowed."
  80. , parseErrorFilename = fname
  81. , parseErrorInlineDesc = Just "repeated 'static'"
  82. , parseErrorBegin = Posn l (c + off)
  83. , parseErrorEnd = Posn l (c + off + length "static")
  84. }
  85. parseCid :: Posn -> Int -> T.Text -> Alex (Maybe T.Text)
  86. parseCid (Posn l c) off id
  87. | T.null id = pure $ Nothing
  88. | isValidCIdent id = pure $ Just id
  89. | otherwise =
  90. alexThrow $ \fname ->
  91. ParseError
  92. { parseErrorMessage = "names for foreign entities must be valid C identifiers."
  93. , parseErrorFilename = fname
  94. , parseErrorInlineDesc = Just "this is not a C identifier"
  95. , parseErrorBegin = Posn l (c + off)
  96. , parseErrorEnd = Posn l (c + off + T.length id)
  97. }
  98. isValidCIdent :: T.Text -> Bool
  99. isValidCIdent = go . T.unpack where
  100. go :: String -> Bool
  101. go [] = False
  102. go (x:xs)
  103. | isAlpha x = go' xs
  104. | otherwise = False
  105. go' :: String -> Bool
  106. go' [] = True
  107. go' (x:xs)
  108. | 'a' <= x && x <= 'z' = go' xs
  109. | 'A' <= x && x <= 'Z' = go' xs
  110. | '0' <= x && x <= '9' = go' xs
  111. | x == '_' = go' xs
  112. | otherwise = False
  113. emptyItem :: FfiImpEnt
  114. emptyItem =
  115. ForeignItem { fiItemName = Nothing
  116. , fiHeader = Nothing
  117. , fiStatic = False
  118. , fiIsRef = False
  119. }