packages feed

bond 0.8.0.0 → 0.9.0.0

raw patch · 13 files changed

+324/−100 lines, 13 filesdep ~derivePVP ok

version bump matches the API change (PVP)

Dependency ranges changed: derive

API changes (from Hackage documentation)

- Language.Bond.Codegen.Templates: Protocol :: String -> String -> Protocol
- Language.Bond.Codegen.Templates: [protocolReader] :: Protocol -> String
- Language.Bond.Codegen.Templates: [protocolWriter] :: Protocol -> String
+ Language.Bond.Codegen.Templates: ProtocolReader :: String -> Protocol
+ Language.Bond.Codegen.Templates: ProtocolWriter :: String -> Protocol
+ Language.Bond.Codegen.Templates: grpc_cs :: MappingContext -> String -> [Import] -> [Declaration] -> (String, Text)
+ Language.Bond.Codegen.Util: uniqueName :: String -> [String] -> String

Files

Main.hs view
@@ -83,25 +83,33 @@     applyProto = map snd $ filter (enabled apply) protocols
     enabled a p = null a || fst p `elem` a
     protocols =
-        [ (Compact, Protocol " ::bond::CompactBinaryReader< ::bond::InputBuffer>"
-                             " ::bond::CompactBinaryWriter< ::bond::OutputBuffer>")
-        , (Fast,    Protocol " ::bond::FastBinaryReader< ::bond::InputBuffer>"
-                             " ::bond::FastBinaryWriter< ::bond::OutputBuffer>")
-        , (Simple,  Protocol " ::bond::SimpleBinaryReader< ::bond::InputBuffer>"
-                             " ::bond::SimpleBinaryWriter< ::bond::OutputBuffer>")
+        [ (Compact, ProtocolReader " ::bond::CompactBinaryReader< ::bond::InputBuffer>")
+        , (Compact, ProtocolWriter " ::bond::CompactBinaryWriter< ::bond::OutputBuffer>")
+        , (Compact, ProtocolWriter " ::bond::CompactBinaryWriter< ::bond::OutputCounter>")
+        , (Fast,    ProtocolReader " ::bond::FastBinaryReader< ::bond::InputBuffer>")
+        , (Fast,    ProtocolWriter " ::bond::FastBinaryWriter< ::bond::OutputBuffer>")
+        , (Simple,  ProtocolReader " ::bond::SimpleBinaryReader< ::bond::InputBuffer>")
+        , (Simple,  ProtocolWriter " ::bond::SimpleBinaryWriter< ::bond::OutputBuffer>")
         ]
 cppCodegen _ = error "cppCodegen: impossible happened."
 
 csCodegen :: Options -> IO()
 csCodegen options@Cs {..} = do
-    let fieldMapping = if readonly_properties
+    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
-    let typeMapping = if collection_interfaces then csCollectionInterfacesTypeMapping else csTypeMapping
-    let templates = [ comm_interface_cs , comm_proxy_cs , comm_service_cs , types_cs Class fieldMapping ]
-    concurrentlyFor_ files $ codeGen options typeMapping templates
+    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."
 
 codeGen :: Options -> TypeMapping -> [Template] -> FilePath -> IO ()
Options.hs view
@@ -50,6 +50,9 @@         , fields :: Bool
         , jobs :: Maybe Int
         , no_banner :: Bool
+        , structs_enabled :: Bool
+        , comm_enabled :: Bool
+        , grpc_enabled :: Bool
         }
     | Schema
         { files :: [FilePath]
@@ -83,6 +86,9 @@     { collection_interfaces = def &= name "c" &= help "Use interfaces rather than concrete collection types"
     , readonly_properties = def &= name "r" &= help "Generate private property setters"
     , fields = def &= name "f" &= help "Generate public fields rather than properties"
+    , structs_enabled = True &= explicit &= name "structs" &= help "Generate C# types for Bond structs and enums (true by default, use \"--structs=false\" to disable)"
+    , comm_enabled = False &= explicit &= name "comm" &= help "Generate C# services and proxies for Bond Comm"
+    , grpc_enabled = False &= explicit &= name "grpc" &= help "Generate C# services and proxies for gRPC"
     } &=
     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.8.0.0
+version:            0.9.0.0
 cabal-version:      >= 1.8
 tested-with:        GHC>=7.4.1
 synopsis:           Bond schema compiler and code generator
@@ -67,6 +67,7 @@                     Language.Bond.Codegen.Cpp.Comm_h
                     Language.Bond.Codegen.Cs.Types_cs
                     Language.Bond.Codegen.Cs.Comm_cs
+                    Language.Bond.Codegen.Cs.Grpc_cs
                     Language.Bond.Codegen.Cpp.ApplyOverloads
                     Language.Bond.Codegen.Cpp.Util
                     Language.Bond.Codegen.Cs.Util
@@ -79,6 +80,7 @@   hs-source-dirs:   tests, .
   main-is:          Main.hs
   other-modules:    Tests.Codegen
+                    Tests.Codegen.Util
                     Tests.Syntax
   ghc-options:      -threaded -Wall
   build-depends:    bond,
@@ -91,7 +93,7 @@                     filepath >= 1.0,
                     monad-loops >= 0.4,
                     text >= 0.11,
-                    derive,
+                    derive < 2.6,
                     HUnit,
                     QuickCheck,
                     Diff >= 0.2 && < 0.4,
src/Language/Bond/Codegen/Cpp/ApplyOverloads.hs view
@@ -16,10 +16,8 @@ -- | Protocol data type is used to specify what protocols the @Apply@ function
 -- overloads should be generated for.
 data Protocol =
-    Protocol
-    { protocolReader :: String -- ^ Name of the class implementing the protocol reader.
-    , protocolWriter :: String -- ^ Name of the class implementing the protocol writer.
-    }
+    ProtocolReader String | -- ^ Name of the class implementing the protocol reader.
+    ProtocolWriter String   -- ^ Name of the class implementing the protocol writer.
 
 
 -- Apply overloads
@@ -35,34 +33,40 @@ 
     #{attr}bool Apply(const ::bond::InitSchemaDef& transform,
                const #{qualifiedName}& value)#{body}
+
+    #{attr}bool Apply(const ::bond::Null& transform,
+               const ::bond::bonded< #{qualifiedName}, ::bond::SimpleBinaryReader< ::bond::InputBuffer>&>& value)#{body}
     #{newlineSep 1 applyOverloads' protocols}|]
   where
     qualifiedName = getDeclTypeName cpp s
 
-    applyOverloads' p = [lt|#{deserialization p}
-    #{serialization serializer p}
-    #{serialization marshaler p}|]
+    applyOverloads' p = [lt|#{deserialization p}#{newlineSep 1 (serialization p) serializingTransforms}|]
 
-    serializer = "Serializer" :: String
-    marshaler = "Marshaler" :: String
+    serializingTransforms =
+        [ [lt|Serializer|]
+        , [lt|Marshaler|]
+        ]
 
-    deserialization Protocol {..} = [lt|
+    deserialization (ProtocolWriter _) = mempty
+    deserialization (ProtocolReader protocolReader) = [lt|
     #{attr}bool Apply(const ::bond::To< #{qualifiedName}>& transform,
                const ::bond::bonded< #{qualifiedName}, #{protocolReader}&>& value)#{body}
 
     #{attr}bool Apply(const ::bond::To< #{qualifiedName}>& transform,
                const ::bond::bonded<void, #{protocolReader}&>& value)#{body}|]
 
-    serialization transform Protocol {..} = [lt|
+    serialization (ProtocolReader _) _ = mempty
+    serialization (ProtocolWriter protocolWriter) transform = [lt|
     #{attr}bool Apply(const ::bond::#{transform}<#{protocolWriter} >& transform,
                const #{qualifiedName}& value)#{body}
 
     #{attr}bool Apply(const ::bond::#{transform}<#{protocolWriter} >& transform,
                const ::bond::bonded< #{qualifiedName}>& value)#{body}
-    #{newlineSep 1 (transcoding transform) protocols}|]
+    #{newlineSep 1 transcoding protocols}|]
       where
-        transcoding transform' Protocol {protocolReader = fromReader} = [lt|
-    #{attr}bool Apply(const ::bond::#{transform'}<#{protocolWriter} >& transform,
-               const ::bond::bonded< #{qualifiedName}, #{fromReader}&>& value)#{body}|]
+        transcoding (ProtocolWriter _) = mempty
+        transcoding (ProtocolReader protocolReader) = [lt|
+    #{attr}bool Apply(const ::bond::#{transform}<#{protocolWriter} >& transform,
+               const ::bond::bonded< #{qualifiedName}, #{protocolReader}&>& value)#{body}|]
 
 applyOverloads _ _ _ _ _ = mempty
src/Language/Bond/Codegen/Cpp/Enum_h.hs view
@@ -20,6 +20,8 @@ enum_h cpp _file _imports declarations = ("_enum.h", [lt|
 #pragma once
 
+#include <stdint.h>
+
 #{CPP.openNamespace cpp}
 namespace _bond_enumerators
 {
src/Language/Bond/Codegen/Cpp/Types_h.hs view
@@ -128,9 +128,9 @@             return true#{optional baseEqual structBase}#{newlineBeginSep 4 fieldEqual structFields};
         }
 
-        bool operator!=(const #{declName}& other) const
+        bool operator!=(const #{declName}& #{otherParamName}) const
         {
-            return !(*this == other);
+            return !(*this == #{otherParamName});
         }
 
         void swap(#{declName}&#{otherParam})
@@ -144,15 +144,19 @@         #{initMetadata}
     };
 
-    #{template}inline void swap(#{qualifiedClassName}& left, #{qualifiedClassName}& right)
+    #{template}inline void swap(#{qualifiedClassName}& #{leftParamName}, #{qualifiedClassName}& #{rightParamName})
     {
-        left.swap(right);
+        #{leftParamName}.swap(#{rightParamName});
     }|]
       where
         template = CPP.template s
         qualifiedClassName = CPP.qualifiedClassName cpp s
 
-        otherParam = if hasOnlyMetaFields then mempty else [lt| other|]
+        fieldNames :: [String]
+        fieldNames = foldMapStructFields (return . fieldName) s
+
+        otherParamName = uniqueName "other" fieldNames
+        otherParam = if hasOnlyMetaFields then mempty else ' ':otherParamName
         hasOnlyMetaFields = not (any (not . getAny . metaField) structFields) && isNothing structBase
         hasMetaFields = getAny $ foldMapStructFields metaField s
 
@@ -248,16 +252,16 @@             -- default OK when there are no meta fields
             implicitlyDeclared = CPP.ifndef CPP.defaultedFunctions [lt|
         // Compiler generated copy ctor OK
-        #{declName}(const #{declName}& other) = default;|]
+        #{declName}(const #{declName}&) = default;|]
 
             -- define ctor to initialize meta fields
-            define = [lt|#{declName}(const #{declName}& other)#{initList}#{ctorBody}|]
+            define = [lt|#{declName}(const #{declName}& #{otherParamName})#{initList}#{ctorBody}|]
               where
                 initList = initializeList
                     (optional baseCopy structBase)
                     (commaLineSep 3 fieldCopy structFields)
-                baseCopy b = [lt|#{cppType b}(other)|]
-                fieldCopy Field {..} = [lt|#{fieldName}(other.#{fieldName}#{getAllocator fieldType})|]
+                baseCopy b = [lt|#{cppType b}(#{otherParamName})|]
+                fieldCopy Field {..} = [lt|#{fieldName}(#{otherParamName}.#{fieldName}#{getAllocator fieldType})|]
                 getAllocator BT_MetaName = [lt|.get_allocator()|]
                 getAllocator BT_MetaFullName =  [lt|.get_allocator()|]
                 getAllocator _ = mempty
@@ -277,7 +281,7 @@ #endif|]
           where
             -- default OK when there are no meta fields
-            implicit = [lt|#{declName}(#{declName}&& other) = default;|]
+            implicit = [lt|#{declName}(#{declName}&&) = default;|]
 
             -- define ctor to perform member-by-member move and--if
             -- needed--initialize meta fields
@@ -285,9 +289,9 @@             initList = initializeList
                 (optional baseMove structBase)
                 (commaLineSep 3 fieldMove structFields)
-            baseMove b = [lt|#{cppType b}(std::move(other))|]
-            fieldMove Field {..} = [lt|#{fieldName}(std::move(other.#{fieldName}))|]
-            param = if initList == mempty then mempty else [lt| other|]
+            baseMove b = [lt|#{cppType b}(std::move(#{otherParamName}))|]
+            fieldMove Field {..} = [lt|#{fieldName}(std::move(#{otherParamName}.#{fieldName}))|]
+            param = if initList == mempty then mempty else ' ':otherParamName
 
         -- operator=
         assignmentOp = if hasMetaFields then define else implicitlyDeclared
@@ -295,12 +299,12 @@             -- default OK when there are no meta fields
             implicitlyDeclared = CPP.ifndef CPP.defaultedFunctions [lt|
         // Compiler generated operator= OK
-        #{declName}& operator=(const #{declName}& other) = default;|]
+        #{declName}& operator=(const #{declName}&) = default;|]
 
             -- define operator= using swap
-            define = [lt|#{declName}& operator=(const #{declName}& other)
+            define = [lt|#{declName}& operator=(const #{declName}& #{otherParamName})
         {
-            #{declName}(other).swap(*this);
+            #{declName}(#{otherParamName}).swap(*this);
             return *this;
         }|]
 
@@ -308,17 +312,20 @@         {#{newlineBeginSep 3 id [baseInit, nameInit, qualifiedInit]}
         }|]
           where
-            nameParam = if baseInit == mempty && nameInit == mempty then mempty else [lt| name|]
-            qualifiedNameParam = if baseInit == mempty && qualifiedInit == mempty then mempty else [lt| qualified_name|]
-            baseInit = optional (\b -> [lt|#{cppType b}::InitMetadata(name, qualified_name);|]) structBase
+            nameParam = if baseInit == mempty && nameInit == mempty then mempty else uniqueName "name" fieldNames
+            qualifiedNameParam = if baseInit == mempty && qualifiedInit == mempty then mempty else uniqueName "qual_name" fieldNames
+            baseInit = optional (\b -> [lt|#{cppType b}::InitMetadata(#{nameParam}, #{qualifiedNameParam});|]) structBase
             nameInit = newlineSep 3 init' structFields
               where
-                init' Field {fieldType = BT_MetaName, ..} = [lt|this->#{fieldName} = name;|]
+                init' Field {fieldType = BT_MetaName, ..} = [lt|this->#{fieldName} = #{nameParam};|]
                 init' _ = mempty
             qualifiedInit = newlineSep 3 init' structFields
               where
-                init' Field {fieldType = BT_MetaFullName, ..} = [lt|this->#{fieldName} = qualified_name;|]
+                init' Field {fieldType = BT_MetaFullName, ..} = [lt|this->#{fieldName} = #{qualifiedNameParam};|]
                 init' _ = mempty
+
+        leftParamName = uniqueName "left" fieldNames
+        rightParamName = uniqueName "right" fieldNames
 
     -- enum definition and helpers
     typeDeclaration e@Enum {..} = [lt|
+ src/Language/Bond/Codegen/Cs/Grpc_cs.hs view
@@ -0,0 +1,146 @@+-- 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.Cs.Grpc_cs (
+  grpc_cs)
+where
+
+import Data.Maybe
+import Data.Monoid
+import Prelude
+import qualified Data.Text.Lazy as L
+import Text.Shakespeare.Text
+import Language.Bond.Syntax.Types
+import Language.Bond.Util
+import Language.Bond.Codegen.Util
+import Language.Bond.Codegen.TypeMapping
+import qualified Language.Bond.Codegen.Cs.Util as CS
+
+-- | Codegen template for generating code containing declarations of
+-- of services including proxies and services files for gRPC integration.
+
+grpc_cs :: MappingContext -> String -> [Import] -> [Declaration] -> (String, L.Text)
+grpc_cs cs _ _ declarations = ("_grpc.cs", [lt|
+#{CS.disableCscWarnings}
+#{CS.disableReSharperWarnings}
+
+namespace #{csNamespace}
+{
+    #{doubleLineSep 1 grpc declarations}
+} // #{csNamespace}
+|])
+  where
+    csNamespace = sepBy "." toText $ getNamespace cs
+    idl = MappingContext idlTypeMapping [] [] []
+
+    grpc s@Service{..} = [lt|#{CS.typeAttributes cs s}public static class #{declName}#{generics} #{genericsWhere}
+    {
+        static readonly string ServiceName = "#{getDeclTypeName idl s}";
+
+        #{doubleLineSep 2 methodDeclaration serviceMethods}
+
+        public abstract class #{declName}Base
+        {
+            #{doubleLineSep 3 serverBaseMethods serviceMethods}
+        }
+
+        public class #{declName}Client : global::Grpc.Core.ClientBase<#{declName}Client>
+        {
+            public #{declName}Client(global::Grpc.Core.Channel channel) : base(channel)
+            {
+            }
+
+            protected #{declName}Client() : base()
+            {
+            }
+
+            protected #{declName}Client(global::Grpc.Core.ClientBase.ClientBaseConfiguration configuration) : base(configuration)
+            {
+            }
+
+            #{doubleLineSep 3 clientMethods serviceMethods}
+
+            protected override #{declName}Client NewInstance(global::Grpc.Core.ClientBase.ClientBaseConfiguration configuration)
+            {
+                return new #{declName}Client(configuration);
+            }
+        }
+
+        public static global::Grpc.Core.ServerServiceDefinition BindService(#{declName}Base serviceImpl)
+        {
+            return global::Grpc.Core.ServerServiceDefinition.CreateBuilder()
+                    #{newlineSep 5 serviceMethodBuilder serviceMethods}
+                    .Build();
+        }
+    }
+|]
+      where
+        methodNames = map methodName serviceMethods
+
+        uniqImplName name = uniqueName (name ++ "_impl") methodNames
+
+        getMessageTypeName t = maybe "global::Bond.Void" (getTypeName cs) t
+
+        generics = angles $ sepBy ", " paramName declParams
+
+        genericsWhere = sepBy " " addWhere declParams
+          where
+            addWhere a = [lt|where #{paramName a} : class|]
+
+        methodDeclaration Function{..} = [lt|static readonly global::Grpc.Core.Method<global::Bond.Grpc.IMessage<#{getMessageTypeName methodInput}>, global::Bond.Grpc.IMessage<#{getMessageTypeName methodResult}>> Method_#{methodName} = new global::Grpc.Core.Method<global::Bond.Grpc.IMessage<#{getMessageTypeName methodInput}>, global::Bond.Grpc.IMessage<#{getMessageTypeName methodResult}>>(
+            global::Grpc.Core.MethodType.Unary,
+            ServiceName,
+            "#{methodName}",
+            global::Bond.Grpc.Marshaller<#{getMessageTypeName methodInput}>.Instance,
+            global::Bond.Grpc.Marshaller<#{getMessageTypeName methodResult}>.Instance);|]
+
+        methodDeclaration Event{..} = [lt|static readonly global::Grpc.Core.Method<global::Bond.Grpc.IMessage<#{getMessageTypeName methodInput}>, global::Bond.Grpc.IMessage<#{getMessageTypeName Nothing}>> Method_#{methodName} = new global::Grpc.Core.Method<global::Bond.Grpc.IMessage<#{getMessageTypeName methodInput}>, global::Bond.Grpc.IMessage<#{getMessageTypeName Nothing}>>(
+            global::Grpc.Core.MethodType.Unary,
+            ServiceName,
+            "#{methodName}",
+            global::Bond.Grpc.Marshaller<#{getMessageTypeName methodInput}>.Instance,
+            global::Bond.Grpc.Marshaller<#{getMessageTypeName Nothing}>.Instance);|]
+
+        serverBaseMethods Function{..} = [lt|public abstract global::System.Threading.Tasks.Task<global::Bond.Grpc.IMessage<#{getMessageTypeName methodResult}>> #{methodName}(global::Bond.Grpc.IMessage<#{getMessageTypeName methodInput}> request, global::Grpc.Core.ServerCallContext context);|]
+
+        serverBaseMethods Event{..} = [lt|public abstract global::System.Threading.Tasks.Task #{methodName}(global::Bond.Grpc.IMessage<#{getMessageTypeName methodInput}> request, global::Grpc.Core.ServerCallContext context);
+
+            internal global::System.Threading.Tasks.Task<global::Bond.Grpc.IMessage<#{getMessageTypeName Nothing}>> #{uniqImplName methodName}(global::Bond.Grpc.IMessage<#{getMessageTypeName methodInput}> request, global::Grpc.Core.ServerCallContext context) {
+                return global::Bond.Grpc.Internal.NothingCallHandler.Handle(#{methodName}, request, context);
+            }|]
+
+        firstParam Nothing = mempty
+        firstParam t = [lt|#{getMessageTypeName t} request, |]
+
+        requestOrVoidConstructor Nothing = [lt|global::Bond.Grpc.Message.Void|]
+        requestOrVoidConstructor _ = [lt|global::Bond.Grpc.Message.From(request)|]
+
+        clientMethods Function{..} = [lt|public virtual global::Grpc.Core.AsyncUnaryCall<global::Bond.Grpc.IMessage<#{getMessageTypeName methodResult}>> #{methodName}Async(#{firstParam methodInput}global::Grpc.Core.Metadata headers = null, global::System.DateTime? deadline = null, global::System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken))
+            {
+                var message = #{requestOrVoidConstructor methodInput};
+                return #{methodName}Async(message, new global::Grpc.Core.CallOptions(headers, deadline, cancellationToken));
+            }
+
+            public virtual global::Grpc.Core.AsyncUnaryCall<global::Bond.Grpc.IMessage<#{getMessageTypeName methodResult}>> #{methodName}Async(global::Bond.Grpc.IMessage<#{getMessageTypeName methodInput}> request, global::Grpc.Core.CallOptions options)
+            {
+                return CallInvoker.AsyncUnaryCall(Method_#{methodName}, null, options, request);
+            }|]
+
+        clientMethods Event{..} = [lt|public virtual void #{methodName}Async(#{firstParam methodInput}global::Grpc.Core.Metadata headers = null, global::System.DateTime? deadline = null, global::System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken))
+            {
+                var message = #{requestOrVoidConstructor methodInput};
+                #{methodName}Async(message, new global::Grpc.Core.CallOptions(headers, deadline, cancellationToken));
+            }
+
+            public virtual void #{methodName}Async(global::Bond.Grpc.IMessage<#{getMessageTypeName methodInput}> request, global::Grpc.Core.CallOptions options)
+            {
+                global::Bond.Grpc.Internal.NothingCallInvoker.NothingCall(CallInvoker, Method_#{methodName}, null, options, request);
+            }|]
+
+        serviceMethodBuilder Function{..} = [lt|.AddMethod(Method_#{methodName}, serviceImpl.#{methodName})|]
+
+        serviceMethodBuilder Event{..} = [lt|.AddMethod(Method_#{methodName}, serviceImpl.#{uniqImplName methodName})|]
+
+    grpc _ = mempty
src/Language/Bond/Codegen/Templates.hs view
@@ -51,6 +51,7 @@     , comm_interface_cs
     , comm_proxy_cs
     , comm_service_cs
+    , grpc_cs
     )
     where
 
@@ -65,6 +66,7 @@ import Language.Bond.Codegen.Cpp.Comm_h
 import Language.Bond.Codegen.Cs.Types_cs
 import Language.Bond.Codegen.Cs.Comm_cs
+import Language.Bond.Codegen.Cs.Grpc_cs
 -- redundant imports for haddock
 import Language.Bond.Codegen.TypeMapping
 import Language.Bond.Syntax.Types
src/Language/Bond/Codegen/Util.hs view
@@ -11,8 +11,8 @@ Stability   : provisional
 Portability : portable
 
-Helper functions for creating common text structures useful in code generation.
-These functions operate on 'Text' objects.
+Helper functions for creating common structures useful in code generation.
+These functions often operate on 'Text' objects.
 -}
 
 module Language.Bond.Codegen.Util
@@ -23,6 +23,7 @@     , newlineBeginSep
     , doubleLineSep
     , doubleLineSepEnd
+    , uniqueName
     ) where
 
 import Data.Int (Int64)
@@ -31,7 +32,7 @@ import Data.Text.Lazy (Text, justifyRight)
 import Text.Shakespeare.Text
 import Paths_bond (version)
-import Data.Version (showVersion) 
+import Data.Version (showVersion)
 import Language.Bond.Util
 
 instance ToText Word16 where
@@ -59,7 +60,7 @@ 
 #{indent n}|]
 
-newlineSep, commaLineSep, newlineSepEnd, newlineBeginSep, doubleLineSep, doubleLineSepEnd 
+newlineSep, commaLineSep, newlineSepEnd, newlineBeginSep, doubleLineSep, doubleLineSepEnd
     :: Int64 -> (a -> Text) -> [a] -> Text
 
 -- | Separates elements of a list with new lines. Starts new lines at the
@@ -102,3 +103,12 @@ #{c}------------------------------------------------------------------------------
 |]
 
+-- | Given an intended name and a list of already taken names, returns a
+-- unique name. Assumes that it's legal to appen digits to the end of the
+-- intended name.
+uniqueName :: String -> [String] -> String
+uniqueName baseName taken = go baseName (0::Integer)
+  where go name counter
+          | not (name `elem` taken) = name
+          | otherwise = go newName (counter + 1)
+                        where newName = baseName ++ (show counter)
src/Language/Bond/Parser.hs view
@@ -65,7 +65,7 @@  -> String                              -- ^ content of a schema file to parse
  -> FilePath                            -- ^ path of the file being parsed, used to resolve relative import paths
  -> ImportResolver                      -- ^ function to resolve and load imported files
- -> IO (Either ParseError Bond)         -- ^ function returns 'Bond' which represents the parsed abstract syntax tree 
+ -> IO (Either ParseError Bond)         -- ^ function returns 'Bond' which represents the parsed abstract syntax tree
                                         --   or 'ParserError' if parsing failed
 parseBond s c f r = runReaderT (runParserT bond (Symbols [] []) s c) (Environment [] [] f r)
 
@@ -240,12 +240,7 @@             [] -> return fields'
             Field {..}:_ -> fail $ "Duplicate definition of the field with ordinal " ++ show fieldOrdinal ++
                 " and name " ++ show fieldName
-      where
-        findDuplicatesBy accessor xs = deleteFirstsBy ((==) `on` accessor) xs (nubBy ((==) `on` accessor) xs)
 
-manySortedBy :: (a -> a -> Ordering) -> ParsecT s u m a -> ParsecT s u m [a]
-manySortedBy = manyAccum . insertBy
-
 -- field definition parser
 field :: Parser Field
 field = do
@@ -282,36 +277,6 @@                                         then Right $ Field a o m t n d
                                         else Left "Invalid default value for field"
 
--- default type validator (type checking, out-of-range, enforce default type)
-validDefaultType :: Type -> Maybe Default -> Bool
-validDefaultType (BT_UserDefined a@Alias {} args) d = validDefaultType (resolveAlias a args) d
-validDefaultType _ Nothing = True
-validDefaultType bondType (Just defaultValue) = validDefaultType' bondType defaultValue
-  where validDefaultType' :: Type -> Default -> Bool
-        validDefaultType' BT_Int8    (DefaultInteger i) = isInBounds i (0::Int8)
-        validDefaultType' BT_Int16   (DefaultInteger i) = isInBounds i (0::Int16)
-        validDefaultType' BT_Int32   (DefaultInteger i) = isInBounds i (0::Int32)
-        validDefaultType' BT_Int64   (DefaultInteger i) = isInBounds i (0::Int64)
-        validDefaultType' BT_UInt8   (DefaultInteger i) = isInBounds i (0::Word8)
-        validDefaultType' BT_UInt16  (DefaultInteger i) = isInBounds i (0::Word16)
-        validDefaultType' BT_UInt32  (DefaultInteger i) = isInBounds i (0::Word32)
-        validDefaultType' BT_UInt64  (DefaultInteger i) = isInBounds i (0::Word64)
-        validDefaultType' BT_Float   (DefaultFloat _)   = True
-        validDefaultType' BT_Float   (DefaultInteger _) = True
-        validDefaultType' BT_Double  (DefaultFloat _)   = True
-        validDefaultType' BT_Double  (DefaultInteger _) = True
-        validDefaultType' BT_Bool    (DefaultBool _)    = True
-        validDefaultType' BT_String  (DefaultString _)  = True
-        validDefaultType' BT_WString (DefaultString _)  = True
-        validDefaultType' (BT_UserDefined Enum {} _) (DefaultEnum _) = True
-        validDefaultType' (BT_TypeParam {}) _           = True
-        validDefaultType' _ _                           = False
-
--- checks whether an Integer is within the bounds of some other Integral and Bounded type.
--- The value of the second paramater is never used: only its type is used.
-isInBounds :: forall a. (Integral a, Bounded a) => Integer -> a -> Bool
-isInBounds value _ = value >= (toInteger (minBound :: a)) && value <= (toInteger (maxBound :: a))
-
 -- enum definition parser
 enum :: Parser Declaration
 enum = Enum <$> asks currentNamespaces <*> attributes <*> name <*> consts <* optional semi <?> "enum definition"
@@ -338,7 +303,6 @@     <|> keyword "string" *> pure BT_String
     <|> keyword "bool" *> pure BT_Bool
 
-
 -- containers parser
 complexType :: Parser Type
 complexType =
@@ -354,7 +318,6 @@     userStruct = try (checkUserType isStruct) <?> "user defined struct"
     isValidKeyType t = isScalar t || isString t
 
-
 -- parser for user defined type (struct, enum, alias or type parameter)
 userType :: Parser Type
 userType = do
@@ -389,7 +352,6 @@     isParam [name] = (name ==) . paramName
     isParam _      = const False
 
-
 -- type parser
 type_ :: Parser Type
 type_ = basicType <|> complexType <|> userType
@@ -410,7 +372,13 @@     local (with params) $ Service namespaces attr name params <$> methods <* optional semi
   where
     with params e = e { currentParams = params }
-    methods = braces $ semiEnd (try event <|> try function)
+    methods = unique $ braces $ semiEnd (try event <|> try function)
+    unique p = do
+        methods' <- p
+        case findDuplicatesBy methodName methods' of
+            [] -> return methods'
+            Function {..}:_ -> fail $ "Duplicate definition of the function with name " ++ show methodName
+            Event {..}:_ -> fail $ "Duplicate definition of the event with name " ++ show methodName
 
 function :: Parser Method
 function = Function <$> attributes <*> payload <*> identifier <*> input
@@ -431,6 +399,8 @@     void_ = keyword "void" *> pure Nothing
     userStruct = try (checkUserType isStruct) <?> "user defined struct"
 
+-- helper methods
+
 checkUserType :: (Type -> Bool) -> Parser Type
 checkUserType check = do
     t <- userType
@@ -439,4 +409,40 @@     valid t = case t of
         BT_TypeParam _ -> True
         _ -> check t
+
+findDuplicatesBy :: (Eq b) => (a -> b) -> [a] -> [a]
+findDuplicatesBy accessor xs = deleteFirstsBy ((==) `on` accessor) xs (nubBy ((==) `on` accessor) xs)
+
+manySortedBy :: (a -> a -> Ordering) -> ParsecT s u m a -> ParsecT s u m [a]
+manySortedBy = manyAccum . insertBy
+
+-- default type validator (type checking, out-of-range, enforce default type)
+validDefaultType :: Type -> Maybe Default -> Bool
+validDefaultType (BT_UserDefined a@Alias {} args) d = validDefaultType (resolveAlias a args) d
+validDefaultType _ Nothing = True
+validDefaultType bondType (Just defaultValue) = validDefaultType' bondType defaultValue
+  where validDefaultType' :: Type -> Default -> Bool
+        validDefaultType' BT_Int8    (DefaultInteger i) = isInBounds i (0::Int8)
+        validDefaultType' BT_Int16   (DefaultInteger i) = isInBounds i (0::Int16)
+        validDefaultType' BT_Int32   (DefaultInteger i) = isInBounds i (0::Int32)
+        validDefaultType' BT_Int64   (DefaultInteger i) = isInBounds i (0::Int64)
+        validDefaultType' BT_UInt8   (DefaultInteger i) = isInBounds i (0::Word8)
+        validDefaultType' BT_UInt16  (DefaultInteger i) = isInBounds i (0::Word16)
+        validDefaultType' BT_UInt32  (DefaultInteger i) = isInBounds i (0::Word32)
+        validDefaultType' BT_UInt64  (DefaultInteger i) = isInBounds i (0::Word64)
+        validDefaultType' BT_Float   (DefaultFloat _)   = True
+        validDefaultType' BT_Float   (DefaultInteger _) = True
+        validDefaultType' BT_Double  (DefaultFloat _)   = True
+        validDefaultType' BT_Double  (DefaultInteger _) = True
+        validDefaultType' BT_Bool    (DefaultBool _)    = True
+        validDefaultType' BT_String  (DefaultString _)  = True
+        validDefaultType' BT_WString (DefaultString _)  = True
+        validDefaultType' (BT_UserDefined Enum {} _) (DefaultEnum _) = True
+        validDefaultType' (BT_TypeParam {}) _           = True
+        validDefaultType' _ _                           = False
+
+-- checks whether an Integer is within the bounds of some other Integral and Bounded type.
+-- The value of the second paramater is never used: only its type is used.
+isInBounds :: forall a. (Integral a, Bounded a) => Integer -> a -> Bool
+isInBounds value _ = value >= (toInteger (minBound :: a)) && value <= (toInteger (maxBound :: a))
 
tests/Main.hs view
@@ -6,6 +6,7 @@ import Test.Tasty.HUnit (testCase)
 import Tests.Syntax
 import Tests.Codegen
+import Tests.Codegen.Util(utilTestGroup)
 
 tests :: TestTree
 tests = testGroup "Compiler tests"
@@ -52,9 +53,11 @@         , 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"
-        [ testGroup "C++"
+        [ utilTestGroup,
+          testGroup "C++"
             [ verifyCppCodegen "attributes"
             , verifyCppCodegen "basic_types"
             , verifyCppCodegen "bond_meta"
@@ -67,6 +70,11 @@             , verifyCppCodegen "aliases"
             , verifyCppCodegen "alias_key"
             , verifyCppCodegen "maybe_blob"
+            , verifyCodegen
+                [ "c++"
+                , "--enum-header"
+                ]
+                "with_enum_header"
             , verifyCodegen
                 [ "c++"
                 , "--allocator=arena"
tests/Tests/Codegen.hs view
@@ -55,12 +55,13 @@         , apply_cpp protocols
         ]
     protocols =
-        [ Protocol "bond::CompactBinaryReader<bond::InputBuffer>"
-                   "bond::CompactBinaryWriter<bond::OutputBuffer>"
-        , Protocol "bond::FastBinaryReader<bond::InputBuffer>"
-                   "bond::FastBinaryWriter<bond::OutputBuffer>"
-        , Protocol "bond::SimpleBinaryReader<bond::InputBuffer>"
-                   "bond::SimpleBinaryWriter<bond::OutputBuffer>"
+        [ ProtocolReader "bond::CompactBinaryReader<bond::InputBuffer>"
+        , ProtocolWriter "bond::CompactBinaryWriter<bond::OutputBuffer>"
+        , ProtocolWriter "bond::CompactBinaryWriter<bond::OutputCounter>"
+        , ProtocolReader "bond::FastBinaryReader<bond::InputBuffer>"
+        , ProtocolWriter "bond::FastBinaryWriter<bond::OutputBuffer>"
+        , ProtocolReader "bond::SimpleBinaryReader<bond::InputBuffer>"
+        , ProtocolWriter "bond::SimpleBinaryWriter<bond::OutputBuffer>"
         ]
 
 verifyExportsCodegen :: [String] -> FilePath -> TestTree
@@ -93,6 +94,7 @@             [ comm_interface_cs
             , comm_proxy_cs
             , comm_service_cs
+            , grpc_cs
             , types_cs Class (fieldMapping (processOptions args))
             ]
   where
@@ -121,7 +123,8 @@         , types_cpp
         , comm_cpp
         , types_h header enum_header allocator
-        ]
+        ] <>
+        [ enum_h | enum_header]
     templates Cs {..} =
         [ types_cs Class $ fieldMapping options
         ]
+ tests/Tests/Codegen/Util.hs view
@@ -0,0 +1,20 @@+-- Copyright (c) Microsoft. All rights reserved.
+-- Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+{-# OPTIONS_GHC -fno-warn-missing-signatures #-}
+
+module Tests.Codegen.Util
+    (
+      utilTestGroup
+    ) where
+
+import Language.Bond.Codegen.Util(uniqueName)
+import Test.Tasty
+import Test.Tasty.QuickCheck
+
+utilTestGroup = testGroup "Codegen Utils"
+  [ testGroup "uniqueName" [ testProperty "unique when taken" prop_collisionReturnsNotSame,
+                             testProperty "given when not taken" prop_noCollisionReturnsSame]]
+
+prop_collisionReturnsNotSame xs = not (null xs) ==> uniqueName (head xs) xs /= head xs
+prop_noCollisionReturnsSame xs = not ("some" `elem` xs) ==> uniqueName "some" xs == "some"