hs-bindgen-runtime-1.0.0.0: src/HsBindgen/Runtime/Macro.hs
{-# LANGUAGE NoFieldSelectors #-}
{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE OverloadedStrings #-}
-- | Raw macros: C macros kept as their token spelling, untyped.
--
-- This module is intended to be imported qualified.
--
-- > import HsBindgen.Runtime.Macro qualified as Macro
module HsBindgen.Runtime.Macro (
-- * Type
Raw (..)
, Params (..)
, Variadic (..)
-- * Construction
, objectLike
, functionLike
, variadic
, variadicNamed
-- * Rendering
, render
) where
import Data.List qualified as List
{-------------------------------------------------------------------------------
Type
-------------------------------------------------------------------------------}
-- | A macro that was not typechecked; only its token spellings are known.
--
-- The name is part of the value so that 'render' can produce a definition
-- rather than just a body.
--
-- @a@ is the representation of a single token.
--
-- Generated code uses @'Raw' 'String'@.
data Raw a = Raw {
name :: a
, params :: Params a
, body :: [a]
}
deriving stock (Eq, Ord, Show, Functor, Foldable, Traversable)
-- | The parameter list of a macro.
data Params a =
-- | Object-like macro: no parameter list at all.
--
-- Note that this differs from @'Params' [] 'NotVariadic'@, the empty
-- parameter list of @#define NOW() 0@.
NoParams
-- | Function-like macro: the named parameters, and how the list ends.
| Params [a] (Variadic a)
deriving stock (Eq, Ord, Show, Functor, Foldable, Traversable)
-- | How the parameter list of a function-like macro ends.
--
-- Variadic macros are technically a C99 feature, but @libclang@ has backported
-- them to C89 as well. We follow @libclang@ behaviour here, and support
-- variadic macros regardless of the C standard that is configured (C89 is the
-- first standard).
--
-- <https://clang.llvm.org/docs/LanguageExtensions.html#language-extensions-back-ported-to-previous-standards>
data Variadic a =
-- | @#define F(x, y)@: the macro takes exactly its named parameters.
NotVariadic
-- | @#define F(x, ...)@: the trailing arguments are @__VA_ARGS__@.
| Ellipsis
-- | @#define F(x, args...)@: the trailing arguments are named.
--
-- This is the GNU named-variadic extension; the name replaces
-- @__VA_ARGS__@ in the body. It is a distinct constructor because it is
-- /not/ equivalent to 'Ellipsis' with the name as its last parameter:
-- @#define F(args...) g(args)@ passes every argument to @g@, whereas
-- @#define F(args, ...) g(args)@ passes only the first.
--
-- <https://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html>
| NamedEllipsis a
deriving stock (Eq, Ord, Show, Functor, Foldable, Traversable)
{-------------------------------------------------------------------------------
Construction
-------------------------------------------------------------------------------}
-- | Construct an object-like macro from its name and body token spellings.
objectLike :: String -> [String] -> Raw String
objectLike name body = Raw {
name = name
, params = NoParams
, body = body
}
-- | Construct a function-like macro from its name, parameter names, and body
-- token spellings.
functionLike :: String -> [String] -> [String] -> Raw String
functionLike name params body =
mkFunctionLike name params NotVariadic body
-- | Like 'functionLike', but for a macro whose parameter list ends in @...@.
variadic :: String -> [String] -> [String] -> Raw String
variadic name params body =
mkFunctionLike name params Ellipsis body
-- | Like 'variadic', but for the GNU named-variadic form: the third argument
-- is the name that stands for the trailing arguments.
--
-- @#define LOG(fmt, args...) printf(fmt, args)@ is
--
-- > variadicNamed "LOG" ["fmt"] "args"
-- > ["printf", "(", "fmt", ",", "args", ")"]
variadicNamed :: String -> [String] -> String -> [String] -> Raw String
variadicNamed name params ellipsisName body =
mkFunctionLike name params (NamedEllipsis ellipsisName) body
mkFunctionLike :: String -> [String] -> Variadic String -> [String] -> Raw String
mkFunctionLike name params variadicity body = Raw {
name = name
, params = Params params variadicity
, body = body
}
{-------------------------------------------------------------------------------
Rendering
-------------------------------------------------------------------------------}
-- | Render a macro as a @#define@ directive.
--
-- >>> render (functionLike "ADD" ["x", "y"] ["x", "+", "y"])
-- "#define ADD(x, y) x + y"
--
-- Whitespace is not stored, so the result is canonical: tokens are separated by
-- a single space, parameters by a comma and a space. @#define ADD(x,y) x+y@
-- renders as above.
render :: Raw String -> String
render raw =
"#define " <> raw.name <> renderParams raw.params <> renderBody raw.body
renderParams :: Params String -> String
renderParams NoParams = ""
renderParams (Params names variadicity) =
"(" <> List.intercalate ", " (names ++ renderVariadic variadicity) <> ")"
-- | Render the end of a parameter list, as the names that follow the named
-- parameters.
renderVariadic :: Variadic String -> [String]
renderVariadic = \case
NotVariadic -> []
Ellipsis -> ["..."]
NamedEllipsis nm -> [nm <> "..."]
-- | Render a body, including the space separating it from what precedes it.
--
-- An empty body renders as the empty text, so that @#define FOO@ does not gain
-- a trailing space.
renderBody :: [String] -> String
renderBody [] = ""
renderBody ts = " " <> unwords ts