diff --git a/spec/formatting.md b/spec/formatting.md index b8f6d22ae..4ac0ca295 100644 --- a/spec/formatting.md +++ b/spec/formatting.md @@ -231,8 +231,7 @@ whether its value was originally a _quoted literal_ or an _unquoted literal_. > For example, > the _option_ `foo=42` and the _option_ `foo=|42|` are treated as identical. - -> For example, in a JavaScript formatter +> For example, in a JavaScript formatter, > the _resolved value_ of a _text_ or a _literal_ could have the following implementation: > > ```ts @@ -257,9 +256,14 @@ Otherwise, the _variable_ is an implicit reference to an input value, and its value is looked up from the _formatting context_ _input mapping_. The resolution of a _variable_ fails if no value is identified for its _name_. -If this happens, an _Unresolved Variable_ error is emitted. -If a _variable_ would resolve to a _fallback value_, -this MUST also be considered a failure. +If this happens, an _Unresolved Variable_ error is emitted +and a _fallback value_ is used as the _resolved value_ of the _variable_. + +If the _resolved value_ identified for the _variable_ _name_ is a _fallback value_, +a _fallback value_ is used as the _resolved value_ of the _variable_. + +The _fallback value_ representation of a _variable_ has a string representation +consisting of the U+0024 DOLLAR SIGN `$` followed by the _name_ of the _variable_. ### Function Resolution @@ -267,13 +271,15 @@ To resolve an _expression_ with a _function_, the following steps are taken: 1. If the _expression_ includes an _operand_, resolve its value. - If this fails, use a _fallback value_ for the _expression_. -2. Resolve the _identifier_ of the _function_ and, based on the starting sigil, + If this is a _fallback value_, + return a _fallback value_ as the _resolved value_ of the _expression_. + +2. Resolve the _identifier_ of the _function_ and find the appropriate _function handler_ to call. If the implementation cannot find the _function handler_, or if the _identifier_ includes a _namespace_ that the implementation does not support, emit an _Unknown Function_ error - and use a _fallback value_ for the _expression_. + and return a _fallback value_ as the _resolved value_ of the _expression_. Implementations are not required to implement _namespaces_ or installable _function registries_. @@ -294,7 +300,7 @@ the following steps are taken: supported by the implementation, process them as specified. Such `u:` options MAY be removed from the resolved mapping of _options_. -5. Call the function implementation with the following arguments: +5. Call the _function handler_ with the following arguments: - The _function context_. - The resolved mapping of _options_. @@ -318,7 +324,7 @@ the following steps are taken: _operand_ did not match that expected by the _function_, the _function_ SHOULD cause a _Bad Operand_ error to be emitted. - In all failure cases, use the _fallback value_ for the _expression_ as its _resolved value_. + In all failure cases, return a _fallback value_ as the _resolved value_ of the _expression_. #### Function Handler @@ -366,17 +372,27 @@ The order of _options_ MUST NOT be significant. For each _option_: -- Resolve the _identifier_ of the _option_. -- If the _option_'s right-hand side successfully resolves to a value, - bind the _identifier_ of the _option_ to the _resolved value_ in the mapping. -- Otherwise, bind the _identifier_ of the _option_ to an unresolved value in the mapping. - Implementations MAY later remove this value before calling the _function_. - (Note that an _Unresolved Variable_ error will have been emitted.) +1. Let `res` be a new empty mapping. +1. For each _option_: + 1. Let `id` be the string value of the _identifier_ of the _option_. + 1. Let `rv` be the _resolved value_ of the _option_ value. + 1. If `rv` is a _fallback value_: + 1. If supported, emit a _Bad Option_ error. + 1. Else: + 1. Set `res[id]` to be `rv`. +1. Return `res`. -Errors MAY be emitted during _option resolution_, -but it always resolves to some mapping of string identifiers to values. +The result of _option resolution_ MUST be a (possibly empty) mapping +of string identifiers to values; +that is, errors MAY be emitted, but such errors MUST NOT be fatal. This mapping can be empty. +> [!NOTE] +> The _resolved value_ of a _function_ _operand_ +> can also include resolved option values. +> These are not included in the _option resolution_ result, +> and need to be processed separately by a _function handler_. + ### Markup Resolution Unlike _functions_, the resolution of _markup_ is not customizable. @@ -395,17 +411,28 @@ The resolution of _markup_ MUST always succeed. ### Fallback Resolution -A **_fallback value_** is the _resolved value_ for an _expression_ that fails to resolve. +A **_fallback value_** is the _resolved value_ for +an _expression_ or _variable_ when that _expression_ or _variable_ fails to resolve. +It contains a string representation that is used for its formatting, +and no option values. + +The _resolved value_ of _text_, _literal_, and _markup_ MUST NOT be a _fallback value_. + +A _variable_ fails to resolve when no value is identified for its _name_. +The string representation of its _fallback value_ is +U+0024 DOLLAR SIGN `$` followed by the _name_ of the _variable_. An _expression_ fails to resolve when: -- A _variable_ used as an _operand_ (with or without a _function_) fails to resolve. - * Note that this does not include a _variable_ used as an _option_ value. -- A _function_ fails to resolve. +- A _variable_ used as its _operand_ resolves to a _fallback value_. + Note that an _expression_ does not necessarily fail to resolve + if an _option_ resolves with a _fallback value_. +- No _function handler_ is found for a _function_ _identifier_. +- Calling a _function handler_ fails or does not return a valid value. -The _fallback value_ depends on the contents of the _expression_: +The string representation of the _fallback value_ of an _expression_ depends on its contents: -- _expression_ with a _literal_ _operand_ (either quoted or unquoted) +- _expression_ with a _literal_ _operand_ (either quoted or unquoted): U+007C VERTICAL LINE `|` followed by the value of the _literal_ with escaping applied to U+005C REVERSE SOLIDUS `\` and U+007C VERTICAL LINE `|`, @@ -413,49 +440,44 @@ The _fallback value_ depends on the contents of the _expression_: > Examples: > In a context where `:func` fails to resolve, - > `{42 :func}` resolves to the _fallback value_ `|42|` and - > `{|C:\\| :func}` resolves to the _fallback value_ `|C:\\|`. + > `{42 :func}` resolves to a _fallback value_ with a string representation `|42|` and + > `{|C:\\| :func}` resolves to a _fallback value_ with a string representation `|C:\\|`. -- _expression_ with _variable_ _operand_ referring to a local _declaration_ (with or without a _function_): - the _value_ to which it resolves (which may already be a _fallback value_) - - > Examples: - > In a context where `:func` fails to resolve, - > the _pattern_'s _expression_ in `.local $var={|val|} {{{$var :func}}}` - > resolves to the _fallback value_ `|val|` and the message formats to `{|val|}`. - > In a context where `:now` fails to resolve but `:datetime` does not, - > the _pattern_'s _expression_ in - > ``` - > .local $t = {:now format=iso8601} - > .local $pretty_t = {$t :datetime} - > {{{$pretty_t}}} - > ``` - > (transitively) resolves to the _fallback value_ `:now` and - > the message formats to `{:now}`. - -- _expression_ with _variable_ _operand_ not referring to a local _declaration_ (with or without a _function_): +- _expression_ with _variable_ _operand_: + the _fallback value_ representation of that _variable_, U+0024 DOLLAR SIGN `$` followed by the _name_ of the _variable_ > Examples: > In a context where `$var` fails to resolve, `{$var}` and `{$var :number}` - > both resolve to the _fallback value_ `$var`. + > both resolve to a _fallback value_ with a string representation `$var` + > (even if `:number` fails to resolve). + > > In a context where `:func` fails to resolve, - > the _pattern_'s _expression_ in `.input $arg {{{$arg :func}}}` - > resolves to the _fallback value_ `$arg` and - > the message formats to `{$arg}`. + > the _placeholder_ in `.local $var = {|val| :func} {{{$var}}}` + > resolves to a _fallback value_ with a string representation `$var`. + > + > In a context where either `:now` or `:pretty` fails to resolve, + > the _placeholder_ in + > ``` + > .local $time = {:now format=iso8601} + > {{{$time :pretty}}} + > ``` + > resolves to a _fallback value_ with a string representation `$time`. - _function_ _expression_ with no _operand_: U+003A COLON `:` followed by the _function_ _identifier_ > Examples: - > In a context where `:func` fails to resolve, `{:func}` resolves to the _fallback value_ `:func`. - > In a context where `:ns:func` fails to resolve, `{:ns:func}` resolves to the _fallback value_ `:ns:func`. + > In a context where `:func` fails to resolve, + > `{:func}` resolves to a _fallback value_ with a string representation `:func`. + > In a context where `:ns:func` fails to resolve, + > `{:ns:func}` resolves to a _fallback value_ with a string representation `:ns:func`. - Otherwise: the U+FFFD REPLACEMENT CHARACTER `�` This is not currently used by any expression, but may apply in future revisions. -_Option_ _identifiers_ and values are not included in the _fallback value_. +_Options_ and _attributes_ are not included in the _fallback value_. _Pattern selection_ is not supported for _fallback values_. diff --git a/test/tests/fallback.json b/test/tests/fallback.json new file mode 100644 index 000000000..fd1429c9b --- /dev/null +++ b/test/tests/fallback.json @@ -0,0 +1,52 @@ +{ + "$schema": "https://raw.githubusercontent.com/unicode-org/message-format-wg/main/test/schemas/v0/tests.schema.json", + "scenario": "Fallback", + "description": "Test cases for fallback behaviour.", + "defaultTestProperties": { + "bidiIsolation": "none", + "locale": "en-US", + "expErrors": true + }, + "tests": [ + { + "description": "function with unquoted literal operand", + "src": "{42 :test:function fails=format}", + "exp": "{|42|}" + }, + { + "description": "function with quoted literal operand", + "src": "{|C:\\\\| :test:function fails=format}", + "exp": "{|C:\\\\|}" + }, + { + "description": "unannotated implicit input variable", + "src": "{$var}", + "exp": "{$var}" + }, + { + "description": "annotated implicit input variable", + "src": "{$var :number}", + "exp": "{$var}" + }, + { + "description": "local variable with unknown function in declaration", + "src": ".local $var = {|val| :test:undefined} {{{$var}}}", + "exp": "{$var}" + }, + { + "description": "function with local variable operand with unknown function in declaration", + "src": ".local $var = {|val| :test:undefined} {{{$var :test:function}}}", + "exp": "{$var}" + }, + { + "description": "local variable with unknown function in placeholder", + "src": ".local $var = {|val|} {{{$var :test:undefined}}}", + "exp": "{$var}" + }, + { + "description": "function with no operand", + "src": "{:test:undefined}", + "exp": "{:test:undefined}" + } + ] +} diff --git a/test/tests/functions/number.json b/test/tests/functions/number.json index d3d76fb7f..ed95a8a10 100644 --- a/test/tests/functions/number.json +++ b/test/tests/functions/number.json @@ -132,33 +132,15 @@ }, { "src": ".local $foo = {$bar :number minimumFractionDigits=foo} {{bar {$foo}}}", - "params": [ - { - "name": "bar", - "value": 4.2 - } - ], - "exp": "bar {$bar}", - "expErrors": [ - { - "type": "bad-option" - } - ] + "params": [{ "name": "bar", "value": 4.2 }], + "exp": "bar {$foo}", + "expErrors": [{ "type": "bad-option" }] }, { "src": ".local $foo = {$bar :number} {{bar {$foo}}}", - "params": [ - { - "name": "bar", - "value": "foo" - } - ], - "exp": "bar {$bar}", - "expErrors": [ - { - "type": "bad-operand" - } - ] + "params": [{ "name": "bar", "value": "foo" }], + "exp": "bar {$foo}", + "expErrors": [{ "type": "bad-operand" }] }, { "src": ".input {$foo :number} {{bar {$foo}}}",