Skip to content

Commit 271b7ec

Browse files
authored
Merge pull request #310 from stasm/zeroeight-part3
Implement Syntax 0.8, part3
2 parents b449a66 + fb5cbef commit 271b7ec

20 files changed

+387
-154
lines changed

fluent-syntax/src/ast.js

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -96,9 +96,10 @@ export class Placeable extends PatternElement {
9696
export class Expression extends SyntaxNode {}
9797

9898
export class StringLiteral extends Expression {
99-
constructor(value) {
99+
constructor(raw, value) {
100100
super();
101101
this.type = "StringLiteral";
102+
this.raw = raw;
102103
this.value = value;
103104
}
104105
}
@@ -135,6 +136,14 @@ export class VariableReference extends Expression {
135136
}
136137
}
137138

139+
export class FunctionReference extends Expression {
140+
constructor(id) {
141+
super();
142+
this.type = "FunctionReference";
143+
this.id = id;
144+
}
145+
}
146+
138147
export class SelectExpression extends Expression {
139148
constructor(selector, variants) {
140149
super();
@@ -236,13 +245,6 @@ export class ResourceComment extends BaseComment {
236245
}
237246
}
238247

239-
export class Function extends Identifier {
240-
constructor(name) {
241-
super(name);
242-
this.type = "Function";
243-
}
244-
}
245-
246248
export class Junk extends SyntaxNode {
247249
constructor(content) {
248250
super();

fluent-syntax/src/parser.js

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -601,7 +601,7 @@ export default class FluentParser {
601601

602602
if (next === "\\" || next === "\"") {
603603
ps.next();
604-
return `\\${next}`;
604+
return [`\\${next}`, next];
605605
}
606606

607607
if (next === "u") {
@@ -618,7 +618,15 @@ export default class FluentParser {
618618
sequence += ch;
619619
}
620620

621-
return `\\u${sequence}`;
621+
const codepoint = parseInt(sequence, 16);
622+
const unescaped = codepoint <= 0xD7FF || 0xE000 <= codepoint
623+
// It's a Unicode scalar value.
624+
? String.fromCodePoint(codepoint)
625+
// Escape sequences reresenting surrogate code points are well-formed
626+
// but invalid in Fluent. Replace them with U+FFFD REPLACEMENT
627+
// CHARACTER.
628+
: "�";
629+
return [`\\u${sequence}`, unescaped];
622630
}
623631

624632
throw new ParseError("E0025", next);
@@ -718,7 +726,7 @@ export default class FluentParser {
718726

719727
ps.expectChar(")");
720728

721-
const func = new AST.Function(literal.id.name);
729+
const func = new AST.FunctionReference(literal.id);
722730
if (this.withSpans) {
723731
func.addSpan(literal.span.start, literal.span.end);
724732
}
@@ -805,16 +813,20 @@ export default class FluentParser {
805813
}
806814

807815
getString(ps) {
808-
let val = "";
816+
let raw = "";
817+
let value = "";
809818

810819
ps.expectChar("\"");
811820

812821
let ch;
813822
while ((ch = ps.takeChar(x => x !== '"' && x !== EOL))) {
814823
if (ch === "\\") {
815-
val += this.getEscapeSequence(ps);
824+
const [sequence, unescaped] = this.getEscapeSequence(ps);
825+
raw += sequence;
826+
value += unescaped;
816827
} else {
817-
val += ch;
828+
raw += ch;
829+
value += ch;
818830
}
819831
}
820832

@@ -824,8 +836,7 @@ export default class FluentParser {
824836

825837
ps.expectChar("\"");
826838

827-
return new AST.StringLiteral(val);
828-
839+
return new AST.StringLiteral(raw, value);
829840
}
830841

831842
getLiteral(ps) {

fluent-syntax/src/serializer.js

Lines changed: 16 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ function serializeMessage(message) {
9696
parts.push(serializeComment(message.comment));
9797
}
9898

99-
parts.push(`${serializeIdentifier(message.id)} =`);
99+
parts.push(`${message.id.name} =`);
100100

101101
if (message.value) {
102102
parts.push(serializeValue(message.value));
@@ -118,7 +118,7 @@ function serializeTerm(term) {
118118
parts.push(serializeComment(term.comment));
119119
}
120120

121-
parts.push(`-${serializeIdentifier(term.id)} =`);
121+
parts.push(`-${term.id.name} =`);
122122
parts.push(serializeValue(term.value));
123123

124124
for (const attribute of term.attributes) {
@@ -131,9 +131,8 @@ function serializeTerm(term) {
131131

132132

133133
function serializeAttribute(attribute) {
134-
const id = serializeIdentifier(attribute.id);
135134
const value = indent(serializeValue(attribute.value));
136-
return `\n .${id} =${value}`;
135+
return `\n .${attribute.id.name} =${value}`;
137136
}
138137

139138

@@ -184,7 +183,7 @@ function serializeVariant(variant) {
184183
function serializeElement(element) {
185184
switch (element.type) {
186185
case "TextElement":
187-
return serializeTextElement(element);
186+
return element.value;
188187
case "Placeable":
189188
return serializePlaceable(element);
190189
default:
@@ -193,11 +192,6 @@ function serializeElement(element) {
193192
}
194193

195194

196-
function serializeTextElement(text) {
197-
return text.value;
198-
}
199-
200-
201195
function serializePlaceable(placeable) {
202196
const expr = placeable.expression;
203197

@@ -217,15 +211,16 @@ function serializePlaceable(placeable) {
217211
function serializeExpression(expr) {
218212
switch (expr.type) {
219213
case "StringLiteral":
220-
return serializeStringLiteral(expr);
214+
return `"${expr.raw}"`;
221215
case "NumberLiteral":
222-
return serializeNumberLiteral(expr);
216+
return expr.value;
223217
case "MessageReference":
224-
return serializeMessageReference(expr);
218+
case "FunctionReference":
219+
return expr.id.name;
225220
case "TermReference":
226-
return serializeTermReference(expr);
221+
return `-${expr.id.name}`;
227222
case "VariableReference":
228-
return serializeVariableReference(expr);
223+
return `$${expr.id.name}`;
229224
case "AttributeExpression":
230225
return serializeAttributeExpression(expr);
231226
case "VariantExpression":
@@ -242,31 +237,6 @@ function serializeExpression(expr) {
242237
}
243238

244239

245-
function serializeStringLiteral(expr) {
246-
return `"${expr.value}"`;
247-
}
248-
249-
250-
function serializeNumberLiteral(expr) {
251-
return expr.value;
252-
}
253-
254-
255-
function serializeMessageReference(expr) {
256-
return serializeIdentifier(expr.id);
257-
}
258-
259-
260-
function serializeTermReference(expr) {
261-
return `-${serializeIdentifier(expr.id)}`;
262-
}
263-
264-
265-
function serializeVariableReference(expr) {
266-
return `$${serializeIdentifier(expr.id)}`;
267-
}
268-
269-
270240
function serializeSelectExpression(expr) {
271241
const parts = [];
272242
const selector = `${serializeExpression(expr.selector)} ->`;
@@ -283,8 +253,7 @@ function serializeSelectExpression(expr) {
283253

284254
function serializeAttributeExpression(expr) {
285255
const ref = serializeExpression(expr.ref);
286-
const name = serializeIdentifier(expr.name);
287-
return `${ref}.${name}`;
256+
return `${ref}.${expr.name.name}`;
288257
}
289258

290259

@@ -296,7 +265,7 @@ function serializeVariantExpression(expr) {
296265

297266

298267
function serializeCallExpression(expr) {
299-
const fun = serializeFunction(expr.callee);
268+
const fun = serializeExpression(expr.callee);
300269
const positional = expr.positional.map(serializeExpression).join(", ");
301270
const named = expr.named.map(serializeNamedArgument).join(", ");
302271
if (expr.positional.length > 0 && expr.named.length > 0) {
@@ -307,40 +276,16 @@ function serializeCallExpression(expr) {
307276

308277

309278
function serializeNamedArgument(arg) {
310-
const name = serializeIdentifier(arg.name);
311-
const value = serializeArgumentValue(arg.value);
312-
return `${name}: ${value}`;
279+
const value = serializeExpression(arg.value);
280+
return `${arg.name.name}: ${value}`;
313281
}
314282

315283

316-
function serializeArgumentValue(argval) {
317-
switch (argval.type) {
318-
case "StringLiteral":
319-
return serializeStringLiteral(argval);
320-
case "NumberLiteral":
321-
return serializeNumberLiteral(argval);
322-
default:
323-
throw new Error(`Unknown argument type: ${argval.type}`);
324-
}
325-
}
326-
327-
328-
function serializeIdentifier(identifier) {
329-
return identifier.name;
330-
}
331-
332284
function serializeVariantKey(key) {
333285
switch (key.type) {
334286
case "Identifier":
335-
return serializeIdentifier(key);
336-
case "NumberLiteral":
337-
return serializeNumberLiteral(key);
287+
return key.name;
338288
default:
339-
throw new Error(`Unknown variant key type: ${key.type}`);
289+
return serializeExpression(key);
340290
}
341291
}
342-
343-
344-
function serializeFunction(fun) {
345-
return fun.name;
346-
}

fluent-syntax/test/fixtures_reference/astral.json

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,8 @@
6868
"type": "Placeable",
6969
"expression": {
7070
"type": "StringLiteral",
71-
"value": "\\uD83D\\uDE02"
71+
"raw": "\\uD83D\\uDE02",
72+
"value": "��"
7273
}
7374
}
7475
]
@@ -89,14 +90,16 @@
8990
"type": "Placeable",
9091
"expression": {
9192
"type": "StringLiteral",
92-
"value": "\\uD83D"
93+
"raw": "\\uD83D",
94+
"value": ""
9395
}
9496
},
9597
{
9698
"type": "Placeable",
9799
"expression": {
98100
"type": "StringLiteral",
99-
"value": "\\uDE02"
101+
"raw": "\\uDE02",
102+
"value": ""
100103
}
101104
}
102105
]
@@ -135,6 +138,7 @@
135138
"type": "Placeable",
136139
"expression": {
137140
"type": "StringLiteral",
141+
"raw": "A face 😂 with tears of joy.",
138142
"value": "A face 😂 with tears of joy."
139143
}
140144
}

fluent-syntax/test/fixtures_reference/call_expressions.ftl

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,19 @@
1+
## Callees
2+
3+
function-callee = {FUNCTION()}
4+
5+
# ERROR Equivalent to a MessageReference callee.
6+
mixed-case-callee = {Function()}
7+
8+
# ERROR MessageReference is not a valid callee.
9+
message-callee = {message()}
10+
# ERROR TermReference is not a valid callee.
11+
term-callee = {-term()}
12+
# ERROR VariableReference is not a valid callee.
13+
variable-callee = {$variable()}
14+
15+
## Arguments
16+
117
positional-args = {FUN(1, "a", msg)}
218
named-args = {FUN(x: 1, y: "Y")}
319
dense-named-args = {FUN(x:1, y:"Y")}

0 commit comments

Comments
 (0)