Skip to content

Commit

Permalink
Merge remote-tracking branch 'hercules/fix-error-message-lines'
Browse files Browse the repository at this point in the history
  • Loading branch information
junjihashimoto committed Sep 28, 2023
2 parents b3d044a + 305ea24 commit eb1573a
Show file tree
Hide file tree
Showing 3 changed files with 135 additions and 7 deletions.
5 changes: 4 additions & 1 deletion inline-c-cpp/src/Language/C/Inline/Cpp/Exception.hs
Original file line number Diff line number Diff line change
Expand Up @@ -210,9 +210,10 @@ exceptionalValue typeStr =

tryBlockQuoteExp :: QuasiQuoter -> String -> Q Exp
tryBlockQuoteExp block blockStr = do
let (ty, body) = C.splitTypedC blockStr
let (ty, body, bodyLineShift) = C.splitTypedC blockStr
_ <- C.include "HaskellException.hxx"
basePtrVarName <- newName "basePtr"
there <- location
let inlineCStr = unlines
[ ty ++ " {"
, " void** __inline_c_cpp_base_ptr__ = $(void** " ++ nameBase basePtrVarName ++ ");"
Expand All @@ -223,7 +224,9 @@ tryBlockQuoteExp block blockStr = do
, " HaskellException** __inline_c_cpp_haskellexception__ = (HaskellException**)(__inline_c_cpp_base_ptr__ + 4);"
, " *__inline_c_cpp_exception_type__ = 0;"
, " try {"
, C.lineDirective (C.shiftLines (bodyLineShift - 1) there)
, body
, C.lineDirective $(C.here)
, " } catch (const HaskellException &e) {"
, " *__inline_c_cpp_exception_type__ = " ++ show ExTypeHaskellException ++ ";"
, " *__inline_c_cpp_haskellexception__ = new HaskellException(e);"
Expand Down
51 changes: 51 additions & 0 deletions inline-c-cpp/test/tests.hs
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,57 @@ main = Hspec.hspec $ do

result `shouldBeRight` 0xDEADBEEF

Hspec.it "code can contain preprocessor directives" $ do
result <- try $ [C.throwBlock| int {
#ifndef THE_MACRO_THAT_HAS_NOT_BEEN_DEFINED
return 0xDEADBEEF;
#else
return 0xBEEFCAFE;
#endif
} |]

result `shouldBeRight` 0xDEADBEEF

{- Manual test cases for testing lineDirective and splitTypedC
Hspec.it "error reporting test case" $ do
result <- try $ [C.throwBlock| int { 0 = 0; }|]
result `shouldBeRight` 0xDEADBEEF
Hspec.it "error reporting test case" $ do
result <- try $ [C.throwBlock| int
{ 1 = 1; }
|]
result `shouldBeRight` 0xDEADBEEF
Hspec.it "error reporting test case" $ do
result <- try $ [C.throwBlock| int
{
2 = 2;
}
|]
result `shouldBeRight` 0xDEADBEEF
Hspec.it "error reporting test case" $ do
result <- try $ [C.throwBlock|
int
{
3 = 3;
}
|]
result `shouldBeRight` 0xDEADBEEF
Hspec.it "error reporting test case" $ do
result <- try $ [C.throwBlock|
int
{
4 = 4;
}
|]
result `shouldBeRight` 0xDEADBEEF
-- -}

Hspec.describe "Macros" $ do
Hspec.it "generated std::vector instances work correctly" $ do
intVec <- StdVector.new @C.CInt
Expand Down
86 changes: 80 additions & 6 deletions inline-c/src/Language/C/Inline/Internal.hs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ module Language.C.Inline.Internal
, runParserInQ
, splitTypedC

-- * Line directives
, lineDirective
, here
, shiftLines

-- * Utility functions for writing quasiquoters
, genericQuote
, funPtrQuote
Expand Down Expand Up @@ -295,7 +300,7 @@ inlineCode Code{..} = do
-- Write out definitions
ctx <- getContext
let out = fromMaybe id $ ctxOutput ctx
let directive = maybe "" (\l -> "#line " ++ show (fst $ TH.loc_start l) ++ " " ++ show (TH.loc_filename l ) ++ "\n") codeLoc
let directive = maybe "" lineDirective codeLoc
void $ emitVerbatim $ out $ directive ++ codeDefs
-- Create and add the FFI declaration.
ffiImportName <- uniqueFfiImportName
Expand Down Expand Up @@ -681,14 +686,37 @@ genericQuote purity build = quoteCode $ \rawStr -> do
go (paramType : params) = do
[t| $(return paramType) -> $(go params) |]

splitTypedC :: String -> (String, String)
-- ^ Returns the type and the body separately
splitTypedC s = (trim ty, case body of
[] -> []
r -> r)

-- NOTE: splitTypedC wouldn't be necessary if inline-c-cpp could reuse C.block
-- internals with a clean interface.
-- This would be a significant refactoring but presumably it would lead to an
-- api that could let users write their own quasiquoters a bit more conveniently.

-- | Returns the type and the body separately.
splitTypedC :: String -> (String, String, Int)
splitTypedC s = (trim ty, bodyIndent <> body, bodyLineShift)
where (ty, body) = span (/= '{') s
trim x = L.dropWhileEnd C.isSpace (dropWhile C.isSpace x)

-- We may need to correct the line number of the body
bodyLineShift = length (filter (== '\n') ty)

-- Indentation is relevant for error messages when the syntax is:
-- [C.foo| type
-- { foo(); }
-- |]
bodyIndent =
let precedingSpaceReversed =
takeWhile (\c -> C.isSpace c) $
reverse $
ty
(precedingSpacesTabsReversed, precedingLine) =
span (`notElem` ("\n\r" :: [Char])) precedingSpaceReversed
in case precedingLine of
('\n':_) -> reverse precedingSpacesTabsReversed
('\r':_) -> reverse precedingSpacesTabsReversed
_ -> "" -- it wasn't indentation after all; just spaces after the type

-- | Data to parse for the 'funPtr' quasi-quoter.
data FunPtrDecl = FunPtrDecl
{ funPtrReturnType :: C.Type C.CIdentifier
Expand Down Expand Up @@ -756,6 +784,52 @@ funPtrQuote callSafety = quoteCode $ \rawCode -> do
]
return (s ++ s')

------------------------------------------------------------------------
-- Line directives

-- | Tell the C compiler where the next line came from.
--
-- Example:
--
-- @@@
-- there <- location
-- f (unlines
-- [ lineDirective $(here)
-- , "generated_code_user_did_not_write()"
-- , lineDirective there
-- ] ++ userCode
-- ])
-- @@@
--
-- Use @lineDirective $(C.here)@ when generating code, so that any errors or
-- warnings report the location of the generating haskell module, rather than
-- tangentially related user code that doesn't contain the actual problem.
lineDirective :: TH.Loc -> String
lineDirective l = "#line " ++ show (fst $ TH.loc_start l) ++ " " ++ show (TH.loc_filename l ) ++ "\n"

-- | Get the location of the code you're looking at, for use with
-- 'lineDirective'; place before generated code that user did not write.
here :: TH.ExpQ
here = [| $(TH.location >>= \(TH.Loc a b c (d1, d2) (e1, e2)) ->
[|Loc
$(TH.lift a)
$(TH.lift b)
$(TH.lift c)
($(TH.lift d1), $(TH.lift d2))
($(TH.lift e1), $(TH.lift e2))
|])
|]

shiftLines :: Int -> TH.Loc -> TH.Loc
shiftLines n l = l
{ TH.loc_start =
let (startLn, startCol) = TH.loc_start l
in (startLn + n, startCol)
, TH.loc_end =
let (endLn, endCol) = TH.loc_end l
in (endLn + n, endCol)
}

------------------------------------------------------------------------
-- Utils

Expand Down

0 comments on commit eb1573a

Please sign in to comment.