use super::Parser;
use super::lexer::{Kind, TokenSet};
use crate::token_tree::Kind as AstKind;
mod feature;
mod glyph;
mod gpos;
mod gsub;
mod metrics;
mod table;
mod variations;
#[cfg(test)]
pub(crate) use self::{gsub::gsub_rule, metrics::expect_glyphs_number_value};
pub(crate) use feature::eat_feature_block_items;
pub fn root(parser: &mut Parser) {
parser.start_node(AstKind::SourceFile);
while !parser.at_eof() {
top_level_element(parser);
}
parser.eat_trivia();
parser.finish_node();
}
fn top_level_element(parser: &mut Parser) {
parser.eat_trivia();
if parser.at_eof() {
} else if parser.matches(0, Kind::IncludeKw) {
include(parser)
} else if parser.matches(0, Kind::TableKw) {
table(parser)
} else if parser.matches(0, Kind::LookupKw) {
lookup_block_or_reference(parser, TokenSet::TOP_LEVEL)
} else if parser.matches(0, Kind::LanguagesystemKw) {
language_system(parser)
} else if parser.matches(0, Kind::FeatureKw) {
feature::feature(parser)
} else if parser.matches(0, Kind::MarkClassKw) {
mark_class(parser)
} else if parser.matches(0, Kind::AnchorDefKw) {
anchor_def(parser)
} else if parser.matches(0, Kind::AnonKw) {
anonymous(parser)
} else if parser.matches(0, Kind::NamedGlyphClass) {
glyph::named_glyph_class_decl(parser, TokenSet::TOP_LEVEL)
} else if parser.matches(0, Kind::ConditionSetKw) {
variations::condition_set(parser)
} else if parser.matches(0, Kind::VariationKw) {
variations::variation(parser)
} else if parser.matches(0, Kind::ValueRecordDefKw) {
value_record_def(parser, TokenSet::TOP_LEVEL)
} else {
parser.err_and_bump(format!(
"Unexpected token '{}', expected global keyword.",
parser.current_token_text()
));
advance_to_top_level(parser);
}
}
fn advance_to_top_level(parser: &mut Parser) {
loop {
parser.eat_trivia();
if parser.at_eof() || parser.matches(0, TokenSet::TOP_LEVEL) {
break;
}
parser.eat_raw();
}
}
pub fn language_system(parser: &mut Parser) {
parser.in_node(AstKind::LanguageSystemNode, |parser| {
assert!(parser.eat(Kind::LanguagesystemKw));
if parser.expect_tag(Kind::Semi).is_none() || parser.expect_tag(Kind::Semi).is_none() {
return advance_to_top_level(parser);
}
parser.expect_semi();
})
}
fn include(parser: &mut Parser) {
fn include_body(parser: &mut Parser) {
assert!(parser.eat(Kind::IncludeKw));
if !parser.expect(Kind::LParen) {
advance_to_top_level(parser);
}
if !parser.split_remap_current(Kind::Path, |path, buf| {
let leading = path.bytes().take_while(|b| b.is_ascii_whitespace()).count();
let trailing = path
.bytes()
.rev()
.take_while(|b| b.is_ascii_whitespace())
.count();
if leading + trailing == 0 || leading == path.len() {
buf.push((0..path.len(), AstKind::Path));
return;
}
if leading > 0 {
buf.push((0..leading, AstKind::Whitespace));
}
buf.push((leading..path.len() - trailing, AstKind::Path));
if trailing > 0 {
buf.push((path.len() - trailing..path.len(), AstKind::Whitespace));
}
}) {
parser.err("Include statement missing path");
return advance_to_top_level(parser);
}
if !parser.expect(Kind::RParen) {
return advance_to_top_level(parser);
}
if !parser.eat(Kind::Semi) {
parser.warn_before_ws("include statement is missing ';'");
}
}
parser.in_node(AstKind::IncludeNode, include_body)
}
fn table(parser: &mut Parser) {
table::table(parser)
}
fn lookup_block_or_reference(parser: &mut Parser, recovery: TokenSet) {
assert!(parser.matches(0, Kind::LookupKw));
if parser.matches(2, Kind::LBrace)
|| (parser.matches(2, Kind::UseExtensionKw) && parser.matches(3, Kind::LBrace))
{
feature::lookup_block(parser, recovery.union(TokenSet::STATEMENT));
} else if parser.matches(2, Kind::Semi) {
parser.in_node(AstKind::LookupRefNode, |parser| {
assert!(parser.eat(Kind::LookupKw));
parser.eat_remap(TokenSet::IDENT_LIKE, AstKind::Ident);
parser.expect_semi();
})
} else {
parser.eat(Kind::LookupKw);
if parser.eat(Kind::Ident) {
parser.err_before_ws("Expected ';' or '{'");
parser.eat_unless(recovery);
} else {
parser.expect_recover(Kind::Ident, recovery);
}
}
}
fn eat_script(parser: &mut Parser, recovery: TokenSet) -> bool {
if !parser.matches(0, Kind::ScriptKw) {
return false;
}
parser.in_node(AstKind::ScriptNode, |parser| {
assert!(parser.eat(Kind::ScriptKw));
parser.expect_tag(recovery.union(TokenSet::SEMI));
parser.expect_semi();
});
true
}
fn eat_language(parser: &mut Parser, recovery: TokenSet) -> bool {
if !parser.matches(0, Kind::LanguageKw) {
return false;
}
parser.in_node(AstKind::LanguageNode, |parser| {
assert!(parser.eat(Kind::LanguageKw));
parser.expect_tag(recovery.union(TokenSet::SEMI));
parser.eat(Kind::ExcludeDfltKw);
parser.eat(Kind::IncludeDfltKw);
parser.eat(Kind::RequiredKw);
parser.expect_semi();
});
true
}
fn mark_class(parser: &mut Parser) {
const ANCHOR_START: TokenSet = TokenSet::new(&[Kind::LAngle]);
fn glyph_or_class(parser: &mut Parser) -> bool {
if parser.eat(Kind::NamedGlyphClass) {
true
} else if parser.matches(0, Kind::LSquare) {
glyph::eat_glyph_class_list(parser, ANCHOR_START)
} else {
parser.eat_remap(TokenSet::IDENT_LIKE, AstKind::GlyphName)
}
}
fn mark_class_body(parser: &mut Parser) {
assert!(parser.eat(Kind::MarkClassKw));
if !glyph_or_class(parser) {
parser.err("Expected glyph name or class");
}
metrics::anchor(parser, TokenSet::new(&[Kind::Semi, Kind::NamedGlyphClass]));
parser.expect_recover(Kind::NamedGlyphClass, Kind::Semi);
parser.expect_semi();
}
parser.in_node(AstKind::MarkClassNode, mark_class_body);
}
fn anchor_def(parser: &mut Parser) {
fn anchor_def_body(parser: &mut Parser) {
assert!(parser.eat(Kind::AnchorDefKw));
parser.in_node(AstKind::AnchorNode, |parser| {
let recovery = TokenSet::TOP_LEVEL
.union(TokenSet::IDENT_LIKE)
.union(TokenSet::new(&[Kind::ContourpointKw, Kind::Semi]));
parser.repeat(|parser| metrics::expect_metric(parser, recovery), 2);
if parser.eat(Kind::ContourpointKw) {
parser.expect_recover(Kind::Number, TokenSet::TOP_SEMI);
}
});
parser.expect_remap_recover(TokenSet::IDENT_LIKE, AstKind::Ident, TokenSet::TOP_SEMI);
parser.expect_semi();
}
parser.in_node(AstKind::AnchorDefNode, anchor_def_body);
}
fn value_record_def(parser: &mut Parser, recovery: TokenSet) {
fn value_record_def_body(parser: &mut Parser, recovery: TokenSet) {
assert!(parser.eat(Kind::ValueRecordDefKw));
metrics::expect_value_record(parser, recovery);
parser.expect_remap_recover(TokenSet::IDENT_LIKE, AstKind::Ident, recovery);
parser.expect_semi();
}
parser.in_node(AstKind::ValueRecordDefNode, |parser| {
value_record_def_body(parser, recovery)
})
}
fn anonymous(parser: &mut Parser) {
fn anon_body(parser: &mut Parser) {
assert!(parser.eat(Kind::AnonKw));
let raw_label_range = parser
.matches(0, TokenSet::IDENT_LIKE)
.then(|| parser.nth_range(0));
if !(parser.expect_recover(TokenSet::IDENT_LIKE, Kind::LBrace)
& parser.expect(Kind::LBrace))
{
return;
}
loop {
match parser.nth(0).kind {
Kind::RBrace
if parser.nth_raw(1) == parser.raw_range(raw_label_range.clone().unwrap())
&& parser.matches(2, Kind::Semi) =>
{
assert!(
parser.eat(Kind::RBrace)
&& parser.eat(TokenSet::IDENT_LIKE)
&& parser.eat(Kind::Semi)
);
break;
}
_ => {
if parser.nth(1).kind == Kind::Eof {
parser.raw_error(raw_label_range.unwrap(), "unterminated anonymous block");
parser.eat_raw();
break;
}
parser.eat_raw();
}
}
}
}
parser.in_node(AstKind::AnonBlockNode, anon_body);
}
fn expect_ignore_pattern_body(parser: &mut Parser, recovery: TokenSet) -> bool {
let recovery = recovery.add(Kind::Semi);
if !eat_ignore_statement_item(parser, recovery) {
parser.err_recover("Expected ignore pattern", recovery);
parser.eat_until(recovery);
return false;
}
while parser.eat(Kind::Comma) {
eat_ignore_statement_item(parser, recovery);
}
parser.expect_semi();
true
}
fn eat_ignore_statement_item(parser: &mut Parser, recovery: TokenSet) -> bool {
let recovery = recovery.union(Kind::Comma.into());
if !glyph::eat_glyph_or_glyph_class(parser, recovery) {
return false;
}
while glyph::eat_glyph_or_glyph_class(parser, recovery) {
continue;
}
if parser.eat(Kind::SingleQuote) {
loop {
glyph::eat_glyph_or_glyph_class(parser, recovery);
if !parser.eat(Kind::SingleQuote) {
break;
}
}
}
while glyph::eat_glyph_or_glyph_class(parser, recovery) {
continue;
}
true
}
fn greedy<F: FnMut(&mut Parser, TokenSet) -> bool>(
mut f: F,
) -> impl FnMut(&mut Parser, TokenSet) -> bool {
move |parser, recovery| {
if !f(parser, recovery) {
return false;
}
while f(parser, recovery) {
continue;
}
true
}
}
#[cfg(test)]
fn debug_parse_output(
text: &str,
f: impl FnOnce(&mut Parser),
) -> (crate::NodeOrToken, Vec<crate::Diagnostic>, String) {
use super::Source;
let source = Source::new("debug_parse_output".into(), text.into());
let mut sink = crate::token_tree::AstSink::new(text, source.id(), None);
let mut parser = Parser::new(text, &mut sink);
f(&mut parser);
let (node, errs, _) = sink.finish();
let mut err_str = String::new();
for err in &errs {
if !err_str.is_empty() {
err_str.push('\n');
}
crate::util::highlighting::write_diagnostic(&mut err_str, err, &source, Some(80), true);
}
(node.into(), errs, err_str)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::token_tree::typed::{self, AstNode};
#[test]
fn no_cv_param_in_lookup() {
let fea = "lookup hi {cvParameters {}; } hi ;";
let (_out, errors, _errstr) = debug_parse_output(fea, root);
assert!(!errors.is_empty(), "{}", fea);
assert!(errors.first().unwrap().text().contains("cvParameters"));
}
#[test]
fn simple_value_record_def() {
let fea = "valueRecordDef 123 foo;";
let (out, errors, _errstr) = debug_parse_output(fea, root);
assert!(errors.is_empty());
let out = typed::Root::cast(&out).unwrap();
let value_def = out.iter().find_map(typed::ValueRecordDef::cast).unwrap();
assert_eq!(
value_def
.value_record()
.advance()
.and_then(|x| x.parse_simple()),
Some(123)
);
assert_eq!(value_def.name().as_str(), "foo");
}
fn assert_include_path_matches(fea: &str, path: &str) {
let (out, errors, _errstr) = debug_parse_output(fea, include);
assert!(errors.is_empty(), "{errors:?}");
let out = typed::Include::cast(&out).unwrap();
assert_eq!(out.path().as_str(), path);
}
#[test]
fn parse_include_with_space() {
assert_include_path_matches("include (myfile.fea);", "myfile.fea");
}
#[test]
fn parse_include_leading_ws() {
assert_include_path_matches("include( myfile.fea);", "myfile.fea");
}
#[test]
fn parse_include_trailing_ws() {
assert_include_path_matches("include(myfile.fea );", "myfile.fea");
}
#[test]
fn parse_include_leading_and_trailing_ws() {
assert_include_path_matches("include( myfile.fea );", "myfile.fea");
}
#[test]
fn parse_include_all_whitespace() {
assert_include_path_matches("include( );", " ");
}
}