mod error;
#[cfg(test)]
mod tests;
use {
crate::{
from_bytes::FromBytes,
xkb::{
code_slice::CodeSlice,
group::GroupIdx,
include::error::{
ParseIncludeError, invalid_group, missing_file_name, missing_merge_mode,
unterminated_map_name,
},
interner::{Interned, Interner},
kccgst::MergeMode,
span::{Span, SpanExt, SpanUnit, Spanned},
},
},
kbvm_proc::CloneWithDelta,
};
#[derive(Debug)]
pub(crate) struct Include {
pub(crate) merge_mode: Option<Spanned<MergeMode>>,
pub(crate) file: Spanned<Interned>,
pub(crate) map: Option<Spanned<Interned>>,
pub(crate) group: Option<IncludeGroup>,
}
#[derive(Copy, Clone, Debug, CloneWithDelta)]
pub(crate) struct IncludeGroup {
pub(crate) group_name: Spanned<Interned>,
pub(crate) group: GroupIdx,
}
pub(crate) struct IncludeIter<'a> {
interner: &'a mut Interner,
s: CodeSlice<'static>,
pos: usize,
span_lo: SpanUnit,
first: bool,
}
pub(crate) fn parse_include(
interner: &mut Interner,
include: Spanned<Interned>,
) -> IncludeIter<'_> {
IncludeIter {
s: interner.get(include.val).to_owned(),
interner,
pos: 0,
span_lo: include.span.lo + 1,
first: true,
}
}
impl IncludeIter<'_> {
pub(crate) fn interner(&mut self) -> &mut Interner {
self.interner
}
}
impl Iterator for IncludeIter<'_> {
type Item = Result<Include, Spanned<ParseIncludeError>>;
fn next(&mut self) -> Option<Self::Item> {
while self.pos < self.s.len() && self.s[self.pos].is_ascii_whitespace() {
self.pos += 1;
}
assert!(self.pos <= self.s.len());
if self.pos == self.s.len() {
return None;
}
let captures = match capture(&self.s, self.span_lo, self.pos) {
Ok(c) => c,
Err(e) => {
self.pos = self.s.len();
return Some(Err(e));
}
};
self.pos = captures.pos;
let file = captures.file;
let mm = captures.mm;
if mm.is_none() && !self.first {
let span = Span {
lo: file.span.lo,
hi: file.span.lo + 1,
};
return Some(Err(missing_merge_mode(span)));
}
let file = self.interner.intern(&file.val).spanned2(file.span);
let map = captures
.map
.map(|g| self.interner.intern(&g.val).spanned2(g.span));
let mut group = None;
if let Some(g) = captures.group {
let group_name = self.interner.intern(&g.val).spanned2(g.span);
let Some(group_idx) = u32::from_bytes_dec(g.val.as_bytes()) else {
return Some(Err(invalid_group(&g.val, g.span.lo, g.span.hi)));
};
let Some(group_idx) = GroupIdx::new(group_idx) else {
return Some(Err(invalid_group(&g.val, g.span.lo, g.span.hi)));
};
group = Some(IncludeGroup {
group_name,
group: group_idx,
});
};
self.first = false;
Some(Ok(Include {
merge_mode: mm,
file,
map,
group,
}))
}
}
struct Capture<'a> {
mm: Option<Spanned<MergeMode>>,
file: Spanned<CodeSlice<'a>>,
map: Option<Spanned<CodeSlice<'a>>>,
group: Option<Spanned<CodeSlice<'a>>>,
pos: usize,
}
fn capture<'a>(
slice: &'a CodeSlice<'static>,
lo: SpanUnit,
mut pos: usize,
) -> Result<Capture<'a>, Spanned<ParseIncludeError>> {
let mut mm = None;
let mm_lo = lo + pos as SpanUnit;
match slice[pos] {
b'|' => {
mm = Some(MergeMode::Augment.spanned(mm_lo, mm_lo + 1));
pos += 1;
}
b'+' => {
mm = Some(MergeMode::Override.spanned(mm_lo, mm_lo + 1));
pos += 1;
}
b'^' => {
mm = Some(MergeMode::Replace.spanned(mm_lo, mm_lo + 1));
pos += 1;
}
_ => {}
};
if matches!(
slice.get(pos),
None | Some(b'(' | b'|' | b'+' | b'^' | b':')
) {
return Err(missing_file_name(Span {
lo: lo + pos as SpanUnit,
hi: lo + pos as SpanUnit + 1,
}));
}
let file_start = pos;
pos += 1;
while pos < slice.len() && !matches!(slice[pos], b'(' | b'|' | b'+' | b'^' | b':') {
pos += 1;
}
let file_end = pos;
let mut map = None;
if pos < slice.len() && slice[pos] == b'(' {
pos += 1;
let map_start = pos;
let map_end = loop {
match slice.get(pos) {
None => {
return Err(unterminated_map_name(
lo + pos as SpanUnit,
lo + pos as SpanUnit + 1,
));
}
Some(b')') => break pos,
_ => pos += 1,
}
};
pos += 1;
map = Some(
slice
.slice(map_start..map_end)
.spanned(lo + map_start as SpanUnit, lo + map_end as SpanUnit),
);
}
let mut group = None;
if pos < slice.len() && slice[pos] == b':' {
pos += 1;
let group_start = pos;
if !matches!(slice.get(pos), Some(b'0'..=b'9')) {
return Err(invalid_group(
&slice.slice(group_start..group_start + 1),
lo + pos as SpanUnit,
lo + pos as SpanUnit + 1,
));
}
pos += 1;
while matches!(slice.get(pos), Some(b'0'..=b'9')) {
pos += 1;
}
let group_end = pos;
group = Some(
slice
.slice(group_start..group_end)
.spanned(lo + group_start as SpanUnit, lo + group_end as SpanUnit),
);
}
Ok(Capture {
mm,
file: slice
.slice(file_start..file_end)
.spanned(lo + file_start as SpanUnit, lo + file_end as SpanUnit),
map,
group,
pos,
})
}