1- import { Message , Parameter , PatternElement , Selector , StringLiteral , Variant } from "./model .js" ;
2- import { REGISTRY } from "./registry .js" ;
1+ import { FormattingContext } from "./context .js" ;
2+ import { Message , PatternElement } from "./model .js" ;
33
44export interface FormattedPart {
55 type : string ;
@@ -12,11 +12,11 @@ export interface OpaquePart {
1212}
1313
1414// A value passed in as a variable to format() or to which literals are resolved
15- // at runtime. There are 4 built-in runtime value types in this implementation:
16- // StringValue, NumberValue, PluralValue, and BooleanValue . Other
17- // implementations may introduce additional types, e.g. Uint32Value.
18- // RuntimeValue can also be inherited from in the userspace code to create new
19- // variable types; see example_list's ArrayValue type.
15+ // at runtime. There are a number of built-in runtime value types in this
16+ // implementation: StringValue, NumberValue, etc . Other implementations may
17+ // introduce additional types, e.g. Uint32Value. RuntimeValue can also be
18+ // inherited from in the userspace code to create new variable types; see
19+ // example_list's ListValue type and example_opaque's WrappedValue .
2020export abstract class RuntimeValue < T > {
2121 public value : T ;
2222
@@ -97,142 +97,6 @@ export class PatternValue extends RuntimeValue<Array<PatternElement>> {
9797 }
9898}
9999
100- // Resolution context for a single formatMessage() call.
101- export class FormattingContext {
102- locale : string ;
103- message : Message ;
104- vars : Record < string , RuntimeValue < unknown > > ;
105- visited : WeakSet < Array < PatternElement > > ;
106- // TODO(stasm): expose cached formatters, etc.
107-
108- constructor ( locale : string , message : Message , vars : Record < string , RuntimeValue < unknown > > ) {
109- this . locale = locale ;
110- this . message = message ;
111- this . vars = vars ;
112- this . visited = new WeakSet ( ) ;
113- }
114-
115- formatPattern ( pattern : Array < PatternElement > ) : string {
116- let output = "" ;
117- for ( let value of this . resolvePattern ( pattern ) ) {
118- output += value . formatToString ( this ) ;
119- }
120- return output ;
121- }
122-
123- * resolvePattern ( pattern : Array < PatternElement > ) : IterableIterator < RuntimeValue < unknown > > {
124- if ( this . visited . has ( pattern ) ) {
125- throw new RangeError ( "Recursive reference to a variant value." ) ;
126- }
127-
128- this . visited . add ( pattern ) ;
129- let result = "" ;
130- for ( let element of pattern ) {
131- switch ( element . type ) {
132- case "StringLiteral" :
133- yield new StringValue ( element . value ) ;
134- continue ;
135- case "VariableReference" : {
136- yield this . vars [ element . name ] ;
137- continue ;
138- }
139- case "FunctionCall" : {
140- let callable = REGISTRY [ element . name ] ;
141- yield callable ( this , element . args , element . opts ) ;
142- continue ;
143- }
144- }
145- }
146-
147- this . visited . delete ( pattern ) ;
148- return result ;
149- }
150-
151- selectVariant ( variants : Array < Variant > , selectors : Array < Selector > ) : Variant {
152- interface ResolvedSelector < T > {
153- value : T | null ;
154- string : string | null ;
155- default : string ;
156- }
157-
158- let resolved_selectors : Array < ResolvedSelector < unknown > > = [ ] ;
159- for ( let selector of selectors ) {
160- if ( selector . expr === null ) {
161- // A special selector which only selects its default value. Used in the
162- // data model of single-variant messages.
163- resolved_selectors . push ( {
164- value : null ,
165- string : null ,
166- default : selector . default . value ,
167- } ) ;
168- continue ;
169- }
170-
171- switch ( selector . expr . type ) {
172- case "VariableReference" : {
173- let value = this . vars [ selector . expr . name ] ;
174- resolved_selectors . push ( {
175- value : value . value ,
176- string : value . formatToString ( this ) ,
177- default : selector . default . value ,
178- } ) ;
179- break ;
180- }
181- case "FunctionCall" : {
182- let callable = REGISTRY [ selector . expr . name ] ;
183- let value = callable ( this , selector . expr . args , selector . expr . opts ) ;
184- resolved_selectors . push ( {
185- value : value . value ,
186- string : value . formatToString ( this ) ,
187- default : selector . default . value ,
188- } ) ;
189- break ;
190- }
191- default :
192- // TODO(stasm): Should we allow Literals as selectors?
193- throw new TypeError ( ) ;
194- }
195- }
196-
197- // TODO(stasm): Add NumberLiterals as keys (maybe).
198- function matches_corresponding_selector ( key : StringLiteral , idx : number ) {
199- return (
200- key . value === resolved_selectors [ idx ] . string ||
201- key . value === resolved_selectors [ idx ] . default
202- ) ;
203- }
204-
205- for ( let variant of variants ) {
206- if ( variant . keys . every ( matches_corresponding_selector ) ) {
207- return variant ;
208- }
209- }
210-
211- throw new RangeError ( "No variant matched the selectors." ) ;
212- }
213-
214- toRuntimeValue ( node : Parameter ) : RuntimeValue < unknown > {
215- if ( typeof node === "undefined" ) {
216- return new BooleanValue ( false ) ;
217- }
218-
219- switch ( node . type ) {
220- case "StringLiteral" :
221- return new StringValue ( node . value ) ;
222- case "IntegerLiteral" :
223- return new NumberValue ( parseInt ( node . value ) ) ;
224- case "DecimalLiteral" :
225- return new NumberValue ( parseFloat ( node . value ) ) ;
226- case "BooleanLiteral" :
227- return new BooleanValue ( node . value ) ;
228- case "VariableReference" :
229- return this . vars [ node . name ] ;
230- default :
231- throw new TypeError ( "Invalid node type." ) ;
232- }
233- }
234- }
235-
236100export function formatMessage (
237101 message : Message ,
238102 vars : Record < string , RuntimeValue < unknown > >
0 commit comments