From 2879325e429aadb9e8d585b3e41270ed0bac3355 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sta=C5=9B=20Ma=C5=82olepszy?= Date: Fri, 20 Aug 2021 22:50:05 +0200 Subject: [PATCH] (third) Add RuntimeValue.match() --- .../stasm/third/example/example_list.ts | 6 +- .../stasm/third/example/example_opaque.ts | 6 +- experiments/stasm/third/impl/context.ts | 70 +++++-------------- experiments/stasm/third/impl/model.ts | 3 +- experiments/stasm/third/impl/runtime.ts | 33 ++++++++- 5 files changed, 63 insertions(+), 55 deletions(-) diff --git a/experiments/stasm/third/example/example_list.ts b/experiments/stasm/third/example/example_list.ts index 74ebde509..808ce945c 100644 --- a/experiments/stasm/third/example/example_list.ts +++ b/experiments/stasm/third/example/example_list.ts @@ -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, @@ -40,6 +40,10 @@ class ListValue extends RuntimeValue> { 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 ( diff --git a/experiments/stasm/third/example/example_opaque.ts b/experiments/stasm/third/example/example_opaque.ts index cbc8d1f84..907ca6c35 100644 --- a/experiments/stasm/third/example/example_opaque.ts +++ b/experiments/stasm/third/example/example_opaque.ts @@ -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 @@ -16,6 +16,10 @@ class WrappedValue extends RuntimeValue { *formatToParts(ctx: FormattingContext): IterableIterator { yield {type: "opaque", value: this.value}; } + + match(ctx: FormattingContext, key: VariantKey): boolean { + return false; + } } test("Pass an opaque instance as a variable", (tap) => { diff --git a/experiments/stasm/third/impl/context.ts b/experiments/stasm/third/impl/context.ts index 74a639c0a..626051f73 100644 --- a/experiments/stasm/third/impl/context.ts +++ b/experiments/stasm/third/impl/context.ts @@ -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. @@ -64,27 +56,22 @@ export class FormattingContext { } selectVariant(variants: Array, selectors: Array): Variant { - interface ResolvedSelector { - value: RuntimeValue; - default: string; - } + let selector_values: Array> = []; + let selector_defaults: Array = []; - let resolved_selectors: Array = []; 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: @@ -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; } } diff --git a/experiments/stasm/third/impl/model.ts b/experiments/stasm/third/impl/model.ts index f313d8cdd..7653d4454 100644 --- a/experiments/stasm/third/impl/model.ts +++ b/experiments/stasm/third/impl/model.ts @@ -17,10 +17,11 @@ export interface Selector { } export interface Variant { - keys: Array; + keys: Array; value: Array; } +export type VariantKey = StringLiteral | IntegerLiteral; export type PatternElement = StringLiteral | VariableReference | FunctionCall; export interface FunctionCall { diff --git a/experiments/stasm/third/impl/runtime.ts b/experiments/stasm/third/impl/runtime.ts index 9d01235a7..b24a16b1a 100644 --- a/experiments/stasm/third/impl/runtime.ts +++ b/experiments/stasm/third/impl/runtime.ts @@ -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; @@ -26,6 +26,7 @@ export abstract class RuntimeValue { abstract formatToString(ctx: FormattingContext): string; abstract formatToParts(ctx: FormattingContext): IterableIterator; + abstract match(ctx: FormattingContext, key: VariantKey): boolean; } export class StringValue extends RuntimeValue { @@ -36,6 +37,10 @@ export class StringValue extends RuntimeValue { *formatToParts(ctx: FormattingContext): IterableIterator { yield {type: "literal", value: this.value}; } + + match(ctx: FormattingContext, key: VariantKey): boolean { + return this.value === key.value; + } } export class NumberValue extends RuntimeValue { @@ -54,6 +59,13 @@ export class NumberValue extends RuntimeValue { *formatToParts(ctx: FormattingContext): IterableIterator { 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 { @@ -71,6 +83,17 @@ export class PluralValue extends RuntimeValue { *formatToParts(ctx: FormattingContext): IterableIterator { 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 { @@ -81,6 +104,10 @@ export class BooleanValue extends RuntimeValue { *formatToParts(ctx: FormattingContext): IterableIterator { 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> { @@ -93,6 +120,10 @@ export class PatternValue extends RuntimeValue> { yield* value.formatToParts(ctx); } } + + match(ctx: FormattingContext, key: VariantKey): boolean { + throw new TypeError("PatternValue cannot match."); + } } export function formatMessage(