40 Commits

Author SHA1 Message Date
Tom Alexander
678106bb65 Use tekton matrix for specifying all combinations of features for the build CI job.
All checks were successful
rust-test Build rust-test has succeeded
2023-08-25 03:56:08 -04:00
Tom Alexander
19432d91ab Get the emacs and org-mode versions when launching the compare script.
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-08-25 03:11:19 -04:00
Tom Alexander
16a107eebb Update org-mode version. 2023-08-25 02:56:28 -04:00
Tom Alexander
77348b560c Parameterize the emacs and org-mode versions in the dockerfiles.
All checks were successful
rustfmt Build rustfmt has succeeded
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
2023-08-25 02:03:35 -04:00
Tom Alexander
fc79507ef3 Merge branch 'plain_list_perf_investigation'
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-08-25 01:10:04 -04:00
Tom Alexander
9c1e6ccc97 Add a detect_element function.
All checks were successful
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
This is an optimization. When you have something like plain text which ends when it hits the next element, we only need to parse enough to detect that an element is about to occur. For elements like plain lists, this is as simple as parsing a line starting with optional whitespace and then a bullet, which avoids parsing the entire plain list tree. The benefit is most noticeable in deeply nested plain lists.
2023-08-25 01:07:53 -04:00
Tom Alexander
0dbc8f0925 Remove redundant exit matcher checks.
All checks were successful
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-08-25 00:01:56 -04:00
Tom Alexander
02fe10fba3 Move objects to a lower exit class.
All checks were successful
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
Paragraph's exit matcher which detects elements was causing the plain list parser to exit after the first item was parsed which was causing significant amounts of re-parsing.
2023-08-24 23:34:23 -04:00
Tom Alexander
33d7ae03d1 Add a TODO.
All checks were successful
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-08-24 21:35:34 -04:00
Tom Alexander
03faa7257f Move the indent level for plain list's exit matcher to const fn instead of grabbing from the context.
All checks were successful
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
This made a slight improvement to performance.
2023-08-24 20:50:24 -04:00
Tom Alexander
ae3510abd5 Do not cast lesser block name to lowercase at runtime.
This reduced the runtime of my problematic test case from 6.9 seconds to 6 seconds.
2023-08-24 20:10:43 -04:00
fluxcdbot
ad3f47864a CI: autofix rust code.
Some checks failed
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has failed
2023-08-24 23:43:41 +00:00
Tom Alexander
533ef2a9a8 Merge branch 'wrapped_input'
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-08-24 19:40:55 -04:00
Tom Alexander
cf37bc4111 Remove unnecessary context from some util functions.
All checks were successful
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-08-24 19:29:00 -04:00
Tom Alexander
e5224cda63 Removing dead code.
All checks were successful
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-08-24 18:40:25 -04:00
Tom Alexander
64e3481660 Update get_consumed to use the new wrapped input type. 2023-08-24 18:33:40 -04:00
Tom Alexander
32071ce74d Fix handling of start of line in OrgSource.
All checks were successful
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-08-24 18:08:16 -04:00
Tom Alexander
e84e2b5147 Update tests to compile again. 2023-08-24 17:15:24 -04:00
Tom Alexander
3348807a05 Eliminate the document root context element. 2023-08-24 17:01:12 -04:00
Tom Alexander
720afa5d32 Update getting the previous character and previous line.
This can be done a lot more efficiently now that we are keeping track of this information in the wrapped input type instead of having to fetch to the original document out of the context tree.
2023-08-24 16:56:07 -04:00
Tom Alexander
dab598e5e7 Convert all functions to using the wrapped input type.
Some checks failed
rust-test Build rust-test has failed
rust-build Build rust-build has failed
2023-08-24 16:06:29 -04:00
Tom Alexander
b7a5dd48ea Impl missing traits. 2023-08-22 23:32:27 -04:00
Tom Alexander
c475dce6da Fix lifetime issue. 2023-08-22 23:14:23 -04:00
Tom Alexander
6d1675fa00 Lifetime issue. 2023-08-22 22:57:44 -04:00
Tom Alexander
cda49c628c Move the wrapped input into the parser. 2023-08-22 22:33:50 -04:00
Tom Alexander
65b87bd65d Merge remote-tracking branch 'input/main' into wrapped_input 2023-08-22 22:26:55 -04:00
Tom Alexander
5a7f34b63e Prepare for merging into Organic. 2023-08-22 22:24:35 -04:00
Tom Alexander
edff1e089d Implement text since line break. 2023-08-22 22:18:44 -04:00
Tom Alexander
bc29f1dfc0 Add slicing tests. 2023-08-22 21:38:50 -04:00
Tom Alexander
e4656cddf6 Implement slice, take, and compare. 2023-08-22 21:25:13 -04:00
Tom Alexander
1e3dadd458 Wrap the input. 2023-08-22 17:24:26 -04:00
Tom Alexander
2ec055af5a Very simple setup. 2023-08-22 17:22:13 -04:00
Tom Alexander
6823db5c60 Initial commit. 2023-08-22 17:11:45 -04:00
Tom Alexander
21e1ceb8e0 Merge branch 'add_performance_check_scripts'
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-08-22 14:21:44 -04:00
Tom Alexander
655af88cdf Add scripts for running perf and callgrind. 2023-08-22 14:21:27 -04:00
Tom Alexander
8561fdc1bd Make the autogen prefix fully integrated into the test name.
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-08-21 00:14:10 -04:00
Tom Alexander
f2089257b0 Re-enable disabled test.
All checks were successful
rustfmt Build rustfmt has succeeded
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
The latest code in org-mode has been fixed.
2023-08-21 00:08:26 -04:00
Tom Alexander
09821c8898 Prefix the automatically generated tests.
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-08-20 23:53:11 -04:00
Tom Alexander
69ecfd2646 Move all the specific-token tests into subfolders.
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-08-20 23:38:47 -04:00
Tom Alexander
8162f03051 Put all trailing whitespace ownership test cases into the automated tests.
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
Notes for this investigation moved to cba1d1e988/notes/plain_list_ownership_notes.org .

Mailing list thread on the investigation: https://list.orgmode.org/9372527e-3852-419e-936a-7b4dd38cc847@app.fastmail.com/ .
2023-08-20 16:03:31 -04:00
130 changed files with 2045 additions and 1213 deletions

View File

@@ -1,3 +1,4 @@
**/.git
target
Cargo.lock
notes/

View File

@@ -14,10 +14,6 @@ spec:
- name: path-to-dockerfile
description: The path to the Dockerfile
type: string
- name: command
type: array
description: Command to run.
default: []
tasks:
- name: report-pending
taskRef:
@@ -81,9 +77,19 @@ spec:
workspace: docker-credentials
runAfter:
- fetch-repository
- name: run-image-none
- name: build-organic
taskRef:
name: run-docker-image
matrix:
params:
- name: feature-compare
value:
- "true"
- "false"
- name: feature-tracing
value:
- "true"
- "false"
workspaces:
- name: source
workspace: git-source
@@ -93,77 +99,21 @@ spec:
- build-image
params:
- name: command
value: ["$(params.command[*])"]
value: ["/bin/sh", "-c"]
- name: args
value: ["--no-default-features"]
- name: docker-image
value: "$(params.image-name):$(tasks.fetch-repository.results.commit)"
- name: run-image-tracing
taskRef:
name: run-docker-image
workspaces:
- name: source
workspace: git-source
- name: cargo-cache
workspace: cargo-cache
runAfter:
- run-image-none
params:
- name: command
value: ["$(params.command[*])"]
- name: args
value: ["--no-default-features", "--features", "tracing"]
- name: docker-image
value: "$(params.image-name):$(tasks.fetch-repository.results.commit)"
- name: run-image-compare
taskRef:
name: run-docker-image
workspaces:
- name: source
workspace: git-source
- name: cargo-cache
workspace: cargo-cache
runAfter:
- run-image-tracing
params:
- name: command
value: ["$(params.command[*])"]
- name: args
value: ["--no-default-features", "--features", "compare"]
- name: docker-image
value: "$(params.image-name):$(tasks.fetch-repository.results.commit)"
- name: run-image-default
taskRef:
name: run-docker-image
workspaces:
- name: source
workspace: git-source
- name: cargo-cache
workspace: cargo-cache
runAfter:
- run-image-compare
params:
- name: command
value: ["$(params.command[*])"]
- name: args
value: []
- name: docker-image
value: "$(params.image-name):$(tasks.fetch-repository.results.commit)"
- name: run-image-all
taskRef:
name: run-docker-image
workspaces:
- name: source
workspace: git-source
- name: cargo-cache
workspace: cargo-cache
runAfter:
- run-image-default
params:
- name: command
value: ["$(params.command[*])"]
- name: args
value: ["--no-default-features", "--features", "tracing,compare"]
value:
- |
set -euo pipefail
IFS=$$'\n\t'
features=()
if [ $(params.feature-compare) = "true" ]; then features+=(compare); fi
if [ $(params.feature-tracing) = "true" ]; then features+=(tracing); fi
if [ $${#features[@]} -eq 0 ]; then
exec cargo build --no-default-features
else
featurelist=$$(IFS="," ; echo "$${features[*]}")
exec cargo build --no-default-features --features "$$featurelist"
fi
- name: docker-image
value: "$(params.image-name):$(tasks.fetch-repository.results.commit)"
finally:
@@ -256,5 +206,3 @@ spec:
value: docker/organic_build/
- name: path-to-dockerfile
value: docker/organic_build/Dockerfile
- name: command
value: [cargo, build]

View File

@@ -40,10 +40,16 @@ tracing-subscriber = { version = "0.3.17", optional = true, features = ["env-fil
walkdir = "2.3.3"
[features]
default = ["compare", "tracing"]
default = ["compare"]
compare = []
tracing = ["dep:opentelemetry", "dep:opentelemetry-otlp", "dep:opentelemetry-semantic-conventions", "dep:tokio", "dep:tracing", "dep:tracing-opentelemetry", "dep:tracing-subscriber"]
[profile.release]
[profile.release-lto]
inherits = "release"
lto = true
strip = "symbols"
[profile.perf]
inherits = "release"
lto = true
debug = true

View File

@@ -41,6 +41,7 @@ fn write_test(test_file: &mut File, test: &walkdir::DirEntry) {
.strip_suffix(".org")
.expect("Should have .org extension")
.replace("/", "_");
let test_name = format!("autogen_{}", test_name);
if let Some(_reason) = is_expect_fail(test_name.as_str()) {
write!(test_file, "#[ignore]\n").unwrap();
@@ -71,11 +72,9 @@ use organic::parser::sexp::sexp_with_padding;
fn is_expect_fail(name: &str) -> Option<&str> {
match name {
"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."),
"paragraphs_paragraph_with_backslash_line_breaks" => Some("The text we're getting out of the parse tree is already processed to remove line breaks, so our comparison needs to take that into account."),
"export_snippet_paragraph_break_precedence" => Some("The latest code for org-mode is matching the export snippet without the closing @@."), // https://list.orgmode.org/orgmode/fb61ea28-f004-4c25-adf7-69fc55683ed4@app.fastmail.com/T/#u
"plain_lists_trailing_whitespace_ownership" => Some("Seeing odd behavior about whitespace ownership."),
"autogen_greater_element_drawer_drawer_with_headline_inside" => Some("Apparently lines with :end: become their own paragraph. This odd behavior needs to be investigated more."),
"autogen_element_container_priority_footnote_definition_dynamic_block" => Some("Apparently broken begin lines become their own paragraph."),
"autogen_lesser_element_paragraphs_paragraph_with_backslash_line_breaks" => Some("The text we're getting out of the parse tree is already processed to remove line breaks, so our comparison needs to take that into account."),
_ => None,
}
}

View File

@@ -6,7 +6,7 @@ all: build push
.PHONY: build
build:
docker build -t $(IMAGE_NAME) -f Dockerfile ../
docker build -t $(IMAGE_NAME) -f Dockerfile ../../
.PHONY: push
push:

View File

@@ -6,7 +6,7 @@ all: build push
.PHONY: build
build:
docker build -t $(IMAGE_NAME) -f Dockerfile ../
docker build -t $(IMAGE_NAME) -f Dockerfile ../../
.PHONY: push
push:

View File

@@ -1,11 +1,10 @@
FROM alpine:3.17 AS build
RUN apk add --no-cache build-base musl-dev git autoconf make texinfo gnutls-dev ncurses-dev gawk
FROM build AS build-emacs
RUN git clone --depth 1 --branch emacs-29.1 https://git.savannah.gnu.org/git/emacs.git /root/emacs
ARG EMACS_VERSION=emacs-29.1
RUN git clone --depth 1 --branch $EMACS_VERSION https://git.savannah.gnu.org/git/emacs.git /root/emacs
WORKDIR /root/emacs
RUN mkdir /root/dist
RUN ./autogen.sh
@@ -15,9 +14,12 @@ RUN make DESTDIR="/root/dist" install
FROM build AS build-org-mode
ARG ORG_VERSION=7bdec435ff5d86220d13c431e799c5ed44a57da1
COPY --from=build-emacs /root/dist/ /
RUN mkdir /root/dist
RUN mkdir /root/org-mode && git -C /root/org-mode init --initial-branch=main && git -C /root/org-mode remote add origin https://git.savannah.gnu.org/git/emacs/org-mode.git && git -C /root/org-mode fetch origin 3cbd9f423385bf725dc964a5cff573bba17db3ff && git -C /root/org-mode checkout FETCH_HEAD
# Savannah does not allow fetching specific revisions, so we're going to have to put unnecessary load on their server by cloning main and then checking out the revision we want.
RUN git clone https://git.savannah.gnu.org/git/emacs/org-mode.git /root/org-mode && git -C /root/org-mode checkout $ORG_VERSION
# RUN mkdir /root/org-mode && git -C /root/org-mode init --initial-branch=main && git -C /root/org-mode remote add origin https://git.savannah.gnu.org/git/emacs/org-mode.git && git -C /root/org-mode fetch origin $ORG_VERSION && git -C /root/org-mode checkout FETCH_HEAD
WORKDIR /root/org-mode
RUN make compile
RUN make DESTDIR="/root/dist" install

View File

@@ -6,7 +6,7 @@ all: build push
.PHONY: build
build:
docker build -t $(IMAGE_NAME) -f Dockerfile ../
docker build -t $(IMAGE_NAME) -f Dockerfile ../../
.PHONY: push
push:

0
foo Normal file
View File

View File

@@ -1,130 +0,0 @@
* Test 1
** Source
#+begin_src org
1. foo
1. bar
2. baz
2. lorem
ipsum
#+end_src
** Ownership
This table is just showing ownership for the plain list items, not the containing plain list nor the elements inside each item.
| Plain List *Item* | Owns trailing blank lines |
|------------------------+---------------------------|
| foo (includes bar baz) | Yes |
| bar | Yes |
| baz | Yes |
| lorem | No |
** Analysis
In this test case, we see that the only list item that doesn't own its trailing blank lines is "lorem", the final list item of the outer-most list.
* Test 2
We add "cat" as a paragraph at the end of foo which makes "baz" lose its trailing blank lines.
** Source
#+begin_src org
1. foo
1. bar
2. baz
cat
2. lorem
ipsum
#+end_src
** Ownership
| Plain List *Item* | Owns trailing blank lines |
|-------------------------------+---------------------------|
| foo -> cat (includes bar baz) | Yes |
| bar | Yes |
| baz | No |
| lorem | No |
** Analysis
In isolation, this implies that the final plain list item does not own its trailing blank lines, which conflicts with "baz" from test 1.
New theory: List items own their trailing blank lines unless they are both the final list item and not the final element of a list item.
| Plain List *Item* | Owns trailing blank lines | Why |
|-------------------------------+---------------------------+-----------------------------------------------------------|
| foo -> cat (includes bar baz) | Yes | Not the final list item |
| bar | Yes | Not the final list item |
| baz | No | Final item of bar->baz and not the final element of "foo" |
| lorem | No | Final item of foo->lorem and not contained in a list item |
* Test 3
So if that theory is true, taking the entire (foo -> lorem) list from test 1 and nesting it inside a list should coerce "lorem" to own its trailing blank lines since it would then be a final list item (of foo -> lorem) and the final element of the new list.
** Source
#+begin_src org
1. cat
1. foo
1. bar
2. baz
2. lorem
ipsum
#+end_src
** Ownership
| Plain List *Item* | Owns trailing blank lines |
|-----------------------------+---------------------------|
| cat (includes foo -> lorem) | No |
| foo (includes bar baz) | Yes |
| bar | Yes |
| baz | Yes |
| lorem | No |
** Analysis
Against expectations, we did not coerce lorem to consume its trailing blank lines. What is different between "baz" and "lorem"? Well, "baz" is contained within "foo" which has a "lorem" after it, whereas "lorem" is contained within "cat" which does not have any list items after it.
New theory: List items own their trailing blank lines unless they are both the final list item and not the final element of a non-final list item.
| Plain List *Item* | Owns trailing blank lines | Why |
|-----------------------------+---------------------------+------------------------------------------------------|
| cat (includes foo -> lorem) | No | Final list item and not contained in a list item |
| foo (includes bar baz) | Yes | Not the final list item |
| bar | Yes | Not the final list item |
| baz | Yes | Final element of non-final list item |
| lorem | No | Final list item and final element of final list item |
* Test 4
So if that theory is true, then we should be able to coerce lorem to consume its trailing blank lines by adding a second item to the cat list.
** Source
#+begin_src org
1. cat
1. foo
1. bar
2. baz
2. lorem
2. dog
ipsum
#+end_src
** Ownership
| Plain List *Item* | Owns trailing blank lines |
|-----------------------------+---------------------------|
| cat (includes foo -> lorem) | Yes |
| foo (includes bar baz) | Yes |
| bar | Yes |
| baz | Yes |
| lorem | Yes |
| dog | No |
** Analysis
For the first time our expectations were met!
Enduring theory: List items own their trailing blank lines unless they are both the final list item and not the final element of a non-final list item.
| Plain List *Item* | Owns trailing blank lines | Why |
|-----------------------------+---------------------------+--------------------------------------------------|
| cat (includes foo -> lorem) | Yes | Not the final list item |
| foo (includes bar baz) | Yes | Not the final list item |
| bar | Yes | Not the final list item |
| baz | Yes | Final element of non-final list item |
| lorem | Yes | Final element of non-final list item |
| dog | No | Final list item and not contained in a list item |

View File

@@ -0,0 +1,11 @@
1. foo
1. bar
2. baz
cat
2. lorem
ipsum

View File

@@ -0,0 +1,10 @@
1. cat
1. foo
1. bar
2. baz
2. lorem
ipsum

View File

@@ -0,0 +1,12 @@
1. cat
1. foo
1. bar
2. baz
2. lorem
2. dog
ipsum

13
scripts/callgrind.bash Executable file
View File

@@ -0,0 +1,13 @@
#!/usr/bin/env bash
#
set -euo pipefail
IFS=$'\n\t'
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
cd "$DIR/../"
RUSTFLAGS="-C opt-level=0" cargo build --no-default-features
valgrind --tool=callgrind --callgrind-out-file=callgrind.out target/debug/compare
echo "You probably want to run:"
echo "callgrind_annotate --auto=yes callgrind.out"

18
scripts/perf.bash Executable file
View File

@@ -0,0 +1,18 @@
#!/usr/bin/env bash
#
set -euo pipefail
IFS=$'\n\t'
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
: ${PROFILE:="perf"}
cd "$DIR/../"
cargo build --profile "$PROFILE" --no-default-features
perf record --freq=2000 --call-graph dwarf --output=perf.data target/${PROFILE}/compare
# Convert to a format firefox will read
# flags to consider --show-info
perf script -F +pid --input perf.data > perf.firefox
echo "You probably want to go to https://profiler.firefox.com/"
echo "Either that or run hotspot"

View File

@@ -3,3 +3,5 @@ mod parse;
mod util;
pub use diff::compare_document;
pub use parse::emacs_parse_org_document;
pub use parse::get_emacs_version;
pub use parse::get_org_mode_version;

View File

@@ -49,3 +49,40 @@ where
}
output
}
pub fn get_emacs_version() -> Result<String, Box<dyn std::error::Error>> {
let elisp_script = r#"(progn
(message "%s" (version))
)"#;
let mut cmd = Command::new("emacs");
let proc = cmd
.arg("-q")
.arg("--no-site-file")
.arg("--no-splash")
.arg("--batch")
.arg("--eval")
.arg(elisp_script);
let out = proc.output()?;
out.status.exit_ok()?;
Ok(String::from_utf8(out.stderr)?)
}
pub fn get_org_mode_version() -> Result<String, Box<dyn std::error::Error>> {
let elisp_script = r#"(progn
(org-mode)
(message "%s" (org-version nil t nil))
)"#;
let mut cmd = Command::new("emacs");
let proc = cmd
.arg("-q")
.arg("--no-site-file")
.arg("--no-splash")
.arg("--batch")
.arg("--eval")
.arg(elisp_script);
let out = proc.output()?;
out.status.exit_ok()?;
Ok(String::from_utf8(out.stderr)?)
}

View File

@@ -42,6 +42,39 @@ pub fn assert_bounds<'s, S: Source<'s>>(
emacs: &'s Token<'s>,
rust: &'s S,
) -> Result<(), Box<dyn std::error::Error>> {
let standard_properties = get_standard_properties(emacs)?;
let (begin, end) = (
standard_properties
.begin
.ok_or("Token should have a begin.")?,
standard_properties
.end
.ok_or("Token should have a begin.")?,
);
let (rust_begin, rust_end) = get_offsets(source, rust);
if (rust_begin + 1) != begin || (rust_end + 1) != end {
Err(format!("Rust bounds ({rust_begin}, {rust_end}) do not match emacs bounds ({emacs_begin}, {emacs_end})", rust_begin = rust_begin + 1, rust_end = rust_end + 1, emacs_begin=begin, emacs_end=end))?;
}
Ok(())
}
struct StandardProperties {
begin: Option<usize>,
#[allow(dead_code)]
post_affiliated: Option<usize>,
#[allow(dead_code)]
contents_begin: Option<usize>,
#[allow(dead_code)]
contents_end: Option<usize>,
end: Option<usize>,
#[allow(dead_code)]
post_blank: Option<usize>,
}
fn get_standard_properties<'s>(
emacs: &'s Token<'s>,
) -> Result<StandardProperties, Box<dyn std::error::Error>> {
let children = emacs.as_list()?;
let attributes_child = children
.iter()
@@ -49,34 +82,60 @@ pub fn assert_bounds<'s, S: Source<'s>>(
.ok_or("Should have an attributes child.")?;
let attributes_map = attributes_child.as_map()?;
let standard_properties = attributes_map.get(":standard-properties");
let (begin, end) = if standard_properties.is_some() {
let std_props = standard_properties
Ok(if standard_properties.is_some() {
let mut std_props = standard_properties
.expect("if statement proves its Some")
.as_vector()?;
let begin = std_props
.get(0)
.ok_or("Missing first element in standard properties")?
.as_atom()?;
let end = std_props
.get(1)
.ok_or("Missing first element in standard properties")?
.as_atom()?;
(begin, end)
} else {
let begin = attributes_map
.get(":begin")
.ok_or("Missing :begin attribute.")?
.as_atom()?;
let end = attributes_map
.get(":end")
.ok_or("Missing :end attribute.")?
.as_atom()?;
(begin, end)
};
let (rust_begin, rust_end) = get_offsets(source, rust);
if (rust_begin + 1).to_string() != begin || (rust_end + 1).to_string() != end {
Err(format!("Rust bounds ({rust_begin}, {rust_end}) do not match emacs bounds ({emacs_begin}, {emacs_end})", rust_begin = rust_begin + 1, rust_end = rust_end + 1, emacs_begin=begin, emacs_end=end))?;
.as_vector()?
.into_iter();
let begin = maybe_token_to_usize(std_props.next())?;
let post_affiliated = maybe_token_to_usize(std_props.next())?;
let contents_begin = maybe_token_to_usize(std_props.next())?;
let contents_end = maybe_token_to_usize(std_props.next())?;
let end = maybe_token_to_usize(std_props.next())?;
let post_blank = maybe_token_to_usize(std_props.next())?;
StandardProperties {
begin,
post_affiliated,
contents_begin,
contents_end,
end,
post_blank,
}
Ok(())
} else {
let begin = maybe_token_to_usize(attributes_map.get(":begin").map(|token| *token))?;
let end = maybe_token_to_usize(attributes_map.get(":end").map(|token| *token))?;
let contents_begin =
maybe_token_to_usize(attributes_map.get(":contents-begin").map(|token| *token))?;
let contents_end =
maybe_token_to_usize(attributes_map.get(":contents-end").map(|token| *token))?;
let post_blank =
maybe_token_to_usize(attributes_map.get(":post-blank").map(|token| *token))?;
let post_affiliated =
maybe_token_to_usize(attributes_map.get(":post-affiliated").map(|token| *token))?;
StandardProperties {
begin,
post_affiliated,
contents_begin,
contents_end,
end,
post_blank,
}
})
}
fn maybe_token_to_usize(
token: Option<&Token<'_>>,
) -> Result<Option<usize>, Box<dyn std::error::Error>> {
Ok(token
.map(|token| token.as_atom())
.map_or(Ok(None), |r| r.map(Some))?
.map(|val| {
if val == "nil" {
None
} else {
Some(val.parse::<usize>())
}
})
.flatten() // Outer option is whether or not the param exists, inner option is whether or not it is nil
.map_or(Ok(None), |r| r.map(Some))?)
}

View File

@@ -4,6 +4,7 @@ use nom::IResult;
pub type Res<T, U> = IResult<T, U, CustomError<T>>;
// TODO: MyError probably shouldn't be based on the same type as the input type since it's used exclusively with static strings right now.
#[derive(Debug, PartialEq)]
pub enum CustomError<I> {
MyError(MyError<I>),

View File

@@ -7,6 +7,10 @@ mod compare;
pub use compare::compare_document;
#[cfg(feature = "compare")]
pub use compare::emacs_parse_org_document;
#[cfg(feature = "compare")]
pub use compare::get_emacs_version;
#[cfg(feature = "compare")]
pub use compare::get_org_mode_version;
mod error;
pub mod parser;

View File

@@ -1,14 +1,16 @@
#![feature(round_char_boundary)]
#[cfg(feature = "compare")]
use std::io::Read;
#[cfg(feature = "compare")]
use ::organic::parser::document;
#[cfg(feature = "compare")]
use organic::compare_document;
#[cfg(feature = "compare")]
use organic::emacs_parse_org_document;
#[cfg(feature = "compare")]
use organic::get_emacs_version;
#[cfg(feature = "compare")]
use organic::get_org_mode_version;
#[cfg(feature = "compare")]
use organic::parser::sexp::sexp_with_padding;
#[cfg(feature = "tracing")]
@@ -37,14 +39,10 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn main_body() -> Result<(), Box<dyn std::error::Error>> {
#[cfg(not(feature = "compare"))]
let org_contents = "";
#[cfg(feature = "compare")]
let org_contents = read_stdin_to_string()?;
run_compare(org_contents)
}
#[cfg(feature = "compare")]
fn read_stdin_to_string() -> Result<String, Box<dyn std::error::Error>> {
let mut stdin_contents = String::new();
std::io::stdin()
@@ -55,10 +53,14 @@ fn read_stdin_to_string() -> Result<String, Box<dyn std::error::Error>> {
#[cfg(feature = "compare")]
fn run_compare<P: AsRef<str>>(org_contents: P) -> Result<(), Box<dyn std::error::Error>> {
let (remaining, rust_parsed) = document(org_contents.as_ref()).expect("Org Parse failure");
let emacs_version = get_emacs_version()?;
let org_mode_version = get_org_mode_version()?;
eprintln!("Using emacs version: {}", emacs_version.trim());
eprintln!("Using org-mode version: {}", org_mode_version.trim());
let (remaining, rust_parsed) = document(org_contents.as_ref()).map_err(|e| e.to_string())?;
let org_sexp = emacs_parse_org_document(org_contents.as_ref())?;
let (_remaining, parsed_sexp) =
sexp_with_padding(org_sexp.as_str()).expect("Sexp Parse failure");
sexp_with_padding(org_sexp.as_str()).map_err(|e| e.to_string())?;
println!("{}\n\n\n", org_contents.as_ref());
println!("{}", org_sexp);
@@ -79,7 +81,11 @@ fn run_compare<P: AsRef<str>>(org_contents: P) -> Result<(), Box<dyn std::error:
}
#[cfg(not(feature = "compare"))]
fn run_compare<P: AsRef<str>>(_org_contents: P) -> Result<(), Box<dyn std::error::Error>> {
println!("This program was built with compare disabled. Doing nothing.");
fn run_compare<P: AsRef<str>>(org_contents: P) -> Result<(), Box<dyn std::error::Error>> {
eprintln!(
"This program was built with compare disabled. Only parsing with organic, not comparing."
);
let (remaining, rust_parsed) = document(org_contents.as_ref()).map_err(|e| e.to_string())?;
println!("{:#?}", rust_parsed);
Ok(())
}

View File

@@ -4,6 +4,7 @@ use nom::combinator::peek;
use nom::combinator::recognize;
use nom::multi::many_till;
use super::org_source::OrgSource;
use super::Context;
use crate::error::Res;
use crate::parser::exiting::ExitClass;
@@ -16,7 +17,10 @@ use crate::parser::util::get_consumed;
use crate::parser::AngleLink;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn angle_link<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, AngleLink<'s>> {
pub fn angle_link<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, AngleLink<'s>> {
let (remaining, _) = tag("<")(input)?;
let (remaining, proto) = protocol(context, remaining)?;
let (remaining, _separator) = tag(":")(remaining)?;
@@ -26,18 +30,21 @@ pub fn angle_link<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s s
Ok((
remaining,
AngleLink {
source,
link_type: proto,
path,
source: source.into(),
link_type: proto.into(),
path: path.into(),
},
))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn path_angle<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
fn path_angle<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
let parser_context =
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta,
class: ExitClass::Gamma,
exit_matcher: &path_angle_end,
}));
@@ -48,6 +55,9 @@ fn path_angle<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str,
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn path_angle_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
fn path_angle_end<'r, 's>(
_context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
tag(">")(input)
}

View File

@@ -11,6 +11,7 @@ use nom::multi::many_till;
use nom::multi::separated_list1;
use nom::sequence::tuple;
use super::org_source::OrgSource;
use super::Context;
use crate::error::CustomError;
use crate::error::Res;
@@ -29,7 +30,10 @@ use crate::parser::util::get_consumed;
use crate::parser::Object;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn citation<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Citation<'s>> {
pub fn citation<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Citation<'s>> {
// TODO: Despite being a standard object, citations cannot exist inside the global prefix/suffix for other citations because citations must contain something that matches @key which is forbidden inside the global prefix/suffix. This TODO is to evaluate if its worth putting in an explicit check for this (which can be easily accomplished by checking the output of `get_bracket_depth()`). I suspect its not worth it because I expect, outside of intentionally crafted inputs, this parser will exit immediately inside a citation since it is unlikely to find the "[cite" substring inside a citation global prefix/suffix.
let (remaining, _) = tag_no_case("[cite")(input)?;
let (remaining, _) = opt(citestyle)(remaining)?;
@@ -44,11 +48,16 @@ pub fn citation<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str
let (remaining, _) = tag("]")(remaining)?;
let (remaining, _) = space0(remaining)?;
let source = get_consumed(input, remaining);
Ok((remaining, Citation { source }))
Ok((
remaining,
Citation {
source: source.into(),
},
))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn citestyle<'r, 's>(input: &'s str) -> Res<&'s str, &'s str> {
fn citestyle<'r, 's>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (remaining, _) = tuple((tag("/"), style))(input)?;
let (remaining, _) = opt(tuple((tag("/"), variant)))(remaining)?;
let source = get_consumed(input, remaining);
@@ -56,14 +65,14 @@ fn citestyle<'r, 's>(input: &'s str) -> Res<&'s str, &'s str> {
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn style<'r, 's>(input: &'s str) -> Res<&'s str, &'s str> {
fn style<'r, 's>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(many1(verify(anychar, |c| {
c.is_alphanumeric() || "_-".contains(*c)
})))(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn variant<'r, 's>(input: &'s str) -> Res<&'s str, &'s str> {
fn variant<'r, 's>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(many1(verify(anychar, |c| {
c.is_alphanumeric() || "_-/".contains(*c)
})))(input)
@@ -72,8 +81,8 @@ fn variant<'r, 's>(input: &'s str) -> Res<&'s str, &'s str> {
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn global_prefix<'r, 's>(
context: Context<'r, 's>,
input: &'s str,
) -> Res<&'s str, Vec<Object<'s>>> {
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Vec<Object<'s>>> {
// TODO: I could insert CitationBracket entries in the context after each matched object to reduce the scanning done for counting brackets which should be more efficient.
let parser_context = context
.with_additional_node(ContextElement::CitationBracket(CitationBracket {
@@ -81,7 +90,7 @@ fn global_prefix<'r, 's>(
depth: 0,
}))
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta,
class: ExitClass::Gamma,
exit_matcher: &global_prefix_end,
}));
let (remaining, (children, _exit_contents)) = verify(
@@ -96,12 +105,15 @@ fn global_prefix<'r, 's>(
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn global_prefix_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
fn global_prefix_end<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
let context_depth = get_bracket_depth(context)
.expect("This function should only be called from inside a citation.");
let text_since_context_entry = get_consumed(context_depth.position, input);
let mut current_depth = context_depth.depth;
for c in text_since_context_entry.chars() {
for c in Into::<&str>::into(text_since_context_entry).chars() {
match c {
'[' => {
current_depth += 1;
@@ -116,7 +128,7 @@ fn global_prefix_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'
}
}
if current_depth == 0 {
let close_bracket = tag::<&str, &str, CustomError<&str>>("]")(input);
let close_bracket = tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>("]")(input);
if close_bracket.is_ok() {
return close_bracket;
}
@@ -130,8 +142,8 @@ fn global_prefix_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn global_suffix<'r, 's>(
context: Context<'r, 's>,
input: &'s str,
) -> Res<&'s str, Vec<Object<'s>>> {
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Vec<Object<'s>>> {
// TODO: I could insert CitationBracket entries in the context after each matched object to reduce the scanning done for counting brackets which should be more efficient.
let parser_context = context
.with_additional_node(ContextElement::CitationBracket(CitationBracket {
@@ -139,7 +151,7 @@ fn global_suffix<'r, 's>(
depth: 0,
}))
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta,
class: ExitClass::Gamma,
exit_matcher: &global_suffix_end,
}));
let (remaining, (children, _exit_contents)) = verify(
@@ -153,12 +165,15 @@ fn global_suffix<'r, 's>(
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn global_suffix_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
fn global_suffix_end<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
let context_depth = get_bracket_depth(context)
.expect("This function should only be called from inside a citation.");
let text_since_context_entry = get_consumed(context_depth.position, input);
let mut current_depth = context_depth.depth;
for c in text_since_context_entry.chars() {
for c in Into::<&str>::into(text_since_context_entry).chars() {
match c {
'[' => {
current_depth += 1;
@@ -173,7 +188,7 @@ fn global_suffix_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'
}
}
if current_depth == 0 {
let close_bracket = tag::<&str, &str, CustomError<&str>>("]")(input);
let close_bracket = tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>("]")(input);
if close_bracket.is_ok() {
return close_bracket;
}
@@ -188,24 +203,21 @@ fn global_suffix_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'
mod tests {
use super::*;
use crate::parser::element_parser::element;
use crate::parser::parser_context::ContextElement;
use crate::parser::parser_context::ContextTree;
use crate::parser::parser_with_context::parser_with_context;
use crate::parser::source::Source;
#[test]
fn citation_simple() {
let input = "[cite:@foo]";
let input = OrgSource::new("[cite:@foo]");
let initial_context: ContextTree<'_, '_> = ContextTree::new();
let document_context =
initial_context.with_additional_node(ContextElement::DocumentRoot(input));
let paragraph_matcher = parser_with_context!(element(true))(&document_context);
let paragraph_matcher = parser_with_context!(element(true))(&initial_context);
let (remaining, first_paragraph) = paragraph_matcher(input).expect("Parse first paragraph");
let first_paragraph = match first_paragraph {
crate::parser::Element::Paragraph(paragraph) => paragraph,
_ => panic!("Should be a paragraph!"),
};
assert_eq!(remaining, "");
assert_eq!(Into::<&str>::into(remaining), "");
assert_eq!(first_paragraph.get_source(), "[cite:@foo]");
assert_eq!(first_paragraph.children.len(), 1);
assert_eq!(

View File

@@ -10,6 +10,7 @@ use nom::multi::many_till;
use nom::sequence::preceded;
use nom::sequence::tuple;
use super::org_source::OrgSource;
use super::Context;
use crate::error::CustomError;
use crate::error::Res;
@@ -28,21 +29,26 @@ use crate::parser::Object;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn citation_reference<'r, 's>(
context: Context<'r, 's>,
input: &'s str,
) -> Res<&'s str, CitationReference<'s>> {
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, CitationReference<'s>> {
let (remaining, _prefix) = opt(parser_with_context!(key_prefix)(context))(input)?;
let (remaining, _key) = parser_with_context!(citation_reference_key)(context)(remaining)?;
let (remaining, _suffix) = opt(parser_with_context!(key_suffix)(context))(remaining)?;
let source = get_consumed(input, remaining);
Ok((remaining, CitationReference { source }))
Ok((
remaining,
CitationReference {
source: source.into(),
},
))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn citation_reference_key<'r, 's>(
context: Context<'r, 's>,
input: &'s str,
) -> Res<&'s str, &'s str> {
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (remaining, source) = recognize(tuple((
tag("@"),
many1(verify(
@@ -59,7 +65,10 @@ pub fn citation_reference_key<'r, 's>(
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn key_prefix<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Vec<Object<'s>>> {
fn key_prefix<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Vec<Object<'s>>> {
// TODO: I could insert CitationBracket entries in the context after each matched object to reduce the scanning done for counting brackets which should be more efficient.
let parser_context = context
.with_additional_node(ContextElement::CitationBracket(CitationBracket {
@@ -67,7 +76,7 @@ fn key_prefix<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str,
depth: 0,
}))
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta,
class: ExitClass::Gamma,
exit_matcher: &key_prefix_end,
}));
let (remaining, (children, _exit_contents)) = verify(
@@ -81,7 +90,10 @@ fn key_prefix<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str,
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn key_suffix<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Vec<Object<'s>>> {
fn key_suffix<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Vec<Object<'s>>> {
// TODO: I could insert CitationBracket entries in the context after each matched object to reduce the scanning done for counting brackets which should be more efficient.
let parser_context = context
.with_additional_node(ContextElement::CitationBracket(CitationBracket {
@@ -89,7 +101,7 @@ fn key_suffix<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str,
depth: 0,
}))
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta,
class: ExitClass::Gamma,
exit_matcher: &key_suffix_end,
}));
let (remaining, (children, _exit_contents)) = verify(
@@ -114,12 +126,15 @@ pub fn get_bracket_depth<'r, 's>(context: Context<'r, 's>) -> Option<&'r Citatio
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn key_prefix_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
fn key_prefix_end<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
let context_depth = get_bracket_depth(context)
.expect("This function should only be called from inside a citation reference.");
let text_since_context_entry = get_consumed(context_depth.position, input);
let mut current_depth = context_depth.depth;
for c in text_since_context_entry.chars() {
for c in Into::<&str>::into(text_since_context_entry).chars() {
match c {
'[' => {
current_depth += 1;
@@ -134,7 +149,7 @@ fn key_prefix_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s s
}
}
if current_depth == 0 {
let close_bracket = tag::<&str, &str, CustomError<&str>>("]")(input);
let close_bracket = tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>("]")(input);
if close_bracket.is_ok() {
return close_bracket;
}
@@ -146,12 +161,15 @@ fn key_prefix_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s s
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn key_suffix_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
fn key_suffix_end<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
let context_depth = get_bracket_depth(context)
.expect("This function should only be called from inside a citation reference.");
let text_since_context_entry = get_consumed(context_depth.position, input);
let mut current_depth = context_depth.depth;
for c in text_since_context_entry.chars() {
for c in Into::<&str>::into(text_since_context_entry).chars() {
match c {
'[' => {
current_depth += 1;
@@ -166,7 +184,7 @@ fn key_suffix_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s s
}
}
if current_depth == 0 {
let close_bracket = tag::<&str, &str, CustomError<&str>>("]")(input);
let close_bracket = tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>("]")(input);
if close_bracket.is_ok() {
return close_bracket;
}

View File

@@ -11,6 +11,7 @@ use nom::combinator::recognize;
use nom::combinator::verify;
use nom::sequence::tuple;
use super::org_source::OrgSource;
use super::Context;
use crate::error::Res;
use crate::parser::parser_with_context::parser_with_context;
@@ -19,8 +20,11 @@ use crate::parser::util::start_of_line;
use crate::parser::Clock;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn clock<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Clock<'s>> {
start_of_line(context, input)?;
pub fn clock<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Clock<'s>> {
start_of_line(input)?;
let (remaining, _leading_whitespace) = space0(input)?;
let (remaining, _clock) = tag_no_case("clock:")(remaining)?;
let (remaining, _gap_whitespace) = space1(remaining)?;
@@ -31,14 +35,19 @@ pub fn clock<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, C
))(remaining)?;
let source = get_consumed(input, remaining);
Ok((remaining, Clock { source }))
Ok((
remaining,
Clock {
source: source.into(),
},
))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn inactive_timestamp_range_duration<'r, 's>(
context: Context<'r, 's>,
input: &'s str,
) -> Res<&'s str, &'s str> {
_context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(tuple((
tag("["),
is_not("\r\n]"),
@@ -50,14 +59,17 @@ fn inactive_timestamp_range_duration<'r, 's>(
space1,
digit1,
tag(":"),
verify(digit1, |mm: &str| mm.len() == 2),
verify(digit1, |mm: &OrgSource<'_>| mm.len() == 2),
space0,
alt((line_ending, eof)),
)))(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn inactive_timestamp<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
fn inactive_timestamp<'r, 's>(
_context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(tuple((
tag("["),
is_not("\r\n]"),

View File

@@ -11,6 +11,7 @@ use nom::multi::many0;
use nom::sequence::preceded;
use nom::sequence::tuple;
use super::org_source::OrgSource;
use super::util::get_consumed;
use super::Context;
use crate::error::CustomError;
@@ -24,10 +25,13 @@ use crate::parser::util::start_of_line;
use crate::parser::Comment;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn comment<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Comment<'s>> {
pub fn comment<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Comment<'s>> {
if immediate_in_section(context, "comment") {
return Err(nom::Err::Error(CustomError::MyError(MyError(
"Cannot nest objects of the same element",
"Cannot nest objects of the same element".into(),
))));
}
let parser_context = context.with_additional_node(ContextElement::Context("comment"));
@@ -38,12 +42,20 @@ pub fn comment<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str,
many0(preceded(not(exit_matcher), comment_line_matcher))(remaining)?;
let source = get_consumed(input, remaining);
Ok((remaining, Comment { source }))
Ok((
remaining,
Comment {
source: source.into(),
},
))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn comment_line<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
start_of_line(context, input)?;
fn comment_line<'r, 's>(
_context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
start_of_line(input)?;
let (remaining, _indent) = space0(input)?;
let (remaining, (_hash, _leading_whitespace_and_content, _line_ending)) = tuple((
tag("#"),
@@ -57,22 +69,21 @@ fn comment_line<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str
#[cfg(test)]
mod tests {
use super::*;
use crate::parser::parser_context::ContextElement;
use crate::parser::parser_context::ContextTree;
use crate::parser::parser_with_context::parser_with_context;
#[test]
fn require_space_after_hash() {
let input = "# Comment line
let input = OrgSource::new(
"# Comment line
#not a comment
# Comment again";
# Comment again",
);
let initial_context: ContextTree<'_, '_> = ContextTree::new();
let document_context =
initial_context.with_additional_node(ContextElement::DocumentRoot(input));
let comment_matcher = parser_with_context!(comment)(&document_context);
let comment_matcher = parser_with_context!(comment)(&initial_context);
let (remaining, first_comment) = comment_matcher(input).expect("Parse first comment");
assert_eq!(
remaining,
Into::<&str>::into(remaining),
r#"#not a comment
# Comment again"#
);

View File

@@ -6,6 +6,7 @@ use nom::combinator::eof;
use nom::combinator::recognize;
use nom::sequence::tuple;
use super::org_source::OrgSource;
use super::sexp::sexp;
use super::Context;
use crate::error::Res;
@@ -14,8 +15,11 @@ use crate::parser::util::start_of_line;
use crate::parser::DiarySexp;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn diary_sexp<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, DiarySexp<'s>> {
start_of_line(context, input)?;
pub fn diary_sexp<'r, 's>(
_context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, DiarySexp<'s>> {
start_of_line(input)?;
let (remaining, _leading_whitespace) = space0(input)?;
let (remaining, _clock) = tag("%%")(remaining)?;
let (remaining, _gap_whitespace) = space0(remaining)?;
@@ -24,5 +28,10 @@ pub fn diary_sexp<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s s
recognize(tuple((space0, alt((line_ending, eof)))))(remaining)?;
let source = get_consumed(input, remaining);
Ok((remaining, DiarySexp { source }))
Ok((
remaining,
DiarySexp {
source: source.into(),
},
))
}

View File

@@ -16,6 +16,8 @@ use nom::sequence::tuple;
use super::element::Element;
use super::object::Object;
use super::org_source::convert_error;
use super::org_source::OrgSource;
use super::parser_with_context::parser_with_context;
use super::source::Source;
use super::token::AllTokensIterator;
@@ -95,9 +97,10 @@ impl<'s> Source<'s> for Heading<'s> {
#[allow(dead_code)]
pub fn document(input: &str) -> Res<&str, Document> {
let initial_context: ContextTree<'_, '_> = ContextTree::new();
let document_context =
initial_context.with_additional_node(ContextElement::DocumentRoot(input));
let (remaining, document) = _document(&document_context, input)?;
let wrapped_input = OrgSource::new(input);
let (remaining, document) = _document(&initial_context, wrapped_input)
.map(|(rem, out)| (Into::<&str>::into(rem), out))
.map_err(convert_error)?;
{
// 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<'_>>> = document
@@ -113,17 +116,22 @@ pub fn document(input: &str) -> Res<&str, Document> {
.map(|rt| &rt.children)
.collect();
if !all_radio_targets.is_empty() {
let document_context = document_context
let initial_context = initial_context
.with_additional_node(ContextElement::RadioTarget(all_radio_targets));
let (remaining, document) = _document(&document_context, input)?;
return Ok((remaining, document));
let (remaining, document) = _document(&initial_context, wrapped_input)
.map(|(rem, out)| (Into::<&str>::into(rem), out))
.map_err(convert_error)?;
return Ok((remaining.into(), document));
}
}
Ok((remaining, document))
Ok((remaining.into(), document))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn _document<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Document<'s>> {
fn _document<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Document<'s>> {
let zeroth_section_matcher = parser_with_context!(zeroth_section)(context);
let heading_matcher = parser_with_context!(heading)(context);
let (remaining, _blank_lines) = many0(blank_line)(input)?;
@@ -133,7 +141,7 @@ pub fn _document<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s st
Ok((
remaining,
Document {
source,
source: source.into(),
zeroth_section,
children,
},
@@ -141,7 +149,10 @@ pub fn _document<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s st
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn zeroth_section<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Section<'s>> {
fn zeroth_section<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Section<'s>> {
// TODO: The zeroth section is specialized so it probably needs its own parser
let parser_context = context
.with_additional_node(ContextElement::ConsumeTrailingWhitespace(true))
@@ -182,11 +193,20 @@ fn zeroth_section<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s s
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((remaining, Section { source, children }))
Ok((
remaining,
Section {
source: source.into(),
children,
},
))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn section<'r, 's>(context: Context<'r, 's>, mut input: &'s str) -> Res<&'s str, Section<'s>> {
fn section<'r, 's>(
context: Context<'r, 's>,
mut input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Section<'s>> {
// TODO: The zeroth section is specialized so it probably needs its own parser
let parser_context = context
.with_additional_node(ContextElement::ConsumeTrailingWhitespace(true))
@@ -223,17 +243,29 @@ fn section<'r, 's>(context: Context<'r, 's>, mut input: &'s str) -> Res<&'s str,
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((remaining, Section { source, children }))
Ok((
remaining,
Section {
source: source.into(),
children,
},
))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn section_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
fn section_end<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
let headline_matcher = parser_with_context!(headline)(context);
recognize(headline_matcher)(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn heading<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Heading<'s>> {
fn heading<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Heading<'s>> {
not(|i| context.check_exit_matcher(i))(input)?;
let (remaining, (star_count, _ws, title)) = headline(context, input)?;
let section_matcher = parser_with_context!(section)(context);
@@ -249,7 +281,7 @@ fn heading<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Hea
Ok((
remaining,
Heading {
source,
source: source.into(),
stars: star_count,
title,
children,
@@ -260,18 +292,17 @@ fn heading<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Hea
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn headline<'r, 's>(
context: Context<'r, 's>,
input: &'s str,
) -> Res<&'s str, (usize, &'s str, Vec<Object<'s>>)> {
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, (usize, OrgSource<'s>, Vec<Object<'s>>)> {
let parser_context =
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Document,
exit_matcher: &headline_end,
}));
let standard_set_object_matcher = parser_with_context!(standard_set_object)(&parser_context);
let start_of_line_matcher = parser_with_context!(start_of_line)(&parser_context);
let (remaining, (_sol, star_count, ws, title, _line_ending)) = tuple((
start_of_line_matcher,
start_of_line,
many1_count(tag("*")),
space1,
many1(standard_set_object_matcher),
@@ -281,7 +312,10 @@ fn headline<'r, 's>(
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn headline_end<'r, 's>(_context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
fn headline_end<'r, 's>(
_context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
line_ending(input)
}

View File

@@ -10,6 +10,7 @@ use nom::combinator::recognize;
use nom::multi::many_till;
use nom::sequence::tuple;
use super::org_source::OrgSource;
use super::Context;
use crate::error::CustomError;
use crate::error::MyError;
@@ -31,13 +32,16 @@ use crate::parser::Element;
use crate::parser::Paragraph;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn drawer<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Drawer<'s>> {
pub fn drawer<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Drawer<'s>> {
if immediate_in_section(context, "drawer") {
return Err(nom::Err::Error(CustomError::MyError(MyError(
"Cannot nest objects of the same element",
"Cannot nest objects of the same element".into(),
))));
}
start_of_line(context, input)?;
start_of_line(input)?;
let (remaining, _leading_whitespace) = space0(input)?;
let (remaining, (_open_colon, drawer_name, _close_colon, _new_line)) = tuple((
tag(":"),
@@ -63,9 +67,9 @@ pub fn drawer<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str,
))(remaining)
{
Ok((remain, (_not_immediate_exit, first_line, (_trailing_whitespace, _exit_contents)))) => {
let mut element = Element::Paragraph(Paragraph::of_text(first_line));
let mut element = Element::Paragraph(Paragraph::of_text(first_line.into()));
let source = get_consumed(remaining, remain);
element.set_source(source);
element.set_source(source.into());
(remain, vec![element])
}
Err(_) => {
@@ -81,21 +85,24 @@ pub fn drawer<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str,
Ok((
remaining,
Drawer {
source,
name: drawer_name,
source: source.into(),
name: drawer_name.into(),
children,
},
))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn name<'s>(input: &'s str) -> Res<&'s str, &'s str> {
fn name<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
take_while(|c| WORD_CONSTITUENT_CHARACTERS.contains(c) || "-_".contains(c))(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn drawer_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
start_of_line(context, input)?;
fn drawer_end<'r, 's>(
_context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
start_of_line(input)?;
recognize(tuple((
space0,
tag_no_case(":end:"),

View File

@@ -11,6 +11,7 @@ use nom::combinator::recognize;
use nom::multi::many_till;
use nom::sequence::tuple;
use super::org_source::OrgSource;
use super::Context;
use crate::error::CustomError;
use crate::error::MyError;
@@ -33,15 +34,15 @@ use crate::parser::Element;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn dynamic_block<'r, 's>(
context: Context<'r, 's>,
input: &'s str,
) -> Res<&'s str, DynamicBlock<'s>> {
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, DynamicBlock<'s>> {
// TODO: Do I need to differentiate between different dynamic block types.
if immediate_in_section(context, "dynamic block") {
return Err(nom::Err::Error(CustomError::MyError(MyError(
"Cannot nest objects of the same element",
"Cannot nest objects of the same element".into(),
))));
}
start_of_line(context, input)?;
start_of_line(input)?;
let (remaining, _leading_whitespace) = space0(input)?;
let (remaining, (_begin, name, parameters, _ws)) = tuple((
recognize(tuple((tag_no_case("#+begin:"), space1))),
@@ -69,9 +70,9 @@ pub fn dynamic_block<'r, 's>(
))(remaining)
{
Ok((remain, (_not_immediate_exit, first_line, (_trailing_whitespace, _exit_contents)))) => {
let mut element = Element::Paragraph(Paragraph::of_text(first_line));
let mut element = Element::Paragraph(Paragraph::of_text(first_line.into()));
let source = get_consumed(remaining, remain);
element.set_source(source);
element.set_source(source.into());
(remain, vec![element])
}
Err(_) => {
@@ -86,27 +87,30 @@ pub fn dynamic_block<'r, 's>(
Ok((
remaining,
DynamicBlock {
source,
name,
parameters,
source: source.into(),
name: name.into(),
parameters: parameters.map(|val| val.into()),
children,
},
))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn name<'s>(input: &'s str) -> Res<&'s str, &'s str> {
fn name<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
is_not(" \t\r\n")(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn parameters<'s>(input: &'s str) -> Res<&'s str, &'s str> {
fn parameters<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
is_not("\r\n")(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn dynamic_block_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
start_of_line(context, input)?;
fn dynamic_block_end<'r, 's>(
_context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
start_of_line(input)?;
let (remaining, source) = recognize(tuple((
space0,
tag_no_case("#+end:"),

View File

@@ -19,28 +19,32 @@ use super::lesser_block::example_block;
use super::lesser_block::export_block;
use super::lesser_block::src_block;
use super::lesser_block::verse_block;
use super::org_source::OrgSource;
use super::paragraph::paragraph;
use super::plain_list::detect_plain_list;
use super::plain_list::plain_list;
use super::source::SetSource;
use super::util::get_consumed;
use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
use super::Context;
use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res;
use crate::parser::parser_with_context::parser_with_context;
use crate::parser::table::org_mode_table;
pub fn element(
pub const fn element(
can_be_paragraph: bool,
) -> impl for<'r, 's> Fn(Context<'r, 's>, &'s str) -> Res<&'s str, Element<'s>> {
move |context: Context, input: &str| _element(context, input, can_be_paragraph)
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, Element<'s>> {
move |context: Context, input: OrgSource<'_>| _element(context, input, can_be_paragraph)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn _element<'r, 's>(
context: Context<'r, 's>,
input: &'s str,
input: OrgSource<'s>,
can_be_paragraph: bool,
) -> Res<&'s str, Element<'s>> {
) -> Res<OrgSource<'s>, Element<'s>> {
let plain_list_matcher = parser_with_context!(plain_list)(context);
let greater_block_matcher = parser_with_context!(greater_block)(context);
let dynamic_block_matcher = parser_with_context!(dynamic_block)(context);
@@ -103,7 +107,30 @@ fn _element<'r, 's>(
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
element.set_source(source);
element.set_source(source.into());
Ok((remaining, element))
}
pub const fn detect_element(
can_be_paragraph: bool,
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
move |context: Context, input: OrgSource<'_>| _detect_element(context, input, can_be_paragraph)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn _detect_element<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
can_be_paragraph: bool,
) -> Res<OrgSource<'s>, ()> {
if detect_plain_list(context, input).is_ok() {
return Ok((input, ()));
}
if _element(context, input, can_be_paragraph).is_ok() {
return Ok((input, ()));
}
return Err(nom::Err::Error(CustomError::MyError(MyError(
"No element detected.".into(),
))));
}

View File

@@ -7,6 +7,7 @@ use nom::combinator::eof;
use nom::combinator::peek;
use nom::combinator::recognize;
use super::org_source::OrgSource;
use super::Context;
use crate::error::Res;
use crate::parser::object::Entity;
@@ -14,7 +15,10 @@ use crate::parser::parser_with_context::parser_with_context;
use crate::parser::util::get_consumed;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn entity<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Entity<'s>> {
pub fn entity<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Entity<'s>> {
let (remaining, _) = tag("\\")(input)?;
let (remaining, entity_name) = name(context, remaining)?;
let (remaining, _) = alt((
@@ -27,14 +31,17 @@ pub fn entity<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str,
Ok((
remaining,
Entity {
source,
entity_name,
source: source.into(),
entity_name: entity_name.into(),
},
))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn name<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
fn name<'r, 's>(
_context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
// TODO: This should be defined by org-entities and optionally org-entities-user
// TODO: Add the rest of the entities, this is a very incomplete list
@@ -43,7 +50,7 @@ fn name<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s st
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn entity_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, ()> {
fn entity_end<'r, 's>(_context: Context<'r, 's>, input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
let (remaining, _) = alt((eof, recognize(satisfy(|c| !c.is_alphabetic()))))(input)?;
Ok((remaining, ()))

View File

@@ -8,6 +8,9 @@ pub enum ExitClass {
/// Elements who cede priority to alpha elements when matching.
Beta = 300,
/// Elements who cede priority to alpha and beta elements when matching.
Gamma = 4000,
}
impl std::fmt::Display for ExitClass {

View File

@@ -7,6 +7,7 @@ use nom::multi::many1;
use nom::multi::many_till;
use nom::sequence::tuple;
use super::org_source::OrgSource;
use super::Context;
use crate::error::Res;
use crate::parser::exiting::ExitClass;
@@ -20,13 +21,13 @@ use crate::parser::ExportSnippet;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn export_snippet<'r, 's>(
context: Context<'r, 's>,
input: &'s str,
) -> Res<&'s str, ExportSnippet<'s>> {
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ExportSnippet<'s>> {
let (remaining, _) = tag("@@")(input)?;
let (remaining, backend_name) = backend(context, remaining)?;
let parser_context =
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta,
class: ExitClass::Gamma,
exit_matcher: &export_snippet_end,
}));
let (remaining, backend_contents) = opt(tuple((
@@ -38,15 +39,18 @@ pub fn export_snippet<'r, 's>(
Ok((
remaining,
ExportSnippet {
source,
backend: backend_name,
contents: backend_contents.map(|(_colon, backend_contents)| backend_contents),
source: source.into(),
backend: backend_name.into(),
contents: backend_contents.map(|(_colon, backend_contents)| backend_contents.into()),
},
))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn backend<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
fn backend<'r, 's>(
_context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (remaining, backend_name) =
recognize(many1(verify(anychar, |c| c.is_alphanumeric() || *c == '-')))(input)?;
@@ -54,7 +58,10 @@ fn backend<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn contents<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
fn contents<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (remaining, source) = recognize(verify(
many_till(anychar, parser_with_context!(exit_matcher_parser)(context)),
|(children, _exit_contents)| !children.is_empty(),
@@ -63,6 +70,9 @@ fn contents<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn export_snippet_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
fn export_snippet_end<'r, 's>(
_context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
tag("@@")(input)
}

View File

@@ -11,6 +11,7 @@ use nom::multi::many0;
use nom::sequence::preceded;
use nom::sequence::tuple;
use super::org_source::OrgSource;
use super::Context;
use crate::error::Res;
use crate::parser::parser_with_context::parser_with_context;
@@ -22,8 +23,8 @@ use crate::parser::FixedWidthArea;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn fixed_width_area<'r, 's>(
context: Context<'r, 's>,
input: &'s str,
) -> Res<&'s str, FixedWidthArea<'s>> {
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, FixedWidthArea<'s>> {
let fixed_width_area_line_matcher = parser_with_context!(fixed_width_area_line)(context);
let exit_matcher = parser_with_context!(exit_matcher_parser)(context);
let (remaining, _first_line) = fixed_width_area_line_matcher(input)?;
@@ -31,15 +32,20 @@ pub fn fixed_width_area<'r, 's>(
many0(preceded(not(exit_matcher), fixed_width_area_line_matcher))(remaining)?;
let source = get_consumed(input, remaining);
Ok((remaining, FixedWidthArea { source }))
Ok((
remaining,
FixedWidthArea {
source: source.into(),
},
))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn fixed_width_area_line<'r, 's>(
context: Context<'r, 's>,
input: &'s str,
) -> Res<&'s str, &'s str> {
start_of_line(context, input)?;
_context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
start_of_line(input)?;
let (remaining, _indent) = space0(input)?;
let (remaining, (_hash, _leading_whitespace_and_content, _line_ending)) = tuple((
tag(":"),

View File

@@ -10,6 +10,7 @@ use nom::multi::many1;
use nom::multi::many_till;
use nom::sequence::tuple;
use super::org_source::OrgSource;
use super::util::WORD_CONSTITUENT_CHARACTERS;
use super::Context;
use crate::error::CustomError;
@@ -31,14 +32,14 @@ use crate::parser::util::start_of_line;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn footnote_definition<'r, 's>(
context: Context<'r, 's>,
input: &'s str,
) -> Res<&'s str, FootnoteDefinition<'s>> {
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, FootnoteDefinition<'s>> {
if immediate_in_section(context, "footnote definition") {
return Err(nom::Err::Error(CustomError::MyError(MyError(
"Cannot nest objects of the same element",
"Cannot nest objects of the same element".into(),
))));
}
start_of_line(context, input)?;
start_of_line(input)?;
// Cannot be indented.
let (remaining, (_lead_in, lbl, _lead_out, _ws)) =
tuple((tag_no_case("[fn:"), label, tag("]"), space0))(input)?;
@@ -59,15 +60,15 @@ pub fn footnote_definition<'r, 's>(
Ok((
remaining,
FootnoteDefinition {
source,
label: lbl,
source: source.into(),
label: lbl.into(),
children,
},
))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn label<'s>(input: &'s str) -> Res<&'s str, &'s str> {
pub fn label<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
alt((
digit1,
take_while(|c| WORD_CONSTITUENT_CHARACTERS.contains(c) || "-_".contains(c)),
@@ -77,9 +78,8 @@ pub fn label<'s>(input: &'s str) -> Res<&'s str, &'s str> {
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn footnote_definition_end<'r, 's>(
context: Context<'r, 's>,
input: &'s str,
) -> Res<&'s str, &'s str> {
let start_of_line_matcher = parser_with_context!(start_of_line)(context);
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
let allow_nesting_context =
context.with_additional_node(ContextElement::Context("allow nesting footnotes"));
let footnote_definition_matcher = parser_with_context!(footnote_definition)(
@@ -97,8 +97,10 @@ fn footnote_definition_end<'r, 's>(
footnote_definition_matcher,
))),
recognize(tuple((
start_of_line_matcher,
verify(many1(blank_line), |lines: &Vec<&str>| lines.len() >= 2),
start_of_line,
verify(many1(blank_line), |lines: &Vec<OrgSource<'_>>| {
lines.len() >= 2
}),
))),
))(input)?;
@@ -108,27 +110,26 @@ fn footnote_definition_end<'r, 's>(
#[cfg(test)]
mod tests {
use super::*;
use crate::parser::parser_context::ContextElement;
use crate::parser::parser_context::ContextTree;
use crate::parser::parser_with_context::parser_with_context;
use crate::parser::Source;
#[test]
fn two_paragraphs() {
let input = "[fn:1] A footnote.
let input = OrgSource::new(
"[fn:1] A footnote.
[fn:2] A multi-
line footnote.";
line footnote.",
);
let initial_context: ContextTree<'_, '_> = ContextTree::new();
let document_context =
initial_context.with_additional_node(ContextElement::DocumentRoot(input));
let footnote_definition_matcher = parser_with_context!(element(true))(&document_context);
let footnote_definition_matcher = parser_with_context!(element(true))(&initial_context);
let (remaining, first_footnote_definition) =
footnote_definition_matcher(input).expect("Parse first footnote_definition");
let (remaining, second_footnote_definition) =
footnote_definition_matcher(remaining).expect("Parse second footnote_definition.");
assert_eq!(remaining, "");
assert_eq!(Into::<&str>::into(remaining), "");
assert_eq!(
first_footnote_definition.get_source(),
"[fn:1] A footnote.
@@ -145,19 +146,19 @@ line footnote."
#[test]
fn multiline_break() {
let input = "[fn:2] A multi-
let input = OrgSource::new(
"[fn:2] A multi-
line footnote.
not in the footnote.";
not in the footnote.",
);
let initial_context: ContextTree<'_, '_> = ContextTree::new();
let document_context =
initial_context.with_additional_node(ContextElement::DocumentRoot(input));
let footnote_definition_matcher = parser_with_context!(element(true))(&document_context);
let footnote_definition_matcher = parser_with_context!(element(true))(&initial_context);
let (remaining, first_footnote_definition) =
footnote_definition_matcher(input).expect("Parse first footnote_definition");
assert_eq!(remaining, "not in the footnote.");
assert_eq!(Into::<&str>::into(remaining), "not in the footnote.");
assert_eq!(
first_footnote_definition.get_source(),
"[fn:2] A multi-

View File

@@ -5,6 +5,7 @@ use nom::character::complete::space0;
use nom::combinator::verify;
use nom::multi::many_till;
use super::org_source::OrgSource;
use super::parser_context::ContextElement;
use super::Context;
use crate::error::CustomError;
@@ -19,13 +20,12 @@ use crate::parser::parser_with_context::parser_with_context;
use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed;
use crate::parser::FootnoteReference;
use crate::parser::Object;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn footnote_reference<'r, 's>(
context: Context<'r, 's>,
input: &'s str,
) -> Res<&'s str, FootnoteReference<'s>> {
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, FootnoteReference<'s>> {
alt((
parser_with_context!(anonymous_footnote)(context),
parser_with_context!(footnote_reference_only)(context),
@@ -36,8 +36,8 @@ pub fn footnote_reference<'r, 's>(
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn anonymous_footnote<'r, 's>(
context: Context<'r, 's>,
input: &'s str,
) -> Res<&'s str, FootnoteReference<'s>> {
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, FootnoteReference<'s>> {
let (remaining, _) = tag_no_case("[fn::")(input)?;
let parser_context = context
.with_additional_node(ContextElement::FootnoteReferenceDefinition(
@@ -65,7 +65,7 @@ fn anonymous_footnote<'r, 's>(
Ok((
remaining,
FootnoteReference {
source,
source: source.into(),
label: None,
definition: children,
},
@@ -75,8 +75,8 @@ fn anonymous_footnote<'r, 's>(
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn inline_footnote<'r, 's>(
context: Context<'r, 's>,
input: &'s str,
) -> Res<&'s str, FootnoteReference<'s>> {
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, FootnoteReference<'s>> {
let (remaining, _) = tag_no_case("[fn:")(input)?;
let (remaining, label_contents) = label(remaining)?;
let (remaining, _) = tag(":")(remaining)?;
@@ -106,8 +106,8 @@ fn inline_footnote<'r, 's>(
Ok((
remaining,
FootnoteReference {
source,
label: Some(label_contents),
source: source.into(),
label: Some(label_contents.into()),
definition: children,
},
))
@@ -115,9 +115,9 @@ fn inline_footnote<'r, 's>(
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn footnote_reference_only<'r, 's>(
context: Context<'r, 's>,
input: &'s str,
) -> Res<&'s str, FootnoteReference<'s>> {
_context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, FootnoteReference<'s>> {
let (remaining, _) = tag_no_case("[fn:")(input)?;
let (remaining, label_contents) = label(remaining)?;
let (remaining, _) = tag("]")(remaining)?;
@@ -126,28 +126,23 @@ fn footnote_reference_only<'r, 's>(
Ok((
remaining,
FootnoteReference {
source,
label: Some(label_contents),
source: source.into(),
label: Some(label_contents.into()),
definition: Vec::with_capacity(0),
},
))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn definition<'s>(input: &'s str) -> Res<&'s str, Vec<Object<'s>>> {
Ok((input, vec![]))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn footnote_definition_end<'r, 's>(
context: Context<'r, 's>,
input: &'s str,
) -> Res<&'s str, &'s str> {
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
let context_depth = get_bracket_depth(context)
.expect("This function should only be called from inside a footnote definition.");
let text_since_context_entry = get_consumed(context_depth.position, input);
let mut current_depth = context_depth.depth;
for c in text_since_context_entry.chars() {
for c in Into::<&str>::into(text_since_context_entry).chars() {
match c {
'[' => {
current_depth += 1;
@@ -164,7 +159,7 @@ fn footnote_definition_end<'r, 's>(
if current_depth > 0 {
// 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(
"NoFootnoteReferenceDefinitionEnd",
"NoFootnoteReferenceDefinitionEnd".into(),
))));
}
tag("]")(input)

Some files were not shown because too many files have changed in this diff Show More