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
74pub(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 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}