-- C->Haskell Compiler: custom header generator -- -- Author : Manuel M T Chakravarty -- Created: 5 February 2003 -- -- Version $Revision: 1.1 $ -- -- Copyright (c) 2004 Manuel M T Chakravarty -- -- This file is free software; you can redistribute it and/or modify -- it under the terms of the GNU General Public License as published by -- the Free Software Foundation; either version 2 of the License, or -- (at your option) any later version. -- -- This file is distributed in the hope that it will be useful, -- but WITHOUT ANY WARRANTY; without even the implied warranty of -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -- GNU General Public License for more details. -- --- DESCRIPTION --------------------------------------------------------------- -- -- This module implements the generation of a custom header from a binding -- module. -- --- DOCU ---------------------------------------------------------------------- -- -- language: Haskell 98 -- -- Computing CPP Conditionals -- ~~~~~~~~~~~~~~~~~~~~~~~~~~ -- We obtain information about which branches of CPP conditions are taken -- during pre-processing of the custom header file by introducing new -- struct declarations. Specifically, after each #if[[n]def] or #elif, -- we place a declaration of the form -- -- struct C2HS_COND_SENTRY; -- -- We can, then, determine which branch of a conditional has been taken by -- checking whether the struct corresponding to that conditional has been -- declared. -- --- TODO ---------------------------------------------------------------------- -- -- * Ideally, `ghFrag[s]' should be tail recursive module GenHeader ( genHeader ) where -- standard libraries import Control.Monad (when) -- Compiler Toolkit import Position (Position, Pos(..), nopos) import DLists (DList, openDL, closeDL, zeroDL, unitDL, joinDL, snocDL) import Errors (interr) import Idents (onlyPosIdent) import UNames (NameSupply, Name, names) -- C->Haskell import C2HSState (CST, getNameSupply, runCST, transCST, raiseError, catchExc, throwExc, errorsPresent, showErrors, fatal) -- friends import CHS (CHSModule(..), CHSFrag(..)) -- The header generation monad -- type GH a = CST [Name] a -- |Generate a custom C header from a CHS binding module. -- -- * All CPP directives and inline-C fragments are moved into the custom header -- -- * The CPP and inline-C fragments are removed from the .chs tree and -- conditionals are replaced by structured conditionals -- genHeader :: CHSModule -> CST s ([String], CHSModule, String) genHeader mod = do supply <- getNameSupply (header, mod) <- runCST (ghModule mod) (names supply) `ifGHExc` return ([], CHSModule []) -- check for errors and finalise -- errs <- errorsPresent if errs then do errmsgs <- showErrors fatal ("Errors during generation of C header:\n\n" -- fatal error ++ errmsgs) else do warnmsgs <- showErrors return (header, mod, warnmsgs) -- Obtain a new base name that may be used, in C, to encode the result of a -- preprocessor conditionl. -- newName :: CST [Name] String newName = transCST $ \supply -> (tail supply, "C2HS_COND_SENTRY_" ++ show (head supply)) -- Various forms of processed fragments -- data FragElem = Frag CHSFrag | Elif String Position | Else Position | Endif Position | EOF instance Pos FragElem where posOf (Frag frag ) = posOf frag posOf (Elif _ pos) = pos posOf (Else pos) = pos posOf (Endif pos) = pos posOf EOF = nopos -- check for end of file -- isEOF :: FragElem -> Bool isEOF EOF = True isEOF _ = False -- Generate the C header for an entire .chs module. -- -- * This works more or less like a recursive decent parser for a statement -- sequence that may contain conditionals, where `ghFrag' implements most of -- the state transition system of the associated automaton -- ghModule :: CHSModule -> GH ([String], CHSModule) ghModule (CHSModule frags) = do (header, frags, last, rest) <- ghFrags frags when (not . isEOF $ last) $ notOpenCondErr (posOf last) return (closeDL header, CHSModule frags) -- Collect header and fragments up to eof or a CPP directive that is part of a -- conditional -- -- * We collect the header (ie, CPP directives and inline-C) using a -- difference list to avoid worst case O(n^2) complexity due to -- concatenation of lines that go into the header. -- ghFrags :: [CHSFrag] -> GH (DList String, [CHSFrag], FragElem, [CHSFrag]) ghFrags [] = return (zeroDL, [], EOF, []) ghFrags frags = do (header, frag, rest) <- ghFrag frags case frag of Frag aFrag -> do (header2, frags', frag', rest) <- ghFrags rest -- FIXME: Not tail rec return (header `joinDL` header2, aFrag:frags', frag', rest) _ -> return (header, [], frag, rest) -- Process a single fragment *structure*; i.e., if the first fragment -- introduces a conditional, process the whole conditional; otherwise, process -- the first fragment -- ghFrag :: [CHSFrag] -> GH (DList String, -- partial header file FragElem, -- processed fragment [CHSFrag]) -- not yet processed fragments ghFrag [] = return (zeroDL, EOF, []) ghFrag (frag@(CHSVerb _ _ ) : frags) = return (zeroDL, Frag frag, frags) ghFrag (frag@(CHSHook _ ) : frags) = return (zeroDL, Frag frag, frags) ghFrag (frag@(CHSLine _ ) : frags) = return (zeroDL, Frag frag, frags) ghFrag (frag@(CHSLang _ _ ) : frags) = return (zeroDL, Frag frag, frags) ghFrag ( (CHSC s _ ) : frags) = do (header, frag, frags' ) <- ghFrag frags -- scan for next CHS fragment return (unitDL s `joinDL` header, frag, frags') -- FIXME: this is not tail recursive... ghFrag ( (CHSCond _ _ ) : frags) = interr "GenHeader.ghFrags: There can't be a structured conditional yet!" ghFrag (frag@(CHSCPP s pos) : frags) = let (directive, _) = break (`elem` " \t") . dropWhile (`elem` " \t") $ s in case directive of "if" -> openIf s pos frags "ifdef" -> openIf s pos frags "ifndef" -> openIf s pos frags "else" -> return (zeroDL , Else pos , frags) "elif" -> return (zeroDL , Elif s pos , frags) "endif" -> return (zeroDL , Endif pos , frags) _ -> return (openDL ['#':s, "\n"], Frag (CHSVerb "" nopos), frags) where -- enter a new conditional (may be an #if[[n]def] or #elif) -- -- * Arguments are the lexeme of the directive `s', the position of that -- directive `pos', and the fragments following the directive `frags' -- openIf s pos frags = do (headerTh, fragsTh, last, rest) <- ghFrags frags case last of Else pos -> do (headerEl, fragsEl, last, rest) <- ghFrags rest case last of Else pos -> notOpenCondErr pos Elif _ pos -> notOpenCondErr pos Endif pos -> closeIf ((headerTh `snocDL` "#else\n") `joinDL` (headerEl `snocDL` "#endif\n")) (s, fragsTh) [] (Just fragsEl) rest EOF -> notClosedCondErr pos Elif s' pos -> do (headerEl, condFrag, rest) <- openIf s' pos rest case condFrag of Frag (CHSCond alts dft) -> closeIf (headerTh `joinDL` headerEl) (s, fragsTh) alts dft rest _ -> interr "GenHeader.ghFrag: Expected CHSCond!" Endif pos -> closeIf (headerTh `snocDL` "#endif\n") (s, fragsTh) [] (Just []) rest EOF -> notClosedCondErr pos -- -- turn a completed conditional into a `CHSCond' fragment -- -- * `(s, fragsTh)' is the CPP directive `s' containing the condition under -- which `fragTh' should be executed; `alts' are alternative branches -- (with conditions); and `oelse' is an optional else-branch -- closeIf headerTail (s, fragsTh) alts oelse rest = do sentryName <- newName let sentry = onlyPosIdent nopos sentryName -- don't use an internal ident, as we need to test for -- equality with identifiers read from the .i file -- during binding hook expansion header = openDL ['#':s, "\n", "struct ", sentryName, ";\n"] `joinDL` headerTail return (header, Frag (CHSCond ((sentry, fragsTh):alts) oelse), rest) -- exception handling -- ------------------ -- exception identifier -- ghExc :: String ghExc = "ghExc" -- throw an exception -- throwGHExc :: GH a throwGHExc = throwExc ghExc "Error during C header generation" -- catch a `ghExc' -- ifGHExc :: CST s a -> CST s a -> CST s a ifGHExc m handler = m `catchExc` (ghExc, const handler) -- raise an error followed by throwing a GH exception -- raiseErrorGHExc :: Position -> [String] -> GH a raiseErrorGHExc pos errs = raiseError pos errs >> throwGHExc -- error messages -- -------------- notClosedCondErr :: Position -> GH a notClosedCondErr pos = raiseErrorGHExc pos ["Unexpected end of file!", "File ended while the conditional block starting here was not closed \ \properly."] notOpenCondErr :: Position -> GH a notOpenCondErr pos = raiseErrorGHExc pos ["Missing #if[[n]def]!", "There is a #else, #elif, or #endif without an #if, #ifdef, or #ifndef."]