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

Match on variables instead of expressions #877

Merged
merged 8 commits into from
Sep 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@ Functions can optionally take _options_:
Messages can use a _selector_ to choose between different _variants_,
which correspond to the grammatical (or other) requirements of the language:

.match {$count :integer}
.input {$count :integer}
.match $count
0 {{You have no notifications.}}
one {{You have {$count} notification.}}
* {{You have {$count} notifications.}}
Expand Down
3 changes: 2 additions & 1 deletion exploration/registry-xml/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,8 @@ For the sake of brevity, only `locales="en"` is considered.
Given the above description, the `:number` function is defined to work both in a selector and a placeholder:

```
.match {$count :number}
.input {$count :number}
.match $count
1 {{One new message}}
* {{{$count :number} new messages}}
```
Expand Down
8 changes: 7 additions & 1 deletion exploration/selection-declaration.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Effect of Selectors on Subsequent Placeholders

Status: **Proposed, Ballot Requested**
Status: **Accepted**

<details>
<summary>Metadata</summary>
Expand All @@ -12,6 +12,12 @@ Status: **Proposed, Ballot Requested**
<dt>Pull Requests</dt>
<dd><a href="https://github.com/unicode-org/message-format-wg/pull/755">#755</a></dd>
<dd><a href="https://github.com/unicode-org/message-format-wg/pull/824">#824</a></dd>
<dd><a href="https://github.com/unicode-org/message-format-wg/pull/860">#860</a></dd>
<dd><a href="https://github.com/unicode-org/message-format-wg/pull/867">#867</a></dd>
<dd><a href="https://github.com/unicode-org/message-format-wg/pull/877">#877</a></dd>
<dt>Ballot</dt>
<dd><a href="https://github.com/unicode-org/message-format-wg/pull/872">#872</a> (discussion)</dd>
<dd><a href="https://github.com/unicode-org/message-format-wg/pull/873">#873</a> (voting)</dd>
</dl>
</details>

Expand Down
2 changes: 1 addition & 1 deletion spec/data-model/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ interface PatternMessage {
interface SelectMessage {
type: "select";
declarations: Declaration[];
selectors: Expression[];
selectors: VariableRef[];
variants: Variant[];
}
```
Expand Down
2 changes: 1 addition & 1 deletion spec/data-model/message.dtd
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
name NMTOKEN #REQUIRED
>

<!ELEMENT selectors (expression)+>
<!ELEMENT selectors (variable)+>
<!ELEMENT variant (key+,pattern)>
<!ELEMENT key (#PCDATA)>
<!ATTLIST key catchall (true | false) "false">
Expand Down
2 changes: 1 addition & 1 deletion spec/data-model/message.json
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@
"declarations": { "$ref": "#/$defs/declarations" },
"selectors": {
"type": "array",
"items": { "$ref": "#/$defs/expression" }
"items": { "$ref": "#/$defs/variable" }
},
"variants": {
"type": "array",
Expand Down
50 changes: 32 additions & 18 deletions spec/errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,10 @@ or contains some error which leads to further errors,
an implementation which does not emit all of the errors
SHOULD prioritise _Syntax Errors_ and _Data Model Errors_ over others.

When an error occurs within a _selector_,
When an error occurs while resolving a _selector_
or calling MatchSelectorKeys with its resolved value,
the _selector_ MUST NOT match any _variant_ _key_ other than the catch-all `*`
and a _Resolution Error_ or a _Message Function Error_ MUST be emitted.
and a _Bad Selector_ error MUST be emitted.

## Syntax Errors

Expand Down Expand Up @@ -85,13 +86,16 @@ does not equal the number of _selectors_.
> Example invalid messages resulting in a _Variant Key Mismatch_ error:
>
> ```
> .match {$one :func}
> .input {$one :func}
> .match $one
> 1 2 {{Too many}}
> * {{Otherwise}}
> ```
>
> ```
> .match {$one :func} {$two :func}
> .input {$one :func}
> .input {$two :func}
> .match $one $two
> 1 2 {{Two keys}}
> * {{Missing a key}}
> * * {{Otherwise}}
Expand All @@ -105,41 +109,44 @@ does not include a _variant_ with only catch-all keys.
> Example invalid messages resulting in a _Missing Fallback Variant_ error:
>
> ```
> .match {$one :func}
> .input {$one :func}
> .match $one
> 1 {{Value is one}}
> 2 {{Value is two}}
> ```
>
> ```
> .match {$one :func} {$two :func}
> .input {$one :func}
> .input {$two :func}
> .match $one $two
> 1 * {{First is one}}
> * 1 {{Second is one}}
> ```

### Missing Selector Annotation

A **_<dfn>Missing Selector Annotation</dfn>_** error occurs when the _message_
contains a _selector_ that does not have an _annotation_,
or contains a _variable_ that does not directly or indirectly reference a _declaration_ with an _annotation_.
contains a _selector_ that does not
directly or indirectly reference a _declaration_ with a _function_.

> Examples of invalid messages resulting in a _Missing Selector Annotation_ error:
>
> ```
> .match {$one}
> .match $one
> 1 {{Value is one}}
> * {{Value is not one}}
> ```
>
> ```
> .local $one = {|The one|}
> .match {$one}
> .match $one
> 1 {{Value is one}}
> * {{Value is not one}}
> ```
>
> ```
> .input {$one}
> .match {$one}
> .match $one
> 1 {{Value is one}}
> * {{Value is not one}}
> ```
Expand Down Expand Up @@ -199,13 +206,16 @@ same list of _keys_ is used for more than one _variant_.
> Examples of invalid messages resulting in a _Duplicate Variant_ error:
>
> ```
> .match {$var :string}
> .input {$var :string}
> .match $var
> * {{The first default}}
> * {{The second default}}
> ```
>
> ```
> .match {$x :string} {$y :string}
> .input {$x :string}
> .input {$y :string}
> .match $x $y
> * foo {{The first "foo" variant}}
> bar * {{The "bar" variant}}
> * |foo| {{The second "foo" variant}}
Expand All @@ -230,7 +240,8 @@ An **_<dfn>Unresolved Variable</dfn>_** error occurs when a variable reference c
> ```
>
> ```
> .match {$var :func}
> .input {$var :func}
> .match $var
> 1 {{The value is one.}}
> * {{The value is not one.}}
> ```
Expand All @@ -249,7 +260,8 @@ a reference to a function which cannot be resolved.
> ```
>
> ```
> .match {|horse| :func}
> .local $horse = {|horse| :func}
> .match $horse
> 1 {{The value is one.}}
> * {{The value is not one.}}
> ```
Expand All @@ -264,7 +276,7 @@ with a resolved value which does not support selection.
>
> ```
> .local $day = {|2024-05-01| :date}
> .match {$day}
> .match $day
> * {{The due date is {$day}}}
> ```

Expand Down Expand Up @@ -326,7 +338,8 @@ for that specific _function_.
> ```
>
> ```
> .match {|horse| :number}
> .local $horse = {|horse| :number}
> .match $horse
> 1 {{The value is one.}}
> * {{The value is not one.}}
> ```
Expand Down Expand Up @@ -363,7 +376,8 @@ does not match the expected implementation-defined format.
> which is a requirement of the `:number` function:
>
> ```
> .match {42 :number}
> .local $answer = {42 :number}
> .match $answer
> 1 {{The value is one.}}
> horse {{The value is a horse.}}
> * {{The value is not one.}}
Expand Down
41 changes: 19 additions & 22 deletions spec/formatting.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,9 @@ nor be made available to function implementations.
> value of a given _expression_ until it is actually used by a
> selection or formatting process.
> However, when an _expression_ is resolved, it MUST behave as if all preceding
> _declarations_ and _selectors_ affecting _variables_ referenced by that _expression_
> _declarations_ affecting _variables_ referenced by that _expression_
> have already been evaluated in the order in which the relevant _declarations_
> and _selectors_ appear in the _message_.
> appear in the _message_.

## Formatting Context

Expand Down Expand Up @@ -85,7 +85,7 @@ Implementations MAY include additional fields in their _formatting context_.

## Expression and Markup Resolution

_Expressions_ are used in _declarations_, _selectors_, and _patterns_.
_Expressions_ are used in _declarations_ and _patterns_.
_Markup_ is only used in _patterns_.

In a _declaration_, the resolved value of the _expression_ is bound to a _variable_,
Expand All @@ -97,8 +97,6 @@ In an _input-declaration_, the _variable_ operand of the _variable-expression_
identifies not only the name of the external input value,
but also the _variable_ to which the resolved value of the _variable-expression_ is bound.

In _selectors_, the resolved value of an _expression_ is used for _pattern selection_.

In a _pattern_, the resolved value of an _expression_ or _markup_ is used in its _formatting_.

The form that resolved values take is implementation-dependent,
Expand Down Expand Up @@ -429,7 +427,8 @@ according to their _key_ values and selecting the first one.
> > For example, in the `pl` (Polish) locale, this _message_ cannot reach
> > the `*` _variant_:
> > ```
> > .match {$num :integer}
> > .input {$num :integer}
> > .match $num
> > 0 {{ }}
> > one {{ }}
> > few {{ }}
Expand All @@ -449,13 +448,16 @@ Each _key_ corresponds to a _selector_ by its position in the _variant_.
> For example, in this message:
>
> ```
> .match {:one} {:two} {:three}
> .input {$one :number}
> .input {$two :number}
> .input {$three :number}
> .match $one $two $three
> 1 2 3 {{ ... }}
> ```
>
> The first _key_ `1` corresponds to the first _selector_ (`{:one}`),
> the second _key_ `2` to the second _selector_ (`{:two}`),
> and the third _key_ `3` to the third _selector_ (`{:three}`).
> The first _key_ `1` corresponds to the first _selector_ (`$one`),
> the second _key_ `2` to the second _selector_ (`$two`),
> and the third _key_ `3` to the third _selector_ (`$three`).

To determine which _variant_ best matches a given set of inputs,
each _selector_ is used in turn to order and filter the list of _variants_.
Expand All @@ -468,15 +470,6 @@ Earlier _selectors_ in the _matcher_'s list of _selectors_ have a higher priorit
When all of the _selectors_ have been processed,
the earliest-sorted _variant_ in the remaining list of _variants_ is selected.

> [!NOTE]
> A _selector_ is not a _declaration_.
> Even when the same _function_ can be used for both formatting and selection
> of a given _operand_
> the _annotation_ that appears in a _selector_ has no effect on subsequent
> _selectors_ nor on the formatting used in _placeholders_.
> To use the same value for selection and formatting,
> set its value with a `.input` or `.local` _declaration_.

This selection method is defined in more detail below.
An implementation MAY use any pattern selection method,
as long as its observable behavior matches the results of the method defined here.
Expand Down Expand Up @@ -600,7 +593,9 @@ the variable reference `$bar` resolves to the string `'bar'`,
pattern selection proceeds as follows for this message:

```
.match {$foo :string} {$bar :string}
.input {$foo :string}
.input {$bar :string}
.match $foo $bar
bar bar {{All bar}}
foo foo {{All foo}}
* * {{Otherwise}}
Expand Down Expand Up @@ -631,7 +626,9 @@ Alternatively, with the same implementation and formatting context as in Example
pattern selection would proceed as follows for this message:

```
.match {$foo :string} {$bar :string}
.input {$foo :string}
.input {$bar :string}
.match $foo $bar
* bar {{Any and bar}}
foo * {{Foo and any}}
foo bar {{Foo and bar}}
Expand Down Expand Up @@ -680,7 +677,7 @@ the pattern selection proceeds as follows for this message:

```
.input {$count :number}
.match {$count}
.match $count
one {{Category match for {$count}}}
1 {{Exact match for {$count}}}
* {{Other match for {$count}}}
Expand Down
6 changes: 3 additions & 3 deletions spec/message.abnf
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ local-declaration = local s variable [s] "=" [s] expression

quoted-pattern = "{{" pattern "}}"

matcher = match-statement 1*([s] variant)
match-statement = match 1*([s] selector)
selector = expression
matcher = match-statement s variant *([s] variant)
Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't like the inconsistency of whitespace being required before the first match variant and optional before later ones, but it is necessary to support unquoted variant keys as in

.input{$count :number}.match $count␣1{{1 new message}}*{{{$count} new messages}}

match-statement = match 1*(s selector)
selector = variable
eemeli marked this conversation as resolved.
Show resolved Hide resolved
variant = key *(s key) [s] quoted-pattern
key = literal / "*"

Expand Down
Loading