fea_rs/parse/grammar/
feature.rs

1use super::super::lexer::{Kind as LexemeKind, TokenSet};
2use super::{glyph, gpos, gsub, metrics};
3
4use crate::parse::Parser;
5use crate::token_tree::Kind;
6
7const LABEL_RECOVERY: TokenSet = TokenSet::new(&[LexemeKind::UseExtensionKw, LexemeKind::LBrace]);
8
9pub(crate) fn feature(parser: &mut Parser) {
10    fn feature_body(parser: &mut Parser) {
11        assert!(parser.eat(Kind::FeatureKw));
12        let open_tag = parser.expect_tag(LABEL_RECOVERY);
13
14        parser.eat(Kind::UseExtensionKw);
15        parser.expect(Kind::LBrace);
16        while !parser.at_eof() && !parser.matches(0, Kind::RBrace) {
17            if !statement(parser, TokenSet::FEATURE_STATEMENT, false) {
18                if let Some(tag) = open_tag.as_ref() {
19                    parser.raw_error(tag.range.clone(), "Feature block is unclosed");
20                }
21                break;
22            }
23        }
24        parser.expect_recover(Kind::RBrace, TokenSet::TOP_SEMI);
25        let close_tag = parser.expect_tag(TokenSet::TOP_LEVEL);
26        if let (Some(open), Some(close)) = (open_tag, close_tag) {
27            if open.tag != close.tag {
28                parser.raw_error(close.range, format!("expected tag '{}'", open.tag));
29            }
30        }
31        parser.expect_semi();
32    }
33
34    parser.in_node(Kind::FeatureNode, feature_body);
35}
36
37pub(crate) fn lookup_block(parser: &mut Parser, recovery: TokenSet) {
38    fn lookup_body(parser: &mut Parser, recovery: TokenSet) {
39        assert!(parser.eat(Kind::LookupKw));
40        let raw_label_range = parser.matches(0, Kind::Ident).then(|| parser.nth_range(0));
41        parser.expect_remap_recover(
42            TokenSet::IDENT_LIKE,
43            Kind::Label,
44            LABEL_RECOVERY.union(recovery),
45        );
46
47        parser.eat(Kind::UseExtensionKw);
48        parser.expect(Kind::LBrace);
49        while !parser.at_eof() && !parser.matches(0, Kind::RBrace) {
50            if !statement(parser, recovery, true) {
51                if let Some(range) = raw_label_range {
52                    parser.raw_error(range, "Table is unclosed");
53                }
54                break;
55            }
56        }
57        parser.expect_recover(
58            Kind::RBrace,
59            recovery.union(TokenSet::IDENT_LIKE.union(TokenSet::SEMI)),
60        );
61        parser.expect_remap_recover(
62            TokenSet::IDENT_LIKE,
63            Kind::Label,
64            recovery.union(TokenSet::SEMI),
65        );
66        parser.expect_semi();
67    }
68
69    parser.in_node(Kind::LookupBlockNode, |parser| {
70        lookup_body(parser, recovery)
71    });
72}
73
74/// returns true if we advanced the parser.
75pub(crate) fn statement(parser: &mut Parser, recovery: TokenSet, in_lookup: bool) -> bool {
76    let start_pos = parser.nth_range(0).start;
77    match parser.nth(0).kind.to_token_kind() {
78        Kind::PosKw | Kind::SubKw | Kind::RsubKw | Kind::IgnoreKw | Kind::EnumKw => {
79            pos_or_sub_rule(parser, recovery)
80        }
81        Kind::NamedGlyphClass => {
82            glyph::named_glyph_class_decl(parser, TokenSet::TOP_LEVEL.union(recovery))
83        }
84        Kind::MarkClassKw => super::mark_class(parser),
85        Kind::SubtableKw => parser.in_node(Kind::SubtableNode, |parser| {
86            parser.eat_raw();
87            parser.expect_recover(Kind::Semi, recovery);
88        }),
89        Kind::LookupKw if in_lookup => {
90            parser.err_and_bump("lookups cannot be nested.");
91            parser.eat_until(recovery);
92        }
93        Kind::IncludeKw => super::include(parser),
94        Kind::LookupKw => super::lookup_block_or_reference(parser, recovery),
95        Kind::LookupflagKw => lookupflag(parser, recovery),
96        Kind::ScriptKw => {
97            super::eat_script(parser, recovery);
98        }
99        Kind::LanguageKw => {
100            super::eat_language(parser, recovery);
101        }
102        Kind::FeatureKw => {
103            // aalt only
104            if parser.matches(1, TokenSet::TAG_LIKE) && parser.matches(2, Kind::Semi) {
105                parser.in_node(Kind::AaltFeatureNode, |parser| {
106                    assert!(parser.eat(Kind::FeatureKw));
107                    parser.expect_tag(TokenSet::EMPTY);
108                    parser.expect_recover(Kind::Semi, recovery);
109                });
110            }
111        }
112        Kind::ParametersKw => metrics::parameters(parser, recovery),
113        Kind::SizemenunameKw => parser.in_node(Kind::SizeMenuNameNode, |parser| {
114            assert!(parser.eat(Kind::SizemenunameKw));
115            metrics::expect_name_record(parser, recovery);
116            parser.expect_recover(Kind::Semi, recovery);
117        }),
118        Kind::CvParametersKw if in_lookup => {
119            parser.err_and_bump("'cvParameters' invalid in lookup block");
120            parser.eat_until(recovery);
121        }
122        Kind::CvParametersKw => cv_parameters(parser, recovery),
123        Kind::FeatureNamesKw => feature_names(parser, recovery),
124        Kind::Semi => {
125            parser.warn("';' should only follow a statement");
126            parser.eat_raw();
127        }
128
129        _ => {
130            let token = parser.current_token_text();
131            let scope = if in_lookup { "lookup" } else { "feature" };
132            parser.err(format!("'{token}' Not valid in a {scope} block"));
133            parser.eat_until(TokenSet::TOP_AND_FEATURE.add(LexemeKind::RBrace));
134        }
135    }
136    parser.nth_range(0).start != start_pos
137}
138
139pub(crate) fn pos_or_sub_rule(parser: &mut Parser, recovery: TokenSet) {
140    match parser.nth(0).kind.to_token_kind() {
141        Kind::PosKw => gpos::gpos_rule(parser, recovery),
142        Kind::EnumKw if parser.nth(1).kind.to_token_kind() == Kind::PosKw => {
143            gpos::gpos_rule(parser, recovery)
144        }
145        Kind::EnumKw => parser.err_and_bump("'enum' keyword must be followed by position rule"),
146        Kind::IgnoreKw => match parser.nth(1).kind.to_token_kind() {
147            Kind::PosKw => gpos::gpos_rule(parser, recovery),
148            Kind::SubKw => gsub::gsub_rule(parser, recovery),
149            _ => parser
150                .err_and_bump("'ignore' keyword must be followed by position or substitution rule"),
151        },
152        Kind::SubKw | Kind::RsubKw => gsub::gsub_rule(parser, recovery),
153        other => panic!("'{other}' is not a valid gpos or gsub token"),
154    }
155}
156fn name_entry(parser: &mut Parser, recovery: TokenSet) {
157    if parser.expect(Kind::NameKw) {
158        metrics::expect_name_record(parser, recovery);
159    } else {
160        parser.eat_until(recovery);
161    }
162    parser.expect_semi();
163}
164
165fn feature_names(parser: &mut Parser, recovery: TokenSet) {
166    let name_recovery = recovery.union(TokenSet::new(&[
167        LexemeKind::NameKw,
168        LexemeKind::RBrace,
169        LexemeKind::Semi,
170    ]));
171
172    parser.in_node(Kind::FeatureNamesKw, |parser| {
173        assert!(parser.eat(Kind::FeatureNamesKw));
174        parser.expect_recover(Kind::LBrace, name_recovery);
175        while !parser.at_eof() && !parser.matches(0, recovery.add(LexemeKind::RBrace)) {
176            name_entry(parser, name_recovery);
177        }
178        parser.expect_recover(Kind::RBrace, name_recovery);
179        parser.expect_semi();
180    })
181}
182
183fn cv_parameters(parser: &mut Parser, recovery: TokenSet) {
184    const UNICODE_VALUE: TokenSet = TokenSet::new(&[LexemeKind::Number, LexemeKind::Hex]);
185    const PARAM_KEYWORDS: TokenSet = TokenSet::new(&[
186        LexemeKind::FeatUiLabelNameIdKw,
187        LexemeKind::FeatUiTooltipTextNameIdKw,
188        LexemeKind::SampleTextNameIdKw,
189        LexemeKind::ParamUiLabelNameIdKw,
190    ]);
191
192    fn entry(parser: &mut Parser, recovery: TokenSet) {
193        if parser.matches(0, Kind::CharacterKw) {
194            parser.in_node(Kind::CharacterKw, |parser| {
195                assert!(parser.eat(Kind::CharacterKw));
196                parser.expect_recover(UNICODE_VALUE, recovery);
197                parser.expect_semi();
198            })
199        } else if parser.matches(0, PARAM_KEYWORDS) {
200            parser.in_node(Kind::CvParamsNameNode, |parser| {
201                assert!(parser.eat(PARAM_KEYWORDS));
202                parser.expect_recover(Kind::LBrace, recovery.add(LexemeKind::NameKw));
203                while !parser.at_eof() && !parser.matches(0, recovery) {
204                    name_entry(parser, recovery.add(LexemeKind::NameKw));
205                }
206                parser.expect_recover(Kind::RBrace, recovery);
207                parser.expect_semi();
208            });
209        } else {
210            parser.err_and_bump("token not valid in cvParameters block");
211            parser.eat_until(recovery);
212            parser.eat(Kind::Semi);
213        }
214    }
215
216    let entry_recovery = recovery.union(PARAM_KEYWORDS).union(TokenSet::new(&[
217        LexemeKind::RBrace,
218        LexemeKind::CharacterKw,
219        LexemeKind::Semi,
220    ]));
221
222    parser.in_node(Kind::CvParametersKw, |parser| {
223        assert!(parser.eat(Kind::CvParametersKw));
224        parser.expect_recover(Kind::LBrace, entry_recovery);
225        while !parser.at_eof() && !parser.matches(0, recovery.add(LexemeKind::RBrace)) {
226            entry(parser, entry_recovery);
227        }
228        parser.expect_recover(Kind::RBrace, entry_recovery);
229        parser.expect_semi();
230    });
231}
232
233fn lookupflag(parser: &mut Parser, recovery: TokenSet) {
234    fn eat_named_lookup_value(parser: &mut Parser, recovery: TokenSet) -> bool {
235        match parser.nth(0).kind.to_token_kind() {
236            Kind::RightToLeftKw
237            | Kind::IgnoreBaseGlyphsKw
238            | Kind::IgnoreMarksKw
239            | Kind::IgnoreLigaturesKw => {
240                parser.eat_raw();
241                true
242            }
243            Kind::MarkAttachmentTypeKw | Kind::UseMarkFilteringSetKw => {
244                parser.eat_raw();
245                if !parser.eat(Kind::NamedGlyphClass)
246                    && !glyph::eat_glyph_class_list(parser, recovery)
247                {
248                    parser.err("lookupflag '{}' must be followed by a glyph class.");
249                }
250                true
251            }
252            _ => false,
253        }
254    }
255
256    fn lookupflag_body(parser: &mut Parser, recovery: TokenSet) {
257        assert!(parser.eat(Kind::LookupflagKw));
258        if !parser.eat(Kind::Number) {
259            while eat_named_lookup_value(parser, recovery) {
260                continue;
261            }
262        }
263        parser.expect_semi();
264    }
265
266    parser.in_node(Kind::LookupFlagNode, |parser| {
267        lookupflag_body(parser, recovery);
268    });
269}