|
|
@ -0,0 +1,135 @@ |
|
|
|
{-# LANGUAGE ViewPatterns #-} |
|
|
|
module Frontend.Parser.Foreign where |
|
|
|
|
|
|
|
import Control.Monad |
|
|
|
|
|
|
|
import qualified Data.Text as T |
|
|
|
import Data.List |
|
|
|
|
|
|
|
|
|
|
|
import Frontend.Lexer.Wrapper |
|
|
|
import Frontend.Lexer.Tokens |
|
|
|
import Frontend.Syntax |
|
|
|
|
|
|
|
import Text.Show.Pretty |
|
|
|
import Frontend.Parser.Posn |
|
|
|
import Data.Char (isAlpha) |
|
|
|
|
|
|
|
parseForeignItem :: Token -> Alex FfiImpEnt |
|
|
|
parseForeignItem token@(Token (TokString impent) line col) = go 0 impent emptyItem |
|
|
|
where |
|
|
|
pn = Posn line (col + 1) |
|
|
|
|
|
|
|
go off xs item |
|
|
|
| T.pack " " `T.isPrefixOf` xs = |
|
|
|
let (run, rest) = T.span (== ' ') xs |
|
|
|
in go (off + T.length run) rest item |
|
|
|
|
|
|
|
go off (T.span (/= ' ') -> (x, xs)) item |
|
|
|
| x == xs, T.null xs = pure item |
|
|
|
| x == T.pack "static" = |
|
|
|
if fiStatic item |
|
|
|
then tooManyStatics pn off |
|
|
|
else go (off + T.length x) xs item{fiStatic = True} |
|
|
|
| T.pack ".h" `T.isSuffixOf` x = |
|
|
|
case fiHeader item of |
|
|
|
Nothing -> go (off + T.length x) xs item{fiHeader = Just x} |
|
|
|
Just _ -> tooManyHeaders pn off (T.length x) |
|
|
|
| T.singleton '&' `T.isPrefixOf` x, T.length x == 1 = |
|
|
|
if fiIsRef item |
|
|
|
then tooManyReferences pn off |
|
|
|
else go (off + 1) xs item{fiIsRef = True} |
|
|
|
| T.singleton '&' `T.isPrefixOf` x, T.length x >= 2 = |
|
|
|
if fiIsRef item |
|
|
|
then tooManyReferences pn off |
|
|
|
else go (off + 1) (T.tail x <> xs) item{fiIsRef = True} |
|
|
|
| otherwise = |
|
|
|
case fiItemName item of |
|
|
|
Nothing -> do |
|
|
|
id <- parseCid pn off x |
|
|
|
go (off + T.length x) xs item{fiItemName = id} |
|
|
|
_ -> tooManyItemNames pn off (T.length x) |
|
|
|
parseForeignItem _ = undefined |
|
|
|
|
|
|
|
tooManyHeaders :: Posn -> Int -> Int -> Alex a |
|
|
|
tooManyHeaders (Posn l c) off len = |
|
|
|
alexThrow $ \fname -> |
|
|
|
ParseError |
|
|
|
{ parseErrorMessage = "this foreign entity has too many header names; only one is allowed." |
|
|
|
, parseErrorFilename = fname |
|
|
|
, parseErrorInlineDesc = Just "repeated header name" |
|
|
|
, parseErrorBegin = Posn l (c + off) |
|
|
|
, parseErrorEnd = Posn l (c + off + len) |
|
|
|
} |
|
|
|
|
|
|
|
tooManyItemNames :: Posn -> Int -> Int -> Alex a |
|
|
|
tooManyItemNames (Posn l c) off len = |
|
|
|
alexThrow $ \fname -> |
|
|
|
ParseError |
|
|
|
{ parseErrorMessage = "this foreign entity has too many names; only one is allowed." |
|
|
|
, parseErrorFilename = fname |
|
|
|
, parseErrorInlineDesc = Just "repeated name" |
|
|
|
, parseErrorBegin = Posn l (c + off) |
|
|
|
, parseErrorEnd = Posn l (c + off + len) |
|
|
|
} |
|
|
|
|
|
|
|
tooManyReferences :: Posn -> Int -> Alex a |
|
|
|
tooManyReferences (Posn l c) off = |
|
|
|
alexThrow $ \fname -> |
|
|
|
ParseError |
|
|
|
{ parseErrorMessage = "this foreign entity has too many '&'s; only one is allowed." |
|
|
|
, parseErrorFilename = fname |
|
|
|
, parseErrorInlineDesc = Just "repeated '&'" |
|
|
|
, parseErrorBegin = Posn l (c + off) |
|
|
|
, parseErrorEnd = Posn l (c + off + 1) |
|
|
|
} |
|
|
|
|
|
|
|
tooManyStatics :: Posn -> Int -> Alex a |
|
|
|
tooManyStatics (Posn l c) off = |
|
|
|
alexThrow $ \fname -> |
|
|
|
ParseError |
|
|
|
{ parseErrorMessage = "this foreign entity has too many 'static's; only one is allowed." |
|
|
|
, parseErrorFilename = fname |
|
|
|
, parseErrorInlineDesc = Just "repeated 'static'" |
|
|
|
, parseErrorBegin = Posn l (c + off) |
|
|
|
, parseErrorEnd = Posn l (c + off + length "static") |
|
|
|
} |
|
|
|
|
|
|
|
parseCid :: Posn -> Int -> T.Text -> Alex (Maybe T.Text) |
|
|
|
parseCid (Posn l c) off id |
|
|
|
| T.null id = pure $ Nothing |
|
|
|
| isValidCIdent id = pure $ Just id |
|
|
|
| otherwise = |
|
|
|
alexThrow $ \fname -> |
|
|
|
ParseError |
|
|
|
{ parseErrorMessage = "names for foreign entities must be valid C identifiers." |
|
|
|
, parseErrorFilename = fname |
|
|
|
, parseErrorInlineDesc = Just "this is not a C identifier" |
|
|
|
, parseErrorBegin = Posn l (c + off) |
|
|
|
, parseErrorEnd = Posn l (c + off + T.length id) |
|
|
|
} |
|
|
|
|
|
|
|
isValidCIdent :: T.Text -> Bool |
|
|
|
isValidCIdent = go . T.unpack where |
|
|
|
go :: String -> Bool |
|
|
|
go [] = False |
|
|
|
go (x:xs) |
|
|
|
| isAlpha x = go' xs |
|
|
|
| otherwise = False |
|
|
|
|
|
|
|
go' :: String -> Bool |
|
|
|
go' [] = True |
|
|
|
go' (x:xs) |
|
|
|
| 'a' <= x && x <= 'z' = go' xs |
|
|
|
| 'A' <= x && x <= 'Z' = go' xs |
|
|
|
| '0' <= x && x <= '9' = go' xs |
|
|
|
| x == '_' = go' xs |
|
|
|
| otherwise = False |
|
|
|
|
|
|
|
emptyItem :: FfiImpEnt |
|
|
|
emptyItem = |
|
|
|
ForeignItem { fiItemName = Nothing |
|
|
|
, fiHeader = Nothing |
|
|
|
, fiStatic = False |
|
|
|
, fiIsRef = False |
|
|
|
} |