Skip to content

Commit dacc294

Browse files
authored
Merge pull request #236 from stasm/runtime
Implement Syntax 0.6 in the runtime
2 parents 6707825 + 1c4d39e commit dacc294

15 files changed

+207
-619
lines changed

fluent/src/context.js

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@ import parse from "./parser";
66
* responsible for parsing translation resources in the Fluent syntax and can
77
* format translation units (entities) to strings.
88
*
9-
* Always use `MessageContext.format` to retrieve translation units from
10-
* a context. Translations can contain references to other entities or
11-
* external arguments, conditional logic in form of select expressions, traits
12-
* which describe their grammatical features, and can use Fluent builtins which
13-
* make use of the `Intl` formatters to format numbers, dates, lists and more
14-
* into the context's language. See the documentation of the Fluent syntax for
15-
* more information.
9+
* Always use `MessageContext.format` to retrieve translation units from a
10+
* context. Translations can contain references to other entities or variables,
11+
* conditional logic in form of select expressions, traits which describe their
12+
* grammatical features, and can use Fluent builtins which make use of the
13+
* `Intl` formatters to format numbers, dates, lists and more into the
14+
* context's language. See the documentation of the Fluent syntax for more
15+
* information.
1616
*/
1717
export class MessageContext {
1818

@@ -134,8 +134,8 @@ export class MessageContext {
134134
* Format a message to a string or null.
135135
*
136136
* Format a raw `message` from the context into a string (or a null if it has
137-
* a null value). `args` will be used to resolve references to external
138-
* arguments inside of the translation.
137+
* a null value). `args` will be used to resolve references to variables
138+
* passed as arguments to the translation.
139139
*
140140
* In case of errors `format` will try to salvage as much of the translation
141141
* as possible and will still return a string. For performance reasons, the
@@ -153,7 +153,7 @@ export class MessageContext {
153153
*
154154
* // Returns 'Hello, name!' and `errors` is now:
155155
*
156-
* [<ReferenceError: Unknown external: name>]
156+
* [<ReferenceError: Unknown variable: name>]
157157
*
158158
* @param {Object | string} message
159159
* @param {Object | undefined} args

fluent/src/parser.js

Lines changed: 16 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -74,46 +74,15 @@ class RuntimeParser {
7474
const ch = this._source[this._index];
7575

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

84-
if (ch === "[") {
85-
this.skipSection();
86-
return;
87-
}
88-
8983
this.getMessage();
9084
}
9185

92-
/**
93-
* Skip the section entry from the current index.
94-
*
95-
* @private
96-
*/
97-
skipSection() {
98-
this._index += 1;
99-
if (this._source[this._index] !== "[") {
100-
throw this.error('Expected "[[" to open a section');
101-
}
102-
103-
this._index += 1;
104-
105-
this.skipInlineWS();
106-
this.getVariantName();
107-
this.skipInlineWS();
108-
109-
if (this._source[this._index] !== "]" ||
110-
this._source[this._index + 1] !== "]") {
111-
throw this.error('Expected "]]" to close a section');
112-
}
113-
114-
this._index += 2;
115-
}
116-
11786
/**
11887
* Parse the source string from the current index as an FTL message
11988
* and add it to the entries property on the Parser.
@@ -127,6 +96,8 @@ class RuntimeParser {
12796

12897
if (this._source[this._index] === "=") {
12998
this._index++;
99+
} else {
100+
throw this.error("Expected \"=\" after the identifier");
130101
}
131102

132103
this.skipInlineWS();
@@ -216,7 +187,7 @@ class RuntimeParser {
216187
* Get identifier using the provided regex.
217188
*
218189
* By default this will get identifiers of public messages, attributes and
219-
* external arguments (without the $).
190+
* variables (without the $).
220191
*
221192
* @returns {String}
222193
* @private
@@ -499,7 +470,7 @@ class RuntimeParser {
499470
const ch = this._source[this._index];
500471

501472
if (ch === "}") {
502-
if (selector.type === "attr" && selector.id.name.startsWith("-")) {
473+
if (selector.type === "getattr" && selector.id.name.startsWith("-")) {
503474
throw this.error(
504475
"Attributes of private messages cannot be interpolated."
505476
);
@@ -516,11 +487,11 @@ class RuntimeParser {
516487
throw this.error("Message references cannot be used as selectors.");
517488
}
518489

519-
if (selector.type === "var") {
490+
if (selector.type === "getvar") {
520491
throw this.error("Variants cannot be used as selectors.");
521492
}
522493

523-
if (selector.type === "attr" && !selector.id.name.startsWith("-")) {
494+
if (selector.type === "getattr" && !selector.id.name.startsWith("-")) {
524495
throw this.error(
525496
"Attributes of public messages cannot be used as selectors."
526497
);
@@ -570,7 +541,7 @@ class RuntimeParser {
570541
const name = this.getIdentifier();
571542
this._index++;
572543
return {
573-
type: "attr",
544+
type: "getattr",
574545
id: literal,
575546
name
576547
};
@@ -582,7 +553,7 @@ class RuntimeParser {
582553
const key = this.getVariantKey();
583554
this._index++;
584555
return {
585-
type: "var",
556+
type: "getvar",
586557
id: literal,
587558
key
588559
};
@@ -865,7 +836,7 @@ class RuntimeParser {
865836
if (cc0 === 36) { // $
866837
this._index++;
867838
return {
868-
type: "ext",
839+
type: "var",
869840
name: this.getIdentifier()
870841
};
871842
}
@@ -905,12 +876,11 @@ class RuntimeParser {
905876
// to parse them properly and skip their content.
906877
let eol = this._source.indexOf("\n", this._index);
907878

908-
while (eol !== -1 &&
909-
((this._source[eol + 1] === "/" && this._source[eol + 2] === "/") ||
910-
(this._source[eol + 1] === "#" &&
911-
[" ", "#"].includes(this._source[eol + 2])))) {
912-
this._index = eol + 3;
879+
while (eol !== -1
880+
&& this._source[eol + 1] === "#"
881+
&& [" ", "#"].includes(this._source[eol + 2])) {
913882

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

916886
if (eol === -1) {
@@ -952,7 +922,7 @@ class RuntimeParser {
952922

953923
if ((cc >= 97 && cc <= 122) || // a-z
954924
(cc >= 65 && cc <= 90) || // A-Z
955-
cc === 47 || cc === 91) { // /[
925+
cc === 45) { // -
956926
this._index = start;
957927
return;
958928
}

fluent/src/resolver.js

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@
44
* The role of the Fluent resolver is to format a translation object to an
55
* instance of `FluentType` or an array of instances.
66
*
7-
* Translations can contain references to other messages or external arguments,
7+
* Translations can contain references to other messages or variables,
88
* conditional logic in form of select expressions, traits which describe their
99
* grammatical features, and can use Fluent builtins which make use of the
1010
* `Intl` formatters to format numbers, dates, lists and more into the
11-
* context's language. See the documentation of the Fluent syntax for more
11+
* context's language. See the documentation of the Fluent syntax for more
1212
* information.
1313
*
1414
* In case of errors the resolver will try to salvage as much of the
@@ -273,8 +273,8 @@ function Type(env, expr) {
273273
return new FluentSymbol(expr.name);
274274
case "num":
275275
return new FluentNumber(expr.val);
276-
case "ext":
277-
return ExternalArgument(env, expr);
276+
case "var":
277+
return VariableReference(env, expr);
278278
case "fun":
279279
return FunctionReference(env, expr);
280280
case "call":
@@ -283,11 +283,11 @@ function Type(env, expr) {
283283
const message = MessageReference(env, expr);
284284
return Type(env, message);
285285
}
286-
case "attr": {
286+
case "getattr": {
287287
const attr = AttributeExpression(env, expr);
288288
return Type(env, attr);
289289
}
290-
case "var": {
290+
case "getvar": {
291291
const variant = VariantExpression(env, expr);
292292
return Type(env, variant);
293293
}
@@ -311,7 +311,7 @@ function Type(env, expr) {
311311
}
312312

313313
/**
314-
* Resolve a reference to an external argument.
314+
* Resolve a reference to a variable.
315315
*
316316
* @param {Object} env
317317
* Resolver environment object.
@@ -322,11 +322,11 @@ function Type(env, expr) {
322322
* @returns {FluentType}
323323
* @private
324324
*/
325-
function ExternalArgument(env, {name}) {
325+
function VariableReference(env, {name}) {
326326
const { args, errors } = env;
327327

328328
if (!args || !args.hasOwnProperty(name)) {
329-
errors.push(new ReferenceError(`Unknown external: ${name}`));
329+
errors.push(new ReferenceError(`Unknown variable: ${name}`));
330330
return new FluentNone(name);
331331
}
332332

@@ -349,7 +349,7 @@ function ExternalArgument(env, {name}) {
349349
}
350350
default:
351351
errors.push(
352-
new TypeError(`Unsupported external type: ${name}, ${typeof arg}`)
352+
new TypeError(`Unsupported variable type: ${name}, ${typeof arg}`)
353353
);
354354
return new FluentNone(name);
355355
}

fluent/test/arguments_test.js

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { MessageContext } from '../src/context';
66
import { FluentType } from '../src/types';
77
import { ftl } from '../src/util';
88

9-
suite('External arguments', function() {
9+
suite('Variables', function() {
1010
let ctx, errs;
1111

1212
setup(function() {
@@ -19,7 +19,7 @@ suite('External arguments', function() {
1919
ctx.addMessages(ftl`
2020
foo = Foo { $num }
2121
bar = { foo }
22-
baz
22+
baz =
2323
.attr = Baz Attribute { $num }
2424
qux = { "a" ->
2525
*[a] Baz Variant A { $num }
@@ -102,49 +102,49 @@ suite('External arguments', function() {
102102
const msg = ctx.getMessage('foo');
103103
const val = ctx.format(msg, {}, errs);
104104
assert.equal(val, 'arg');
105-
assert(errs[0] instanceof ReferenceError); // unknown external
105+
assert(errs[0] instanceof ReferenceError); // unknown variable
106106
});
107107

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

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

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

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

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

143143
test('cannot be a function', function() {
144144
const msg = ctx.getMessage('foo');
145145
const val = ctx.format(msg, { arg: () => null }, errs);
146146
assert.equal(val, 'arg');
147-
assert(errs[0] instanceof TypeError); // unsupported external type
147+
assert(errs[0] instanceof TypeError); // unsupported variable type
148148
});
149149
});
150150

fluent/test/fixtures_structure/term.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
"val": [
3232
"Zaktualizuj ",
3333
{
34-
"type": "var",
34+
"type": "getvar",
3535
"id": {
3636
"type": "ref",
3737
"name": "-brand-name"
@@ -49,7 +49,7 @@
4949
{
5050
"type": "sel",
5151
"exp": {
52-
"type": "attr",
52+
"type": "getattr",
5353
"id": {
5454
"type": "ref",
5555
"name": "-brand-name"

fluent/test/functions_runtime_test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ suite('Runtime-specific functions', function() {
3434
assert.equal(errs.length, 0);
3535
});
3636

37-
// XXX When passed as external args, convert JS types to FTL types
37+
// XXX When they are passed as variables, convert JS types to FTL types
3838
// https://bugzil.la/1307116
3939
it.skip('works for numbers', function() {
4040
const msg = ctx.getMessage('bar');

fluent/test/functions_test.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ suite('Functions', function() {
4545
pass-number = { IDENTITY(1) }
4646
pass-message = { IDENTITY(foo) }
4747
pass-attr = { IDENTITY(foo.attr) }
48-
pass-external = { IDENTITY($ext) }
48+
pass-variable = { IDENTITY($var) }
4949
pass-function-call = { IDENTITY(IDENTITY(1)) }
5050
`);
5151
});
@@ -90,10 +90,10 @@ suite('Functions', function() {
9090
assert.equal(errs.length, 0);
9191
});
9292

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

0 commit comments

Comments
 (0)