Compare commits

..

6 Commits

Author SHA1 Message Date
Tom Alexander
503db94b2c
Publish version 0.1.12.
All checks were successful
rustfmt Build rustfmt has succeeded
clippy Build clippy has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
2023-10-18 19:01:43 -04:00
Tom Alexander
a4381e5e39
Merge branch 'keyword_constants' 2023-10-18 18:48:52 -04:00
Tom Alexander
e11de60def
Clippy fixes.
All checks were successful
clippy Build clippy has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
2023-10-18 18:39:04 -04:00
Tom Alexander
b2479e9de8
Remove Debug from the context variables.
Now that entities are stored in the settings struct, these variables are massive which makes them balloon trace sizes while being mostly unreadable. This removes Debug from them to serve as a static-analysis check that context is ALWAYS ignored in tracing calls.
2023-10-18 18:36:25 -04:00
Tom Alexander
49d1cef7ae
Remove context from functions that no longer need it. 2023-10-18 18:28:24 -04:00
Tom Alexander
ba72cc1b29
The variables for keywords are actually constants.
These settings do not need to exist in GlobalSettings because they are actually constants in upstream Org-Mode.
2023-10-18 18:22:01 -04:00
11 changed files with 57 additions and 114 deletions

View File

@ -2,7 +2,7 @@
[package] [package]
name = "organic" name = "organic"
version = "0.1.11" version = "0.1.12"
authors = ["Tom Alexander <tom@fizz.buzz>"] authors = ["Tom Alexander <tom@fizz.buzz>"]
description = "An org-mode parser." description = "An org-mode parser."
edition = "2021" edition = "2021"

View File

@ -1,15 +1,27 @@
use super::global_settings::EntityDefinition; use super::global_settings::EntityDefinition;
pub(crate) const DEFAULT_ORG_ELEMENT_PARSED_KEYWORDS: [&str; 1] = ["CAPTION"]; /// Keywords that contain the standard set of objects (excluding footnote references).
///
/// Corresponds to org-element-parsed-keywords elisp variable.
pub(crate) const ORG_ELEMENT_PARSED_KEYWORDS: [&str; 1] = ["CAPTION"];
pub(crate) const DEFAULT_ORG_ELEMENT_DUAL_KEYWORDS: [&str; 2] = ["CAPTION", "RESULTS"]; /// Keywords that can have a secondary value in square brackets.
///
/// Corresponds to org-element-dual-keywords elisp variable.
pub(crate) const ORG_ELEMENT_DUAL_KEYWORDS: [&str; 2] = ["CAPTION", "RESULTS"];
pub(crate) const DEFAULT_ORG_ELEMENT_AFFILIATED_KEYWORDS: [&str; 13] = [ /// Keywords that can be affiliated with an element.
///
/// Corresponds to org-element-affiliated-keywords elisp variable.
pub(crate) const ORG_ELEMENT_AFFILIATED_KEYWORDS: [&str; 13] = [
"CAPTION", "DATA", "HEADER", "HEADERS", "LABEL", "NAME", "PLOT", "RESNAME", "RESULT", "CAPTION", "DATA", "HEADER", "HEADERS", "LABEL", "NAME", "PLOT", "RESNAME", "RESULT",
"RESULTS", "SOURCE", "SRCNAME", "TBLNAME", "RESULTS", "SOURCE", "SRCNAME", "TBLNAME",
]; ];
pub(crate) const DEFAULT_ORG_ELEMENT_KEYWORD_TRANSLATION_ALIST: [(&str, &str); 8] = [ /// Mapping of keyword names.
///
/// Corresponds to org-element-keyword-translation-alist elisp variable.
pub(crate) const ORG_ELEMENT_KEYWORD_TRANSLATION_ALIST: [(&str, &str); 8] = [
("DATA", "NAME"), ("DATA", "NAME"),
("LABEL", "NAME"), ("LABEL", "NAME"),
("RESNAME", "NAME"), ("RESNAME", "NAME"),

View File

@ -12,7 +12,6 @@ use crate::error::CustomError;
use crate::error::Res; use crate::error::Res;
use crate::parser::OrgSource; use crate::parser::OrgSource;
#[derive(Debug)]
pub(crate) enum ContextElement<'r, 's> { pub(crate) enum ContextElement<'r, 's> {
/// Stores a parser that indicates that children should exit upon matching an exit matcher. /// Stores a parser that indicates that children should exit upon matching an exit matcher.
ExitMatcherNode(ExitMatcherNode<'r>), ExitMatcherNode(ExitMatcherNode<'r>),
@ -34,15 +33,6 @@ pub(crate) struct ExitMatcherNode<'r> {
pub(crate) class: ExitClass, pub(crate) class: ExitClass,
} }
impl<'r> std::fmt::Debug for ExitMatcherNode<'r> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut formatter = f.debug_struct("ExitMatcherNode");
formatter.field("class", &self.class.to_string());
formatter.finish()
}
}
#[derive(Debug)]
pub(crate) struct Context<'g, 'r, 's> { pub(crate) struct Context<'g, 'r, 's> {
global_settings: &'g GlobalSettings<'g, 's>, global_settings: &'g GlobalSettings<'g, 's>,
tree: List<'r, &'r ContextElement<'r, 's>>, tree: List<'r, &'r ContextElement<'r, 's>>,

View File

@ -1,13 +1,7 @@
#[derive(Debug, Copy, Clone)] #[derive(Copy, Clone)]
pub(crate) enum ExitClass { pub(crate) enum ExitClass {
Document = 1, Document = 1,
Alpha = 2, Alpha = 2,
Beta = 3, Beta = 3,
Gamma = 4, Gamma = 4,
} }
impl std::fmt::Display for ExitClass {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}

View File

@ -1,17 +1,16 @@
use std::fmt::Debug;
use std::path::PathBuf; use std::path::PathBuf;
#[cfg(any(feature = "compare", feature = "foreign_document_test"))] #[cfg(any(feature = "compare", feature = "foreign_document_test"))]
pub trait FileAccessInterface: Sync + Debug { pub trait FileAccessInterface: Sync {
fn read_file(&self, path: &str) -> Result<String, std::io::Error>; fn read_file(&self, path: &str) -> Result<String, std::io::Error>;
} }
#[cfg(not(any(feature = "compare", feature = "foreign_document_test")))] #[cfg(not(any(feature = "compare", feature = "foreign_document_test")))]
pub trait FileAccessInterface: Debug { pub trait FileAccessInterface {
fn read_file(&self, path: &str) -> Result<String, std::io::Error>; fn read_file(&self, path: &str) -> Result<String, std::io::Error>;
} }
#[derive(Debug, Clone)] #[derive(Clone)]
pub struct LocalFileAccessInterface { pub struct LocalFileAccessInterface {
pub working_directory: Option<PathBuf>, pub working_directory: Option<PathBuf>,
} }

View File

@ -5,16 +5,12 @@ use super::constants::DEFAULT_ORG_ENTITIES;
use super::constants::DEFAULT_ORG_LINK_PARAMETERS; use super::constants::DEFAULT_ORG_LINK_PARAMETERS;
use super::FileAccessInterface; use super::FileAccessInterface;
use super::LocalFileAccessInterface; use super::LocalFileAccessInterface;
use crate::context::constants::DEFAULT_ORG_ELEMENT_AFFILIATED_KEYWORDS;
use crate::context::constants::DEFAULT_ORG_ELEMENT_DUAL_KEYWORDS;
use crate::context::constants::DEFAULT_ORG_ELEMENT_KEYWORD_TRANSLATION_ALIST;
use crate::context::constants::DEFAULT_ORG_ELEMENT_PARSED_KEYWORDS;
use crate::types::IndentationLevel; use crate::types::IndentationLevel;
use crate::types::Object; use crate::types::Object;
// TODO: Ultimately, I think we'll need most of this: https://orgmode.org/manual/In_002dbuffer-Settings.html // TODO: Ultimately, I think we'll need most of this: https://orgmode.org/manual/In_002dbuffer-Settings.html
#[derive(Debug, Clone)] #[derive(Clone)]
pub struct GlobalSettings<'g, 's> { pub struct GlobalSettings<'g, 's> {
pub radio_targets: Vec<&'g Vec<Object<'s>>>, pub radio_targets: Vec<&'g Vec<Object<'s>>>,
pub file_access: &'g dyn FileAccessInterface, pub file_access: &'g dyn FileAccessInterface,
@ -58,26 +54,6 @@ pub struct GlobalSettings<'g, 's> {
/// ///
/// Corresponds to org-entities elisp variable. /// Corresponds to org-entities elisp variable.
pub entities: &'g [EntityDefinition<'s>], pub entities: &'g [EntityDefinition<'s>],
/// Keywords that contain the standard set of objects (excluding footnote references).
///
/// Corresponds to org-element-parsed-keywords elisp variable.
pub element_parsed_keywords: &'g [&'s str],
/// Keywords that can have a secondary value in square brackets.
///
/// Corresponds to org-element-dual-keywords elisp variable.
pub element_dual_keywords: &'g [&'s str],
/// Keywords that can be affiliated with an element.
///
/// Corresponds to org-element-affiliated-keywords elisp variable.
pub element_affiliated_keywords: &'g [&'s str],
/// Mapping of keyword names.
///
/// Corresponds to org-element-keyword-translation-alist elisp variable.
pub element_keyword_translation_alist: &'g [(&'s str, &'s str)],
} }
pub const DEFAULT_TAB_WIDTH: IndentationLevel = 8; pub const DEFAULT_TAB_WIDTH: IndentationLevel = 8;
@ -112,10 +88,6 @@ impl<'g, 's> GlobalSettings<'g, 's> {
link_parameters: &DEFAULT_ORG_LINK_PARAMETERS, link_parameters: &DEFAULT_ORG_LINK_PARAMETERS,
link_templates: BTreeMap::new(), link_templates: BTreeMap::new(),
entities: &DEFAULT_ORG_ENTITIES, entities: &DEFAULT_ORG_ENTITIES,
element_parsed_keywords: &DEFAULT_ORG_ELEMENT_PARSED_KEYWORDS,
element_dual_keywords: &DEFAULT_ORG_ELEMENT_DUAL_KEYWORDS,
element_affiliated_keywords: &DEFAULT_ORG_ELEMENT_AFFILIATED_KEYWORDS,
element_keyword_translation_alist: &DEFAULT_ORG_ELEMENT_KEYWORD_TRANSLATION_ALIST,
} }
} }
} }
@ -126,7 +98,7 @@ impl<'g, 's> Default for GlobalSettings<'g, 's> {
} }
} }
#[derive(Debug, Clone, PartialEq, Default)] #[derive(Clone, PartialEq, Default)]
pub enum HeadlineLevelFilter { pub enum HeadlineLevelFilter {
Odd, Odd,

View File

@ -1,7 +1,7 @@
use crate::error::Res; use crate::error::Res;
use crate::parser::OrgSource; use crate::parser::OrgSource;
mod constants; pub(crate) mod constants;
#[allow(clippy::module_inception)] #[allow(clippy::module_inception)]
mod context; mod context;
mod exiting; mod exiting;

View File

@ -19,29 +19,27 @@ use super::object_parser::standard_set_object;
use super::util::confine_context; use super::util::confine_context;
use super::OrgSource; use super::OrgSource;
use crate::context::bind_context; use crate::context::bind_context;
use crate::context::constants::ORG_ELEMENT_DUAL_KEYWORDS;
use crate::context::constants::ORG_ELEMENT_KEYWORD_TRANSLATION_ALIST;
use crate::context::constants::ORG_ELEMENT_PARSED_KEYWORDS;
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::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( #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
feature = "tracing", pub(crate) fn affiliated_keywords<'s>(
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>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Vec<Keyword<'s>>> { ) -> Res<OrgSource<'s>, Vec<Keyword<'s>>> {
let mut ret = Vec::new(); let mut ret = Vec::new();
let mut remaining = input; let mut remaining = input;
loop { loop {
let result = affiliated_keyword(context, remaining); let result = affiliated_keyword(remaining);
match result { match result {
Ok((remain, kw)) => { Ok((remain, kw)) => {
remaining = remain; remaining = remain;
@ -65,8 +63,8 @@ where
{ {
let mut ret = BTreeMap::new(); let mut ret = BTreeMap::new();
for kw in input { for kw in input {
let translated_name = translate_name(global_settings, kw.key); let translated_name = translate_name(kw.key);
let keyword_type = identify_keyword_type(global_settings, translated_name.as_str()); let keyword_type = identify_keyword_type(translated_name.as_str());
match keyword_type { match keyword_type {
AffiliatedKeywordType::SingleString => { AffiliatedKeywordType::SingleString => {
ret.insert( ret.insert(
@ -151,12 +149,12 @@ where
AffiliatedKeywords { keywords: ret } AffiliatedKeywords { keywords: ret }
} }
fn translate_name<'g, 's>(global_settings: &'g GlobalSettings<'g, 's>, name: &'s str) -> String { fn translate_name(name: &str) -> String {
let name_until_optval = name let name_until_optval = name
.split_once('[') .split_once('[')
.map(|(before, _after)| before) .map(|(before, _after)| before)
.unwrap_or(name); .unwrap_or(name);
for (src, dst) in global_settings.element_keyword_translation_alist { for (src, dst) in ORG_ELEMENT_KEYWORD_TRANSLATION_ALIST {
if name_until_optval.eq_ignore_ascii_case(src) { if name_until_optval.eq_ignore_ascii_case(src) {
return dst.to_lowercase(); return dst.to_lowercase();
} }
@ -171,20 +169,15 @@ enum AffiliatedKeywordType {
ObjectTree, ObjectTree,
} }
fn identify_keyword_type<'g, 's>( fn identify_keyword_type(name: &str) -> AffiliatedKeywordType {
global_settings: &'g GlobalSettings<'g, 's>,
name: &'s str,
) -> AffiliatedKeywordType {
let is_multiple = ["CAPTION", "HEADER"] let is_multiple = ["CAPTION", "HEADER"]
.into_iter() .into_iter()
.any(|candidate| name.eq_ignore_ascii_case(candidate)) .any(|candidate| name.eq_ignore_ascii_case(candidate))
|| name.to_lowercase().starts_with("attr_"); || name.to_lowercase().starts_with("attr_");
let is_parsed = global_settings let is_parsed = ORG_ELEMENT_PARSED_KEYWORDS
.element_parsed_keywords
.iter() .iter()
.any(|candidate| name.eq_ignore_ascii_case(candidate)); .any(|candidate| name.eq_ignore_ascii_case(candidate));
let can_have_optval = global_settings let can_have_optval = ORG_ELEMENT_DUAL_KEYWORDS
.element_dual_keywords
.iter() .iter()
.any(|candidate| name.eq_ignore_ascii_case(candidate)); .any(|candidate| name.eq_ignore_ascii_case(candidate));
match (is_multiple, is_parsed, can_have_optval) { match (is_multiple, is_parsed, can_have_optval) {

View File

@ -59,8 +59,7 @@ fn _element<'b, 'g, 'r, 's>(
) -> Res<OrgSource<'s>, Element<'s>> { ) -> Res<OrgSource<'s>, Element<'s>> {
#[cfg(feature = "event_count")] #[cfg(feature = "event_count")]
record_event(EventType::ElementStart, input); record_event(EventType::ElementStart, input);
let (post_affiliated_keywords_input, affiliated_keywords) = let (post_affiliated_keywords_input, affiliated_keywords) = affiliated_keywords(input)?;
affiliated_keywords(context, input)?;
let mut affiliated_keywords = affiliated_keywords.into_iter(); let mut affiliated_keywords = affiliated_keywords.into_iter();
@ -277,8 +276,7 @@ fn _detect_element<'b, 'g, 'r, 's>(
input: OrgSource<'s>, input: OrgSource<'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(input)?;
affiliated_keywords(context, input)?;
let mut affiliated_keywords = affiliated_keywords.into_iter(); let mut affiliated_keywords = affiliated_keywords.into_iter();

View File

@ -84,7 +84,10 @@ fn in_buffer_settings_key<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSou
))(input) ))(input)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(level = "debug"))] #[cfg_attr(
feature = "tracing",
tracing::instrument(level = "debug", skip(original_settings))
)]
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>,

View File

@ -21,7 +21,8 @@ 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 super::util::org_line_ending;
use crate::context::bind_context; use crate::context::constants::ORG_ELEMENT_AFFILIATED_KEYWORDS;
use crate::context::constants::ORG_ELEMENT_DUAL_KEYWORDS;
use crate::context::RefContext; use crate::context::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::Res; use crate::error::Res;
@ -98,11 +99,8 @@ where
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn affiliated_keyword<'b, 'g, 'r, 's>( pub(crate) fn affiliated_keyword<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Keyword<'s>> {
context: RefContext<'b, 'g, 'r, 's>, filtered_keyword(affiliated_key)(input)
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Keyword<'s>> {
filtered_keyword(bind_context!(affiliated_key, context))(input)
} }
#[cfg_attr( #[cfg_attr(
@ -137,24 +135,18 @@ fn regular_keyword_key<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn affiliated_key<'b, 'g, 'r, 's>( fn affiliated_key<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
context: RefContext<'b, 'g, 'r, 's>, element!(dual_affiliated_key, input);
input: OrgSource<'s>, element!(plain_affiliated_key, input);
) -> Res<OrgSource<'s>, OrgSource<'s>> {
element!(dual_affiliated_key, context, input);
element!(plain_affiliated_key, context, input);
element!(export_keyword, input); element!(export_keyword, input);
Err(nom::Err::Error(CustomError::Static("No affiliated key."))) Err(nom::Err::Error(CustomError::Static("No affiliated key.")))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn plain_affiliated_key<'b, 'g, 'r, 's>( fn plain_affiliated_key<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
context: RefContext<'b, 'g, 'r, 's>, for keyword in ORG_ELEMENT_AFFILIATED_KEYWORDS {
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
for keyword in context.get_global_settings().element_affiliated_keywords {
let result = map( let result = map(
tuple((tag_no_case::<_, _, CustomError>(*keyword), peek(tag(":")))), tuple((tag_no_case::<_, _, CustomError>(keyword), peek(tag(":")))),
|(key, _)| key, |(key, _)| key,
)(input); )(input);
if let Ok((remaining, ent)) = result { if let Ok((remaining, ent)) = result {
@ -166,13 +158,10 @@ fn plain_affiliated_key<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn dual_affiliated_key<'b, 'g, 'r, 's>( fn dual_affiliated_key<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
context: RefContext<'b, 'g, 'r, 's>, for keyword in ORG_ELEMENT_DUAL_KEYWORDS {
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
for keyword in context.get_global_settings().element_dual_keywords {
let result = recognize(tuple(( let result = recognize(tuple((
tag_no_case::<_, _, CustomError>(*keyword), tag_no_case::<_, _, CustomError>(keyword),
tag("["), tag("["),
optval, optval,
tag("]"), tag("]"),
@ -232,19 +221,12 @@ mod tests {
use test::Bencher; use test::Bencher;
use super::*; use super::*;
use crate::context::Context;
use crate::context::ContextElement;
use crate::context::GlobalSettings;
use crate::context::List;
use crate::parser::OrgSource; use crate::parser::OrgSource;
#[bench] #[bench]
fn bench_affiliated_keyword(b: &mut Bencher) { fn bench_affiliated_keyword(b: &mut Bencher) {
let input = OrgSource::new("#+CAPTION[*foo*]: bar *baz*"); let input = OrgSource::new("#+CAPTION[*foo*]: bar *baz*");
let global_settings = GlobalSettings::default();
let initial_context = ContextElement::document_context();
let initial_context = Context::new(&global_settings, List::new(&initial_context));
b.iter(|| assert!(affiliated_keyword(&initial_context, input).is_ok())); b.iter(|| assert!(affiliated_keyword(input).is_ok()));
} }
} }