Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix fallback value definition and use #903

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
Open
124 changes: 73 additions & 51 deletions spec/formatting.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -257,23 +256,30 @@ 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_.
Copy link
Member

@macchiati macchiati Nov 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A general issue: The resolved value is logically {datatype, optionMap}. So whenever we have a statement like this, that implies that a fallback value is a resolved value. That means we must say what the optionMap is: I presume an empty map, but we need to be clear.

I think for this case, we can probably put this in one place, the definition of fallback value. We should check other instances of 'resolved value' for completeness.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. I've updated the text to make the references more explicit. PTAL?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Excellent fixes to all the fallback value text, thanks!


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

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_.
Expand All @@ -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_.
Expand All @@ -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

Expand Down Expand Up @@ -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_.
eemeli marked this conversation as resolved.
Show resolved Hide resolved
1. Let `rv` be the _resolved value_ of the _option_ value.
1. If `rv` is a _fallback value_:
1. 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.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the first mention I've noticed in the spec of a non-fatal error, i.e. a warning. If Bad Option is sometimes a fatal error and sometimes a warning, it seems like the spec needs some more language to clarify what implementations should do with it. Or if it's meant to always be a warning, maybe https://github.com/unicode-org/message-format-wg/blob/main/spec/errors.md should say something about that?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On second thought: from the perspective of an implementation can only signal errors or return usable output, but not both, I'm reading this as saying that the implementation can't signal a Bad Option error. Which is fine, since it's a "MAY".

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.
Expand All @@ -395,67 +411,73 @@ The resolution of _markup_ MUST always succeed.

### Fallback Resolution

A **_<dfn>fallback value</dfn>_** is the _resolved value_ for an _expression_ that fails to resolve.
A **_<dfn>fallback value</dfn>_** 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 `|`,
and then by U+007C VERTICAL LINE `|`.

> 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_.

Expand Down
52 changes: 52 additions & 0 deletions test/tests/fallback.json
Original file line number Diff line number Diff line change
@@ -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}"
}
]
}
30 changes: 6 additions & 24 deletions test/tests/functions/number.json
Original file line number Diff line number Diff line change
Expand Up @@ -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}}}",
Expand Down