use super::{glyph, metrics};
use crate::parse::{
Parser,
lexer::{Kind, TokenSet},
};
use crate::token_tree::Kind as AstKind;
pub(crate) fn gpos_rule(parser: &mut Parser, recovery: TokenSet) {
fn gpos_body(parser: &mut Parser, recovery: TokenSet) -> AstKind {
if parser.matches(0, Kind::IgnoreKw) {
return parse_ignore(parser, recovery);
}
let recovery = recovery.union(Kind::Semi.into());
if parser.eat(Kind::EnumKw) {
assert!(parser.eat(Kind::PosKw));
if glyph::expect_glyph_or_glyph_class(parser, recovery)
&& glyph::expect_glyph_or_glyph_class(parser, recovery)
&& metrics::expect_value_record(parser, recovery)
&& parser.expect_semi()
{
return AstKind::GposType2;
} else {
return AstKind::GposNode;
}
}
assert!(parser.eat(Kind::PosKw));
if parser.matches(0, Kind::CursiveKw) {
gpos_cursive(parser, recovery);
AstKind::GposType3
} else if parser.matches(0, Kind::MarkKw) {
gpos_mark_to_mark(parser, recovery);
AstKind::GposType6
} else if parser.nth_raw(0) == b"base" {
gpos_mark_to_base(parser, recovery);
AstKind::GposType4
} else if parser.nth_raw(0) == b"ligature" {
gpos_ligature(parser, recovery);
AstKind::GposType5
} else {
if !glyph::expect_glyph_or_glyph_class(parser, recovery) {
parser.eat_until(recovery);
return AstKind::GposNode;
}
if metrics::eat_value_record(parser, recovery) {
if glyph::eat_glyph_or_glyph_class(parser, recovery) {
metrics::eat_value_record(parser, recovery);
parser.expect_semi();
return AstKind::GposType2;
}
parser.expect_semi();
return AstKind::GposType1;
}
if glyph::eat_glyph_or_glyph_class(parser, recovery)
&& metrics::eat_value_record(parser, recovery)
{
parser.expect_semi();
return AstKind::GposType2;
}
finish_chain_rule(parser, recovery)
}
}
parser.eat_trivia();
parser.start_node(AstKind::GposNode);
let kind = gpos_body(parser, recovery);
parser.finish_and_remap_node(kind);
}
fn parse_ignore(parser: &mut Parser, recovery: TokenSet) -> AstKind {
assert!(parser.eat(Kind::IgnoreKw));
assert!(parser.eat(Kind::PosKw));
if super::expect_ignore_pattern_body(parser, recovery) {
AstKind::GposNodeNeedsRewrite
} else {
AstKind::GposNode
}
}
fn gpos_cursive(parser: &mut Parser, recovery: TokenSet) {
assert!(parser.eat(Kind::CursiveKw));
glyph::eat_glyph_or_glyph_class(parser, recovery.union(Kind::LAngle.into()));
metrics::anchor(parser, recovery.union(Kind::LAngle.into()));
metrics::anchor(parser, recovery);
parser.expect_semi();
}
fn gpos_mark_to_mark(parser: &mut Parser, recovery: TokenSet) {
assert!(parser.eat(Kind::MarkKw));
gpos_mark_to_(parser, recovery);
}
fn gpos_mark_to_base(parser: &mut Parser, recovery: TokenSet) {
assert!(parser.nth_raw(0) == b"base");
parser.eat_remap(Kind::Ident, AstKind::BaseKw);
gpos_mark_to_(parser, recovery);
}
fn gpos_mark_to_(parser: &mut Parser, recovery: TokenSet) {
glyph::eat_glyph_or_glyph_class(
parser,
recovery.union(TokenSet::new(&[Kind::LAngle, Kind::AnchorKw])),
);
super::greedy(anchor_mark)(parser, recovery);
parser.expect_semi();
}
fn gpos_ligature(parser: &mut Parser, recovery: TokenSet) {
assert!(parser.nth_raw(0) == b"ligature");
parser.eat_remap(Kind::Ident, AstKind::LigatureKw);
glyph::eat_glyph_or_glyph_class(
parser,
recovery.union(TokenSet::new(&[Kind::LAngle, Kind::AnchorKw])),
);
parser.in_node(AstKind::LigatureComponentNode, |parser| {
super::greedy(anchor_mark)(parser, recovery);
});
while parser.nth_raw(0) == b"ligComponent" {
parser.in_node(AstKind::LigatureComponentNode, |parser| {
parser.eat_raw();
super::greedy(anchor_mark)(parser, recovery);
});
}
parser.expect_semi();
}
fn finish_chain_rule(parser: &mut Parser, recovery: TokenSet) -> AstKind {
const RECOVERY: TokenSet = TokenSet::new(&[
Kind::LAngle,
Kind::SingleQuote,
Kind::Number,
Kind::LookupKw,
]);
debug_assert!(recovery.contains(Kind::Semi));
debug_assert!(recovery.contains(Kind::PosKw));
debug_assert!(recovery.contains(Kind::EnumKw));
debug_assert!(recovery.contains(Kind::SubKw));
super::greedy(glyph::eat_glyph_or_glyph_class)(parser, recovery.union(RECOVERY));
if !parser.matches(0, Kind::SingleQuote) {
parser.err("expected marked glyph");
parser.eat_until(recovery);
return AstKind::GposNode;
}
while parser.eat(Kind::SingleQuote) {
if !super::greedy(eat_lookup)(parser, recovery) {
metrics::eat_value_record(parser, recovery);
}
glyph::eat_glyph_or_glyph_class(parser, recovery.union(RECOVERY));
}
super::greedy(glyph::eat_glyph_or_glyph_class)(parser, recovery);
metrics::eat_value_record(parser, recovery);
if parser.expect_semi() {
AstKind::GposNodeNeedsRewrite
} else {
AstKind::GposNode
}
}
fn eat_lookup(parser: &mut Parser, recovery: TokenSet) -> bool {
if parser.eat(Kind::LookupKw) {
if !parser.eat(Kind::Ident) {
parser.err_recover("expected named lookup", recovery);
}
return true;
}
false
}
fn anchor_mark(parser: &mut Parser, recovery: TokenSet) -> bool {
const RECOVERY: TokenSet = TokenSet::new(&[Kind::MarkKw, Kind::NamedGlyphClass, Kind::Semi]);
if !(parser.matches(0, Kind::LAngle) && parser.matches(1, Kind::AnchorKw)) {
return false;
}
parser.in_node(AstKind::AnchorMarkNode, |parser| {
metrics::anchor(parser, recovery.union(RECOVERY));
if !parser.matches(0, Kind::Semi) {
parser.expect_recover(Kind::MarkKw, recovery.union(RECOVERY));
parser.expect_recover(Kind::NamedGlyphClass, recovery.union(RECOVERY));
}
});
true
}