| // Copyright 2013 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import 'ast.dart'; |
| import 'functional.dart'; |
| import 'generator.dart'; |
| import 'generator_tools.dart'; |
| import 'pigeon_lib.dart' show Error; |
| |
| /// General comment opening token. |
| const String _commentPrefix = '//'; |
| |
| const String _voidType = 'void'; |
| |
| /// Documentation comment spec. |
| const DocumentCommentSpecification _docCommentSpec = |
| DocumentCommentSpecification(_commentPrefix); |
| |
| /// The default serializer for Flutter. |
| const String _defaultCodecSerializer = 'flutter::StandardCodecSerializer'; |
| |
| /// Options that control how C++ code will be generated. |
| class CppOptions { |
| /// Creates a [CppOptions] object |
| const CppOptions({ |
| this.headerIncludePath, |
| this.namespace, |
| this.copyrightHeader, |
| this.headerOutPath, |
| }); |
| |
| /// The path to the header that will get placed in the source filed (example: |
| /// "foo.h"). |
| final String? headerIncludePath; |
| |
| /// The namespace where the generated class will live. |
| final String? namespace; |
| |
| /// A copyright header that will get prepended to generated code. |
| final Iterable<String>? copyrightHeader; |
| |
| /// The path to the output header file location. |
| final String? headerOutPath; |
| |
| /// Creates a [CppOptions] from a Map representation where: |
| /// `x = CppOptions.fromMap(x.toMap())`. |
| static CppOptions fromMap(Map<String, Object> map) { |
| return CppOptions( |
| headerIncludePath: map['header'] as String?, |
| namespace: map['namespace'] as String?, |
| copyrightHeader: map['copyrightHeader'] as Iterable<String>?, |
| headerOutPath: map['cppHeaderOut'] as String?, |
| ); |
| } |
| |
| /// Converts a [CppOptions] to a Map representation where: |
| /// `x = CppOptions.fromMap(x.toMap())`. |
| Map<String, Object> toMap() { |
| final Map<String, Object> result = <String, Object>{ |
| if (headerIncludePath != null) 'header': headerIncludePath!, |
| if (namespace != null) 'namespace': namespace!, |
| if (copyrightHeader != null) 'copyrightHeader': copyrightHeader!, |
| }; |
| return result; |
| } |
| |
| /// Overrides any non-null parameters from [options] into this to make a new |
| /// [CppOptions]. |
| CppOptions merge(CppOptions options) { |
| return CppOptions.fromMap(mergeMaps(toMap(), options.toMap())); |
| } |
| } |
| |
| /// Class that manages all Cpp code generation. |
| class CppGenerator extends Generator<OutputFileOptions<CppOptions>> { |
| /// Constructor. |
| const CppGenerator(); |
| |
| /// Generates C++ file of type specified in [generatorOptions] |
| @override |
| void generate( |
| OutputFileOptions<CppOptions> generatorOptions, |
| Root root, |
| StringSink sink, { |
| required String dartPackageName, |
| }) { |
| assert(generatorOptions.fileType == FileType.header || |
| generatorOptions.fileType == FileType.source); |
| if (generatorOptions.fileType == FileType.header) { |
| const CppHeaderGenerator().generate( |
| generatorOptions.languageOptions, |
| root, |
| sink, |
| dartPackageName: dartPackageName, |
| ); |
| } else if (generatorOptions.fileType == FileType.source) { |
| const CppSourceGenerator().generate( |
| generatorOptions.languageOptions, |
| root, |
| sink, |
| dartPackageName: dartPackageName, |
| ); |
| } |
| } |
| } |
| |
| /// Writes C++ header (.h) file to sink. |
| class CppHeaderGenerator extends StructuredGenerator<CppOptions> { |
| /// Constructor. |
| const CppHeaderGenerator(); |
| |
| @override |
| void writeFilePrologue( |
| CppOptions generatorOptions, |
| Root root, |
| Indent indent, { |
| required String dartPackageName, |
| }) { |
| if (generatorOptions.copyrightHeader != null) { |
| addLines(indent, generatorOptions.copyrightHeader!, linePrefix: '// '); |
| } |
| indent.writeln('$_commentPrefix ${getGeneratedCodeWarning()}'); |
| indent.writeln('$_commentPrefix $seeAlsoWarning'); |
| indent.newln(); |
| } |
| |
| @override |
| void writeFileImports( |
| CppOptions generatorOptions, |
| Root root, |
| Indent indent, { |
| required String dartPackageName, |
| }) { |
| final String guardName = _getGuardName(generatorOptions.headerIncludePath); |
| indent.writeln('#ifndef $guardName'); |
| indent.writeln('#define $guardName'); |
| |
| _writeSystemHeaderIncludeBlock(indent, <String>[ |
| 'flutter/basic_message_channel.h', |
| 'flutter/binary_messenger.h', |
| 'flutter/encodable_value.h', |
| 'flutter/standard_message_codec.h', |
| ]); |
| indent.newln(); |
| _writeSystemHeaderIncludeBlock(indent, <String>[ |
| 'map', |
| 'string', |
| 'optional', |
| ]); |
| indent.newln(); |
| if (generatorOptions.namespace != null) { |
| indent.writeln('namespace ${generatorOptions.namespace} {'); |
| } |
| indent.newln(); |
| if (generatorOptions.namespace?.endsWith('_pigeontest') ?? false) { |
| final String testFixtureClass = |
| '${_pascalCaseFromSnakeCase(generatorOptions.namespace!.replaceAll('_pigeontest', ''))}Test'; |
| indent.writeln('class $testFixtureClass;'); |
| } |
| indent.newln(); |
| indent.writeln('$_commentPrefix Generated class from Pigeon.'); |
| } |
| |
| @override |
| void writeEnum( |
| CppOptions generatorOptions, |
| Root root, |
| Indent indent, |
| Enum anEnum, { |
| required String dartPackageName, |
| }) { |
| indent.newln(); |
| addDocumentationComments( |
| indent, anEnum.documentationComments, _docCommentSpec); |
| indent.write('enum class ${anEnum.name} '); |
| indent.addScoped('{', '};', () { |
| enumerate(anEnum.members, (int index, final EnumMember member) { |
| addDocumentationComments( |
| indent, member.documentationComments, _docCommentSpec); |
| indent.writeln( |
| '${member.name} = $index${index == anEnum.members.length - 1 ? '' : ','}'); |
| }); |
| }); |
| } |
| |
| @override |
| void writeGeneralUtilities( |
| CppOptions generatorOptions, |
| Root root, |
| Indent indent, { |
| required String dartPackageName, |
| }) { |
| _writeErrorOr(indent, friends: root.apis.map((Api api) => api.name)); |
| } |
| |
| @override |
| void writeDataClass( |
| CppOptions generatorOptions, |
| Root root, |
| Indent indent, |
| Class klass, { |
| required String dartPackageName, |
| }) { |
| // When generating for a Pigeon unit test, add a test fixture friend class to |
| // allow unit testing private methods, since testing serialization via public |
| // methods is essentially an end-to-end test. |
| String? testFixtureClass; |
| if (generatorOptions.namespace?.endsWith('_pigeontest') ?? false) { |
| testFixtureClass = |
| '${_pascalCaseFromSnakeCase(generatorOptions.namespace!.replaceAll('_pigeontest', ''))}Test'; |
| } |
| indent.newln(); |
| |
| const List<String> generatedMessages = <String>[ |
| ' Generated class from Pigeon that represents data sent in messages.' |
| ]; |
| |
| addDocumentationComments( |
| indent, klass.documentationComments, _docCommentSpec, |
| generatorComments: generatedMessages); |
| |
| final Iterable<NamedType> orderedFields = |
| getFieldsInSerializationOrder(klass); |
| |
| indent.write('class ${klass.name} '); |
| indent.addScoped('{', '};', () { |
| _writeAccessBlock(indent, _ClassAccess.public, () { |
| final Iterable<NamedType> requiredFields = |
| orderedFields.where((NamedType type) => !type.type.isNullable); |
| // Minimal constructor, if needed. |
| if (requiredFields.length != orderedFields.length) { |
| _writeClassConstructor(root, indent, klass, requiredFields, |
| 'Constructs an object setting all non-nullable fields.'); |
| } |
| // All-field constructor. |
| _writeClassConstructor(root, indent, klass, orderedFields, |
| 'Constructs an object setting all fields.'); |
| |
| for (final NamedType field in orderedFields) { |
| addDocumentationComments( |
| indent, field.documentationComments, _docCommentSpec); |
| final HostDatatype baseDatatype = getFieldHostDatatype( |
| field, root.classes, root.enums, _baseCppTypeForBuiltinDartType); |
| // Declare a getter and setter. |
| _writeFunctionDeclaration(indent, _makeGetterName(field), |
| returnType: _getterReturnType(baseDatatype), isConst: true); |
| final String setterName = _makeSetterName(field); |
| _writeFunctionDeclaration(indent, setterName, |
| returnType: _voidType, |
| parameters: <String>[ |
| '${_unownedArgumentType(baseDatatype)} value_arg' |
| ]); |
| if (field.type.isNullable) { |
| // Add a second setter that takes the non-nullable version of the |
| // argument for convenience, since setting literal values with the |
| // pointer version is non-trivial. |
| final HostDatatype nonNullType = _nonNullableType(baseDatatype); |
| _writeFunctionDeclaration(indent, setterName, |
| returnType: _voidType, |
| parameters: <String>[ |
| '${_unownedArgumentType(nonNullType)} value_arg' |
| ]); |
| } |
| indent.newln(); |
| } |
| }); |
| |
| _writeAccessBlock(indent, _ClassAccess.private, () { |
| _writeFunctionDeclaration(indent, 'FromEncodableList', |
| returnType: klass.name, |
| parameters: <String>['const flutter::EncodableList& list'], |
| isStatic: true); |
| _writeFunctionDeclaration(indent, 'ToEncodableList', |
| returnType: 'flutter::EncodableList', isConst: true); |
| for (final Class friend in root.classes) { |
| if (friend != klass && |
| friend.fields.any( |
| (NamedType element) => element.type.baseName == klass.name)) { |
| indent.writeln('friend class ${friend.name};'); |
| } |
| } |
| for (final Api api in root.apis) { |
| // TODO(gaaclarke): Find a way to be more precise with our |
| // friendships. |
| indent.writeln('friend class ${api.name};'); |
| indent.writeln('friend class ${_getCodecSerializerName(api)};'); |
| } |
| if (testFixtureClass != null) { |
| indent.writeln('friend class $testFixtureClass;'); |
| } |
| |
| for (final NamedType field in orderedFields) { |
| final HostDatatype hostDatatype = getFieldHostDatatype( |
| field, root.classes, root.enums, _baseCppTypeForBuiltinDartType); |
| indent.writeln( |
| '${_valueType(hostDatatype)} ${_makeInstanceVariableName(field)};'); |
| } |
| }); |
| }, nestCount: 0); |
| indent.newln(); |
| } |
| |
| @override |
| void writeFlutterApi( |
| CppOptions generatorOptions, |
| Root root, |
| Indent indent, |
| Api api, { |
| required String dartPackageName, |
| }) { |
| assert(api.location == ApiLocation.flutter); |
| if (getCodecClasses(api, root).isNotEmpty) { |
| _writeCodec(generatorOptions, root, indent, api); |
| } |
| const List<String> generatedMessages = <String>[ |
| ' Generated class from Pigeon that represents Flutter messages that can be called from C++.' |
| ]; |
| addDocumentationComments(indent, api.documentationComments, _docCommentSpec, |
| generatorComments: generatedMessages); |
| indent.write('class ${api.name} '); |
| indent.addScoped('{', '};', () { |
| _writeAccessBlock(indent, _ClassAccess.public, () { |
| _writeFunctionDeclaration(indent, api.name, |
| parameters: <String>['flutter::BinaryMessenger* binary_messenger']); |
| _writeFunctionDeclaration(indent, 'GetCodec', |
| returnType: 'const flutter::StandardMessageCodec&', isStatic: true); |
| for (final Method func in api.methods) { |
| final HostDatatype returnType = getHostDatatype(func.returnType, |
| root.classes, root.enums, _baseCppTypeForBuiltinDartType); |
| addDocumentationComments( |
| indent, func.documentationComments, _docCommentSpec); |
| |
| final Iterable<String> argTypes = func.arguments.map((NamedType arg) { |
| final HostDatatype hostType = getFieldHostDatatype( |
| arg, root.classes, root.enums, _baseCppTypeForBuiltinDartType); |
| return _flutterApiArgumentType(hostType); |
| }); |
| final Iterable<String> argNames = |
| indexMap(func.arguments, _getArgumentName); |
| final List<String> parameters = <String>[ |
| ...map2(argTypes, argNames, (String x, String y) => '$x $y'), |
| ..._flutterApiCallbackParameters(returnType), |
| ]; |
| _writeFunctionDeclaration(indent, _makeMethodName(func), |
| returnType: _voidType, parameters: parameters); |
| } |
| }); |
| indent.addScoped(' private:', null, () { |
| indent.writeln('flutter::BinaryMessenger* binary_messenger_;'); |
| }); |
| }, nestCount: 0); |
| indent.newln(); |
| } |
| |
| @override |
| void writeHostApi( |
| CppOptions generatorOptions, |
| Root root, |
| Indent indent, |
| Api api, { |
| required String dartPackageName, |
| }) { |
| assert(api.location == ApiLocation.host); |
| if (getCodecClasses(api, root).isNotEmpty) { |
| _writeCodec(generatorOptions, root, indent, api); |
| } |
| const List<String> generatedMessages = <String>[ |
| ' Generated interface from Pigeon that represents a handler of messages from Flutter.' |
| ]; |
| addDocumentationComments(indent, api.documentationComments, _docCommentSpec, |
| generatorComments: generatedMessages); |
| indent.write('class ${api.name} '); |
| indent.addScoped('{', '};', () { |
| _writeAccessBlock(indent, _ClassAccess.public, () { |
| // Prevent copying/assigning. |
| _writeFunctionDeclaration(indent, api.name, |
| parameters: <String>['const ${api.name}&'], deleted: true); |
| _writeFunctionDeclaration(indent, 'operator=', |
| returnType: '${api.name}&', |
| parameters: <String>['const ${api.name}&'], |
| deleted: true); |
| // No-op virtual destructor. |
| _writeFunctionDeclaration(indent, '~${api.name}', |
| isVirtual: true, inlineNoop: true); |
| for (final Method method in api.methods) { |
| final HostDatatype returnType = getHostDatatype(method.returnType, |
| root.classes, root.enums, _baseCppTypeForBuiltinDartType); |
| final String returnTypeName = _hostApiReturnType(returnType); |
| |
| final List<String> parameters = <String>[]; |
| if (method.arguments.isNotEmpty) { |
| final Iterable<String> argTypes = |
| method.arguments.map((NamedType arg) { |
| final HostDatatype hostType = getFieldHostDatatype(arg, |
| root.classes, root.enums, _baseCppTypeForBuiltinDartType); |
| return _hostApiArgumentType(hostType); |
| }); |
| final Iterable<String> argNames = |
| method.arguments.map((NamedType e) => _makeVariableName(e)); |
| parameters.addAll( |
| map2(argTypes, argNames, (String argType, String argName) { |
| return '$argType $argName'; |
| })); |
| } |
| |
| addDocumentationComments( |
| indent, method.documentationComments, _docCommentSpec); |
| final String methodReturn; |
| if (method.isAsynchronous) { |
| methodReturn = _voidType; |
| parameters.add('std::function<void($returnTypeName reply)> result'); |
| } else { |
| methodReturn = returnTypeName; |
| } |
| _writeFunctionDeclaration(indent, _makeMethodName(method), |
| returnType: methodReturn, |
| parameters: parameters, |
| isVirtual: true, |
| isPureVirtual: true); |
| } |
| indent.newln(); |
| indent.writeln('$_commentPrefix The codec used by ${api.name}.'); |
| _writeFunctionDeclaration(indent, 'GetCodec', |
| returnType: 'const flutter::StandardMessageCodec&', isStatic: true); |
| indent.writeln( |
| '$_commentPrefix Sets up an instance of `${api.name}` to handle messages through the `binary_messenger`.'); |
| _writeFunctionDeclaration(indent, 'SetUp', |
| returnType: _voidType, |
| isStatic: true, |
| parameters: <String>[ |
| 'flutter::BinaryMessenger* binary_messenger', |
| '${api.name}* api', |
| ]); |
| _writeFunctionDeclaration(indent, 'WrapError', |
| returnType: 'flutter::EncodableValue', |
| isStatic: true, |
| parameters: <String>['std::string_view error_message']); |
| _writeFunctionDeclaration(indent, 'WrapError', |
| returnType: 'flutter::EncodableValue', |
| isStatic: true, |
| parameters: <String>['const FlutterError& error']); |
| }); |
| _writeAccessBlock(indent, _ClassAccess.protected, () { |
| indent.writeln('${api.name}() = default;'); |
| }); |
| }, nestCount: 0); |
| } |
| |
| void _writeClassConstructor(Root root, Indent indent, Class klass, |
| Iterable<NamedType> params, String docComment) { |
| final List<String> paramStrings = params.map((NamedType param) { |
| final HostDatatype hostDatatype = getFieldHostDatatype( |
| param, root.classes, root.enums, _baseCppTypeForBuiltinDartType); |
| return '${_hostApiArgumentType(hostDatatype)} ${_makeVariableName(param)}'; |
| }).toList(); |
| indent.writeln('$_commentPrefix $docComment'); |
| _writeFunctionDeclaration(indent, klass.name, |
| isConstructor: true, parameters: paramStrings); |
| indent.newln(); |
| } |
| |
| void _writeCodec( |
| CppOptions generatorOptions, Root root, Indent indent, Api api) { |
| assert(getCodecClasses(api, root).isNotEmpty); |
| final String codeSerializerName = _getCodecSerializerName(api); |
| indent |
| .write('class $codeSerializerName : public $_defaultCodecSerializer '); |
| indent.addScoped('{', '};', () { |
| _writeAccessBlock(indent, _ClassAccess.public, () { |
| _writeFunctionDeclaration(indent, codeSerializerName, |
| isConstructor: true); |
| _writeFunctionDeclaration(indent, 'GetInstance', |
| returnType: '$codeSerializerName&', isStatic: true, inlineBody: () { |
| indent.writeln('static $codeSerializerName sInstance;'); |
| indent.writeln('return sInstance;'); |
| }); |
| indent.newln(); |
| _writeFunctionDeclaration(indent, 'WriteValue', |
| returnType: _voidType, |
| parameters: <String>[ |
| 'const flutter::EncodableValue& value', |
| 'flutter::ByteStreamWriter* stream' |
| ], |
| isConst: true, |
| isOverride: true); |
| }); |
| indent.writeScoped(' protected:', '', () { |
| _writeFunctionDeclaration(indent, 'ReadValueOfType', |
| returnType: 'flutter::EncodableValue', |
| parameters: <String>[ |
| 'uint8_t type', |
| 'flutter::ByteStreamReader* stream' |
| ], |
| isConst: true, |
| isOverride: true); |
| }); |
| }, nestCount: 0); |
| indent.newln(); |
| } |
| |
| void _writeErrorOr(Indent indent, |
| {Iterable<String> friends = const <String>[]}) { |
| final String friendLines = friends |
| .map((String className) => '\tfriend class $className;') |
| .join('\n'); |
| indent.format(''' |
| |
| class FlutterError { |
| public: |
| \texplicit FlutterError(const std::string& code) |
| \t\t: code_(code) {} |
| \texplicit FlutterError(const std::string& code, const std::string& message) |
| \t\t: code_(code), message_(message) {} |
| \texplicit FlutterError(const std::string& code, const std::string& message, const flutter::EncodableValue& details) |
| \t\t: code_(code), message_(message), details_(details) {} |
| |
| \tconst std::string& code() const { return code_; } |
| \tconst std::string& message() const { return message_; } |
| \tconst flutter::EncodableValue& details() const { return details_; } |
| |
| private: |
| \tstd::string code_; |
| \tstd::string message_; |
| \tflutter::EncodableValue details_; |
| }; |
| |
| template<class T> class ErrorOr { |
| public: |
| \tErrorOr(const T& rhs) : v_(rhs) {} |
| \tErrorOr(const T&& rhs) : v_(std::move(rhs)) {} |
| \tErrorOr(const FlutterError& rhs) : v_(rhs) {} |
| \tErrorOr(const FlutterError&& rhs) : v_(std::move(rhs)) {} |
| |
| \tbool has_error() const { return std::holds_alternative<FlutterError>(v_); } |
| \tconst T& value() const { return std::get<T>(v_); }; |
| \tconst FlutterError& error() const { return std::get<FlutterError>(v_); }; |
| |
| private: |
| $friendLines |
| \tErrorOr() = default; |
| \tT TakeValue() && { return std::get<T>(std::move(v_)); } |
| |
| \tstd::variant<T, FlutterError> v_; |
| }; |
| '''); |
| } |
| |
| @override |
| void writeCloseNamespace( |
| CppOptions generatorOptions, |
| Root root, |
| Indent indent, { |
| required String dartPackageName, |
| }) { |
| if (generatorOptions.namespace != null) { |
| indent.writeln('} // namespace ${generatorOptions.namespace}'); |
| } |
| final String guardName = _getGuardName(generatorOptions.headerIncludePath); |
| indent.writeln('#endif // $guardName'); |
| } |
| } |
| |
| /// Writes C++ source (.cpp) file to sink. |
| class CppSourceGenerator extends StructuredGenerator<CppOptions> { |
| /// Constructor. |
| const CppSourceGenerator(); |
| |
| @override |
| void writeFilePrologue( |
| CppOptions generatorOptions, |
| Root root, |
| Indent indent, { |
| required String dartPackageName, |
| }) { |
| if (generatorOptions.copyrightHeader != null) { |
| addLines(indent, generatorOptions.copyrightHeader!, linePrefix: '// '); |
| } |
| indent.writeln('$_commentPrefix ${getGeneratedCodeWarning()}'); |
| indent.writeln('$_commentPrefix $seeAlsoWarning'); |
| indent.newln(); |
| indent.addln('#undef _HAS_EXCEPTIONS'); |
| indent.newln(); |
| } |
| |
| @override |
| void writeFileImports( |
| CppOptions generatorOptions, |
| Root root, |
| Indent indent, { |
| required String dartPackageName, |
| }) { |
| indent.writeln('#include "${generatorOptions.headerIncludePath}"'); |
| indent.newln(); |
| _writeSystemHeaderIncludeBlock(indent, <String>[ |
| 'flutter/basic_message_channel.h', |
| 'flutter/binary_messenger.h', |
| 'flutter/encodable_value.h', |
| 'flutter/standard_message_codec.h', |
| ]); |
| indent.newln(); |
| _writeSystemHeaderIncludeBlock(indent, <String>[ |
| 'map', |
| 'string', |
| 'optional', |
| ]); |
| indent.newln(); |
| } |
| |
| @override |
| void writeOpenNamespace( |
| CppOptions generatorOptions, |
| Root root, |
| Indent indent, { |
| required String dartPackageName, |
| }) { |
| if (generatorOptions.namespace != null) { |
| indent.writeln('namespace ${generatorOptions.namespace} {'); |
| } |
| } |
| |
| @override |
| void writeGeneralUtilities( |
| CppOptions generatorOptions, |
| Root root, |
| Indent indent, { |
| required String dartPackageName, |
| }) { |
| final List<String> usingDirectives = <String>[ |
| 'flutter::BasicMessageChannel', |
| 'flutter::CustomEncodableValue', |
| 'flutter::EncodableList', |
| 'flutter::EncodableMap', |
| 'flutter::EncodableValue', |
| ]; |
| usingDirectives.sort(); |
| for (final String using in usingDirectives) { |
| indent.writeln('using $using;'); |
| } |
| indent.newln(); |
| } |
| |
| @override |
| void writeDataClass( |
| CppOptions generatorOptions, |
| Root root, |
| Indent indent, |
| Class klass, { |
| required String dartPackageName, |
| }) { |
| final Set<String> customClassNames = |
| root.classes.map((Class x) => x.name).toSet(); |
| final Set<String> customEnumNames = |
| root.enums.map((Enum x) => x.name).toSet(); |
| |
| indent.writeln('$_commentPrefix ${klass.name}'); |
| indent.newln(); |
| |
| final Iterable<NamedType> orderedFields = |
| getFieldsInSerializationOrder(klass); |
| final Iterable<NamedType> requiredFields = |
| orderedFields.where((NamedType type) => !type.type.isNullable); |
| // Minimal constructor, if needed. |
| if (requiredFields.length != orderedFields.length) { |
| _writeClassConstructor(root, indent, klass, requiredFields); |
| } |
| // All-field constructor. |
| _writeClassConstructor(root, indent, klass, orderedFields); |
| |
| // Getters and setters. |
| for (final NamedType field in orderedFields) { |
| _writeCppSourceClassField(generatorOptions, root, indent, klass, field); |
| } |
| |
| // Serialization. |
| writeClassEncode( |
| generatorOptions, |
| root, |
| indent, |
| klass, |
| customClassNames, |
| customEnumNames, |
| dartPackageName: dartPackageName, |
| ); |
| |
| // Deserialization. |
| writeClassDecode( |
| generatorOptions, |
| root, |
| indent, |
| klass, |
| customClassNames, |
| customEnumNames, |
| dartPackageName: dartPackageName, |
| ); |
| } |
| |
| @override |
| void writeClassEncode( |
| CppOptions generatorOptions, |
| Root root, |
| Indent indent, |
| Class klass, |
| Set<String> customClassNames, |
| Set<String> customEnumNames, { |
| required String dartPackageName, |
| }) { |
| _writeFunctionDefinition(indent, 'ToEncodableList', |
| scope: klass.name, |
| returnType: 'EncodableList', |
| isConst: true, body: () { |
| indent.writeln('EncodableList list;'); |
| indent.writeln('list.reserve(${klass.fields.length});'); |
| for (final NamedType field in getFieldsInSerializationOrder(klass)) { |
| final HostDatatype hostDatatype = getFieldHostDatatype(field, |
| root.classes, root.enums, _shortBaseCppTypeForBuiltinDartType); |
| final String encodableValue = _wrappedHostApiArgumentExpression( |
| root, _makeInstanceVariableName(field), field.type, hostDatatype, |
| preSerializeClasses: true); |
| indent.writeln('list.push_back($encodableValue);'); |
| } |
| indent.writeln('return list;'); |
| }); |
| } |
| |
| @override |
| void writeClassDecode( |
| CppOptions generatorOptions, |
| Root root, |
| Indent indent, |
| Class klass, |
| Set<String> customClassNames, |
| Set<String> customEnumNames, { |
| required String dartPackageName, |
| }) { |
| // Returns the expression to convert the given EncodableValue to a field |
| // value. |
| String getValueExpression(NamedType field, String encodable) { |
| if (customEnumNames.contains(field.type.baseName)) { |
| return '(${field.type.baseName})(std::get<int32_t>($encodable))'; |
| } else if (field.type.baseName == 'int') { |
| return '$encodable.LongValue()'; |
| } else if (field.type.baseName == 'Object') { |
| return encodable; |
| } else { |
| final HostDatatype hostDatatype = getFieldHostDatatype(field, |
| root.classes, root.enums, _shortBaseCppTypeForBuiltinDartType); |
| if (!hostDatatype.isBuiltin && |
| root.classes |
| .map((Class x) => x.name) |
| .contains(field.type.baseName)) { |
| return '${hostDatatype.datatype}::FromEncodableList(std::get<EncodableList>($encodable))'; |
| } else { |
| return 'std::get<${hostDatatype.datatype}>($encodable)'; |
| } |
| } |
| } |
| |
| _writeFunctionDefinition(indent, 'FromEncodableList', |
| scope: klass.name, |
| returnType: klass.name, |
| parameters: <String>['const EncodableList& list'], body: () { |
| const String instanceVariable = 'decoded'; |
| final Iterable<_IndexedField> indexedFields = indexMap( |
| getFieldsInSerializationOrder(klass), |
| (int index, NamedType field) => _IndexedField(index, field)); |
| final Iterable<_IndexedField> nullableFields = indexedFields |
| .where((_IndexedField field) => field.field.type.isNullable); |
| final Iterable<_IndexedField> nonNullableFields = indexedFields |
| .where((_IndexedField field) => !field.field.type.isNullable); |
| |
| // Non-nullable fields must be set via the constructor. |
| String constructorArgs = nonNullableFields |
| .map((_IndexedField param) => |
| getValueExpression(param.field, 'list[${param.index}]')) |
| .join(',\n\t'); |
| if (constructorArgs.isNotEmpty) { |
| constructorArgs = '(\n\t$constructorArgs)'; |
| } |
| indent.format('${klass.name} $instanceVariable$constructorArgs;'); |
| |
| // Add the nullable fields via setters, since converting the encodable |
| // values to the pointer types that the convenience constructor uses for |
| // nullable fields is non-trivial. |
| for (final _IndexedField entry in nullableFields) { |
| final NamedType field = entry.field; |
| final String setterName = _makeSetterName(field); |
| final String encodableFieldName = |
| '${_encodablePrefix}_${_makeVariableName(field)}'; |
| indent.writeln('auto& $encodableFieldName = list[${entry.index}];'); |
| |
| final String valueExpression = |
| getValueExpression(field, encodableFieldName); |
| indent.writeScoped('if (!$encodableFieldName.IsNull()) {', '}', () { |
| indent.writeln('$instanceVariable.$setterName($valueExpression);'); |
| }); |
| } |
| |
| // This returns by value, relying on copy elision, since it makes the |
| // usage more convenient during deserialization than it would be with |
| // explicit transfer via unique_ptr. |
| indent.writeln('return $instanceVariable;'); |
| }); |
| } |
| |
| @override |
| void writeFlutterApi( |
| CppOptions generatorOptions, |
| Root root, |
| Indent indent, |
| Api api, { |
| required String dartPackageName, |
| }) { |
| assert(api.location == ApiLocation.flutter); |
| if (getCodecClasses(api, root).isNotEmpty) { |
| _writeCodec(generatorOptions, root, indent, api); |
| } |
| indent.writeln( |
| '$_commentPrefix Generated class from Pigeon that represents Flutter messages that can be called from C++.'); |
| _writeFunctionDefinition(indent, api.name, |
| scope: api.name, |
| parameters: <String>['flutter::BinaryMessenger* binary_messenger'], |
| initializers: <String>['binary_messenger_(binary_messenger)']); |
| final String codeSerializerName = getCodecClasses(api, root).isNotEmpty |
| ? _getCodecSerializerName(api) |
| : _defaultCodecSerializer; |
| _writeFunctionDefinition(indent, 'GetCodec', |
| scope: api.name, |
| returnType: 'const flutter::StandardMessageCodec&', body: () { |
| indent.writeln( |
| 'return flutter::StandardMessageCodec::GetInstance(&$codeSerializerName::GetInstance());'); |
| }); |
| for (final Method func in api.methods) { |
| final String channelName = makeChannelName(api, func, dartPackageName); |
| final HostDatatype returnType = getHostDatatype(func.returnType, |
| root.classes, root.enums, _shortBaseCppTypeForBuiltinDartType); |
| |
| // Determine the input parameter list, saved in a structured form for later |
| // use as platform channel call arguments. |
| final Iterable<_HostNamedType> hostParameters = |
| indexMap(func.arguments, (int i, NamedType arg) { |
| final HostDatatype hostType = getFieldHostDatatype( |
| arg, root.classes, root.enums, _shortBaseCppTypeForBuiltinDartType); |
| return _HostNamedType(_getSafeArgumentName(i, arg), hostType, arg.type); |
| }); |
| final List<String> parameters = <String>[ |
| ...hostParameters.map((_HostNamedType arg) => |
| '${_flutterApiArgumentType(arg.hostType)} ${arg.name}'), |
| ..._flutterApiCallbackParameters(returnType), |
| ]; |
| _writeFunctionDefinition(indent, _makeMethodName(func), |
| scope: api.name, |
| returnType: _voidType, |
| parameters: parameters, body: () { |
| const String channel = 'channel'; |
| indent.writeln( |
| 'auto channel = std::make_unique<BasicMessageChannel<>>(binary_messenger_, ' |
| '"$channelName", &GetCodec());'); |
| |
| // Convert arguments to EncodableValue versions. |
| const String argumentListVariableName = 'encoded_api_arguments'; |
| indent.write('EncodableValue $argumentListVariableName = '); |
| if (func.arguments.isEmpty) { |
| indent.addln('EncodableValue();'); |
| } else { |
| indent.addScoped('EncodableValue(EncodableList{', '});', () { |
| for (final _HostNamedType param in hostParameters) { |
| final String encodedArgument = _wrappedHostApiArgumentExpression( |
| root, param.name, param.originalType, param.hostType, |
| preSerializeClasses: false); |
| indent.writeln('$encodedArgument,'); |
| } |
| }); |
| } |
| |
| indent.write('$channel->Send($argumentListVariableName, ' |
| // ignore: missing_whitespace_between_adjacent_strings |
| '[on_success = std::move(on_success), on_error = std::move(on_error)]' |
| '(const uint8_t* reply, size_t reply_size) '); |
| indent.addScoped('{', '});', () { |
| final String successCallbackArgument; |
| if (func.returnType.isVoid) { |
| successCallbackArgument = ''; |
| } else { |
| successCallbackArgument = 'return_value'; |
| final String encodedReplyName = |
| 'encodable_$successCallbackArgument'; |
| indent.writeln( |
| 'std::unique_ptr<EncodableValue> response = GetCodec().DecodeMessage(reply, reply_size);'); |
| indent.writeln('const auto& $encodedReplyName = *response;'); |
| _writeEncodableValueArgumentUnwrapping( |
| indent, |
| root, |
| returnType, |
| argName: successCallbackArgument, |
| encodableArgName: encodedReplyName, |
| apiType: ApiType.flutter, |
| ); |
| } |
| indent.writeln('on_success($successCallbackArgument);'); |
| }); |
| }); |
| } |
| } |
| |
| @override |
| void writeHostApi( |
| CppOptions generatorOptions, |
| Root root, |
| Indent indent, |
| Api api, { |
| required String dartPackageName, |
| }) { |
| assert(api.location == ApiLocation.host); |
| if (getCodecClasses(api, root).isNotEmpty) { |
| _writeCodec(generatorOptions, root, indent, api); |
| } |
| |
| final String codeSerializerName = getCodecClasses(api, root).isNotEmpty |
| ? _getCodecSerializerName(api) |
| : _defaultCodecSerializer; |
| indent.writeln('/// The codec used by ${api.name}.'); |
| _writeFunctionDefinition(indent, 'GetCodec', |
| scope: api.name, |
| returnType: 'const flutter::StandardMessageCodec&', body: () { |
| indent.writeln( |
| 'return flutter::StandardMessageCodec::GetInstance(&$codeSerializerName::GetInstance());'); |
| }); |
| indent.writeln( |
| '$_commentPrefix Sets up an instance of `${api.name}` to handle messages through the `binary_messenger`.'); |
| _writeFunctionDefinition(indent, 'SetUp', |
| scope: api.name, |
| returnType: _voidType, |
| parameters: <String>[ |
| 'flutter::BinaryMessenger* binary_messenger', |
| '${api.name}* api' |
| ], body: () { |
| for (final Method method in api.methods) { |
| final String channelName = |
| makeChannelName(api, method, dartPackageName); |
| indent.writeScoped('{', '}', () { |
| indent.writeln( |
| 'auto channel = std::make_unique<BasicMessageChannel<>>(binary_messenger, ' |
| '"$channelName", &GetCodec());'); |
| indent.writeScoped('if (api != nullptr) {', '} else {', () { |
| indent.write( |
| 'channel->SetMessageHandler([api](const EncodableValue& message, const flutter::MessageReply<EncodableValue>& reply) '); |
| indent.addScoped('{', '});', () { |
| indent.writeScoped('try {', '}', () { |
| final List<String> methodArgument = <String>[]; |
| if (method.arguments.isNotEmpty) { |
| indent.writeln( |
| 'const auto& args = std::get<EncodableList>(message);'); |
| |
| enumerate(method.arguments, (int index, NamedType arg) { |
| final HostDatatype hostType = getHostDatatype( |
| arg.type, |
| root.classes, |
| root.enums, |
| (TypeDeclaration x) => |
| _shortBaseCppTypeForBuiltinDartType(x)); |
| final String argName = _getSafeArgumentName(index, arg); |
| |
| final String encodableArgName = |
| '${_encodablePrefix}_$argName'; |
| indent.writeln( |
| 'const auto& $encodableArgName = args.at($index);'); |
| if (!arg.type.isNullable) { |
| indent.writeScoped( |
| 'if ($encodableArgName.IsNull()) {', '}', () { |
| indent.writeln( |
| 'reply(WrapError("$argName unexpectedly null."));'); |
| indent.writeln('return;'); |
| }); |
| } |
| _writeEncodableValueArgumentUnwrapping( |
| indent, |
| root, |
| hostType, |
| argName: argName, |
| encodableArgName: encodableArgName, |
| apiType: ApiType.host, |
| ); |
| final String unwrapEnum = |
| isEnum(root, arg.type) && arg.type.isNullable |
| ? ' ? &(*$argName) : nullptr' |
| : ''; |
| methodArgument.add('$argName$unwrapEnum'); |
| }); |
| } |
| |
| final HostDatatype returnType = getHostDatatype( |
| method.returnType, |
| root.classes, |
| root.enums, |
| _shortBaseCppTypeForBuiltinDartType); |
| final String returnTypeName = _hostApiReturnType(returnType); |
| if (method.isAsynchronous) { |
| methodArgument.add( |
| '[reply]($returnTypeName&& output) {${indent.newline}' |
| '${_wrapResponse(indent, root, method.returnType, prefix: '\t')}${indent.newline}' |
| '}', |
| ); |
| } |
| final String call = |
| 'api->${_makeMethodName(method)}(${methodArgument.join(', ')})'; |
| if (method.isAsynchronous) { |
| indent.format('$call;'); |
| } else { |
| indent.writeln('$returnTypeName output = $call;'); |
| indent.format(_wrapResponse(indent, root, method.returnType)); |
| } |
| }, addTrailingNewline: false); |
| indent.add(' catch (const std::exception& exception) '); |
| indent.addScoped('{', '}', () { |
| // There is a potential here for `reply` to be called twice, which |
| // is a violation of the API contract, because there's no way of |
| // knowing whether or not the plugin code called `reply` before |
| // throwing. Since use of `@async` suggests that the reply is |
| // probably not sent within the scope of the stack, err on the |
| // side of potential double-call rather than no call (which is |
| // also an API violation) so that unexpected errors have a better |
| // chance of being caught and handled in a useful way. |
| indent.writeln('reply(WrapError(exception.what()));'); |
| }); |
| }); |
| }); |
| indent.addScoped(null, '}', () { |
| indent.writeln('channel->SetMessageHandler(nullptr);'); |
| }); |
| }); |
| } |
| }); |
| |
| _writeFunctionDefinition(indent, 'WrapError', |
| scope: api.name, |
| returnType: 'EncodableValue', |
| parameters: <String>['std::string_view error_message'], body: () { |
| indent.format(''' |
| return EncodableValue(EncodableList{ |
| \tEncodableValue(std::string(error_message)), |
| \tEncodableValue("Error"), |
| \tEncodableValue() |
| });'''); |
| }); |
| _writeFunctionDefinition(indent, 'WrapError', |
| scope: api.name, |
| returnType: 'EncodableValue', |
| parameters: <String>['const FlutterError& error'], body: () { |
| indent.format(''' |
| return EncodableValue(EncodableList{ |
| \tEncodableValue(error.code()), |
| \tEncodableValue(error.message()), |
| \terror.details() |
| });'''); |
| }); |
| } |
| |
| void _writeCodec( |
| CppOptions generatorOptions, |
| Root root, |
| Indent indent, |
| Api api, |
| ) { |
| assert(getCodecClasses(api, root).isNotEmpty); |
| final String codeSerializerName = _getCodecSerializerName(api); |
| indent.newln(); |
| _writeFunctionDefinition(indent, codeSerializerName, |
| scope: codeSerializerName); |
| _writeFunctionDefinition(indent, 'ReadValueOfType', |
| scope: codeSerializerName, |
| returnType: 'EncodableValue', |
| parameters: <String>[ |
| 'uint8_t type', |
| 'flutter::ByteStreamReader* stream', |
| ], |
| isConst: true, body: () { |
| indent.write('switch (type) '); |
| indent.addScoped('{', '}', () { |
| for (final EnumeratedClass customClass in getCodecClasses(api, root)) { |
| indent.writeln('case ${customClass.enumeration}:'); |
| indent.nest(1, () { |
| indent.writeln( |
| 'return CustomEncodableValue(${customClass.name}::FromEncodableList(std::get<EncodableList>(ReadValue(stream))));'); |
| }); |
| } |
| indent.writeln('default:'); |
| indent.nest(1, () { |
| indent.writeln( |
| 'return $_defaultCodecSerializer::ReadValueOfType(type, stream);'); |
| }); |
| }); |
| }); |
| _writeFunctionDefinition(indent, 'WriteValue', |
| scope: codeSerializerName, |
| returnType: _voidType, |
| parameters: <String>[ |
| 'const EncodableValue& value', |
| 'flutter::ByteStreamWriter* stream', |
| ], |
| isConst: true, body: () { |
| indent.write( |
| 'if (const CustomEncodableValue* custom_value = std::get_if<CustomEncodableValue>(&value)) '); |
| indent.addScoped('{', '}', () { |
| for (final EnumeratedClass customClass in getCodecClasses(api, root)) { |
| indent.write( |
| 'if (custom_value->type() == typeid(${customClass.name})) '); |
| indent.addScoped('{', '}', () { |
| indent.writeln('stream->WriteByte(${customClass.enumeration});'); |
| indent.writeln( |
| 'WriteValue(EncodableValue(std::any_cast<${customClass.name}>(*custom_value).ToEncodableList()), stream);'); |
| indent.writeln('return;'); |
| }); |
| } |
| }); |
| indent.writeln('$_defaultCodecSerializer::WriteValue(value, stream);'); |
| }); |
| } |
| |
| void _writeClassConstructor( |
| Root root, Indent indent, Class klass, Iterable<NamedType> params) { |
| final Iterable<_HostNamedType> hostParams = params.map((NamedType param) { |
| return _HostNamedType( |
| _makeVariableName(param), |
| getFieldHostDatatype( |
| param, |
| root.classes, |
| root.enums, |
| _shortBaseCppTypeForBuiltinDartType, |
| ), |
| param.type, |
| ); |
| }); |
| |
| final List<String> paramStrings = hostParams |
| .map((_HostNamedType param) => |
| '${_hostApiArgumentType(param.hostType)} ${param.name}') |
| .toList(); |
| final List<String> initializerStrings = hostParams |
| .map((_HostNamedType param) => |
| '${param.name}_(${_fieldValueExpression(param.hostType, param.name)})') |
| .toList(); |
| _writeFunctionDefinition(indent, klass.name, |
| scope: klass.name, |
| parameters: paramStrings, |
| initializers: initializerStrings); |
| } |
| |
| void _writeCppSourceClassField(CppOptions generatorOptions, Root root, |
| Indent indent, Class klass, NamedType field) { |
| final HostDatatype hostDatatype = getFieldHostDatatype( |
| field, root.classes, root.enums, _shortBaseCppTypeForBuiltinDartType); |
| final String instanceVariableName = _makeInstanceVariableName(field); |
| final String setterName = _makeSetterName(field); |
| final String returnExpression = hostDatatype.isNullable |
| ? '$instanceVariableName ? &(*$instanceVariableName) : nullptr' |
| : instanceVariableName; |
| |
| // Writes a setter treating the type as [type], to allow generating multiple |
| // setter variants. |
| void writeSetter(HostDatatype type) { |
| const String setterArgumentName = 'value_arg'; |
| _writeFunctionDefinition( |
| indent, |
| setterName, |
| scope: klass.name, |
| returnType: _voidType, |
| parameters: <String>[ |
| '${_unownedArgumentType(type)} $setterArgumentName' |
| ], |
| body: () { |
| indent.writeln( |
| '$instanceVariableName = ${_fieldValueExpression(type, setterArgumentName)};'); |
| }, |
| ); |
| } |
| |
| _writeFunctionDefinition( |
| indent, |
| _makeGetterName(field), |
| scope: klass.name, |
| returnType: _getterReturnType(hostDatatype), |
| isConst: true, |
| body: () { |
| indent.writeln('return $returnExpression;'); |
| }, |
| ); |
| writeSetter(hostDatatype); |
| if (hostDatatype.isNullable) { |
| // Write the non-nullable variant; see _writeCppHeaderDataClass. |
| writeSetter(_nonNullableType(hostDatatype)); |
| } |
| |
| indent.newln(); |
| } |
| |
| /// Returns the value to use when setting a field of the given type from |
| /// an argument of that type. |
| /// |
| /// For non-nullable values this is just the variable itself, but for nullable |
| /// values this handles the conversion between an argument type (a pointer) |
| /// and the field type (a std::optional). |
| String _fieldValueExpression(HostDatatype type, String variable) { |
| return type.isNullable |
| ? '$variable ? ${_valueType(type)}(*$variable) : std::nullopt' |
| : variable; |
| } |
| |
| String _wrapResponse(Indent indent, Root root, TypeDeclaration returnType, |
| {String prefix = ''}) { |
| final String nonErrorPath; |
| final String errorCondition; |
| final String errorGetter; |
| |
| const String nullValue = 'EncodableValue()'; |
| String enumPrefix = ''; |
| if (isEnum(root, returnType)) { |
| enumPrefix = '(int) '; |
| } |
| if (returnType.isVoid) { |
| nonErrorPath = '${prefix}wrapped.push_back($nullValue);'; |
| errorCondition = 'output.has_value()'; |
| errorGetter = 'value'; |
| } else { |
| final HostDatatype hostType = getHostDatatype(returnType, root.classes, |
| root.enums, _shortBaseCppTypeForBuiltinDartType); |
| |
| const String extractedValue = 'std::move(output).TakeValue()'; |
| final String wrapperType = hostType.isBuiltin || isEnum(root, returnType) |
| ? 'EncodableValue' |
| : 'CustomEncodableValue'; |
| if (returnType.isNullable) { |
| // The value is a std::optional, so needs an extra layer of |
| // handling. |
| nonErrorPath = ''' |
| ${prefix}auto output_optional = $extractedValue; |
| ${prefix}if (output_optional) { |
| $prefix\twrapped.push_back($wrapperType(${enumPrefix}std::move(output_optional).value())); |
| $prefix} else { |
| $prefix\twrapped.push_back($nullValue); |
| $prefix}'''; |
| } else { |
| nonErrorPath = |
| '${prefix}wrapped.push_back($wrapperType($enumPrefix$extractedValue));'; |
| } |
| errorCondition = 'output.has_error()'; |
| errorGetter = 'error'; |
| } |
| // Ideally this code would use an initializer list to create |
| // an EncodableList inline, which would be less code. However, |
| // that would always copy the element, so the slightly more |
| // verbose create-and-push approach is used instead. |
| return ''' |
| ${prefix}if ($errorCondition) { |
| $prefix\treply(WrapError(output.$errorGetter())); |
| $prefix\treturn; |
| $prefix} |
| ${prefix}EncodableList wrapped; |
| $nonErrorPath |
| ${prefix}reply(EncodableValue(std::move(wrapped)));'''; |
| } |
| |
| @override |
| void writeCloseNamespace( |
| CppOptions generatorOptions, |
| Root root, |
| Indent indent, { |
| required String dartPackageName, |
| }) { |
| if (generatorOptions.namespace != null) { |
| indent.writeln('} // namespace ${generatorOptions.namespace}'); |
| } |
| } |
| |
| /// Returns the expression to create an EncodableValue from a host API argument |
| /// with the given [variableName] and types. |
| /// |
| /// If [preSerializeClasses] is true, custom classes will be returned as |
| /// encodable lists rather than CustomEncodableValues; see |
| /// https://github.com/flutter/flutter/issues/119351 for why this is currently |
| /// needed. |
| String _wrappedHostApiArgumentExpression(Root root, String variableName, |
| TypeDeclaration dartType, HostDatatype hostType, |
| {required bool preSerializeClasses}) { |
| final String encodableValue; |
| if (!hostType.isBuiltin && |
| root.classes.any((Class c) => c.name == dartType.baseName)) { |
| if (preSerializeClasses) { |
| final String operator = hostType.isNullable ? '->' : '.'; |
| encodableValue = |
| 'EncodableValue($variableName${operator}ToEncodableList())'; |
| } else { |
| final String nonNullValue = |
| hostType.isNullable ? '*$variableName' : variableName; |
| encodableValue = 'CustomEncodableValue($nonNullValue)'; |
| } |
| } else if (!hostType.isBuiltin && |
| root.enums.any((Enum e) => e.name == dartType.baseName)) { |
| final String nonNullValue = |
| hostType.isNullable ? '(*$variableName)' : variableName; |
| encodableValue = 'EncodableValue((int)$nonNullValue)'; |
| } else if (dartType.baseName == 'Object') { |
| final String operator = hostType.isNullable ? '*' : ''; |
| encodableValue = '$operator$variableName'; |
| } else { |
| final String operator = hostType.isNullable ? '*' : ''; |
| encodableValue = 'EncodableValue($operator$variableName)'; |
| } |
| |
| if (hostType.isNullable) { |
| return '$variableName ? $encodableValue : EncodableValue()'; |
| } |
| return encodableValue; |
| } |
| |
| /// Writes the code to declare and populate a variable of type [hostType] |
| /// called [argName] to use as a parameter to an API method call, from an |
| /// existing EncodableValue variable called [encodableArgName]. |
| void _writeEncodableValueArgumentUnwrapping( |
| Indent indent, |
| Root root, |
| HostDatatype hostType, { |
| required String argName, |
| required String encodableArgName, |
| required ApiType apiType, |
| }) { |
| if (hostType.isNullable) { |
| // Nullable arguments are always pointers, with nullptr corresponding to |
| // null. |
| if (hostType.datatype == 'int64_t') { |
| // The EncodableValue will either be an int32_t or an int64_t depending |
| // on the value, but the generated API requires an int64_t so that it can |
| // handle any case. Create a local variable for the 64-bit value... |
| final String valueVarName = '${argName}_value'; |
| indent.writeln( |
| 'const int64_t $valueVarName = $encodableArgName.IsNull() ? 0 : $encodableArgName.LongValue();'); |
| // ... then declare the arg as a reference to that local. |
| indent.writeln( |
| 'const auto* $argName = $encodableArgName.IsNull() ? nullptr : &$valueVarName;'); |
| } else if (hostType.datatype == 'EncodableValue') { |
| // Generic objects just pass the EncodableValue through directly. |
| indent.writeln('const auto* $argName = &$encodableArgName;'); |
| } else if (hostType.isBuiltin) { |
| indent.writeln( |
| 'const auto* $argName = std::get_if<${hostType.datatype}>(&$encodableArgName);'); |
| } else if (hostType.isEnum) { |
| if (hostType.isNullable) { |
| final String valueVarName = '${argName}_value'; |
| indent.writeln( |
| 'const int64_t $valueVarName = $encodableArgName.IsNull() ? 0 : $encodableArgName.LongValue();'); |
| if (apiType == ApiType.flutter) { |
| indent.writeln( |
| 'const auto* $argName = $encodableArgName.IsNull() ? nullptr : &(${hostType.datatype})$valueVarName;'); |
| } else { |
| indent.writeln( |
| 'const auto $argName = $encodableArgName.IsNull() ? std::nullopt : std::make_optional<${hostType.datatype}>(static_cast<${hostType.datatype}>(${argName}_value));'); |
| } |
| } else { |
| indent.writeln( |
| 'const auto* $argName = &((${hostType.datatype})std::get<int>($encodableArgName));'); |
| } |
| } else { |
| indent.writeln( |
| 'const auto* $argName = &(std::any_cast<const ${hostType.datatype}&>(std::get<CustomEncodableValue>($encodableArgName)));'); |
| } |
| } else { |
| // Non-nullable arguments are either passed by value or reference, but the |
| // extraction doesn't need to distinguish since those are the same at the |
| // call site. |
| if (hostType.datatype == 'int64_t') { |
| // The EncodableValue will either be an int32_t or an int64_t depending |
| // on the value, but the generated API requires an int64_t so that it can |
| // handle any case. |
| indent |
| .writeln('const int64_t $argName = $encodableArgName.LongValue();'); |
| } else if (hostType.datatype == 'EncodableValue') { |
| // Generic objects just pass the EncodableValue through directly. This |
| // creates an alias just to avoid having to special-case the |
| // argName/encodableArgName distinction at a higher level. |
| indent.writeln('const auto& $argName = $encodableArgName;'); |
| } else if (hostType.isBuiltin) { |
| indent.writeln( |
| 'const auto& $argName = std::get<${hostType.datatype}>($encodableArgName);'); |
| } else if (hostType.isEnum) { |
| indent.writeln( |
| 'const ${hostType.datatype}& $argName = (${hostType.datatype})$encodableArgName.LongValue();'); |
| } else { |
| indent.writeln( |
| 'const auto& $argName = std::any_cast<const ${hostType.datatype}&>(std::get<CustomEncodableValue>($encodableArgName));'); |
| } |
| } |
| } |
| |
| /// A wrapper for [_baseCppTypeForBuiltinDartType] that generated Flutter |
| /// types without the namespace, since the implementation file uses `using` |
| /// directives. |
| String? _shortBaseCppTypeForBuiltinDartType(TypeDeclaration type) { |
| return _baseCppTypeForBuiltinDartType(type, includeFlutterNamespace: false); |
| } |
| } |
| |
| /// Contains information about a host function argument. |
| /// |
| /// This is comparable to a [NamedType], but has already gone through host type |
| /// and variable name mapping, and it tracks the original [NamedType] that it |
| /// was created from. |
| class _HostNamedType { |
| const _HostNamedType(this.name, this.hostType, this.originalType); |
| final String name; |
| final HostDatatype hostType; |
| final TypeDeclaration originalType; |
| } |
| |
| /// Contains a class field and its serialization index. |
| class _IndexedField { |
| const _IndexedField(this.index, this.field); |
| final int index; |
| final NamedType field; |
| } |
| |
| String _getCodecSerializerName(Api api) => '${api.name}CodecSerializer'; |
| |
| const String _encodablePrefix = 'encodable'; |
| |
| String _getArgumentName(int count, NamedType argument) => |
| argument.name.isEmpty ? 'arg$count' : _makeVariableName(argument); |
| |
| /// Returns an argument name that can be used in a context where it is possible to collide. |
| String _getSafeArgumentName(int count, NamedType argument) => |
| '${_getArgumentName(count, argument)}_arg'; |
| |
| /// Returns a non-nullable variant of [type]. |
| HostDatatype _nonNullableType(HostDatatype type) { |
| return HostDatatype( |
| datatype: type.datatype, |
| isBuiltin: type.isBuiltin, |
| isNullable: false, |
| isEnum: type.isEnum, |
| ); |
| } |
| |
| String _pascalCaseFromCamelCase(String camelCase) => |
| camelCase[0].toUpperCase() + camelCase.substring(1); |
| |
| String _snakeCaseFromCamelCase(String camelCase) { |
| return camelCase.replaceAllMapped(RegExp(r'[A-Z]'), |
| (Match m) => '${m.start == 0 ? '' : '_'}${m[0]!.toLowerCase()}'); |
| } |
| |
| String _pascalCaseFromSnakeCase(String snakeCase) { |
| final String camelCase = snakeCase.replaceAllMapped( |
| RegExp(r'_([a-z])'), (Match m) => m[1]!.toUpperCase()); |
| return _pascalCaseFromCamelCase(camelCase); |
| } |
| |
| String _makeMethodName(Method method) => _pascalCaseFromCamelCase(method.name); |
| |
| String _makeGetterName(NamedType field) => _snakeCaseFromCamelCase(field.name); |
| |
| String _makeSetterName(NamedType field) => |
| 'set_${_snakeCaseFromCamelCase(field.name)}'; |
| |
| String _makeVariableName(NamedType field) => |
| _snakeCaseFromCamelCase(field.name); |
| |
| String _makeInstanceVariableName(NamedType field) => |
| '${_makeVariableName(field)}_'; |
| |
| // TODO(stuartmorgan): Remove this in favor of _isPodType once callers have |
| // all been updated to using HostDatatypes. |
| bool _isReferenceType(String dataType) { |
| switch (dataType) { |
| case 'bool': |
| case 'int64_t': |
| case 'double': |
| return false; |
| default: |
| return true; |
| } |
| } |
| |
| /// Returns the parameters to use for the success and error callbacks in a |
| /// Flutter API function signature. |
| List<String> _flutterApiCallbackParameters(HostDatatype returnType) { |
| return <String>[ |
| 'std::function<void(${_flutterApiReturnType(returnType)})>&& on_success', |
| 'std::function<void(const FlutterError&)>&& on_error', |
| ]; |
| } |
| |
| /// Returns true if [type] corresponds to a plain-old-data type (i.e., one that |
| /// should generally be passed by value rather than pointer/reference) in C++. |
| bool _isPodType(HostDatatype type) { |
| return !_isReferenceType(type.datatype); |
| } |
| |
| String? _baseCppTypeForBuiltinDartType( |
| TypeDeclaration type, { |
| bool includeFlutterNamespace = true, |
| }) { |
| final String flutterNamespace = includeFlutterNamespace ? 'flutter::' : ''; |
| final Map<String, String> cppTypeForDartTypeMap = <String, String>{ |
| 'void': 'void', |
| 'bool': 'bool', |
| 'int': 'int64_t', |
| 'String': 'std::string', |
| 'double': 'double', |
| 'Uint8List': 'std::vector<uint8_t>', |
| 'Int32List': 'std::vector<int32_t>', |
| 'Int64List': 'std::vector<int64_t>', |
| 'Float64List': 'std::vector<double>', |
| 'Map': '${flutterNamespace}EncodableMap', |
| 'List': '${flutterNamespace}EncodableList', |
| 'Object': '${flutterNamespace}EncodableValue', |
| }; |
| if (cppTypeForDartTypeMap.containsKey(type.baseName)) { |
| return cppTypeForDartTypeMap[type.baseName]; |
| } else { |
| return null; |
| } |
| } |
| |
| /// Returns the C++ type to use in a value context (variable declaration, |
| /// pass-by-value, etc.) for the given C++ base type. |
| String _valueType(HostDatatype type) { |
| final String baseType = type.datatype; |
| return type.isNullable ? 'std::optional<$baseType>' : baseType; |
| } |
| |
| /// Returns the C++ type to use in an argument context without ownership |
| /// transfer for the given base type. |
| String _unownedArgumentType(HostDatatype type) { |
| final bool isString = type.datatype == 'std::string'; |
| final String baseType = isString ? 'std::string_view' : type.datatype; |
| if (isString || _isPodType(type)) { |
| return type.isNullable ? 'const $baseType*' : baseType; |
| } |
| // TODO(stuartmorgan): Consider special-casing `Object?` here, so that there |
| // aren't two ways of representing null (nullptr or an isNull EncodableValue). |
| return type.isNullable ? 'const $baseType*' : 'const $baseType&'; |
| } |
| |
| /// Returns the C++ type to use for arguments to a host API. This is slightly |
| /// different from [_unownedArgumentType] since passing `std::string_view*` in |
| /// to the host API implementation when the actual type is `std::string*` is |
| /// needlessly complicated, so it uses `std::string` directly. |
| String _hostApiArgumentType(HostDatatype type) { |
| final String baseType = type.datatype; |
| if (_isPodType(type)) { |
| return type.isNullable ? 'const $baseType*' : baseType; |
| } |
| return type.isNullable ? 'const $baseType*' : 'const $baseType&'; |
| } |
| |
| /// Returns the C++ type to use for arguments to a Flutter API. |
| String _flutterApiArgumentType(HostDatatype type) { |
| // Nullable strings use std::string* rather than std::string_view* |
| // since there's no implicit conversion for the pointer types, making them |
| // more awkward to use. For consistency, and since EncodableValue will end |
| // up making a std::string internally anyway, std::string is used for the |
| // non-nullable case as well. |
| if (type.datatype == 'std::string') { |
| return type.isNullable ? 'const std::string*' : 'const std::string&'; |
| } |
| return _unownedArgumentType(type); |
| } |
| |
| /// Returns the C++ type to use for the return of a getter for a field of type |
| /// [type]. |
| String _getterReturnType(HostDatatype type) { |
| final String baseType = type.datatype; |
| if (_isPodType(type)) { |
| // Use pointers rather than optionals even for nullable POD, since the |
| // semantics of using them is essentially identical and this makes them |
| // consistent with non-POD. |
| return type.isNullable ? 'const $baseType*' : baseType; |
| } |
| return type.isNullable ? 'const $baseType*' : 'const $baseType&'; |
| } |
| |
| /// Returns the C++ type to use for the return of a host API method returning |
| /// [type]. |
| String _hostApiReturnType(HostDatatype type) { |
| if (type.datatype == 'void') { |
| return 'std::optional<FlutterError>'; |
| } |
| String valueType = type.datatype; |
| if (type.isNullable) { |
| valueType = 'std::optional<$valueType>'; |
| } |
| return 'ErrorOr<$valueType>'; |
| } |
| |
| /// Returns the C++ type to use for the paramer to the asynchronous "return" |
| /// callback of a Flutter API method returning [type]. |
| String _flutterApiReturnType(HostDatatype type) { |
| if (type.datatype == 'void') { |
| return 'void'; |
| } |
| // For anything other than void, handle it the same way as a host API argument |
| // since it has the same basic structure of being a function defined by the |
| // client, being called by the generated code. |
| return _hostApiArgumentType(type); |
| } |
| |
| String _getGuardName(String? headerFileName) { |
| const String prefix = 'PIGEON_'; |
| if (headerFileName != null) { |
| return '$prefix${headerFileName.replaceAll('.', '_').toUpperCase()}_'; |
| } else { |
| return '${prefix}H_'; |
| } |
| } |
| |
| void _writeSystemHeaderIncludeBlock(Indent indent, List<String> headers) { |
| headers.sort(); |
| for (final String header in headers) { |
| indent.writeln('#include <$header>'); |
| } |
| } |
| |
| enum _FunctionOutputType { declaration, definition } |
| |
| /// Writes a function declaration or definition to [indent]. |
| /// |
| /// If [parameters] are given, each should be a string of the form 'type name'. |
| void _writeFunction( |
| Indent indent, |
| _FunctionOutputType type, { |
| required String name, |
| String? returnType, |
| String? scope, |
| List<String> parameters = const <String>[], |
| List<String> startingAnnotations = const <String>[], |
| List<String> trailingAnnotations = const <String>[], |
| List<String> initializers = const <String>[], |
| void Function()? body, |
| }) { |
| assert(body == null || type == _FunctionOutputType.definition); |
| |
| // Set the initial indentation. |
| indent.write(''); |
| // Write any starting annotations (e.g., 'static'). |
| for (final String annotation in startingAnnotations) { |
| indent.add('$annotation '); |
| } |
| // Write the signature. |
| if (returnType != null) { |
| indent.add('$returnType '); |
| } |
| if (scope != null) { |
| indent.add('$scope::'); |
| } |
| indent.add(name); |
| // Write the parameters. |
| if (parameters.isEmpty) { |
| indent.add('()'); |
| } else if (parameters.length == 1) { |
| indent.add('(${parameters.first})'); |
| } else { |
| indent.addScoped('(', null, () { |
| enumerate(parameters, (int index, final String param) { |
| if (index == parameters.length - 1) { |
| indent.write('$param)'); |
| } else { |
| indent.writeln('$param,'); |
| } |
| }); |
| }, addTrailingNewline: false); |
| } |
| // Write any trailing annotations (e.g., 'const'). |
| for (final String annotation in trailingAnnotations) { |
| indent.add(' $annotation'); |
| } |
| // Write the initializer list, if any. |
| if (initializers.isNotEmpty) { |
| indent.newln(); |
| indent.write(' : '); |
| // The first item goes on the same line as the ":", the rest go on their |
| // own lines indented two extra levels, with no comma or newline after the |
| // last one. The easiest way to express the special casing of the first and |
| // last is with a join+format. |
| indent.format(initializers.join(',\n\t\t'), |
| leadingSpace: false, trailingNewline: false); |
| } |
| // Write the body or end the declaration. |
| if (type == _FunctionOutputType.declaration) { |
| indent.addln(';'); |
| } else { |
| if (body != null) { |
| indent.addScoped(' {', '}', body); |
| } else { |
| indent.addln(' {}'); |
| } |
| } |
| } |
| |
| void _writeFunctionDeclaration( |
| Indent indent, |
| String name, { |
| String? returnType, |
| List<String> parameters = const <String>[], |
| bool isStatic = false, |
| bool isVirtual = false, |
| bool isConstructor = false, |
| bool isPureVirtual = false, |
| bool isConst = false, |
| bool isOverride = false, |
| bool deleted = false, |
| bool inlineNoop = false, |
| void Function()? inlineBody, |
| }) { |
| assert(!(isVirtual && isOverride), 'virtual is redundant with override'); |
| assert(isVirtual || !isPureVirtual, 'pure virtual methods must be virtual'); |
| assert(returnType == null || !isConstructor, |
| 'constructors cannot have return types'); |
| _writeFunction( |
| indent, |
| inlineNoop || (inlineBody != null) |
| ? _FunctionOutputType.definition |
| : _FunctionOutputType.declaration, |
| name: name, |
| returnType: returnType, |
| parameters: parameters, |
| startingAnnotations: <String>[ |
| if (inlineBody != null) 'inline', |
| if (isStatic) 'static', |
| if (isVirtual) 'virtual', |
| if (isConstructor && parameters.isNotEmpty) 'explicit' |
| ], |
| trailingAnnotations: <String>[ |
| if (isConst) 'const', |
| if (isOverride) 'override', |
| if (deleted) '= delete', |
| if (isPureVirtual) '= 0', |
| ], |
| body: inlineBody, |
| ); |
| } |
| |
| void _writeFunctionDefinition( |
| Indent indent, |
| String name, { |
| String? returnType, |
| String? scope, |
| List<String> parameters = const <String>[], |
| bool isConst = false, |
| List<String> initializers = const <String>[], |
| void Function()? body, |
| }) { |
| _writeFunction( |
| indent, |
| _FunctionOutputType.definition, |
| name: name, |
| scope: scope, |
| returnType: returnType, |
| parameters: parameters, |
| trailingAnnotations: <String>[ |
| if (isConst) 'const', |
| ], |
| initializers: initializers, |
| body: body, |
| ); |
| indent.newln(); |
| } |
| |
| enum _ClassAccess { public, protected, private } |
| |
| void _writeAccessBlock( |
| Indent indent, _ClassAccess access, void Function() body) { |
| final String accessLabel; |
| switch (access) { |
| case _ClassAccess.public: |
| accessLabel = 'public'; |
| break; |
| case _ClassAccess.protected: |
| accessLabel = 'protected'; |
| break; |
| case _ClassAccess.private: |
| accessLabel = 'private'; |
| break; |
| } |
| indent.addScoped(' $accessLabel:', '', body); |
| } |
| |
| /// Validates an AST to make sure the cpp generator supports everything. |
| List<Error> validateCpp(CppOptions options, Root root) { |
| final List<Error> result = <Error>[]; |
| for (final Api api in root.apis) { |
| for (final Method method in api.methods) { |
| for (final NamedType arg in method.arguments) { |
| if (isEnum(root, arg.type)) { |
| // TODO(gaaclarke): Add line number and filename. |
| result.add(Error( |
| message: |
| "Nullable enum types aren't supported in C++ arguments in method:${api.name}.${method.name} argument:(${arg.type.baseName} ${arg.name}).")); |
| } |
| } |
| } |
| } |
| return result; |
| } |