• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

projectfluent / fluent-rs / 3750965814

pending completion
3750965814

push

github

GitHub
Fix FluentValue::try_number accepting numbers (#306)

5 of 5 new or added lines in 3 files covered. (100.0%)

3749 of 4191 relevant lines covered (89.45%)

1278.09 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.21
/fluent-syntax/src/parser/core.rs
1
use super::{
2
    comment,
3
    errors::{ErrorKind, ParserError},
4
    slice::Slice,
5
};
6
use crate::ast;
7

8
pub type Result<T> = std::result::Result<T, ParserError>;
9

10
pub struct Parser<S> {
11
    pub(super) source: S,
12
    pub(super) ptr: usize,
13
    pub(super) length: usize,
14
}
15

16
impl<'s, S> Parser<S>
17
where
18
    S: Slice<'s>,
19
{
20
    pub fn new(source: S) -> Self {
2,215✔
21
        let length = source.as_ref().as_bytes().len();
2,215✔
22
        Self {
2,215✔
23
            source,
2,215✔
24
            ptr: 0,
2,215✔
25
            length,
2,215✔
26
        }
2,215✔
27
    }
2,215✔
28

29
    pub fn parse(
224✔
30
        mut self,
224✔
31
    ) -> std::result::Result<ast::Resource<S>, (ast::Resource<S>, Vec<ParserError>)> {
224✔
32
        let mut errors = vec![];
224✔
33

224✔
34
        let mut body = vec![];
224✔
35

224✔
36
        self.skip_blank_block();
224✔
37
        let mut last_comment = None;
224✔
38
        let mut last_blank_count = 0;
224✔
39

40
        while self.ptr < self.length {
4,311✔
41
            let entry_start = self.ptr;
4,087✔
42
            let mut entry = self.get_entry(entry_start);
4,087✔
43

44
            if let Some(comment) = last_comment.take() {
4,087✔
45
                match entry {
15✔
46
                    Ok(ast::Entry::Message(ref mut msg)) if last_blank_count < 2 => {
237✔
47
                        msg.comment = Some(comment);
213✔
48
                    }
213✔
49
                    Ok(ast::Entry::Term(ref mut term)) if last_blank_count < 2 => {
15✔
50
                        term.comment = Some(comment);
11✔
51
                    }
11✔
52
                    _ => {
398✔
53
                        body.push(ast::Entry::Comment(comment));
398✔
54
                    }
398✔
55
                }
56
            }
3,465✔
57

58
            match entry {
3,683✔
59
                Ok(ast::Entry::Comment(comment)) => {
638✔
60
                    last_comment = Some(comment);
638✔
61
                }
638✔
62
                Ok(entry) => {
3,045✔
63
                    body.push(entry);
3,045✔
64
                }
3,045✔
65
                Err(mut err) => {
404✔
66
                    self.skip_to_next_entry_start();
404✔
67
                    err.slice = Some(entry_start..self.ptr);
404✔
68
                    errors.push(err);
404✔
69
                    let content = self.source.slice(entry_start..self.ptr);
404✔
70
                    body.push(ast::Entry::Junk { content });
404✔
71
                }
404✔
72
            }
73
            last_blank_count = self.skip_blank_block();
4,087✔
74
        }
75

76
        if let Some(last_comment) = last_comment.take() {
224✔
77
            body.push(ast::Entry::Comment(last_comment));
16✔
78
        }
208✔
79
        if errors.is_empty() {
224✔
80
            Ok(ast::Resource { body })
126✔
81
        } else {
82
            Err((ast::Resource { body }, errors))
98✔
83
        }
84
    }
224✔
85

86
    fn get_entry(&mut self, entry_start: usize) -> Result<ast::Entry<S>> {
4,086✔
87
        let entry = match get_current_byte!(self) {
4,086✔
88
            Some(b'#') => {
89
                let (comment, level) = self.get_comment()?;
976✔
90
                match level {
964✔
91
                    comment::Level::Regular => ast::Entry::Comment(comment),
638✔
92
                    comment::Level::Group => ast::Entry::GroupComment(comment),
279✔
93
                    comment::Level::Resource => ast::Entry::ResourceComment(comment),
47✔
94
                    comment::Level::None => unreachable!(),
×
95
                }
96
            }
97
            Some(b'-') => ast::Entry::Term(self.get_term(entry_start)?),
100✔
98
            _ => ast::Entry::Message(self.get_message(entry_start)?),
3,010✔
99
        };
100
        Ok(entry)
3,682✔
101
    }
4,086✔
102

103
    pub fn get_message(&mut self, entry_start: usize) -> Result<ast::Message<S>> {
14,733✔
104
        let id = self.get_identifier()?;
14,733✔
105
        self.skip_blank_inline();
14,629✔
106
        self.expect_byte(b'=')?;
14,629✔
107
        let pattern = self.get_pattern()?;
14,592✔
108

109
        self.skip_blank_block();
14,212✔
110

14,212✔
111
        let attributes = self.get_attributes();
14,212✔
112

14,212✔
113
        if pattern.is_none() && attributes.is_empty() {
14,212✔
114
            let entry_id = id.name.as_ref().to_owned();
180✔
115
            return error!(
180✔
116
                ErrorKind::ExpectedMessageField { entry_id },
180✔
117
                entry_start, self.ptr
180✔
118
            );
180✔
119
        }
14,033✔
120

14,033✔
121
        Ok(ast::Message {
14,033✔
122
            id,
14,033✔
123
            value: pattern,
14,033✔
124
            attributes,
14,033✔
125
            comment: None,
14,033✔
126
        })
14,033✔
127
    }
14,734✔
128

129
    pub fn get_term(&mut self, entry_start: usize) -> Result<ast::Term<S>> {
130
        self.expect_byte(b'-')?;
1,680✔
131
        let id = self.get_identifier()?;
1,680✔
132
        self.skip_blank_inline();
1,680✔
133
        self.expect_byte(b'=')?;
1,680✔
134
        self.skip_blank_inline();
1,676✔
135

136
        let value = self.get_pattern()?;
1,676✔
137

138
        self.skip_blank_block();
1,672✔
139

1,672✔
140
        let attributes = self.get_attributes();
1,672✔
141

142
        if let Some(value) = value {
1,672✔
143
            Ok(ast::Term {
1,656✔
144
                id,
1,656✔
145
                value,
1,656✔
146
                attributes,
1,656✔
147
                comment: None,
1,656✔
148
            })
1,656✔
149
        } else {
150
            error!(
16✔
151
                ErrorKind::ExpectedTermField {
16✔
152
                    entry_id: id.name.as_ref().to_owned()
16✔
153
                },
16✔
154
                entry_start, self.ptr
16✔
155
            )
16✔
156
        }
157
    }
1,680✔
158

159
    fn get_attributes(&mut self) -> Vec<ast::Attribute<S>> {
15,885✔
160
        let mut attributes = vec![];
15,885✔
161

162
        loop {
20,564✔
163
            let line_start = self.ptr;
20,564✔
164
            self.skip_blank_inline();
20,564✔
165
            if !self.take_byte_if(b'.') {
20,564✔
166
                self.ptr = line_start;
15,721✔
167
                break;
15,721✔
168
            }
4,843✔
169

170
            if let Ok(attr) = self.get_attribute() {
4,843✔
171
                attributes.push(attr);
4,679✔
172
            } else {
4,679✔
173
                self.ptr = line_start;
164✔
174
                break;
164✔
175
            }
176
        }
177
        attributes
15,885✔
178
    }
15,885✔
179

180
    fn get_attribute(&mut self) -> Result<ast::Attribute<S>> {
4,843✔
181
        let id = self.get_identifier()?;
4,843✔
182
        self.skip_blank_inline();
4,843✔
183
        self.expect_byte(b'=')?;
4,843✔
184
        let pattern = self.get_pattern()?;
4,823✔
185

186
        match pattern {
4,751✔
187
            Some(pattern) => Ok(ast::Attribute { id, value: pattern }),
4,679✔
188
            None => error!(ErrorKind::MissingValue, self.ptr),
72✔
189
        }
190
    }
4,843✔
191

192
    pub(super) fn get_identifier_unchecked(&mut self) -> ast::Identifier<S> {
39,121✔
193
        let mut ptr = self.ptr;
39,121✔
194

195
        while matches!(get_byte!(self, ptr), Some(b) if b.is_ascii_alphanumeric() || *b == b'-' || *b == b'_')
286,774✔
196
        {
247,653✔
197
            ptr += 1;
247,653✔
198
        }
247,653✔
199

200
        let name = self.source.slice(self.ptr - 1..ptr);
39,122✔
201
        self.ptr = ptr;
39,122✔
202

39,122✔
203
        ast::Identifier { name }
39,122✔
204
    }
39,122✔
205

206
    pub(super) fn get_identifier(&mut self) -> Result<ast::Identifier<S>> {
27,825✔
207
        if !self.is_identifier_start() {
27,825✔
208
            return error!(
120✔
209
                ErrorKind::ExpectedCharRange {
120✔
210
                    range: "a-zA-Z".to_string()
120✔
211
                },
120✔
212
                self.ptr
120✔
213
            );
120✔
214
        }
27,705✔
215
        self.ptr += 1;
27,705✔
216
        Ok(self.get_identifier_unchecked())
27,705✔
217
    }
27,825✔
218

219
    pub(super) fn get_attribute_accessor(&mut self) -> Result<Option<ast::Identifier<S>>> {
10,526✔
220
        if self.take_byte_if(b'.') {
10,526✔
221
            let ident = self.get_identifier()?;
1,781✔
222
            Ok(Some(ident))
1,781✔
223
        } else {
224
            Ok(None)
8,745✔
225
        }
226
    }
10,526✔
227

228
    fn get_variant_key(&mut self) -> Result<ast::VariantKey<S>> {
3,132✔
229
        self.skip_blank();
3,132✔
230

231
        let key = if self.is_number_start() {
3,132✔
232
            ast::VariantKey::NumberLiteral {
233
                value: self.get_number_literal()?,
161✔
234
            }
235
        } else {
236
            ast::VariantKey::Identifier {
237
                name: self.get_identifier()?.name,
2,971✔
238
            }
239
        };
240

241
        self.skip_blank();
3,128✔
242

3,128✔
243
        self.expect_byte(b']')?;
3,128✔
244

245
        Ok(key)
3,116✔
246
    }
3,132✔
247

248
    pub(super) fn get_variants(&mut self) -> Result<Vec<ast::Variant<S>>> {
1,634✔
249
        let mut variants = Vec::with_capacity(2);
1,634✔
250
        let mut has_default = false;
1,634✔
251

252
        loop {
4,742✔
253
            let default = self.take_byte_if(b'*');
4,742✔
254
            if default {
4,742✔
255
                if has_default {
1,634✔
256
                    return error!(ErrorKind::MultipleDefaultVariants, self.ptr);
×
257
                } else {
1,634✔
258
                    has_default = true;
1,634✔
259
                }
1,634✔
260
            }
3,108✔
261

262
            if !self.take_byte_if(b'[') {
4,742✔
263
                break;
1,610✔
264
            }
3,132✔
265

266
            let key = self.get_variant_key()?;
3,132✔
267

268
            let value = self.get_pattern()?;
3,116✔
269

270
            if let Some(value) = value {
3,112✔
271
                variants.push(ast::Variant {
3,108✔
272
                    key,
3,108✔
273
                    value,
3,108✔
274
                    default,
3,108✔
275
                });
3,108✔
276
                self.skip_blank();
3,108✔
277
            } else {
3,108✔
278
                return error!(ErrorKind::MissingValue, self.ptr);
4✔
279
            }
280
        }
281

282
        if has_default {
1,610✔
283
            Ok(variants)
1,610✔
284
        } else {
285
            error!(ErrorKind::MissingDefaultVariant, self.ptr)
×
286
        }
287
    }
1,634✔
288

289
    pub(super) fn get_placeable(&mut self) -> Result<ast::Expression<S>> {
12,542✔
290
        self.skip_blank();
12,542✔
291
        let exp = self.get_expression()?;
12,542✔
292
        self.skip_blank_inline();
12,214✔
293
        self.expect_byte(b'}')?;
12,214✔
294

295
        let invalid_expression_found = match &exp {
12,094✔
296
            ast::Expression::Inline(ast::InlineExpression::TermReference {
297
                ref attribute, ..
4,235✔
298
            }) => attribute.is_some(),
4,235✔
299
            _ => false,
7,859✔
300
        };
301
        if invalid_expression_found {
12,094✔
302
            return error!(ErrorKind::TermAttributeAsPlaceable, self.ptr);
×
303
        }
12,094✔
304

12,094✔
305
        Ok(exp)
12,094✔
306
    }
12,542✔
307
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc