| // Copyright 2020 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 'generator_tools.dart'; |
| |
| /// Options that control how Objective-C code will be generated. |
| class ObjcOptions { |
| /// Parametric constructor for ObjcOptions. |
| ObjcOptions({this.header, this.prefix}); |
| |
| /// The path to the header that will get placed in the source filed (example: |
| /// "foo.h"). |
| String header; |
| |
| /// Prefix that will be appended before all generated classes and protocols. |
| String prefix; |
| } |
| |
| String _className(String prefix, String className) { |
| if (prefix != null) { |
| return '$prefix$className'; |
| } else { |
| return className; |
| } |
| } |
| |
| const Map<String, String> _objcTypeForDartTypeMap = <String, String>{ |
| 'bool': 'NSNumber *', |
| 'int': 'NSNumber *', |
| 'String': 'NSString *', |
| 'double': 'NSNumber *', |
| 'Uint8List': 'FlutterStandardTypedData *', |
| 'Int32List': 'FlutterStandardTypedData *', |
| 'Int64List': 'FlutterStandardTypedData *', |
| 'Float64List': 'FlutterStandardTypedData *', |
| }; |
| |
| const Map<String, String> _propertyTypeForDartTypeMap = <String, String>{ |
| 'String': 'copy', |
| 'bool': 'strong', |
| 'int': 'strong', |
| 'double': 'strong', |
| 'Uint8List': 'strong', |
| 'Int32List': 'strong', |
| 'Int64List': 'strong', |
| 'Float64List': 'strong', |
| }; |
| |
| String _objcTypeForDartType(String type) { |
| return _objcTypeForDartTypeMap[type]; |
| } |
| |
| String _propertyTypeForDartType(String type) { |
| final String result = _propertyTypeForDartTypeMap[type]; |
| if (result == null) { |
| return 'assign'; |
| } else { |
| return result; |
| } |
| } |
| |
| /// Generates the ".h" file for the AST represented by [root] to [sink] with the |
| /// provided [options]. |
| void generateObjcHeader(ObjcOptions options, Root root, StringSink sink) { |
| final Indent indent = Indent(sink); |
| indent.writeln('// Autogenerated from Dartle.'); |
| indent.writeln('#import <Foundation/Foundation.h>'); |
| indent.writeln('@protocol FlutterBinaryMessenger;'); |
| indent.writeln('@class FlutterStandardTypedData;'); |
| indent.writeln(''); |
| |
| for (Class klass in root.classes) { |
| indent.writeln('@class ${_className(options.prefix, klass.name)};'); |
| } |
| |
| indent.writeln(''); |
| |
| for (Class klass in root.classes) { |
| indent.writeln( |
| '@interface ${_className(options.prefix, klass.name)} : NSObject '); |
| for (Field field in klass.fields) { |
| final HostDatatype hostDatatype = getHostDatatype( |
| field, |
| root.classes, |
| _objcTypeForDartType, |
| (String x) => '${_className(options.prefix, x)} *'); |
| final String propertyType = hostDatatype.isBuiltin |
| ? _propertyTypeForDartType(field.dataType) |
| : 'strong'; |
| indent.writeln( |
| '@property(nonatomic, $propertyType) ${hostDatatype.datatype} ${field.name};'); |
| } |
| indent.writeln('@end'); |
| indent.writeln(''); |
| } |
| |
| for (Api api in root.apis) { |
| final String apiName = _className(options.prefix, api.name); |
| if (api.location == ApiLocation.host) { |
| indent.writeln('@protocol $apiName'); |
| for (Method func in api.methods) { |
| final String returnType = _className(options.prefix, func.returnType); |
| final String argType = _className(options.prefix, func.argType); |
| indent.writeln('-($returnType *)${func.name}:($argType*)input;'); |
| } |
| indent.writeln('@end'); |
| indent.writeln(''); |
| indent.writeln( |
| 'extern void ${apiName}Setup(id<FlutterBinaryMessenger> binaryMessenger, id<$apiName> api);'); |
| indent.writeln(''); |
| } else if (api.location == ApiLocation.flutter) { |
| indent.writeln('@interface $apiName : NSObject'); |
| indent.writeln( |
| '- (instancetype)initWithBinaryMessenger:(id<FlutterBinaryMessenger>)binaryMessenger;'); |
| for (Method func in api.methods) { |
| final String returnType = _className(options.prefix, func.returnType); |
| final String argType = _className(options.prefix, func.argType); |
| indent.writeln( |
| '- (void)${func.name}:($argType*)input completion:(void(^)($returnType*, NSError*))completion;'); |
| } |
| indent.writeln('@end'); |
| } |
| } |
| } |
| |
| String _dictGetter( |
| List<String> classnames, String dict, Field field, String prefix) { |
| if (classnames.contains(field.dataType)) { |
| String className = field.dataType; |
| if (prefix != null) { |
| className = '$prefix$className'; |
| } |
| return '[$className fromMap:$dict[@"${field.name}"]]'; |
| } else { |
| return '$dict[@"${field.name}"]'; |
| } |
| } |
| |
| String _dictValue(List<String> classnames, Field field) { |
| if (classnames.contains(field.dataType)) { |
| return '[self.${field.name} toMap]'; |
| } else { |
| return 'self.${field.name}'; |
| } |
| } |
| |
| void _writeHostApiSource(Indent indent, ObjcOptions options, Api api) { |
| assert(api.location == ApiLocation.host); |
| final String apiName = _className(options.prefix, api.name); |
| indent.write( |
| 'void ${apiName}Setup(id<FlutterBinaryMessenger> binaryMessenger, id<$apiName> api) '); |
| indent.scoped('{', '}', () { |
| for (Method func in api.methods) { |
| indent.write(''); |
| indent.scoped('{', '}', () { |
| indent.writeln('FlutterBasicMessageChannel *channel ='); |
| indent.inc(); |
| indent.writeln('[FlutterBasicMessageChannel'); |
| indent.inc(); |
| indent |
| .writeln('messageChannelWithName:@"${makeChannelName(api, func)}"'); |
| indent.writeln('binaryMessenger:binaryMessenger];'); |
| indent.dec(); |
| indent.dec(); |
| |
| indent.write( |
| '[channel setMessageHandler:^(id _Nullable message, FlutterReply callback) '); |
| indent.scoped('{', '}];', () { |
| final String argType = _className(options.prefix, func.argType); |
| final String returnType = _className(options.prefix, func.returnType); |
| indent.writeln('$argType *input = [$argType fromMap:message];'); |
| indent.writeln('$returnType *output = [api ${func.name}:input];'); |
| indent.writeln('callback([output toMap]);'); |
| }); |
| }); |
| } |
| }); |
| } |
| |
| void _writeFlutterApiSource(Indent indent, ObjcOptions options, Api api) { |
| assert(api.location == ApiLocation.flutter); |
| final String apiName = _className(options.prefix, api.name); |
| indent.writeln('@interface $apiName ()'); |
| indent.writeln( |
| '@property (nonatomic, strong) NSObject<FlutterBinaryMessenger>* binaryMessenger;'); |
| indent.writeln('@end'); |
| indent.addln(''); |
| indent.writeln('@implementation $apiName'); |
| indent.write( |
| '- (instancetype)initWithBinaryMessenger:(NSObject<FlutterBinaryMessenger>*)binaryMessenger '); |
| indent.scoped('{', '}', () { |
| indent.writeln('self = [super init];'); |
| indent.write('if (self) '); |
| indent.scoped('{', '}', () { |
| indent.writeln('self.binaryMessenger = binaryMessenger;'); |
| }); |
| indent.writeln('return self;'); |
| }); |
| indent.addln(''); |
| for (Method func in api.methods) { |
| final String returnType = _className(options.prefix, func.returnType); |
| final String argType = _className(options.prefix, func.argType); |
| indent.write( |
| '- (void)${func.name}:($argType*)input completion:(void(^)($returnType*, NSError*))completion '); |
| indent.scoped('{', '}', () { |
| indent.writeln('FlutterBasicMessageChannel *channel ='); |
| indent.inc(); |
| indent.writeln('[FlutterBasicMessageChannel'); |
| indent.inc(); |
| indent.writeln('messageChannelWithName:@"${makeChannelName(api, func)}"'); |
| indent.writeln('binaryMessenger:self.binaryMessenger];'); |
| indent.dec(); |
| indent.dec(); |
| indent.writeln('NSDictionary* inputMap = [input toMap];'); |
| indent.write('[channel sendMessage:inputMap reply:^(id reply) '); |
| indent.scoped('{', '}];', () { |
| indent.writeln('NSDictionary* outputMap = reply;'); |
| indent |
| .writeln('$returnType * output = [$returnType fromMap:outputMap];'); |
| indent.writeln('completion(output, nil);'); |
| }); |
| }); |
| } |
| indent.writeln('@end'); |
| } |
| |
| /// Generates the ".m" file for the AST represented by [root] to [sink] with the |
| /// provided [options]. |
| void generateObjcSource(ObjcOptions options, Root root, StringSink sink) { |
| final Indent indent = Indent(sink); |
| final List<String> classnames = |
| root.classes.map((Class x) => x.name).toList(); |
| |
| indent.writeln('// Autogenerated from Dartle.'); |
| indent.writeln('#import "${options.header}"'); |
| indent.writeln('#import <Flutter/Flutter.h>'); |
| indent.writeln(''); |
| |
| for (Class klass in root.classes) { |
| final String className = _className(options.prefix, klass.name); |
| indent.writeln('@interface $className ()'); |
| indent.writeln('+($className*)fromMap:(NSDictionary*)dict;'); |
| indent.writeln('-(NSDictionary*)toMap;'); |
| indent.writeln('@end'); |
| } |
| |
| indent.writeln(''); |
| |
| for (Class klass in root.classes) { |
| final String className = _className(options.prefix, klass.name); |
| indent.writeln('@implementation $className'); |
| indent.write('+($className*)fromMap:(NSDictionary*)dict '); |
| indent.scoped('{', '}', () { |
| indent.writeln('$className* result = [[$className alloc] init];'); |
| for (Field field in klass.fields) { |
| indent.writeln( |
| 'result.${field.name} = ${_dictGetter(classnames, 'dict', field, options.prefix)};'); |
| } |
| indent.writeln('return result;'); |
| }); |
| indent.write('-(NSDictionary*)toMap '); |
| indent.scoped('{', '}', () { |
| indent.write('return [NSDictionary dictionaryWithObjectsAndKeys:'); |
| for (Field field in klass.fields) { |
| indent.add(_dictValue(classnames, field) + ', @"${field.name}", '); |
| } |
| indent.addln('nil];'); |
| }); |
| indent.writeln('@end'); |
| indent.writeln(''); |
| } |
| |
| for (Api api in root.apis) { |
| if (api.location == ApiLocation.host) { |
| _writeHostApiSource(indent, options, api); |
| } else if (api.location == ApiLocation.flutter) { |
| _writeFlutterApiSource(indent, options, api); |
| } |
| } |
| } |