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.

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