22
33namespace Tempest \Intl \MessageFormat \Formatter ;
44
5- use Exception ;
6- use Tempest \Intl \Locale ;
75use Tempest \Intl \MessageFormat \FormattingFunction ;
86use Tempest \Intl \MessageFormat \Parser \Node \ComplexBody \ComplexBody ;
97use Tempest \Intl \MessageFormat \Parser \Node \ComplexBody \Matcher ;
1210use Tempest \Intl \MessageFormat \Parser \Node \Declaration \InputDeclaration ;
1311use Tempest \Intl \MessageFormat \Parser \Node \Declaration \LocalDeclaration ;
1412use Tempest \Intl \MessageFormat \Parser \Node \Expression \Expression ;
13+ use Tempest \Intl \MessageFormat \Parser \Node \Expression \FunctionCall ;
1514use Tempest \Intl \MessageFormat \Parser \Node \Expression \FunctionExpression ;
1615use Tempest \Intl \MessageFormat \Parser \Node \Expression \LiteralExpression ;
1716use Tempest \Intl \MessageFormat \Parser \Node \Expression \VariableExpression ;
2827use Tempest \Intl \MessageFormat \Parser \Node \SimpleMessage ;
2928use Tempest \Intl \MessageFormat \Parser \Node \Variable ;
3029use Tempest \Intl \MessageFormat \Parser \Parser ;
31- use Tempest \Intl \PluralRules \PluralRulesMatcher ;
30+ use Tempest \Intl \MessageFormat \SelectorFunction ;
31+
32+ use function Tempest \Support \arr ;
3233
3334final class MessageFormatter
3435{
35- /** @var array<string,mixed > $variables */
36+ /** @var array<string,LocalVariable > $variables */
3637 private array $ variables = [];
3738
3839 public function __construct (
3940 /** @var FormattingFunction[] */
4041 private readonly array $ functions = [],
41- private readonly PluralRulesMatcher $ pluralRules = new PluralRulesMatcher (),
4242 ) {}
4343
4444 /**
@@ -49,7 +49,7 @@ public function format(string $message, mixed ...$variables): string
4949 try {
5050 $ ast = new Parser ($ message )->parse ();
5151
52- $ this ->variables = $ variables ;
52+ $ this ->variables = $ this -> parseLocalVariables ( $ variables) ;
5353
5454 return $ this ->formatMessage ($ ast , $ variables );
5555 } catch (ParsingException $ e ) {
@@ -72,30 +72,40 @@ private function formatMessage(MessageNode $message): string
7272
7373 foreach ($ message ->declarations as $ declaration ) {
7474 if ($ declaration instanceof InputDeclaration) {
75- $ variableName = $ declaration ->expression ->variable ->name ->name ;
75+ $ expression = $ declaration ->expression ;
76+ $ variableName = $ expression ->variable ->name ->name ;
7677
7778 if (! array_key_exists ($ variableName , $ this ->variables )) {
78- throw new FormattingException ("Required input variable ' {$ variableName }' not provided. " );
79+ throw new FormattingException ("Required input variable ` {$ variableName }` not provided. " );
80+ }
81+
82+ if ($ expression ->function instanceof FunctionCall) {
83+ $ this ->variables [$ variableName ] = new LocalVariable (
84+ identifier: $ variableName ,
85+ value: $ this ->variables [$ variableName ]->value ,
86+ function: $ this ->getSelectorFunction ((string ) $ expression ->function ->identifier ),
87+ parameters: $ this ->evaluateOptions ($ expression ->function ->options ),
88+ );
7989 }
8090 } elseif ($ declaration instanceof LocalDeclaration) {
8191 $ variableName = $ declaration ->variable ->name ->name ;
82- $ value = $ this ->evaluateExpression ($ declaration ->expression );
83- $ localVariables [$ variableName ] = $ value ->value ;
92+
93+ $ localVariables [$ variableName ] = new LocalVariable (
94+ identifier: $ variableName ,
95+ value: $ this ->evaluateExpression ($ declaration ->expression )->value ,
96+ function: $ this ->getSelectorFunction ($ declaration ->expression ->function ?->identifier),
97+ parameters: $ declaration ->expression ->attributes ,
98+ );
8499 }
85100 }
86101
87102 $ originalVariables = $ this ->variables ;
88103 $ this ->variables = [...$ this ->variables , ...$ localVariables ];
89104
90105 try {
91- $ result = $ this ->formatComplexBody ($ message ->body );
92- $ this ->variables = $ originalVariables ;
93-
94- return $ result ;
95- } catch (Exception $ e ) {
106+ return $ this ->formatComplexBody ($ message ->body );
107+ } finally {
96108 $ this ->variables = $ originalVariables ;
97-
98- throw $ e ;
99109 }
100110 }
101111
@@ -121,44 +131,55 @@ private function formatComplexBody(ComplexBody $body): string
121131
122132 private function formatMatcher (Matcher $ matcher ): string
123133 {
124- $ selectorValues = [];
134+ $ selectorVariables = [];
125135
126136 foreach ($ matcher ->selectors as $ selector ) {
127137 $ variableName = $ selector ->name ->name ;
128138
129139 if (! array_key_exists ($ variableName , $ this ->variables )) {
130- throw new FormattingException ("Selector variable ' {$ variableName }' not found. " );
140+ throw new FormattingException ("Selector variable ` {$ variableName }` not found. " );
131141 }
132142
133- $ selectorValues [] = $ this ->variables [$ variableName ];
143+ $ selectorVariables [] = $ this ->variables [$ variableName ];
134144 }
135145
136- // Find the best matching variant
137146 $ bestVariant = null ;
138147 $ wildcardVariant = null ;
139148
140149 foreach ($ matcher ->variants as $ variant ) {
141- if (count ($ variant ->keys ) !== count ($ selectorValues )) {
142- continue ; // Key count mismatch
150+ if (count ($ variant ->keys ) !== count ($ selectorVariables )) {
151+ continue ;
143152 }
144153
145154 $ matches = true ;
146155 $ hasWildcard = false ;
147156
148157 for ($ i = 0 ; $ i < count ($ variant ->keys ); $ i ++) {
149- $ key = $ variant ->keys [$ i ];
150- $ selectorValue = $ selectorValues [$ i ];
158+ $ keyNode = $ variant ->keys [$ i ];
159+ $ variable = $ selectorVariables [$ i ];
151160
152- if ($ key instanceof WildcardKey) {
161+ if ($ keyNode instanceof WildcardKey) {
153162 $ hasWildcard = true ;
154163 continue ;
155164 }
156165
157- if ($ key instanceof Literal) {
158- if (! $ this ->matchesKey ($ selectorValue , $ key ->value )) {
159- $ matches = false ;
160- break ;
161- }
166+ if (! ($ keyNode instanceof Literal)) {
167+ $ matches = false ;
168+ break ;
169+ }
170+
171+ $ variantKey = $ keyNode ->value ;
172+ $ isMatch = false ;
173+
174+ if ($ variable ->function ) {
175+ $ isMatch = $ variable ->function ->match ($ variantKey , $ variable ->value , $ variable ->parameters );
176+ } else {
177+ $ isMatch = $ variable ->value === $ variantKey ;
178+ }
179+
180+ if (! $ isMatch ) {
181+ $ matches = false ;
182+ break ;
162183 }
163184 }
164185
@@ -175,29 +196,15 @@ private function formatMatcher(Matcher $matcher): string
175196 $ selectedVariant = $ bestVariant ?? $ wildcardVariant ;
176197
177198 if ($ selectedVariant === null ) {
199+ $ selectorValues = array_column ($ selectorVariables , 'value ' );
200+
201+ // TODO: test this
178202 throw new FormattingException ('No matching variant found for selector values: ' . json_encode ($ selectorValues ));
179203 }
180204
181205 return $ this ->formatPattern ($ selectedVariant ->pattern ->pattern );
182206 }
183207
184- private function matchesKey (mixed $ value , string $ keyValue ): bool
185- {
186- if (is_numeric ($ value )) {
187- $ number = (float ) $ value ;
188-
189- if ($ keyValue === ((string ) $ number ) || $ keyValue === ((string ) ((int ) $ number ))) {
190- return true ;
191- }
192-
193- if ($ keyValue === $ this ->pluralRules ->getPluralCategory (Locale::default (), $ number )) {
194- return true ;
195- }
196- }
197-
198- return ((string ) $ value ) === $ keyValue ;
199- }
200-
201208 private function formatPattern (Pattern $ pattern ): string
202209 {
203210 $ result = '' ;
@@ -245,7 +252,7 @@ private function evaluateExpression(Expression $expression): FormattedValue
245252 throw new FormattingException ("Variable ` {$ variableName }` not found " );
246253 }
247254
248- $ value = $ this ->variables [$ variableName ];
255+ $ value = $ this ->variables [$ variableName ]-> value ;
249256 } elseif ($ expression instanceof FunctionExpression) {
250257 $ value = null ; // Function-only expressions start with null
251258 }
@@ -254,7 +261,7 @@ private function evaluateExpression(Expression $expression): FormattedValue
254261 $ functionName = (string ) $ expression ->function ->identifier ;
255262 $ options = $ this ->evaluateOptions ($ expression ->function ->options );
256263
257- if ($ function = $ this ->getFunction ($ functionName )) {
264+ if ($ function = $ this ->getFormattingFunction ($ functionName )) {
258265 return $ function ->format ($ value , $ options );
259266 } else {
260267 throw new FormattingException ("Unknown function ` {$ functionName }`. " );
@@ -266,12 +273,26 @@ private function evaluateExpression(Expression $expression): FormattedValue
266273 return new FormattedValue ($ value , $ formatted );
267274 }
268275
269- private function getFunction ( string $ name ): ?FormattingFunction
276+ private function getSelectorFunction (? string $ name ): ?SelectorFunction
270277 {
271- return array_find (
272- array: $ this ->functions ,
273- callback: fn (FormattingFunction $ fn ) => $ fn ->name === $ name ,
274- );
278+ if (! $ name ) {
279+ return null ;
280+ }
281+
282+ return arr ($ this ->functions )
283+ ->filter (fn (FormattingFunction |SelectorFunction $ fn ) => $ fn instanceof SelectorFunction)
284+ ->first (fn (SelectorFunction $ fn ) => $ fn ->name === $ name );
285+ }
286+
287+ private function getFormattingFunction (?string $ name ): ?FormattingFunction
288+ {
289+ if (! $ name ) {
290+ return null ;
291+ }
292+
293+ return arr ($ this ->functions )
294+ ->filter (fn (FormattingFunction |SelectorFunction $ fn ) => $ fn instanceof FormattingFunction)
295+ ->first (fn (FormattingFunction $ fn ) => $ fn ->name === $ name );
275296 }
276297
277298 private function evaluateOptions (array $ options ): array
@@ -288,7 +309,7 @@ private function evaluateOptions(array $options): array
288309 throw new FormattingException ("Option variable ` {$ variableName }` not found. " );
289310 }
290311
291- $ result [$ name ] = $ this ->variables [$ variableName ];
312+ $ result [$ name ] = $ this ->variables [$ variableName ]-> value ;
292313 } elseif ($ option ->value instanceof Literal) {
293314 $ result [$ name ] = $ option ->value ->value ;
294315 }
@@ -297,6 +318,24 @@ private function evaluateOptions(array $options): array
297318 return $ result ;
298319 }
299320
321+ private function parseLocalVariables (array $ variables ): array
322+ {
323+ $ result = [];
324+
325+ foreach ($ variables as $ key => $ value ) {
326+ if ($ value instanceof LocalVariable) {
327+ $ result [$ key ] = $ value ;
328+ } else {
329+ $ result [$ key ] = new LocalVariable (
330+ identifier: $ key ,
331+ value: $ value ,
332+ );
333+ }
334+ }
335+
336+ return $ result ;
337+ }
338+
300339 private function formatMarkup (Markup $ markup ): string
301340 {
302341 // TODO: more advanced with options
0 commit comments