51 Commits

Author SHA1 Message Date
Tom Alexander
ee201e1336 Merge branch 'explicit_all_node_iter'
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-23 18:39:16 -04:00
Tom Alexander
4897952330 Make creating AllAstNodeIter explicit.
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
This is to remove the ambiguity between calling iter on the specific structs like Document and calling iter on an AstNode by having an explicitly-named function to create the iterator.
2023-10-23 18:25:59 -04:00
Tom Alexander
e1d85c6dc2 Merge branch 'remove_set_source'
All checks were successful
rustfmt Build rustfmt has succeeded
clippy Build clippy has succeeded
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-test Build rust-test has succeeded
2023-10-23 18:06:56 -04:00
Tom Alexander
c420ccd029 Fix clippy errors.
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-23 17:43:43 -04:00
Tom Alexander
a880629831 Make clippy not write to the host git repo. 2023-10-23 17:43:32 -04:00
Tom Alexander
5e2dea1f28 Remove the SetSource trait.
It was only being used for creating paragraphs of specific text, so I just adjusted the of_text function to handle it.
2023-10-23 17:43:32 -04:00
Tom Alexander
f47d688be4 Remove owned String from CustomError.
Some checks failed
rustfmt Build rustfmt has failed
rust-test Build rust-test has failed
clippy Build clippy has failed
rust-build Build rust-build has failed
rust-foreign-document-test Build rust-foreign-document-test has succeeded
This is a 15% performance improvement.
2023-10-21 14:29:37 -04:00
Tom Alexander
acfc5e5e68 Only allocate memory when unquoting sexp string that contains escapes.
All checks were successful
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-test Build rust-test has succeeded
clippy Build clippy has succeeded
rustfmt Build rustfmt has succeeded
If the quoted string contains no escape sequences, then unquoting the string can be done by simply shaving off the leading and trailing quotation marks which can be a slice operation. By returning Cow, we can return either a borrowed slice or an owned String.
2023-10-20 12:53:27 -04:00
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
Tom Alexander
c58b0e7c35 Add a script to dump an AST using docker.
All checks were successful
rust-test Build rust-test has succeeded
clippy Build clippy has succeeded
rustfmt Build rustfmt has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-build Build rust-build has succeeded
2023-10-18 15:39:52 -04:00
Tom Alexander
f19d262825 Merge branch 'bullshitium'
All checks were successful
rustfmt Build rustfmt has succeeded
clippy Build clippy has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
2023-10-18 13:04:16 -04:00
Tom Alexander
68f3f2e159 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 12:42:09 -04:00
Tom Alexander
269e23c1b1 No more expect-fail tests! 2023-10-18 12:41:12 -04:00
Tom Alexander
e111b8b9b8 Performance optimization. 2023-10-18 12:39:08 -04:00
Tom Alexander
353ff07420 Handle bullshitium for broken dynamic blocks. 2023-10-18 12:32:48 -04:00
Tom Alexander
94dec31130 Consuming trailing whitespace for 🔚 bullshitium. 2023-10-18 12:17:57 -04:00
Tom Alexander
cf5d3ed745 Add tests for the 🔚 bullshitium. 2023-10-18 11:59:55 -04:00
Tom Alexander
b0b287cd47 Handle bullshitium for 🔚. 2023-10-18 11:57:39 -04:00
Tom Alexander
bcdf1f5e9d Merge branch 'entity_special_case'
All checks were successful
rustfmt Build rustfmt has succeeded
clippy Build clippy has succeeded
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-test Build rust-test has succeeded
2023-10-18 08:53:57 -04:00
Tom Alexander
17d8e76e05 Do not match POST for entities that end with a space.
This is a special case for en-spaces.
2023-10-18 08:52:18 -04:00
Tom Alexander
8db9038c53 Merge branch 'list_perf_improvement'
Some checks failed
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-test Build rust-test has failed
rustfmt Build rustfmt has succeeded
clippy Build clippy has succeeded
2023-10-18 08:40:19 -04:00
Tom Alexander
a276ba70e0 Fix empty content items with final item whitespace cut-off before headlines.
Some checks failed
clippy Build clippy has succeeded
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
2023-10-17 15:56:02 -04:00
Tom Alexander
b7442c1e92 Do not match headlines as plain list items. 2023-10-17 15:35:43 -04:00
Tom Alexander
364ba79517 It actually worked on trailing whitespace ownership test case 2. 2023-10-17 15:22:31 -04:00
Tom Alexander
47408763e5 A first stab at a final item whitespace cut-off exit matcher. 2023-10-17 15:08:36 -04:00
Tom Alexander
bd187ebfe7 Remove re-parsing of the final list child. 2023-10-17 14:17:47 -04:00
Tom Alexander
59cb3c2bbf Remove unnecessary closures in plain lists. 2023-10-17 13:59:33 -04:00
Tom Alexander
44f7412a5c Merge branch 'perf_improvement'
Some checks failed
rustfmt Build rustfmt has succeeded
clippy Build clippy has succeeded
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-test Build rust-test has succeeded
2023-10-17 13:54:44 -04:00
Tom Alexander
01464057ad Remove unused event types. 2023-10-17 13:43:33 -04:00
Tom Alexander
0208020e3e Also print byte offset.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
2023-10-17 13:35:40 -04:00
Tom Alexander
a2f53361eb Record element start events and report them when the event_count feature is enabled. 2023-10-17 13:32:01 -04:00
Tom Alexander
17db05c2c7 Unify more error handling. 2023-10-17 12:42:34 -04:00
Tom Alexander
6139ea328d Unify some more error handling. 2023-10-17 12:22:52 -04:00
Tom Alexander
d20b4a410b Remove pointless map_err calls. 2023-10-17 11:56:36 -04:00
Tom Alexander
05c64f53b1 Remove boxed error from CustomError. 2023-10-17 11:40:11 -04:00
Tom Alexander
f65d0bb82d Remove redundant call to space0. 2023-10-17 11:33:26 -04:00
Tom Alexander
50d2831081 Cleanup. 2023-10-17 11:30:23 -04:00
Tom Alexander
bc9bd4f97b Eliminate some closures. 2023-10-17 11:10:18 -04:00
Tom Alexander
369d3e8c50 Add a full-document parse benchmark. 2023-10-17 10:57:04 -04:00
Tom Alexander
7d73eb6bd4 Merge branch 'error_rework'
Some checks failed
clippy Build clippy has succeeded
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has failed
rustfmt Build rustfmt has failed
rust-test Build rust-test has succeeded
2023-10-17 10:40:46 -04:00
Tom Alexander
f59f153ee7 Clean up. 2023-10-17 10:39:21 -04:00
Tom Alexander
20c4a0f8f7 Continue removing MyError. 2023-10-17 10:35:33 -04:00
Tom Alexander
e776a051ad Continue removing MyError. 2023-10-17 10:13:00 -04:00
Tom Alexander
77e6c22ad8 Continue removing MyError. 2023-10-17 10:09:37 -04:00
Tom Alexander
c9d7251e3b Begin removing the MyError type. 2023-10-17 09:45:18 -04:00
Tom Alexander
8417b5fc9d Add an owned string entry for CustomError. 2023-10-17 09:27:15 -04:00
67 changed files with 889 additions and 576 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"
@@ -59,6 +59,7 @@ 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

@@ -45,10 +45,6 @@ dockerclippy:
clippy: clippy:
> cargo clippy --no-deps --all-targets --all-features -- -D warnings > cargo clippy --no-deps --all-targets --all-features -- -D warnings
.PHONY: clippyfix
clippyfix:
> cargo clippy --fix --lib -p organic --all-features
.PHONY: test .PHONY: test
test: test:
> cargo test --no-default-features --features compare --no-fail-fast --lib --test test_loader -- --test-threads $(TESTJOBS) > cargo test --no-default-features --features compare --no-fail-fast --lib --test test_loader -- --test-threads $(TESTJOBS)

View File

@@ -66,10 +66,6 @@ fn write_test(test_file: &mut File, test: &walkdir::DirEntry) {
} }
#[cfg(feature = "compare")] #[cfg(feature = "compare")]
fn is_expect_fail(name: &str) -> Option<&str> { fn is_expect_fail(_name: &str) -> Option<&str> {
match name { None
"greater_element_drawer_drawer_with_headline_inside" => Some("Apparently lines with :end: become their own paragraph. This odd behavior needs to be investigated more."),
"element_container_priority_footnote_definition_dynamic_block" => Some("Apparently broken begin lines become their own paragraph."),
_ => None,
}
} }

View File

@@ -25,13 +25,13 @@ ifdef REMOTE_REPO
else else
@echo "REMOTE_REPO not defined, not removing from remote repo." @echo "REMOTE_REPO not defined, not removing from remote repo."
endif endif
docker volume rm cargo-cache docker volume rm rust-cache cargo-cache
# NOTE: This target will write to folders underneath the git-root # NOTE: This target will write to folders underneath the git-root
.PHONY: run .PHONY: run
run: build run: build
docker run --rm --init --read-only --mount type=tmpfs,destination=/tmp -v "$$(readlink -f ../../):/source" --workdir=/source --mount source=cargo-cache,target=/usr/local/cargo/registry $(IMAGE_NAME) docker run --rm --init --read-only --mount type=tmpfs,destination=/tmp -v "$$(readlink -f ../../):/source:ro" --workdir=/source --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target $(IMAGE_NAME)
.PHONY: shell .PHONY: shell
shell: build shell: build
docker run --rm -i -t --entrypoint /bin/sh --mount type=tmpfs,destination=/tmp -v "$$(readlink -f ../../):/source" --workdir=/source --mount source=cargo-cache,target=/usr/local/cargo/registry $(IMAGE_NAME) docker run --rm -i -t --entrypoint /bin/sh --mount type=tmpfs,destination=/tmp -v "$$(readlink -f ../../):/source:ro" --workdir=/source --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target $(IMAGE_NAME)

View File

@@ -0,0 +1,3 @@
foo
:end:
bar

View File

@@ -0,0 +1,2 @@
foo
:end:

58
scripts/dump_ast.bash Executable file
View File

@@ -0,0 +1,58 @@
#!/usr/bin/env bash
#
# Dump the AST of an org-mode document from emacs
set -euo pipefail
IFS=$'\n\t'
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
REALPATH=$(command -v uu-realpath || command -v realpath)
MAKE=$(command -v gmake || command -v make)
############## Setup #########################
function die {
local status_code="$1"
shift
(>&2 echo "${@}")
exit "$status_code"
}
function log {
(>&2 echo "${@}")
}
############## Program #########################
function main {
if [ $# -eq 0 ]; then
dump_ast_stdin "${@}"
else
dump_ast_file "${@}"
fi
}
function dump_ast_stdin {
# Until we can find a good way to encode stdin as an elisp string in bash, I cannot operate on stdin.
die 1 "This script only works on files."
}
function dump_ast_file {
local target_file mounted_file elisp_script
target_file=$($REALPATH "$1")
mounted_file="/input${target_file}"
elisp_script=$(cat <<EOF
(progn
(erase-buffer)
(require 'org)
(defun org-table-align () t)
(find-file-read-only "${mounted_file}")
(org-mode)
(message "%s" (pp-to-string (org-element-parse-buffer)))
)
EOF
)
exec docker run --init --rm -i --mount type=tmpfs,destination=/tmp -v "/:/input:ro" --entrypoint "" organic-test emacs -q --no-site-file --no-splash --batch --eval "$elisp_script"
}
main "${@}"

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=2000 --call-graph dwarf --output="$DIR/../perf.data" "$DIR/../target/${PROFILE}/parse" "${@}" perf record --freq=70000 --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

@@ -1,3 +1,5 @@
use std::borrow::Borrow;
use std::borrow::Cow;
use std::collections::BTreeSet; use std::collections::BTreeSet;
use std::fmt::Debug; use std::fmt::Debug;
use std::str::FromStr; use std::str::FromStr;
@@ -262,11 +264,11 @@ pub(crate) fn compare_property_set_of_quoted_string<
.iter() .iter()
.map(|e| e.as_atom()) .map(|e| e.as_atom())
.collect::<Result<Vec<_>, _>>()?; .collect::<Result<Vec<_>, _>>()?;
let value: Vec<String> = value let value: Vec<Cow<'_, str>> = value
.into_iter() .into_iter()
.map(unquote) .map(unquote)
.collect::<Result<Vec<_>, _>>()?; .collect::<Result<Vec<_>, _>>()?;
let value: BTreeSet<&str> = value.iter().map(|e| e.as_str()).collect(); let value: BTreeSet<&str> = value.iter().map(|e| e.borrow()).collect();
let mismatched: Vec<_> = value.symmetric_difference(&rust_value).copied().collect(); let mismatched: Vec<_> = value.symmetric_difference(&rust_value).copied().collect();
if !mismatched.is_empty() { if !mismatched.is_empty() {
let this_status = DiffStatus::Bad; let this_status = DiffStatus::Bad;
@@ -653,7 +655,7 @@ pub(crate) fn compare_property_number_lines<
(Some(number_lines), Some(rust_number_lines)) => { (Some(number_lines), Some(rust_number_lines)) => {
let token_list = number_lines.as_list()?; let token_list = number_lines.as_list()?;
let number_type = token_list let number_type = token_list
.get(0) .first()
.map(Token::as_atom) .map(Token::as_atom)
.map_or(Ok(None), |r| r.map(Some))? .map_or(Ok(None), |r| r.map(Some))?
.ok_or(":number-lines should have a type.")?; .ok_or(":number-lines should have a type.")?;

View File

@@ -2153,7 +2153,7 @@ fn compare_plain_text<'b, 's>(
let text = emacs.as_text()?; let text = emacs.as_text()?;
let start_ind: usize = text let start_ind: usize = text
.properties .properties
.get(0) .first()
.expect("Should have start index.") .expect("Should have start index.")
.as_atom()? .as_atom()?
.parse()?; .parse()?;

View File

@@ -1,3 +1,4 @@
use std::borrow::Cow;
use std::collections::HashMap; use std::collections::HashMap;
use nom::branch::alt; use nom::branch::alt;
@@ -36,12 +37,6 @@ pub struct TextWithProperties<'s> {
pub(crate) properties: Vec<Token<'s>>, pub(crate) properties: Vec<Token<'s>>,
} }
enum ParseState {
Normal,
Escape,
Octal(Vec<u8>),
}
impl<'s> Token<'s> { impl<'s> Token<'s> {
pub(crate) fn as_vector<'p>( pub(crate) fn as_vector<'p>(
&'p self, &'p self,
@@ -117,8 +112,27 @@ fn get_consumed<'s>(input: &'s str, remaining: &'s str) -> &'s str {
&input[..offset] &input[..offset]
} }
pub(crate) fn unquote(text: &str) -> Result<String, Box<dyn std::error::Error>> { #[derive(Debug)]
let mut out: Vec<u8> = Vec::with_capacity(text.len()); enum UnquoteState {
Normal,
Escape,
HasEscape {
out: Vec<u8>,
},
HasEscapeEscape {
out: Vec<u8>,
},
Octal {
octal_begin_offset: usize,
octal: Vec<u8>,
},
HasEscapeOctal {
out: Vec<u8>,
octal: Vec<u8>,
},
}
pub(crate) fn unquote(text: &str) -> Result<Cow<'_, str>, Box<dyn std::error::Error>> {
if !text.starts_with('"') { if !text.starts_with('"') {
return Err("Quoted text does not start with quote.".into()); return Err("Quoted text does not start with quote.".into());
} }
@@ -126,54 +140,143 @@ pub(crate) fn unquote(text: &str) -> Result<String, Box<dyn std::error::Error>>
return Err("Quoted text does not end with quote.".into()); return Err("Quoted text does not end with quote.".into());
} }
let interior_text = &text[1..(text.len() - 1)]; let interior_text = &text[1..(text.len() - 1)];
let mut state = ParseState::Normal; let mut state = UnquoteState::Normal;
for current_char in interior_text.bytes() { for (offset, current_char) in interior_text.bytes().enumerate() {
// Check to see if octal finished // Check to see if octal finished
state = match (state, current_char) { state = match (state, current_char) {
(ParseState::Octal(octal), b'0'..=b'7') if octal.len() < MAX_OCTAL_LENGTH => { (
ParseState::Octal(octal) UnquoteState::Octal {
octal_begin_offset,
octal,
},
b'0'..=b'7',
) if octal.len() < MAX_OCTAL_LENGTH => UnquoteState::Octal {
octal_begin_offset,
octal,
},
(
UnquoteState::Octal {
octal_begin_offset,
octal,
},
_,
) => {
let octal_number_string = String::from_utf8(octal)?;
let decoded_byte = u8::from_str_radix(&octal_number_string, 8)?;
let mut out: Vec<u8> = Vec::with_capacity(interior_text.len());
out.extend_from_slice(&interior_text.as_bytes()[..octal_begin_offset]);
out.push(decoded_byte);
UnquoteState::HasEscape { out }
} }
(ParseState::Octal(octal), _) => { (UnquoteState::HasEscapeOctal { out, octal }, b'0'..=b'7')
if octal.len() < MAX_OCTAL_LENGTH =>
{
UnquoteState::HasEscapeOctal { out, octal }
}
(UnquoteState::HasEscapeOctal { mut out, octal }, _) => {
let octal_number_string = String::from_utf8(octal)?; let octal_number_string = String::from_utf8(octal)?;
let decoded_byte = u8::from_str_radix(&octal_number_string, 8)?; let decoded_byte = u8::from_str_radix(&octal_number_string, 8)?;
out.push(decoded_byte); out.push(decoded_byte);
ParseState::Normal UnquoteState::HasEscape { out }
} }
(state, _) => state, (state, _) => state,
}; };
state = match (state, current_char) { state = match (state, current_char) {
(ParseState::Normal, b'\\') => ParseState::Escape, (UnquoteState::Normal, b'\\') => UnquoteState::Escape,
(ParseState::Normal, _) => { (UnquoteState::Normal, _) => UnquoteState::Normal,
(UnquoteState::HasEscape { out }, b'\\') => UnquoteState::HasEscapeEscape { out },
(UnquoteState::HasEscape { mut out }, _) => {
out.push(current_char); out.push(current_char);
ParseState::Normal UnquoteState::HasEscape { out }
} }
(ParseState::Escape, b'n') => { (UnquoteState::Escape, b'n') => {
let mut out: Vec<u8> = Vec::with_capacity(interior_text.len());
// Subtract 1 from offset to account for backslash.
out.extend_from_slice(&interior_text.as_bytes()[..(offset - 1)]);
out.push(b'\n'); out.push(b'\n');
ParseState::Normal UnquoteState::HasEscape { out }
} }
(ParseState::Escape, b'\\') => { (UnquoteState::HasEscapeEscape { mut out }, b'n') => {
out.push(b'\n');
UnquoteState::HasEscape { out }
}
(UnquoteState::Escape, b'\\') => {
let mut out: Vec<u8> = Vec::with_capacity(interior_text.len());
// Subtract 1 from offset to account for backslash.
out.extend_from_slice(&interior_text.as_bytes()[..(offset - 1)]);
out.push(b'\\'); out.push(b'\\');
ParseState::Normal UnquoteState::HasEscape { out }
} }
(ParseState::Escape, b'"') => { (UnquoteState::HasEscapeEscape { mut out }, b'\\') => {
out.push(b'\\');
UnquoteState::HasEscape { out }
}
(UnquoteState::Escape, b'"') => {
let mut out: Vec<u8> = Vec::with_capacity(interior_text.len());
// Subtract 1 from offset to account for backslash.
out.extend_from_slice(&interior_text.as_bytes()[..(offset - 1)]);
out.push(b'"'); out.push(b'"');
ParseState::Normal UnquoteState::HasEscape { out }
} }
(ParseState::Escape, b'0'..=b'7') => { (UnquoteState::HasEscapeEscape { mut out }, b'"') => {
out.push(b'"');
UnquoteState::HasEscape { out }
}
(UnquoteState::Escape, b'0'..=b'7') => {
let mut octal = Vec::with_capacity(MAX_OCTAL_LENGTH); let mut octal = Vec::with_capacity(MAX_OCTAL_LENGTH);
octal.push(current_char); octal.push(current_char);
ParseState::Octal(octal) // Substract 1 from offset to account for backslash
UnquoteState::Octal {
octal_begin_offset: offset - 1,
octal,
} }
(ParseState::Octal(mut octal), b'0'..=b'7') => { }
(UnquoteState::HasEscapeEscape { out }, b'0'..=b'7') => {
let mut octal = Vec::with_capacity(MAX_OCTAL_LENGTH);
octal.push(current_char); octal.push(current_char);
ParseState::Octal(octal) // Substract 1 from offset to account for backslash
UnquoteState::HasEscapeOctal { out, octal }
} }
_ => panic!("Invalid state unquoting string."), (
UnquoteState::Octal {
octal_begin_offset,
mut octal,
},
b'0'..=b'7',
) => {
octal.push(current_char);
UnquoteState::Octal {
octal_begin_offset,
octal,
}
}
(UnquoteState::HasEscapeOctal { out, mut octal }, b'0'..=b'7') => {
octal.push(current_char);
UnquoteState::HasEscapeOctal { out, octal }
}
(state, _) => panic!(
"Invalid state unquoting string: {:?} | {} | {:?}",
state, offset, interior_text
),
}; };
} }
Ok(String::from_utf8(out)?) match state {
UnquoteState::Normal | UnquoteState::Escape | UnquoteState::Octal { .. } => {
Ok(Cow::Borrowed(interior_text))
}
UnquoteState::HasEscape { out } => Ok(Cow::Owned(String::from_utf8(out)?)),
UnquoteState::HasEscapeEscape { mut out } => {
out.push(b'\\');
Ok(Cow::Owned(String::from_utf8(out)?))
}
UnquoteState::HasEscapeOctal { mut out, octal } => {
out.push(b'\\');
out.extend(octal);
Ok(Cow::Owned(String::from_utf8(out)?))
}
}
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]

View File

@@ -1,3 +1,4 @@
use std::borrow::Cow;
use std::str::FromStr; use std::str::FromStr;
use super::compare_field::compare_property_list_of_quoted_string; use super::compare_field::compare_property_list_of_quoted_string;
@@ -206,10 +207,10 @@ pub(crate) fn get_property_unquoted_atom<'s>(
/// Get a named property containing an quoted string from the emacs token. /// Get a named property containing an quoted string from the emacs token.
/// ///
/// Returns None if key is not found. /// Returns None if key is not found.
pub(crate) fn get_property_quoted_string( pub(crate) fn get_property_quoted_string<'s>(
emacs: &Token<'_>, emacs: &Token<'s>,
key: &str, key: &str,
) -> Result<Option<String>, Box<dyn std::error::Error>> { ) -> Result<Option<Cow<'s, str>>, Box<dyn std::error::Error>> {
get_property(emacs, key)? get_property(emacs, key)?
.map(Token::as_atom) .map(Token::as_atom)
.map_or(Ok(None), |r| r.map(Some))? .map_or(Ok(None), |r| r.map(Some))?

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

@@ -9,11 +9,9 @@ use super::list::List;
use super::DynContextMatcher; use super::DynContextMatcher;
use super::RefContext; use super::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError;
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>),
@@ -35,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>>,
@@ -108,7 +97,7 @@ impl<'g, 'r, 's> Context<'g, 'r, 's> {
pub(crate) fn check_exit_matcher( pub(crate) fn check_exit_matcher(
&'r self, &'r self,
i: OrgSource<'s>, i: OrgSource<'s>,
) -> IResult<OrgSource<'s>, OrgSource<'s>, CustomError<OrgSource<'s>>> { ) -> IResult<OrgSource<'s>, OrgSource<'s>, CustomError> {
let mut current_class_filter = ExitClass::Gamma; let mut current_class_filter = ExitClass::Gamma;
for current_node in self.iter_context() { for current_node in self.iter_context() {
let context_element = current_node.get_data(); let context_element = current_node.get_data();
@@ -123,7 +112,7 @@ impl<'g, 'r, 's> Context<'g, 'r, 's> {
} }
} }
// TODO: Make this a specific error instead of just a generic MyError // TODO: Make this a specific error instead of just a generic MyError
return Err(nom::Err::Error(CustomError::MyError(MyError("NoExit")))); return Err(nom::Err::Error(CustomError::Static("NoExit")));
} }
/// Indicates if elements should consume the whitespace after them. /// Indicates if elements should consume the whitespace after them.

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

@@ -2,22 +2,18 @@ use nom::error::ErrorKind;
use nom::error::ParseError; use nom::error::ParseError;
use nom::IResult; use nom::IResult;
pub(crate) type Res<T, U> = IResult<T, U, CustomError<T>>; pub(crate) type Res<T, U> = IResult<T, U, CustomError>;
#[derive(Debug)] #[derive(Debug)]
pub enum CustomError<I> { pub enum CustomError {
MyError(MyError<&'static str>), Static(&'static str),
Nom(I, ErrorKind),
IO(std::io::Error), IO(std::io::Error),
BoxedError(Box<dyn std::error::Error>), Parser(ErrorKind),
} }
#[derive(Debug)] impl<I: std::fmt::Debug> ParseError<I> for CustomError {
pub struct MyError<I>(pub(crate) I); fn from_error_kind(_input: I, kind: ErrorKind) -> Self {
CustomError::Parser(kind)
impl<I> ParseError<I> for CustomError<I> {
fn from_error_kind(input: I, kind: ErrorKind) -> Self {
CustomError::Nom(input, kind)
} }
fn append(_input: I, _kind: ErrorKind, /*mut*/ other: Self) -> Self { fn append(_input: I, _kind: ErrorKind, /*mut*/ other: Self) -> Self {
@@ -26,20 +22,14 @@ impl<I> ParseError<I> for CustomError<I> {
} }
} }
impl<I> From<std::io::Error> for CustomError<I> { impl From<std::io::Error> for CustomError {
fn from(value: std::io::Error) -> Self { fn from(value: std::io::Error) -> Self {
CustomError::IO(value) CustomError::IO(value)
} }
} }
impl<I> From<&'static str> for CustomError<I> { impl From<&'static str> for CustomError {
fn from(value: &'static str) -> Self { fn from(value: &'static str) -> Self {
CustomError::MyError(MyError(value)) CustomError::Static(value)
}
}
impl<I> From<Box<dyn std::error::Error>> for CustomError<I> {
fn from(value: Box<dyn std::error::Error>) -> Self {
CustomError::BoxedError(value)
} }
} }

View File

@@ -1,5 +1,4 @@
#[allow(clippy::module_inception)] #[allow(clippy::module_inception)]
mod error; mod error;
pub(crate) use error::CustomError; pub(crate) use error::CustomError;
pub(crate) use error::MyError;
pub(crate) use error::Res; pub(crate) use error::Res;

View File

@@ -0,0 +1,43 @@
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

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

6
src/event_count/mod.rs Normal file
View File

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

View File

@@ -90,12 +90,11 @@ impl<'r, 's> Iterator for AllAstNodeIter<'r, 's> {
} }
} }
impl<'r, 's> IntoIterator for AstNode<'r, 's> { impl<'r, 's> AstNode<'r, 's> {
type Item = AstNode<'r, 's>; /// Iterate all AST nodes.
///
type IntoIter = AllAstNodeIter<'r, 's>; /// This is different from the iter/into_iter functions which iterate a single level of the children. This iterates the entire tree including returning the root node itself.
pub fn iter_all_ast_nodes(self) -> AllAstNodeIter<'r, 's> {
fn into_iter(self) -> Self::IntoIter {
AllAstNodeIter { AllAstNodeIter {
root: Some(self), root: Some(self),
queue: VecDeque::new(), queue: VecDeque::new(),

View File

@@ -13,6 +13,8 @@ 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,8 +54,11 @@ 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 rust_parsed = parse(org_contents.as_ref())?; let org_contents = 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(())
} }
@@ -75,5 +78,7 @@ 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,17 +14,46 @@ 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::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::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"))]
pub(crate) fn affiliated_keywords<'s>(
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Vec<Keyword<'s>>> {
let mut ret = Vec::new();
let mut remaining = input;
loop {
let result = affiliated_keyword(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,
@@ -34,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(
@@ -120,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();
} }
@@ -140,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

@@ -21,7 +21,6 @@ use super::OrgSource;
use crate::context::Matcher; use crate::context::Matcher;
use crate::context::RefContext; use crate::context::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
use crate::parser::util::get_consumed; use crate::parser::util::get_consumed;
use crate::parser::util::org_line_ending; use crate::parser::util::org_line_ending;
@@ -217,9 +216,7 @@ fn impl_balanced_bracket<
} }
if fail_parser(remaining).is_ok() { if fail_parser(remaining).is_ok() {
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::Static("Fail parser matched.")));
"Fail parser matched.",
))));
} }
let (remain, _) = anychar(remaining)?; let (remain, _) = anychar(remaining)?;

157
src/parser/bullshitium.rs Normal file
View File

@@ -0,0 +1,157 @@
use nom::branch::alt;
use nom::bytes::complete::tag_no_case;
use nom::character::complete::anychar;
use nom::character::complete::space0;
use nom::multi::many_till;
use nom::sequence::tuple;
use super::paragraph::paragraph;
use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
use super::util::org_line_ending;
use super::util::start_of_line;
use super::OrgSource;
use crate::context::bind_context;
use crate::context::RefContext;
use crate::error::CustomError;
use crate::error::Res;
use crate::parser::macros::element;
use crate::types::Object;
use crate::types::Paragraph;
#[cfg_attr(
feature = "tracing",
tracing::instrument(ret, level = "debug", skip(context))
)]
pub(crate) fn bullshitium<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Paragraph<'s>> {
alt((
bind_context!(broken_end, context),
bind_context!(broken_dynamic_block, context),
))(input)
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(ret, level = "debug", skip(context))
)]
pub(crate) fn detect_bullshitium<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ()> {
element!(detect_broken_end, context, input);
element!(detect_broken_dynamic_block, context, input);
Err(nom::Err::Error(CustomError::Static("No bullshitium.")))
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(ret, level = "debug", skip(context))
)]
pub(crate) fn broken_end<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Paragraph<'s>> {
start_of_line(input)?;
let (remaining, _) = space0(input)?;
let (remaining, _) = tag_no_case(":end:")(remaining)?;
let (lead_in_remaining, _) = tuple((space0, org_line_ending))(remaining)?;
if let Ok((remaining, mut paragraph)) =
paragraph(std::iter::empty(), lead_in_remaining, context, input)
{
match paragraph.children.first_mut() {
Some(Object::PlainText(plain_text)) => {
plain_text.source = input.get_until_end_of_str(plain_text.source).into();
}
Some(obj) => {
panic!("Unhandled first object type inside bullshitium {:?}", obj);
}
None => {
unreachable!("Paragraph must have children.");
}
};
Ok((remaining, paragraph))
} else {
let (remaining, _trailing_ws) =
maybe_consume_trailing_whitespace_if_not_exiting(context, lead_in_remaining)?;
Ok((
remaining,
Paragraph::of_text(
input.get_until(remaining).into(),
input.get_until(lead_in_remaining).into(),
),
))
}
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(ret, level = "debug", skip(_context))
)]
pub(crate) fn detect_broken_end<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ()> {
start_of_line(input)?;
let (remaining, _) = space0(input)?;
let (remaining, _) = tag_no_case(":end:")(remaining)?;
let (_remaining, _) = tuple((space0, org_line_ending))(remaining)?;
Ok((input, ()))
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(ret, level = "debug", skip(context))
)]
pub(crate) fn broken_dynamic_block<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Paragraph<'s>> {
start_of_line(input)?;
let (remaining, _) = space0(input)?;
let (remaining, _) = tag_no_case("#+BEGIN:")(remaining)?;
let (lead_in_remaining, _) = many_till(anychar, org_line_ending)(remaining)?;
if let Ok((remaining, mut paragraph)) =
paragraph(std::iter::empty(), lead_in_remaining, context, input)
{
match paragraph.children.first_mut() {
Some(Object::PlainText(plain_text)) => {
plain_text.source = input.get_until_end_of_str(plain_text.source).into();
}
Some(obj) => {
panic!("Unhandled first object type inside bullshitium {:?}", obj);
}
None => {
unreachable!("Paragraph must have children.");
}
};
Ok((remaining, paragraph))
} else {
let (remaining, _trailing_ws) =
maybe_consume_trailing_whitespace_if_not_exiting(context, lead_in_remaining)?;
Ok((
remaining,
Paragraph::of_text(
input.get_until(remaining).into(),
input.get_until(lead_in_remaining).into(),
),
))
}
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(ret, level = "debug", skip(_context))
)]
pub(crate) fn detect_broken_dynamic_block<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ()> {
start_of_line(input)?;
let (remaining, _) = space0(input)?;
let (_remaining, _) = tag_no_case("#+BEGIN:")(remaining)?;
Ok((input, ()))
}

View File

@@ -137,7 +137,7 @@ fn _global_prefix_end<'b, 'g, 'r, 's>(
unreachable!("Exceeded citation global prefix bracket depth.") unreachable!("Exceeded citation global prefix bracket depth.")
} }
if current_depth == 0 { if current_depth == 0 {
let close_bracket = tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>("]")(input); let close_bracket = tag::<_, _, CustomError>("]")(input);
if close_bracket.is_ok() { if close_bracket.is_ok() {
return close_bracket; return close_bracket;
} }
@@ -191,7 +191,7 @@ fn _global_suffix_end<'b, 'g, 'r, 's>(
unreachable!("Exceeded citation global suffix bracket depth.") unreachable!("Exceeded citation global suffix bracket depth.")
} }
if current_depth == 0 { if current_depth == 0 {
let close_bracket = tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>("]")(input); let close_bracket = tag::<_, _, CustomError>("]")(input);
if close_bracket.is_ok() { if close_bracket.is_ok() {
return close_bracket; return close_bracket;
} }
@@ -235,7 +235,7 @@ mod tests {
assert_eq!( assert_eq!(
first_paragraph first_paragraph
.children .children
.get(0) .first()
.expect("Len already asserted to be 1"), .expect("Len already asserted to be 1"),
&Object::Citation(Citation { &Object::Citation(Citation {
source: "[cite:@foo]", source: "[cite:@foo]",

View File

@@ -20,7 +20,6 @@ use crate::context::ExitClass;
use crate::context::ExitMatcherNode; use crate::context::ExitMatcherNode;
use crate::context::RefContext; use crate::context::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
use crate::parser::object_parser::minimal_set_object; use crate::parser::object_parser::minimal_set_object;
use crate::parser::util::exit_matcher_parser; use crate::parser::util::exit_matcher_parser;
@@ -151,7 +150,7 @@ fn _key_prefix_end<'b, 'g, 'r, 's>(
unreachable!("Exceeded citation key prefix bracket depth.") unreachable!("Exceeded citation key prefix bracket depth.")
} }
if current_depth == 0 { if current_depth == 0 {
let close_bracket = tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>("]")(input); let close_bracket = tag::<_, _, CustomError>("]")(input);
if close_bracket.is_ok() { if close_bracket.is_ok() {
return close_bracket; return close_bracket;
} }
@@ -181,7 +180,7 @@ fn _key_suffix_end<'b, 'g, 'r, 's>(
unreachable!("Exceeded citation key suffix bracket depth.") unreachable!("Exceeded citation key suffix bracket depth.")
} }
if current_depth == 0 { if current_depth == 0 {
let close_bracket = tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>("]")(input); let close_bracket = tag::<_, _, CustomError>("]")(input);
if close_bracket.is_ok() { if close_bracket.is_ok() {
return close_bracket; return close_bracket;
} }
@@ -199,9 +198,7 @@ where
let pre_bracket_depth = input.get_bracket_depth(); let pre_bracket_depth = input.get_bracket_depth();
let (remaining, output) = inner(input)?; let (remaining, output) = inner(input)?;
if remaining.get_bracket_depth() - pre_bracket_depth != 0 { if remaining.get_bracket_depth() - pre_bracket_depth != 0 {
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::Static("UnbalancedBrackets")));
"UnbalancedBrackets",
))));
} }
Ok((remaining, output)) Ok((remaining, output))
} }

View File

@@ -19,7 +19,6 @@ use crate::context::parser_with_context;
use crate::context::ContextElement; use crate::context::ContextElement;
use crate::context::RefContext; use crate::context::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
use crate::parser::util::exit_matcher_parser; use crate::parser::util::exit_matcher_parser;
use crate::parser::util::immediate_in_section; use crate::parser::util::immediate_in_section;
@@ -35,9 +34,9 @@ pub(crate) fn comment<'b, 'g, 'r, 's>(
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Comment<'s>> { ) -> Res<OrgSource<'s>, Comment<'s>> {
if immediate_in_section(context, "comment") { if immediate_in_section(context, "comment") {
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::Static(
"Cannot nest objects of the same element", "Cannot nest objects of the same element",
)))); )));
} }
let parser_context = ContextElement::Context("comment"); let parser_context = ContextElement::Context("comment");
let parser_context = context.with_additional_node(&parser_context); let parser_context = context.with_additional_node(&parser_context);

View File

@@ -19,9 +19,7 @@ use crate::context::GlobalSettings;
use crate::context::List; use crate::context::List;
use crate::context::RefContext; use crate::context::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
use crate::parser::org_source::convert_error;
use crate::parser::util::blank_line; use crate::parser::util::blank_line;
use crate::types::AstNode; use crate::types::AstNode;
use crate::types::Document; use crate::types::Document;
@@ -103,7 +101,7 @@ pub fn parse_file_with_settings<'g, 's, P: AsRef<Path>>(
/// This will not prevent additional settings from being learned during parsing, for example when encountering a "#+TODO". /// This will not prevent additional settings from being learned during parsing, for example when encountering a "#+TODO".
#[allow(dead_code)] #[allow(dead_code)]
fn document<'s>(context: RefContext<'_, '_, '_, 's>, input: &'s str) -> Res<&'s str, Document<'s>> { fn document<'s>(context: RefContext<'_, '_, '_, 's>, input: &'s str) -> Res<&'s str, Document<'s>> {
let (remaining, doc) = document_org_source(context, input.into()).map_err(convert_error)?; let (remaining, doc) = document_org_source(context, input.into())?;
Ok((Into::<&str>::into(remaining), doc)) Ok((Into::<&str>::into(remaining), doc))
} }
@@ -127,27 +125,16 @@ fn document_org_source<'b, 'g, 'r, 's>(
.get_global_settings() .get_global_settings()
.file_access .file_access
.read_file(setup_file) .read_file(setup_file)
.map_err(|err| nom::Err::<CustomError<OrgSource<'_>>>::Failure(err.into())) .map_err(|err| nom::Err::<CustomError>::Failure(err.into()))
}) })
.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) = let (_, setup_file_settings) = scan_for_in_buffer_settings(setup_file.into())?;
scan_for_in_buffer_settings(setup_file.into()).map_err(|err| {
eprintln!("{}", err);
nom::Err::Error(CustomError::MyError(MyError(
"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(|err| { .map_err(nom::Err::Error)?;
eprintln!("{}", err);
nom::Err::Error(CustomError::MyError(MyError(
"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;
@@ -156,7 +143,7 @@ fn document_org_source<'b, 'g, 'r, 's>(
{ {
// If there are radio targets in this document then we need to parse the entire document again with the knowledge of the radio targets. // If there are radio targets in this document then we need to parse the entire document again with the knowledge of the radio targets.
let all_radio_targets: Vec<&Vec<Object<'_>>> = Into::<AstNode>::into(&document) let all_radio_targets: Vec<&Vec<Object<'_>>> = Into::<AstNode>::into(&document)
.into_iter() .iter_all_ast_nodes()
.filter_map(|ast_node| { .filter_map(|ast_node| {
if let AstNode::RadioTarget(ast_node) = ast_node { if let AstNode::RadioTarget(ast_node) = ast_node {
Some(ast_node) Some(ast_node)
@@ -172,15 +159,13 @@ 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<OrgSource<'_>>>::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<OrgSource<'_>>>::Failure(err.into()))?;
Ok((remaining.into(), document)) Ok((remaining.into(), document))
} }
@@ -210,3 +195,17 @@ 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

@@ -19,7 +19,6 @@ use crate::context::ExitClass;
use crate::context::ExitMatcherNode; use crate::context::ExitMatcherNode;
use crate::context::RefContext; use crate::context::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
use crate::parser::element_parser::element; use crate::parser::element_parser::element;
use crate::parser::util::blank_line; use crate::parser::util::blank_line;
@@ -32,7 +31,6 @@ use crate::types::Drawer;
use crate::types::Element; use crate::types::Element;
use crate::types::Keyword; use crate::types::Keyword;
use crate::types::Paragraph; use crate::types::Paragraph;
use crate::types::SetSource;
#[cfg_attr( #[cfg_attr(
feature = "tracing", feature = "tracing",
@@ -48,9 +46,9 @@ where
AK: IntoIterator<Item = Keyword<'s>>, AK: IntoIterator<Item = Keyword<'s>>,
{ {
if immediate_in_section(context, "drawer") { if immediate_in_section(context, "drawer") {
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::Static(
"Cannot nest objects of the same element", "Cannot nest objects of the same element",
)))); )));
} }
start_of_line(remaining)?; start_of_line(remaining)?;
let (remaining, _leading_whitespace) = space0(remaining)?; let (remaining, _leading_whitespace) = space0(remaining)?;
@@ -82,9 +80,8 @@ where
))(remaining) ))(remaining)
{ {
Ok((remain, (_not_immediate_exit, first_line, (_trailing_whitespace, _exit_contents)))) => { Ok((remain, (_not_immediate_exit, first_line, (_trailing_whitespace, _exit_contents)))) => {
let mut element = Element::Paragraph(Paragraph::of_text(first_line.into()));
let source = get_consumed(remaining, remain); let source = get_consumed(remaining, remain);
element.set_source(source.into()); let element = Element::Paragraph(Paragraph::of_text(source.into(), first_line.into()));
(remain, vec![element]) (remain, vec![element])
} }
Err(_) => { Err(_) => {

View File

@@ -26,7 +26,6 @@ use crate::context::ExitClass;
use crate::context::ExitMatcherNode; use crate::context::ExitMatcherNode;
use crate::context::RefContext; use crate::context::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
use crate::parser::element_parser::element; use crate::parser::element_parser::element;
use crate::parser::util::blank_line; use crate::parser::util::blank_line;
@@ -38,7 +37,6 @@ use crate::types::DynamicBlock;
use crate::types::Element; use crate::types::Element;
use crate::types::Keyword; use crate::types::Keyword;
use crate::types::Paragraph; use crate::types::Paragraph;
use crate::types::SetSource;
#[cfg_attr( #[cfg_attr(
feature = "tracing", feature = "tracing",
@@ -54,9 +52,9 @@ where
AK: IntoIterator<Item = Keyword<'s>>, AK: IntoIterator<Item = Keyword<'s>>,
{ {
if immediate_in_section(context, "dynamic block") { if immediate_in_section(context, "dynamic block") {
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::Static(
"Cannot nest objects of the same element", "Cannot nest objects of the same element",
)))); )));
} }
start_of_line(remaining)?; start_of_line(remaining)?;
@@ -89,9 +87,7 @@ where
))))(remaining)?; ))))(remaining)?;
let leading_blank_lines = let leading_blank_lines =
leading_blank_lines.map(|(source, (first_line, _remaining_lines))| { leading_blank_lines.map(|(source, (first_line, _remaining_lines))| {
let mut element = Element::Paragraph(Paragraph::of_text(first_line.into())); Element::Paragraph(Paragraph::of_text(source.into(), first_line.into()))
element.set_source(source.into());
element
}); });
let (remaining, (mut children, _exit_contents)) = let (remaining, (mut children, _exit_contents)) =
many_till(element_matcher, exit_matcher)(remaining)?; many_till(element_matcher, exit_matcher)(remaining)?;

View File

@@ -1,5 +1,3 @@
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;
@@ -14,7 +12,6 @@ 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;
@@ -27,11 +24,16 @@ 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::MyError;
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::bullshitium::bullshitium;
use crate::parser::bullshitium::detect_bullshitium;
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,8 +57,9 @@ 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>> {
let (post_affiliated_keywords_input, affiliated_keywords) = #[cfg(feature = "event_count")]
many0(parser_with_context!(affiliated_keyword)(context))(input)?; record_event(EventType::ElementStart, input);
let (post_affiliated_keywords_input, affiliated_keywords) = affiliated_keywords(input)?;
let mut affiliated_keywords = affiliated_keywords.into_iter(); let mut affiliated_keywords = affiliated_keywords.into_iter();
@@ -240,6 +243,9 @@ fn _element<'b, 'g, 'r, 's>(
); );
if can_be_paragraph { if can_be_paragraph {
// Fake paragraphs
element!(bullshitium, context, input, Element::Paragraph);
// Paragraph without affiliated keyword // Paragraph without affiliated keyword
ak_element!( ak_element!(
paragraph, paragraph,
@@ -251,9 +257,7 @@ fn _element<'b, 'g, 'r, 's>(
); );
} }
Err(nom::Err::Error(CustomError::MyError(MyError( Err(nom::Err::Error(CustomError::Static("No element.")))
"No element.",
))))
} }
pub(crate) const fn detect_element( pub(crate) const fn detect_element(
@@ -272,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)?;
many0(parser_with_context!(affiliated_keyword)(context))(input)?;
let mut affiliated_keywords = affiliated_keywords.into_iter(); let mut affiliated_keywords = affiliated_keywords.into_iter();
@@ -319,11 +322,14 @@ fn _detect_element<'b, 'g, 'r, 's>(
input input
); );
// Fake paragraphs
if !can_be_paragraph {
element!(detect_bullshitium, context, input);
}
if _element(context, input, can_be_paragraph).is_ok() { if _element(context, input, can_be_paragraph).is_ok() {
return Ok((input, ())); return Ok((input, ()));
} }
Err(nom::Err::Error(CustomError::MyError(MyError( Err(nom::Err::Error(CustomError::Static("No element detected.")))
"No element detected.",
))))
} }

View File

@@ -1,11 +1,11 @@
use nom::branch::alt; use nom::branch::alt;
use nom::bytes::complete::tag; use nom::bytes::complete::tag;
use nom::character::complete::satisfy; use nom::character::complete::satisfy;
use nom::combinator::cond;
use nom::combinator::eof; use nom::combinator::eof;
use nom::combinator::map; use nom::combinator::map;
use nom::combinator::peek; use nom::combinator::peek;
use nom::combinator::recognize; use nom::combinator::recognize;
use nom::combinator::verify;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::org_source::OrgSource; use super::org_source::OrgSource;
@@ -13,7 +13,6 @@ use super::util::maybe_consume_object_trailing_whitespace_if_not_exiting;
use crate::context::EntityDefinition; use crate::context::EntityDefinition;
use crate::context::RefContext; use crate::context::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
use crate::parser::util::get_consumed; use crate::parser::util::get_consumed;
use crate::types::Entity; use crate::types::Entity;
@@ -58,18 +57,21 @@ fn name<'b, 'g, 'r, 's>(
) -> Res<OrgSource<'s>, (&'g EntityDefinition<'s>, OrgSource<'s>, bool)> { ) -> Res<OrgSource<'s>, (&'g EntityDefinition<'s>, OrgSource<'s>, bool)> {
for entity in context.get_global_settings().entities { for entity in context.get_global_settings().entities {
let result = tuple(( let result = tuple((
tag::<_, _, CustomError<_>>(entity.name), tag::<_, _, CustomError>(entity.name),
cond(
!entity.name.ends_with(' '),
alt(( alt((
verify(map(tag("{}"), |_| true), |_| !entity.name.ends_with(' ')), map(tag("{}"), |_| true),
map(peek(recognize(entity_end)), |_| false), map(peek(recognize(entity_end)), |_| false),
)), )),
),
))(input); ))(input);
if let Ok((remaining, (ent, use_brackets))) = result { if let Ok((remaining, (ent, use_brackets))) = result {
return Ok((remaining, (entity, ent, use_brackets))); return Ok((remaining, (entity, ent, use_brackets.unwrap_or(false))));
} }
} }
Err(nom::Err::Error(CustomError::MyError(MyError("NoEntity")))) Err(nom::Err::Error(CustomError::Static("NoEntity")))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]

View File

@@ -22,7 +22,6 @@ use crate::context::ExitClass;
use crate::context::ExitMatcherNode; use crate::context::ExitMatcherNode;
use crate::context::RefContext; use crate::context::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
use crate::parser::element_parser::element; use crate::parser::element_parser::element;
use crate::parser::util::blank_line; use crate::parser::util::blank_line;
@@ -48,9 +47,9 @@ where
AK: IntoIterator<Item = Keyword<'s>>, AK: IntoIterator<Item = Keyword<'s>>,
{ {
if immediate_in_section(context, "footnote definition") { if immediate_in_section(context, "footnote definition") {
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::Static(
"Cannot nest objects of the same element", "Cannot nest objects of the same element",
)))); )));
} }
start_of_line(remaining)?; start_of_line(remaining)?;
// Cannot be indented. // Cannot be indented.

View File

@@ -20,7 +20,6 @@ use crate::context::ExitMatcherNode;
use crate::context::List; use crate::context::List;
use crate::context::RefContext; use crate::context::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
use crate::parser::footnote_definition::label; use crate::parser::footnote_definition::label;
use crate::parser::object_parser::standard_set_object; use crate::parser::object_parser::standard_set_object;
@@ -176,9 +175,9 @@ fn _footnote_definition_end<'b, 'g, 'r, 's>(
let current_depth = input.get_bracket_depth() - starting_bracket_depth; let current_depth = input.get_bracket_depth() - starting_bracket_depth;
if current_depth > 0 { if current_depth > 0 {
// Its impossible for the next character to end the footnote reference definition if we're any amount of brackets deep // Its impossible for the next character to end the footnote reference definition if we're any amount of brackets deep
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::Static(
"NoFootnoteReferenceDefinitionEnd", "NoFootnoteReferenceDefinitionEnd",
)))); )));
} }
if current_depth < 0 { if current_depth < 0 {
// This shouldn't be possible because if depth is 0 then a closing bracket should end the footnote definition. // This shouldn't be possible because if depth is 0 then a closing bracket should end the footnote definition.

View File

@@ -28,7 +28,6 @@ use crate::context::ExitClass;
use crate::context::ExitMatcherNode; use crate::context::ExitMatcherNode;
use crate::context::RefContext; use crate::context::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
use crate::parser::element_parser::element; use crate::parser::element_parser::element;
use crate::parser::util::blank_line; use crate::parser::util::blank_line;
@@ -40,7 +39,6 @@ use crate::types::Element;
use crate::types::Keyword; use crate::types::Keyword;
use crate::types::Paragraph; use crate::types::Paragraph;
use crate::types::QuoteBlock; use crate::types::QuoteBlock;
use crate::types::SetSource;
use crate::types::SpecialBlock; use crate::types::SpecialBlock;
#[cfg_attr( #[cfg_attr(
@@ -232,9 +230,9 @@ fn greater_block_body<'c, 'b, 'g, 'r, 's>(
context_name: &'c str, context_name: &'c str,
) -> Res<OrgSource<'s>, (&'s str, Vec<Element<'s>>)> { ) -> Res<OrgSource<'s>, (&'s str, Vec<Element<'s>>)> {
if in_section(context, context_name) { if in_section(context, context_name) {
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::Static(
"Cannot nest objects of the same element", "Cannot nest objects of the same element",
)))); )));
} }
let exit_with_name = greater_block_end(name); let exit_with_name = greater_block_end(name);
let (remaining, _nl) = tuple((space0, line_ending))(input)?; let (remaining, _nl) = tuple((space0, line_ending))(input)?;
@@ -258,9 +256,7 @@ fn greater_block_body<'c, 'b, 'g, 'r, 's>(
))))(remaining)?; ))))(remaining)?;
let leading_blank_lines = let leading_blank_lines =
leading_blank_lines.map(|(source, (first_line, _remaining_lines))| { leading_blank_lines.map(|(source, (first_line, _remaining_lines))| {
let mut element = Element::Paragraph(Paragraph::of_text(first_line.into())); Element::Paragraph(Paragraph::of_text(source.into(), first_line.into()))
element.set_source(source.into());
element
}); });
let (remaining, (mut children, _exit_contents)) = let (remaining, (mut children, _exit_contents)) =
many_till(element_matcher, exit_matcher)(remaining)?; many_till(element_matcher, exit_matcher)(remaining)?;

View File

@@ -18,18 +18,18 @@ 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::parser_with_context; use crate::context::bind_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;
use crate::context::RefContext; use crate::context::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
use crate::parser::object_parser::standard_set_object; use crate::parser::object_parser::standard_set_object;
use crate::parser::util::blank_line; use crate::parser::util::blank_line;
@@ -62,10 +62,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(|i| context.check_exit_matcher(i))(input)?; not(bind_context!(exit_matcher_parser, context))(input)?;
let (remaining, pre_headline) = headline(context, input, parent_star_count)?; let (remaining, pre_headline) = headline(context, input, parent_star_count)?;
let section_matcher = parser_with_context!(section)(context); let section_matcher = bind_context!(section, context);
let heading_matcher = parser_with_context!(heading(pre_headline.star_count))(context); let heading_matcher = bind_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 +155,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(
parser_with_context!(headline_level)(&parser_context), bind_context!(headline_level, &parser_context),
|(_, count, _)| *count > parent_star_count, |(_, count, _)| *count > parent_star_count,
), ),
peek(org_space), peek(org_space),
@@ -163,7 +163,7 @@ fn headline<'b, 'g, 'r, 's>(
let (remaining, maybe_todo_keyword) = opt(tuple(( let (remaining, maybe_todo_keyword) = opt(tuple((
space1, space1,
parser_with_context!(heading_keyword)(&parser_context), bind_context!(heading_keyword, &parser_context),
peek(org_space_or_line_ending), peek(org_space_or_line_ending),
)))(remaining)?; )))(remaining)?;
@@ -177,9 +177,7 @@ fn headline<'b, 'g, 'r, 's>(
let (remaining, maybe_title) = opt(tuple(( let (remaining, maybe_title) = opt(tuple((
space1, space1,
consumed(many1(parser_with_context!(standard_set_object)( consumed(many1(bind_context!(standard_set_object, &parser_context))),
&parser_context,
))),
)))(remaining)?; )))(remaining)?;
let (remaining, maybe_tags) = opt(tuple((space0, tags)))(remaining)?; let (remaining, maybe_tags) = opt(tuple((space0, tags)))(remaining)?;
@@ -260,7 +258,7 @@ fn heading_keyword<'b, 'g, 'r, 's>(
.iter() .iter()
.map(String::as_str) .map(String::as_str)
{ {
let result = tag::<_, _, CustomError<_>>(todo_keyword)(input); let result = tag::<_, _, CustomError>(todo_keyword)(input);
if let Ok((remaining, ent)) = result { if let Ok((remaining, ent)) = result {
return Ok((remaining, (TodoKeywordType::Todo, ent))); return Ok((remaining, (TodoKeywordType::Todo, ent)));
} }
@@ -270,14 +268,12 @@ fn heading_keyword<'b, 'g, 'r, 's>(
.iter() .iter()
.map(String::as_str) .map(String::as_str)
{ {
let result = tag::<_, _, CustomError<_>>(todo_keyword)(input); let result = tag::<_, _, CustomError>(todo_keyword)(input);
if let Ok((remaining, ent)) = result { if let Ok((remaining, ent)) = result {
return Ok((remaining, (TodoKeywordType::Done, ent))); return Ok((remaining, (TodoKeywordType::Done, ent)));
} }
} }
Err(nom::Err::Error(CustomError::MyError(MyError( Err(nom::Err::Error(CustomError::Static("NoTodoKeyword")))
"NoTodoKeyword",
))))
} }
} }
@@ -288,9 +284,9 @@ fn priority_cookie(input: OrgSource<'_>) -> Res<OrgSource<'_>, PriorityCookie> {
tag("]"), tag("]"),
))(input)?; ))(input)?;
let cookie = PriorityCookie::try_from(priority_character).map_err(|_| { let cookie = PriorityCookie::try_from(priority_character).map_err(|_| {
nom::Err::Error(CustomError::MyError(MyError( nom::Err::Error(CustomError::Static(
"Failed to cast priority cookie to number.", "Failed to cast priority cookie to number.",
))) ))
})?; })?;
Ok((remaining, cookie)) Ok((remaining, cookie))
} }

View File

@@ -35,7 +35,7 @@ pub(crate) fn scan_for_in_buffer_settings<'s>(
let mut remaining = input; let mut remaining = input;
loop { loop {
// Skip text until possible in_buffer_setting // Skip text until possible in_buffer_setting
let start_of_pound = take_until::<_, _, CustomError<_>>("#+")(remaining); let start_of_pound = take_until::<_, _, CustomError>("#+")(remaining);
let start_of_pound = if let Ok((start_of_pound, _)) = start_of_pound { let start_of_pound = if let Ok((start_of_pound, _)) = start_of_pound {
start_of_pound start_of_pound
} else { } else {
@@ -47,7 +47,7 @@ pub(crate) fn scan_for_in_buffer_settings<'s>(
let (remain, maybe_kw) = match filtered_keyword(in_buffer_settings_key)(start_of_line) { let (remain, maybe_kw) = match filtered_keyword(in_buffer_settings_key)(start_of_line) {
Ok((remain, kw)) => (remain, Some(kw)), Ok((remain, kw)) => (remain, Some(kw)),
Err(_) => { Err(_) => {
let end_of_line = take_until::<_, _, CustomError<_>>("\n")(start_of_pound); let end_of_line = take_until::<_, _, CustomError>("\n")(start_of_pound);
if let Ok((end_of_line, _)) = end_of_line { if let Ok((end_of_line, _)) = end_of_line {
(end_of_line, None) (end_of_line, None)
} else { } else {
@@ -84,11 +84,14 @@ 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>,
) -> Result<GlobalSettings<'g, 's>, String> { ) -> Result<GlobalSettings<'g, 's>, CustomError> {
let mut new_settings = original_settings.clone(); let mut new_settings = original_settings.clone();
// Todo Keywords // Todo Keywords
@@ -98,7 +101,11 @@ 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| err.to_string())?; todo_keywords(kw.value).map_err(|err| match err {
nom::Err::Incomplete(_) => panic!("This parser does not support streaming."),
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));
@@ -112,9 +119,14 @@ 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) = let (_remaining, settings) = separated_list0(space1::<&str, CustomError>, is_not(" \t"))(
separated_list0(space1::<&str, nom::error::Error<_>>, is_not(" \t"))(kw.value) kw.value,
.map_err(|err: nom::Err<_>| err.to_string())?; )
.map_err(|err: nom::Err<_>| match err {
nom::Err::Incomplete(_) => panic!("This parser does not support streaming."),
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;
} }
@@ -128,7 +140,11 @@ 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(|e| e.to_string())?; let (_, (link_key, link_value)) = link_template(kw.value).map_err(|err| match err {
nom::Err::Incomplete(_) => panic!("This parser does not support streaming."),
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());
@@ -139,11 +155,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>( pub(crate) fn apply_post_parse_in_buffer_settings<'g, 's, 'sf>(document: &mut Document<'s>) {
document: &mut Document<'s>,
) -> Result<(), &'static str> {
document.category = Into::<AstNode>::into(&*document) document.category = Into::<AstNode>::into(&*document)
.into_iter() .iter_all_ast_nodes()
.filter_map(|ast_node| { .filter_map(|ast_node| {
if let AstNode::Keyword(ast_node) = ast_node { if let AstNode::Keyword(ast_node) = ast_node {
if ast_node.key.eq_ignore_ascii_case("category") { if ast_node.key.eq_ignore_ascii_case("category") {
@@ -154,7 +168,6 @@ pub(crate) fn apply_post_parse_in_buffer_settings<'g, 's, 'sf>(
}) })
.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

@@ -19,7 +19,6 @@ use crate::context::ExitClass;
use crate::context::ExitMatcherNode; use crate::context::ExitMatcherNode;
use crate::context::RefContext; use crate::context::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
use crate::parser::util::exit_matcher_parser; use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed; use crate::parser::util::get_consumed;
@@ -131,9 +130,7 @@ fn _header_end<'b, 'g, 'r, 's>(
let current_depth = input.get_bracket_depth() - starting_bracket_depth; let current_depth = input.get_bracket_depth() - starting_bracket_depth;
if current_depth > 0 { if current_depth > 0 {
// Its impossible for the next character to end the header if we're any amount of bracket deep // Its impossible for the next character to end the header if we're any amount of bracket deep
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::Static("NoHeaderEnd")));
"NoHeaderEnd",
))));
} }
if current_depth < 0 { if current_depth < 0 {
// This shouldn't be possible because if depth is 0 then a closing bracket should end the header. // This shouldn't be possible because if depth is 0 then a closing bracket should end the header.
@@ -183,9 +180,7 @@ fn _argument_end<'b, 'g, 'r, 's>(
let current_depth = input.get_parenthesis_depth() - starting_parenthesis_depth; let current_depth = input.get_parenthesis_depth() - starting_parenthesis_depth;
if current_depth > 0 { if current_depth > 0 {
// Its impossible for the next character to end the argument if we're any amount of parenthesis deep // Its impossible for the next character to end the argument if we're any amount of parenthesis deep
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::Static("NoArgumentEnd")));
"NoArgumentEnd",
))));
} }
if current_depth < 0 { if current_depth < 0 {
// This shouldn't be possible because if depth is 0 then a closing parenthesis should end the argument. // This shouldn't be possible because if depth is 0 then a closing parenthesis should end the argument.

View File

@@ -21,7 +21,6 @@ use crate::context::ExitClass;
use crate::context::ExitMatcherNode; use crate::context::ExitMatcherNode;
use crate::context::RefContext; use crate::context::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
use crate::parser::util::exit_matcher_parser; use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed; use crate::parser::util::get_consumed;
@@ -125,9 +124,7 @@ fn _header_end<'b, 'g, 'r, 's>(
let current_depth = input.get_bracket_depth() - starting_bracket_depth; let current_depth = input.get_bracket_depth() - starting_bracket_depth;
if current_depth > 0 { if current_depth > 0 {
// Its impossible for the next character to end the header if we're any amount of bracket deep // Its impossible for the next character to end the header if we're any amount of bracket deep
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::Static("NoHeaderEnd")));
"NoHeaderEnd",
))));
} }
if current_depth < 0 { if current_depth < 0 {
// This shouldn't be possible because if depth is 0 then a closing bracket should end the header. // This shouldn't be possible because if depth is 0 then a closing bracket should end the header.
@@ -187,7 +184,7 @@ fn _body_end<'b, 'g, 'r, 's>(
let current_depth = input.get_brace_depth() - starting_brace_depth; let current_depth = input.get_brace_depth() - starting_brace_depth;
if current_depth > 0 { if current_depth > 0 {
// Its impossible for the next character to end the body if we're any amount of brace deep // Its impossible for the next character to end the body if we're any amount of brace deep
return Err(nom::Err::Error(CustomError::MyError(MyError("NoBodyEnd")))); return Err(nom::Err::Error(CustomError::Static("NoBodyEnd")));
} }
if current_depth < 0 { if current_depth < 0 {
// This shouldn't be possible because if depth is 0 then a closing brace should end the body. // This shouldn't be possible because if depth is 0 then a closing brace should end the body.

View File

@@ -4,11 +4,9 @@ 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;
@@ -22,10 +20,11 @@ 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 crate::context::parser_with_context; use super::util::org_line_ending;
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::MyError;
use crate::error::Res; use crate::error::Res;
use crate::parser::macros::element; use crate::parser::macros::element;
use crate::parser::util::start_of_line; use crate::parser::util::start_of_line;
@@ -50,11 +49,8 @@ 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)?;
if let Ok((remaining, _)) = tuple(( let (remaining, _ws) = space0(remaining)?;
space0::<OrgSource<'_>, CustomError<OrgSource<'_>>>, if let Ok((remaining, _)) = org_line_ending(remaining) {
alt((line_ending, eof)),
))(remaining)
{
return Ok(( return Ok((
remaining, remaining,
Keyword { Keyword {
@@ -65,12 +61,9 @@ fn _filtered_keyword<'s, F: Fn(OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s
}, },
)); ));
} }
let (remaining, _ws) = space0(remaining)?; let (remaining, parsed_value) =
let (remaining, parsed_value) = recognize(many_till( recognize(many_till(anychar, peek(tuple((space0, org_line_ending)))))(remaining)?;
anychar, let (remaining, _ws) = tuple((space0, org_line_ending))(remaining)?;
peek(tuple((space0, alt((line_ending, eof))))),
))(remaining)?;
let (remaining, _ws) = tuple((space0, alt((line_ending, eof))))(remaining)?;
Ok(( Ok((
remaining, remaining,
Keyword { Keyword {
@@ -106,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(parser_with_context!(affiliated_key)(context))(input)
} }
#[cfg_attr( #[cfg_attr(
@@ -145,29 +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::MyError(MyError( Err(nom::Err::Error(CustomError::Static("No affiliated key.")))
"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(( tuple((tag_no_case::<_, _, CustomError>(keyword), peek(tag(":")))),
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 {
@@ -175,19 +154,14 @@ fn plain_affiliated_key<'b, 'g, 'r, 's>(
} }
} }
Err(nom::Err::Error(CustomError::MyError(MyError( Err(nom::Err::Error(CustomError::Static("NoKeywordKey")))
"NoKeywordKey",
))))
} }
#[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("]"),
@@ -198,9 +172,7 @@ fn dual_affiliated_key<'b, 'g, 'r, 's>(
} }
} }
Err(nom::Err::Error(CustomError::MyError(MyError( Err(nom::Err::Error(CustomError::Static("NoKeywordKey")))
"NoKeywordKey",
))))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
@@ -228,7 +200,7 @@ fn _optval_end<'s>(
unreachable!("Exceeded optval bracket depth.") unreachable!("Exceeded optval bracket depth.")
} }
if current_depth == 0 { if current_depth == 0 {
let close_bracket = tag::<_, _, CustomError<_>>("]")(input); let close_bracket = tag::<_, _, CustomError>("]")(input);
if close_bracket.is_ok() { if close_bracket.is_ok() {
return close_bracket; return close_bracket;
} }
@@ -249,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()));
} }
} }

View File

@@ -17,7 +17,6 @@ use super::util::maybe_consume_object_trailing_whitespace_if_not_exiting;
use crate::context::parser_with_context; 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::MyError;
use crate::error::Res; use crate::error::Res;
use crate::parser::util::exit_matcher_parser; use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed; use crate::parser::util::get_consumed;
@@ -209,9 +208,9 @@ fn pre<'b, 'g, 'r, 's>(
) -> Res<OrgSource<'s>, ()> { ) -> Res<OrgSource<'s>, ()> {
let preceding_character = input.get_preceding_character(); let preceding_character = input.get_preceding_character();
if let Some('$') = preceding_character { if let Some('$') = preceding_character {
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::Static(
"Not a valid pre character for dollar char fragment.", "Not a valid pre character for dollar char fragment.",
)))); )));
} }
Ok((input, ())) Ok((input, ()))
} }
@@ -283,9 +282,9 @@ fn close_border<'b, 'g, 'r, 's>(
match preceding_character { match preceding_character {
Some(c) if !c.is_whitespace() && !".,;$".contains(c) => Ok((input, ())), Some(c) if !c.is_whitespace() && !".,;$".contains(c) => Ok((input, ())),
_ => { _ => {
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::Static(
"Not a valid pre character for dollar char fragment.", "Not a valid pre character for dollar char fragment.",
)))); )));
} }
} }
} }

View File

@@ -27,7 +27,6 @@ use crate::context::ExitClass;
use crate::context::ExitMatcherNode; use crate::context::ExitMatcherNode;
use crate::context::RefContext; use crate::context::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
use crate::parser::object_parser::standard_set_object; use crate::parser::object_parser::standard_set_object;
use crate::parser::util::blank_line; use crate::parser::util::blank_line;
@@ -604,7 +603,7 @@ fn _example_src_switches<'s>(
} }
} }
if !matched_a_word { if !matched_a_word {
return Err(nom::Err::Error(CustomError::MyError(MyError("No words.")))); return Err(nom::Err::Error(CustomError::Static("No words.")));
} }
let remaining = last_match_remaining; let remaining = last_match_remaining;

View File

@@ -7,7 +7,6 @@ use nom::multi::many0;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use crate::context::RefContext; use crate::context::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
use crate::parser::util::get_consumed; use crate::parser::util::get_consumed;
use crate::types::LineBreak; use crate::types::LineBreak;
@@ -45,9 +44,9 @@ fn pre<'b, 'g, 'r, 's>(
match preceding_character { match preceding_character {
// If None, we are at the start of the file // If None, we are at the start of the file
None | Some('\\') => { None | Some('\\') => {
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::Static(
"Not a valid pre character for line break.", "Not a valid pre character for line break.",
)))); )));
} }
_ => {} _ => {}
}; };
@@ -55,9 +54,9 @@ fn pre<'b, 'g, 'r, 's>(
let current_line = input.text_since_line_break(); let current_line = input.text_since_line_break();
let is_non_empty_line = current_line.chars().any(|c| !c.is_whitespace()); let is_non_empty_line = current_line.chars().any(|c| !c.is_whitespace());
if !is_non_empty_line { if !is_non_empty_line {
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::Static(
"Not a valid pre line for line break.", "Not a valid pre line for line break.",
)))); )));
} }
Ok((input, ())) Ok((input, ()))

View File

@@ -1,6 +1,7 @@
mod affiliated_keyword; mod affiliated_keyword;
mod angle_link; mod angle_link;
mod babel_call; mod babel_call;
mod bullshitium;
mod citation; mod citation;
mod citation_reference; mod citation_reference;
mod clock; mod clock;

View File

@@ -4,7 +4,6 @@ use super::regular_link::regular_link;
use super::subscript_and_superscript::detect_subscript_or_superscript; use super::subscript_and_superscript::detect_subscript_or_superscript;
use crate::context::RefContext; use crate::context::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
use crate::parser::angle_link::angle_link; use crate::parser::angle_link::angle_link;
use crate::parser::citation::citation; use crate::parser::citation::citation;
@@ -43,7 +42,7 @@ pub(crate) fn standard_set_object<'b, 'g, 'r, 's>(
input, input,
Object::PlainText Object::PlainText
); );
Err(nom::Err::Error(CustomError::MyError(MyError("No object.")))) Err(nom::Err::Error(CustomError::Static("No object.")))
} }
#[cfg_attr( #[cfg_attr(
@@ -61,7 +60,7 @@ pub(crate) fn minimal_set_object<'b, 'g, 'r, 's>(
input, input,
Object::PlainText Object::PlainText
); );
Err(nom::Err::Error(CustomError::MyError(MyError("No object.")))) Err(nom::Err::Error(CustomError::Static("No object.")))
} }
#[cfg_attr( #[cfg_attr(
@@ -103,7 +102,7 @@ fn standard_set_object_sans_plain_text<'b, 'g, 'r, 's>(
element!(angle_link, context, input, Object::AngleLink); element!(angle_link, context, input, Object::AngleLink);
element!(org_macro, context, input, Object::OrgMacro); element!(org_macro, context, input, Object::OrgMacro);
Err(nom::Err::Error(CustomError::MyError(MyError("No object.")))) Err(nom::Err::Error(CustomError::Static("No object.")))
} }
#[cfg_attr( #[cfg_attr(
@@ -119,7 +118,7 @@ fn minimal_set_object_sans_plain_text<'b, 'g, 'r, 's>(
element!(entity, context, input, Object::Entity); element!(entity, context, input, Object::Entity);
element!(latex_fragment, context, input, Object::LatexFragment); element!(latex_fragment, context, input, Object::LatexFragment);
element!(text_markup, context, input); element!(text_markup, context, input);
Err(nom::Err::Error(CustomError::MyError(MyError("No object.")))) Err(nom::Err::Error(CustomError::Static("No object.")))
} }
#[cfg_attr( #[cfg_attr(
@@ -137,9 +136,7 @@ pub(crate) fn detect_standard_set_object_sans_plain_text<'b, 'g, 'r, 's>(
return Ok((input, ())); return Ok((input, ()));
} }
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::Static("No object detected.")));
"No object detected.",
))));
} }
#[cfg_attr( #[cfg_attr(
@@ -157,9 +154,7 @@ fn detect_minimal_set_object_sans_plain_text<'b, 'g, 'r, 's>(
return Ok((input, ())); return Ok((input, ()));
} }
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::Static("No object detected.")));
"No object detected.",
))));
} }
#[cfg_attr( #[cfg_attr(
@@ -182,7 +177,7 @@ pub(crate) fn regular_link_description_set_object<'b, 'g, 'r, 's>(
input, input,
Object::PlainText Object::PlainText
); );
Err(nom::Err::Error(CustomError::MyError(MyError("No object.")))) Err(nom::Err::Error(CustomError::Static("No object.")))
} }
#[cfg_attr( #[cfg_attr(
@@ -205,7 +200,7 @@ fn regular_link_description_set_object_sans_plain_text<'b, 'g, 'r, 's>(
element!(inline_babel_call, context, input, Object::InlineBabelCall); element!(inline_babel_call, context, input, Object::InlineBabelCall);
element!(org_macro, context, input, Object::OrgMacro); element!(org_macro, context, input, Object::OrgMacro);
element!(minimal_set_object_sans_plain_text, context, input); element!(minimal_set_object_sans_plain_text, context, input);
Err(nom::Err::Error(CustomError::MyError(MyError("No object.")))) Err(nom::Err::Error(CustomError::Static("No object.")))
} }
#[cfg_attr( #[cfg_attr(
@@ -223,9 +218,7 @@ fn detect_regular_link_description_set_object_sans_plain_text<'b, 'g, 'r, 's>(
return Ok((input, ())); return Ok((input, ()));
} }
Err(nom::Err::Error(CustomError::MyError(MyError( Err(nom::Err::Error(CustomError::Static("No object detected.")))
"No object detected.",
))))
} }
#[cfg_attr( #[cfg_attr(
@@ -243,7 +236,7 @@ pub(crate) fn table_cell_set_object<'b, 'g, 'r, 's>(
input, input,
Object::PlainText Object::PlainText
); );
Err(nom::Err::Error(CustomError::MyError(MyError("No object.")))) Err(nom::Err::Error(CustomError::Static("No object.")))
} }
#[cfg_attr( #[cfg_attr(
@@ -271,7 +264,7 @@ fn table_cell_set_object_sans_plain_text<'b, 'g, 'r, 's>(
element!(target, context, input, Object::Target); element!(target, context, input, Object::Target);
element!(timestamp, context, input, Object::Timestamp); element!(timestamp, context, input, Object::Timestamp);
element!(minimal_set_object_sans_plain_text, context, input); element!(minimal_set_object_sans_plain_text, context, input);
Err(nom::Err::Error(CustomError::MyError(MyError("No object.")))) Err(nom::Err::Error(CustomError::Static("No object.")))
} }
#[cfg_attr( #[cfg_attr(
@@ -289,7 +282,5 @@ fn detect_table_cell_set_object_sans_plain_text<'b, 'g, 'r, 's>(
return Ok((input, ())); return Ok((input, ()));
} }
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::Static("No object detected.")));
"No object detected.",
))));
} }

View File

@@ -96,7 +96,7 @@ fn org_macro_arg<'b, 'g, 'r, 's>(
loop { loop {
not(parser_with_context!(exit_matcher_parser)(context))(remaining)?; not(parser_with_context!(exit_matcher_parser)(context))(remaining)?;
not(peek(tag("}}}")))(remaining)?; not(peek(tag("}}}")))(remaining)?;
if peek(tuple((space0::<OrgSource<'_>, CustomError<_>>, tag(")"))))(remaining).is_ok() { if peek(tuple((space0::<_, CustomError>, tag(")"))))(remaining).is_ok() {
break; break;
} }
@@ -108,7 +108,7 @@ fn org_macro_arg<'b, 'g, 'r, 's>(
} }
if next_char == '\\' { if next_char == '\\' {
escaping = true; escaping = true;
if peek(tag::<_, _, CustomError<_>>(")"))(new_remaining).is_ok() { if peek(tag::<_, _, CustomError>(")"))(new_remaining).is_ok() {
// Special case for backslash at the end of a macro // Special case for backslash at the end of a macro
remaining = new_remaining; remaining = new_remaining;
break; break;

View File

@@ -10,9 +10,6 @@ use nom::InputTakeAtPosition;
use nom::Offset; use nom::Offset;
use nom::Slice; use nom::Slice;
use crate::error::CustomError;
use crate::error::MyError;
pub(crate) type BracketDepth = i16; pub(crate) type BracketDepth = i16;
#[derive(Copy, Clone, PartialEq)] #[derive(Copy, Clone, PartialEq)]
@@ -85,6 +82,15 @@ impl<'s> OrgSource<'s> {
self.slice(..(other.end - self.start)) self.slice(..(other.end - self.start))
} }
pub(crate) fn get_until_end_of_str(&self, other: &'s str) -> OrgSource<'s> {
let full_source_start = self.full_source.as_ptr() as usize;
let other_start = other.as_ptr() as usize - full_source_start;
let other_end = other_start + other.len();
debug_assert!(other_start >= self.start);
debug_assert!(other_end <= self.end);
self.slice(..(other_end - self.start))
}
pub(crate) fn get_start_of_line(&self) -> OrgSource<'s> { pub(crate) fn get_start_of_line(&self) -> OrgSource<'s> {
let skipped_text = self.text_since_line_break(); let skipped_text = self.text_since_line_break();
let mut bracket_depth = self.bracket_depth; let mut bracket_depth = self.bracket_depth;
@@ -385,33 +391,6 @@ impl<'n, 's> FindSubstring<&'n str> for OrgSource<'s> {
} }
} }
pub(crate) fn convert_error<'a, I: Into<CustomError<&'a str>>>(
err: nom::Err<I>,
) -> nom::Err<CustomError<&'a str>> {
match err {
nom::Err::Incomplete(needed) => nom::Err::Incomplete(needed),
nom::Err::Error(err) => nom::Err::Error(err.into()),
nom::Err::Failure(err) => nom::Err::Failure(err.into()),
}
}
impl<'s> From<CustomError<OrgSource<'s>>> for CustomError<&'s str> {
fn from(value: CustomError<OrgSource<'s>>) -> Self {
match value {
CustomError::MyError(err) => CustomError::MyError(err),
CustomError::Nom(input, error_kind) => CustomError::Nom(input.into(), error_kind),
CustomError::IO(err) => CustomError::IO(err),
CustomError::BoxedError(err) => CustomError::BoxedError(err),
}
}
}
impl<'s> From<MyError<OrgSource<'s>>> for MyError<&'s str> {
fn from(value: MyError<OrgSource<'s>>) -> Self {
MyError(value.0.into())
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View File

@@ -36,7 +36,6 @@ use crate::context::ExitMatcherNode;
use crate::context::Matcher; use crate::context::Matcher;
use crate::context::RefContext; use crate::context::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
use crate::parser::util::exit_matcher_parser; use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed; use crate::parser::util::get_consumed;
@@ -95,9 +94,9 @@ fn pre<'b, 'g, 'r, 's>(
Some(x) if !WORD_CONSTITUENT_CHARACTERS.contains(x) => {} Some(x) if !WORD_CONSTITUENT_CHARACTERS.contains(x) => {}
Some(_) => { Some(_) => {
// Not at start of line, cannot be a heading // Not at start of line, cannot be a heading
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::Static(
"Not a valid pre character for plain link.", "Not a valid pre character for plain link.",
)))); )));
} }
}; };
Ok((input, ())) Ok((input, ()))
@@ -262,15 +261,13 @@ pub(crate) fn protocol<'b, 'g, 'r, 's>(
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
for link_parameter in context.get_global_settings().link_parameters { for link_parameter in context.get_global_settings().link_parameters {
let result = tag_no_case::<_, _, CustomError<_>>(*link_parameter)(input); let result = tag_no_case::<_, _, CustomError>(*link_parameter)(input);
if let Ok((remaining, ent)) = result { if let Ok((remaining, ent)) = result {
return Ok((remaining, ent)); return Ok((remaining, ent));
} }
} }
Err(nom::Err::Error(CustomError::MyError(MyError( Err(nom::Err::Error(CustomError::Static("NoLinkProtocol")))
"NoLinkProtocol",
))))
} }
#[cfg_attr( #[cfg_attr(
@@ -327,8 +324,7 @@ fn impl_path_plain_end<'b, 'g, 'r, 's>(
} }
if current_depth == 0 { if current_depth == 0 {
let close_parenthesis = let close_parenthesis = tag::<_, _, CustomError>(")")(remaining);
tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>(")")(remaining);
if close_parenthesis.is_ok() { if close_parenthesis.is_ok() {
return close_parenthesis; return close_parenthesis;
} }
@@ -342,9 +338,7 @@ fn impl_path_plain_end<'b, 'g, 'r, 's>(
} }
} }
Err(nom::Err::Error(CustomError::MyError(MyError( Err(nom::Err::Error(CustomError::Static("No path plain end")))
"No path plain end",
))))
} }
#[cfg_attr( #[cfg_attr(
@@ -420,18 +414,18 @@ fn _path_plain_parenthesis_end<'s>(
unreachable!("Exceeded plain link parenthesis depth.") unreachable!("Exceeded plain link parenthesis depth.")
} }
if current_depth == 0 { if current_depth == 0 {
let close_parenthesis = tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>(")")(input); let close_parenthesis = tag::<_, _, CustomError>(")")(input);
if close_parenthesis.is_ok() { if close_parenthesis.is_ok() {
return close_parenthesis; return close_parenthesis;
} }
} }
if current_depth == 1 { if current_depth == 1 {
let open_parenthesis = tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>("(")(input); let open_parenthesis = tag::<_, _, CustomError>("(")(input);
if open_parenthesis.is_ok() { if open_parenthesis.is_ok() {
return open_parenthesis; return open_parenthesis;
} }
} }
Err(nom::Err::Error(CustomError::MyError(MyError( Err(nom::Err::Error(CustomError::Static(
"No closing parenthesis", "No closing parenthesis",
)))) )))
} }

View File

@@ -3,6 +3,7 @@ use nom::bytes::complete::tag;
use nom::character::complete::anychar; use nom::character::complete::anychar;
use nom::character::complete::digit1; use nom::character::complete::digit1;
use nom::character::complete::line_ending; use nom::character::complete::line_ending;
use nom::character::complete::multispace1;
use nom::character::complete::one_of; use nom::character::complete::one_of;
use nom::character::complete::space0; use nom::character::complete::space0;
use nom::character::complete::space1; use nom::character::complete::space1;
@@ -17,6 +18,7 @@ use nom::multi::many0;
use nom::multi::many1; use nom::multi::many1;
use nom::multi::many_till; use nom::multi::many_till;
use nom::sequence::tuple; use nom::sequence::tuple;
use nom::InputTake;
use super::affiliated_keyword::parse_affiliated_keywords; use super::affiliated_keyword::parse_affiliated_keywords;
use super::element_parser::element; use super::element_parser::element;
@@ -25,6 +27,7 @@ use super::org_source::OrgSource;
use super::util::include_input; use super::util::include_input;
use super::util::indentation_level; use super::util::indentation_level;
use super::util::non_whitespace_character; use super::util::non_whitespace_character;
use crate::context::bind_context;
use crate::context::parser_with_context; use crate::context::parser_with_context;
use crate::context::ContextElement; use crate::context::ContextElement;
use crate::context::ContextMatcher; use crate::context::ContextMatcher;
@@ -32,7 +35,6 @@ use crate::context::ExitClass;
use crate::context::ExitMatcherNode; use crate::context::ExitMatcherNode;
use crate::context::RefContext; use crate::context::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
use crate::parser::util::blank_line; use crate::parser::util::blank_line;
use crate::parser::util::exit_matcher_parser; use crate::parser::util::exit_matcher_parser;
@@ -78,9 +80,47 @@ where
{ {
return Ok((input, ())); return Ok((input, ()));
} }
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::Static("No element detected.")));
"No element detected.", }
))));
#[cfg_attr(
feature = "tracing",
tracing::instrument(ret, level = "debug", skip(context))
)]
pub(crate) fn detect_not_plain_list_item_indent<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, (u16, OrgSource<'s>)> {
if let Ok((_remaining, (_, indent, _))) = tuple((
start_of_line,
parser_with_context!(indentation_level)(context),
not(tuple((
parser_with_context!(bullet)(context),
alt((space1, line_ending, eof)),
))),
))(input)
{
return Ok((input, indent));
}
// Headlines are not plain list items.
if let Ok((_remaining, (_, indent, _))) = verify(
tuple((
start_of_line,
parser_with_context!(indentation_level)(context),
tuple((
parser_with_context!(bullet)(context),
alt((space1, line_ending, eof)),
)),
)),
|(_, (depth, _), ((_, bullet), _))| {
*depth == 0 && Into::<&str>::into(bullet).starts_with('*')
},
)(input)
{
return Ok((input, indent));
}
return Err(nom::Err::Error(CustomError::Static("No element detected.")));
} }
#[cfg_attr( #[cfg_attr(
@@ -123,7 +163,7 @@ where
// While #3 is the most slow, it also seems to cleanest and involves the least manual mutation of already-parsed objects so I am going with #3 for now, but we should revisit #1 or #2 when the parser is more developed. // While #3 is the most slow, it also seems to cleanest and involves the least manual mutation of already-parsed objects so I am going with #3 for now, but we should revisit #1 or #2 when the parser is more developed.
loop { loop {
let list_item = parser_with_context!(plain_list_item)(&parser_context)(remaining); let list_item = plain_list_item(&parser_context, remaining);
match (&first_item_list_type, &list_item) { match (&first_item_list_type, &list_item) {
(None, Ok((_remain, (list_type, _item)))) => { (None, Ok((_remain, (list_type, _item)))) => {
let _ = first_item_list_type.insert(*list_type); let _ = first_item_list_type.insert(*list_type);
@@ -143,25 +183,17 @@ where
} }
}; };
let maybe_exit = parser_with_context!(exit_matcher_parser)(&parser_context)(remaining); let maybe_exit = exit_matcher_parser(&parser_context, remaining);
if maybe_exit.is_ok() { if maybe_exit.is_ok() {
break; break;
} }
} }
let (final_child_start, _final_item_first_parse) = match children.pop() { if children.is_empty() {
Some(final_child) => final_child, return Err(nom::Err::Error(CustomError::Static(
None => {
return Err(nom::Err::Error(CustomError::MyError(MyError(
"Plain lists require at least one element.", "Plain lists require at least one element.",
)))); )));
} }
};
let final_item_context = ContextElement::ConsumeTrailingWhitespace(false);
let final_item_context = parser_context.with_additional_node(&final_item_context);
let (remaining, (_, reparsed_final_item)) =
parser_with_context!(plain_list_item)(&final_item_context)(final_child_start)?;
children.push((final_child_start, reparsed_final_item));
let (remaining, _trailing_ws) = let (remaining, _trailing_ws) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?; maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
@@ -190,10 +222,10 @@ fn plain_list_item<'b, 'g, 'r, 's>(
) -> Res<OrgSource<'s>, (PlainListType, PlainListItem<'s>)> { ) -> Res<OrgSource<'s>, (PlainListType, PlainListItem<'s>)> {
start_of_line(input)?; start_of_line(input)?;
let (remaining, (indent_level, _leading_whitespace)) = indentation_level(context, input)?; let (remaining, (indent_level, _leading_whitespace)) = indentation_level(context, input)?;
let (remaining, (bullet_type, bull)) = verify( let (remaining, (bullet_type, bull)) =
parser_with_context!(bullet)(context), verify(bind_context!(bullet, context), |(_bullet_type, bull)| {
|(_bullet_type, bull)| !Into::<&str>::into(bull).starts_with('*') || indent_level > 0, !Into::<&str>::into(bull).starts_with('*') || indent_level > 0
)(remaining)?; })(remaining)?;
let (remaining, maybe_counter_set) = let (remaining, maybe_counter_set) =
opt(tuple((space1, tag("[@"), counter_set_value, tag("]"))))(remaining)?; opt(tuple((space1, tag("[@"), counter_set_value, tag("]"))))(remaining)?;
@@ -202,7 +234,7 @@ fn plain_list_item<'b, 'g, 'r, 's>(
let (remaining, maybe_checkbox) = opt(tuple((space1, item_checkbox)))(remaining)?; let (remaining, maybe_checkbox) = opt(tuple((space1, item_checkbox)))(remaining)?;
let (remaining, maybe_tag) = if let BulletType::Unordered = bullet_type { let (remaining, maybe_tag) = if let BulletType::Unordered = bullet_type {
opt(tuple((space1, parser_with_context!(item_tag)(context))))(remaining)? opt(tuple((space1, bind_context!(item_tag, context))))(remaining)?
} else { } else {
(remaining, None) (remaining, None)
}; };
@@ -214,6 +246,12 @@ fn plain_list_item<'b, 'g, 'r, 's>(
}; };
let exit_matcher = plain_list_item_end(indent_level); let exit_matcher = plain_list_item_end(indent_level);
let final_item_whitespace_cutoff = final_item_whitespace_cutoff(indent_level);
let final_whitespace_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta,
exit_matcher: &final_item_whitespace_cutoff,
});
let final_whitespace_context = context.with_additional_node(&final_whitespace_context);
let contexts = [ let contexts = [
ContextElement::ConsumeTrailingWhitespace(true), ContextElement::ConsumeTrailingWhitespace(true),
ContextElement::ExitMatcherNode(ExitMatcherNode { ContextElement::ExitMatcherNode(ExitMatcherNode {
@@ -221,17 +259,21 @@ fn plain_list_item<'b, 'g, 'r, 's>(
exit_matcher: &exit_matcher, exit_matcher: &exit_matcher,
}), }),
]; ];
let parser_context = context.with_additional_node(&contexts[0]); let parser_context = final_whitespace_context.with_additional_node(&contexts[0]);
let parser_context = parser_context.with_additional_node(&contexts[1]); let parser_context = parser_context.with_additional_node(&contexts[1]);
let maybe_contentless_item: Res<OrgSource<'_>, ()> = peek(parser_with_context!( let maybe_contentless_item: Res<OrgSource<'_>, ()> =
detect_contentless_item_contents detect_contentless_item_contents(&parser_context, remaining);
)(&parser_context))(remaining);
if let Ok((_rem, _ws)) = maybe_contentless_item { if let Ok((_rem, _ws)) = maybe_contentless_item {
let (remaining, _trailing_ws) = if context.should_consume_trailing_whitespace() { let (remaining, _trailing_ws) = if tuple((
recognize(alt((recognize(many1(blank_line)), eof)))(remaining)? blank_line,
} else { bind_context!(final_item_whitespace_cutoff, context),
))(remaining)
.is_ok()
{
recognize(alt((blank_line, eof)))(remaining)? recognize(alt((blank_line, eof)))(remaining)?
} else {
recognize(alt((recognize(many1(blank_line)), eof)))(remaining)?
}; };
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
return Ok(( return Ok((
@@ -259,26 +301,14 @@ fn plain_list_item<'b, 'g, 'r, 's>(
.filter(|b| *b == b'\n') .filter(|b| *b == b'\n')
.count(); .count();
let (mut remaining, (mut children, _exit_contents)) = many_till( let (remaining, (children, _exit_contents)) = many_till(
include_input(parser_with_context!(element(true))(&parser_context)), include_input(bind_context!(element(true), &parser_context)),
parser_with_context!(exit_matcher_parser)(&parser_context), bind_context!(exit_matcher_parser, &parser_context),
)(remaining)?; )(remaining)?;
if !children.is_empty() && !context.should_consume_trailing_whitespace() { // We have to use the parser_context here to include the whitespace cut-off
let final_item_context = ContextElement::ConsumeTrailingWhitespace(false);
let final_item_context = parser_context.with_additional_node(&final_item_context);
let (final_child_start, _original_final_child) = children
.pop()
.expect("if-statement already checked that children was non-empty.");
let (remain, reparsed_final_element) = include_input(parser_with_context!(element(true))(
&final_item_context,
))(final_child_start)?;
remaining = remain;
children.push(reparsed_final_element);
}
let (remaining, _trailing_ws) = let (remaining, _trailing_ws) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?; maybe_consume_trailing_whitespace_if_not_exiting(&final_whitespace_context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
return Ok(( return Ok((
@@ -325,7 +355,7 @@ fn bullet<'b, 'g, 'r, 's>(
map(tag("+"), |bull| (BulletType::Unordered, bull)), map(tag("+"), |bull| (BulletType::Unordered, bull)),
map( map(
recognize(tuple(( recognize(tuple((
parser_with_context!(counter)(context), bind_context!(counter, context),
alt((tag("."), tag(")"))), alt((tag("."), tag(")"))),
))), ))),
|bull| (BulletType::Ordered, bull), |bull| (BulletType::Ordered, bull),
@@ -380,6 +410,52 @@ fn counter_set_value<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, PlainListIt
))(input) ))(input)
} }
const fn final_item_whitespace_cutoff(indent_level: IndentationLevel) -> impl ContextMatcher {
move |context, input: OrgSource<'_>| {
impl_final_item_whitespace_cutoff(context, input, indent_level)
}
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(ret, level = "debug", skip(context))
)]
fn impl_final_item_whitespace_cutoff<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
indent_level: IndentationLevel,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
start_of_line(input)?;
// element!(plain_list_end, context, input);
if let Ok((_remaining, _)) = verify(
tuple((
opt(blank_line),
bind_context!(indentation_level, context),
not(multispace1),
)),
|(_, (depth, _stars), _not_whitespace)| *depth < indent_level,
)(input)
{
return Ok((input, input.take(0)));
}
if let Ok((_remaining, _)) = tuple((
opt(blank_line),
verify(
bind_context!(detect_not_plain_list_item_indent, context),
|(depth, _)| *depth == indent_level,
),
))(input)
{
return Ok((input, input.take(0)));
}
Err(nom::Err::Error(CustomError::Static(
"No whitespace cut-off.",
)))
}
#[cfg_attr( #[cfg_attr(
feature = "tracing", feature = "tracing",
tracing::instrument(ret, level = "debug", skip(_context)) tracing::instrument(ret, level = "debug", skip(_context))
@@ -415,7 +491,7 @@ fn _plain_list_item_end<'b, 'g, 'r, 's>(
start_of_line(input)?; start_of_line(input)?;
recognize(tuple(( recognize(tuple((
opt(blank_line), opt(blank_line),
parser_with_context!(line_indented_lte_matcher)(context), bind_context!(line_indented_lte_matcher, context),
)))(input) )))(input)
} }
@@ -434,7 +510,7 @@ fn _line_indented_lte<'b, 'g, 'r, 's>(
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
let matched = recognize(verify( let matched = recognize(verify(
tuple(( tuple((
parser_with_context!(indentation_level)(context), bind_context!(indentation_level, context),
non_whitespace_character, non_whitespace_character,
)), )),
// It is fine that we get the indent level using the number of bytes rather than the number of characters because nom's space0 only matches space and tab (0x20 and 0x09) // It is fine that we get the indent level using the number of bytes rather than the number of characters because nom's space0 only matches space and tab (0x20 and 0x09)
@@ -460,8 +536,8 @@ fn item_tag<'b, 'g, 'r, 's>(
let (remaining, (children, _exit_contents)) = verify( let (remaining, (children, _exit_contents)) = verify(
many_till( many_till(
// TODO: Should this be using a different set like the minimal set? // TODO: Should this be using a different set like the minimal set?
parser_with_context!(standard_set_object)(&parser_context), bind_context!(standard_set_object, &parser_context),
parser_with_context!(exit_matcher_parser)(&parser_context), bind_context!(exit_matcher_parser, &parser_context),
), ),
|(children, _exit_contents)| !children.is_empty(), |(children, _exit_contents)| !children.is_empty(),
)(input)?; )(input)?;
@@ -511,7 +587,7 @@ fn item_tag_post_gap<'b, 'g, 'r, 's>(
alt(( alt((
peek(recognize(not(blank_line))), peek(recognize(not(blank_line))),
peek(recognize(tuple((many0(blank_line), eof)))), peek(recognize(tuple((many0(blank_line), eof)))),
parser_with_context!(exit_matcher_parser)(context), bind_context!(exit_matcher_parser, context),
)), )),
), ),
))), ))),
@@ -541,7 +617,7 @@ fn detect_contentless_item_contents<'b, 'g, 'r, 's>(
) -> Res<OrgSource<'s>, ()> { ) -> Res<OrgSource<'s>, ()> {
let (remaining, _) = recognize(many_till( let (remaining, _) = recognize(many_till(
blank_line, blank_line,
parser_with_context!(exit_matcher_parser)(context), bind_context!(exit_matcher_parser, context),
))(input)?; ))(input)?;
Ok((remaining, ())) Ok((remaining, ()))
} }

View File

@@ -20,7 +20,6 @@ use super::util::org_space_or_line_ending;
use crate::context::parser_with_context; 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::MyError;
use crate::error::Res; use crate::error::Res;
use crate::types::Object; use crate::types::Object;
use crate::types::PlainText; use crate::types::PlainText;
@@ -92,7 +91,7 @@ impl<'x> RematchObject<'x> for PlainText<'x> {
break; break;
} }
let is_not_whitespace = is_not::<&str, &str, CustomError<_>>(" \t\r\n")(goal); let is_not_whitespace = is_not::<_, _, CustomError>(" \t\r\n")(goal);
if let Ok((new_goal, payload)) = is_not_whitespace { if let Ok((new_goal, payload)) = is_not_whitespace {
let (new_remaining, _) = tuple(( let (new_remaining, _) = tuple((
tag_no_case(payload), tag_no_case(payload),
@@ -108,7 +107,7 @@ impl<'x> RematchObject<'x> for PlainText<'x> {
} }
let is_whitespace = recognize(many1(alt(( let is_whitespace = recognize(many1(alt((
recognize(one_of::<&str, &str, CustomError<_>>(" \t")), recognize(one_of::<_, _, CustomError>(" \t")),
line_ending, line_ending,
))))(goal); ))))(goal);
if let Ok((new_goal, _)) = is_whitespace { if let Ok((new_goal, _)) = is_whitespace {
@@ -118,9 +117,9 @@ impl<'x> RematchObject<'x> for PlainText<'x> {
continue; continue;
} }
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::Static(
"Target does not match.", "Target does not match.",
)))); )));
} }
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);

View File

@@ -21,7 +21,6 @@ use crate::context::ExitClass;
use crate::context::ExitMatcherNode; use crate::context::ExitMatcherNode;
use crate::context::RefContext; use crate::context::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
use crate::parser::util::get_consumed; use crate::parser::util::get_consumed;
use crate::parser::util::immediate_in_section; use crate::parser::util::immediate_in_section;
@@ -39,9 +38,9 @@ pub(crate) fn property_drawer<'b, 'g, 'r, 's>(
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, PropertyDrawer<'s>> { ) -> Res<OrgSource<'s>, PropertyDrawer<'s>> {
if immediate_in_section(context, "property-drawer") { if immediate_in_section(context, "property-drawer") {
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::Static(
"Cannot nest objects of the same element", "Cannot nest objects of the same element",
)))); )));
} }
let ( let (
remaining, remaining,

View File

@@ -21,7 +21,6 @@ use crate::context::ExitMatcherNode;
use crate::context::List; use crate::context::List;
use crate::context::RefContext; use crate::context::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
use crate::parser::util::get_consumed; use crate::parser::util::get_consumed;
use crate::types::Object; use crate::types::Object;
@@ -52,9 +51,7 @@ pub(crate) fn radio_link<'b, 'g, 'r, 's>(
)); ));
} }
} }
Err(nom::Err::Error(CustomError::MyError(MyError( Err(nom::Err::Error(CustomError::Static("NoRadioLink")))
"NoRadioLink",
))))
} }
#[cfg_attr( #[cfg_attr(
@@ -98,9 +95,9 @@ pub(crate) fn rematch_target<'x, 'b, 'g, 'r, 's>(
new_matches.push(new_match); new_matches.push(new_match);
} }
_ => { _ => {
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::Static(
"OnlyMinimalSetObjectsAllowed", "OnlyMinimalSetObjectsAllowed",
)))); )));
} }
}; };
} }

View File

@@ -43,7 +43,6 @@ use crate::context::ExitMatcherNode;
use crate::context::List; use crate::context::List;
use crate::context::RefContext; use crate::context::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
use crate::types::LinkType; use crate::types::LinkType;
use crate::types::Object; use crate::types::Object;
@@ -163,11 +162,7 @@ fn parse_path_reg<'b, 'g, 'r, 's>(
parser_with_context!(protocol_path_reg)(context), parser_with_context!(protocol_path_reg)(context),
fuzzy_path_reg, fuzzy_path_reg,
))(replaced_input) ))(replaced_input)
.map_err(|_| { .map_err(|_| nom::Err::Error(CustomError::Static("No pathreg match after replacement.")))?;
nom::Err::Error(CustomError::MyError(MyError(
"No pathreg match after replacement.",
)))
})?;
let remaining = input.take(input.len()); let remaining = input.take(input.len());
let link_type = match link.link_type { let link_type = match link.link_type {
LinkType::Protocol(protocol) => LinkType::Protocol(protocol.into_owned().into()), LinkType::Protocol(protocol) => LinkType::Protocol(protocol.into_owned().into()),
@@ -483,7 +478,5 @@ fn impl_path_reg_end<'b, 'g, 'r, 's>(
} }
} }
Err(nom::Err::Error(CustomError::MyError(MyError( Err(nom::Err::Error(CustomError::Static("No path reg end")))
"No path reg end",
))))
} }

View File

@@ -26,7 +26,6 @@ use crate::context::ExitMatcherNode;
use crate::context::Matcher; use crate::context::Matcher;
use crate::context::RefContext; use crate::context::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
use crate::parser::util::get_consumed; use crate::parser::util::get_consumed;
use crate::types::Object; use crate::types::Object;
@@ -39,7 +38,7 @@ pub(crate) fn detect_subscript_or_superscript<'s>(input: OrgSource<'s>) -> Res<O
// This does not have to detect all valid subscript/superscript but all that it detects must be valid. // This does not have to detect all valid subscript/superscript but all that it detects must be valid.
let (remaining, _) = one_of("_^")(input)?; let (remaining, _) = one_of("_^")(input)?;
pre(input)?; pre(input)?;
if tag::<_, _, CustomError<_>>("*")(remaining).is_ok() { if tag::<_, _, CustomError>("*")(remaining).is_ok() {
return Ok((input, ())); return Ok((input, ()));
} }
let (remaining, _) = opt(one_of("+-"))(remaining)?; let (remaining, _) = opt(one_of("+-"))(remaining)?;
@@ -232,9 +231,9 @@ fn _script_with_braces_end<'b, 'g, 'r, 's>(
let current_depth = input.get_brace_depth() - starting_brace_depth; let current_depth = input.get_brace_depth() - starting_brace_depth;
if current_depth > 0 { if current_depth > 0 {
// Its impossible for the next character to end the subscript or superscript if we're any amount of braces deep // Its impossible for the next character to end the subscript or superscript if we're any amount of braces deep
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::Static(
"Not a valid end for subscript or superscript.", "Not a valid end for subscript or superscript.",
)))); )));
} }
if current_depth < 0 { if current_depth < 0 {
// This shouldn't be possible because if depth is 0 then a closing brace should end the subscript or superscript. // This shouldn't be possible because if depth is 0 then a closing brace should end the subscript or superscript.
@@ -282,12 +281,12 @@ fn _script_with_parenthesis_end<'s>(
unreachable!("Exceeded citation key suffix bracket depth.") unreachable!("Exceeded citation key suffix bracket depth.")
} }
if current_depth == 0 { if current_depth == 0 {
let close_parenthesis = tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>(")")(input); let close_parenthesis = tag::<_, _, CustomError>(")")(input);
if close_parenthesis.is_ok() { if close_parenthesis.is_ok() {
return close_parenthesis; return close_parenthesis;
} }
} }
Err(nom::Err::Error(CustomError::MyError(MyError( Err(nom::Err::Error(CustomError::Static(
"No script parenthesis end.", "No script parenthesis end.",
)))) )))
} }

View File

@@ -13,7 +13,6 @@ use crate::context::ExitClass;
use crate::context::ExitMatcherNode; use crate::context::ExitMatcherNode;
use crate::context::RefContext; use crate::context::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
use crate::parser::util::get_consumed; use crate::parser::util::get_consumed;
use crate::types::Target; use crate::types::Target;
@@ -42,9 +41,9 @@ pub(crate) fn target<'b, 'g, 'r, 's>(
.get_preceding_character() .get_preceding_character()
.expect("We cannot be at the start of the file because we are inside a target."); .expect("We cannot be at the start of the file because we are inside a target.");
if preceding_character.is_whitespace() { if preceding_character.is_whitespace() {
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::Static(
"Targets cannot end with whitespace.", "Targets cannot end with whitespace.",
)))); )));
} }
let (remaining, _) = tag(">>")(remaining)?; let (remaining, _) = tag(">>")(remaining)?;
let (remaining, _trailing_whitespace) = let (remaining, _trailing_whitespace) =

View File

@@ -34,7 +34,6 @@ use crate::context::ExitMatcherNode;
use crate::context::List; use crate::context::List;
use crate::context::RefContext; use crate::context::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
use crate::parser::radio_link::rematch_target; use crate::parser::radio_link::rematch_target;
use crate::parser::util::exit_matcher_parser; use crate::parser::util::exit_matcher_parser;
@@ -234,9 +233,9 @@ fn _text_markup_object<'b, 'g, 'r, 's, 'c>(
#[cfg(feature = "tracing")] #[cfg(feature = "tracing")]
let _enter = span.enter(); let _enter = span.enter();
if exit_matcher_parser(context, remaining).is_ok() { if exit_matcher_parser(context, remaining).is_ok() {
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::Static(
"Parent exit matcher is triggering.", "Parent exit matcher is triggering.",
)))); )));
} }
} }
@@ -290,9 +289,9 @@ fn _text_markup_string<'b, 'g, 'r, 's, 'c>(
#[cfg(feature = "tracing")] #[cfg(feature = "tracing")]
let _enter = span.enter(); let _enter = span.enter();
if exit_matcher_parser(context, remaining).is_ok() { if exit_matcher_parser(context, remaining).is_ok() {
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::Static(
"Parent exit matcher is triggering.", "Parent exit matcher is triggering.",
)))); )));
} }
} }
@@ -321,9 +320,9 @@ fn pre<'b, 'g, 'r, 's>(
// If None, we are at the start of the file which is technically the beginning of a line. // If None, we are at the start of the file which is technically the beginning of a line.
Some('-') | Some('(') | Some('{') | Some('\'') | Some('"') => {} Some('-') | Some('(') | Some('{') | Some('\'') | Some('"') => {}
Some(_) => { Some(_) => {
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::Static(
"Not a valid pre character for text markup.", "Not a valid pre character for text markup.",
)))); )));
} }
None => unreachable!(), // None is for start of file, which should already be handled by the start_of_line matcher above. None => unreachable!(), // None is for start of file, which should already be handled by the start_of_line matcher above.
}; };
@@ -360,9 +359,9 @@ fn _text_markup_end<'b, 'g, 'r, 's, 'c>(
contents_start_offset: usize, contents_start_offset: usize,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
if input.get_byte_offset() == contents_start_offset { if input.get_byte_offset() == contents_start_offset {
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::Static(
"Text markup cannot be empty", "Text markup cannot be empty",
)))); )));
} }
not(preceded_by_whitespace(false))(input)?; not(preceded_by_whitespace(false))(input)?;
let (remaining, _marker) = terminated( let (remaining, _marker) = terminated(
@@ -495,9 +494,9 @@ fn _rematch_text_markup_object<'b, 'g, 'r, 's, 'x>(
#[cfg(feature = "tracing")] #[cfg(feature = "tracing")]
let _enter = span.enter(); let _enter = span.enter();
if exit_matcher_parser(context, remaining).is_ok() { if exit_matcher_parser(context, remaining).is_ok() {
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::Static(
"Parent exit matcher is triggering.", "Parent exit matcher is triggering.",
)))); )));
} }
} }

View File

@@ -20,7 +20,6 @@ use crate::context::parser_with_context;
use crate::context::ContextElement; use crate::context::ContextElement;
use crate::context::RefContext; use crate::context::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
use crate::types::IndentationLevel; use crate::types::IndentationLevel;
@@ -129,9 +128,7 @@ pub(crate) fn start_of_line<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()>
if input.is_at_start_of_line() { if input.is_at_start_of_line() {
Ok((input, ())) Ok((input, ()))
} else { } else {
Err(nom::Err::Error(CustomError::MyError(MyError( Err(nom::Err::Error(CustomError::Static("Not at start of line")))
"Not at start of line",
))))
} }
} }
@@ -152,9 +149,9 @@ fn _preceded_by_whitespace<'s>(
.map(|c| c.is_whitespace() || c == '\u{200B}') // 200B = Zero-width space .map(|c| c.is_whitespace() || c == '\u{200B}') // 200B = Zero-width space
.unwrap_or(allow_start_of_file) .unwrap_or(allow_start_of_file)
{ {
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::Static(
"Must be preceded by a whitespace character.", "Must be preceded by a whitespace character.",
)))); )));
} }
Ok((input, ())) Ok((input, ()))
} }
@@ -195,9 +192,7 @@ pub(crate) fn text_until_exit<'b, 'g, 'r, 's>(
#[allow(dead_code)] #[allow(dead_code)]
fn not_yet_implemented() -> Res<OrgSource<'static>, ()> { fn not_yet_implemented() -> Res<OrgSource<'static>, ()> {
Err(nom::Err::Error(CustomError::MyError(MyError( Err(nom::Err::Error(CustomError::Static("Not implemented yet.")))
"Not implemented yet.",
))))
} }
#[allow(dead_code)] #[allow(dead_code)]
@@ -205,9 +200,7 @@ fn not_yet_implemented() -> Res<OrgSource<'static>, ()> {
/// Text from the current point until the next line break or end of file /// Text from the current point until the next line break or end of file
/// ///
/// Useful for debugging. /// Useful for debugging.
fn text_until_eol<'r, 's>( fn text_until_eol<'r, 's>(input: OrgSource<'s>) -> Result<&'s str, nom::Err<CustomError>> {
input: OrgSource<'s>,
) -> Result<&'s str, nom::Err<CustomError<OrgSource<'s>>>> {
let line = recognize(many_till(anychar, alt((line_ending, eof))))(input) let line = recognize(many_till(anychar, alt((line_ending, eof))))(input)
.map(|(_remaining, line)| Into::<&str>::into(line))?; .map(|(_remaining, line)| Into::<&str>::into(line))?;
Ok(line.trim()) Ok(line.trim())
@@ -250,6 +243,10 @@ pub(crate) fn org_line_ending(input: OrgSource<'_>) -> Res<OrgSource<'_>, OrgSou
} }
/// Match the whitespace at the beginning of a line and give it an indentation level. /// Match the whitespace at the beginning of a line and give it an indentation level.
#[cfg_attr(
feature = "tracing",
tracing::instrument(ret, level = "debug", skip(context))
)]
pub(crate) fn indentation_level<'s>( pub(crate) fn indentation_level<'s>(
context: RefContext<'_, '_, '_, 's>, context: RefContext<'_, '_, '_, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,

View File

@@ -22,7 +22,6 @@ use super::CenterBlock;
use super::Drawer; use super::Drawer;
use super::GetStandardProperties; use super::GetStandardProperties;
use super::QuoteBlock; use super::QuoteBlock;
use super::SetSource;
use super::SpecialBlock; use super::SpecialBlock;
use super::StandardProperties; use super::StandardProperties;
@@ -55,38 +54,6 @@ pub enum Element<'s> {
LatexEnvironment(LatexEnvironment<'s>), LatexEnvironment(LatexEnvironment<'s>),
} }
impl<'s> SetSource<'s> for Element<'s> {
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn set_source(&mut self, source: &'s str) {
match self {
Element::Paragraph(obj) => obj.source = source,
Element::PlainList(obj) => obj.source = source,
Element::CenterBlock(obj) => obj.source = source,
Element::QuoteBlock(obj) => obj.source = source,
Element::SpecialBlock(obj) => obj.source = source,
Element::DynamicBlock(obj) => obj.source = source,
Element::FootnoteDefinition(obj) => obj.source = source,
Element::Comment(obj) => obj.source = source,
Element::Drawer(obj) => obj.source = source,
Element::PropertyDrawer(obj) => obj.source = source,
Element::Table(obj) => obj.source = source,
Element::VerseBlock(obj) => obj.source = source,
Element::CommentBlock(obj) => obj.source = source,
Element::ExampleBlock(obj) => obj.source = source,
Element::ExportBlock(obj) => obj.source = source,
Element::SrcBlock(obj) => obj.source = source,
Element::Clock(obj) => obj.source = source,
Element::DiarySexp(obj) => obj.source = source,
Element::Planning(obj) => obj.source = source,
Element::FixedWidthArea(obj) => obj.source = source,
Element::HorizontalRule(obj) => obj.source = source,
Element::Keyword(obj) => obj.source = source,
Element::BabelCall(obj) => obj.source = source,
Element::LatexEnvironment(obj) => obj.source = source,
}
}
}
impl<'s> GetStandardProperties<'s> for Element<'s> { impl<'s> GetStandardProperties<'s> for Element<'s> {
fn get_standard_properties<'b>(&'b self) -> &'b dyn StandardProperties<'s> { fn get_standard_properties<'b>(&'b self) -> &'b dyn StandardProperties<'s> {
match self { match self {

View File

@@ -169,11 +169,11 @@ impl<'s> Paragraph<'s> {
/// Generate a paragraph of the passed in text with no additional properties. /// Generate a paragraph of the passed in text with no additional properties.
/// ///
/// This is used for elements that support an "empty" content like greater blocks. /// This is used for elements that support an "empty" content like greater blocks.
pub(crate) fn of_text(input: &'s str) -> Self { pub(crate) fn of_text(source: &'s str, body: &'s str) -> Self {
Paragraph { Paragraph {
source: input, source,
affiliated_keywords: AffiliatedKeywords::default(), affiliated_keywords: AffiliatedKeywords::default(),
children: vec![Object::PlainText(PlainText { source: input })], children: vec![Object::PlainText(PlainText { source: body })],
} }
} }
} }

View File

@@ -7,14 +7,13 @@ mod greater_element;
mod lesser_element; mod lesser_element;
mod macros; mod macros;
mod object; mod object;
mod source;
mod standard_properties; mod standard_properties;
mod util; mod util;
pub use affiliated_keyword::AffiliatedKeyword; pub use affiliated_keyword::AffiliatedKeyword;
pub use affiliated_keyword::AffiliatedKeywordValue; pub use affiliated_keyword::AffiliatedKeywordValue;
pub use affiliated_keyword::AffiliatedKeywords; pub use affiliated_keyword::AffiliatedKeywords;
pub use affiliated_keyword::GetAffiliatedKeywords; pub use affiliated_keyword::GetAffiliatedKeywords;
pub(crate) use ast_node::AstNode; pub use ast_node::AstNode;
pub use document::Document; pub use document::Document;
pub use document::DocumentElement; pub use document::DocumentElement;
pub use document::Heading; pub use document::Heading;
@@ -113,5 +112,4 @@ pub use object::WarningDelay;
pub use object::WarningDelayType; pub use object::WarningDelayType;
pub use object::Year; pub use object::Year;
pub use object::YearInner; pub use object::YearInner;
pub(crate) use source::SetSource;
pub use standard_properties::StandardProperties; pub use standard_properties::StandardProperties;

View File

@@ -1,3 +0,0 @@
pub(crate) trait SetSource<'s> {
fn set_source(&mut self, source: &'s str);
}

View File

@@ -78,7 +78,7 @@ enum RemoveLineBreakState {
/// Removes all whitespace from a string if any line breaks are present. /// Removes all whitespace from a string if any line breaks are present.
/// ///
/// Example: "foo bar" => "foo bar" but "foo \n bar" => "foobar". /// Example: "foo bar" => "foo bar" but "foo \n bar" => "foo bar".
pub(crate) fn coalesce_whitespace_if_line_break(input: &str) -> Cow<'_, str> { pub(crate) fn coalesce_whitespace_if_line_break(input: &str) -> Cow<'_, str> {
let mut state = CoalesceWhitespaceIfLineBreakState::Normal; let mut state = CoalesceWhitespaceIfLineBreakState::Normal;
for (offset, c) in input.char_indices() { for (offset, c) in input.char_indices() {
@@ -515,4 +515,25 @@ mod tests {
assert!(matches!(output, Cow::Borrowed(_))); assert!(matches!(output, Cow::Borrowed(_)));
Ok(()) Ok(())
} }
#[test]
fn test_coalesce_whitespace_if_line_break() -> Result<(), Box<dyn std::error::Error>> {
assert_eq!(coalesce_whitespace_if_line_break("foo bar"), "foo bar");
assert_eq!(coalesce_whitespace_if_line_break("foo \n bar"), "foo bar");
Ok(())
}
#[test]
fn test_remove_whitespace_if_line_break() -> Result<(), Box<dyn std::error::Error>> {
assert_eq!(remove_whitespace_if_line_break("foo bar"), "foo bar");
assert_eq!(remove_whitespace_if_line_break("foo \n bar"), "foobar");
Ok(())
}
#[test]
fn test_remove_line_break() -> Result<(), Box<dyn std::error::Error>> {
assert_eq!(remove_line_break("foo bar"), "foo bar");
assert_eq!(remove_line_break("foo \n bar"), "foo bar");
Ok(())
}
} }