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 +18/−10
- Options.hs +6/−0
- bond.cabal +4/−2
- src/Language/Bond/Codegen/Cpp/ApplyOverloads.hs +19/−15
- src/Language/Bond/Codegen/Cpp/Enum_h.hs +2/−0
- src/Language/Bond/Codegen/Cpp/Types_h.hs +28/−21
- src/Language/Bond/Codegen/Cs/Grpc_cs.hs +146/−0
- src/Language/Bond/Codegen/Templates.hs +2/−0
- src/Language/Bond/Codegen/Util.hs +14/−4
- src/Language/Bond/Parser.hs +46/−40
- tests/Main.hs +9/−1
- tests/Tests/Codegen.hs +10/−7
- tests/Tests/Codegen/Util.hs +20/−0
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"