Skip to content

Commit

Permalink
(third) Add RuntimeValue.match()
Browse files Browse the repository at this point in the history
  • Loading branch information
stasm committed Aug 20, 2021
1 parent 3cb74f1 commit 2879325
Show file tree
Hide file tree
Showing 5 changed files with 63 additions and 55 deletions.
6 changes: 5 additions & 1 deletion experiments/stasm/third/example/example_list.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {test} from "tap";
import {FormattingContext} from "../impl/context.js";
import {Argument, Message, Parameter} from "../impl/model.js";
import {Argument, Message, Parameter, VariantKey} from "../impl/model.js";
import {REGISTRY} from "../impl/registry.js";
import {
formatMessage,
Expand Down Expand Up @@ -40,6 +40,10 @@ class ListValue<T> extends RuntimeValue<Array<T>> {
let lf = new Intl.ListFormat(ctx.locale, this.opts);
yield* lf.formatToParts(this.value);
}

match(ctx: FormattingContext, key: VariantKey): boolean {
return false;
}
}

REGISTRY["PLURAL_LEN"] = function (
Expand Down
6 changes: 5 additions & 1 deletion experiments/stasm/third/example/example_opaque.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {test} from "tap";
import {FormattingContext} from "../impl/context.js";
import {Message} from "../impl/model.js";
import {Message, VariantKey} from "../impl/model.js";
import {formatToParts, OpaquePart, RuntimeValue} from "../impl/runtime.js";

// We want to pass it into the translation and get it back out unformatted, in
Expand All @@ -16,6 +16,10 @@ class WrappedValue extends RuntimeValue<SomeUnstringifiableClass> {
*formatToParts(ctx: FormattingContext): IterableIterator<OpaquePart> {
yield {type: "opaque", value: this.value};
}

match(ctx: FormattingContext, key: VariantKey): boolean {
return false;
}
}

test("Pass an opaque instance as a variable", (tap) => {
Expand Down
70 changes: 19 additions & 51 deletions experiments/stasm/third/impl/context.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,6 @@
import {
IntegerLiteral,
Message,
Parameter,
PatternElement,
Selector,
StringLiteral,
Variant,
} from "./model.js";
import {Message, Parameter, PatternElement, Selector, StringLiteral, Variant} from "./model.js";
import {REGISTRY} from "./registry.js";
import {BooleanValue, NumberValue, PluralValue, RuntimeValue, StringValue} from "./runtime.js";
import {BooleanValue, NumberValue, RuntimeValue, StringValue} from "./runtime.js";

// Resolution context for a single formatMessage() call.

Expand Down Expand Up @@ -64,27 +56,22 @@ export class FormattingContext {
}

selectVariant(variants: Array<Variant>, selectors: Array<Selector>): Variant {
interface ResolvedSelector {
value: RuntimeValue<unknown>;
default: string;
}
let selector_values: Array<RuntimeValue<unknown>> = [];
let selector_defaults: Array<StringLiteral> = [];

let resolved_selectors: Array<ResolvedSelector> = [];
for (let selector of selectors) {
switch (selector.expr.type) {
case "VariableReference": {
resolved_selectors.push({
value: this.vars[selector.expr.name],
default: selector.default.value,
});
let value = this.vars[selector.expr.name];
selector_values.push(value);
selector_defaults.push(selector.default);
break;
}
case "FunctionCall": {
let callable = REGISTRY[selector.expr.name];
resolved_selectors.push({
value: callable(this, selector.expr.args, selector.expr.opts),
default: selector.default.value,
});
let value = callable(this, selector.expr.args, selector.expr.opts);
selector_values.push(value);
selector_defaults.push(selector.default);
break;
}
default:
Expand All @@ -93,37 +80,18 @@ export class FormattingContext {
}
}

function matches_corresponding_selector(key: StringLiteral | IntegerLiteral, idx: number) {
let selector = resolved_selectors[idx];
switch (key.type) {
case "StringLiteral": {
if (key.value === selector.value.value) {
return true;
}
break;
}
case "IntegerLiteral": {
let num = parseInt(key.value);
if (selector.value instanceof NumberValue) {
if (num === selector.value.value) {
return true;
}
} else if (selector.value instanceof PluralValue) {
if (key.value === selector.value.value || num === selector.value.count) {
return true;
}
}
break;
}
}

return key.value === selector.default;
}

for (let variant of variants) {
// When keys is an empty array, every() always returns true. This is
// used single-variant messages to return their only variant.
if (variant.keys.every(matches_corresponding_selector)) {
if (
variant.keys.every(
(key, idx) =>
// Key matches corresponding selector value…
selector_values[idx].match(this, key) ||
// … or the corresponding default.
key.value === selector_defaults[idx].value
)
) {
return variant;
}
}
Expand Down
3 changes: 2 additions & 1 deletion experiments/stasm/third/impl/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,11 @@ export interface Selector {
}

export interface Variant {
keys: Array<StringLiteral | IntegerLiteral>;
keys: Array<VariantKey>;
value: Array<PatternElement>;
}

export type VariantKey = StringLiteral | IntegerLiteral;
export type PatternElement = StringLiteral | VariableReference | FunctionCall;

export interface FunctionCall {
Expand Down
33 changes: 32 additions & 1 deletion experiments/stasm/third/impl/runtime.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {FormattingContext} from "./context.js";
import {Message, PatternElement} from "./model.js";
import {Message, PatternElement, VariantKey} from "./model.js";

export interface FormattedPart {
type: string;
Expand All @@ -26,6 +26,7 @@ export abstract class RuntimeValue<T> {

abstract formatToString(ctx: FormattingContext): string;
abstract formatToParts(ctx: FormattingContext): IterableIterator<FormattedPart | OpaquePart>;
abstract match(ctx: FormattingContext, key: VariantKey): boolean;
}

export class StringValue extends RuntimeValue<string> {
Expand All @@ -36,6 +37,10 @@ export class StringValue extends RuntimeValue<string> {
*formatToParts(ctx: FormattingContext): IterableIterator<FormattedPart> {
yield {type: "literal", value: this.value};
}

match(ctx: FormattingContext, key: VariantKey): boolean {
return this.value === key.value;
}
}

export class NumberValue extends RuntimeValue<number> {
Expand All @@ -54,6 +59,13 @@ export class NumberValue extends RuntimeValue<number> {
*formatToParts(ctx: FormattingContext): IterableIterator<FormattedPart> {
yield* new Intl.NumberFormat(ctx.locale, this.opts).formatToParts(this.value);
}

match(ctx: FormattingContext, key: VariantKey): boolean {
if (key.type === "IntegerLiteral") {
return this.value === parseInt(key.value);
}
return false;
}
}

export class PluralValue extends RuntimeValue<Intl.LDMLPluralRule> {
Expand All @@ -71,6 +83,17 @@ export class PluralValue extends RuntimeValue<Intl.LDMLPluralRule> {
*formatToParts(ctx: FormattingContext): IterableIterator<FormattedPart> {
throw new TypeError("PluralValue is not formattable.");
}

match(ctx: FormattingContext, key: VariantKey): boolean {
switch (key.type) {
case "StringLiteral": {
return this.value === key.value;
}
case "IntegerLiteral": {
return this.count === parseInt(key.value);
}
}
}
}

export class BooleanValue extends RuntimeValue<boolean> {
Expand All @@ -81,6 +104,10 @@ export class BooleanValue extends RuntimeValue<boolean> {
*formatToParts(ctx: FormattingContext): IterableIterator<FormattedPart> {
throw new TypeError("BooleanValue is not formattable to parts.");
}

match(ctx: FormattingContext, key: VariantKey): boolean {
throw new TypeError("BooleanValue cannot match.");
}
}

export class PatternValue extends RuntimeValue<Array<PatternElement>> {
Expand All @@ -93,6 +120,10 @@ export class PatternValue extends RuntimeValue<Array<PatternElement>> {
yield* value.formatToParts(ctx);
}
}

match(ctx: FormattingContext, key: VariantKey): boolean {
throw new TypeError("PatternValue cannot match.");
}
}

export function formatMessage(
Expand Down

0 comments on commit 2879325

Please sign in to comment.