packages feed

bond 0.9.0.0 → 0.10.0.0

raw patch · 14 files changed

+589/−224 lines, 14 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

+ Language.Bond.Codegen.Templates: grpc_h :: Maybe String -> MappingContext -> String -> [Import] -> [Declaration] -> (String, Text)
+ Language.Bond.Syntax.Types: [serviceBase] :: Declaration -> Maybe Type
- Language.Bond.Syntax.Types: Service :: [Namespace] -> [Attribute] -> String -> [TypeParam] -> [Method] -> Declaration
+ Language.Bond.Syntax.Types: Service :: [Namespace] -> [Attribute] -> String -> [TypeParam] -> Maybe Type -> [Method] -> Declaration

Files

Main.hs view
@@ -6,7 +6,9 @@ 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
@@ -29,7 +31,7 @@ main :: IO()
 main = do
     args <- getArgs
-    options <- (if null args then withArgs ["--help"] else id) getOptions
+    options <- (if null args then withArgs ["--help=all"] else id) getOptions
     setJobs $ jobs options
     case options of
         Cpp {..}    -> cppCodegen options
@@ -69,28 +71,32 @@ cppCodegen :: Options -> IO()
 cppCodegen options@Cpp {..} = do
     let typeMapping = maybe cppTypeMapping cppCustomAllocTypeMapping allocator
-    concurrentlyFor_ files $ codeGen options typeMapping $
-        [ reflection_h export_attribute
-        , types_h header enum_header allocator
-        , types_cpp
-        , apply_h applyProto export_attribute
-        , apply_cpp applyProto
-        , comm_h export_attribute
-        , comm_cpp
-        ] <>
-        [ enum_h | enum_header]
+    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::OutputCounter>")
+        , (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])
+                        ]
+    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()
@@ -112,6 +118,12 @@                         ]
 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
@@ -120,9 +132,13 @@     namespaceMapping <- parseNamespaceMappings $ namespace options
     (Bond imports namespaces declarations) <- parseFile (import_dir options) file
     let mappingContext = MappingContext typeMapping aliasMapping namespaceMapping namespaces
-    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
+    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
Options.hs view
@@ -38,6 +38,10 @@         , export_attribute :: Maybe String
         , jobs :: Maybe Int
         , no_banner :: Bool
+        , core_enabled :: Bool
+        , comm_enabled :: Bool
+        , grpc_enabled :: Bool
+        , service_inheritance_enabled :: Bool
         }
     | Cs
         { files :: [FilePath]
@@ -53,6 +57,7 @@         , structs_enabled :: Bool
         , comm_enabled :: Bool
         , grpc_enabled :: Bool
+        , service_inheritance_enabled :: Bool
         }
     | Schema
         { files :: [FilePath]
@@ -60,6 +65,7 @@         , output_dir :: FilePath
         , jobs :: Maybe Int
         , runtime_schema :: Bool
+        , service_inheritance_enabled :: Bool
         }
       deriving (Show, Data, Typeable)
 
@@ -77,6 +83,10 @@     , export_attribute = def &= typ "ATTRIBUTE" &= explicit &= name "apply-attribute" &= name "export-attribute" &= help "Prefix declarations for library export with the specified C++ attribute/declspec. apply-attribute is a deprecated synonym."
     , jobs = def &= opt "0" &= typ "NUM" &= name "j" &= help "Run NUM jobs simultaneously (or '$ncpus' if no NUM is not given)"
     , no_banner = def &= help "Omit the banner at the top of generated files"
+    , core_enabled = True &= explicit &= name "core" &= help "Generate core serialization definitions (true by default, --core=false to disable)"
+    , comm_enabled = False &= explicit &= name "comm" &= help "Generate comm definitions"
+    , grpc_enabled = False &= explicit &= name "grpc" &= help "Generate gRPC definitions"
+    , service_inheritance_enabled = False &= explicit &= name "enable-service-inheritance" &= help "Enable service inheritance syntax in IDL"
     } &=
     name "c++" &=
     help "Generate C++ code"
bond.cabal view
@@ -2,7 +2,7 @@ -- Licensed under the MIT license. See LICENSE file in the project root for full license information.
 
 name:               bond
-version:            0.9.0.0
+version:            0.10.0.0
 cabal-version:      >= 1.8
 tested-with:        GHC>=7.4.1
 synopsis:           Bond schema compiler and code generator
@@ -65,6 +65,7 @@                     Language.Bond.Codegen.Cpp.Types_h
                     Language.Bond.Codegen.Cpp.Comm_cpp
                     Language.Bond.Codegen.Cpp.Comm_h
+                    Language.Bond.Codegen.Cpp.Grpc_h
                     Language.Bond.Codegen.Cs.Types_cs
                     Language.Bond.Codegen.Cs.Comm_cs
                     Language.Bond.Codegen.Cs.Grpc_cs
@@ -78,8 +79,10 @@ test-suite gbc-tests
   type:             exitcode-stdio-1.0
   hs-source-dirs:   tests, .
-  main-is:          Main.hs
-  other-modules:    Tests.Codegen
+  main-is:          TestMain.hs
+  other-modules:    IO
+                    Options
+                    Tests.Codegen
                     Tests.Codegen.Util
                     Tests.Syntax
   ghc-options:      -threaded -Wall
src/Language/Bond/Codegen/Cpp/ApplyOverloads.hs view
@@ -22,20 +22,23 @@ 
 -- Apply overloads
 applyOverloads :: [Protocol] -> MappingContext -> Text -> Text -> Declaration -> Text
-applyOverloads protocols cpp attr body s@Struct {..} | null declParams = [lt|
+applyOverloads protocols cpp attr extern s@Struct {..} | null declParams = [lt|
     //
-    // Overloads of Apply function with common transforms for #{declName}.
-    // These overloads will be selected using argument dependent lookup
-    // before ::bond::Apply function templates.
+    // Extern template specializations of Apply function with common
+    // transforms for #{declName}.
     //
-    #{attr}bool Apply(const ::bond::To< #{qualifiedName}>& transform,
-               const ::bond::bonded< #{qualifiedName}>& value)#{body}
 
-    #{attr}bool Apply(const ::bond::InitSchemaDef& transform,
-               const #{qualifiedName}& value)#{body}
+    #{extern}template #{attr}
+    bool Apply(const ::bond::To< #{qualifiedName}>& transform,
+               const ::bond::bonded< #{qualifiedName}>& value);
 
-    #{attr}bool Apply(const ::bond::Null& transform,
-               const ::bond::bonded< #{qualifiedName}, ::bond::SimpleBinaryReader< ::bond::InputBuffer>&>& value)#{body}
+    #{extern}template #{attr}
+    bool Apply(const ::bond::InitSchemaDef& transform,
+               const #{qualifiedName}& value);
+
+    #{extern}template #{attr}
+    bool Apply(const ::bond::Null& transform,
+               const ::bond::bonded< #{qualifiedName}, ::bond::SimpleBinaryReader< ::bond::InputBuffer>&>& value);
     #{newlineSep 1 applyOverloads' protocols}|]
   where
     qualifiedName = getDeclTypeName cpp s
@@ -49,24 +52,29 @@ 
     deserialization (ProtocolWriter _) = mempty
     deserialization (ProtocolReader protocolReader) = [lt|
-    #{attr}bool Apply(const ::bond::To< #{qualifiedName}>& transform,
-               const ::bond::bonded< #{qualifiedName}, #{protocolReader}&>& value)#{body}
+    #{extern}template #{attr}
+    bool Apply(const ::bond::To< #{qualifiedName}>& transform,
+               const ::bond::bonded< #{qualifiedName}, #{protocolReader}&>& value);
 
-    #{attr}bool Apply(const ::bond::To< #{qualifiedName}>& transform,
-               const ::bond::bonded<void, #{protocolReader}&>& value)#{body}|]
+    #{extern}template #{attr}
+    bool Apply(const ::bond::To< #{qualifiedName}>& transform,
+               const ::bond::bonded<void, #{protocolReader}&>& value);|]
 
     serialization (ProtocolReader _) _ = mempty
     serialization (ProtocolWriter protocolWriter) transform = [lt|
-    #{attr}bool Apply(const ::bond::#{transform}<#{protocolWriter} >& transform,
-               const #{qualifiedName}& value)#{body}
+    #{extern}template #{attr}
+    bool Apply(const ::bond::#{transform}<#{protocolWriter} >& transform,
+               const #{qualifiedName}& value);
 
-    #{attr}bool Apply(const ::bond::#{transform}<#{protocolWriter} >& transform,
-               const ::bond::bonded< #{qualifiedName}>& value)#{body}
+    #{extern}template #{attr}
+    bool Apply(const ::bond::#{transform}<#{protocolWriter} >& transform,
+               const ::bond::bonded< #{qualifiedName}>& value);
     #{newlineSep 1 transcoding protocols}|]
       where
         transcoding (ProtocolWriter _) = mempty
         transcoding (ProtocolReader protocolReader) = [lt|
-    #{attr}bool Apply(const ::bond::#{transform}<#{protocolWriter} >& transform,
-               const ::bond::bonded< #{qualifiedName}, #{protocolReader}&>& value)#{body}|]
+    #{extern}template #{attr}
+    bool Apply(const ::bond::#{transform}<#{protocolWriter} >& transform,
+               const ::bond::bonded< #{qualifiedName}, #{protocolReader}&>& value);|]
 
 applyOverloads _ _ _ _ _ = mempty
src/Language/Bond/Codegen/Cpp/Apply_cpp.hs view
@@ -11,7 +11,6 @@ import Language.Bond.Codegen.TypeMapping
 import Language.Bond.Codegen.Util
 import Language.Bond.Codegen.Cpp.ApplyOverloads
-import qualified Language.Bond.Codegen.Cpp.Util as CPP
 
 -- | Codegen template for generating /base_name/_apply.cpp containing
 -- definitions of the @Apply@ function overloads for the specified protocols.
@@ -21,15 +20,12 @@ #include "#{file}_apply.h"
 #include "#{file}_reflection.h"
 
-#{CPP.openNamespace cpp}
-    #{newlineSepEnd 1 (applyOverloads protocols cpp attr body) declarations}
-#{CPP.closeNamespace cpp}
+namespace bond
+{
+    #{newlineSepEnd 1 (applyOverloads protocols cpp attr extern) declarations}
+} // namespace bond
 |])
   where
-    body = [lt|
-    {
-        return ::bond::Apply<>(transform, value);
-    }|]
-
-    attr = [lt||]
+    attr = ""
+    extern = ""
 
src/Language/Bond/Codegen/Cpp/Apply_h.hs view
@@ -14,7 +14,6 @@ import Language.Bond.Codegen.Util
 import Language.Bond.Codegen.TypeMapping
 import Language.Bond.Codegen.Cpp.ApplyOverloads
-import qualified Language.Bond.Codegen.Cpp.Util as CPP
 
 -- | Codegen template for generating /base_name/_apply.h containing declarations of
 -- <https://microsoft.github.io/bond/manual/bond_cpp.html#optimizing-build-time Apply>
@@ -30,15 +29,15 @@ #include <bond/stream/output_buffer.h>
 #{newlineSep 0 includeImport imports}
 
-#{CPP.openNamespace cpp}
-    #{newlineSepEnd 1 (applyOverloads protocols cpp export_attr semi) declarations}
-#{CPP.closeNamespace cpp}
+namespace bond
+{
+    #{newlineSepEnd 1 (applyOverloads protocols cpp export_attr extern) declarations}
+} // namespace bond
 |])
   where
     includeImport (Import path) = [lt|#include "#{dropExtension path}_apply.h"|]
 
-    export_attr = optional (\a -> [lt|#{a}
-    |]) export_attribute
+    export_attr = optional (\a -> [lt|#{a}|]) export_attribute
 
-    semi = [lt|;|]
+    extern = "extern "
 
+ src/Language/Bond/Codegen/Cpp/Grpc_h.hs view
@@ -0,0 +1,291 @@+-- Copyright (c) Microsoft. All rights reserved.
+-- Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+{-# LANGUAGE QuasiQuotes, OverloadedStrings, RecordWildCards #-}
+
+module Language.Bond.Codegen.Cpp.Grpc_h (grpc_h) where
+
+import System.FilePath
+import Data.Monoid
+import Prelude
+import qualified Data.Text.Lazy as L
+import Data.Text.Lazy.Builder
+import Text.Shakespeare.Text
+import Language.Bond.Syntax.Types
+import Language.Bond.Codegen.Util
+import Language.Bond.Codegen.TypeMapping
+import qualified Language.Bond.Codegen.Cpp.Util as CPP
+
+
+-- | Codegen template for generating /base_name/_grpc.h containing declarations of
+-- of service interface and proxy.
+grpc_h :: Maybe String -> MappingContext -> String -> [Import] -> [Declaration] -> (String, L.Text)
+grpc_h _ cpp file imports declarations = ("_grpc.h", [lt|
+#pragma once
+
+#include "#{file}_reflection.h"
+#include "#{file}_types.h"
+#{newlineSep 0 includeImport imports}
+#include <bond/core/bonded.h>
+#include <bond/ext/grpc/bond_utils.h>
+#include <bond/ext/grpc/client_callback.h>
+#include <bond/ext/grpc/io_manager.h>
+#include <bond/ext/grpc/thread_pool.h>
+#include <bond/ext/grpc/unary_call.h>
+#include <bond/ext/grpc/detail/client_call_data.h>
+#include <bond/ext/grpc/detail/service.h>
+#include <bond/ext/grpc/detail/service_call_data.h>
+
+#include <boost/optional/optional.hpp>
+#include <functional>
+#include <memory>
+
+#ifdef _MSC_VER
+#pragma warning (push)
+#pragma warning (disable: 4100 4267)
+#endif
+
+#include <grpc++/impl/codegen/channel_interface.h>
+#include <grpc++/impl/codegen/client_context.h>
+#include <grpc++/impl/codegen/completion_queue.h>
+#include <grpc++/impl/codegen/rpc_method.h>
+#include <grpc++/impl/codegen/status.h>
+
+#ifdef _MSC_VER
+#pragma warning (pop)
+#endif
+
+#{CPP.openNamespace cpp}
+#{doubleLineSep 1 grpc declarations}
+
+#{CPP.closeNamespace cpp}
+
+|])
+  where
+    includeImport (Import path) = [lt|#include "#{dropExtension path}_grpc.h"|]
+
+    idl = MappingContext idlTypeMapping [] [] []
+
+    cppType = getTypeName cpp
+
+    payload = maybe "::bond::Void" cppType
+
+    bonded mt = bonded' (payload mt)
+      where
+        bonded' params =  [lt|::bond::bonded<#{padLeft}#{params}>|]
+          where
+            paramsText = toLazyText params
+            padLeft = if L.head paramsText == ':' then [lt| |] else mempty
+
+    grpc s@Service{..} = [lt|
+#{template}class #{declName} final
+{
+public:
+    template <typename TThreadPool>
+    class #{proxyName}
+    {
+    public:
+        #{proxyName}(
+            const std::shared_ptr< ::grpc::ChannelInterface>& channel,
+            std::shared_ptr< ::bond::ext::gRPC::io_manager> ioManager,
+            std::shared_ptr<TThreadPool> threadPool);
+
+        #{doubleLineSep 2 publicProxyMethodDecl serviceMethods}
+
+        #{proxyName}(const #{proxyName}&) = delete;
+        #{proxyName}& operator=(const #{proxyName}&) = delete;
+
+        #{proxyName}(#{proxyName}&&) = default;
+        #{proxyName}& operator=(#{proxyName}&&) = default;
+
+    private:
+        std::shared_ptr< ::grpc::ChannelInterface> _channel;
+        std::shared_ptr< ::bond::ext::gRPC::io_manager> _ioManager;
+        std::shared_ptr<TThreadPool> _threadPool;
+
+        #{doubleLineSep 2 privateProxyMethodDecl serviceMethods}
+    };
+
+    using Client = #{proxyName}< ::bond::ext::gRPC::thread_pool>;
+
+    template <typename TThreadPool>
+    class #{serviceName} : public ::bond::ext::gRPC::detail::service<TThreadPool>
+    {
+    public:
+        #{serviceName}()
+        {
+            #{newlineSep 3 serviceAddMethod serviceMethods}
+        }
+
+        virtual ~#{serviceName}() { }
+        #{serviceStartMethod}
+
+        #{newlineSep 2 serviceVirtualMethod serviceMethods}
+
+    private:
+        #{newlineSep 2 serviceMethodReceiveData serviceMethods}
+    };
+
+    using Service = #{serviceName}< ::bond::ext::gRPC::thread_pool>;
+};
+
+#{template}template <typename TThreadPool>
+inline #{className}::#{proxyName}<TThreadPool>::#{proxyName}(
+    const std::shared_ptr< ::grpc::ChannelInterface>& channel,
+    std::shared_ptr< ::bond::ext::gRPC::io_manager> ioManager,
+    std::shared_ptr<TThreadPool> threadPool)
+    : _channel(channel)
+    , _ioManager(ioManager)
+    , _threadPool(threadPool)
+    #{newlineSep 1 proxyMethodMemberInit serviceMethods}
+    { }
+
+#{doubleLineSep 0 methodDecl serviceMethods}
+|]
+      where
+        className = CPP.className s
+        template = CPP.template s
+        proxyName = "ClientCore" :: String
+        serviceName = "ServiceCore" :: String
+
+        methodNames :: [String]
+        methodNames = map methodName serviceMethods
+
+        serviceMethodsWithIndex :: [(Integer,Method)]
+        serviceMethodsWithIndex = zip [0..] serviceMethods
+
+        publicProxyMethodDecl Function{methodInput = Nothing, ..} = [lt|void Async#{methodName}(::std::shared_ptr< ::grpc::ClientContext> context, const std::function<void(std::shared_ptr< ::bond::ext::gRPC::unary_call_result< #{payload methodResult}>>)>& cb);
+        void Async#{methodName}(const std::function<void(std::shared_ptr< ::bond::ext::gRPC::unary_call_result< #{payload methodResult}>>)>& cb)
+        {
+            Async#{methodName}(::std::make_shared< ::grpc::ClientContext>(), cb);
+        }|]
+        publicProxyMethodDecl Function{..} = [lt|void Async#{methodName}(::std::shared_ptr< ::grpc::ClientContext> context, const #{bonded methodInput}& request, const std::function<void(std::shared_ptr< ::bond::ext::gRPC::unary_call_result< #{payload methodResult}>>)>& cb);
+        void Async#{methodName}(::std::shared_ptr< ::grpc::ClientContext> context, const #{payload methodInput}& request, const std::function<void(std::shared_ptr< ::bond::ext::gRPC::unary_call_result< #{payload methodResult}>>)>& cb)
+        {
+            Async#{methodName}(context, #{bonded methodInput}{request}, cb);
+        }
+        void Async#{methodName}(const #{bonded methodInput}& request, const std::function<void(std::shared_ptr< ::bond::ext::gRPC::unary_call_result< #{payload methodResult}>>)>& cb)
+        {
+            Async#{methodName}(::std::make_shared< ::grpc::ClientContext>(), request, cb);
+        }
+        void Async#{methodName}(const #{payload methodInput}& request, const std::function<void(std::shared_ptr< ::bond::ext::gRPC::unary_call_result< #{payload methodResult}>>)>& cb)
+        {
+            Async#{methodName}(::std::make_shared< ::grpc::ClientContext>(), #{bonded methodInput}{request}, cb);
+        }|]
+        publicProxyMethodDecl Event{methodInput = Nothing, ..} = [lt|void Async#{methodName}(::std::shared_ptr< ::grpc::ClientContext> context);
+        void Async#{methodName}()
+        {
+            Async#{methodName}(::std::make_shared< ::grpc::ClientContext>());
+        }|]
+        publicProxyMethodDecl Event{..} = [lt|void Async#{methodName}(::std::shared_ptr< ::grpc::ClientContext> context, const #{bonded methodInput}& request);
+        void Async#{methodName}(::std::shared_ptr< ::grpc::ClientContext> context, const #{payload methodInput}& request)
+        {
+            Async#{methodName}(context, #{bonded methodInput}{request});
+        }
+        void Async#{methodName}(const #{bonded methodInput}& request)
+        {
+            Async#{methodName}(::std::make_shared< ::grpc::ClientContext>(), request);
+        }
+        void Async#{methodName}(const #{payload methodInput}& request)
+        {
+            Async#{methodName}(::std::make_shared< ::grpc::ClientContext>(), #{bonded methodInput}{request});
+        }|]
+
+        privateProxyMethodDecl Function{..} = [lt|const ::grpc::RpcMethod rpcmethod_#{methodName}_;|]
+        privateProxyMethodDecl Event{..} = [lt|const ::grpc::RpcMethod rpcmethod_#{methodName}_;|]
+
+        proxyMethodMemberInit Function{..} = [lt|, rpcmethod_#{methodName}_("/#{getDeclTypeName idl s}/#{methodName}", ::grpc::RpcMethod::NORMAL_RPC, channel)|]
+        proxyMethodMemberInit Event{..} = [lt|, rpcmethod_#{methodName}_("/#{getDeclTypeName idl s}/#{methodName}", ::grpc::RpcMethod::NORMAL_RPC, channel)|]
+
+        methodDecl Function{..} = [lt|#{template}template <typename TThreadPool>
+inline void #{className}::#{proxyName}<TThreadPool>::Async#{methodName}(
+    ::std::shared_ptr< ::grpc::ClientContext> context,
+    #{voidParam methodInput}
+    const std::function<void(std::shared_ptr< ::bond::ext::gRPC::unary_call_result< #{payload methodResult}>>)>& cb)
+{
+    #{voidRequest methodInput}
+    auto calldata = std::make_shared< ::bond::ext::gRPC::detail::client_unary_call_data< #{payload methodInput}, #{payload methodResult}, TThreadPool>>(
+        _channel,
+        _ioManager,
+        _threadPool,
+        context,
+        cb);
+    calldata->dispatch(rpcmethod_#{methodName}_, request);
+}|]
+          where
+            voidRequest Nothing = [lt|auto request = ::bond::bonded< ::bond::Void>{ ::bond::Void()};|]
+            voidRequest _ = mempty
+            voidParam Nothing = mempty
+            voidParam _ = [lt|const #{bonded methodInput}& request,|]
+
+        methodDecl Event{..} = [lt|#{template}template <typename TThreadPool>
+inline void #{className}::#{proxyName}<TThreadPool>::Async#{methodName}(
+    ::std::shared_ptr< ::grpc::ClientContext> context
+    #{voidParam methodInput})
+{
+    #{voidRequest methodInput}
+    auto calldata = std::make_shared< ::bond::ext::gRPC::detail::client_unary_call_data< #{payload methodInput}, #{payload Nothing}, TThreadPool>>(
+        _channel,
+        _ioManager,
+        _threadPool,
+        context);
+    calldata->dispatch(rpcmethod_#{methodName}_, request);
+}|]
+          where
+            voidRequest Nothing = [lt|auto request = ::bond::bonded< ::bond::Void>{ ::bond::Void()};|]
+            voidRequest _ = mempty
+            voidParam Nothing = mempty
+            voidParam _ = [lt|, const #{bonded methodInput}& request|]
+
+        serviceAddMethod Function{..} = [lt|this->AddMethod("/#{getDeclTypeName idl s}/#{methodName}");|]
+        serviceAddMethod Event{..} = [lt|this->AddMethod("/#{getDeclTypeName idl s}/#{methodName}");|]
+
+        serviceStartMethod = [lt|virtual void start(
+            ::grpc::ServerCompletionQueue* #{cqParam},
+            std::shared_ptr<TThreadPool> #{tpParam}) override
+        {
+            BOOST_ASSERT(#{cqParam});
+            BOOST_ASSERT(#{tpParam});
+
+            #{newlineSep 3 initMethodReceiveData serviceMethodsWithIndex}
+
+            #{newlineSep 3 queueReceive serviceMethodsWithIndex}
+        }|]
+            where cqParam = uniqueName "cq" methodNames
+                  tpParam = uniqueName "tp" methodNames
+                  initMethodReceiveData (index,Function{..}) = [lt|#{serviceRdMember methodName}.emplace(
+                this,
+                #{index},
+                #{cqParam},
+                #{tpParam},
+                std::bind(&#{serviceName}::#{methodName}, this, std::placeholders::_1));|]
+                  initMethodReceiveData (index,Event{..}) = [lt|#{serviceRdMember methodName}.emplace(
+                this,
+                #{index},
+                #{cqParam},
+                #{tpParam},
+                std::bind(&#{serviceName}::#{methodName}, this, std::placeholders::_1));|]
+                  queueReceive (index,Function{..}) = [lt|this->queue_receive(
+                #{index},
+                &#{serviceRdMember methodName}->_receivedCall->_context,
+                &#{serviceRdMember methodName}->_receivedCall->_request,
+                &#{serviceRdMember methodName}->_receivedCall->_responder,
+                #{cqParam},
+                &#{serviceRdMember methodName}.get());|]
+                  queueReceive (index,Event{..}) = [lt|this->queue_receive(
+                #{index},
+                &#{serviceRdMember methodName}->_receivedCall->_context,
+                &#{serviceRdMember methodName}->_receivedCall->_request,
+                &#{serviceRdMember methodName}->_receivedCall->_responder,
+                #{cqParam},
+                &#{serviceRdMember methodName}.get());|]
+
+        serviceMethodReceiveData Function{..} = [lt|::boost::optional< ::bond::ext::gRPC::detail::service_unary_call_data< #{bonded methodInput}, #{payload methodResult}, TThreadPool>> #{serviceRdMember methodName};|]
+        serviceMethodReceiveData Event{..} = [lt|::boost::optional< ::bond::ext::gRPC::detail::service_unary_call_data< #{bonded methodInput}, #{payload Nothing}, TThreadPool>> #{serviceRdMember methodName};|]
+
+        serviceVirtualMethod Function{..} = [lt|virtual void #{methodName}(::bond::ext::gRPC::unary_call< #{bonded methodInput}, #{payload methodResult}>) = 0;|]
+        serviceVirtualMethod Event{..} = [lt|virtual void #{methodName}(::bond::ext::gRPC::unary_call< #{bonded methodInput}, #{payload Nothing}>) = 0;|]
+
+        serviceRdMember methodName = uniqueName ("_rd_" ++ methodName) methodNames
+
+    grpc _ = mempty
src/Language/Bond/Codegen/Cpp/Reflection_h.hs view
@@ -107,14 +107,14 @@ 
         fieldTemplates = F.foldMap $ \ f@Field {..} -> [lt|
             // #{fieldName}
-            typedef ::bond::reflection::FieldTemplate<
+            typedef struct : ::bond::reflection::FieldTemplate<
                 #{fieldOrdinal},
                 #{CPP.modifierTag f},
                 #{className},
                 #{cppType fieldType},
                 &#{className}::#{fieldName},
                 &s_#{fieldName}_metadata
-            > #{fieldName};
+            > {}  #{fieldName};
         |]
 
 
src/Language/Bond/Codegen/Templates.hs view
@@ -44,6 +44,7 @@       -- ** C++ Comm
     , comm_h
     , comm_cpp
+    , grpc_h
       -- ** C#
     , FieldMapping(..)
     , StructMapping(..)
@@ -64,6 +65,7 @@ import Language.Bond.Codegen.Cpp.Types_h
 import Language.Bond.Codegen.Cpp.Comm_cpp
 import Language.Bond.Codegen.Cpp.Comm_h
+import Language.Bond.Codegen.Cpp.Grpc_h
 import Language.Bond.Codegen.Cs.Types_cs
 import Language.Bond.Codegen.Cs.Comm_cs
 import Language.Bond.Codegen.Cs.Grpc_cs
src/Language/Bond/Parser.hs view
@@ -327,6 +327,15 @@         Right (Service {..}, _) -> fail $ "Unexpected service " ++ declName ++ ". Expected struct, enum or alias."
         Right (decl, args) -> return $ BT_UserDefined decl args
 
+-- parser for service type
+serviceType :: Parser Type
+serviceType = do
+    symbol_ <- userSymbol
+    case symbol_ of
+        Right (decl@Service{}, args) -> return $ BT_UserDefined decl args
+        Right (decl, _) -> fail $ "Unexpected type " ++ (declName decl) ++ ". Expected a service."
+        Left param -> fail $ "Unexpected type parameter " ++ (paramName param) ++ ". Expected a service."
+
 userSymbol :: Parser (Either TypeParam (Declaration, [Type]))
 userSymbol = do
     name <- qualifiedName
@@ -369,8 +378,9 @@     name <- keyword "service" *> identifier <?> "service definition"
     params <- parameters
     namespaces <- asks currentNamespaces
-    local (with params) $ Service namespaces attr name params <$> methods <* optional semi
+    local (with params) $ Service namespaces attr name params <$> base <*> methods <* optional semi
   where
+    base = optional (colon *> serviceType <?> "base service")
     with params e = e { currentParams = params }
     methods = unique $ braces $ semiEnd (try event <|> try function)
     unique p = do
src/Language/Bond/Syntax/Types.hs view
@@ -173,6 +173,7 @@         , declAttributes :: [Attribute]     -- zero or more attributes
         , declName :: String                -- service name
         , declParams :: [TypeParam]         -- type parameters for generic service
+        , serviceBase :: Maybe Type         -- optional base service
         , serviceMethods :: [Method]        -- zero or more methods
         }                                   -- ^ <https://microsoft.github.io/bond/manual/compiler.html#service-definition service definition>
     deriving (Eq, Show)
− tests/Main.hs
@@ -1,163 +0,0 @@--- Copyright (c) Microsoft. All rights reserved.
--- Licensed under the MIT license. See LICENSE file in the project root for full license information.
-
-import Test.Tasty
-import Test.Tasty.QuickCheck
-import Test.Tasty.HUnit (testCase)
-import Tests.Syntax
-import Tests.Codegen
-import Tests.Codegen.Util(utilTestGroup)
-
-tests :: TestTree
-tests = testGroup "Compiler tests"
-    [ testGroup "AST"
-        [ localOption (QuickCheckMaxSize 6) $
-            testProperty "roundtrip" roundtripAST
-        , testGroup "Compare .bond and .json"
-            [ testCase "attributes" $ compareAST "attributes"
-            , testCase "basic types" $ compareAST "basic_types"
-            , testCase "bond_meta types" $ compareAST "bond_meta"
-            , testCase "complex types" $ compareAST "complex_types"
-            , testCase "default values" $ compareAST "defaults"
-            , testCase "empty" $ compareAST "empty"
-            , testCase "field modifiers" $ compareAST "field_modifiers"
-            , testCase "generics" $ compareAST "generics"
-            , testCase "inheritance" $ compareAST "inheritance"
-            , testCase "type aliases" $ compareAST "aliases"
-            , testCase "documentation example" $ compareAST "example"
-            , testCase "simple service syntax" $ compareAST "service"
-            , testCase "service attributes" $ compareAST "service_attributes"
-            , testCase "generic service" $ compareAST "generic_service"
-            , testCase "documentation example" $ compareAST "example"
-            ]
-        ]
-    , testGroup "SchemaDef"
-        [ verifySchemaDef "attributes" "Foo"
-        , verifySchemaDef "basic_types" "BasicTypes"
-        , verifySchemaDef "defaults" "Foo"
-        , verifySchemaDef "field_modifiers" "Foo"
-        , verifySchemaDef "inheritance" "Foo"
-        , verifySchemaDef "alias_key" "foo"
-        , verifySchemaDef "maybe_blob" "Foo"
-        , verifySchemaDef "nullable_alias" "foo"
-        , verifySchemaDef "schemadef" "AliasBase"
-        , verifySchemaDef "schemadef" "EnumDefault"
-        , verifySchemaDef "schemadef" "StringTree"
-        , verifySchemaDef "example" "SomeStruct"
-        ]
-    , testGroup "Types"
-        [ testCase "type alias resolution" aliasResolution
-        ]
-    , testGroup "Codegen Failures (Expect to see errors below check for OK or FAIL)"
-        [ testCase "Struct default value nothing" $ failBadSyntax "Should fail when default value of a struct field is 'nothing'" "struct_nothing"
-        , testCase "Enum no default value" $ failBadSyntax "Should fail when an enum field has no default value" "enum_no_default"
-        , testCase "Alias default value" $ failBadSyntax "Should fail when underlying default value is of the wrong type" "aliases_default"
-        , testCase "Out of range" $ failBadSyntax "Should fail, out of range for int16" "int_out_of_range"
-        , testCase "Duplicate method definition in service" $ failBadSyntax "Should fail, method name should be unique" "duplicate_service_method"
-        ]
-    , testGroup "Codegen"
-        [ utilTestGroup,
-          testGroup "C++"
-            [ verifyCppCodegen "attributes"
-            , verifyCppCodegen "basic_types"
-            , verifyCppCodegen "bond_meta"
-            , verifyCppCodegen "complex_types"
-            , verifyCppCodegen "defaults"
-            , verifyCppCodegen "empty"
-            , verifyCppCodegen "field_modifiers"
-            , verifyCppCodegen "generics"
-            , verifyCppCodegen "inheritance"
-            , verifyCppCodegen "aliases"
-            , verifyCppCodegen "alias_key"
-            , verifyCppCodegen "maybe_blob"
-            , verifyCodegen
-                [ "c++"
-                , "--enum-header"
-                ]
-                "with_enum_header"
-            , verifyCodegen
-                [ "c++"
-                , "--allocator=arena"
-                ]
-                "alias_with_allocator"
-            , verifyCodegen
-                [ "c++"
-                , "--allocator=arena"
-                , "--using=List=my::list<{0}, arena>"
-                , "--using=Vector=my::vector<{0}, arena>"
-                , "--using=Set=my::set<{0}, arena>"
-                , "--using=Map=my::map<{0}, {1}, arena>"
-                , "--using=String=my::string<arena>"
-                ]
-                "custom_alias_with_allocator"
-            , verifyCodegen
-                [ "c++"
-                , "--allocator=arena"
-                , "--using=List=my::list<{0}>"
-                , "--using=Vector=my::vector<{0}>"
-                , "--using=Set=my::set<{0}>"
-                , "--using=Map=my::map<{0}, {1}>"
-                , "--using=String=my::string"
-                ]
-                "custom_alias_without_allocator"
-           , testGroup "Apply"
-                [ verifyApplyCodegen
-                    [ "c++"
-                    , "--apply-attribute=DllExport"
-                    ]
-                    "basic_types"
-                ]
-           , testGroup "Exports"
-                [ verifyExportsCodegen
-                    [ "c++"
-                    , "--export-attribute=DllExport"
-                    ]
-                    "service"
-                ]
-            , testGroup "Comm"
-                [ verifyCppCommCodegen
-                    [ "c++"
-                    ]
-                    "service"
-                , verifyCppCommCodegen
-                    [ "c++"
-                    ]
-                    "generic_service"
-                , verifyCppCommCodegen
-                    [ "c++"
-                    ]
-                    "service_attributes"
-                ]
-            ]
-        , testGroup "C#"
-            [ verifyCsCodegen "attributes"
-            , verifyCsCodegen "basic_types"
-            , verifyCsCodegen "bond_meta"
-            , verifyCsCodegen "complex_types"
-            , verifyCsCodegen "defaults"
-            , verifyCsCodegen "empty"
-            , verifyCsCodegen "field_modifiers"
-            , verifyCsCodegen "generics"
-            , verifyCsCodegen "inheritance"
-            , verifyCsCodegen "aliases"
-            , verifyCodegen
-                [ "c#"
-                , "--using=time=System.DateTime"
-                ]
-                "nullable_alias"
-            , testGroup "Comm"
-                [ verifyCsCommCodegen
-                    [ "c#"
-                    ]
-                    "service"
-                , verifyCsCommCodegen
-                    [ "c#"
-                    ]
-                    "generic_service"
-                ]
-            ]
-        ]
-    ]
-
-main :: IO ()
-main = defaultMain tests
+ tests/TestMain.hs view
@@ -0,0 +1,180 @@+-- Copyright (c) Microsoft. All rights reserved.
+-- Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+import Test.Tasty
+import Test.Tasty.QuickCheck
+import Test.Tasty.HUnit (testCase)
+import Tests.Syntax
+import Tests.Codegen
+import Tests.Codegen.Util(utilTestGroup)
+
+tests :: TestTree
+tests = testGroup "Compiler tests"
+    [ testGroup "AST"
+        [ localOption (QuickCheckMaxSize 6) $
+            testProperty "roundtrip" roundtripAST
+        , testGroup "Compare .bond and .json"
+            [ testCase "attributes" $ compareAST "attributes"
+            , testCase "basic types" $ compareAST "basic_types"
+            , testCase "bond_meta types" $ compareAST "bond_meta"
+            , testCase "complex types" $ compareAST "complex_types"
+            , testCase "default values" $ compareAST "defaults"
+            , testCase "empty" $ compareAST "empty"
+            , testCase "field modifiers" $ compareAST "field_modifiers"
+            , testCase "generics" $ compareAST "generics"
+            , testCase "inheritance" $ compareAST "inheritance"
+            , testCase "type aliases" $ compareAST "aliases"
+            , testCase "documentation example" $ compareAST "example"
+            , testCase "simple service syntax" $ compareAST "service"
+            , testCase "service attributes" $ compareAST "service_attributes"
+            , testCase "generic service" $ compareAST "generic_service"
+            , testCase "documentation example" $ compareAST "example"
+            , testCase "service inheritance" $ compareAST "service_inheritance"
+            ]
+        ]
+    , testGroup "SchemaDef"
+        [ verifySchemaDef "attributes" "Foo"
+        , verifySchemaDef "basic_types" "BasicTypes"
+        , verifySchemaDef "defaults" "Foo"
+        , verifySchemaDef "field_modifiers" "Foo"
+        , verifySchemaDef "inheritance" "Foo"
+        , verifySchemaDef "alias_key" "foo"
+        , verifySchemaDef "maybe_blob" "Foo"
+        , verifySchemaDef "nullable_alias" "foo"
+        , verifySchemaDef "schemadef" "AliasBase"
+        , verifySchemaDef "schemadef" "EnumDefault"
+        , verifySchemaDef "schemadef" "StringTree"
+        , verifySchemaDef "example" "SomeStruct"
+        ]
+    , testGroup "Types"
+        [ testCase "type alias resolution" aliasResolution
+        ]
+    , testGroup "Codegen Failures (Expect to see errors below check for OK or FAIL)"
+        [ testCase "Struct default value nothing" $ failBadSyntax "Should fail when default value of a struct field is 'nothing'" "struct_nothing"
+        , testCase "Enum no default value" $ failBadSyntax "Should fail when an enum field has no default value" "enum_no_default"
+        , testCase "Alias default value" $ failBadSyntax "Should fail when underlying default value is of the wrong type" "aliases_default"
+        , testCase "Out of range" $ failBadSyntax "Should fail, out of range for int16" "int_out_of_range"
+        , testCase "Duplicate method definition in service" $ failBadSyntax "Should fail, method name should be unique" "duplicate_service_method"
+        , testCase "Invalid service base: struct" $ failBadSyntax "Should fail, struct can't be used as service base" "service_invalid_base_struct"
+        , testCase "Invalid service base: type param" $ failBadSyntax "Should fail, type param can't be used as service base" "service_invalid_base_type_param"
+        ]
+    , testGroup "Codegen"
+        [ utilTestGroup,
+          testGroup "C++"
+            [ verifyCppCodegen "attributes"
+            , verifyCppCodegen "basic_types"
+            , verifyCppCodegen "bond_meta"
+            , verifyCppCodegen "complex_types"
+            , verifyCppCodegen "defaults"
+            , verifyCppCodegen "empty"
+            , verifyCppCodegen "field_modifiers"
+            , verifyCppCodegen "generics"
+            , verifyCppCodegen "inheritance"
+            , verifyCppCodegen "aliases"
+            , verifyCppCodegen "alias_key"
+            , verifyCppCodegen "maybe_blob"
+            , verifyCodegen
+                [ "c++"
+                , "--enum-header"
+                ]
+                "with_enum_header"
+            , verifyCodegen
+                [ "c++"
+                , "--allocator=arena"
+                ]
+                "alias_with_allocator"
+            , verifyCodegen
+                [ "c++"
+                , "--allocator=arena"
+                , "--using=List=my::list<{0}, arena>"
+                , "--using=Vector=my::vector<{0}, arena>"
+                , "--using=Set=my::set<{0}, arena>"
+                , "--using=Map=my::map<{0}, {1}, arena>"
+                , "--using=String=my::string<arena>"
+                ]
+                "custom_alias_with_allocator"
+            , verifyCodegen
+                [ "c++"
+                , "--allocator=arena"
+                , "--using=List=my::list<{0}>"
+                , "--using=Vector=my::vector<{0}>"
+                , "--using=Set=my::set<{0}>"
+                , "--using=Map=my::map<{0}, {1}>"
+                , "--using=String=my::string"
+                ]
+                "custom_alias_without_allocator"
+           , testGroup "Apply"
+                [ verifyApplyCodegen
+                    [ "c++"
+                    , "--apply-attribute=DllExport"
+                    ]
+                    "basic_types"
+                ]
+           , testGroup "Exports"
+                [ verifyExportsCodegen
+                    [ "c++"
+                    , "--export-attribute=DllExport"
+                    ]
+                    "service"
+                ]
+            , testGroup "Comm"
+                [ verifyCppCommCodegen
+                    [ "c++"
+                    ]
+                    "service"
+                , verifyCppCommCodegen
+                    [ "c++"
+                    ]
+                    "generic_service"
+                , verifyCppCommCodegen
+                    [ "c++"
+                    ]
+                    "service_attributes"
+                ]
+            , testGroup "Grpc"
+                [ verifyCppGrpcCodegen
+                    [ "c++"
+                    ]
+                    "service"
+                , verifyCppGrpcCodegen
+                    [ "c++"
+                    ]
+                    "generic_service"
+                , verifyCppGrpcCodegen
+                    [ "c++"
+                    ]
+                    "service_attributes"
+                ]
+            ]
+        , testGroup "C#"
+            [ verifyCsCodegen "attributes"
+            , verifyCsCodegen "basic_types"
+            , verifyCsCodegen "bond_meta"
+            , verifyCsCodegen "complex_types"
+            , verifyCsCodegen "defaults"
+            , verifyCsCodegen "empty"
+            , verifyCsCodegen "field_modifiers"
+            , verifyCsCodegen "generics"
+            , verifyCsCodegen "inheritance"
+            , verifyCsCodegen "aliases"
+            , verifyCodegen
+                [ "c#"
+                , "--using=time=System.DateTime"
+                ]
+                "nullable_alias"
+            , testGroup "Comm"
+                [ verifyCsCommCodegen
+                    [ "c#"
+                    ]
+                    "service"
+                , verifyCsCommCodegen
+                    [ "c#"
+                    ]
+                    "generic_service"
+                ]
+            ]
+        ]
+    ]
+
+main :: IO ()
+main = defaultMain tests
tests/Tests/Codegen.hs view
@@ -8,6 +8,7 @@     ( verifyCodegen
     , verifyCppCodegen
     , verifyCppCommCodegen
+    , verifyCppGrpcCodegen
     , verifyApplyCodegen
     , verifyExportsCodegen
     , verifyCsCodegen
@@ -84,6 +85,17 @@     templates =
         [ comm_h (export_attribute options)
         , comm_cpp
+        , types_cpp
+        ]
+
+verifyCppGrpcCodegen :: [String] -> FilePath -> TestTree
+verifyCppGrpcCodegen args baseName =
+    testGroup baseName $
+        map (verifyFile options baseName cppTypeMapping "") templates
+  where
+    options = processOptions args
+    templates =
+        [ grpc_h (export_attribute options)
         , types_cpp
         ]