Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 /** | 5 /** |
| 6 * This provides classes to represent the internal structure of the | 6 * This provides classes to represent the internal structure of the |
| 7 * arguments to `Intl.message`. It is used when parsing sources to extract | 7 * arguments to `Intl.message`. It is used when parsing sources to extract |
| 8 * messages or to generate code for message substitution. Normal programs | 8 * messages or to generate code for message substitution. Normal programs |
| 9 * using Intl would not import this library. | 9 * using Intl would not import this library. |
| 10 * | 10 * |
| (...skipping 15 matching lines...) Expand all Loading... | |
| 26 * CompositeMessage containing three pieces, a LiteralString for | 26 * CompositeMessage containing three pieces, a LiteralString for |
| 27 * 'This is plural (', a VariableSubstitution for `num`. amd a LiteralString | 27 * 'This is plural (', a VariableSubstitution for `num`. amd a LiteralString |
| 28 * for '.)'. | 28 * for '.)'. |
| 29 * | 29 * |
| 30 * This representation isn't used at runtime. Rather, we read some format | 30 * This representation isn't used at runtime. Rather, we read some format |
| 31 * from a translation file, parse it into these objects, and they are then | 31 * from a translation file, parse it into these objects, and they are then |
| 32 * used to generate the code representation above. | 32 * used to generate the code representation above. |
| 33 */ | 33 */ |
| 34 library intl_message; | 34 library intl_message; |
| 35 | 35 |
| 36 import 'package:analyzer_experimental/analyzer.dart'; | |
| 37 | |
| 36 /** A default function for the [Message.expanded] method. */ | 38 /** A default function for the [Message.expanded] method. */ |
| 37 _nullTransform(msg, chunk) => chunk; | 39 _nullTransform(msg, chunk) => chunk; |
| 38 | 40 |
| 39 /** | 41 /** |
| 40 * An abstract superclass for Intl.message/plural/gender calls in the | 42 * An abstract superclass for Intl.message/plural/gender calls in the |
| 41 * program's source text. We | 43 * program's source text. We |
| 42 * assemble these into objects that can be used to write out some translation | 44 * assemble these into objects that can be used to write out some translation |
| 43 * format and can also print themselves into code. | 45 * format and can also print themselves into code. |
| 44 */ | 46 */ |
| 45 abstract class Message { | 47 abstract class Message { |
| 46 | 48 |
| 47 /** | 49 /** |
| 48 * All [Message]s except a [MainMessage] are contained inside some parent, | 50 * All [Message]s except a [MainMessage] are contained inside some parent, |
| 49 * terminating at an Intl.message call which supplies the arguments we | 51 * terminating at an Intl.message call which supplies the arguments we |
| 50 * use for variable substitutions. | 52 * use for variable substitutions. |
| 51 */ | 53 */ |
| 52 Message parent; | 54 Message parent; |
| 53 | 55 |
| 54 Message(this.parent); | 56 Message(this.parent); |
| 55 | 57 |
| 56 /** | 58 /** |
| 57 * We find the arguments from the top-level [MainMessage] and use those to | 59 * We find the arguments from the top-level [MainMessage] and use those to |
| 58 * do variable substitutions. | 60 * do variable substitutions. |
| 59 */ | 61 */ |
| 60 get arguments => parent == null ? const [] : parent.arguments; | 62 get arguments => parent == null ? const [] : parent.arguments; |
| 61 | 63 |
| 64 String checkValidity(MethodInvocation node, List arguments, | |
| 65 String outerName, FormalParameterList outerArgs) { | |
| 66 var hasArgs = arguments.any( | |
| 67 (each) => each is NamedExpression && each.name.label.name == 'args'); | |
| 68 var hasParameters = !outerArgs.parameters.isEmpty; | |
| 69 if (!hasArgs && hasParameters) { | |
| 70 return "The 'args' argument for Intl.message must be specified"; | |
| 71 } | |
| 72 | |
| 73 var messageName = arguments.firstWhere( | |
| 74 (eachArg) => eachArg is NamedExpression && | |
| 75 eachArg.name.label.name == 'name', | |
| 76 orElse: () => null); | |
| 77 if (messageName == null) { | |
| 78 return "The 'name' argument for Intl.message must be specified"; | |
| 79 } | |
| 80 if ((messageName.expression is! SimpleStringLiteral) | |
| 81 || messageName.expression.value != outerName) { | |
| 82 return "The 'name' argument for Intl.message must be a simple string " | |
| 83 "literal and match the containing function name."; | |
| 84 } | |
| 85 var simpleArguments = arguments.where( | |
| 86 (each) => each is NamedExpression | |
| 87 && ["desc", "locale", "name"].contains(each.name.label.name)); | |
| 88 var values = simpleArguments.map((each) => each.expression).toList(); | |
| 89 for (var arg in values) { | |
| 90 if (arg is! SimpleStringLiteral) { | |
| 91 return "Intl.message argument '${arg.name.label.name}' must be " | |
| 92 "a simple string literal"; | |
| 93 } | |
| 94 } | |
| 95 } | |
| 96 | |
| 62 /** | 97 /** |
| 63 * Turn a value, typically read from a translation file or created out of an | 98 * Turn a value, typically read from a translation file or created out of an |
| 64 * AST for a source program, into the appropriate | 99 * AST for a source program, into the appropriate |
| 65 * subclass. We expect to get literal Strings, variable substitutions | 100 * subclass. We expect to get literal Strings, variable substitutions |
| 66 * represented by integers, things that are already MessageChunks and | 101 * represented by integers, things that are already MessageChunks and |
| 67 * lists of the same. | 102 * lists of the same. |
| 68 */ | 103 */ |
| 69 static Message from(value, Message parent) { | 104 static Message from(value, Message parent) { |
| 70 if (value is String) return new LiteralString(value, parent); | 105 if (value is String) return new LiteralString(value, parent); |
| 71 if (value is int) return new VariableSubstitution(value, parent); | 106 if (value is int) return new VariableSubstitution(value, parent); |
| (...skipping 155 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 227 | 262 |
| 228 MainMessage() : super(null); | 263 MainMessage() : super(null); |
| 229 | 264 |
| 230 /** | 265 /** |
| 231 * All the pieces of the message. When we go to print, these will | 266 * All the pieces of the message. When we go to print, these will |
| 232 * all be expanded appropriately. The exact form depends on what we're | 267 * all be expanded appropriately. The exact form depends on what we're |
| 233 * printing it for See [expanded], [toCode]. | 268 * printing it for See [expanded], [toCode]. |
| 234 */ | 269 */ |
| 235 List<Message> messagePieces = []; | 270 List<Message> messagePieces = []; |
| 236 | 271 |
| 272 /** Verify that this looks like a correct Intl.message invocation. */ | |
| 273 String checkValidity(MethodInvocation node, List arguments, | |
| 274 String outerName, FormalParameterList outerArgs) { | |
| 275 if (arguments.first is! StringLiteral) { | |
| 276 return "Intl.message messages must be string literals"; | |
| 277 } | |
| 278 | |
| 279 return super.checkValidity(node, arguments, outerName, outerArgs); | |
| 280 } | |
| 281 | |
| 237 void addPieces(List<Message> messages) { | 282 void addPieces(List<Message> messages) { |
| 238 for (var each in messages) { | 283 for (var each in messages) { |
| 239 messagePieces.add(Message.from(each, this)); | 284 messagePieces.add(Message.from(each, this)); |
| 240 } | 285 } |
| 241 } | 286 } |
| 242 | 287 |
| 243 /** The description provided in the Intl.message call. */ | 288 /** The description provided in the Intl.message call. */ |
| 244 String description; | 289 String description; |
| 245 | 290 |
| 246 /** The examples from the Intl.message call */ | 291 /** The examples from the Intl.message call */ |
| (...skipping 130 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 377 | 422 |
| 378 toString() => expanded(); | 423 toString() => expanded(); |
| 379 | 424 |
| 380 /** | 425 /** |
| 381 * The name of the main argument, which is expected to have the value | 426 * The name of the main argument, which is expected to have the value |
| 382 * which is one of [attributeNames] and is used to decide which clause to use. | 427 * which is one of [attributeNames] and is used to decide which clause to use. |
| 383 */ | 428 */ |
| 384 String mainArgument; | 429 String mainArgument; |
| 385 | 430 |
| 386 /** | 431 /** |
| 432 * Return the arguments that affect this SubMessage as a map of | |
| 433 * argument names and values. | |
| 434 */ | |
| 435 Map argumentsOfInterestFor(MethodInvocation node) { | |
| 436 var basicArguments = node.argumentList.arguments.elements; | |
| 437 var others = basicArguments.where((each) => each is NamedExpression); | |
| 438 return new Map.fromIterable(others, | |
| 439 key: (node) => node.name.label.token.value(), | |
| 440 value: (node) => node.expression); | |
| 441 } | |
| 442 | |
| 443 /** | |
| 387 * Return the list of attribute names to use when generating code. This | 444 * Return the list of attribute names to use when generating code. This |
| 388 * may be different from [attributeNames] if there are multiple aliases | 445 * may be different from [attributeNames] if there are multiple aliases |
| 389 * that map to the same clause. | 446 * that map to the same clause. |
| 390 */ | 447 */ |
| 391 List<String> get codeAttributeNames; | 448 List<String> get codeAttributeNames; |
| 392 | 449 |
| 393 String expanded([Function transform = _nullTransform]) { | 450 String expanded([Function transform = _nullTransform]) { |
| 394 fullMessageForClause(key) => key + '{' + | 451 fullMessageForClause(key) => key + '{' + |
| 395 transform(parent, this[key]).toString() + '}'; | 452 transform(parent, this[key]).toString() + '}'; |
| 396 var clauses = attributeNames | 453 var clauses = attributeNames |
| (...skipping 19 matching lines...) Expand all Loading... | |
| 416 | 473 |
| 417 /** | 474 /** |
| 418 * Represents a message send of [Intl.gender] inside a message that is to | 475 * Represents a message send of [Intl.gender] inside a message that is to |
| 419 * be internationalized. This corresponds to an ICU message syntax "select" | 476 * be internationalized. This corresponds to an ICU message syntax "select" |
| 420 * with "male", "female", and "other" as the possible options. | 477 * with "male", "female", and "other" as the possible options. |
| 421 */ | 478 */ |
| 422 class Gender extends SubMessage { | 479 class Gender extends SubMessage { |
| 423 | 480 |
| 424 Gender(); | 481 Gender(); |
| 425 /** | 482 /** |
| 426 * Create a new IntlGender providing [mainArgument] and the list of possible | 483 * Create a new Gender providing [mainArgument] and the list of possible |
| 427 * clauses. Each clause is expected to be a list whose first element is a | 484 * clauses. Each clause is expected to be a list whose first element is a |
| 428 * variable name and whose second element is either a String or | 485 * variable name and whose second element is either a [String] or |
| 429 * a list of strings and IntlMessageSends or IntlVariableSubstitution. | 486 * a list of strings and [Message] or [VariableSubstitution]. |
| 430 */ | 487 */ |
| 431 Gender.from(mainArgument, List clauses, parent) : | 488 Gender.from(mainArgument, List clauses, parent) : |
| 432 super.from(mainArgument, clauses, parent); | 489 super.from(mainArgument, clauses, parent); |
| 433 | 490 |
| 434 Message female; | 491 Message female; |
| 435 Message male; | 492 Message male; |
| 436 Message other; | 493 Message other; |
| 437 | 494 |
| 438 String get icuMessageName => "select"; | 495 String get icuMessageName => "select"; |
| 439 String get dartMessageName => 'Intl.gender'; | 496 String get dartMessageName => 'Intl.gender'; |
| (...skipping 72 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 512 case "two" : return two; | 569 case "two" : return two; |
| 513 case "=2" : return two; | 570 case "=2" : return two; |
| 514 case "few" : return few; | 571 case "few" : return few; |
| 515 case "many" : return many; | 572 case "many" : return many; |
| 516 case "other" : return other; | 573 case "other" : return other; |
| 517 default: return other; | 574 default: return other; |
| 518 } | 575 } |
| 519 } | 576 } |
| 520 } | 577 } |
| 521 | 578 |
| 579 /** | |
| 580 * Represents a message send of [Intl.select] inside a message that is to | |
| 581 * be internationalized. This corresponds to an ICU message syntax "select" | |
| 582 * with arbitrary options. | |
| 583 */ | |
| 584 class Select extends SubMessage { | |
| 585 | |
| 586 Select(); | |
| 587 /** | |
| 588 * Create a new [Select] providing [mainArgument] and the list of possible | |
|
Emily Fortuna
2013/08/08 23:49:55
what types can mainArgument be since it's not type
Alan Knight
2013/08/09 17:09:16
It's a String. On the superclass it's typed as a f
| |
| 589 * clauses. Each clause is expected to be a list whose first element is a | |
| 590 * variable name and whose second element is either a String or | |
| 591 * a list of strings and [Message]s or [VariableSubstitution]s. | |
| 592 */ | |
| 593 Select.from(mainArgument, List clauses, parent) : | |
| 594 super.from(mainArgument, clauses, parent); | |
| 595 | |
| 596 Map<String, Message> cases = new Map<String, Message>(); | |
| 597 | |
| 598 String get icuMessageName => "select"; | |
| 599 String get dartMessageName => 'Intl.select'; | |
| 600 | |
| 601 get attributeNames => cases.keys; | |
| 602 get codeAttributeNames => attributeNames; | |
| 603 | |
| 604 void operator []=(attributeName, rawValue) { | |
| 605 var value = Message.from(rawValue, this); | |
| 606 cases[attributeName] = value; | |
| 607 } | |
| 608 | |
| 609 Message operator [](String attributeName) { | |
| 610 var exact = cases[attributeName]; | |
| 611 return exact == null ? cases["other"] : exact; | |
| 612 } | |
| 613 | |
| 614 /** | |
| 615 * Return the arguments that we care about for the select. In this | |
| 616 * case they will all be passed in as a Map rather than as the named | |
| 617 * arguments used in Plural/Gender. | |
| 618 */ | |
| 619 Map argumentsOfInterestFor(MethodInvocation node) { | |
|
Emily Fortuna
2013/08/08 23:49:55
should this actually be private? It seems more lik
Alan Knight
2013/08/09 17:09:16
It's somewhat of a helper, but it's a helper for t
| |
| 620 var casesArgument = node.argumentList.arguments.elements[1]; | |
| 621 return new Map.fromIterable(casesArgument.entries, | |
| 622 key: (node) => node.key.value, | |
| 623 value: (node) => node.value); | |
| 624 } | |
| 625 | |
| 626 /** | |
| 627 * Write out the generated representation of this message. This differs | |
| 628 * from Plural/Gender in that it prints a literal map rather than | |
| 629 * named arguments. | |
| 630 */ | |
| 631 String toCode() { | |
| 632 var out = new StringBuffer(); | |
| 633 out.write('\${'); | |
| 634 out.write(dartMessageName); | |
| 635 out.write('('); | |
| 636 out.write(mainArgument); | |
| 637 var args = codeAttributeNames; | |
| 638 out.write(", {"); | |
| 639 args.fold(out, (buffer, arg) => buffer..write( | |
| 640 "'$arg': '${this[arg].toCode()}', ")); | |
| 641 out.write("})}"); | |
| 642 return out.toString(); | |
| 643 } | |
| 644 } | |
| OLD | NEW |