OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. |
| 4 |
| 5 /** |
| 6 * This file contains code to generate serialization/deserialization logic for |
| 7 * summaries based on an "IDL" description of the summary format (written in |
| 8 * stylized Dart). |
| 9 * |
| 10 * For each class in the "IDL" input, two corresponding classes are generated: |
| 11 * - A class with the same name which represents deserialized summary data in |
| 12 * memory. This class has read-only semantics. |
| 13 * - A "builder" class which can be used to generate serialized summary data. |
| 14 * This class has write-only semantics. |
| 15 * |
| 16 * Each of the "builder" classess has a single `finish` method which finalizes |
| 17 * the entity being built and returns it as an [Object]. This object should |
| 18 * only be passed to other builders (or to [BuilderContext.getBuffer]); |
| 19 * otherwise the client should treat it as opaque, since it exposes |
| 20 * implementation details of the underlying summary infrastructure. |
| 21 */ |
| 22 library analyzer.tool.summary.generate; |
| 23 |
| 24 import 'dart:convert'; |
| 25 import 'dart:io' hide File; |
| 26 |
| 27 import 'package:analyzer/analyzer.dart'; |
| 28 import 'package:analyzer/file_system/file_system.dart'; |
| 29 import 'package:analyzer/file_system/physical_file_system.dart'; |
| 30 import 'package:analyzer/src/codegen/tools.dart'; |
| 31 import 'package:analyzer/src/generated/parser.dart'; |
| 32 import 'package:analyzer/src/generated/scanner.dart'; |
| 33 import 'package:analyzer/src/generated/source.dart'; |
| 34 import 'package:path/path.dart'; |
| 35 |
| 36 import 'idl_model.dart' as idlModel; |
| 37 |
| 38 main() { |
| 39 String script = Platform.script.toFilePath(windows: Platform.isWindows); |
| 40 String pkgPath = normalize(join(dirname(script), '..', '..')); |
| 41 GeneratedContent.generateAll(pkgPath, <GeneratedContent>[target]); |
| 42 } |
| 43 |
| 44 final GeneratedFile target = |
| 45 new GeneratedFile('lib/src/summary/format.dart', (String pkgPath) { |
| 46 // Parse the input "IDL" file and pass it to the [_CodeGenerator]. |
| 47 PhysicalResourceProvider provider = new PhysicalResourceProvider( |
| 48 PhysicalResourceProvider.NORMALIZE_EOL_ALWAYS); |
| 49 String idlPath = join(pkgPath, 'tool', 'summary', 'idl.dart'); |
| 50 File idlFile = provider.getFile(idlPath); |
| 51 Source idlSource = provider.getFile(idlPath).createSource(); |
| 52 String idlText = idlFile.readAsStringSync(); |
| 53 BooleanErrorListener errorListener = new BooleanErrorListener(); |
| 54 CharacterReader idlReader = new CharSequenceReader(idlText); |
| 55 Scanner scanner = new Scanner(idlSource, idlReader, errorListener); |
| 56 Token tokenStream = scanner.tokenize(); |
| 57 Parser parser = new Parser(idlSource, new BooleanErrorListener()); |
| 58 CompilationUnit idlParsed = parser.parseCompilationUnit(tokenStream); |
| 59 _CodeGenerator codeGenerator = new _CodeGenerator(); |
| 60 codeGenerator.processCompilationUnit(idlParsed); |
| 61 return codeGenerator._outBuffer.toString(); |
| 62 }); |
| 63 |
| 64 class _CodeGenerator { |
| 65 /** |
| 66 * Buffer in which generated code is accumulated. |
| 67 */ |
| 68 final StringBuffer _outBuffer = new StringBuffer(); |
| 69 |
| 70 /** |
| 71 * Current indentation level. |
| 72 */ |
| 73 String _indentation = ''; |
| 74 |
| 75 /** |
| 76 * Semantic model of the "IDL" input file. |
| 77 */ |
| 78 idlModel.Idl _idl; |
| 79 |
| 80 /** |
| 81 * Perform basic sanity checking of the IDL (over and above that done by |
| 82 * [extractIdl]). |
| 83 */ |
| 84 void checkIdl() { |
| 85 _idl.classes.forEach((String name, idlModel.ClassDeclaration cls) { |
| 86 cls.fields.forEach((String fieldName, idlModel.FieldType type) { |
| 87 if (type.isList) { |
| 88 if (_idl.classes.containsKey(type.typeName)) { |
| 89 // List of classes is ok |
| 90 } else if (type.typeName == 'int') { |
| 91 // List of ints is ok |
| 92 } else { |
| 93 throw new Exception( |
| 94 '$name.$fieldName: illegal type (list of ${type.typeName})'); |
| 95 } |
| 96 } |
| 97 }); |
| 98 }); |
| 99 } |
| 100 |
| 101 /** |
| 102 * Generate a string representing the Dart type which should be used to |
| 103 * represent [type] when deserialized. |
| 104 */ |
| 105 String dartType(idlModel.FieldType type) { |
| 106 if (type.isList) { |
| 107 return 'List<${type.typeName}>'; |
| 108 } else { |
| 109 return type.typeName; |
| 110 } |
| 111 } |
| 112 |
| 113 /** |
| 114 * Generate a Dart expression representing the default value for a field |
| 115 * having the given [type], or `null` if there is no default value. |
| 116 */ |
| 117 String defaultValue(idlModel.FieldType type) { |
| 118 if (type.isList) { |
| 119 return 'const <${type.typeName}>[]'; |
| 120 } else if (_idl.enums.containsKey(type.typeName)) { |
| 121 return '${type.typeName}.${_idl.enums[type.typeName].values[0]}'; |
| 122 } else if (type.typeName == 'int') { |
| 123 return '0'; |
| 124 } else if (type.typeName == 'String') { |
| 125 return "''"; |
| 126 } else if (type.typeName == 'bool') { |
| 127 return 'false'; |
| 128 } else { |
| 129 return null; |
| 130 } |
| 131 } |
| 132 |
| 133 /** |
| 134 * Generate a string representing the Dart type which should be used to |
| 135 * represent [type] while building a serialized data structure. |
| 136 */ |
| 137 String encodedType(idlModel.FieldType type) { |
| 138 if (type.isList) { |
| 139 if (type.typeName == 'int') { |
| 140 return 'List<int>'; |
| 141 } else { |
| 142 return 'List<Object>'; |
| 143 } |
| 144 } else if (_idl.classes.containsKey(type.typeName)) { |
| 145 return 'Object'; |
| 146 } else { |
| 147 return dartType(type); |
| 148 } |
| 149 } |
| 150 |
| 151 /** |
| 152 * Process the AST in [idlParsed] and store the resulting semantic model in |
| 153 * [_idl]. Also perform some error checking. |
| 154 */ |
| 155 void extractIdl(CompilationUnit idlParsed) { |
| 156 _idl = new idlModel.Idl(); |
| 157 for (CompilationUnitMember decl in idlParsed.declarations) { |
| 158 if (decl is ClassDeclaration) { |
| 159 idlModel.ClassDeclaration cls = new idlModel.ClassDeclaration(); |
| 160 _idl.classes[decl.name.name] = cls; |
| 161 for (ClassMember classMember in decl.members) { |
| 162 if (classMember is FieldDeclaration) { |
| 163 TypeName type = classMember.fields.type; |
| 164 bool isList = false; |
| 165 if (type.name.name == 'List' && |
| 166 type.typeArguments != null && |
| 167 type.typeArguments.arguments.length == 1) { |
| 168 isList = true; |
| 169 type = type.typeArguments.arguments[0]; |
| 170 } |
| 171 if (type.typeArguments != null) { |
| 172 throw new Exception('Cannot handle type arguments in `$type`'); |
| 173 } |
| 174 idlModel.FieldType fieldType = |
| 175 new idlModel.FieldType(type.name.name, isList); |
| 176 for (VariableDeclaration field in classMember.fields.variables) { |
| 177 cls.fields[field.name.name] = fieldType; |
| 178 } |
| 179 } else { |
| 180 throw new Exception('Unexpected class member `$classMember`'); |
| 181 } |
| 182 } |
| 183 } else if (decl is EnumDeclaration) { |
| 184 idlModel.EnumDeclaration enm = new idlModel.EnumDeclaration(); |
| 185 _idl.enums[decl.name.name] = enm; |
| 186 for (EnumConstantDeclaration constDecl in decl.constants) { |
| 187 enm.values.add(constDecl.name.name); |
| 188 } |
| 189 } else if (decl is TopLevelVariableDeclaration) { |
| 190 // Ignore top leve variable declarations; they are present just to make |
| 191 // the IDL analyze without warnings. |
| 192 } else { |
| 193 throw new Exception('Unexpected declaration `$decl`'); |
| 194 } |
| 195 } |
| 196 } |
| 197 |
| 198 /** |
| 199 * Execute [callback] with two spaces added to [_indentation]. |
| 200 */ |
| 201 void indent(void callback()) { |
| 202 String oldIndentation = _indentation; |
| 203 try { |
| 204 _indentation += ' '; |
| 205 callback(); |
| 206 } finally { |
| 207 _indentation = oldIndentation; |
| 208 } |
| 209 } |
| 210 |
| 211 /** |
| 212 * Add the string [s] to the output as a single line, indenting as |
| 213 * appropriate. |
| 214 */ |
| 215 void out([String s = '']) { |
| 216 if (s == '') { |
| 217 _outBuffer.writeln(''); |
| 218 } else { |
| 219 _outBuffer.writeln('$_indentation$s'); |
| 220 } |
| 221 } |
| 222 |
| 223 /** |
| 224 * Entry point to the code generator. Interpret the AST in [idlParsed], |
| 225 * generate code, and output it to [_outBuffer]. |
| 226 */ |
| 227 void processCompilationUnit(CompilationUnit idlParsed) { |
| 228 extractIdl(idlParsed); |
| 229 checkIdl(); |
| 230 out('// Copyright (c) 2015, the Dart project authors. Please see the AUTHOR
S file'); |
| 231 out('// for details. All rights reserved. Use of this source code is governe
d by a'); |
| 232 out('// BSD-style license that can be found in the LICENSE file.'); |
| 233 out('//'); |
| 234 out('// This file has been automatically generated. Please do not edit it m
anually.'); |
| 235 out('// To regenerate the file, use the script "pkg/analyzer/tool/generate_f
iles".'); |
| 236 out(); |
| 237 out('library analyzer.src.summary.format;'); |
| 238 out(); |
| 239 out("import 'builder.dart' as builder;"); |
| 240 out(); |
| 241 _idl.enums.forEach((String name, idlModel.EnumDeclaration enm) { |
| 242 out('enum $name {'); |
| 243 indent(() { |
| 244 for (String value in enm.values) { |
| 245 out('$value,'); |
| 246 } |
| 247 }); |
| 248 out('}'); |
| 249 out(); |
| 250 }); |
| 251 _idl.classes.forEach((String name, idlModel.ClassDeclaration cls) { |
| 252 out('class $name {'); |
| 253 indent(() { |
| 254 cls.fields.forEach((String fieldName, idlModel.FieldType type) { |
| 255 out('${dartType(type)} _$fieldName;'); |
| 256 }); |
| 257 out(); |
| 258 out('$name.fromJson(Map json)'); |
| 259 indent(() { |
| 260 List<String> initializers = <String>[]; |
| 261 cls.fields.forEach((String fieldName, idlModel.FieldType type) { |
| 262 String convert = 'json[${quoted(fieldName)}]'; |
| 263 if (type.isList && type.typeName == 'int') { |
| 264 // No conversion necessary. |
| 265 } else if (type.isList) { |
| 266 convert = |
| 267 '$convert?.map((x) => new ${type.typeName}.fromJson(x))?.toLis
t()'; |
| 268 } else if (_idl.classes.containsKey(type.typeName)) { |
| 269 convert = |
| 270 '$convert == null ? null : new ${type.typeName}.fromJson($conv
ert)'; |
| 271 } else if (_idl.enums.containsKey(type.typeName)) { |
| 272 convert = |
| 273 '$convert == null ? null : ${type.typeName}.values[$convert]'; |
| 274 } |
| 275 initializers.add('_$fieldName = $convert'); |
| 276 }); |
| 277 for (int i = 0; i < initializers.length; i++) { |
| 278 String prefix = i == 0 ? ': ' : ' '; |
| 279 String suffix = i == initializers.length - 1 ? ';' : ','; |
| 280 out('$prefix${initializers[i]}$suffix'); |
| 281 } |
| 282 }); |
| 283 out(); |
| 284 cls.fields.forEach((String fieldName, idlModel.FieldType type) { |
| 285 String def = defaultValue(type); |
| 286 String defaultSuffix = def == null ? '' : ' ?? $def'; |
| 287 out('${dartType(type)} get $fieldName => _$fieldName$defaultSuffix;'); |
| 288 }); |
| 289 }); |
| 290 out('}'); |
| 291 out(); |
| 292 List<String> builderParams = <String>[]; |
| 293 out('class ${name}Builder {'); |
| 294 indent(() { |
| 295 out('final Map _json = {};'); |
| 296 out(); |
| 297 out('${name}Builder(builder.BuilderContext context);'); |
| 298 cls.fields.forEach((String fieldName, idlModel.FieldType type) { |
| 299 out(); |
| 300 String conversion = '_value'; |
| 301 String condition = ''; |
| 302 if (type.isList) { |
| 303 conversion = '$conversion.toList()'; |
| 304 condition = ' || _value.isEmpty'; |
| 305 } else if (_idl.enums.containsKey(type.typeName)) { |
| 306 conversion = '$conversion.index'; |
| 307 condition = ' || _value == ${defaultValue(type)}'; |
| 308 } |
| 309 builderParams.add('${encodedType(type)} $fieldName'); |
| 310 out('void set $fieldName(${encodedType(type)} _value) {'); |
| 311 indent(() { |
| 312 out('assert(!_json.containsKey(${quoted(fieldName)}));'); |
| 313 out('if (_value != null$condition) {'); |
| 314 indent(() { |
| 315 out('_json[${quoted(fieldName)}] = $conversion;'); |
| 316 }); |
| 317 out('}'); |
| 318 }); |
| 319 out('}'); |
| 320 }); |
| 321 out(); |
| 322 out('Object finish() => _json;'); |
| 323 }); |
| 324 out('}'); |
| 325 out(); |
| 326 out('Object encode$name(builder.BuilderContext builderContext, {${builderP
arams.join(', ')}}) {'); |
| 327 indent(() { |
| 328 out('${name}Builder builder = new ${name}Builder(builderContext);'); |
| 329 cls.fields.forEach((String fieldName, idlModel.FieldType type) { |
| 330 out('builder.$fieldName = $fieldName;'); |
| 331 }); |
| 332 out('return builder.finish();'); |
| 333 }); |
| 334 out('}'); |
| 335 out(); |
| 336 }); |
| 337 } |
| 338 |
| 339 /** |
| 340 * Enclose [s] in quotes, escaping as necessary. |
| 341 */ |
| 342 String quoted(String s) { |
| 343 return JSON.encode(s); |
| 344 } |
| 345 } |
OLD | NEW |