Compare commits

..

No commits in common. "0208020e3e6db801b17851f2f06c19561d525163" and "7d73eb6bd456c1b1755ea17f804684c61058a99a" have entirely different histories.

14 changed files with 61 additions and 163 deletions

View File

@ -59,7 +59,6 @@ default = []
compare = ["tokio/process", "tokio/macros"] compare = ["tokio/process", "tokio/macros"]
foreign_document_test = ["compare", "dep:futures", "tokio/sync", "dep:walkdir", "tokio/process"] foreign_document_test = ["compare", "dep:futures", "tokio/sync", "dep:walkdir", "tokio/process"]
tracing = ["dep:opentelemetry", "dep:opentelemetry-otlp", "dep:opentelemetry-semantic-conventions", "dep:tokio", "dep:tracing", "dep:tracing-opentelemetry", "dep:tracing-subscriber"] tracing = ["dep:opentelemetry", "dep:opentelemetry-otlp", "dep:opentelemetry-semantic-conventions", "dep:tokio", "dep:tracing", "dep:tracing-opentelemetry", "dep:tracing-subscriber"]
event_count = []
# Optimized build for any sort of release. # Optimized build for any sort of release.
[profile.release-lto] [profile.release-lto]

View File

@ -14,7 +14,7 @@ function main {
additional_flags+=(--profile "$PROFILE") additional_flags+=(--profile "$PROFILE")
fi fi
(cd "$DIR/../" && cargo build --no-default-features "${additional_flags[@]}") (cd "$DIR/../" && cargo build --no-default-features "${additional_flags[@]}")
perf record --freq=70000 --call-graph dwarf --output="$DIR/../perf.data" "$DIR/../target/${PROFILE}/parse" "${@}" perf record --freq=2000 --call-graph dwarf --output="$DIR/../perf.data" "$DIR/../target/${PROFILE}/parse" "${@}"
# Convert to a format firefox will read # Convert to a format firefox will read
# flags to consider --show-info # flags to consider --show-info

View File

@ -10,6 +10,7 @@ pub enum CustomError {
Text(String), Text(String),
Static(&'static str), Static(&'static str),
IO(std::io::Error), IO(std::io::Error),
BoxedError(Box<dyn std::error::Error>),
Parser(ErrorKind), Parser(ErrorKind),
} }
@ -36,8 +37,8 @@ impl From<&'static str> for CustomError {
} }
} }
impl From<String> for CustomError { impl From<Box<dyn std::error::Error>> for CustomError {
fn from(value: String) -> Self { fn from(value: Box<dyn std::error::Error>) -> Self {
CustomError::Text(value) CustomError::BoxedError(value)
} }
} }

View File

@ -1,43 +0,0 @@
use std::collections::HashMap;
use std::sync::Mutex;
use super::EventType;
use crate::parser::OrgSource;
#[derive(Debug, Eq, Hash, PartialEq)]
struct EventKey {
event_type: EventType,
byte_offset: usize,
}
pub(crate) type EventCount = usize;
static GLOBAL_DATA: Mutex<Option<HashMap<EventKey, EventCount>>> = Mutex::new(None);
pub(crate) fn record_event(event_type: EventType, input: OrgSource<'_>) {
let mut db = GLOBAL_DATA.lock().unwrap();
let db = db.get_or_insert_with(HashMap::new);
let key = EventKey {
event_type,
byte_offset: input.get_byte_offset(),
};
*db.entry(key).or_insert(0) += 1;
}
pub fn report(original_document: &str) {
let mut db = GLOBAL_DATA.lock().unwrap();
let db = db.get_or_insert_with(HashMap::new);
let mut results: Vec<_> = db.iter().map(|(k, v)| (k, v)).collect();
results.sort_by_key(|(_k, v)| *v);
// This would put the most common at the top, but that is a pain when there is already a lot of output from the parser.
// results.sort_by(|(_ak, av), (_bk, bv)| bv.cmp(av));
for (key, count) in results {
println!(
"{:?} {} character offset: {} byte offset: {}",
key.event_type,
count,
original_document[..key.byte_offset].chars().count() + 1,
key.byte_offset
)
}
}

View File

@ -1,7 +0,0 @@
#[derive(Debug, Eq, Hash, PartialEq)]
pub(crate) enum EventType {
ElementStart,
ElementFinish,
ObjectStart,
ObjectFinish,
}

View File

@ -1,6 +0,0 @@
mod database;
mod event_type;
pub(crate) use database::record_event;
pub use database::report;
pub(crate) use event_type::EventType;

View File

@ -13,8 +13,6 @@ pub mod compare;
mod context; mod context;
mod error; mod error;
#[cfg(feature = "event_count")]
pub mod event_count;
mod iter; mod iter;
pub mod parser; pub mod parser;
pub mod types; pub mod types;

View File

@ -54,11 +54,8 @@ fn read_stdin_to_string() -> Result<String, Box<dyn std::error::Error>> {
} }
fn run_anonymous_parse<P: AsRef<str>>(org_contents: P) -> Result<(), Box<dyn std::error::Error>> { fn run_anonymous_parse<P: AsRef<str>>(org_contents: P) -> Result<(), Box<dyn std::error::Error>> {
let org_contents = org_contents.as_ref(); let rust_parsed = parse(org_contents.as_ref())?;
let rust_parsed = parse(org_contents)?;
println!("{:#?}", rust_parsed); println!("{:#?}", rust_parsed);
#[cfg(feature = "event_count")]
organic::event_count::report(org_contents);
Ok(()) Ok(())
} }
@ -78,7 +75,5 @@ fn run_parse_on_file<P: AsRef<Path>>(org_path: P) -> Result<(), Box<dyn std::err
}; };
let rust_parsed = parse_with_settings(org_contents, &global_settings)?; let rust_parsed = parse_with_settings(org_contents, &global_settings)?;
println!("{:#?}", rust_parsed); println!("{:#?}", rust_parsed);
#[cfg(feature = "event_count")]
organic::event_count::report(org_contents);
Ok(()) Ok(())
} }

View File

@ -14,48 +14,17 @@ use nom::multi::many0;
use nom::multi::many_till; use nom::multi::many_till;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::keyword::affiliated_keyword;
use super::object_parser::standard_set_object; use super::object_parser::standard_set_object;
use super::util::confine_context; use super::util::confine_context;
use super::OrgSource;
use crate::context::bind_context; use crate::context::bind_context;
use crate::context::Context; use crate::context::Context;
use crate::context::ContextElement; use crate::context::ContextElement;
use crate::context::GlobalSettings; use crate::context::GlobalSettings;
use crate::context::List; use crate::context::List;
use crate::context::RefContext;
use crate::error::Res;
use crate::types::AffiliatedKeywordValue; use crate::types::AffiliatedKeywordValue;
use crate::types::AffiliatedKeywords; use crate::types::AffiliatedKeywords;
use crate::types::Keyword; use crate::types::Keyword;
#[cfg_attr(
feature = "tracing",
tracing::instrument(ret, level = "debug", skip(context))
)]
pub(crate) fn affiliated_keywords<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Vec<Keyword<'s>>> {
let mut ret = Vec::new();
let mut remaining = input;
loop {
let result = affiliated_keyword(context, remaining);
match result {
Ok((remain, kw)) => {
remaining = remain;
ret.push(kw);
}
Err(_) => {
break;
}
}
}
Ok((remaining, ret))
}
pub(crate) fn parse_affiliated_keywords<'g, 's, AK>( pub(crate) fn parse_affiliated_keywords<'g, 's, AK>(
global_settings: &'g GlobalSettings<'g, 's>, global_settings: &'g GlobalSettings<'g, 's>,
input: AK, input: AK,

View File

@ -129,12 +129,23 @@ fn document_org_source<'b, 'g, 'r, 's>(
}) })
.collect::<Result<Vec<_>, _>>()?; .collect::<Result<Vec<_>, _>>()?;
for setup_file in setup_files.iter().map(String::as_str) { for setup_file in setup_files.iter().map(String::as_str) {
let (_, setup_file_settings) = scan_for_in_buffer_settings(setup_file.into())?; let (_, setup_file_settings) =
scan_for_in_buffer_settings(setup_file.into()).map_err(|err| {
eprintln!("{}", err);
nom::Err::Error(CustomError::Static(
"TODO: make this take an owned string so I can dump err.to_string() into it.",
))
})?;
final_settings.extend(setup_file_settings); final_settings.extend(setup_file_settings);
} }
final_settings.extend(document_settings); final_settings.extend(document_settings);
let new_settings = apply_in_buffer_settings(final_settings, context.get_global_settings()) let new_settings = apply_in_buffer_settings(final_settings, context.get_global_settings())
.map_err(nom::Err::Error)?; .map_err(|err| {
eprintln!("{}", err);
nom::Err::Error(CustomError::Static(
"TODO: make this take an owned string so I can dump err.to_string() into it.",
))
})?;
let new_context = context.with_global_settings(&new_settings); let new_context = context.with_global_settings(&new_settings);
let context = &new_context; let context = &new_context;
@ -159,13 +170,15 @@ fn document_org_source<'b, 'g, 'r, 's>(
let parser_context = context.with_global_settings(&new_global_settings); let parser_context = context.with_global_settings(&new_global_settings);
let (remaining, mut document) = _document(&parser_context, input) let (remaining, mut document) = _document(&parser_context, input)
.map(|(rem, out)| (Into::<&str>::into(rem), out))?; .map(|(rem, out)| (Into::<&str>::into(rem), out))?;
apply_post_parse_in_buffer_settings(&mut document); apply_post_parse_in_buffer_settings(&mut document)
.map_err(|err| nom::Err::<CustomError>::Failure(err.into()))?;
return Ok((remaining.into(), document)); return Ok((remaining.into(), document));
} }
} }
// Find final in-buffer settings that do not impact parsing // Find final in-buffer settings that do not impact parsing
apply_post_parse_in_buffer_settings(&mut document); apply_post_parse_in_buffer_settings(&mut document)
.map_err(|err| nom::Err::<CustomError>::Failure(err.into()))?;
Ok((remaining.into(), document)) Ok((remaining.into(), document))
} }
@ -195,17 +208,3 @@ fn _document<'b, 'g, 'r, 's>(
}, },
)) ))
} }
#[cfg(test)]
mod tests {
use test::Bencher;
use super::*;
#[bench]
fn bench_full_document(b: &mut Bencher) {
let input = include_str!("../../org_mode_samples/element_container_priority/README.org");
b.iter(|| assert!(parse(input).is_ok()));
}
}

View File

@ -1,3 +1,5 @@
use nom::multi::many0;
use super::babel_call::babel_call; use super::babel_call::babel_call;
use super::clock::clock; use super::clock::clock;
use super::comment::comment; use super::comment::comment;
@ -12,6 +14,7 @@ use super::footnote_definition::detect_footnote_definition;
use super::footnote_definition::footnote_definition; use super::footnote_definition::footnote_definition;
use super::greater_block::greater_block; use super::greater_block::greater_block;
use super::horizontal_rule::horizontal_rule; use super::horizontal_rule::horizontal_rule;
use super::keyword::affiliated_keyword;
use super::keyword::keyword; use super::keyword::keyword;
use super::latex_environment::latex_environment; use super::latex_environment::latex_environment;
use super::lesser_block::comment_block; use super::lesser_block::comment_block;
@ -24,14 +27,10 @@ use super::paragraph::paragraph;
use super::plain_list::detect_plain_list; use super::plain_list::detect_plain_list;
use super::plain_list::plain_list; use super::plain_list::plain_list;
use super::table::detect_table; use super::table::detect_table;
use crate::context::parser_with_context;
use crate::context::RefContext; use crate::context::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::Res; use crate::error::Res;
#[cfg(feature = "event_count")]
use crate::event_count::record_event;
#[cfg(feature = "event_count")]
use crate::event_count::EventType;
use crate::parser::affiliated_keyword::affiliated_keywords;
use crate::parser::macros::ak_element; use crate::parser::macros::ak_element;
use crate::parser::macros::element; use crate::parser::macros::element;
use crate::parser::table::org_mode_table; use crate::parser::table::org_mode_table;
@ -55,10 +54,8 @@ fn _element<'b, 'g, 'r, 's>(
input: OrgSource<'s>, input: OrgSource<'s>,
can_be_paragraph: bool, can_be_paragraph: bool,
) -> Res<OrgSource<'s>, Element<'s>> { ) -> Res<OrgSource<'s>, Element<'s>> {
#[cfg(feature = "event_count")]
record_event(EventType::ElementStart, input);
let (post_affiliated_keywords_input, affiliated_keywords) = let (post_affiliated_keywords_input, affiliated_keywords) =
affiliated_keywords(context, input)?; many0(parser_with_context!(affiliated_keyword)(context))(input)?;
let mut affiliated_keywords = affiliated_keywords.into_iter(); let mut affiliated_keywords = affiliated_keywords.into_iter();
@ -273,7 +270,7 @@ fn _detect_element<'b, 'g, 'r, 's>(
can_be_paragraph: bool, can_be_paragraph: bool,
) -> Res<OrgSource<'s>, ()> { ) -> Res<OrgSource<'s>, ()> {
let (post_affiliated_keywords_input, affiliated_keywords) = let (post_affiliated_keywords_input, affiliated_keywords) =
affiliated_keywords(context, input)?; many0(parser_with_context!(affiliated_keyword)(context))(input)?;
let mut affiliated_keywords = affiliated_keywords.into_iter(); let mut affiliated_keywords = affiliated_keywords.into_iter();

View File

@ -18,13 +18,12 @@ use nom::sequence::tuple;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::section::section; use super::section::section;
use super::util::exit_matcher_parser;
use super::util::get_consumed; use super::util::get_consumed;
use super::util::org_line_ending; use super::util::org_line_ending;
use super::util::org_space; use super::util::org_space;
use super::util::org_space_or_line_ending; use super::util::org_space_or_line_ending;
use super::util::start_of_line; use super::util::start_of_line;
use crate::context::bind_context; use crate::context::parser_with_context;
use crate::context::ContextElement; use crate::context::ContextElement;
use crate::context::ExitClass; use crate::context::ExitClass;
use crate::context::ExitMatcherNode; use crate::context::ExitMatcherNode;
@ -62,10 +61,10 @@ fn _heading<'b, 'g, 'r, 's>(
let mut scheduled = None; let mut scheduled = None;
let mut deadline = None; let mut deadline = None;
let mut closed = None; let mut closed = None;
not(bind_context!(exit_matcher_parser, context))(input)?; not(|i| context.check_exit_matcher(i))(input)?;
let (remaining, pre_headline) = headline(context, input, parent_star_count)?; let (remaining, pre_headline) = headline(context, input, parent_star_count)?;
let section_matcher = bind_context!(section, context); let section_matcher = parser_with_context!(section)(context);
let heading_matcher = bind_context!(heading(pre_headline.star_count), context); let heading_matcher = parser_with_context!(heading(pre_headline.star_count))(context);
let (remaining, maybe_section) = let (remaining, maybe_section) =
opt(map(section_matcher, DocumentElement::Section))(remaining)?; opt(map(section_matcher, DocumentElement::Section))(remaining)?;
let (remaining, _ws) = opt(tuple((start_of_line, many0(blank_line))))(remaining)?; let (remaining, _ws) = opt(tuple((start_of_line, many0(blank_line))))(remaining)?;
@ -155,7 +154,7 @@ fn headline<'b, 'g, 'r, 's>(
let (remaining, (_, (headline_level, star_count, _), _)) = tuple(( let (remaining, (_, (headline_level, star_count, _), _)) = tuple((
start_of_line, start_of_line,
verify( verify(
bind_context!(headline_level, &parser_context), parser_with_context!(headline_level)(&parser_context),
|(_, count, _)| *count > parent_star_count, |(_, count, _)| *count > parent_star_count,
), ),
peek(org_space), peek(org_space),
@ -163,7 +162,7 @@ fn headline<'b, 'g, 'r, 's>(
let (remaining, maybe_todo_keyword) = opt(tuple(( let (remaining, maybe_todo_keyword) = opt(tuple((
space1, space1,
bind_context!(heading_keyword, &parser_context), parser_with_context!(heading_keyword)(&parser_context),
peek(org_space_or_line_ending), peek(org_space_or_line_ending),
)))(remaining)?; )))(remaining)?;
@ -177,7 +176,9 @@ fn headline<'b, 'g, 'r, 's>(
let (remaining, maybe_title) = opt(tuple(( let (remaining, maybe_title) = opt(tuple((
space1, space1,
consumed(many1(bind_context!(standard_set_object, &parser_context))), consumed(many1(parser_with_context!(standard_set_object)(
&parser_context,
))),
)))(remaining)?; )))(remaining)?;
let (remaining, maybe_tags) = opt(tuple((space0, tags)))(remaining)?; let (remaining, maybe_tags) = opt(tuple((space0, tags)))(remaining)?;

View File

@ -88,7 +88,7 @@ fn in_buffer_settings_key<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSou
pub(crate) fn apply_in_buffer_settings<'g, 's, 'sf>( pub(crate) fn apply_in_buffer_settings<'g, 's, 'sf>(
keywords: Vec<Keyword<'sf>>, keywords: Vec<Keyword<'sf>>,
original_settings: &'g GlobalSettings<'g, 's>, original_settings: &'g GlobalSettings<'g, 's>,
) -> Result<GlobalSettings<'g, 's>, CustomError> { ) -> Result<GlobalSettings<'g, 's>, String> {
let mut new_settings = original_settings.clone(); let mut new_settings = original_settings.clone();
// Todo Keywords // Todo Keywords
@ -98,11 +98,7 @@ pub(crate) fn apply_in_buffer_settings<'g, 's, 'sf>(
|| kw.key.eq_ignore_ascii_case("typ_todo") || kw.key.eq_ignore_ascii_case("typ_todo")
}) { }) {
let (_, (in_progress_words, complete_words)) = let (_, (in_progress_words, complete_words)) =
todo_keywords(kw.value).map_err(|err| match err { todo_keywords(kw.value).map_err(|err| err.to_string())?;
nom::Err::Incomplete(_) => CustomError::Text(err.to_string()),
nom::Err::Error(e) => e,
nom::Err::Failure(e) => e,
})?;
new_settings new_settings
.in_progress_todo_keywords .in_progress_todo_keywords
.extend(in_progress_words.into_iter().map(str::to_string)); .extend(in_progress_words.into_iter().map(str::to_string));
@ -116,14 +112,9 @@ pub(crate) fn apply_in_buffer_settings<'g, 's, 'sf>(
.iter() .iter()
.filter(|kw| kw.key.eq_ignore_ascii_case("startup")) .filter(|kw| kw.key.eq_ignore_ascii_case("startup"))
{ {
let (_remaining, settings) = separated_list0(space1::<&str, CustomError>, is_not(" \t"))( let (_remaining, settings) =
kw.value, separated_list0(space1::<&str, nom::error::Error<_>>, is_not(" \t"))(kw.value)
) .map_err(|err: nom::Err<_>| err.to_string())?;
.map_err(|err: nom::Err<_>| match err {
nom::Err::Incomplete(_) => CustomError::Text(err.to_string()),
nom::Err::Error(e) => e,
nom::Err::Failure(e) => e,
})?;
if settings.contains(&"odd") { if settings.contains(&"odd") {
new_settings.odd_levels_only = HeadlineLevelFilter::Odd; new_settings.odd_levels_only = HeadlineLevelFilter::Odd;
} }
@ -137,11 +128,7 @@ pub(crate) fn apply_in_buffer_settings<'g, 's, 'sf>(
.iter() .iter()
.filter(|kw| kw.key.eq_ignore_ascii_case("link")) .filter(|kw| kw.key.eq_ignore_ascii_case("link"))
{ {
let (_, (link_key, link_value)) = link_template(kw.value).map_err(|err| match err { let (_, (link_key, link_value)) = link_template(kw.value).map_err(|e| e.to_string())?;
nom::Err::Incomplete(_) => CustomError::Text(err.to_string()),
nom::Err::Error(e) => e,
nom::Err::Failure(e) => e,
})?;
new_settings new_settings
.link_templates .link_templates
.insert(link_key.to_owned(), link_value.to_owned()); .insert(link_key.to_owned(), link_value.to_owned());
@ -152,7 +139,9 @@ pub(crate) fn apply_in_buffer_settings<'g, 's, 'sf>(
/// Apply in-buffer settings that do not impact parsing and therefore can be applied after parsing. /// Apply in-buffer settings that do not impact parsing and therefore can be applied after parsing.
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn apply_post_parse_in_buffer_settings<'g, 's, 'sf>(document: &mut Document<'s>) { pub(crate) fn apply_post_parse_in_buffer_settings<'g, 's, 'sf>(
document: &mut Document<'s>,
) -> Result<(), &'static str> {
document.category = Into::<AstNode>::into(&*document) document.category = Into::<AstNode>::into(&*document)
.into_iter() .into_iter()
.filter_map(|ast_node| { .filter_map(|ast_node| {
@ -165,6 +154,7 @@ pub(crate) fn apply_post_parse_in_buffer_settings<'g, 's, 'sf>(document: &mut Do
}) })
.last() .last()
.map(|kw| kw.value.to_owned()); .map(|kw| kw.value.to_owned());
Ok(())
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]

View File

@ -4,9 +4,11 @@ use nom::bytes::complete::tag;
use nom::bytes::complete::tag_no_case; use nom::bytes::complete::tag_no_case;
use nom::bytes::complete::take_while1; use nom::bytes::complete::take_while1;
use nom::character::complete::anychar; use nom::character::complete::anychar;
use nom::character::complete::line_ending;
use nom::character::complete::one_of; use nom::character::complete::one_of;
use nom::character::complete::space0; use nom::character::complete::space0;
use nom::combinator::consumed; use nom::combinator::consumed;
use nom::combinator::eof;
use nom::combinator::map; use nom::combinator::map;
use nom::combinator::not; use nom::combinator::not;
use nom::combinator::peek; use nom::combinator::peek;
@ -20,8 +22,7 @@ use super::org_source::BracketDepth;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::util::get_consumed; use super::util::get_consumed;
use super::util::maybe_consume_trailing_whitespace_if_not_exiting; use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
use super::util::org_line_ending; use crate::context::parser_with_context;
use crate::context::bind_context;
use crate::context::RefContext; use crate::context::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::Res; use crate::error::Res;
@ -48,8 +49,9 @@ fn _filtered_keyword<'s, F: Fn(OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s
// TODO: When key is a member of org-element-parsed-keywords, value can contain the standard set objects, excluding footnote references. // TODO: When key is a member of org-element-parsed-keywords, value can contain the standard set objects, excluding footnote references.
let (remaining, (consumed_input, (_, _, parsed_key, _))) = let (remaining, (consumed_input, (_, _, parsed_key, _))) =
consumed(tuple((space0, tag("#+"), key_parser, tag(":"))))(input)?; consumed(tuple((space0, tag("#+"), key_parser, tag(":"))))(input)?;
let (remaining, _ws) = space0(remaining)?; if let Ok((remaining, _)) =
if let Ok((remaining, _)) = org_line_ending(remaining) { tuple((space0::<_, CustomError>, alt((line_ending, eof))))(remaining)
{
return Ok(( return Ok((
remaining, remaining,
Keyword { Keyword {
@ -60,9 +62,12 @@ fn _filtered_keyword<'s, F: Fn(OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s
}, },
)); ));
} }
let (remaining, parsed_value) = let (remaining, _ws) = space0(remaining)?;
recognize(many_till(anychar, peek(tuple((space0, org_line_ending)))))(remaining)?; let (remaining, parsed_value) = recognize(many_till(
let (remaining, _ws) = tuple((space0, org_line_ending))(remaining)?; anychar,
peek(tuple((space0, alt((line_ending, eof))))),
))(remaining)?;
let (remaining, _ws) = tuple((space0, alt((line_ending, eof))))(remaining)?;
Ok(( Ok((
remaining, remaining,
Keyword { Keyword {
@ -102,7 +107,7 @@ pub(crate) fn affiliated_keyword<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Keyword<'s>> { ) -> Res<OrgSource<'s>, Keyword<'s>> {
filtered_keyword(bind_context!(affiliated_key, context))(input) filtered_keyword(parser_with_context!(affiliated_key)(context))(input)
} }
#[cfg_attr( #[cfg_attr(