packages feed

bond-0.10.1.0: Main.hs

-- Copyright (c) Microsoft. All rights reserved.
-- Licensed under the MIT license. See LICENSE file in the project root for full license information.

{-# LANGUAGE RecordWildCards #-}

import System.Environment (getArgs, withArgs)
import System.Directory
import System.FilePath
import Data.Maybe
import Data.Monoid
import qualified Data.Foldable as F
import Control.Monad
import Prelude
import Control.Concurrent.Async
import GHC.Conc (getNumProcessors, setNumCapabilities)
import Data.Text.Lazy (Text)
import qualified Data.Text.Lazy.IO as L
import Data.Aeson (encode)
import qualified Data.ByteString.Lazy as BL
import Language.Bond.Syntax.Types (Bond(..), Declaration(..), Import, Type(..))
import Language.Bond.Syntax.JSON()
import Language.Bond.Syntax.SchemaDef
import Language.Bond.Codegen.Util
import Language.Bond.Codegen.Templates
import Language.Bond.Codegen.TypeMapping
import Options
import IO

type Template = MappingContext -> String -> [Import] -> [Declaration] -> (String, Text)

main :: IO()
main = do
    args <- getArgs
    options <- (if null args then withArgs ["--help=all"] else id) getOptions
    setJobs $ jobs options
    case options of
        Cpp {..}    -> cppCodegen options
        Cs {..}     -> csCodegen options
        Schema {..} -> writeSchema options
        _           -> print options

setJobs :: Maybe Int -> IO ()
setJobs Nothing = return ()
setJobs (Just n)
    | n > 0     = setNumCapabilities n
    | otherwise = do
        numProc <- getNumProcessors
        -- if n is less than 0 use that many fewer jobs than processors
        setNumCapabilities $ max 1 (numProc + n)

concurrentlyFor_ :: [a] -> (a -> IO b) -> IO ()
concurrentlyFor_ = (void .) . flip mapConcurrently


writeSchema :: Options -> IO()
writeSchema Schema {..} =
    concurrentlyFor_ files $ \file -> do
        let fileName = takeBaseName file
        bond <- parseFile import_dir file
        if runtime_schema then
                forM_ (bondDeclarations bond) (writeSchemaDef fileName)
            else
                BL.writeFile (output_dir </> fileName <.> "json") $ encode bond
  where
    writeSchemaDef fileName s@Struct{..} | null declParams =
        BL.writeFile (output_dir </> fileName <.> declName <.> "json") $ encodeSchemaDef $ BT_UserDefined s []
    writeSchemaDef _ _ = return ()

writeSchema _ = error "writeSchema: impossible happened."

cppCodegen :: Options -> IO()
cppCodegen options@Cpp {..} = do
    let typeMapping = maybe cppTypeMapping cppCustomAllocTypeMapping allocator
    concurrentlyFor_ files $ codeGen options typeMapping templates
  where
    applyProto = map snd $ filter (enabled apply) protocols
    enabled a p = null a || fst p `elem` a
    protocols =
        [ (Compact, ProtocolReader " ::bond::CompactBinaryReader< ::bond::InputBuffer>")
        , (Compact, ProtocolWriter " ::bond::CompactBinaryWriter< ::bond::OutputBuffer>")
        , (Compact, ProtocolWriter " ::bond::CompactBinaryWriter< ::bond::CompactBinaryCounter::type>")
        , (Fast,    ProtocolReader " ::bond::FastBinaryReader< ::bond::InputBuffer>")
        , (Fast,    ProtocolWriter " ::bond::FastBinaryWriter< ::bond::OutputBuffer>")
        , (Simple,  ProtocolReader " ::bond::SimpleBinaryReader< ::bond::InputBuffer>")
        , (Simple,  ProtocolWriter " ::bond::SimpleBinaryWriter< ::bond::OutputBuffer>")
        ]
    templates = concat $ map snd $ filter fst codegen_templates
    codegen_templates = [ (core_enabled, core_files)
                        , (comm_enabled, [comm_h export_attribute, comm_cpp])
                        , (grpc_enabled, [grpc_h export_attribute, grpc_cpp])
                        ]
    core_files = [
          reflection_h export_attribute
        , types_h header enum_header allocator
        , types_cpp
        , apply_h applyProto export_attribute
        , apply_cpp applyProto
        ] <>
        [ enum_h | enum_header]
cppCodegen _ = error "cppCodegen: impossible happened."

csCodegen :: Options -> IO()
csCodegen options@Cs {..} = do
    concurrentlyFor_ files $ codeGen options typeMapping templates
  where
    typeMapping = if collection_interfaces
            then csCollectionInterfacesTypeMapping
            else csTypeMapping
    fieldMapping = if readonly_properties
            then ReadOnlyProperties
            else if fields
                 then PublicFields
                 else Properties
    templates = concat $ map snd $ filter fst codegen_templates
    codegen_templates = [ (structs_enabled, [types_cs Class fieldMapping])
                        , (comm_enabled, [comm_interface_cs, comm_proxy_cs, comm_service_cs])
                        , (grpc_enabled, [grpc_cs])
                        ]
csCodegen _ = error "csCodegen: impossible happened."

anyServiceInheritance :: [Declaration] -> Bool
anyServiceInheritance = getAny . F.foldMap serviceWithBase
  where
    serviceWithBase Service{..} = Any $ isJust serviceBase
    serviceWithBase _ = Any False

codeGen :: Options -> TypeMapping -> [Template] -> FilePath -> IO ()
codeGen options typeMapping templates file = do
    let outputDir = output_dir options
    let baseName = takeBaseName file
    aliasMapping <- parseAliasMappings $ using options
    namespaceMapping <- parseNamespaceMappings $ namespace options
    (Bond imports namespaces declarations) <- parseFile (import_dir options) file
    let mappingContext = MappingContext typeMapping aliasMapping namespaceMapping namespaces
    case (anyServiceInheritance declarations, service_inheritance_enabled options, grpc_enabled options, comm_enabled options) of
        (True, False, _, _)   -> fail "Use --enable-service-inheritance to enable service inheritance syntax."
        (True, True, True, _) -> fail "Service inheritance is not supported in gRPC codegen."
        (True, True, _, True) -> fail "Service inheritance is not supported in Comm codegen."
        _                     -> forM_ templates $ \template -> do
                                    let (suffix, code) = template mappingContext baseName imports declarations
                                    let fileName = baseName ++ suffix
                                    createDirectoryIfMissing True outputDir
                                    let content = if (no_banner options) then code else (commonHeader "//" fileName <> code)
                                    L.writeFile (outputDir </> fileName) content