| // Copyright 2013 The Flutter Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import 'package:path/path.dart' as path; |
| |
| import '../ast.dart'; |
| import '../functional.dart'; |
| import '../generator.dart'; |
| import '../generator_tools.dart'; |
| import '../pigeon_lib.dart'; |
| |
| /// 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 _standardCodecSerializer = '::flutter::StandardCodecSerializer'; |
| |
| /// The name of the codec serializer. |
| const String _codecSerializerName = '${classNamePrefix}CodecSerializer'; |
| |
| const String _overflowClassName = '${classNamePrefix}CodecOverflow'; |
| |
| final NamedType _overflowType = NamedType( |
| name: 'type', |
| type: const TypeDeclaration(baseName: 'int', isNullable: false), |
| ); |
| final NamedType _overflowObject = NamedType( |
| name: 'wrapped', |
| type: const TypeDeclaration(baseName: 'Object', isNullable: false), |
| ); |
| final List<NamedType> _overflowFields = <NamedType>[_overflowType, _overflowObject]; |
| final Class _overflowClass = Class(name: _overflowClassName, fields: _overflowFields); |
| final EnumeratedType _enumeratedOverflow = EnumeratedType( |
| _overflowClassName, |
| maximumCodecFieldKey, |
| CustomTypes.customClass, |
| associatedClass: _overflowClass, |
| ); |
| |
| /// 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 file (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['headerIncludePath'] 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 result = <String, Object>{ |
| if (headerIncludePath != null) 'headerIncludePath': 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(mergePigeonMaps(toMap(), options.toMap())); |
| } |
| } |
| |
| /// Options that control how C++ code will be generated. |
| /// |
| /// For internal use only. |
| class InternalCppOptions extends InternalOptions { |
| /// Creates a [InternalCppOptions] object. |
| const InternalCppOptions({ |
| required this.headerIncludePath, |
| required this.cppHeaderOut, |
| required this.cppSourceOut, |
| this.namespace, |
| this.copyrightHeader, |
| this.headerOutPath, |
| }); |
| |
| /// Creates InternalCppOptions from CppOptions. |
| InternalCppOptions.fromCppOptions( |
| CppOptions options, { |
| required this.cppHeaderOut, |
| required this.cppSourceOut, |
| Iterable<String>? copyrightHeader, |
| }) : headerIncludePath = options.headerIncludePath ?? path.basename(cppHeaderOut), |
| namespace = options.namespace, |
| copyrightHeader = options.copyrightHeader ?? copyrightHeader, |
| headerOutPath = options.headerOutPath; |
| |
| /// The path to the header that will get placed in the source file (example: |
| /// "foo.h"). |
| final String headerIncludePath; |
| |
| /// Path to the ".h" C++ file that will be generated. |
| final String cppHeaderOut; |
| |
| /// Path to the ".cpp" C++ file that will be generated. |
| final String cppSourceOut; |
| |
| /// 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; |
| } |
| |
| /// Class that manages all Cpp code generation. |
| class CppGenerator extends Generator<OutputFileOptions<InternalCppOptions>> { |
| /// Constructor. |
| const CppGenerator(); |
| |
| /// Generates C++ file of type specified in [generatorOptions] |
| @override |
| void generate( |
| OutputFileOptions<InternalCppOptions> 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<InternalCppOptions> { |
| /// Constructor. |
| const CppHeaderGenerator(); |
| |
| @override |
| void writeFilePrologue( |
| InternalCppOptions 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( |
| InternalCppOptions 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', 'ostream']); |
| indent.newln(); |
| if (generatorOptions.namespace != null) { |
| indent.writeln('namespace ${generatorOptions.namespace} {'); |
| } |
| indent.newln(); |
| if (generatorOptions.namespace?.endsWith('_pigeontest') ?? false) { |
| final testFixtureClass = |
| '${_pascalCaseFromSnakeCase(generatorOptions.namespace!.replaceAll('_pigeontest', ''))}Test'; |
| indent.writeln('class $testFixtureClass;'); |
| } |
| indent.newln(); |
| indent.writeln('$_commentPrefix Generated class from Pigeon.'); |
| } |
| |
| @override |
| void writeEnum( |
| InternalCppOptions 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, EnumMember member) { |
| addDocumentationComments(indent, member.documentationComments, _docCommentSpec); |
| final valueName = 'k${_pascalCaseFromCamelCase(member.name)}'; |
| indent.writeln('$valueName = $index${index == anEnum.members.length - 1 ? '' : ','}'); |
| }); |
| }); |
| } |
| |
| @override |
| void writeGeneralUtilities( |
| InternalCppOptions generatorOptions, |
| Root root, |
| Indent indent, { |
| required String dartPackageName, |
| }) { |
| _writeFlutterError(indent); |
| if (root.containsHostApi) { |
| _writeErrorOr( |
| indent, |
| friends: root.apis |
| .where((Api api) => api is AstFlutterApi || api is AstHostApi) |
| .map((Api api) => api.name), |
| ); |
| } |
| } |
| |
| @override |
| void writeDataClasses( |
| InternalCppOptions generatorOptions, |
| Root root, |
| Indent indent, { |
| required String dartPackageName, |
| }) { |
| indent.newln(); |
| super.writeDataClasses(generatorOptions, root, indent, dartPackageName: dartPackageName); |
| if (root.requiresOverflowClass) { |
| writeDataClass( |
| generatorOptions, |
| root, |
| indent, |
| _overflowClass, |
| dartPackageName: dartPackageName, |
| isOverflowClass: true, |
| ); |
| } |
| } |
| |
| @override |
| void writeDataClass( |
| InternalCppOptions generatorOptions, |
| Root root, |
| Indent indent, |
| Class classDefinition, { |
| required String dartPackageName, |
| bool isOverflowClass = false, |
| }) { |
| // 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 generatedMessages = <String>[ |
| ' Generated class from Pigeon that represents data sent in messages.', |
| ]; |
| |
| addDocumentationComments( |
| indent, |
| classDefinition.documentationComments, |
| _docCommentSpec, |
| generatorComments: generatedMessages, |
| ); |
| |
| final Iterable<NamedType> orderedFields = getFieldsInSerializationOrder(classDefinition); |
| |
| indent.write('class ${classDefinition.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, |
| classDefinition, |
| requiredFields, |
| 'Constructs an object setting all non-nullable fields.', |
| ); |
| } |
| // All-field constructor. |
| _writeClassConstructor( |
| root, |
| indent, |
| classDefinition, |
| orderedFields, |
| 'Constructs an object setting all fields.', |
| ); |
| |
| // If any fields are pointer type, then the class requires a custom |
| // copy constructor, so declare the rule-of-five group of functions. |
| if (orderedFields.any( |
| (NamedType field) => |
| _isPointerField(getFieldHostDatatype(field, _baseCppTypeForBuiltinDartType)), |
| )) { |
| final String className = classDefinition.name; |
| // Add the default destructor, since unique_ptr destroys itself. |
| _writeFunctionDeclaration(indent, '~$className', defaultImpl: true); |
| // Declare custom copy/assign to deep-copy the pointer. |
| _writeFunctionDeclaration( |
| indent, |
| className, |
| isConstructor: true, |
| isCopy: true, |
| parameters: <String>['const $className& other'], |
| ); |
| _writeFunctionDeclaration( |
| indent, |
| 'operator=', |
| returnType: '$className&', |
| parameters: <String>['const $className& other'], |
| ); |
| // Re-add the default move operations, since they work fine with |
| // unique_ptr. |
| _writeFunctionDeclaration( |
| indent, |
| className, |
| isConstructor: true, |
| isCopy: true, |
| parameters: <String>['$className&& other'], |
| defaultImpl: true, |
| ); |
| _writeFunctionDeclaration( |
| indent, |
| 'operator=', |
| returnType: '$className&', |
| parameters: <String>['$className&& other'], |
| defaultImpl: true, |
| noexcept: true, |
| ); |
| } |
| |
| for (final field in orderedFields) { |
| addDocumentationComments(indent, field.documentationComments, _docCommentSpec); |
| final HostDatatype baseDatatype = getFieldHostDatatype( |
| field, |
| _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(); |
| } |
| |
| _writeFunctionDeclaration( |
| indent, |
| 'operator==', |
| returnType: 'bool', |
| parameters: <String>['const ${classDefinition.name}& other'], |
| isConst: true, |
| ); |
| _writeFunctionDeclaration( |
| indent, |
| 'operator!=', |
| returnType: 'bool', |
| parameters: <String>['const ${classDefinition.name}& other'], |
| isConst: true, |
| ); |
| indent.writeln( |
| '/// Returns a hash code value for the object. This method is supported for the benefit of hash tables.', |
| ); |
| _writeFunctionDeclaration(indent, 'Hash', returnType: 'size_t', isConst: true); |
| indent.writeln('/// Stream output operator for formatted string representation.'); |
| indent.writeln( |
| 'friend std::ostream& operator<<(std::ostream& os, const ${classDefinition.name}& obj);', |
| ); |
| }); |
| |
| _writeAccessBlock(indent, _ClassAccess.private, () { |
| _writeFunctionDeclaration( |
| indent, |
| 'FromEncodableList', |
| returnType: isOverflowClass ? '::flutter::EncodableValue' : classDefinition.name, |
| parameters: <String>['const ::flutter::EncodableList& list'], |
| isStatic: true, |
| ); |
| _writeFunctionDeclaration( |
| indent, |
| 'ToEncodableList', |
| returnType: '::flutter::EncodableList', |
| isConst: true, |
| ); |
| if (isOverflowClass) { |
| _writeFunctionDeclaration(indent, 'Unwrap', returnType: '::flutter::EncodableValue'); |
| } |
| if (!isOverflowClass && root.requiresOverflowClass) { |
| indent.writeln('friend class $_overflowClassName;'); |
| } |
| for (final Class friend in root.classes) { |
| if (friend != classDefinition && |
| friend.fields.any( |
| (NamedType element) => element.type.baseName == classDefinition.name, |
| )) { |
| indent.writeln('friend class ${friend.name};'); |
| } |
| } |
| for (final Api api in root.apis.where( |
| (Api api) => api is AstFlutterApi || api is AstHostApi, |
| )) { |
| // TODO(gaaclarke): Find a way to be more precise with our |
| // friendships. |
| indent.writeln('friend class ${api.name};'); |
| } |
| indent.writeln('friend class $_codecSerializerName;'); |
| if (testFixtureClass != null) { |
| indent.writeln('friend class $testFixtureClass;'); |
| } |
| |
| for (final field in orderedFields) { |
| final HostDatatype hostDatatype = getFieldHostDatatype( |
| field, |
| _baseCppTypeForBuiltinDartType, |
| ); |
| indent.writeln('${_fieldType(hostDatatype)} ${_makeInstanceVariableName(field)};'); |
| } |
| }); |
| }, nestCount: 0); |
| indent.newln(); |
| } |
| |
| @override |
| void writeGeneralCodec( |
| InternalCppOptions generatorOptions, |
| Root root, |
| Indent indent, { |
| required String dartPackageName, |
| }) { |
| indent.newln(); |
| indent.write('class $_codecSerializerName : public $_standardCodecSerializer '); |
| indent.addScoped('{', '};', () { |
| _writeAccessBlock(indent, _ClassAccess.public, () { |
| _writeFunctionDeclaration(indent, _codecSerializerName, isConstructor: true); |
| _writeFunctionDeclaration( |
| indent, |
| 'GetInstance', |
| returnType: '$_codecSerializerName&', |
| isStatic: true, |
| inlineBody: () { |
| indent.writeln('static $_codecSerializerName 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(); |
| } |
| |
| @override |
| void writeFlutterApi( |
| InternalCppOptions generatorOptions, |
| Root root, |
| Indent indent, |
| AstFlutterApi api, { |
| required String dartPackageName, |
| }) { |
| const 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, |
| api.name, |
| parameters: <String>[ |
| '::flutter::BinaryMessenger* binary_messenger', |
| 'const std::string& message_channel_suffix', |
| ], |
| ); |
| _writeFunctionDeclaration( |
| indent, |
| 'GetCodec', |
| returnType: 'const ::flutter::StandardMessageCodec&', |
| isStatic: true, |
| ); |
| for (final Method func in api.methods) { |
| final HostDatatype returnType = getHostDatatype( |
| func.returnType, |
| _baseCppTypeForBuiltinDartType, |
| ); |
| addDocumentationComments(indent, func.documentationComments, _docCommentSpec); |
| |
| final Iterable<String> argTypes = func.parameters.map((NamedType arg) { |
| final HostDatatype hostType = getFieldHostDatatype(arg, _baseCppTypeForBuiltinDartType); |
| return _flutterApiArgumentType(hostType); |
| }); |
| final Iterable<String> argNames = indexMap(func.parameters, _getArgumentName); |
| final 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_;'); |
| indent.writeln('std::string message_channel_suffix_;'); |
| }); |
| }, nestCount: 0); |
| indent.newln(); |
| } |
| |
| @override |
| void writeHostApi( |
| InternalCppOptions generatorOptions, |
| Root root, |
| Indent indent, |
| AstHostApi api, { |
| required String dartPackageName, |
| }) { |
| const 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, |
| _baseCppTypeForBuiltinDartType, |
| ); |
| final String returnTypeName = _hostApiReturnType(returnType); |
| |
| final parameters = <String>[]; |
| if (method.parameters.isNotEmpty) { |
| final Iterable<String> argTypes = method.parameters.map((NamedType arg) { |
| final HostDatatype hostType = getFieldHostDatatype( |
| arg, |
| _baseCppTypeForBuiltinDartType, |
| ); |
| return _hostApiArgumentType(hostType); |
| }); |
| final Iterable<String> argNames = method.parameters.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, |
| 'SetUp', |
| returnType: _voidType, |
| isStatic: true, |
| parameters: <String>[ |
| '::flutter::BinaryMessenger* binary_messenger', |
| '${api.name}* api', |
| 'const std::string& message_channel_suffix', |
| ], |
| ); |
| _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 classDefinition, |
| Iterable<NamedType> params, |
| String docComment, |
| ) { |
| final List<String> paramStrings = params.map((NamedType param) { |
| final HostDatatype hostDatatype = getFieldHostDatatype(param, _baseCppTypeForBuiltinDartType); |
| return '${_hostApiArgumentType(hostDatatype)} ${_makeVariableName(param)}'; |
| }).toList(); |
| indent.writeln('$_commentPrefix $docComment'); |
| _writeFunctionDeclaration( |
| indent, |
| classDefinition.name, |
| isConstructor: true, |
| parameters: paramStrings, |
| ); |
| indent.newln(); |
| } |
| |
| void _writeFlutterError(Indent indent) { |
| 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_; |
| \t::flutter::EncodableValue details_; |
| };'''); |
| } |
| |
| void _writeErrorOr(Indent indent, {Iterable<String> friends = const <String>[]}) { |
| final String friendLines = friends |
| .map((String className) => '\tfriend class $className;') |
| .join('\n'); |
| indent.format(''' |
| |
| 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 writeConstants( |
| InternalCppOptions generatorOptions, |
| Root root, |
| Indent indent, { |
| required String dartPackageName, |
| }) { |
| if (root.constants.isEmpty) { |
| return; |
| } |
| indent.newln(); |
| for (final Constant constant in root.constants) { |
| addDocumentationComments(indent, constant.documentationComments, _docCommentSpec); |
| final String type = constant.type.baseName; |
| if (type == 'String') { |
| final String escaped = escapeStringDoubleQuotes(constant.value.toString()); |
| indent.writeln('inline constexpr const char* ${constant.name} = "$escaped";'); |
| } else { |
| final String cppType = _baseCppTypeForBuiltinDartType(constant.type) ?? 'auto'; |
| indent.writeln('inline constexpr $cppType ${constant.name} = ${constant.value};'); |
| } |
| } |
| } |
| |
| @override |
| void writeCloseNamespace( |
| InternalCppOptions 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<InternalCppOptions> { |
| /// Constructor. |
| const CppSourceGenerator(); |
| |
| @override |
| void writeFilePrologue( |
| InternalCppOptions 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( |
| InternalCppOptions 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>[ |
| 'cmath', |
| 'limits', |
| 'map', |
| 'string', |
| 'optional', |
| 'sstream', |
| ]); |
| indent.newln(); |
| } |
| |
| @override |
| void writeOpenNamespace( |
| InternalCppOptions generatorOptions, |
| Root root, |
| Indent indent, { |
| required String dartPackageName, |
| }) { |
| if (generatorOptions.namespace != null) { |
| indent.writeln('namespace ${generatorOptions.namespace} {'); |
| } |
| } |
| |
| @override |
| void writeGeneralUtilities( |
| InternalCppOptions generatorOptions, |
| Root root, |
| Indent indent, { |
| required String dartPackageName, |
| }) { |
| final usingDirectives = <String>[ |
| '::flutter::BasicMessageChannel', |
| '::flutter::CustomEncodableValue', |
| '::flutter::EncodableList', |
| '::flutter::EncodableMap', |
| '::flutter::EncodableValue', |
| ]; |
| usingDirectives.sort(); |
| for (final using in usingDirectives) { |
| indent.writeln('using $using;'); |
| } |
| indent.newln(); |
| _writeFunctionDefinition( |
| indent, |
| 'CreateConnectionError', |
| returnType: 'FlutterError', |
| parameters: <String>['const std::string channel_name'], |
| body: () { |
| indent.format(''' |
| return FlutterError( |
| "channel-error", |
| "Unable to establish connection on channel: '" + channel_name + "'.", |
| EncodableValue(""));'''); |
| }, |
| ); |
| indent.writeln('namespace {'); |
| _writeDeepEquals(indent); |
| _writeDeepHash(indent); |
| _writeDeepToString(indent); |
| indent.writeln('} // namespace'); |
| } |
| |
| @override |
| void writeDataClass( |
| InternalCppOptions generatorOptions, |
| Root root, |
| Indent indent, |
| Class classDefinition, { |
| required String dartPackageName, |
| }) { |
| indent.writeln('$_commentPrefix ${classDefinition.name}'); |
| indent.newln(); |
| |
| final Iterable<NamedType> orderedFields = getFieldsInSerializationOrder(classDefinition); |
| final Iterable<NamedType> requiredFields = orderedFields.where( |
| (NamedType type) => !type.type.isNullable, |
| ); |
| // Minimal constructor, if needed. |
| if (requiredFields.length != orderedFields.length) { |
| _writeClassConstructor(root, indent, classDefinition, requiredFields); |
| } |
| // All-field constructor. |
| _writeClassConstructor(root, indent, classDefinition, orderedFields); |
| |
| // Custom copy/assign to handle pointer fields, if necessary. |
| if (orderedFields.any( |
| (NamedType field) => |
| _isPointerField(getFieldHostDatatype(field, _baseCppTypeForBuiltinDartType)), |
| )) { |
| _writeCopyConstructor(root, indent, classDefinition, orderedFields); |
| _writeAssignmentOperator(root, indent, classDefinition, orderedFields); |
| } |
| |
| // Getters and setters. |
| for (final field in orderedFields) { |
| _writeCppSourceClassField(generatorOptions, root, indent, classDefinition, field); |
| } |
| |
| // Serialization. |
| writeClassEncode( |
| generatorOptions, |
| root, |
| indent, |
| classDefinition, |
| dartPackageName: dartPackageName, |
| ); |
| |
| // Deserialization. |
| writeClassDecode( |
| generatorOptions, |
| root, |
| indent, |
| classDefinition, |
| dartPackageName: dartPackageName, |
| ); |
| |
| _writeFunctionDefinition( |
| indent, |
| 'operator==', |
| scope: classDefinition.name, |
| returnType: 'bool', |
| parameters: <String>['const ${classDefinition.name}& other'], |
| isConst: true, |
| body: () { |
| final Iterable<String> checks = orderedFields.map((NamedType field) { |
| final String name = _makeInstanceVariableName(field); |
| return 'PigeonInternalDeepEquals($name, other.$name)'; |
| }); |
| if (checks.isEmpty) { |
| indent.writeln('return true;'); |
| } else { |
| indent.writeln('return ${checks.join(' && ')};'); |
| } |
| }, |
| ); |
| |
| _writeFunctionDefinition( |
| indent, |
| 'operator!=', |
| scope: classDefinition.name, |
| returnType: 'bool', |
| parameters: <String>['const ${classDefinition.name}& other'], |
| isConst: true, |
| body: () { |
| indent.writeln('return !(*this == other);'); |
| }, |
| ); |
| |
| _writeFunctionDefinition( |
| indent, |
| 'Hash', |
| scope: classDefinition.name, |
| returnType: 'size_t', |
| isConst: true, |
| body: () { |
| indent.writeln('size_t result = 1;'); |
| for (final field in orderedFields) { |
| final String name = _makeInstanceVariableName(field); |
| indent.writeln('result = result * 31 + PigeonInternalDeepHash($name);'); |
| } |
| indent.writeln('return result;'); |
| }, |
| ); |
| |
| _writeFunctionDefinition( |
| indent, |
| 'operator<<', |
| returnType: 'std::ostream&', |
| parameters: <String>['std::ostream& os', 'const ${classDefinition.name}& obj'], |
| body: () { |
| indent.writeln('os << "${classDefinition.name}(";'); |
| enumerate(orderedFields, (int index, NamedType field) { |
| final name = 'obj.${_makeInstanceVariableName(field)}'; |
| final comma = index == 0 ? '' : ', '; |
| indent.writeln('os << "$comma${_makeVariableName(field)}: ";'); |
| if (field.type.isNullable) { |
| indent.writeScoped('if ($name) {', '}', () { |
| if (field.type.isClass) { |
| indent.writeln('os << *$name;'); |
| } else { |
| indent.writeln('os << PigeonInternalToString(*$name);'); |
| } |
| }); |
| indent.writeScoped('else {', '}', () { |
| indent.writeln('os << "null";'); |
| }); |
| } else { |
| if (field.type.isClass) { |
| indent.writeln('os << $name;'); |
| } else { |
| indent.writeln('os << PigeonInternalToString($name);'); |
| } |
| } |
| }); |
| indent.writeln('os << ")";'); |
| indent.writeln('return os;'); |
| }, |
| ); |
| |
| _writeFunctionDefinition( |
| indent, |
| 'PigeonInternalDeepHash', |
| returnType: 'size_t', |
| parameters: <String>['const ${classDefinition.name}& v'], |
| body: () { |
| indent.writeln('return v.Hash();'); |
| }, |
| ); |
| } |
| |
| void _writeDeepEquals(Indent indent) { |
| indent.format(''' |
| template<typename T> |
| bool PigeonInternalDeepEquals(const T& a, const T& b); |
| |
| bool PigeonInternalDeepEquals(const double& a, const double& b); |
| |
| template<typename T> |
| bool PigeonInternalDeepEquals(const std::vector<T>& a, const std::vector<T>& b); |
| |
| template<typename K, typename V> |
| bool PigeonInternalDeepEquals(const std::map<K, V>& a, const std::map<K, V>& b); |
| |
| template<typename T> |
| bool PigeonInternalDeepEquals(const std::optional<T>& a, const std::optional<T>& b); |
| |
| template<typename T> |
| bool PigeonInternalDeepEquals(const std::unique_ptr<T>& a, const std::unique_ptr<T>& b); |
| |
| bool PigeonInternalDeepEquals(const ::flutter::EncodableValue& a, const ::flutter::EncodableValue& b); |
| |
| template<typename T> |
| bool PigeonInternalDeepEquals(const T& a, const T& b) { |
| return a == b; |
| } |
| |
| template<typename T> |
| bool PigeonInternalDeepEquals(const std::vector<T>& a, const std::vector<T>& b) { |
| if (a.size() != b.size()) { |
| return false; |
| } |
| for (size_t i = 0; i < a.size(); ++i) { |
| if (!PigeonInternalDeepEquals(a[i], b[i])) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| template <typename K, typename V> |
| bool PigeonInternalDeepEquals(const std::map<K, V>& a, const std::map<K, V>& b) { |
| if (a.size() != b.size()) { |
| return false; |
| } |
| for (const auto& kv : a) { |
| bool found = false; |
| for (const auto& b_kv : b) { |
| if (PigeonInternalDeepEquals(kv.first, b_kv.first)) { |
| if (PigeonInternalDeepEquals(kv.second, b_kv.second)) { |
| found = true; |
| break; |
| } else { |
| return false; |
| } |
| } |
| } |
| if (!found) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| bool PigeonInternalDeepEquals(const double& a, const double& b) { |
| // Normalize -0.0 to 0.0 and handle NaN equality. |
| return (a == b) || (std::isnan(a) && std::isnan(b)); |
| } |
| |
| template<typename T> |
| bool PigeonInternalDeepEquals(const std::optional<T>& a, const std::optional<T>& b) { |
| if (!a && !b) { |
| return true; |
| } |
| if (!a || !b) { |
| return false; |
| } |
| return PigeonInternalDeepEquals(*a, *b); |
| } |
| |
| template<typename T> |
| bool PigeonInternalDeepEquals(const std::unique_ptr<T>& a, const std::unique_ptr<T>& b) { |
| if (a.get() == b.get()) { |
| return true; |
| } |
| if (!a || !b) { |
| return false; |
| } |
| return PigeonInternalDeepEquals(*a, *b); |
| } |
| |
| bool PigeonInternalDeepEquals(const ::flutter::EncodableValue& a, const ::flutter::EncodableValue& b) { |
| if (a.index() != b.index()) { |
| return false; |
| } |
| if (const double* da = std::get_if<double>(&a)) { |
| return PigeonInternalDeepEquals(*da, std::get<double>(b)); |
| } else if (const ::flutter::EncodableList* la = std::get_if<::flutter::EncodableList>(&a)) { |
| return PigeonInternalDeepEquals(*la, std::get<::flutter::EncodableList>(b)); |
| } else if (const ::flutter::EncodableMap* ma = std::get_if<::flutter::EncodableMap>(&a)) { |
| return PigeonInternalDeepEquals(*ma, std::get<::flutter::EncodableMap>(b)); |
| } |
| return a == b; |
| } |
| '''); |
| } |
| |
| void _writeDeepHash(Indent indent) { |
| indent.format(''' |
| template <typename T> |
| size_t PigeonInternalDeepHash(const T& v); |
| |
| size_t PigeonInternalDeepHash(const double& v); |
| |
| template <typename T> |
| size_t PigeonInternalDeepHash(const std::vector<T>& v); |
| |
| template <typename K, typename V> |
| size_t PigeonInternalDeepHash(const std::map<K, V>& v); |
| |
| template <typename T> |
| size_t PigeonInternalDeepHash(const std::optional<T>& v); |
| |
| template <typename T> |
| size_t PigeonInternalDeepHash(const std::unique_ptr<T>& v); |
| |
| size_t PigeonInternalDeepHash(const ::flutter::EncodableValue& v); |
| |
| template <typename T> |
| size_t PigeonInternalDeepHash(const T& v) { |
| return std::hash<T>()(v); |
| } |
| |
| template <typename T> |
| size_t PigeonInternalDeepHash(const std::vector<T>& v) { |
| size_t result = 1; |
| for (const auto& item : v) { |
| result = result * 31 + PigeonInternalDeepHash(item); |
| } |
| return result; |
| } |
| |
| template <typename K, typename V> |
| size_t PigeonInternalDeepHash(const std::map<K, V>& v) { |
| size_t result = 0; |
| for (const auto& kv : v) { |
| result += ((PigeonInternalDeepHash(kv.first) * 31) ^ PigeonInternalDeepHash(kv.second)); |
| } |
| return result; |
| } |
| |
| size_t PigeonInternalDeepHash(const double& v) { |
| if (std::isnan(v)) { |
| // Normalize NaN to a consistent hash. |
| return std::hash<double>()(std::numeric_limits<double>::quiet_NaN()); |
| } |
| if (v == 0.0) { |
| // Normalize -0.0 to 0.0 so they have the same hash code. |
| return std::hash<double>()(0.0); |
| } |
| return std::hash<double>()(v); |
| } |
| |
| template <typename T> |
| size_t PigeonInternalDeepHash(const std::optional<T>& v) { |
| return v ? PigeonInternalDeepHash(*v) : 0; |
| } |
| |
| template <typename T> |
| size_t PigeonInternalDeepHash(const std::unique_ptr<T>& v) { |
| return v ? PigeonInternalDeepHash(*v) : 0; |
| } |
| |
| size_t PigeonInternalDeepHash(const ::flutter::EncodableValue& v) { |
| size_t result = v.index(); |
| if (const double* dv = std::get_if<double>(&v)) { |
| result = result * 31 + PigeonInternalDeepHash(*dv); |
| } else if (const ::flutter::EncodableList* lv = |
| std::get_if<::flutter::EncodableList>(&v)) { |
| result = result * 31 + PigeonInternalDeepHash(*lv); |
| } else if (const ::flutter::EncodableMap* mv = |
| std::get_if<::flutter::EncodableMap>(&v)) { |
| result = result * 31 + PigeonInternalDeepHash(*mv); |
| } else { |
| std::visit( |
| [&result](const auto& val) { |
| using T = std::decay_t<decltype(val)>; |
| if constexpr (!std::is_same_v<T, double> && |
| !std::is_same_v<T, ::flutter::EncodableList> && |
| !std::is_same_v<T, ::flutter::EncodableMap> && |
| !std::is_same_v<T, std::monostate> && |
| !std::is_same_v<T, ::flutter::CustomEncodableValue>) { |
| result = result * 31 + PigeonInternalDeepHash(val); |
| } |
| }, |
| v); |
| } |
| return result; |
| } |
| '''); |
| } |
| |
| void _writeDeepToString(Indent indent) { |
| indent.format(r''' |
| template <typename T> |
| std::string PigeonInternalToString(const T& v); |
| |
| std::string PigeonInternalToString(const bool& v); |
| |
| template <typename T> |
| std::string PigeonInternalToString(const std::vector<T>& v); |
| |
| template <typename K, typename V> |
| std::string PigeonInternalToString(const std::map<K, V>& v); |
| |
| template <typename T> |
| std::string PigeonInternalToString(const std::optional<T>& v); |
| |
| template <typename T> |
| std::string PigeonInternalToString(const std::unique_ptr<T>& v); |
| |
| std::string PigeonInternalToString(const ::flutter::EncodableValue& v); |
| |
| template <typename T> |
| std::string PigeonInternalToString(const T& v) { |
| std::stringstream ss; |
| if constexpr (std::is_enum_v<T>) { |
| ss << static_cast<int>(v); |
| } else { |
| ss << v; |
| } |
| return ss.str(); |
| } |
| |
| std::string PigeonInternalToString(const bool& v) { |
| return v ? "true" : "false"; |
| } |
| |
| template <typename T> |
| std::string PigeonInternalToString(const std::vector<T>& v) { |
| std::stringstream ss; |
| ss << "["; |
| for (size_t i = 0; i < v.size(); ++i) { |
| if (i > 0) { |
| ss << ", "; |
| } |
| ss << PigeonInternalToString(v[i]); |
| } |
| ss << "]"; |
| return ss.str(); |
| } |
| |
| template <typename K, typename V> |
| std::string PigeonInternalToString(const std::map<K, V>& v) { |
| std::stringstream ss; |
| ss << "{"; |
| bool first = true; |
| for (const auto& kv : v) { |
| if (!first) { |
| ss << ", "; |
| } |
| first = false; |
| ss << PigeonInternalToString(kv.first) << ": " << PigeonInternalToString(kv.second); |
| } |
| ss << "}"; |
| return ss.str(); |
| } |
| |
| template <typename T> |
| std::string PigeonInternalToString(const std::optional<T>& v) { |
| return v ? PigeonInternalToString(*v) : "null"; |
| } |
| |
| template <typename T> |
| std::string PigeonInternalToString(const std::unique_ptr<T>& v) { |
| return v ? PigeonInternalToString(*v) : "null"; |
| } |
| |
| std::string PigeonInternalToString(const ::flutter::EncodableValue& v) { |
| return std::visit( |
| [](const auto& val) { |
| using T = std::decay_t<decltype(val)>; |
| if constexpr (std::is_same_v<T, std::monostate>) { |
| return std::string("null"); |
| } else if constexpr (std::is_same_v<T, bool>) { |
| return val ? std::string("true") : std::string("false"); |
| } else if constexpr (std::is_same_v<T, std::string>) { |
| return "\"" + val + "\""; |
| } else if constexpr (std::is_same_v<T, ::flutter::CustomEncodableValue>) { |
| return std::string("[custom]"); |
| } else { |
| return PigeonInternalToString(val); |
| } |
| }, |
| v); |
| } |
| '''); |
| } |
| |
| @override |
| void writeClassEncode( |
| InternalCppOptions generatorOptions, |
| Root root, |
| Indent indent, |
| Class classDefinition, { |
| required String dartPackageName, |
| }) { |
| _writeFunctionDefinition( |
| indent, |
| 'ToEncodableList', |
| scope: classDefinition.name, |
| returnType: 'EncodableList', |
| isConst: true, |
| body: () { |
| indent.writeln('EncodableList list;'); |
| indent.writeln('list.reserve(${classDefinition.fields.length});'); |
| for (final NamedType field in getFieldsInSerializationOrder(classDefinition)) { |
| final HostDatatype hostDatatype = getFieldHostDatatype( |
| field, |
| _shortBaseCppTypeForBuiltinDartType, |
| ); |
| final String encodableValue = _wrappedHostApiArgumentExpression( |
| root, |
| _makeInstanceVariableName(field), |
| field.type, |
| hostDatatype, |
| true, |
| ); |
| indent.writeln('list.push_back($encodableValue);'); |
| } |
| indent.writeln('return list;'); |
| }, |
| ); |
| } |
| |
| @override |
| void writeClassDecode( |
| InternalCppOptions generatorOptions, |
| Root root, |
| Indent indent, |
| Class classDefinition, { |
| required String dartPackageName, |
| }) { |
| // Returns the expression to convert the given EncodableValue to a field |
| // value. |
| String getValueExpression(NamedType field, String encodable) { |
| if (field.type.baseName == 'Object') { |
| return encodable; |
| } else { |
| final HostDatatype hostDatatype = getFieldHostDatatype( |
| field, |
| _shortBaseCppTypeForBuiltinDartType, |
| ); |
| if (field.type.isClass || field.type.isEnum) { |
| return _classReferenceFromEncodableValue(hostDatatype, encodable); |
| } else { |
| return 'std::get<${hostDatatype.datatype}>($encodable)'; |
| } |
| } |
| } |
| |
| _writeFunctionDefinition( |
| indent, |
| 'FromEncodableList', |
| scope: classDefinition.name, |
| returnType: classDefinition.name, |
| parameters: <String>['const EncodableList& list'], |
| body: () { |
| const instanceVariable = 'decoded'; |
| final Iterable<_IndexedField> indexedFields = indexMap( |
| getFieldsInSerializationOrder(classDefinition), |
| (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('${classDefinition.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 entry in nullableFields) { |
| final NamedType field = entry.field; |
| final String setterName = _makeSetterName(field); |
| final 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;'); |
| }, |
| ); |
| } |
| |
| void _writeCodecOverflowUtilities( |
| InternalCppOptions generatorOptions, |
| Root root, |
| Indent indent, |
| List<EnumeratedType> types, { |
| required String dartPackageName, |
| }) { |
| _writeClassConstructor(root, indent, _overflowClass, _overflowFields); |
| // Getters and setters. |
| for (final NamedType field in _overflowFields) { |
| _writeCppSourceClassField(generatorOptions, root, indent, _overflowClass, field); |
| } |
| // Serialization. |
| writeClassEncode( |
| generatorOptions, |
| root, |
| indent, |
| _overflowClass, |
| dartPackageName: dartPackageName, |
| ); |
| |
| indent.format(''' |
| EncodableValue $_overflowClassName::FromEncodableList( |
| const EncodableList& list) { |
| return $_overflowClassName(list[0].LongValue(), |
| list[1].IsNull() ? EncodableValue() : list[1]) |
| .Unwrap(); |
| }'''); |
| |
| indent.writeScoped('EncodableValue $_overflowClassName::Unwrap() {', '}', () { |
| indent.writeScoped('if (wrapped_.IsNull()) {', '}', () { |
| indent.writeln('return EncodableValue();'); |
| }); |
| indent.writeScoped('switch(type_) {', '}', () { |
| for (int i = totalCustomCodecKeysAllowed; i < types.length; i++) { |
| indent.write('case ${types[i].enumeration - maximumCodecFieldKey}: '); |
| _writeCodecDecode(indent, types[i], 'wrapped_'); |
| } |
| }); |
| indent.writeln('return EncodableValue();'); |
| }); |
| } |
| |
| void _writeCodecDecode(Indent indent, EnumeratedType customType, String value) { |
| indent.addScoped('{', '}', () { |
| if (customType.type == CustomTypes.customClass) { |
| if (customType.name == _overflowClassName) { |
| indent.writeln( |
| 'return ${customType.name}::FromEncodableList(std::get<EncodableList>($value));', |
| ); |
| } else { |
| indent.writeln( |
| 'return CustomEncodableValue(${customType.name}::FromEncodableList(std::get<EncodableList>($value)));', |
| ); |
| } |
| } else if (customType.type == CustomTypes.customEnum) { |
| indent.writeln('const auto& encodable_enum_arg = $value;'); |
| indent.writeln( |
| 'const int64_t enum_arg_value = encodable_enum_arg.IsNull() ? 0 : encodable_enum_arg.LongValue();', |
| ); |
| indent.writeln( |
| 'return encodable_enum_arg.IsNull() ? EncodableValue() : CustomEncodableValue(static_cast<${customType.name}>(enum_arg_value));', |
| ); |
| } |
| }); |
| } |
| |
| @override |
| void writeGeneralCodec( |
| InternalCppOptions generatorOptions, |
| Root root, |
| Indent indent, { |
| required String dartPackageName, |
| }) { |
| final List<EnumeratedType> enumeratedTypes = getEnumeratedTypes( |
| root, |
| excludeSealedClasses: true, |
| ).toList(); |
| indent.newln(); |
| if (root.requiresOverflowClass) { |
| _writeCodecOverflowUtilities( |
| generatorOptions, |
| root, |
| indent, |
| enumeratedTypes, |
| dartPackageName: dartPackageName, |
| ); |
| } |
| _writeFunctionDefinition(indent, _codecSerializerName, scope: _codecSerializerName); |
| _writeFunctionDefinition( |
| indent, |
| 'ReadValueOfType', |
| scope: _codecSerializerName, |
| returnType: 'EncodableValue', |
| parameters: <String>['uint8_t type', '::flutter::ByteStreamReader* stream'], |
| isConst: true, |
| body: () { |
| if (enumeratedTypes.isNotEmpty) { |
| indent.writeln('switch (type) {'); |
| indent.inc(); |
| for (final customType in enumeratedTypes) { |
| if (customType.enumeration < maximumCodecFieldKey) { |
| indent.write('case ${customType.enumeration}: '); |
| indent.nest(1, () { |
| _writeCodecDecode(indent, customType, 'ReadValue(stream)'); |
| }); |
| } |
| } |
| if (root.requiresOverflowClass) { |
| indent.write('case $maximumCodecFieldKey:'); |
| _writeCodecDecode(indent, _enumeratedOverflow, 'ReadValue(stream)'); |
| } |
| indent.writeln('default:'); |
| indent.inc(); |
| } |
| indent.writeln('return $_standardCodecSerializer::ReadValueOfType(type, stream);'); |
| if (enumeratedTypes.isNotEmpty) { |
| indent.dec(); |
| indent.writeln('}'); |
| indent.dec(); |
| } |
| }, |
| ); |
| _writeFunctionDefinition( |
| indent, |
| 'WriteValue', |
| scope: _codecSerializerName, |
| returnType: _voidType, |
| parameters: <String>['const EncodableValue& value', '::flutter::ByteStreamWriter* stream'], |
| isConst: true, |
| body: () { |
| if (enumeratedTypes.isNotEmpty) { |
| indent.write( |
| 'if (const CustomEncodableValue* custom_value = std::get_if<CustomEncodableValue>(&value)) ', |
| ); |
| indent.addScoped('{', '}', () { |
| for (final customType in enumeratedTypes) { |
| final encodeString = customType.type == CustomTypes.customClass |
| ? 'std::any_cast<${customType.name}>(*custom_value).ToEncodableList()' |
| : 'static_cast<int>(std::any_cast<${customType.name}>(*custom_value))'; |
| final valueString = customType.enumeration < maximumCodecFieldKey |
| ? encodeString |
| : 'wrap.ToEncodableList()'; |
| final int enumeration = customType.enumeration < maximumCodecFieldKey |
| ? customType.enumeration |
| : maximumCodecFieldKey; |
| indent.write('if (custom_value->type() == typeid(${customType.name})) '); |
| indent.addScoped('{', '}', () { |
| indent.writeln('stream->WriteByte($enumeration);'); |
| if (enumeration == maximumCodecFieldKey) { |
| indent.writeln( |
| 'const auto wrap = $_overflowClassName(${customType.enumeration - maximumCodecFieldKey}, $encodeString);', |
| ); |
| } |
| indent.writeln('WriteValue(EncodableValue($valueString), stream);'); |
| indent.writeln('return;'); |
| }); |
| } |
| }); |
| } |
| indent.writeln('$_standardCodecSerializer::WriteValue(value, stream);'); |
| }, |
| ); |
| } |
| |
| @override |
| void writeFlutterApi( |
| InternalCppOptions generatorOptions, |
| Root root, |
| Indent indent, |
| AstFlutterApi api, { |
| required String dartPackageName, |
| }) { |
| 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)', 'message_channel_suffix_("")'], |
| ); |
| _writeFunctionDefinition( |
| indent, |
| api.name, |
| scope: api.name, |
| parameters: <String>[ |
| '::flutter::BinaryMessenger* binary_messenger', |
| 'const std::string& message_channel_suffix', |
| ], |
| initializers: <String>[ |
| 'binary_messenger_(binary_messenger)', |
| 'message_channel_suffix_(message_channel_suffix.length() > 0 ? std::string(".") + message_channel_suffix : "")', |
| ], |
| ); |
| _writeFunctionDefinition( |
| indent, |
| 'GetCodec', |
| scope: api.name, |
| returnType: 'const ::flutter::StandardMessageCodec&', |
| body: () { |
| indent.writeln( |
| 'return ::flutter::StandardMessageCodec::GetInstance(&$_codecSerializerName::GetInstance());', |
| ); |
| }, |
| ); |
| for (final Method func in api.methods) { |
| final HostDatatype returnType = getHostDatatype( |
| func.returnType, |
| _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.parameters, ( |
| int i, |
| NamedType arg, |
| ) { |
| final HostDatatype hostType = getFieldHostDatatype( |
| arg, |
| _shortBaseCppTypeForBuiltinDartType, |
| ); |
| return _HostNamedType(_getSafeArgumentName(i, arg), hostType, arg.type); |
| }); |
| final 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: () { |
| indent.writeln( |
| 'const std::string channel_name = "${makeChannelName(api, func, dartPackageName)}" + message_channel_suffix_;', |
| ); |
| indent.writeln( |
| 'BasicMessageChannel<> channel(binary_messenger_, ' |
| 'channel_name, &GetCodec());', |
| ); |
| |
| // Convert arguments to EncodableValue versions. |
| const argumentListVariableName = 'encoded_api_arguments'; |
| indent.write('EncodableValue $argumentListVariableName = '); |
| if (func.parameters.isEmpty) { |
| indent.addln('EncodableValue();'); |
| } else { |
| indent.addScoped('EncodableValue(EncodableList{', '});', () { |
| for (final param in hostParameters) { |
| final String encodedArgument = _wrappedHostApiArgumentExpression( |
| root, |
| param.name, |
| param.originalType, |
| param.hostType, |
| false, |
| ); |
| indent.writeln('$encodedArgument,'); |
| } |
| }); |
| } |
| |
| indent.write( |
| 'channel.Send($argumentListVariableName, ' |
| // ignore: missing_whitespace_between_adjacent_strings |
| '[channel_name, on_success = std::move(on_success), on_error = std::move(on_error)]' |
| '(const uint8_t* reply, size_t reply_size) ', |
| ); |
| indent.addScoped('{', '});', () { |
| String successCallbackArgument; |
| successCallbackArgument = 'return_value'; |
| final encodedReplyName = 'encodable_$successCallbackArgument'; |
| final listReplyName = 'list_$successCallbackArgument'; |
| indent.writeln( |
| 'std::unique_ptr<EncodableValue> response = GetCodec().DecodeMessage(reply, reply_size);', |
| ); |
| indent.writeln('const auto& $encodedReplyName = *response;'); |
| indent.writeln( |
| 'const auto* $listReplyName = std::get_if<EncodableList>(&$encodedReplyName);', |
| ); |
| indent.writeScoped('if ($listReplyName) {', '} ', () { |
| indent.writeScoped('if ($listReplyName->size() > 1) {', '} ', () { |
| indent.writeln( |
| 'on_error(FlutterError(std::get<std::string>($listReplyName->at(0)), std::get<std::string>($listReplyName->at(1)), $listReplyName->at(2)));', |
| ); |
| }, addTrailingNewline: false); |
| indent.addScoped('else {', '}', () { |
| if (func.returnType.isVoid) { |
| successCallbackArgument = ''; |
| } else { |
| _writeEncodableValueArgumentUnwrapping( |
| indent, |
| root, |
| returnType, |
| argName: successCallbackArgument, |
| encodableArgName: '$listReplyName->at(0)', |
| apiType: ApiType.flutter, |
| ); |
| } |
| indent.writeln('on_success($successCallbackArgument);'); |
| }); |
| }, addTrailingNewline: false); |
| indent.addScoped('else {', '} ', () { |
| indent.writeln('on_error(CreateConnectionError(channel_name));'); |
| }); |
| }); |
| }, |
| ); |
| } |
| } |
| |
| @override |
| void writeHostApi( |
| InternalCppOptions generatorOptions, |
| Root root, |
| Indent indent, |
| AstHostApi api, { |
| required String dartPackageName, |
| }) { |
| 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(&$_codecSerializerName::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: () { |
| indent.writeln('${api.name}::SetUp(binary_messenger, api, "");'); |
| }, |
| ); |
| _writeFunctionDefinition( |
| indent, |
| 'SetUp', |
| scope: api.name, |
| returnType: _voidType, |
| parameters: <String>[ |
| '::flutter::BinaryMessenger* binary_messenger', |
| '${api.name}* api', |
| 'const std::string& message_channel_suffix', |
| ], |
| body: () { |
| indent.writeln( |
| 'const std::string prepended_suffix = message_channel_suffix.length() > 0 ? std::string(".") + message_channel_suffix : "";', |
| ); |
| for (final Method method in api.methods) { |
| final String channelName = makeChannelName(api, method, dartPackageName); |
| indent.writeScoped('{', '}', () { |
| indent.writeln( |
| 'BasicMessageChannel<> channel(binary_messenger, ' |
| '"$channelName" + prepended_suffix, &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 methodArgument = <String>[]; |
| if (method.parameters.isNotEmpty) { |
| indent.writeln('const auto& args = std::get<EncodableList>(message);'); |
| |
| enumerate(method.parameters, (int index, NamedType arg) { |
| final HostDatatype hostType = getHostDatatype( |
| arg.type, |
| (TypeDeclaration x) => _shortBaseCppTypeForBuiltinDartType(x), |
| ); |
| final String argName = _getSafeArgumentName(index, arg); |
| |
| final 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 unwrapEnum = arg.type.isEnum && arg.type.isNullable |
| ? ' ? &(*$argName) : nullptr' |
| : ''; |
| methodArgument.add('$argName$unwrapEnum'); |
| }); |
| } |
| |
| final HostDatatype returnType = getHostDatatype( |
| method.returnType, |
| _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 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 _writeClassConstructor( |
| Root root, |
| Indent indent, |
| Class classDefinition, |
| Iterable<NamedType> params, |
| ) { |
| final Iterable<_HostNamedType> hostParams = params.map((NamedType param) { |
| return _HostNamedType( |
| _makeVariableName(param), |
| getFieldHostDatatype(param, _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, |
| classDefinition.name, |
| scope: classDefinition.name, |
| parameters: paramStrings, |
| initializers: initializerStrings, |
| ); |
| } |
| |
| void _writeCopyConstructor( |
| Root root, |
| Indent indent, |
| Class classDefinition, |
| Iterable<NamedType> fields, |
| ) { |
| final List<String> initializerStrings = fields.map((NamedType param) { |
| final String fieldName = _makeInstanceVariableName(param); |
| final HostDatatype hostType = getFieldHostDatatype( |
| param, |
| _shortBaseCppTypeForBuiltinDartType, |
| ); |
| return '$fieldName(${_fieldValueExpression(hostType, 'other.$fieldName', sourceIsField: true)})'; |
| }).toList(); |
| _writeFunctionDefinition( |
| indent, |
| classDefinition.name, |
| scope: classDefinition.name, |
| parameters: <String>['const ${classDefinition.name}& other'], |
| initializers: initializerStrings, |
| ); |
| } |
| |
| void _writeAssignmentOperator( |
| Root root, |
| Indent indent, |
| Class classDefinition, |
| Iterable<NamedType> fields, |
| ) { |
| _writeFunctionDefinition( |
| indent, |
| 'operator=', |
| scope: classDefinition.name, |
| returnType: '${classDefinition.name}&', |
| parameters: <String>['const ${classDefinition.name}& other'], |
| body: () { |
| for (final field in fields) { |
| final HostDatatype hostDatatype = getFieldHostDatatype( |
| field, |
| _shortBaseCppTypeForBuiltinDartType, |
| ); |
| |
| final String ivarName = _makeInstanceVariableName(field); |
| final otherIvar = 'other.$ivarName'; |
| final String valueExpression; |
| if (_isPointerField(hostDatatype)) { |
| final constructor = 'std::make_unique<${hostDatatype.datatype}>(*$otherIvar)'; |
| valueExpression = hostDatatype.isNullable |
| ? '$otherIvar ? $constructor : nullptr' |
| : constructor; |
| } else { |
| valueExpression = otherIvar; |
| } |
| indent.writeln('$ivarName = $valueExpression;'); |
| } |
| indent.writeln('return *this;'); |
| }, |
| ); |
| } |
| |
| void _writeCppSourceClassField( |
| InternalCppOptions generatorOptions, |
| Root root, |
| Indent indent, |
| Class classDefinition, |
| NamedType field, |
| ) { |
| final HostDatatype hostDatatype = getFieldHostDatatype( |
| field, |
| _shortBaseCppTypeForBuiltinDartType, |
| ); |
| final String instanceVariableName = _makeInstanceVariableName(field); |
| final String setterName = _makeSetterName(field); |
| final String returnExpression; |
| if (_isPointerField(hostDatatype)) { |
| // Convert std::unique_ptr<T> to either T* or const T&. |
| returnExpression = hostDatatype.isNullable |
| ? '$instanceVariableName.get()' |
| : '*$instanceVariableName'; |
| } else if (hostDatatype.isNullable) { |
| // Convert std::optional<T> to T*. |
| returnExpression = '$instanceVariableName ? &(*$instanceVariableName) : nullptr'; |
| } else { |
| returnExpression = instanceVariableName; |
| } |
| |
| // Writes a setter treating the type as [type], to allow generating multiple |
| // setter variants. |
| void writeSetter(HostDatatype type) { |
| const setterArgumentName = 'value_arg'; |
| _writeFunctionDefinition( |
| indent, |
| setterName, |
| scope: classDefinition.name, |
| returnType: _voidType, |
| parameters: <String>['${_unownedArgumentType(type)} $setterArgumentName'], |
| body: () { |
| indent.writeln( |
| '$instanceVariableName = ${_fieldValueExpression(type, setterArgumentName)};', |
| ); |
| }, |
| ); |
| } |
| |
| _writeFunctionDefinition( |
| indent, |
| _makeGetterName(field), |
| scope: classDefinition.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 and non-custom-class values this is just the variable |
| /// itself, but for other values this handles the conversion between an |
| /// argument type (a pointer or value/reference) and the field type |
| /// (a std::optional or std::unique_ptr). |
| String _fieldValueExpression(HostDatatype type, String variable, {bool sourceIsField = false}) { |
| if (_isPointerField(type)) { |
| final constructor = 'std::make_unique<${type.datatype}>'; |
| // If the source is a pointer field, it always needs dereferencing. |
| final maybeDereference = sourceIsField ? '*' : ''; |
| return type.isNullable |
| ? '$variable ? $constructor(*$variable) : nullptr' |
| : '$constructor($maybeDereference$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 nullValue = 'EncodableValue()'; |
| if (returnType.isVoid) { |
| nonErrorPath = '${prefix}wrapped.push_back($nullValue);'; |
| errorCondition = 'output.has_value()'; |
| errorGetter = 'value'; |
| } else { |
| final HostDatatype hostType = getHostDatatype( |
| returnType, |
| _shortBaseCppTypeForBuiltinDartType, |
| ); |
| |
| const extractedValue = 'std::move(output).TakeValue()'; |
| final wrapperType = hostType.isBuiltin ? '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(std::move(output_optional).value())); |
| $prefix} else { |
| $prefix\twrapped.push_back($nullValue); |
| $prefix}'''; |
| } else { |
| nonErrorPath = '${prefix}wrapped.push_back($wrapperType($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( |
| InternalCppOptions 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. |
| String _wrappedHostApiArgumentExpression( |
| Root root, |
| String variableName, |
| TypeDeclaration dartType, |
| HostDatatype hostType, |
| bool isNestedClass, |
| ) { |
| final String encodableValue; |
| if (!hostType.isBuiltin) { |
| final nonNullValue = hostType.isNullable || (!hostType.isEnum && isNestedClass) |
| ? '*$variableName' |
| : variableName; |
| encodableValue = 'CustomEncodableValue($nonNullValue)'; |
| } else if (dartType.baseName == 'Object') { |
| final operator = hostType.isNullable ? '*' : ''; |
| encodableValue = '$operator$variableName'; |
| } else { |
| final 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 == '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) { |
| indent.format(''' |
| ${hostType.datatype} ${argName}_value; |
| const ${hostType.datatype}* $argName = nullptr; |
| if (!$encodableArgName.IsNull()) { |
| ${argName}_value = ${_classReferenceFromEncodableValue(hostType, encodableArgName)}; |
| $argName = &${argName}_value; |
| }'''); |
| } else { |
| indent.writeln( |
| 'const auto* $argName = $encodableArgName.IsNull() ? nullptr : &(${_classReferenceFromEncodableValue(hostType, 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 { |
| indent.writeln( |
| 'const auto& $argName = ${_classReferenceFromEncodableValue(hostType, 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); |
| } |
| |
| /// Returns the code to extract a `const {type.datatype}&` from an EncodableValue |
| /// variable [variableName] that contains an instance of [type]. |
| String _classReferenceFromEncodableValue(HostDatatype type, String variableName) { |
| return 'std::any_cast<const ${type.datatype}&>(std::get<CustomEncodableValue>($variableName))'; |
| } |
| } |
| |
| /// 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; |
| } |
| |
| 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 flutterNamespace = includeFlutterNamespace ? '::flutter::' : ''; |
| final 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 when declaring a data class field for the |
| /// given type. |
| String _fieldType(HostDatatype type) { |
| return _isPointerField(type) ? 'std::unique_ptr<${type.datatype}>' : _valueType(type); |
| } |
| |
| /// Returns true if [type] should be stored as a pointer, rather than a |
| /// value type, in a data class. |
| bool _isPointerField(HostDatatype type) { |
| // Custom class types are stored as `unique_ptr`s since they can have |
| // arbitrary size, and can also be arbitrarily (including recursively) |
| // nested, so must be stored as pointers. |
| return !type.isBuiltin && !type.isEnum; |
| } |
| |
| /// Returns the C++ type to use in an argument context without ownership |
| /// transfer for the given base type. |
| String _unownedArgumentType(HostDatatype type) { |
| final 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 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 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 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, 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 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 isCopy = false, |
| bool isPureVirtual = false, |
| bool isConst = false, |
| bool isOverride = false, |
| bool deleted = false, |
| bool defaultImpl = false, |
| bool inlineNoop = false, |
| bool noexcept = 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'); |
| assert(!(deleted && defaultImpl), 'a function cannot be deleted and default'); |
| _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 && !isCopy) 'explicit', |
| ], |
| trailingAnnotations: <String>[ |
| if (isConst) 'const', |
| if (noexcept) 'noexcept', |
| if (isOverride) 'override', |
| if (deleted) '= delete', |
| if (defaultImpl) '= default', |
| 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'; |
| case _ClassAccess.protected: |
| accessLabel = 'protected'; |
| case _ClassAccess.private: |
| accessLabel = 'private'; |
| } |
| indent.addScoped(' $accessLabel:', '', body); |
| } |
| |
| /// Validates an AST to make sure the cpp generator supports everything. |
| List<Error> validateCpp(InternalCppOptions options, Root root) { |
| final result = <Error>[]; |
| for (final Api api in root.apis) { |
| for (final Method method in api.methods) { |
| for (final NamedType arg in method.parameters) { |
| if (arg.type.isEnum) { |
| // 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; |
| } |