Skip to content

Commit 51df1f3

Browse files
committed
(third) Factor FormattingContext out from runtime.ts
1 parent 661ecc6 commit 51df1f3

File tree

6 files changed

+155
-154
lines changed

6 files changed

+155
-154
lines changed

experiments/stasm/third/example/example_glossary.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
import {FormattingContext} from "../impl/context.js";
12
import {Argument, Message, Parameter} from "../impl/model.js";
23
import {REGISTRY} from "../impl/registry.js";
3-
import {formatMessage, FormattingContext, formatToParts, StringValue} from "../impl/runtime.js";
4+
import {formatMessage, formatToParts, StringValue} from "../impl/runtime.js";
45
import {get_term} from "./glossary.js";
56

67
REGISTRY["NOUN"] = function get_noun(

experiments/stasm/third/example/example_list.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1+
import {FormattingContext} from "../impl/context.js";
12
import {Argument, Message, Parameter} from "../impl/model.js";
23
import {REGISTRY} from "../impl/registry.js";
34
import {
45
formatMessage,
56
FormattedPart,
6-
FormattingContext,
77
formatToParts,
88
PluralValue,
99
RuntimeValue,

experiments/stasm/third/example/example_opaque.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
import {FormattingContext} from "../impl/context.js";
12
import {Message} from "../impl/model.js";
2-
import {FormattingContext, formatToParts, OpaquePart, RuntimeValue} from "../impl/runtime.js";
3+
import {formatToParts, OpaquePart, RuntimeValue} from "../impl/runtime.js";
34

45
// We want to pass it into the translation and get it back out unformatted, in
56
// the correct position in the sentence, via formatToParts.
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
import {Message, Parameter, PatternElement, Selector, StringLiteral, Variant} from "./model.js";
2+
import {REGISTRY} from "./registry.js";
3+
import {BooleanValue, NumberValue, RuntimeValue, StringValue} from "./runtime.js";
4+
5+
// Resolution context for a single formatMessage() call.
6+
7+
export class FormattingContext {
8+
locale: string;
9+
message: Message;
10+
vars: Record<string, RuntimeValue<unknown>>;
11+
visited: WeakSet<Array<PatternElement>>;
12+
// TODO(stasm): expose cached formatters, etc.
13+
14+
constructor(locale: string, message: Message, vars: Record<string, RuntimeValue<unknown>>) {
15+
this.locale = locale;
16+
this.message = message;
17+
this.vars = vars;
18+
this.visited = new WeakSet();
19+
}
20+
21+
formatPattern(pattern: Array<PatternElement>): string {
22+
let output = "";
23+
for (let value of this.resolvePattern(pattern)) {
24+
output += value.formatToString(this);
25+
}
26+
return output;
27+
}
28+
29+
*resolvePattern(pattern: Array<PatternElement>): IterableIterator<RuntimeValue<unknown>> {
30+
if (this.visited.has(pattern)) {
31+
throw new RangeError("Recursive reference to a variant value.");
32+
}
33+
34+
this.visited.add(pattern);
35+
36+
let result = "";
37+
for (let element of pattern) {
38+
switch (element.type) {
39+
case "StringLiteral":
40+
yield new StringValue(element.value);
41+
continue;
42+
case "VariableReference": {
43+
yield this.vars[element.name];
44+
continue;
45+
}
46+
case "FunctionCall": {
47+
let callable = REGISTRY[element.name];
48+
yield callable(this, element.args, element.opts);
49+
continue;
50+
}
51+
}
52+
}
53+
54+
this.visited.delete(pattern);
55+
return result;
56+
}
57+
58+
selectVariant(variants: Array<Variant>, selectors: Array<Selector>): Variant {
59+
interface ResolvedSelector<T> {
60+
value: T | null;
61+
string: string | null;
62+
default: string;
63+
}
64+
65+
let resolved_selectors: Array<ResolvedSelector<unknown>> = [];
66+
for (let selector of selectors) {
67+
if (selector.expr === null) {
68+
// A special selector which only selects its default value. Used in the
69+
// data model of single-variant messages.
70+
resolved_selectors.push({
71+
value: null,
72+
string: null,
73+
default: selector.default.value,
74+
});
75+
continue;
76+
}
77+
78+
switch (selector.expr.type) {
79+
case "VariableReference": {
80+
let value = this.vars[selector.expr.name];
81+
resolved_selectors.push({
82+
value: value.value,
83+
string: value.formatToString(this),
84+
default: selector.default.value,
85+
});
86+
break;
87+
}
88+
case "FunctionCall": {
89+
let callable = REGISTRY[selector.expr.name];
90+
let value = callable(this, selector.expr.args, selector.expr.opts);
91+
resolved_selectors.push({
92+
value: value.value,
93+
string: value.formatToString(this),
94+
default: selector.default.value,
95+
});
96+
break;
97+
}
98+
default:
99+
// TODO(stasm): Should we allow Literals as selectors?
100+
throw new TypeError();
101+
}
102+
}
103+
104+
// TODO(stasm): Add NumberLiterals as keys (maybe).
105+
function matches_corresponding_selector(key: StringLiteral, idx: number) {
106+
return (
107+
key.value === resolved_selectors[idx].string ||
108+
key.value === resolved_selectors[idx].default
109+
);
110+
}
111+
112+
for (let variant of variants) {
113+
if (variant.keys.every(matches_corresponding_selector)) {
114+
return variant;
115+
}
116+
}
117+
118+
throw new RangeError("No variant matched the selectors.");
119+
}
120+
121+
toRuntimeValue(node: Parameter): RuntimeValue<unknown> {
122+
if (typeof node === "undefined") {
123+
return new BooleanValue(false);
124+
}
125+
126+
switch (node.type) {
127+
case "StringLiteral":
128+
return new StringValue(node.value);
129+
case "IntegerLiteral":
130+
return new NumberValue(parseInt(node.value));
131+
case "DecimalLiteral":
132+
return new NumberValue(parseFloat(node.value));
133+
case "BooleanLiteral":
134+
return new BooleanValue(node.value);
135+
case "VariableReference":
136+
return this.vars[node.name];
137+
default:
138+
throw new TypeError("Invalid node type.");
139+
}
140+
}
141+
}

experiments/stasm/third/impl/registry.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,6 @@
1+
import {FormattingContext} from "./context.js";
12
import {Argument, Parameter} from "./model.js";
2-
import {
3-
FormattingContext,
4-
NumberValue,
5-
PatternValue,
6-
PluralValue,
7-
RuntimeValue,
8-
StringValue,
9-
} from "./runtime.js";
3+
import {NumberValue, PatternValue, PluralValue, RuntimeValue, StringValue} from "./runtime.js";
104

115
export type RegistryFunc<T> = (
126
ctx: FormattingContext,

experiments/stasm/third/impl/runtime.ts

Lines changed: 7 additions & 143 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
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

44
export 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.
2020
export 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-
236100
export function formatMessage(
237101
message: Message,
238102
vars: Record<string, RuntimeValue<unknown>>

0 commit comments

Comments
 (0)