Skip to content

Implement Syntax 0.6 in the runtime #236

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 2, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions fluent/src/context.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ import parse from "./parser";
* responsible for parsing translation resources in the Fluent syntax and can
* format translation units (entities) to strings.
*
* Always use `MessageContext.format` to retrieve translation units from
* a context. Translations can contain references to other entities or
* external arguments, conditional logic in form of select expressions, traits
* which describe their grammatical features, and can use Fluent builtins which
* make use of the `Intl` formatters to format numbers, dates, lists and more
* into the context's language. See the documentation of the Fluent syntax for
* more information.
* Always use `MessageContext.format` to retrieve translation units from a
* context. Translations can contain references to other entities or variables,
* conditional logic in form of select expressions, traits which describe their
* grammatical features, and can use Fluent builtins which make use of the
* `Intl` formatters to format numbers, dates, lists and more into the
* context's language. See the documentation of the Fluent syntax for more
* information.
*/
export class MessageContext {

Expand Down Expand Up @@ -134,8 +134,8 @@ export class MessageContext {
* Format a message to a string or null.
*
* Format a raw `message` from the context into a string (or a null if it has
* a null value). `args` will be used to resolve references to external
* arguments inside of the translation.
* a null value). `args` will be used to resolve references to variables
* passed as arguments to the translation.
*
* In case of errors `format` will try to salvage as much of the translation
* as possible and will still return a string. For performance reasons, the
Expand All @@ -153,7 +153,7 @@ export class MessageContext {
*
* // Returns 'Hello, name!' and `errors` is now:
*
* [<ReferenceError: Unknown external: name>]
* [<ReferenceError: Unknown variable: name>]
*
* @param {Object | string} message
* @param {Object | undefined} args
Expand Down
62 changes: 16 additions & 46 deletions fluent/src/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,46 +74,15 @@ class RuntimeParser {
const ch = this._source[this._index];

// We don't care about comments or sections at runtime
if (ch === "/" ||
(ch === "#" &&
[" ", "#", "\n"].includes(this._source[this._index + 1]))) {
if (ch === "#" &&
[" ", "#", "\n"].includes(this._source[this._index + 1])) {
this.skipComment();
return;
}

if (ch === "[") {
this.skipSection();
return;
}

this.getMessage();
}

/**
* Skip the section entry from the current index.
*
* @private
*/
skipSection() {
this._index += 1;
if (this._source[this._index] !== "[") {
throw this.error('Expected "[[" to open a section');
}

this._index += 1;

this.skipInlineWS();
this.getVariantName();
this.skipInlineWS();

if (this._source[this._index] !== "]" ||
this._source[this._index + 1] !== "]") {
throw this.error('Expected "]]" to close a section');
}

this._index += 2;
}

/**
* Parse the source string from the current index as an FTL message
* and add it to the entries property on the Parser.
Expand All @@ -127,6 +96,8 @@ class RuntimeParser {

if (this._source[this._index] === "=") {
this._index++;
} else {
throw this.error("Expected \"=\" after the identifier");
}

this.skipInlineWS();
Expand Down Expand Up @@ -216,7 +187,7 @@ class RuntimeParser {
* Get identifier using the provided regex.
*
* By default this will get identifiers of public messages, attributes and
* external arguments (without the $).
* variables (without the $).
*
* @returns {String}
* @private
Expand Down Expand Up @@ -499,7 +470,7 @@ class RuntimeParser {
const ch = this._source[this._index];

if (ch === "}") {
if (selector.type === "attr" && selector.id.name.startsWith("-")) {
if (selector.type === "getattr" && selector.id.name.startsWith("-")) {
throw this.error(
"Attributes of private messages cannot be interpolated."
);
Expand All @@ -516,11 +487,11 @@ class RuntimeParser {
throw this.error("Message references cannot be used as selectors.");
}

if (selector.type === "var") {
if (selector.type === "getvar") {
throw this.error("Variants cannot be used as selectors.");
}

if (selector.type === "attr" && !selector.id.name.startsWith("-")) {
if (selector.type === "getattr" && !selector.id.name.startsWith("-")) {
throw this.error(
"Attributes of public messages cannot be used as selectors."
);
Expand Down Expand Up @@ -570,7 +541,7 @@ class RuntimeParser {
const name = this.getIdentifier();
this._index++;
return {
type: "attr",
type: "getattr",
id: literal,
name
};
Expand All @@ -582,7 +553,7 @@ class RuntimeParser {
const key = this.getVariantKey();
this._index++;
return {
type: "var",
type: "getvar",
id: literal,
key
};
Expand Down Expand Up @@ -865,7 +836,7 @@ class RuntimeParser {
if (cc0 === 36) { // $
this._index++;
return {
type: "ext",
type: "var",
name: this.getIdentifier()
};
}
Expand Down Expand Up @@ -905,12 +876,11 @@ class RuntimeParser {
// to parse them properly and skip their content.
let eol = this._source.indexOf("\n", this._index);

while (eol !== -1 &&
((this._source[eol + 1] === "/" && this._source[eol + 2] === "/") ||
(this._source[eol + 1] === "#" &&
[" ", "#"].includes(this._source[eol + 2])))) {
this._index = eol + 3;
while (eol !== -1
&& this._source[eol + 1] === "#"
&& [" ", "#"].includes(this._source[eol + 2])) {

this._index = eol + 3;
eol = this._source.indexOf("\n", this._index);

if (eol === -1) {
Expand Down Expand Up @@ -952,7 +922,7 @@ class RuntimeParser {

if ((cc >= 97 && cc <= 122) || // a-z
(cc >= 65 && cc <= 90) || // A-Z
cc === 47 || cc === 91) { // /[
cc === 45) { // -
this._index = start;
return;
}
Expand Down
20 changes: 10 additions & 10 deletions fluent/src/resolver.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
* The role of the Fluent resolver is to format a translation object to an
* instance of `FluentType` or an array of instances.
*
* Translations can contain references to other messages or external arguments,
* Translations can contain references to other messages or variables,
* conditional logic in form of select expressions, traits which describe their
* grammatical features, and can use Fluent builtins which make use of the
* `Intl` formatters to format numbers, dates, lists and more into the
* context's language. See the documentation of the Fluent syntax for more
* context's language. See the documentation of the Fluent syntax for more
* information.
*
* In case of errors the resolver will try to salvage as much of the
Expand Down Expand Up @@ -273,8 +273,8 @@ function Type(env, expr) {
return new FluentSymbol(expr.name);
case "num":
return new FluentNumber(expr.val);
case "ext":
return ExternalArgument(env, expr);
case "var":
return VariableReference(env, expr);
case "fun":
return FunctionReference(env, expr);
case "call":
Expand All @@ -283,11 +283,11 @@ function Type(env, expr) {
const message = MessageReference(env, expr);
return Type(env, message);
}
case "attr": {
case "getattr": {
const attr = AttributeExpression(env, expr);
return Type(env, attr);
}
case "var": {
case "getvar": {
const variant = VariantExpression(env, expr);
return Type(env, variant);
}
Expand All @@ -311,7 +311,7 @@ function Type(env, expr) {
}

/**
* Resolve a reference to an external argument.
* Resolve a reference to a variable.
*
* @param {Object} env
* Resolver environment object.
Expand All @@ -322,11 +322,11 @@ function Type(env, expr) {
* @returns {FluentType}
* @private
*/
function ExternalArgument(env, {name}) {
function VariableReference(env, {name}) {
const { args, errors } = env;

if (!args || !args.hasOwnProperty(name)) {
errors.push(new ReferenceError(`Unknown external: ${name}`));
errors.push(new ReferenceError(`Unknown variable: ${name}`));
return new FluentNone(name);
}

Expand All @@ -349,7 +349,7 @@ function ExternalArgument(env, {name}) {
}
default:
errors.push(
new TypeError(`Unsupported external type: ${name}, ${typeof arg}`)
new TypeError(`Unsupported variable type: ${name}, ${typeof arg}`)
);
return new FluentNone(name);
}
Expand Down
18 changes: 9 additions & 9 deletions fluent/test/arguments_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { MessageContext } from '../src/context';
import { FluentType } from '../src/types';
import { ftl } from '../src/util';

suite('External arguments', function() {
suite('Variables', function() {
let ctx, errs;

setup(function() {
Expand All @@ -19,7 +19,7 @@ suite('External arguments', function() {
ctx.addMessages(ftl`
foo = Foo { $num }
bar = { foo }
baz
baz =
.attr = Baz Attribute { $num }
qux = { "a" ->
*[a] Baz Variant A { $num }
Expand Down Expand Up @@ -102,49 +102,49 @@ suite('External arguments', function() {
const msg = ctx.getMessage('foo');
const val = ctx.format(msg, {}, errs);
assert.equal(val, 'arg');
assert(errs[0] instanceof ReferenceError); // unknown external
assert(errs[0] instanceof ReferenceError); // unknown variable
});

test('cannot be arrays', function() {
const msg = ctx.getMessage('foo');
const val = ctx.format(msg, { arg: [1, 2, 3] }, errs);
assert.equal(val, 'arg');
assert(errs[0] instanceof TypeError); // unsupported external type
assert(errs[0] instanceof TypeError); // unsupported variable type
});

test('cannot be a dict-like object', function() {
const msg = ctx.getMessage('foo');
const val = ctx.format(msg, { arg: { prop: 1 } }, errs);
assert.equal(val, 'arg');
assert(errs[0] instanceof TypeError); // unsupported external type
assert(errs[0] instanceof TypeError); // unsupported variable type
});

test('cannot be a boolean', function() {
const msg = ctx.getMessage('foo');
const val = ctx.format(msg, { arg: true }, errs);
assert.equal(val, 'arg');
assert(errs[0] instanceof TypeError); // unsupported external type
assert(errs[0] instanceof TypeError); // unsupported variable type
});

test('cannot be undefined', function() {
const msg = ctx.getMessage('foo');
const val = ctx.format(msg, { arg: undefined }, errs);
assert.equal(val, 'arg');
assert(errs[0] instanceof TypeError); // unsupported external type
assert(errs[0] instanceof TypeError); // unsupported variable type
});

test('cannot be null', function() {
const msg = ctx.getMessage('foo');
const val = ctx.format(msg, { arg: null }, errs);
assert.equal(val, 'arg');
assert(errs[0] instanceof TypeError); // unsupported external type
assert(errs[0] instanceof TypeError); // unsupported variable type
});

test('cannot be a function', function() {
const msg = ctx.getMessage('foo');
const val = ctx.format(msg, { arg: () => null }, errs);
assert.equal(val, 'arg');
assert(errs[0] instanceof TypeError); // unsupported external type
assert(errs[0] instanceof TypeError); // unsupported variable type
});
});

Expand Down
4 changes: 2 additions & 2 deletions fluent/test/fixtures_structure/term.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"val": [
"Zaktualizuj ",
{
"type": "var",
"type": "getvar",
"id": {
"type": "ref",
"name": "-brand-name"
Expand All @@ -49,7 +49,7 @@
{
"type": "sel",
"exp": {
"type": "attr",
"type": "getattr",
"id": {
"type": "ref",
"name": "-brand-name"
Expand Down
2 changes: 1 addition & 1 deletion fluent/test/functions_runtime_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ suite('Runtime-specific functions', function() {
assert.equal(errs.length, 0);
});

// XXX When passed as external args, convert JS types to FTL types
// XXX When they are passed as variables, convert JS types to FTL types
// https://bugzil.la/1307116
it.skip('works for numbers', function() {
const msg = ctx.getMessage('bar');
Expand Down
10 changes: 5 additions & 5 deletions fluent/test/functions_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ suite('Functions', function() {
pass-number = { IDENTITY(1) }
pass-message = { IDENTITY(foo) }
pass-attr = { IDENTITY(foo.attr) }
pass-external = { IDENTITY($ext) }
pass-variable = { IDENTITY($var) }
pass-function-call = { IDENTITY(IDENTITY(1)) }
`);
});
Expand Down Expand Up @@ -90,10 +90,10 @@ suite('Functions', function() {
assert.equal(errs.length, 0);
});

test('accepts externals', function() {
const msg = ctx.getMessage('pass-external');
const val = ctx.format(msg, { ext: "Ext" }, errs);
assert.equal(val, 'Ext');
test('accepts variables', function() {
const msg = ctx.getMessage('pass-variable');
const val = ctx.format(msg, { var: "Variable" }, errs);
assert.equal(val, 'Variable');
assert.equal(errs.length, 0);
});

Expand Down
Loading