Skip to content

Commit 5b3382e

Browse files
authored
Add private messages (#38)
1 parent 66a6719 commit 5b3382e

File tree

8 files changed

+229
-90
lines changed

8 files changed

+229
-90
lines changed

src/syntax/errors/list.rs

+22-4
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ pub enum ErrorKind {
3434
ForbiddenWhitespace,
3535
ForbiddenCallee,
3636
ForbiddenKey,
37+
ForbiddenPrivateAttributeExpression,
38+
ForbiddenPublicAttributeExpression,
39+
ForbiddenVariantExpression,
3740
}
3841

3942
pub fn get_error_desc(err: &ErrorKind) -> (&'static str, String, &'static str) {
@@ -69,17 +72,17 @@ pub fn get_error_desc(err: &ErrorKind) -> (&'static str, String, &'static str) {
6972
}
7073
ErrorKind::ForbiddenWhitespace => (
7174
"E0007",
72-
"keyword cannot end with a whitespace".to_owned(),
75+
"Keyword cannot end with a whitespace".to_owned(),
7376
"",
7477
),
7578
ErrorKind::ForbiddenCallee => (
7679
"E0008",
77-
"a callee has to be a simple identifier".to_owned(),
80+
"The callee has to be a simple, upper-case, identifier".to_owned(),
7881
"",
7982
),
8083
ErrorKind::ForbiddenKey => (
8184
"E0009",
82-
"a key has to be a simple identifier".to_owned(),
85+
"The key has to be a simple identifier".to_owned(),
8386
"",
8487
),
8588
ErrorKind::MissingDefaultVariant => (
@@ -88,9 +91,24 @@ pub fn get_error_desc(err: &ErrorKind) -> (&'static str, String, &'static str) {
8891
"",
8992
),
9093
ErrorKind::MissingVariants => (
91-
"E0010",
94+
"E0011",
9295
"Expected at least one variant after \"->\".".to_owned(),
9396
"",
9497
),
98+
ErrorKind::ForbiddenPrivateAttributeExpression => (
99+
"E0012",
100+
"Attributes of private messages cannot be used as placeables.".to_owned(),
101+
"",
102+
),
103+
ErrorKind::ForbiddenPublicAttributeExpression => (
104+
"E0013",
105+
"Attributes of public messages cannot be used as selectors.".to_owned(),
106+
"",
107+
),
108+
ErrorKind::ForbiddenVariantExpression => (
109+
"E0014",
110+
"Variants cannot be used as selectors.".to_owned(),
111+
"",
112+
),
95113
}
96114
}

src/syntax/ftlstream.rs

+31-17
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,14 @@ pub trait FTLParserStream<I> {
1414
where
1515
F: Fn(char) -> bool;
1616

17-
fn is_id_start(&mut self) -> bool;
17+
fn is_char_id_start(&mut self, ch: Option<char>) -> bool;
18+
fn is_message_id_start(&mut self) -> bool;
1819
fn is_peek_next_line_indented(&mut self) -> bool;
1920
fn is_peek_next_line_variant_start(&mut self) -> bool;
2021
fn is_peek_next_line_attribute_start(&mut self) -> bool;
2122
fn is_peek_next_line_pattern(&mut self) -> bool;
2223
fn skip_to_next_entry_start(&mut self);
23-
fn take_id_start(&mut self) -> Result<char>;
24+
fn take_id_start(&mut self, allow_private: bool) -> Result<char>;
2425
fn take_id_char(&mut self) -> Option<char>;
2526
fn take_symb_char(&mut self) -> Option<char>;
2627
fn take_digit(&mut self) -> Option<char>;
@@ -91,14 +92,21 @@ where
9192
None
9293
}
9394

94-
fn is_id_start(&mut self) -> bool {
95-
if let Some(ch) = self.ch {
96-
return match ch {
97-
'a'...'z' | 'A'...'Z' | '_' => true,
98-
_ => false,
99-
};
95+
fn is_char_id_start(&mut self, ch: Option<char>) -> bool {
96+
match ch {
97+
Some('a'...'z') | Some('A'...'Z') => true,
98+
_ => false,
10099
}
101-
false
100+
}
101+
102+
fn is_message_id_start(&mut self) -> bool {
103+
if let Some('-') = self.ch {
104+
self.peek();
105+
}
106+
let ch = self.current_peek();
107+
let is_id = self.is_char_id_start(ch);
108+
self.reset_peek();
109+
is_id
102110
}
103111

104112
fn is_peek_next_line_indented(&mut self) -> bool {
@@ -196,7 +204,7 @@ where
196204
fn skip_to_next_entry_start(&mut self) {
197205
while let Some(_) = self.next() {
198206
if self.current_is('\n') && !self.peek_char_is('\n')
199-
&& (self.next() == None || self.is_id_start() || self.current_is('/')
207+
&& (self.next() == None || self.is_message_id_start() || self.current_is('/')
200208
|| self.current_is('[') || self.peek_char_is('/')
201209
|| self.peek_char_is('['))
202210
{
@@ -205,17 +213,23 @@ where
205213
}
206214
}
207215

208-
fn take_id_start(&mut self) -> Result<char> {
216+
fn take_id_start(&mut self, allow_private: bool) -> Result<char> {
209217
let closure = |x| match x {
210-
'a'...'z' | 'A'...'Z' | '_' => true,
218+
'a'...'z' | 'A'...'Z' => true,
219+
'-' => allow_private,
211220
_ => false,
212221
};
213222

214-
match self.take_char(closure) {
215-
Some(ch) => Ok(ch),
216-
None => error!(ErrorKind::ExpectedCharRange {
217-
range: String::from("'a'...'z' | 'A'...'Z' | '_'"),
218-
}),
223+
if let Some(ch) = self.take_char(closure) {
224+
Ok(ch)
225+
} else if allow_private {
226+
error!(ErrorKind::ExpectedCharRange {
227+
range: String::from("'a'...'z' | 'A'...'Z'"),
228+
})
229+
} else {
230+
error!(ErrorKind::ExpectedCharRange {
231+
range: String::from("'a'...'z' | 'A'...'Z' | '-'"),
232+
})
219233
}
220234
}
221235

src/syntax/parser.rs

+51-10
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ where
8080
return Ok(get_section(ps, comment)?);
8181
}
8282

83-
if ps.is_id_start() {
83+
if ps.is_message_id_start() {
8484
return Ok(get_message(ps, comment)?);
8585
}
8686

@@ -150,7 +150,7 @@ fn get_message<I>(ps: &mut ParserStream<I>, comment: Option<ast::Comment>) -> Re
150150
where
151151
I: Iterator<Item = char>,
152152
{
153-
let id = get_identifier(ps)?;
153+
let id = get_private_identifier(ps)?;
154154

155155
ps.skip_line_ws();
156156

@@ -196,7 +196,7 @@ where
196196

197197
ps.expect_char('.')?;
198198

199-
let key = get_identifier(ps)?;
199+
let key = get_public_identifier(ps)?;
200200

201201
ps.skip_line_ws();
202202

@@ -222,13 +222,27 @@ where
222222
Ok(attributes)
223223
}
224224

225-
fn get_identifier<I>(ps: &mut ParserStream<I>) -> Result<ast::Identifier>
225+
fn get_private_identifier<I>(ps: &mut ParserStream<I>) -> Result<ast::Identifier>
226+
where
227+
I: Iterator<Item = char>,
228+
{
229+
get_identifier(ps, true)
230+
}
231+
232+
fn get_public_identifier<I>(ps: &mut ParserStream<I>) -> Result<ast::Identifier>
233+
where
234+
I: Iterator<Item = char>,
235+
{
236+
get_identifier(ps, false)
237+
}
238+
239+
fn get_identifier<I>(ps: &mut ParserStream<I>, allow_private: bool) -> Result<ast::Identifier>
226240
where
227241
I: Iterator<Item = char>,
228242
{
229243
let mut name = String::new();
230244

231-
name.push(ps.take_id_start()?);
245+
name.push(ps.take_id_start(allow_private)?);
232246

233247
while let Some(ch) = ps.take_id_char() {
234248
name.push(ch);
@@ -315,7 +329,7 @@ where
315329
{
316330
let mut name = String::new();
317331

318-
name.push(ps.take_id_start()?);
332+
name.push(ps.take_id_start(false)?);
319333

320334
while let Some(ch) = ps.take_symb_char() {
321335
name.push(ch);
@@ -490,6 +504,13 @@ where
490504

491505
if ps.current_is('-') {
492506
if let Some('>') = ps.peek() {
507+
if let ast::Expression::AttributeExpression { ref id, .. } = selector {
508+
if !id.name.starts_with('-') {
509+
return error!(ErrorKind::ForbiddenPublicAttributeExpression);
510+
}
511+
} else if let ast::Expression::VariantExpression { .. } = selector {
512+
return error!(ErrorKind::ForbiddenVariantExpression);
513+
}
493514
ps.next();
494515
ps.next();
495516

@@ -512,6 +533,12 @@ where
512533
} else {
513534
ps.reset_peek();
514535
}
536+
} else {
537+
if let ast::Expression::AttributeExpression { ref id, .. } = selector {
538+
if id.name.starts_with('-') {
539+
return error!(ErrorKind::ForbiddenPrivateAttributeExpression);
540+
}
541+
}
515542
}
516543

517544
Ok(selector)
@@ -527,7 +554,7 @@ where
527554
ast::Expression::MessageReference { id } => match ps.ch {
528555
Some('.') => {
529556
ps.next();
530-
let attr = get_identifier(ps)?;
557+
let attr = get_public_identifier(ps)?;
531558
Ok(ast::Expression::AttributeExpression { id, name: attr })
532559
}
533560
Some('[') => {
@@ -538,6 +565,9 @@ where
538565
Ok(ast::Expression::VariantExpression { id, key: key })
539566
}
540567
Some('(') => {
568+
if id.name.starts_with('-') || id.name.chars().any(|c| c.is_lowercase()) {
569+
return error!(ErrorKind::ForbiddenCallee);
570+
}
541571
ps.next();
542572
let args = get_call_args(ps)?;
543573
ps.expect_char(')')?;
@@ -642,20 +672,31 @@ where
642672
{
643673
if let Some(ch) = ps.current() {
644674
let exp = match ch {
645-
'0'...'9' | '-' => ast::Expression::NumberExpression {
675+
'0'...'9' => ast::Expression::NumberExpression {
646676
value: get_number(ps)?,
647677
},
678+
'-' => if let Some('0'...'9') = ps.peek() {
679+
ps.reset_peek();
680+
ast::Expression::NumberExpression {
681+
value: get_number(ps)?,
682+
}
683+
} else {
684+
ps.reset_peek();
685+
ast::Expression::MessageReference {
686+
id: get_private_identifier(ps)?,
687+
}
688+
},
648689
'"' => ast::Expression::StringExpression {
649690
value: get_string(ps)?,
650691
},
651692
'$' => {
652693
ps.next();
653694
ast::Expression::ExternalArgument {
654-
id: get_identifier(ps)?,
695+
id: get_public_identifier(ps)?,
655696
}
656697
}
657698
_ => ast::Expression::MessageReference {
658-
id: get_identifier(ps)?,
699+
id: get_private_identifier(ps)?,
659700
},
660701
};
661702
Ok(exp)

tests/fixtures/parser/ftl/06-placeables01.ftl

+10-10
Original file line numberDiff line numberDiff line change
@@ -21,24 +21,24 @@ key7 = { LIST($user1, $user2) }
2121
2222
key9 = { LEN(2, 2.5, -3.12, -1.00) }
2323
24-
key11 = { len() }
24+
key11 = { LEN() }
2525
26-
key12 = { len(1) }
26+
key12 = { LEN(1) }
2727
28-
key13 = { len(-1) }
28+
key13 = { LEN(-1) }
2929
30-
key14 = { len($foo) }
30+
key14 = { LEN($foo) }
3131
32-
key15 = { len(foo) }
32+
key15 = { LEN(foo) }
3333
34-
key19 = { len(bar: 1) }
34+
key19 = { LEN(bar: 1) }
3535
36-
key20 = { len(bar: -1) }
36+
key20 = { LEN(bar: -1) }
3737
38-
key21 = { len(bar: "user") }
38+
key21 = { LEN(bar: "user") }
3939
4040
key22 = { brand-name[masculine] }
4141
42-
key23 = { number(style: "percent") }
42+
key23 = { NUMBER(style: "percent") }
4343
44-
key24 = { number($num, style: "percent", foo: "bar") }
44+
key24 = { NUMBER_SPECIAL($num, style: "percent", foo: "bar") }
+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
-brand-short-name = Firefox
2+
.gender = masculine
3+
4+
key = Test { -brand-short-name }
5+
6+
key2 = Test { -brand-short-name.gender ->
7+
[masculine] Foo
8+
*[feminine] Foo 2
9+
}
10+
11+
key3 = Test { -brand[one] }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
2+
key =
3+
{ $foo ->
4+
[one] Foo
5+
*[-other] Foo 2
6+
}
7+
8+
key2 = { $-foo }
9+
10+
key3 = { -brand.gender }
11+
12+
key4 = { -brand() }

0 commit comments

Comments
 (0)